Skip to content
Browse files

Allow associations to take a lambda which builds the scope

  • Loading branch information...
1 parent 4b4a855 commit 5a54bffe37f9e16405779d9729d7903d7c7d6c56 @jonleighton jonleighton committed
View
16 activerecord/lib/active_record/associations.rb
@@ -1192,8 +1192,8 @@ module ClassMethods
# 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 @@ def has_many(name, options = {}, &extension)
# 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 @@ def has_one(name, options = {})
# 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 @@ def belongs_to(name, options = {})
# 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
View
6 activerecord/lib/active_record/associations/association.rb
@@ -82,7 +82,7 @@ def target=(target)
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 @@ def reset_scope
@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)
View
21 activerecord/lib/active_record/associations/builder/association.rb
@@ -6,14 +6,23 @@ class Association #:nodoc:
# 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 @@ def mixin
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
View
8 activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -9,12 +9,8 @@ class CollectionAssociation < Association #:nodoc:
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
View
11 activerecord/lib/active_record/reflection.rb
@@ -20,9 +20,9 @@ module Reflection # :nodoc:
# 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 @@ class MacroReflection
# <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 @@ class MacroReflection
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 @@ def klass
@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
View
2 activerecord/test/cases/associations/extension_test.rb
@@ -70,7 +70,7 @@ def test_proxy_association_after_scoped
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
View
7 activerecord/test/cases/associations/has_many_associations_test.rb
@@ -1638,4 +1638,11 @@ def test_collection_association_with_private_kernel_method
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
View
44 activerecord/test/cases/reflection_test.rb
@@ -77,7 +77,7 @@ def test_integer_columns
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 @@ def test_reflect_on_all_autosave_associations
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 @@ def test_has_many_reflection
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 @@ def test_association_primary_key
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 @@ def test_active_record_primary_key
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 @@ def test_collection_association
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
View
2 activerecord/test/models/author.rb
@@ -26,6 +26,8 @@ def testing_proxy_target
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'

1 comment on commit 5a54bff

@kuraga

Hm... Not documented by this year?!.

Please sign in to comment.
Something went wrong with that request. Please try again.