From 22ca50e2822fc9503d6390d546288bd440dcabc7 Mon Sep 17 00:00:00 2001 From: Andre Arko Date: Sat, 31 May 2008 23:19:40 -0700 Subject: [PATCH] Add has_many :primary_key option to allow setting the primary key on a has many association --- activerecord/lib/active_record/associations.rb | 5 +++-- .../associations/has_many_association.rb | 8 ++++++++ .../associations/has_many_associations_test.rb | 4 ++++ activerecord/test/fixtures/companies.yml | 1 + activerecord/test/models/company.rb | 2 ++ activerecord/test/schema/schema.rb | 1 + 6 files changed, 19 insertions(+), 2 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 4b71540..c5e8207 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -663,6 +663,7 @@ module ActiveRecord # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_many+ association will use "person_id" # as the default :foreign_key. + # * :primary_key - Specify the method that returns the primary key used for the association. By default this is +id+. # * :dependent - If set to :destroy all the associated objects are destroyed # alongside this object by calling their +destroy+ method. If set to :delete_all all associated # objects are deleted *without* calling their +destroy+ method. If set to :nullify all associated @@ -678,7 +679,7 @@ module ActiveRecord # * :limit - An integer determining the limit on the number of rows that should be returned. # * :offset - An integer determining the offset from where the rows should be fetched. So at 5, it would skip the first 4 rows. # * :select - By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join - # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will rise an error. + # but not include the joined columns. Do not forget to include the primary and foreign keys, otherwise it will raise an error. # * :as - Specifies a polymorphic interface (See belongs_to). # * :through - Specifies a Join Model through which to perform the query. Options for :class_name and :foreign_key # are ignored, as the association uses the source reflection. You can only use a :through query through a belongs_to @@ -1347,7 +1348,7 @@ module ActiveRecord def create_has_many_reflection(association_id, options, &extension) options.assert_valid_keys( - :class_name, :table_name, :foreign_key, + :class_name, :table_name, :foreign_key, :primary_key, :dependent, :select, :conditions, :include, :order, :group, :limit, :offset, :as, :through, :source, :source_type, diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 37440aa..cb58f0b 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -19,6 +19,14 @@ module ActiveRecord end protected + def owner_quoted_id + if @reflection.options[:primary_key] + quote_value(@owner.send(@reflection.options[:primary_key])) + else + @owner.quoted_id + end + end + def count_records count = if has_cached_counter? @owner.send(:read_attribute, cached_counter_attribute_name) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 247726b..e90edbb 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -129,6 +129,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase assert_equal "Microsoft", Firm.find(:first).clients_like_ms_with_hash_conditions.first.name end + def test_finding_using_primary_key + assert_equal "Summit", Firm.find(:first).clients_using_primary_key.first.name + end + def test_finding_using_sql firm = Firm.find(:first) first_client = firm.clients_using_sql.first diff --git a/activerecord/test/fixtures/companies.yml b/activerecord/test/fixtures/companies.yml index c61128c..e7691fd 100644 --- a/activerecord/test/fixtures/companies.yml +++ b/activerecord/test/fixtures/companies.yml @@ -5,6 +5,7 @@ first_client: client_of: 2 name: Summit ruby_type: Client + firm_name: 37signals first_firm: id: 1 diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index 9fa810a..c45a21b 100755 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -46,6 +46,8 @@ class Firm < Company has_many :clients_using_finder_sql, :class_name => "Client", :finder_sql => 'SELECT * FROM companies WHERE 1=1' has_many :plain_clients, :class_name => 'Client' has_many :readonly_clients, :class_name => 'Client', :readonly => true + has_many :clients_using_primary_key, :class_name => 'Client', + :primary_key => 'name', :foreign_key => 'firm_name' has_one :account, :foreign_key => "firm_id", :dependent => :destroy, :validate => true has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 234c434..29c91a4 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -102,6 +102,7 @@ ActiveRecord::Schema.define do t.string :type t.string :ruby_type t.integer :firm_id + t.string :firm_name t.string :name t.integer :client_of t.integer :rating, :default => 1 -- 1.5.4.4 From 36e14e17734cb501124de1d32f759f6aef114c54 Mon Sep 17 00:00:00 2001 From: Brad Greenlee Date: Mon, 2 Jun 2008 22:04:46 -0700 Subject: [PATCH] Add support for :primary_key option to has_one as well as has_many so that a key other than the default primary key can be used for the association --- activerecord/lib/active_record/associations.rb | 3 ++- .../associations/has_one_association.rb | 11 ++++++++++- .../associations/has_one_associations_test.rb | 7 +++++++ activerecord/test/cases/reflection_test.rb | 6 +++--- activerecord/test/models/company.rb | 1 + 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index c5e8207..856d872 100755 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -759,6 +759,7 @@ module ActiveRecord # * :foreign_key - Specify the foreign key used for the association. By default this is guessed to be the name # of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association will use "person_id" # as the default :foreign_key. + # * :primary_key - Specify the method that returns the primary key used for the association. By default this is +id+. # * :include - Specify second-order associations that should be eager loaded when this object is loaded. # * :as - Specifies a polymorphic interface (See belongs_to). # * :select - By default, this is * as in SELECT * FROM, but can be changed if, for example, you want to do a join @@ -1366,7 +1367,7 @@ module ActiveRecord def create_has_one_reflection(association_id, options) options.assert_valid_keys( - :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate + :class_name, :foreign_key, :remote, :select, :conditions, :order, :include, :dependent, :counter_cache, :extend, :as, :readonly, :validate, :primary_key ) create_reflection(:has_one, association_id, options, self) diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 25a268e..fdc0fa5 100755 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -47,7 +47,16 @@ module ActiveRecord return (obj.nil? ? nil : self) end end - + + protected + def owner_quoted_id + if @reflection.options[:primary_key] + quote_value(@owner.send(@reflection.options[:primary_key])) + else + @owner.quoted_id + end + end + private def find_target @reflection.klass.find(:first, diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index d3ca0ca..aa2a33d 100755 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -29,6 +29,13 @@ class HasOneAssociationsTest < ActiveRecord::TestCase assert_equal Firm.find(1, :include => :account_with_select).account_with_select.attributes.size, 2 end + def test_finding_using_primary_key + firm = companies(:first_firm) + assert_equal Account.find_by_firm_id(firm.id), firm.account + firm.firm_id = companies(:rails_core).id + assert_equal accounts(:rails_core_account), firm.account_using_primary_key + end + def test_can_marshal_has_one_association_with_nil_target firm = Firm.new assert_nothing_raised do diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb index 0c57b79..723062e 100644 --- a/activerecord/test/cases/reflection_test.rb +++ b/activerecord/test/cases/reflection_test.rb @@ -160,9 +160,9 @@ class ReflectionTest < ActiveRecord::TestCase def test_reflection_of_all_associations # FIXME these assertions bust a lot - assert_equal 22, Firm.reflect_on_all_associations.size - assert_equal 17, Firm.reflect_on_all_associations(:has_many).size - assert_equal 5, Firm.reflect_on_all_associations(:has_one).size + assert_equal 24, Firm.reflect_on_all_associations.size + assert_equal 18, Firm.reflect_on_all_associations(:has_many).size + assert_equal 6, Firm.reflect_on_all_associations(:has_one).size assert_equal 0, Firm.reflect_on_all_associations(:belongs_to).size end diff --git a/activerecord/test/models/company.rb b/activerecord/test/models/company.rb index c45a21b..e6aa810 100755 --- a/activerecord/test/models/company.rb +++ b/activerecord/test/models/company.rb @@ -53,6 +53,7 @@ class Firm < Company has_one :unvalidated_account, :foreign_key => "firm_id", :class_name => 'Account', :validate => false has_one :account_with_select, :foreign_key => "firm_id", :select => "id, firm_id", :class_name=>'Account' has_one :readonly_account, :foreign_key => "firm_id", :class_name => "Account", :readonly => true + has_one :account_using_primary_key, :primary_key => "firm_id", :class_name => "Account" end class DependentFirm < Company -- 1.5.4.4