activerecord 4.1.0.beta2 → 4.1.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +622 -9
- data/MIT-LICENSE +1 -1
- data/lib/active_record.rb +1 -1
- data/lib/active_record/associations.rb +10 -7
- data/lib/active_record/associations/alias_tracker.rb +39 -29
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/association_scope.rb +56 -31
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -0
- data/lib/active_record/associations/builder/association.rb +6 -0
- data/lib/active_record/associations/builder/belongs_to.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +33 -9
- data/lib/active_record/associations/collection_proxy.rb +53 -5
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +8 -8
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +28 -5
- data/lib/active_record/attribute_methods/dirty.rb +27 -4
- data/lib/active_record/attribute_methods/read.rb +1 -1
- data/lib/active_record/attribute_methods/serialization.rb +18 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -8
- data/lib/active_record/connection_adapters/abstract/transaction.rb +4 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/connection_specification.rb +200 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -1
- data/lib/active_record/connection_adapters/mysql_adapter.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/cast.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -17
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +25 -3
- data/lib/active_record/connection_handling.rb +64 -3
- data/lib/active_record/core.rb +28 -24
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +111 -17
- data/lib/active_record/errors.rb +12 -0
- data/lib/active_record/fixtures.rb +13 -15
- data/lib/active_record/inheritance.rb +29 -9
- data/lib/active_record/integration.rb +4 -2
- data/lib/active_record/migration.rb +20 -7
- data/lib/active_record/migration/command_recorder.rb +18 -6
- data/lib/active_record/persistence.rb +10 -5
- data/lib/active_record/querying.rb +1 -0
- data/lib/active_record/railtie.rb +11 -8
- data/lib/active_record/railties/databases.rake +24 -38
- data/lib/active_record/relation.rb +3 -2
- data/lib/active_record/relation/batches.rb +24 -9
- data/lib/active_record/relation/finder_methods.rb +100 -11
- data/lib/active_record/relation/query_methods.rb +39 -27
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +7 -5
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/scoping/named.rb +6 -0
- data/lib/active_record/store.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +45 -23
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/transactions.rb +7 -7
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/version.rb +1 -1
- metadata +5 -6
- data/lib/active_record/associations/join_helper.rb +0 -36
    
        data/MIT-LICENSE
    CHANGED
    
    
    
        data/lib/active_record.rb
    CHANGED
    
    
| @@ -75,13 +75,13 @@ module ActiveRecord | |
| 75 75 |  | 
| 76 76 | 
             
              class EagerLoadPolymorphicError < ActiveRecordError #:nodoc:
         | 
| 77 77 | 
             
                def initialize(reflection)
         | 
