diff options
author | Jon Leighton <j@jonathanleighton.com> | 2012-06-22 11:40:13 +0100 |
---|---|---|
committer | Jon Leighton <j@jonathanleighton.com> | 2012-07-13 11:55:48 +0100 |
commit | 5a54bffe37f9e16405779d9729d7903d7c7d6c56 (patch) | |
tree | 1c7c0eaa6dacfdf89499c9c137b891bbc197517b /activerecord | |
parent | 4b4a85515bbae4ec88547727a5f7f17bc5cefdce (diff) | |
download | rails-5a54bffe37f9e16405779d9729d7903d7c7d6c56.tar.gz rails-5a54bffe37f9e16405779d9729d7903d7c7d6c56.tar.bz2 rails-5a54bffe37f9e16405779d9729d7903d7c7d6c56.zip |
Allow associations to take a lambda which builds the scope
Diffstat (limited to 'activerecord')
9 files changed, 69 insertions, 48 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a62fce4756..a90c5a2a9a 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1192,8 +1192,8 @@ module ActiveRecord # ORDER BY p.first_name # } # } - def has_many(name, options = {}, &extension) - Builder::HasMany.build(self, name, options, &extension) + def has_many(name, scope = {}, options = nil, &extension) + Builder::HasMany.build(self, name, scope, options, &extension) end # Specifies a one-to-one association with another class. This method should only be used @@ -1308,8 +1308,8 @@ module ActiveRecord # has_one :boss, :readonly => :true # has_one :club, :through => :membership # has_one :primary_address, :through => :addressables, :conditions => ["addressable.primary = ?", true], :source => :addressable - def has_one(name, options = {}) - Builder::HasOne.build(self, name, options) + def has_one(name, scope = {}, options = nil) + Builder::HasOne.build(self, name, scope, options) end # Specifies a one-to-one association with another class. This method should only be used @@ -1431,8 +1431,8 @@ module ActiveRecord # belongs_to :post, :counter_cache => true # belongs_to :company, :touch => true # belongs_to :company, :touch => :employees_last_updated_at - def belongs_to(name, options = {}) - Builder::BelongsTo.build(self, name, options) + def belongs_to(name, scope = {}, options = nil) + Builder::BelongsTo.build(self, name, scope, options) end # Specifies a many-to-many relationship with another class. This associates two classes via an @@ -1605,8 +1605,8 @@ module ActiveRecord # has_and_belongs_to_many :categories, :readonly => true # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => # proc { |record| "DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}" } - def has_and_belongs_to_many(name, options = {}, &extension) - Builder::HasAndBelongsToMany.build(self, name, options, &extension) + def has_and_belongs_to_many(name, scope = {}, options = nil, &extension) + Builder::HasAndBelongsToMany.build(self, name, scope, options, &extension) end end end diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index e75003f261..624942d08a 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -82,7 +82,7 @@ module ActiveRecord end def scoped - target_scope.merge(association_scope) + target_scope.merge(association_scope).merge(reflection_scope) end # The scope for this association. @@ -101,6 +101,10 @@ module ActiveRecord @association_scope = nil end + def reflection_scope + reflection.scope && klass.instance_exec(&reflection.scope) + end + # Set the inverse association, if possible def set_inverse_instance(record) if record && invertible_for?(record) diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 9a6896dd55..f1a9f254b1 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -6,14 +6,23 @@ module ActiveRecord::Associations::Builder # Set by subclasses class_attribute :macro - attr_reader :model, :name, :options, :reflection + attr_reader :model, :name, :scope, :options, :reflection - def self.build(model, name, options) - new(model, name, options).build + def self.build(*args, &block) + new(*args, &block).build end - def initialize(model, name, options) - @model, @name, @options = model, name, options + def initialize(model, name, scope, options) + @model = model + @name = name + + if options + @scope = scope + @options = options + else + @scope = nil + @options = scope + end end def mixin @@ -22,7 +31,7 @@ module ActiveRecord::Associations::Builder def build validate_options - reflection = model.create_reflection(self.class.macro, name, options, model) + reflection = model.create_reflection(self.class.macro, name, scope, options, model) define_accessors reflection end diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index 768f70b6c9..50836b496f 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -9,12 +9,8 @@ module ActiveRecord::Associations::Builder attr_reader :block_extension - def self.build(model, name, options, &extension) - new(model, name, options, &extension).build - end - - def initialize(model, name, options, &extension) - super(model, name, options) + def initialize(*args, &extension) + super(*args) @block_extension = extension end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 0d9534acd6..31c674e1c4 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -20,9 +20,9 @@ module ActiveRecord # MacroReflection class has info for the AssociationReflection # class. module ClassMethods - def create_reflection(macro, name, options, active_record) + def create_reflection(macro, name, scope, options, active_record) klass = options[:through] ? ThroughReflection : AssociationReflection - reflection = klass.new(macro, name, options, active_record) + reflection = klass.new(macro, name, scope, options, active_record) self.reflections = self.reflections.merge(name => reflection) reflection @@ -71,6 +71,8 @@ module ActiveRecord # <tt>has_many :clients</tt> returns <tt>:has_many</tt> attr_reader :macro + attr_reader :scope + # Returns the hash of options used for the macro. # # <tt>has_many :clients</tt> returns +{}+ @@ -80,9 +82,10 @@ module ActiveRecord attr_reader :plural_name # :nodoc: - def initialize(macro, name, options, active_record) + def initialize(macro, name, scope, options, active_record) @macro = macro @name = name + @scope = scope @options = options @active_record = active_record @plural_name = active_record.pluralize_table_names ? @@ -142,7 +145,7 @@ module ActiveRecord @klass ||= active_record.send(:compute_type, class_name) end - def initialize(macro, name, options, active_record) + def initialize(*args) super @collection = [:has_many, :has_and_belongs_to_many].include?(macro) end diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb index d7c489c2b5..91fd17e572 100644 --- a/activerecord/test/cases/associations/extension_test.rb +++ b/activerecord/test/cases/associations/extension_test.rb @@ -70,7 +70,7 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase private def extension_name(model) - builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, {}) { } + builder = ActiveRecord::Associations::Builder::HasMany.new(model, :association_name, nil, {}) { } builder.send(:wrap_block_extension) builder.options[:extend].first.name end diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 3ea6201d60..477bb91412 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1638,4 +1638,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase post.taggings_with_delete_all.delete_all end end + + test "association using a scope block" do + author = authors(:david) + + assert author.posts.size > 1 + assert_equal author.posts.order('posts.id').limit(1), author.posts_with_scope_block + end end diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 6631dce08f..04042d156d 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -77,7 +77,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_reflection_klass_for_nested_class_name - reflection = MacroReflection.new(:company, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base) + reflection = MacroReflection.new(:company, nil, nil, { :class_name => 'MyApplication::Business::Company' }, ActiveRecord::Base) assert_nothing_raised do assert_equal MyApplication::Business::Company, reflection.klass end @@ -93,7 +93,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_has_many_reflection - reflection_for_clients = AssociationReflection.new(:has_many, :clients, { :order => "id", :dependent => :destroy }, Firm) + reflection_for_clients = AssociationReflection.new(:has_many, :clients, nil, { :order => "id", :dependent => :destroy }, Firm) assert_equal reflection_for_clients, Firm.reflect_on_association(:clients) @@ -105,7 +105,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_has_one_reflection - reflection_for_account = AssociationReflection.new(:has_one, :account, { :foreign_key => "firm_id", :dependent => :destroy }, Firm) + reflection_for_account = AssociationReflection.new(:has_one, :account, nil, { :foreign_key => "firm_id", :dependent => :destroy }, Firm) assert_equal reflection_for_account, Firm.reflect_on_association(:account) assert_equal Account, Firm.reflect_on_association(:account).klass @@ -230,10 +230,10 @@ class ReflectionTest < ActiveRecord::TestCase end def test_association_primary_key_raises_when_missing_primary_key - reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :edge, {}, Author) + reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :edge, nil, {}, Author) assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.association_primary_key } - through = ActiveRecord::Reflection::ThroughReflection.new(:fuu, :edge, {}, Author) + through = ActiveRecord::Reflection::ThroughReflection.new(:fuu, :edge, nil, {}, Author) through.stubs(:source_reflection).returns(stub_everything(:options => {}, :class_name => 'Edge')) assert_raises(ActiveRecord::UnknownPrimaryKey) { through.association_primary_key } end @@ -244,7 +244,7 @@ class ReflectionTest < ActiveRecord::TestCase end def test_active_record_primary_key_raises_when_missing_primary_key - reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :author, {}, Edge) + reflection = ActiveRecord::Reflection::AssociationReflection.new(:fuu, :author, nil, {}, Edge) assert_raises(ActiveRecord::UnknownPrimaryKey) { reflection.active_record_primary_key } end @@ -262,32 +262,32 @@ class ReflectionTest < ActiveRecord::TestCase end def test_default_association_validation - assert AssociationReflection.new(:has_many, :clients, {}, Firm).validate? + assert AssociationReflection.new(:has_many, :clients, nil, {}, Firm).validate? - assert !AssociationReflection.new(:has_one, :client, {}, Firm).validate? - assert !AssociationReflection.new(:belongs_to, :client, {}, Firm).validate? - assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, {}, Firm).validate? + assert !AssociationReflection.new(:has_one, :client, nil, {}, Firm).validate? + assert !AssociationReflection.new(:belongs_to, :client, nil, {}, Firm).validate? + assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, nil, {}, Firm).validate? end def test_always_validate_association_if_explicit - assert AssociationReflection.new(:has_one, :client, { :validate => true }, Firm).validate? - assert AssociationReflection.new(:belongs_to, :client, { :validate => true }, Firm).validate? - assert AssociationReflection.new(:has_many, :clients, { :validate => true }, Firm).validate? - assert AssociationReflection.new(:has_and_belongs_to_many, :clients, { :validate => true }, Firm).validate? + assert AssociationReflection.new(:has_one, :client, nil, { :validate => true }, Firm).validate? + assert AssociationReflection.new(:belongs_to, :client, nil, { :validate => true }, Firm).validate? + assert AssociationReflection.new(:has_many, :clients, nil, { :validate => true }, Firm).validate? + assert AssociationReflection.new(:has_and_belongs_to_many, :clients, nil, { :validate => true }, Firm).validate? end def test_validate_association_if_autosave - assert AssociationReflection.new(:has_one, :client, { :autosave => true }, Firm).validate? - assert AssociationReflection.new(:belongs_to, :client, { :autosave => true }, Firm).validate? - assert AssociationReflection.new(:has_many, :clients, { :autosave => true }, Firm).validate? - assert AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true }, Firm).validate? + assert AssociationReflection.new(:has_one, :client, nil, { :autosave => true }, Firm).validate? + assert AssociationReflection.new(:belongs_to, :client, nil, { :autosave => true }, Firm).validate? + assert AssociationReflection.new(:has_many, :clients, nil, { :autosave => true }, Firm).validate? + assert AssociationReflection.new(:has_and_belongs_to_many, :clients, nil, { :autosave => true }, Firm).validate? end def test_never_validate_association_if_explicit - assert !AssociationReflection.new(:has_one, :client, { :autosave => true, :validate => false }, Firm).validate? - assert !AssociationReflection.new(:belongs_to, :client, { :autosave => true, :validate => false }, Firm).validate? - assert !AssociationReflection.new(:has_many, :clients, { :autosave => true, :validate => false }, Firm).validate? - assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, { :autosave => true, :validate => false }, Firm).validate? + assert !AssociationReflection.new(:has_one, :client, nil, { :autosave => true, :validate => false }, Firm).validate? + assert !AssociationReflection.new(:belongs_to, :client, nil, { :autosave => true, :validate => false }, Firm).validate? + assert !AssociationReflection.new(:has_many, :clients, nil, { :autosave => true, :validate => false }, Firm).validate? + assert !AssociationReflection.new(:has_and_belongs_to_many, :clients, nil, { :autosave => true, :validate => false }, Firm).validate? end def test_foreign_key diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 14444a0092..6017178289 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -26,6 +26,8 @@ class Author < ActiveRecord::Base has_many :comments_with_order_and_conditions, :through => :posts, :source => :comments, :order => 'comments.body', :conditions => "comments.body like 'Thank%'" has_many :comments_with_include, :through => :posts, :source => :comments, :include => :post + has_many :posts_with_scope_block, -> { order('posts.id').limit(1) }, :class_name => "Post" + has_many :first_posts has_many :comments_on_first_posts, :through => :first_posts, :source => :comments, :order => 'posts.id desc, comments.id asc' |