lib/api_resource/associations.rb in api_resource-0.6.18 vs lib/api_resource/associations.rb in api_resource-0.6.19

- old
+ new

@@ -1,10 +1,10 @@ require 'active_support' require 'active_support/string_inquirer' module ApiResource - + module Associations #TODO: many of these methods should also force loading of the resource definition extend ActiveSupport::Concern extend ActiveSupport::Autoload @@ -18,14 +18,14 @@ autoload :MultiObjectProxy autoload :RelatedObjectHash autoload :SingleObjectProxy included do - + unless self.ancestors.include?(ApiResource::AssociationActivation) raise Exception.new( - "Can't include Associations without AssociationActivation" + "Can't include Associations without AssociationActivation" ) end class_attribute :related_objects attr_accessor :assoc_attributes @@ -40,20 +40,20 @@ # until after this module in included/extended, so it's in its own # little mini module extend InheritedMethod self.define_association_methods - + end # module methods to include the proper associations in various libraries - this is usually loaded in Railties def self.activate_active_record ActiveRecord::Base.class_eval do include ApiResource::AssociationActivation self.activate_associations( - :has_many_remote => :has_many_remote, - :belongs_to_remote => :belongs_to_remote, + :has_many_remote => :has_many_remote, + :belongs_to_remote => :belongs_to_remote, :has_one_remote => :has_one_remote, ) end end @@ -69,41 +69,41 @@ super(descendant) end end module ClassMethods - + # Define the methods for creating and testing for associations, unfortunately # scopes are different enough to require different methods :( def define_association_methods self.association_types.each_key do |assoc| self.instance_eval <<-EOE, __FILE__, __LINE__ + 1 def #{assoc}(*args) options = args.extract_options! options = options.with_indifferent_access # Raise an error if we have multiple args and options raise "Invalid arguments to #{assoc}" unless options.blank? || args.length == 1 - args.each do |arg| + args.each do |arg| klass_name = (options[:class_name] ? options[:class_name].to_s.classify : arg.to_s.classify) # add this to any descendants - the other methods etc are handled by inheritance ([self] + self.descendants).each do |klass| #We need to merge upon itself to generate a new object since the children all share their related objects with each other klass.related_objects = klass.related_objects.merge(:#{assoc} => klass.related_objects[:#{assoc}].merge(arg.to_sym => klass_name)) end # We need to define reader and writer methods here define_association_as_attribute(:#{assoc}, arg, options) end end - + def #{assoc}?(name) return self.related_objects[:#{assoc}][name.to_s.pluralize.to_sym].present? || self.related_objects[:#{assoc}][name.to_s.singularize.to_sym].present? end - + def #{assoc}_class_name(name) raise "No such" + :#{assoc}.to_s + " association on #{name}" unless self.#{assoc}?(name) return self.find_namespaced_class_name(self.related_objects[:#{assoc}][name.to_sym]) - end + end EOE # For convenience we will define the methods for testing for the existence of an association # and getting the class for an association as instance methods too to avoid tons of self.class calls self.class_eval <<-EOE, __FILE__, __LINE__ + 1 @@ -115,38 +115,38 @@ return self.class.#{assoc}_class_name(name) end EOE end end - + def association?(assoc) self.related_objects.any? do |key, value| next if key.to_s == "scopes" value.detect { |k,v| k.to_sym == assoc.to_sym } end end - + def association_names # structure is {:has_many => {"myname" => "ClassName"}} self.related_objects.clone.delete_if{|k,v| k.to_s == "scopes"}.collect{|k,v| v.keys.collect(&:to_sym)}.flatten end def association_class(assoc) self.association_class_name(assoc).constantize end - + def association_class_name(assoc) raise ArgumentError, "#{assoc} is not a valid association of #{self}" unless self.association?(assoc) result = self.related_objects.detect do |key,value| ret = value.detect{|k,v| k.to_sym == assoc.to_sym } return self.find_namespaced_class_name(ret[1]) if ret end end # TODO: add a special foreign_key option to associations def association_foreign_key_field(assoc, type = nil) - + if type.nil? && has_many?(assoc) type = :has_many else type = type.to_s.to_sym end @@ -158,11 +158,11 @@ str = str.pluralize end str.to_sym end - + protected def clear_related_objects # Hash to hold onto the definitions of the related objects self.related_objects = RelatedObjectHash.new @@ -182,10 +182,26 @@ end # TODO :Remove scopes from related_objects. self.related_objects[:scopes] = self.related_objects[:scopes].clone end + # + # Wrapper method to define all associations from the + # resource definition + # + # @return [Boolean] true + def define_all_associations + if self.resource_definition["associations"] + self.resource_definition["associations"].each_pair do |key, hash| + hash.each_pair do |assoc_name, assoc_options| + self.send(key, assoc_name, assoc_options) + end + end + end + true + end + def define_association_as_attribute(assoc_type, assoc_name, opts) opts.stringify_keys! id_method_name = association_foreign_key_field( assoc_name, assoc_type @@ -196,11 +212,11 @@ # TODO: change this if self.ancestors.include?(ApiResource::Base) define_attribute_method(assoc_name) define_attribute_method(id_method_name) end - + # a module to contain our generated methods cattr_accessor :api_resource_generated_methods self.api_resource_generated_methods = Module.new # include our anonymous module include self.api_resource_generated_methods @@ -214,11 +230,11 @@ opts["class_name"] = self.find_namespaced_class_name( opts["class_name"] || assoc_name.to_s.classify ) klass.define_association_as_attribute(self, assoc_name, opts) end - + def find_namespaced_class_name(klass) # return the name if it is itself namespaced return klass if klass =~ /::/ ancestors = self.name.split("::") if ancestors.size > 1 @@ -231,28 +247,28 @@ end end return klass end - + end - + def association?(assoc) self.class.association?(assoc) end - + def association_class(assoc) self.class.association_class(assoc) end def association_class_name(assoc) self.class.association_class_name(assoc) end - + # list of all association names def association_names self.class.association_names end end - + end