sdks/other/ruby-on-rails/lib/usergrid_ironhorse/base.rb (205 lines of code) (raw):
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
require 'active_record/validations'
require 'active_record/errors'
require 'active_record/callbacks'
module Usergrid
module Ironhorse
class Base
include ActiveModel::AttributeMethods
include ActiveModel::Conversion
include ActiveModel::Validations
include ActiveModel::Dirty
include ActiveModel::Serialization
include ActiveModel::MassAssignmentSecurity
extend ActiveModel::Naming
extend ActiveModel::Callbacks
RESERVED_ATTRIBUTES = %w(metadata created modified uuid type uri)
define_model_callbacks :create, :destroy, :save, :update
# todo: determine the subset to support...
# unsupported: :group, :joins, :preload, :eager_load, :includes, :from, :lock,
# :having, :create_with, :uniq, :references, :none, :count,
# :average, :minimum, :maximum, :sum, :calculate, :ids
# :find_each, :find_in_batches, :offset, :readonly
#delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :to => :all
#delegate :first_or_create, :first_or_create!, :first_or_initialize, :to => :all
#delegate :find_by, :find_by!, :to => :all
#delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, :to => :all
#delegate :find_each, :find_in_batches, :to => :all
#delegate :select, :group, :order, :except, :reorder, :limit, :offset,
# :where, :preload, :eager_load, :includes, :from, :lock, :readonly,
# :having, :create_with, :uniq, :references, :none, :to => :all
#delegate :count, :average, :minimum, :maximum, :sum, :calculate, :pluck, :ids, :to => :all
@@settings ||= nil
attr_accessor :attributes
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
RecordNotSaved = ActiveRecord::RecordNotSaved
def initialize(attrs=nil)
@attributes = HashWithIndifferentAccess.new
assign_attributes attrs if attrs
end
def self.configure!(application_url, auth_token)
@@settings = HashWithIndifferentAccess.new application_url: application_url, auth_token: auth_token
end
def self.settings
return @@settings if @@settings
path = "config/usergrid.yml"
environment = defined?(Rails) && Rails.respond_to?(:env) ? Rails.env : ENV['RACK_ENV']
@@settings = HashWithIndifferentAccess.new YAML.load(ERB.new(File.new(path).read).result)[environment]
end
# forward to all
def self.method_missing(method, *args, &block)
all.send method, *args, &block
end
# forward to all
def method_missing(method, *args, &block)
if args.size == 0
attributes[method]
elsif args.size == 1 && method[-1] == '='
attr = method[0..-2]
if attributes[attr] != args[0]
attribute_will_change!(attr)
attributes[attr] = args[0]
end
else
all.send method, *args, &block
end
end
# todo: scopes
def self.all
unscoped
end
#def self.scope(symbol, scope)
# @scopes[symbol] = scope
#end
#def self.current_scope
# @current_scope ||= default_scope
#end
#
#def self.current_scope=(scope)
# @current_scope = scope
#end
#
#def self.default_scope
# @default_scope ||= unscoped
#end
def self.unscoped
Query.new(self)
end
def self.create(attributes=nil, options=nil, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| create(attr, options, &block) }
else
object = new(attributes, &block)
object.save
object
end
end
def self.create!(attributes=nil, options=nil, &block)
if attributes.is_a?(Array)
attributes.collect {|attr| create!(attr, options, &block)}
else
object = new(attributes)
yield(object) if block_given?
object.save!
object
end
end
def self.class_attributes
@class_attributes ||= {}
end
# Returns true if the record is persisted, i.e. it's not a new record and it was
# not destroyed, otherwise returns false.
def persisted?
!(new_record? || destroyed?)
end
def new_record?
!self.uuid
end
def self.group
model_name.plural.downcase
end
# Creates a Usergrid::Resource
def self.resource
app = Usergrid::Application.new settings[:application_url]
#app.auth_token = Thread.current[:usergrid_auth_token]
app[group]
end
# Saves the model.
#
# If the model is new a record gets created in the database, otherwise
# the existing record gets updated.
#
# By default, save always run validations. If any of them fail the action
# is cancelled and +save+ returns +false+. However, if you supply
# :validate => false, validations are bypassed altogether. See
# ActiveRecord::Validations for more information.
#
# There's a series of callbacks associated with +save+. If any of the
# <tt>before_*</tt> callbacks return +false+ the action is cancelled and
# +save+ returns +false+. See ActiveRecord::Callbacks for further
# details.
def save
begin
create_or_update
rescue ActiveRecord::RecordInvalid
false
end
end
# Saves the model.
#
# If the model is new a record gets created in the database, otherwise
# the existing record gets updated.
#
# With <tt>save!</tt> validations always run. If any of them fail
# ActiveRecord::RecordInvalid gets raised. See ActiveRecord::Validations
# for more information.
#
# There's a series of callbacks associated with <tt>save!</tt>. If any of
# the <tt>before_*</tt> callbacks return +false+ the action is cancelled
# and <tt>save!</tt> raises ActiveRecord::RecordNotSaved. See
# ActiveRecord::Callbacks for further details.
def save!
create_or_update or raise RecordNotSaved
end
# Deletes the record in the database and freezes this instance to
# reflect that no changes should be made (since they can't be
# persisted). Returns the frozen instance.
#
# The row is simply removed with a +DELETE+ statement on the
# record's primary key, and no callbacks are executed.
#
# To enforce the object's +before_destroy+ and +after_destroy+
# callbacks, Observer methods, or any <tt>:dependent</tt> association
# options, use <tt>#destroy</tt>.
def delete
self.class.delete(id) if persisted?
@destroyed = true
freeze
end
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
#
# There's a series of callbacks associated with <tt>destroy</tt>. If
# the <tt>before_destroy</tt> callback return +false+ the action is cancelled
# and <tt>destroy</tt> returns +false+. See
# ActiveRecord::Callbacks for further details.
def destroy
raise ReadOnlyRecord if readonly?
# todo: callbacks?
instance_resource.delete if persisted?
@destroyed = true
freeze
end
# Deletes the record in the database and freezes this instance to reflect
# that no changes should be made (since they can't be persisted).
#
# There's a series of callbacks associated with <tt>destroy!</tt>. If
# the <tt>before_destroy</tt> callback return +false+ the action is cancelled
# and <tt>destroy!</tt> raises ActiveRecord::RecordNotDestroyed. See
# ActiveRecord::Callbacks for further details.
def destroy!
destroy || raise(ActiveRecord::RecordNotDestroyed)
end
# Returns true if this object has been destroyed, otherwise returns false.
def destroyed?
!!@destroyed
end
# Reloads the attributes of this object from the database.
def reload
return false if !persisted?
fresh_object = self.class.find(id)
refresh_data fresh_object.instance_variable_get('@attributes')
self
end
# Updates the attributes of the model from the passed-in hash and saves the
# record, all wrapped in a transaction. If the object is invalid, the saving
# will fail and false will be returned.
def update_attributes(attributes)
assign_attributes attributes
save
end
# Updates its receiver just like +update_attributes+ but calls <tt>save!</tt> instead
# of +save+, so an exception is raised if the record is invalid.
def update_attributes!(attributes)
assign_attributes attributes
save!
end
# Note that whenever you include ActiveModel::AttributeMethods in your class,
# it requires you to implement an +attributes+ method which returns a hash
# with each attribute name in your model as hash key and the attribute value as
# hash value.
#
# Hash keys must be strings.
def attributes
@attributes ||= self.class.class_attributes.clone
end
def id; self.uuid end
def created_at; self.created end
def updated_at; self.modified end
protected
def assign_attributes(attrs)
attrs = sanitize_for_mass_assignment(attrs)
attrs.each do |attr,value|
attr = attr.to_s
unless attributes[attr] == value
attribute_will_change!(attr) unless RESERVED_ATTRIBUTES.include? attr
attributes[attr] = value
end
end
end
def create_or_update
raise ReadOnlyRecord if readonly?
if valid?
run_callbacks :save do
return new_record? ? do_create : do_update
end
end
false
end
def do_create
group_resource.post(unsaved_attributes) do |resp, req, res, &block|
if resp.code.to_s == "200" || resp.code.to_s == "201"
refresh_data resp.entity_data
return true
else
errors.add(resp.code.to_s, resp)
return false
end
end
end
def do_update
return false unless changed?
instance_resource.put(unsaved_attributes) do |resp, req, res, &block|
if resp.code.to_s == "200" || resp.code.to_s == "201"
refresh_data resp.entity_data
return true
else
errors.add(resp.code, resp)
return false
end
end
end
def unsaved_attributes
HashWithIndifferentAccess[changed.collect {|k| [k, attributes[k]]}]
end
def group_resource
self.class.resource
end
def instance_resource
self.class.resource["#{self.id}"]
end
def refresh_data(entity_data)
@previously_changed = changes
@changed_attributes.clear
@attributes = HashWithIndifferentAccess.new entity_data
end
def attribute_will_change!(attr)
begin
value = __send__(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
end
changed_attributes[attr] = value unless changed_attributes.include?(attr)
end
end
end
end