1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Allow associations to take a lambda which builds the scope

This commit is contained in:
Jon Leighton 2012-06-22 11:40:13 +01:00
parent 4b4a85515b
commit 5a54bffe37
9 changed files with 69 additions and 48 deletions

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'