From c8dd66fdcdc2170982fea8a1782c1728f79e3d41 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Sun, 6 Nov 2005 19:05:42 +0000 Subject: Made association extensions use simpler block syntax git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2895 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- activerecord/CHANGELOG | 4 +- activerecord/lib/active_record/associations.rb | 45 +++++++++++++++++++---- activerecord/lib/active_record/reflection.rb | 8 ++-- activerecord/test/associations_extensions_test.rb | 24 +++++++++++- activerecord/test/fixtures/developer.rb | 17 +++++++-- activerecord/test/fixtures/post.rb | 8 ++-- 6 files changed, 84 insertions(+), 22 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 97261e4f66..75af4c8902 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -34,14 +34,14 @@ * 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 { + has_many :people do def find_or_create_by_name(name) first_name, *last_name = name.split last_name = last_name.join " " find_or_create_by_first_name_and_last_name(first_name, last_name) end - } + end end person = Account.find(:first).people.find_or_create_by_name("David Heinemeier Hansson") diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 5a9dc21a78..d4bc2ed9ef 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -127,7 +127,7 @@ module ActiveRecord # Example: # # class Account < ActiveRecord::Base - # has_many :people, :extend => Module.new { + # has_many :people do # def find_or_create_by_name(name) # first_name, *last_name = name.split # last_name = last_name.join " " @@ -135,14 +135,32 @@ module ActiveRecord # find_by_first_name_and_last_name(first_name, last_name) || # create({ :first_name => first_name, :last_name => last_name }) # end - # } + # 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). + # 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 # @@ -306,7 +324,7 @@ module ActiveRecord # associations that depend on multiple tables. Note: When this option is used, +find_in_collection+ is _not_ added. # * :counter_sql - 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. - # * :extend - anonymous module for extending the proxy, see "Association extensions". + # * :extend - specify a named module for extending the proxy, see "Association extensions". # # Option examples: # has_many :comments, :order => "posted_on" @@ -317,13 +335,15 @@ module ActiveRecord # 'FROM people p, post_subscriptions ps ' + # 'WHERE ps.post_id = #{id} AND ps.person_id = p.id ' + # 'ORDER BY p.first_name' - def has_many(association_id, options = {}) + def has_many(association_id, options = {}, &extension) 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, :extend ) + options[:extend] = create_extension_module(association_id, extension) if block_given? + association_name, association_class_name, association_class_primary_key_name = associate_identification(association_id, options[:class_name], options[:foreign_key]) @@ -602,13 +622,15 @@ module ActiveRecord # has_and_belongs_to_many :categories, :join_table => "prods_cats" # 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}' - def has_and_belongs_to_many(association_id, options = {}) + def has_and_belongs_to_many(association_id, options = {}, &extension) 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, :extend ) + options[:extend] = create_extension_module(association_id, extension) if block_given? + association_name, association_class_name, association_class_primary_key_name = associate_identification(association_id, options[:class_name], options[:foreign_key]) @@ -1004,7 +1026,16 @@ module ActiveRecord def condition_word(sql) sql =~ /where/i ? " AND " : "WHERE " 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 diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index de5a01c9be..affbb65ca6 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -21,12 +21,12 @@ module ActiveRecord base.module_eval <<-"end_eval" class << self alias_method :#{association_type}_without_reflection, :#{association_type} - - def #{association_type}_with_reflection(association_id, options = {}) - #{association_type}_without_reflection(association_id, options) + + def #{association_type}_with_reflection(association_id, options = {}, &block) + #{association_type}_without_reflection(association_id, options, &block) reflect_on_all_associations << AssociationReflection.new(:#{association_type}, association_id, options, self) end - + alias_method :#{association_type}, :#{association_type}_with_reflection end end_eval diff --git a/activerecord/test/associations_extensions_test.rb b/activerecord/test/associations_extensions_test.rb index fa9674a149..320432bc28 100644 --- a/activerecord/test/associations_extensions_test.rb +++ b/activerecord/test/associations_extensions_test.rb @@ -7,11 +7,31 @@ require 'fixtures/developer' class AssociationsExtensionsTest < Test::Unit::TestCase 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 assert_equal projects(:action_controller), developers(:david).projects.find_most_recent 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 - assert_equal comments(:more_greetings), posts(:welcome).comments.find_most_recent + def test_marshalling_extensions + 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 \ No newline at end of file diff --git a/activerecord/test/fixtures/developer.rb b/activerecord/test/fixtures/developer.rb index 336b48f087..ce65ff78c0 100644 --- a/activerecord/test/fixtures/developer.rb +++ b/activerecord/test/fixtures/developer.rb @@ -1,10 +1,21 @@ +module DeveloperProjectsAssociationExtension + def find_most_recent + find(:first, :order => "id DESC") + end +end + class Developer < ActiveRecord::Base - has_and_belongs_to_many :projects, :extend => Module.new { + has_and_belongs_to_many :projects do def find_most_recent find(:first, :order => "id DESC") 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' diff --git a/activerecord/test/fixtures/post.rb b/activerecord/test/fixtures/post.rb index 1a34823ca0..1697f0a599 100644 --- a/activerecord/test/fixtures/post.rb +++ b/activerecord/test/fixtures/post.rb @@ -1,15 +1,15 @@ class Post < ActiveRecord::Base - belongs_to :author, :extend => Module.new { + belongs_to :author do def greeting "hello" end - } + end - has_many :comments, :order => "body", :extend => Module.new { + has_many :comments, :order => "body" do def find_most_recent find(:first, :order => "id DESC") end - } + end has_one :very_special_comment has_many :special_comments -- cgit v1.2.3