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:
parent
4b4a85515b
commit
5a54bffe37
9 changed files with 69 additions and 48 deletions
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
Loading…
Reference in a new issue