aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/lib/active_record/associations.rb19
-rw-r--r--activerecord/lib/active_record/callbacks.rb8
-rw-r--r--activerecord/lib/active_record/persistence.rb13
-rw-r--r--activerecord/lib/active_record/reflection.rb26
-rw-r--r--activerecord/lib/active_record/timestamp.rb13
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb40
-rw-r--r--activerecord/test/cases/associations_test.rb13
-rw-r--r--activerecord/test/cases/timestamp_test.rb19
-rw-r--r--activerecord/test/fixtures/subscriptions.yml2
-rw-r--r--activerecord/test/models/book.rb3
-rw-r--r--activerecord/test/models/electron.rb3
-rw-r--r--activerecord/test/models/liquid.rb5
-rw-r--r--activerecord/test/models/molecule.rb4
-rw-r--r--activerecord/test/schema/schema.rb15
14 files changed, 146 insertions, 37 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index f540aa7f25..fdc203e298 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1437,9 +1437,11 @@ module ActiveRecord
association.replace(new_value)
association
end
-
+
redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
- ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i)
+ pk_column = reflection.primary_key_column
+ ids = (new_value || []).reject { |nid| nid.blank? }
+ ids.map!{ |i| pk_column.type_cast(i) }
send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids))
end
end
@@ -1498,6 +1500,7 @@ module ActiveRecord
end
end
after_save(method_name)
+ after_touch(method_name)
after_destroy(method_name)
end
@@ -1802,9 +1805,7 @@ module ActiveRecord
case associations
when Symbol, String
reflection = base.reflections[associations]
- if reflection && reflection.collection?
- records.each { |record| record.send(reflection.name).target.uniq! }
- end
+ remove_uniq_by_reflection(reflection, records)
when Array
associations.each do |association|
remove_duplicate_results!(base, records, association)
@@ -1812,6 +1813,7 @@ module ActiveRecord
when Hash
associations.keys.each do |name|
reflection = base.reflections[name]
+ remove_uniq_by_reflection(reflection, records)
parent_records = []
records.each do |record|
@@ -1830,6 +1832,7 @@ module ActiveRecord
end
protected
+
def build(associations, parent = nil, join_class = Arel::InnerJoin)
parent ||= @joins.last
case associations
@@ -1852,6 +1855,12 @@ module ActiveRecord
end
end
+ def remove_uniq_by_reflection(reflection, records)
+ if reflection && reflection.collection?
+ records.each { |record| record.send(reflection.name).target.uniq! }
+ end
+ end
+
def build_join_association(reflection, parent)
JoinAssociation.new(reflection, self, parent)
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 637dac450b..82c45a41b0 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -228,7 +228,7 @@ module ActiveRecord
extend ActiveSupport::Concern
CALLBACKS = [
- :after_initialize, :after_find, :before_validation, :after_validation,
+ :after_initialize, :after_find, :after_touch, :before_validation, :after_validation,
:before_save, :around_save, :after_save, :before_create, :around_create,
:after_create, :before_update, :around_update, :after_update,
:before_destroy, :around_destroy, :after_destroy
@@ -238,7 +238,7 @@ module ActiveRecord
extend ActiveModel::Callbacks
include ActiveModel::Validations::Callbacks
- define_model_callbacks :initialize, :find, :only => :after
+ define_model_callbacks :initialize, :find, :touch, :only => :after
define_model_callbacks :save, :create, :update, :destroy
end
@@ -256,6 +256,10 @@ module ActiveRecord
_run_destroy_callbacks { super }
end
+ def touch(*) #:nodoc:
+ _run_touch_callbacks { super }
+ end
+
def deprecated_callback_method(symbol) #:nodoc:
if respond_to?(symbol, true)
ActiveSupport::Deprecation.warn("Overwriting #{symbol} in your models has been deprecated, please use Base##{symbol} :method_name instead")
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 38b91652ee..cbc2220e96 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -218,6 +218,19 @@ module ActiveRecord
self
end
+ # Saves the record with the updated_at/on attributes set to the current time.
+ # Please note that no validation is performed and no callbacks are executed.
+ # If an attribute name is passed, that attribute is updated along with
+ # updated_at/on attributes.
+ #
+ # Examples:
+ #
+ # product.touch # updates updated_at/on
+ # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
+ def touch(attribute = nil)
+ update_attribute(attribute, current_time_from_proper_timezone)
+ end
+
private
def create_or_update
raise ReadOnlyRecord if readonly?
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 03a932f642..7f47a812eb 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -91,25 +91,19 @@ module ActiveRecord
#
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
- def name
- @name
- end
+ attr_reader :name
# Returns the macro type.
#
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
- def macro
- @macro
- end
+ attr_reader :macro
# Returns the hash of options used for the macro.
#
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
# <tt>has_many :clients</tt> returns +{}+
- def options
- @options
- end
+ attr_reader :options
# Returns the class for the macro.
#
@@ -137,11 +131,6 @@ module ActiveRecord
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
end
- # Returns +true+ if +self+ is a +belongs_to+ reflection.
- def belongs_to?
- macro == :belongs_to
- end
-
private
def derive_class_name
name.to_s.camelize
@@ -213,6 +202,10 @@ module ActiveRecord
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
end
+ def primary_key_column
+ @primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
+ end
+
def association_foreign_key
@association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
end
@@ -307,6 +300,11 @@ module ActiveRecord
dependent_conditions
end
+ # Returns +true+ if +self+ is a +belongs_to+ reflection.
+ def belongs_to?
+ macro == :belongs_to
+ end
+
private
def derive_class_name
class_name = name.to_s.camelize
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 92f7a7753d..32b3f03f13 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -31,19 +31,6 @@ module ActiveRecord
class_inheritable_accessor :record_timestamps, :instance_writer => false
self.record_timestamps = true
end
-
- # Saves the record with the updated_at/on attributes set to the current time.
- # Please note that no validation is performed and no callbacks are executed.
- # If an attribute name is passed, that attribute is updated along with
- # updated_at/on attributes.
- #
- # Examples:
- #
- # product.touch # updates updated_at/on
- # product.touch(:designed_at) # updates the designed_at attribute and updated_at/on
- def touch(attribute = nil)
- update_attribute(attribute, current_time_from_proper_timezone)
- end
private
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index e4dd810732..0eaadac5ae 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -14,9 +14,14 @@ require 'models/toy'
require 'models/contract'
require 'models/company'
require 'models/developer'
+require 'models/subscriber'
+require 'models/book'
+require 'models/subscription'
class HasManyThroughAssociationsTest < ActiveRecord::TestCase
- fixtures :posts, :readers, :people, :comments, :authors, :owners, :pets, :toys, :jobs, :references, :companies
+ fixtures :posts, :readers, :people, :comments, :authors,
+ :owners, :pets, :toys, :jobs, :references, :companies,
+ :subscribers, :books, :subscriptions, :developers
# Dummies to force column loads so query counts are clean.
def setup
@@ -383,4 +388,37 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
lambda { authors(:david).very_special_comments.delete(authors(:david).very_special_comments.first) },
].each {|block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) }
end
+
+ def test_collection_singular_ids_getter_with_string_primary_keys
+ book = books(:awdr)
+ assert_equal 2, book.subscriber_ids.size
+ assert_equal [subscribers(:first).nick, subscribers(:second).nick].sort, book.subscriber_ids.sort
+ end
+
+ def test_collection_singular_ids_setter
+ company = companies(:rails_core)
+ dev = Developer.find(:first)
+
+ company.developer_ids = [dev.id]
+ assert_equal [dev], company.developers
+ end
+
+ def test_collection_singular_ids_setter_with_string_primary_keys
+ assert_nothing_raised do
+ book = books(:awdr)
+ book.subscriber_ids = [subscribers(:second).nick]
+ assert_equal [subscribers(:second)], book.subscribers(true)
+
+ book.subscriber_ids = []
+ assert_equal [], book.subscribers(true)
+ end
+
+ end
+
+ def test_collection_singular_ids_setter_raises_exception_when_invalid_ids_set
+ company = companies(:rails_core)
+ ids = [Developer.find(:first).id, -9999]
+ assert_raises(ActiveRecord::RecordNotFound) {company.developer_ids= ids}
+ end
+
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index a1c794c084..d328ca630b 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -14,11 +14,24 @@ require 'models/reader'
require 'models/parrot'
require 'models/ship_part'
require 'models/ship'
+require 'models/liquid'
+require 'models/molecule'
+require 'models/electron'
class AssociationsTest < ActiveRecord::TestCase
fixtures :accounts, :companies, :developers, :projects, :developers_projects,
:computers, :people, :readers
+ def test_eager_loading_should_not_change_count_of_children
+ liquid = Liquid.create(:name => 'salty')
+ molecule = liquid.molecules.create(:name => 'molecule_1')
+ molecule.electrons.create(:name => 'electron_1')
+ molecule.electrons.create(:name => 'electron_2')
+
+ liquids = Liquid.includes(:molecules => :electrons).where('molecules.id is not null')
+ assert_equal 1, liquids[0].molecules.length
+ end
+
def test_loading_the_association_target_should_keep_child_records_marked_for_destruction
ship = Ship.create!(:name => "The good ship Dollypop")
part = ship.parts.create!(:name => "Mast")
diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb
index f765540808..06ab7aa9c7 100644
--- a/activerecord/test/cases/timestamp_test.rb
+++ b/activerecord/test/cases/timestamp_test.rb
@@ -2,9 +2,10 @@ require 'cases/helper'
require 'models/developer'
require 'models/owner'
require 'models/pet'
+require 'models/toy'
class TimestampTest < ActiveRecord::TestCase
- fixtures :developers, :owners, :pets
+ fixtures :developers, :owners, :pets, :toys
def setup
@developer = Developer.first
@@ -82,4 +83,20 @@ class TimestampTest < ActiveRecord::TestCase
ensure
Pet.belongs_to :owner, :touch => true
end
+
+ def test_touching_a_record_touches_parent_record_and_grandparent_record
+ Toy.belongs_to :pet, :touch => true
+ Pet.belongs_to :owner, :touch => true
+
+ toy = Toy.first
+ pet = toy.pet
+ owner = pet.owner
+
+ owner.update_attribute(:updated_at, (time = 3.days.ago))
+ toy.touch
+
+ assert_not_equal time, owner.updated_at
+ ensure
+ Toy.belongs_to :pet
+ end
end
diff --git a/activerecord/test/fixtures/subscriptions.yml b/activerecord/test/fixtures/subscriptions.yml
index 371bfd3422..5a93c12193 100644
--- a/activerecord/test/fixtures/subscriptions.yml
+++ b/activerecord/test/fixtures/subscriptions.yml
@@ -9,4 +9,4 @@ webster_rfr:
alterself_awdr:
id: 3
subscriber_id: alterself
- book_id: 3 \ No newline at end of file
+ book_id: 1
diff --git a/activerecord/test/models/book.rb b/activerecord/test/models/book.rb
index cfd07abddc..1e030b4f59 100644
--- a/activerecord/test/models/book.rb
+++ b/activerecord/test/models/book.rb
@@ -1,4 +1,7 @@
class Book < ActiveRecord::Base
has_many :citations, :foreign_key => 'book1_id'
has_many :references, :through => :citations, :source => :reference_of, :uniq => true
+
+ has_many :subscriptions
+ has_many :subscribers, :through => :subscriptions
end
diff --git a/activerecord/test/models/electron.rb b/activerecord/test/models/electron.rb
new file mode 100644
index 0000000000..35af9f679b
--- /dev/null
+++ b/activerecord/test/models/electron.rb
@@ -0,0 +1,3 @@
+class Electron < ActiveRecord::Base
+ belongs_to :molecule
+end
diff --git a/activerecord/test/models/liquid.rb b/activerecord/test/models/liquid.rb
new file mode 100644
index 0000000000..b96c054f6c
--- /dev/null
+++ b/activerecord/test/models/liquid.rb
@@ -0,0 +1,5 @@
+class Liquid < ActiveRecord::Base
+ set_table_name :liquid
+ has_many :molecules, :uniq => true
+end
+
diff --git a/activerecord/test/models/molecule.rb b/activerecord/test/models/molecule.rb
new file mode 100644
index 0000000000..69325b8d29
--- /dev/null
+++ b/activerecord/test/models/molecule.rb
@@ -0,0 +1,4 @@
+class Molecule < ActiveRecord::Base
+ belongs_to :liquid
+ has_many :electrons
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index f3fd37cd61..fc3810f82b 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -398,6 +398,7 @@ ActiveRecord::Schema.define do
create_table :pets, :primary_key => :pet_id ,:force => true do |t|
t.string :name
t.integer :owner_id, :integer
+ t.timestamps
end
create_table :pirates, :force => true do |t|
@@ -530,6 +531,7 @@ ActiveRecord::Schema.define do
create_table :toys, :primary_key => :toy_id ,:force => true do |t|
t.string :name
t.integer :pet_id, :integer
+ t.timestamps
end
create_table :traffic_lights, :force => true do |t|
@@ -616,6 +618,19 @@ ActiveRecord::Schema.define do
t.datetime :updated_at
end
+ create_table :liquid, :force => true do |t|
+ t.string :name
+ end
+ create_table :molecules, :force => true do |t|
+ t.integer :liquid_id
+ t.string :name
+ end
+ create_table :electrons, :force => true do |t|
+ t.integer :molecule_id
+ t.string :name
+ end
+
+
except 'SQLite' do
# fk_test_has_fk should be before fk_test_has_pk
create_table :fk_test_has_fk, :force => true do |t|