| 78 | 
            -
                  super(" | 
| 78 | 
            +
                  super("Cannot eagerly load the polymorphic association #{reflection.name.inspect}")
         | 
| 79 79 | 
             
                end
         | 
| 80 80 | 
             
              end
         | 
| 81 81 |  | 
| 82 82 | 
             
              class ReadOnlyAssociation < ActiveRecordError #:nodoc:
         | 
| 83 83 | 
             
                def initialize(reflection)
         | 
| 84 | 
            -
                  super(" | 
| 84 | 
            +
                  super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.")
         | 
| 85 85 | 
             
                end
         | 
| 86 86 | 
             
              end
         | 
| 87 87 |  | 
| @@ -130,7 +130,6 @@ module ActiveRecord | |
| 130 130 | 
             
                  autoload :JoinDependency,   'active_record/associations/join_dependency'
         | 
| 131 131 | 
             
                  autoload :AssociationScope, 'active_record/associations/association_scope'
         | 
| 132 132 | 
             
                  autoload :AliasTracker,     'active_record/associations/alias_tracker'
         | 
| 133 | 
            -
                  autoload :JoinHelper,       'active_record/associations/join_helper'
         | 
| 134 133 | 
             
                end
         | 
| 135 134 |  | 
| 136 135 | 
             
                # Clears out the association cache.
         | 
| @@ -669,11 +668,14 @@ module ActiveRecord | |
| 669 668 | 
             
                # and member posts that use the posts table for STI. In this case, there must be a +type+
         | 
| 670 669 | 
             
                # column in the posts table.
         | 
| 671 670 | 
             
                #
         | 
| 671 | 
            +
                # Note: The <tt>attachable_type=</tt> method is being called when assigning an +attachable+.
         | 
| 672 | 
            +
                # The +class_name+ of the +attachable+ is passed as a String.
         | 
| 673 | 
            +
                #
         | 
| 672 674 | 
             
                #   class Asset < ActiveRecord::Base
         | 
| 673 675 | 
             
                #     belongs_to :attachable, polymorphic: true
         | 
| 674 676 | 
             
                #
         | 
| 675 | 
            -
                #     def attachable_type=( | 
| 676 | 
            -
                #        super( | 
| 677 | 
            +
                #     def attachable_type=(class_name)
         | 
| 678 | 
            +
                #        super(class_name.constantize.base_class.to_s)
         | 
| 677 679 | 
             
                #     end
         | 
| 678 680 | 
             
                #   end
         | 
| 679 681 | 
             
                #
         | 
| @@ -1213,7 +1215,8 @@ module ActiveRecord | |
| 1213 1215 | 
             
                  #   Returns the associated object. +nil+ is returned if none is found.
         | 
| 1214 1216 | 
             
                  # [association=(associate)]
         | 
| 1215 1217 | 
             
                  #   Assigns the associate object, extracts the primary key, sets it as the foreign key,
         | 
| 1216 | 
            -
                  #   and saves the associate object.
         | 
| 1218 | 
            +
                  #   and saves the associate object. To avoid database inconsistencies, permanently deletes an existing
         | 
| 1219 | 
            +
                  #   associated object when assigning a new one, even if the new one isn't saved to database.
         | 
| 1217 1220 | 
             
                  # [build_association(attributes = {})]
         | 
| 1218 1221 | 
             
                  #   Returns a new object of the associated type that has been instantiated
         | 
| 1219 1222 | 
             
                  #   with +attributes+ and linked to this object through a foreign key, but has not
         | 
| @@ -1581,7 +1584,7 @@ module ActiveRecord | |
| 1581 1584 | 
             
                    hm_options[:through] = middle_reflection.name
         | 
| 1582 1585 | 
             
                    hm_options[:source] = join_model.right_reflection.name
         | 
| 1583 1586 |  | 
| 1584 | 
            -
                    [:before_add, :after_add, :before_remove, :after_remove].each do |k|
         | 
| 1587 | 
            +
                    [:before_add, :after_add, :before_remove, :after_remove, :autosave].each do |k|
         | 
| 1585 1588 | 
             
                      hm_options[k] = options[k] if options.key? k
         | 
| 1586 1589 | 
             
                    end
         | 
| 1587 1590 |  | 
| @@ -5,16 +5,48 @@ module ActiveRecord | |
| 5 5 | 
             
                # Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
         | 
| 6 6 | 
             
                # ActiveRecord::Associations::ThroughAssociationScope
         | 
| 7 7 | 
             
                class AliasTracker # :nodoc:
         | 
| 8 | 
            -
                  attr_reader :aliases, : | 
| 8 | 
            +
                  attr_reader :aliases, :connection
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def self.empty(connection)
         | 
| 11 | 
            +
                    new connection, Hash.new(0)
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  def self.create(connection, table_joins)
         | 
| 15 | 
            +
                    if table_joins.empty?
         | 
| 16 | 
            +
                      empty connection
         | 
| 17 | 
            +
                    else
         | 
| 18 | 
            +
                      aliases = Hash.new { |h,k|
         | 
| 19 | 
            +
                        h[k] = initial_count_for(connection, k, table_joins)
         | 
| 20 | 
            +
                      }
         | 
| 21 | 
            +
                      new connection, aliases
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def self.initial_count_for(connection, name, table_joins)
         | 
| 26 | 
            +
                    # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
         | 
| 27 | 
            +
                    quoted_name = connection.quote_table_name(name).downcase
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                    counts = table_joins.map do |join|
         | 
| 30 | 
            +
                      if join.is_a?(Arel::Nodes::StringJoin)
         | 
| 31 | 
            +
                        # Table names + table aliases
         | 
| 32 | 
            +
                        join.left.downcase.scan(
         | 
| 33 | 
            +
                          /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
         | 
| 34 | 
            +
                        ).size
         | 
| 35 | 
            +
                      else
         | 
| 36 | 
            +
                        join.left.table_name == name ? 1 : 0
         | 
| 37 | 
            +
                      end
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                    counts.sum
         | 
| 41 | 
            +
                  end
         | 
| 9 42 |  | 
| 10 43 | 
             
                  # table_joins is an array of arel joins which might conflict with the aliases we assign here
         | 
| 11 | 
            -
                  def initialize(connection | 
| 12 | 
            -
                    @aliases | 
| 13 | 
            -
                    @ | 
| 14 | 
            -
                    @connection  = connection
         | 
| 44 | 
            +
                  def initialize(connection, aliases)
         | 
| 45 | 
            +
                    @aliases    = aliases
         | 
| 46 | 
            +
                    @connection = connection
         | 
| 15 47 | 
             
                  end
         | 
| 16 48 |  | 
| 17 | 
            -
                  def aliased_table_for(table_name, aliased_name | 
| 49 | 
            +
                  def aliased_table_for(table_name, aliased_name)
         | 
| 18 50 | 
             
                    table_alias = aliased_name_for(table_name, aliased_name)
         | 
| 19 51 |  | 
| 20 52 | 
             
                    if table_alias == table_name
         | 
| @@ -24,9 +56,7 @@ module ActiveRecord | |
| 24 56 | 
             
                    end
         | 
| 25 57 | 
             
                  end
         | 
| 26 58 |  | 
| 27 | 
            -
                  def aliased_name_for(table_name, aliased_name | 
| 28 | 
            -
                    aliased_name ||= table_name
         | 
| 29 | 
            -
             | 
| 59 | 
            +
                  def aliased_name_for(table_name, aliased_name)
         | 
| 30 60 | 
             
                    if aliases[table_name].zero?
         | 
| 31 61 | 
             
                      # If it's zero, we can have our table_name
         | 
| 32 62 | 
             
                      aliases[table_name] = 1
         | 
| @@ -48,26 +78,6 @@ module ActiveRecord | |
| 48 78 |  | 
| 49 79 | 
             
                  private
         | 
| 50 80 |  | 
| 51 | 
            -
                    def initial_count_for(name)
         | 
| 52 | 
            -
                      return 0 if Arel::Table === table_joins
         | 
| 53 | 
            -
             | 
| 54 | 
            -
                      # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
         | 
| 55 | 
            -
                      quoted_name = connection.quote_table_name(name).downcase
         | 
| 56 | 
            -
             | 
| 57 | 
            -
                      counts = table_joins.map do |join|
         | 
| 58 | 
            -
                        if join.is_a?(Arel::Nodes::StringJoin)
         | 
| 59 | 
            -
                          # Table names + table aliases
         | 
| 60 | 
            -
                          join.left.downcase.scan(
         | 
| 61 | 
            -
                            /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
         | 
| 62 | 
            -
                          ).size
         | 
| 63 | 
            -
                        else
         | 
| 64 | 
            -
                          join.left.table_name == name ? 1 : 0
         | 
| 65 | 
            -
                        end
         | 
| 66 | 
            -
                      end
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                      counts.sum
         | 
| 69 | 
            -
                    end
         | 
| 70 | 
            -
             | 
| 71 81 | 
             
                    def truncate(name)
         | 
| 72 82 | 
             
                      name.slice(0, connection.table_alias_length - 2)
         | 
| 73 83 | 
             
                    end
         | 
| @@ -1,52 +1,77 @@ | |
| 1 1 | 
             
            module ActiveRecord
         | 
| 2 2 | 
             
              module Associations
         | 
| 3 3 | 
             
                class AssociationScope #:nodoc:
         | 
| 4 | 
            -
                   | 
| 4 | 
            +
                  INSTANCE = new
         | 
| 5 5 |  | 
| 6 | 
            -
                   | 
| 6 | 
            +
                  def self.scope(association, connection)
         | 
| 7 | 
            +
                    INSTANCE.scope association, connection
         | 
| 8 | 
            +
                  end
         | 
| 7 9 |  | 
| 8 | 
            -
                   | 
| 9 | 
            -
             | 
| 10 | 
            +
                  def scope(association, connection)
         | 
| 11 | 
            +
                    klass         = association.klass
         | 
| 12 | 
            +
                    reflection    = association.reflection
         | 
| 13 | 
            +
                    scope         = klass.unscoped
         | 
| 14 | 
            +
                    owner         = association.owner
         | 
| 15 | 
            +
                    alias_tracker = AliasTracker.empty connection
         | 
| 10 16 |  | 
| 11 | 
            -
             | 
| 12 | 
            -
                     | 
| 13 | 
            -
                    @alias_tracker = AliasTracker.new klass.connection
         | 
| 17 | 
            +
                    scope.extending! Array(reflection.options[:extend])
         | 
| 18 | 
            +
                    add_constraints(scope, owner, klass, reflection, alias_tracker)
         | 
| 14 19 | 
             
                  end
         | 
| 15 20 |  | 
| 16 | 
            -
                  def  | 
| 17 | 
            -
                     | 
| 18 | 
            -
                    scope.extending! Array(options[:extend])
         | 
| 19 | 
            -
                    add_constraints(scope)
         | 
| 21 | 
            +
                  def join_type
         | 
| 22 | 
            +
                    Arel::Nodes::InnerJoin
         | 
| 20 23 | 
             
                  end
         | 
| 21 24 |  | 
| 22 25 | 
             
                  private
         | 
| 23 26 |  | 
| 24 | 
            -
                  def  | 
| 27 | 
            +
                  def construct_tables(chain, klass, refl, alias_tracker)
         | 
| 28 | 
            +
                    chain.map do |reflection|
         | 
| 29 | 
            +
                      alias_tracker.aliased_table_for(
         | 
| 30 | 
            +
                        table_name_for(reflection, klass, refl),
         | 
| 31 | 
            +
                        table_alias_for(reflection, refl, reflection != refl)
         | 
| 32 | 
            +
                      )
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  def table_alias_for(reflection, refl, join = false)
         | 
| 37 | 
            +
                    name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
         | 
| 38 | 
            +
                    name << "_join" if join
         | 
| 39 | 
            +
                    name
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def join(table, constraint)
         | 
| 43 | 
            +
                    table.create_join(table, table.create_on(constraint), join_type)
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                  def column_for(table_name, column_name, alias_tracker)
         | 
| 25 47 | 
             
                    columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
         | 
| 26 48 | 
             
                    columns[column_name]
         | 
| 27 49 | 
             
                  end
         | 
| 28 50 |  | 
| 29 | 
            -
                  def bind_value(scope, column, value)
         | 
| 51 | 
            +
                  def bind_value(scope, column, value, alias_tracker)
         | 
| 30 52 | 
             
                    substitute = alias_tracker.connection.substitute_at(
         | 
| 31 53 | 
             
                      column, scope.bind_values.length)
         | 
| 32 54 | 
             
                    scope.bind_values += [[column, value]]
         | 
| 33 55 | 
             
                    substitute
         | 
| 34 56 | 
             
                  end
         | 
| 35 57 |  | 
| 36 | 
            -
                  def bind(scope, table_name, column_name, value)
         | 
| 37 | 
            -
                    column   = column_for table_name, column_name
         | 
| 38 | 
            -
                    bind_value scope, column, value
         | 
| 58 | 
            +
                  def bind(scope, table_name, column_name, value, tracker)
         | 
| 59 | 
            +
                    column   = column_for table_name, column_name, tracker
         | 
| 60 | 
            +
                    bind_value scope, column, value, tracker
         | 
| 39 61 | 
             
                  end
         | 
| 40 62 |  | 
| 41 | 
            -
                  def add_constraints(scope)
         | 
| 42 | 
            -
                     | 
| 63 | 
            +
                  def add_constraints(scope, owner, assoc_klass, refl, tracker)
         | 
| 64 | 
            +
                    chain = refl.chain
         | 
| 65 | 
            +
                    scope_chain = refl.scope_chain
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    tables = construct_tables(chain, assoc_klass, refl, tracker)
         | 
| 43 68 |  | 
| 44 69 | 
             
                    chain.each_with_index do |reflection, i|
         | 
| 45 70 | 
             
                      table, foreign_table = tables.shift, tables.first
         | 
| 46 71 |  | 
| 47 72 | 
             
                      if reflection.source_macro == :belongs_to
         | 
| 48 73 | 
             
                        if reflection.options[:polymorphic]
         | 
| 49 | 
            -
                          key = reflection.association_primary_key( | 
| 74 | 
            +
                          key = reflection.association_primary_key(assoc_klass)
         | 
| 50 75 | 
             
                        else
         | 
| 51 76 | 
             
                          key = reflection.association_primary_key
         | 
| 52 77 | 
             
                        end
         | 
| @@ -58,12 +83,12 @@ module ActiveRecord | |
| 58 83 | 
             
                      end
         | 
| 59 84 |  | 
| 60 85 | 
             
                      if reflection == chain.last
         | 
| 61 | 
            -
                        bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key]
         | 
| 86 | 
            +
                        bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
         | 
| 62 87 | 
             
                        scope    = scope.where(table[key].eq(bind_val))
         | 
| 63 88 |  | 
| 64 89 | 
             
                        if reflection.type
         | 
| 65 90 | 
             
                          value    = owner.class.base_class.name
         | 
| 66 | 
            -
                          bind_val = bind scope, table.table_name, reflection.type.to_s, value
         | 
| 91 | 
            +
                          bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
         | 
| 67 92 | 
             
                          scope    = scope.where(table[reflection.type].eq(bind_val))
         | 
| 68 93 | 
             
                        end
         | 
| 69 94 | 
             
                      else
         | 
| @@ -71,7 +96,7 @@ module ActiveRecord | |
| 71 96 |  | 
| 72 97 | 
             
                        if reflection.type
         | 
| 73 98 | 
             
                          value    = chain[i + 1].klass.base_class.name
         | 
| 74 | 
            -
                          bind_val = bind scope, table.table_name, reflection.type.to_s, value
         | 
| 99 | 
            +
                          bind_val = bind scope, table.table_name, reflection.type.to_s, value, tracker
         | 
| 75 100 | 
             
                          scope    = scope.where(table[reflection.type].eq(bind_val))
         | 
| 76 101 | 
             
                        end
         | 
| 77 102 |  | 
| @@ -79,14 +104,14 @@ module ActiveRecord | |
| 79 104 | 
             
                      end
         | 
| 80 105 |  | 
| 81 106 | 
             
                      is_first_chain = i == 0
         | 
| 82 | 
            -
                      klass = is_first_chain ?  | 
| 107 | 
            +
                      klass = is_first_chain ? assoc_klass : reflection.klass
         | 
| 83 108 |  | 
| 84 109 | 
             
                      # Exclude the scope of the association itself, because that
         | 
| 85 110 | 
             
                      # was already merged in the #scope method.
         | 
| 86 111 | 
             
                      scope_chain[i].each do |scope_chain_item|
         | 
| 87 | 
            -
                        item  = eval_scope(klass, scope_chain_item)
         | 
| 112 | 
            +
                        item  = eval_scope(klass, scope_chain_item, owner)
         | 
| 88 113 |  | 
| 89 | 
            -
                        if scope_chain_item ==  | 
| 114 | 
            +
                        if scope_chain_item == refl.scope
         | 
| 90 115 | 
             
                          scope.merge! item.except(:where, :includes, :bind)
         | 
| 91 116 | 
             
                        end
         | 
| 92 117 |  | 
| @@ -102,22 +127,22 @@ module ActiveRecord | |
| 102 127 | 
             
                    scope
         | 
| 103 128 | 
             
                  end
         | 
| 104 129 |  | 
| 105 | 
            -
                  def alias_suffix
         | 
| 106 | 
            -
                     | 
| 130 | 
            +
                  def alias_suffix(refl)
         | 
| 131 | 
            +
                    refl.name
         | 
| 107 132 | 
             
                  end
         | 
| 108 133 |  | 
| 109 | 
            -
                  def table_name_for(reflection)
         | 
| 110 | 
            -
                    if reflection ==  | 
| 134 | 
            +
                  def table_name_for(reflection, klass, refl)
         | 
| 135 | 
            +
                    if reflection == refl
         | 
| 111 136 | 
             
                      # If this is a polymorphic belongs_to, we want to get the klass from the
         | 
| 112 137 | 
             
                      # association because it depends on the polymorphic_type attribute of
         | 
| 113 138 | 
             
                      # the owner
         | 
| 114 139 | 
             
                      klass.table_name
         | 
| 115 140 | 
             
                    else
         | 
| 116 | 
            -
                       | 
| 141 | 
            +
                      reflection.table_name
         | 
| 117 142 | 
             
                    end
         | 
| 118 143 | 
             
                  end
         | 
| 119 144 |  | 
| 120 | 
            -
                  def eval_scope(klass, scope)
         | 
| 145 | 
            +
                  def eval_scope(klass, scope, owner)
         | 
| 121 146 | 
             
                    if scope.is_a?(Relation)
         | 
| 122 147 | 
             
                      scope
         | 
| 123 148 | 
             
                    else
         | 
| @@ -26,6 +26,12 @@ module ActiveRecord::Associations::Builder | |
| 26 26 | 
             
                attr_reader :name, :scope, :options
         | 
| 27 27 |  | 
| 28 28 | 
             
                def self.build(model, name, scope, options, &block)
         | 
| 29 | 
            +
                  if model.dangerous_attribute_method?(name)
         | 
| 30 | 
            +
                    raise ArgumentError, "You tried to define an association named #{name} on the model #{model.name}, but " \
         | 
| 31 | 
            +
                                         "this will conflict with a method #{name} already defined by Active Record. " \
         | 
| 32 | 
            +
                                         "Please choose a different association name."
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 29 35 | 
             
                  builder = create_builder model, name, scope, options, &block
         | 
| 30 36 | 
             
                  reflection = builder.build(model)
         | 
| 31 37 | 
             
                  define_accessors model, reflection
         | 
| @@ -4,7 +4,7 @@ module ActiveRecord | |
| 4 4 | 
             
                #
         | 
| 5 5 | 
             
                # CollectionAssociation is an abstract class that provides common stuff to
         | 
| 6 6 | 
             
                # ease the implementation of association proxies that represent
         | 
| 7 | 
            -
                # collections. See the class hierarchy in  | 
| 7 | 
            +
                # collections. See the class hierarchy in Association.
         | 
| 8 8 | 
             
                #
         | 
| 9 9 | 
             
                #   CollectionAssociation:
         | 
| 10 10 | 
             
                #     HasManyAssociation => has_many
         | 
| @@ -24,6 +24,10 @@ module ActiveRecord | |
| 24 24 | 
             
                # If you need to work on all current children, new and existing records,
         | 
| 25 25 | 
             
                # +load_target+ and the +loaded+ flag are your friends.
         | 
| 26 26 | 
             
                class CollectionAssociation < Association #:nodoc:
         | 
| 27 | 
            +
                  def initialize(owner, reflection)
         | 
| 28 | 
            +
                    super
         | 
| 29 | 
            +
                    @proxy = CollectionProxy.create(klass, self)
         | 
| 30 | 
            +
                  end
         | 
| 27 31 |  | 
| 28 32 | 
             
                  # Implements the reader method, e.g. foo.items for Foo.has_many :items
         | 
| 29 33 | 
             
                  def reader(force_reload = false)
         | 
| @@ -33,7 +37,7 @@ module ActiveRecord | |
| 33 37 | 
             
                      reload
         | 
| 34 38 | 
             
                    end
         | 
| 35 39 |  | 
| 36 | 
            -
                    @proxy | 
| 40 | 
            +
                    @proxy
         | 
| 37 41 | 
             
                  end
         | 
| 38 42 |  | 
| 39 43 | 
             
                  # Implements the writer method, e.g. foo.items= for Foo.has_many :items
         | 
| @@ -66,11 +70,11 @@ module ActiveRecord | |
| 66 70 | 
             
                    @target = []
         | 
| 67 71 | 
             
                  end
         | 
| 68 72 |  | 
| 69 | 
            -
                  def select( | 
| 73 | 
            +
                  def select(*fields)
         | 
| 70 74 | 
             
                    if block_given?
         | 
| 71 75 | 
             
                      load_target.select.each { |e| yield e }
         | 
| 72 76 | 
             
                    else
         | 
| 73 | 
            -
                      scope.select( | 
| 77 | 
            +
                      scope.select(*fields)
         | 
| 74 78 | 
             
                    end
         | 
| 75 79 | 
             
                  end
         | 
| 76 80 |  | 
| @@ -96,11 +100,31 @@ module ActiveRecord | |
| 96 100 | 
             
                  end
         | 
| 97 101 |  | 
| 98 102 | 
             
                  def first(*args)
         | 
| 99 | 
            -
                     | 
| 103 | 
            +
                    first_nth_or_last(:first, *args)
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                  def second(*args)
         | 
| 107 | 
            +
                    first_nth_or_last(:second, *args)
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  def third(*args)
         | 
| 111 | 
            +
                    first_nth_or_last(:third, *args)
         | 
| 112 | 
            +
                  end
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                  def fourth(*args)
         | 
| 115 | 
            +
                    first_nth_or_last(:fourth, *args)
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                  def fifth(*args)
         | 
| 119 | 
            +
                    first_nth_or_last(:fifth, *args)
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                  def forty_two(*args)
         | 
| 123 | 
            +
                    first_nth_or_last(:forty_two, *args)
         | 
| 100 124 | 
             
                  end
         | 
| 101 125 |  | 
| 102 126 | 
             
                  def last(*args)
         | 
| 103 | 
            -
                     | 
| 127 | 
            +
                    first_nth_or_last(:last, *args)
         | 
| 104 128 | 
             
                  end
         | 
| 105 129 |  | 
| 106 130 | 
             
                  def build(attributes = {}, &block)
         | 
| @@ -526,7 +550,7 @@ module ActiveRecord | |
| 526 550 | 
             
                    #   * target already loaded
         | 
| 527 551 | 
             
                    #   * owner is new record
         | 
| 528 552 | 
             
                    #   * target contains new or changed record(s)
         | 
| 529 | 
            -
                    def  | 
| 553 | 
            +
                    def fetch_first_nth_or_last_using_find?(args)
         | 
| 530 554 | 
             
                      if args.first.is_a?(Hash)
         | 
| 531 555 | 
             
                        true
         | 
| 532 556 | 
             
                      else
         | 
| @@ -564,10 +588,10 @@ module ActiveRecord | |
| 564 588 | 
             
                    end
         | 
| 565 589 |  | 
| 566 590 | 
             
                    # Fetches the first/last using SQL if possible, otherwise from the target array.
         | 
| 567 | 
            -
                    def  | 
| 591 | 
            +
                    def first_nth_or_last(type, *args)
         | 
| 568 592 | 
             
                      args.shift if args.first.is_a?(Hash) && args.first.empty?
         | 
| 569 593 |  | 
| 570 | 
            -
                      collection =  | 
| 594 | 
            +
                      collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
         | 
| 571 595 | 
             
                      collection.send(type, *args).tap do |record|
         | 
| 572 596 | 
             
                        set_inverse_instance record if record.is_a? ActiveRecord::Base
         | 
| 573 597 | 
             
                      end
         |