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

Added extension capabilities to has_many and has_and_belongs_to_many proxies [DHH]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2861 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
上级 f109bfb7
*SVN*
* Added extension capabilities to has_many and has_and_belongs_to_many proxies [DHH]. Example:
class Account < ActiveRecord::Base
has_many :people, :extend => Module.new {
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
person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
person.first_name # => "David"
person.last_name # => "Heinemeier Hansson"
Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
* A missing primary key column shouldn't raise an error when generating its error message. [Don Park <don.park@gmail.com>]
* Changed :dbfile to :database for SQLite adapter for consistency (old key still works as an alias) #2644 [Dan Peterson]
......
......@@ -120,6 +120,30 @@ def clear_association_cache #:nodoc:
# Should any of the before_add callbacks throw an exception, the object does not get added to the collection. Same with
# the before_remove callbacks, if an exception is thrown the object doesn't get removed.
#
# === Association extensions
#
# The proxy objects that controls the access to associations can be extended through anonymous modules. This is especially
# beneficial for adding new finders, creators, and other factory-type methods that are only used as part of this associatio.
# Example:
#
# class Account < ActiveRecord::Base
# has_many :people, :extend => Module.new {
# 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
#
# person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson")
# person.first_name # => "David"
# person.last_name # => "Heinemeier Hansson"
#
# Note that the anoymous module must be declared using brackets, not do/end (due to order of evaluation).
#
# == Caching
#
# All of the methods are built on a simple caching principle that will keep the result of the last query around unless specifically
......@@ -282,6 +306,7 @@ module ClassMethods
# 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
# 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".
#
# Option examples:
# has_many :comments, :order => "posted_on"
......@@ -296,7 +321,7 @@ def has_many(association_id, options = {})
options.assert_valid_keys(
:foreign_key, :class_name, :exclusively_dependent, :dependent,
:conditions, :order, :finder_sql, :counter_sql,
:before_add, :after_add, :before_remove, :after_remove
:before_add, :after_add, :before_remove, :after_remove, :extend
)
association_name, association_class_name, association_class_primary_key_name =
......@@ -380,7 +405,7 @@ def has_many(association_id, options = {})
# has_one :last_comment, :class_name => "Comment", :order => "posted_on"
# has_one :project_manager, :class_name => "Person", :conditions => "role = 'project_manager'"
def has_one(association_id, options = {})
options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache)
options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache, :extend)
association_name, association_class_name, association_class_primary_key_name =
associate_identification(association_id, options[:class_name], options[:foreign_key], false)
......@@ -460,7 +485,7 @@ def has_one(association_id, options = {})
# belongs_to :valid_coupon, :class_name => "Coupon", :foreign_key => "coupon_id",
# :conditions => 'discounts > #{payments_count}'
def belongs_to(association_id, options = {})
options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache)
options.assert_valid_keys(:class_name, :foreign_key, :remote, :conditions, :order, :dependent, :counter_cache, :extend)
association_name, association_class_name, class_primary_key_name =
associate_identification(association_id, options[:class_name], options[:foreign_key], false)
......@@ -569,6 +594,7 @@ def belongs_to(association_id, options = {})
# classes with a manual one
# * <tt>:insert_sql</tt> - overwrite the default generated SQL used to add links between the associated classes
# with a manual one
# * <tt>:extend</tt> - anonymous module for extending the proxy, see "Association extensions".
#
# Option examples:
# has_and_belongs_to_many :projects
......@@ -580,7 +606,7 @@ def has_and_belongs_to_many(association_id, options = {})
options.assert_valid_keys(
:class_name, :table_name, :foreign_key, :association_foreign_key, :conditions,
:join_table, :finder_sql, :delete_sql, :insert_sql, :order, :uniq, :before_add, :after_add,
:before_remove, :after_remove
:before_remove, :after_remove, :extend
)
association_name, association_class_name, association_class_primary_key_name =
......
......@@ -2,7 +2,8 @@ module ActiveRecord
module Associations
class AssociationProxy #:nodoc:
alias_method :proxy_respond_to?, :respond_to?
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^send)/ }
alias_method :proxy_extend, :extend
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?|^proxy_respond_to\?|^proxy_extend|^send)/ }
def initialize(owner, association_name, association_class_name, association_class_primary_key_name, options)
@owner = owner
......@@ -11,6 +12,8 @@ def initialize(owner, association_name, association_class_name, association_clas
@association_class = eval(association_class_name, nil, __FILE__, __LINE__)
@association_class_primary_key_name = association_class_primary_key_name
proxy_extend(options[:extend]) if options[:extend]
reset
end
......@@ -95,4 +98,4 @@ def raise_on_type_mismatch(record)
end
end
end
end
end
\ No newline at end of file
require 'abstract_unit'
require 'fixtures/project'
require 'fixtures/developer'
class AssociationsExtensionsTest < Test::Unit::TestCase
fixtures :projects, :developers
def test_extension_on_habtm
assert_equal projects(:action_controller), developers(:david).projects.find_most_recent
end
def test_extension_on_has_many
assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent
end
end
\ No newline at end of file
class Developer < ActiveRecord::Base
has_and_belongs_to_many :projects
has_and_belongs_to_many :projects, :extend => Module.new {
def find_most_recent
find(:first, :order => "id DESC")
end
}
has_and_belongs_to_many :special_projects, :join_table => 'developers_projects', :association_foreign_key => 'project_id'
validates_inclusion_of :salary, :in => 50000..200000
......
class Post < ActiveRecord::Base
belongs_to :author
has_many :comments, :order => "body"
has_one :very_special_comment, :class_name => "VerySpecialComment"
belongs_to :author, :extend => Module.new {
def greeting
"hello"
end
}
has_many :comments, :order => "body", :extend => Module.new {
def find_most_recent
find(:first, :order => "id DESC")
end
}
has_many :special_comments, :class_name => "SpecialComment"
has_and_belongs_to_many :categories
has_and_belongs_to_many :special_categories, :join_table => "categories_posts"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册