提交 c8dd66fd 编写于 作者: D David Heinemeier Hansson

Made association extensions use simpler block syntax

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2895 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 0073a759
...@@ -34,14 +34,14 @@ ...@@ -34,14 +34,14 @@
* Added extension capabilities to has_many and has_and_belongs_to_many proxies [DHH]. Example: * Added extension capabilities to has_many and has_and_belongs_to_many proxies [DHH]. Example:
class Account < ActiveRecord::Base class Account < ActiveRecord::Base
has_many :people, :extend => Module.new { has_many :people do
def find_or_create_by_name(name) def find_or_create_by_name(name)
first_name, *last_name = name.split first_name, *last_name = name.split
last_name = last_name.join " " last_name = last_name.join " "
find_or_create_by_first_name_and_last_name(first_name, last_name) find_or_create_by_first_name_and_last_name(first_name, last_name)
end end
} end
end end
person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson") person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
......
...@@ -127,7 +127,7 @@ def clear_association_cache #:nodoc: ...@@ -127,7 +127,7 @@ def clear_association_cache #:nodoc:
# Example: # Example:
# #
# class Account < ActiveRecord::Base # class Account < ActiveRecord::Base
# has_many :people, :extend => Module.new { # has_many :people do
# def find_or_create_by_name(name) # def find_or_create_by_name(name)
# first_name, *last_name = name.split # first_name, *last_name = name.split
# last_name = last_name.join " " # last_name = last_name.join " "
...@@ -135,14 +135,32 @@ def clear_association_cache #:nodoc: ...@@ -135,14 +135,32 @@ def clear_association_cache #:nodoc:
# find_by_first_name_and_last_name(first_name, last_name) || # find_by_first_name_and_last_name(first_name, last_name) ||
# create({ :first_name => first_name, :last_name => last_name }) # create({ :first_name => first_name, :last_name => last_name })
# end # end
# } # end
# end # end
# #
# person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson") # person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
# person.first_name # => "David" # person.first_name # => "David"
# person.last_name # => "Heinemeier Hansson" # person.last_name # => "Heinemeier Hansson"
# #
# Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation). # If you need to share the same extensions between many associations, you can use a named extension module. Example:
#
# module FindOrCreateByNameExtension
# def find_or_create_by_name(name)
# first_name, *last_name = name.split
# last_name = last_name.join " "
#
# find_by_first_name_and_last_name(first_name, last_name) ||
# create({ :first_name => first_name, :last_name => last_name })
# end
# end
#
# class Account < ActiveRecord::Base
# has_many :people, :extend => FindOrCreateByNameExtension
# end
#
# class Company < ActiveRecord::Base
# has_many :people, :extend => FindOrCreateByNameExtension
# end
# #
# == Caching # == Caching
# #
...@@ -306,7 +324,7 @@ module ClassMethods ...@@ -306,7 +324,7 @@ module ClassMethods
# associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added. # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added.
# * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is # * <tt>:counter_sql</tt> - specify a complete SQL statement to fetch the size of the association. If +:finder_sql+ is
# specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM. # specified but +:counter_sql+, +:counter_sql+ will be generated by replacing SELECT ... FROM with SELECT COUNT(*) FROM.
# * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions". # * <tt>:extend</tt> - specify a named module for extending the proxy, see "Association extensions".
# #
# Option examples: # Option examples:
# has_many :comments, :order => "posted_on" # has_many :comments, :order => "posted_on"
...@@ -317,13 +335,15 @@ module ClassMethods ...@@ -317,13 +335,15 @@ module ClassMethods
# 'FROM people p, post_subscriptions ps ' + # 'FROM people p, post_subscriptions ps ' +
# 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' + # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' +
# 'ORDER BY p.first_name' # 'ORDER BY p.first_name'
def has_many(association_id, options = {}) def has_many(association_id, options = {}, &extension)
options.assert_valid_keys( options.assert_valid_keys(
:foreign_key, :class_name, :exclusively_dependent, :dependent, :foreign_key, :class_name, :exclusively_dependent, :dependent,
:conditions, :order, :finder_sql, :counter_sql, :conditions, :order, :finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove, :extend :before_add, :after_add, :before_remove, :after_remove, :extend
) )
options[:extend] = create_extension_module(association_id, extension) if block_given?
association_name, association_class_name, association_class_primary_key_name = association_name, association_class_name, association_class_primary_key_name =
associate_identification(association_id, options[:class_name], options[:foreign_key]) associate_identification(association_id, options[:class_name], options[:foreign_key])
...@@ -602,13 +622,15 @@ def belongs_to(association_id, options = {}) ...@@ -602,13 +622,15 @@ def belongs_to(association_id, options = {})
# has_and_belongs_to_many :categories, :join_table => "prods_cats" # has_and_belongs_to_many :categories, :join_table => "prods_cats"
# has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql => # has_and_belongs_to_many :active_projects, :join_table => 'developers_projects', :delete_sql =>
# 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}' # 'DELETE FROM developers_projects WHERE active=1 AND developer_id = #{id} AND project_id = #{record.id}'
def has_and_belongs_to_many(association_id, options = {}) def has_and_belongs_to_many(association_id, options = {}, &extension)
options.assert_valid_keys( options.assert_valid_keys(
:class_name, :table_name, :foreign_key, :association_foreign_key, :conditions, :class_name, :table_name, :foreign_key, :association_foreign_key, :conditions,
:join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add, :join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add,
:before_remove, :after_remove, :extend :before_remove, :after_remove, :extend
) )
options[:extend] = create_extension_module(association_id, extension) if block_given?
association_name, association_class_name, association_class_primary_key_name = association_name, association_class_name, association_class_primary_key_name =
associate_identification(association_id, options[:class_name], options[:foreign_key]) associate_identification(association_id, options[:class_name], options[:foreign_key])
...@@ -1004,7 +1026,16 @@ def extract_record(schema_abbreviations, table_name, row) ...@@ -1004,7 +1026,16 @@ def extract_record(schema_abbreviations, table_name, row)
def condition_word(sql) def condition_word(sql)
sql =~ /where/i ? " AND " : "WHERE " sql =~ /where/i ? " AND " : "WHERE "
end end
end
def create_extension_module(association_id, extension)
extension_module_name = "#{self.to_s}#{association_id.to_s.camelize}AssociationExtension"
silence_warnings do
Object.const_set(extension_module_name, Module.new(&extension))
end
extension_module_name.constantize
end
end
end end
end end
...@@ -21,12 +21,12 @@ def composed_of_with_reflection(part_id, options = {}) ...@@ -21,12 +21,12 @@ def composed_of_with_reflection(part_id, options = {})
base.module_eval <<-"end_eval" base.module_eval <<-"end_eval"
class << self class << self
alias_method :#{association_type}_without_reflection, :#{association_type} alias_method :#{association_type}_without_reflection, :#{association_type}
def #{association_type}_with_reflection(association_id, options = {}) def #{association_type}_with_reflection(association_id, options = {}, &block)
#{association_type}_without_reflection(association_id, options) #{association_type}_without_reflection(association_id, options, &block)
reflect_on_all_associations << AssociationReflection.new(:#{association_type}, association_id, options, self) reflect_on_all_associations << AssociationReflection.new(:#{association_type}, association_id, options, self)
end end
alias_method :#{association_type}, :#{association_type}_with_reflection alias_method :#{association_type}, :#{association_type}_with_reflection
end end
end_eval end_eval
......
...@@ -7,11 +7,31 @@ ...@@ -7,11 +7,31 @@
class AssociationsExtensionsTest < Test::Unit::TestCase class AssociationsExtensionsTest < Test::Unit::TestCase
fixtures :projects, :developers, :comments, :posts fixtures :projects, :developers, :comments, :posts
def test_extension_on_has_many
assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent
end
def test_extension_on_habtm def test_extension_on_habtm
assert_equal projects(:action_controller), developers(:david).projects.find_most_recent assert_equal projects(:action_controller), developers(:david).projects.find_most_recent
end end
def test_named_extension_on_habtm
assert_equal projects(:action_controller), developers(:david).projects_extended_by_name.find_most_recent
end
def test_extension_on_has_many def test_marshalling_extensions
assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent david = developers(:david)
assert_equal projects(:action_controller), david.projects.find_most_recent
david = Marshal.load(Marshal.dump(david))
assert_equal projects(:action_controller), david.projects.find_most_recent
end
def test_marshalling_named_extensions
david = developers(:david)
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
david = Marshal.load(Marshal.dump(david))
assert_equal projects(:action_controller), david.projects_extended_by_name.find_most_recent
end end
end end
\ No newline at end of file
module DeveloperProjectsAssociationExtension
def find_most_recent
find(:first, :order => "id DESC")
end
end
class Developer < ActiveRecord::Base class Developer < ActiveRecord::Base
has_and_belongs_to_many :projects, :extend => Module.new { has_and_belongs_to_many :projects do
def find_most_recent def find_most_recent
find(:first, :order => "id DESC") find(:first, :order => "id DESC")
end end
} end
has_and_belongs_to_many :projects_extended_by_name,
:class_name => "Project",
:join_table => "developers_projects",
:association_foreign_key => "project_id",
:extend => DeveloperProjectsAssociationExtension
has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id' has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id'
......
class Post < ActiveRecord::Base class Post < ActiveRecord::Base
belongs_to :author, :extend => Module.new { belongs_to :author do
def greeting def greeting
"hello" "hello"
end end
} end
has_many :comments, :order => "body", :extend => Module.new { has_many :comments, :order => "body" do
def find_most_recent def find_most_recent
find(:first, :order => "id DESC") find(:first, :order => "id DESC")
end end
} end
has_one :very_special_comment has_one :very_special_comment
has_many :special_comments has_many :special_comments
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册