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