X-Git-Url: https://git.njae.me.uk/?a=blobdiff_plain;f=vendor%2Frails%2Factiverecord%2Flib%2Factive_record%2Fassociations%2Fassociation_proxy.rb;fp=vendor%2Frails%2Factiverecord%2Flib%2Factive_record%2Fassociations%2Fassociation_proxy.rb;h=d1a79df6e6853828c64d473e2c60892ac2cdca7e;hb=d115f2e23823271635bad69229a42cd8ac68debe;hp=0000000000000000000000000000000000000000;hpb=37cb670bf3ddde90b214e591f100ed4446469484;p=depot.git diff --git a/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb b/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb new file mode 100644 index 0000000..d1a79df --- /dev/null +++ b/vendor/rails/activerecord/lib/active_record/associations/association_proxy.rb @@ -0,0 +1,272 @@ +module ActiveRecord + module Associations + # This is the root class of all association proxies: + # + # AssociationProxy + # BelongsToAssociation + # HasOneAssociation + # BelongsToPolymorphicAssociation + # AssociationCollection + # HasAndBelongsToManyAssociation + # HasManyAssociation + # HasManyThroughAssociation + # HasOneThroughAssociation + # + # Association proxies in Active Record are middlemen between the object that + # holds the association, known as the @owner, and the actual associated + # object, known as the @target. The kind of association any proxy is + # about is available in @reflection. That's an instance of the class + # ActiveRecord::Reflection::AssociationReflection. + # + # For example, given + # + # class Blog < ActiveRecord::Base + # has_many :posts + # end + # + # blog = Blog.find(:first) + # + # the association proxy in blog.posts has the object in +blog+ as + # @owner, the collection of its posts as @target, and + # the @reflection object represents a :has_many macro. + # + # This class has most of the basic instance methods removed, and delegates + # unknown methods to @target via method_missing. As a + # corner case, it even removes the +class+ method and that's why you get + # + # blog.posts.class # => Array + # + # though the object behind blog.posts is not an Array, but an + # ActiveRecord::Associations::HasManyAssociation. + # + # The @target object is not \loaded until needed. For example, + # + # blog.posts.count + # + # is computed directly through SQL and does not trigger by itself the + # instantiation of the actual post records. + class AssociationProxy #:nodoc: + alias_method :proxy_respond_to?, :respond_to? + alias_method :proxy_extend, :extend + delegate :to_param, :to => :proxy_target + instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ } + + def initialize(owner, reflection) + @owner, @reflection = owner, reflection + Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) } + reset + end + + # Returns the owner of the proxy. + def proxy_owner + @owner + end + + # Returns the reflection object that represents the association handled + # by the proxy. + def proxy_reflection + @reflection + end + + # Returns the \target of the proxy, same as +target+. + def proxy_target + @target + end + + # Does the proxy or its \target respond to +symbol+? + def respond_to?(*args) + proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args)) + end + + # Forwards === explicitly to the \target because the instance method + # removal above doesn't catch it. Loads the \target if needed. + def ===(other) + load_target + other === @target + end + + # Returns the name of the table of the related class: + # + # post.comments.aliased_table_name # => "comments" + # + def aliased_table_name + @reflection.klass.table_name + end + + # Returns the SQL string that corresponds to the :conditions + # option of the macro, if given, or +nil+ otherwise. + def conditions + @conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions + end + alias :sql_conditions :conditions + + # Resets the \loaded flag to +false+ and sets the \target to +nil+. + def reset + @loaded = false + @target = nil + end + + # Reloads the \target and returns +self+ on success. + def reload + reset + load_target + self unless @target.nil? + end + + # Has the \target been already \loaded? + def loaded? + @loaded + end + + # Asserts the \target has been loaded setting the \loaded flag to +true+. + def loaded + @loaded = true + end + + # Returns the target of this proxy, same as +proxy_target+. + def target + @target + end + + # Sets the target of this proxy to \target, and the \loaded flag to +true+. + def target=(target) + @target = target + loaded + end + + # Forwards the call to the target. Loads the \target if needed. + def inspect + load_target + @target.inspect + end + + def send(method, *args) + if proxy_respond_to?(method) + super + else + load_target + @target.send(method, *args) + end + end + + protected + # Does the association have a :dependent option? + def dependent? + @reflection.options[:dependent] + end + + # Returns a string with the IDs of +records+ joined with a comma, quoted + # if needed. The result is ready to be inserted into a SQL IN clause. + # + # quoted_record_ids(records) # => "23,56,58,67" + # + def quoted_record_ids(records) + records.map { |record| record.quoted_id }.join(',') + end + + def interpolate_sql(sql, record = nil) + @owner.send(:interpolate_sql, sql, record) + end + + # Forwards the call to the reflection class. + def sanitize_sql(sql) + @reflection.klass.send(:sanitize_sql, sql) + end + + # Assigns the ID of the owner to the corresponding foreign key in +record+. + # If the association is polymorphic the type of the owner is also set. + def set_belongs_to_association_for(record) + if @reflection.options[:as] + record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record? + record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s + else + record[@reflection.primary_key_name] = @owner.id unless @owner.new_record? + end + end + + # Merges into +options+ the ones coming from the reflection. + def merge_options_from_reflection!(options) + options.reverse_merge!( + :group => @reflection.options[:group], + :limit => @reflection.options[:limit], + :offset => @reflection.options[:offset], + :joins => @reflection.options[:joins], + :include => @reflection.options[:include], + :select => @reflection.options[:select], + :readonly => @reflection.options[:readonly] + ) + end + + # Forwards +with_scope+ to the reflection. + def with_scope(*args, &block) + @reflection.klass.send :with_scope, *args, &block + end + + private + # Forwards any missing method call to the \target. + def method_missing(method, *args) + if load_target + raise NoMethodError unless @target.respond_to?(method) + + if block_given? + @target.send(method, *args) { |*block_args| yield(*block_args) } + else + @target.send(method, *args) + end + end + end + + # Loads the \target if needed and returns it. + # + # This method is abstract in the sense that it relies on +find_target+, + # which is expected to be provided by descendants. + # + # If the \target is already \loaded it is just returned. Thus, you can call + # +load_target+ unconditionally to get the \target. + # + # ActiveRecord::RecordNotFound is rescued within the method, and it is + # not reraised. The proxy is \reset and +nil+ is the return value. + def load_target + return nil unless defined?(@loaded) + + if !loaded? and (!@owner.new_record? || foreign_key_present) + @target = find_target + end + + @loaded = true + @target + rescue ActiveRecord::RecordNotFound + reset + end + + # Can be overwritten by associations that might have the foreign key + # available for an association without having the object itself (and + # still being a new record). Currently, only +belongs_to+ presents + # this scenario (both vanilla and polymorphic). + def foreign_key_present + false + end + + # Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of + # the kind of the class of the associated objects. Meant to be used as + # a sanity check when you are about to assign an associated record. + def raise_on_type_mismatch(record) + unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize) + message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})" + raise ActiveRecord::AssociationTypeMismatch, message + end + end + + # Array#flatten has problems with recursive arrays. Going one level + # deeper solves the majority of the problems. + def flatten_deeper(array) + array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten + end + + # Returns the ID of the owner, quoted if needed. + def owner_quoted_id + @owner.quoted_id + end + end + end +end