diff options
Diffstat (limited to 'activerecord')
17 files changed, 117 insertions, 24 deletions
diff --git a/activerecord/lib/active_record/associations/association_collection.rb b/activerecord/lib/active_record/associations/association_collection.rb index a28be9eed1..9061037b39 100644 --- a/activerecord/lib/active_record/associations/association_collection.rb +++ b/activerecord/lib/active_record/associations/association_collection.rb @@ -344,7 +344,7 @@ module ActiveRecord callback(:before_add, record) yield(record) if block_given? @target ||= [] unless loaded? - @target << record + @target << record unless @reflection.options[:uniq] && @target.include?(record) callback(:after_add, record) record end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index cf7ae97452..8e40b331d9 100755 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -2599,7 +2599,7 @@ module ActiveRecord #:nodoc: removed_attributes = attributes.keys - safe_attributes.keys if removed_attributes.any? - logger.debug "WARNING: Can't mass-assign these protected attributes: #{removed_attributes.join(', ')}" + log_protected_attribute_removal(removed_attributes) end safe_attributes @@ -2614,6 +2614,10 @@ module ActiveRecord #:nodoc: end end + def log_protected_attribute_removal(*attributes) + logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}" + end + # The primary key and inheritance column can never be set by mass-assignment for security reasons. def attributes_protected_by_default default = [ self.class.primary_key, self.class.inheritance_column ] @@ -2627,8 +2631,15 @@ module ActiveRecord #:nodoc: quoted = {} connection = self.class.connection attribute_names.each do |name| - if column = column_for_attribute(name) - quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary + if (column = column_for_attribute(name)) && (include_primary_key || !column.primary) + value = read_attribute(name) + + # We need explicit to_yaml because quote() does not properly convert Time/Date fields to YAML. + if value && self.class.serialized_attributes.has_key?(name) && (value.acts_like?(:date) || value.acts_like?(:time)) + value = value.to_yaml + end + + quoted[name] = connection.quote(value, column) end end include_readonly_attributes ? quoted : remove_readonly_attributes(quoted) diff --git a/activerecord/lib/active_record/calculations.rb b/activerecord/lib/active_record/calculations.rb index 34ffc9a5e5..e765b46cc2 100644 --- a/activerecord/lib/active_record/calculations.rb +++ b/activerecord/lib/active_record/calculations.rb @@ -211,7 +211,7 @@ module ActiveRecord sql << " ORDER BY #{options[:order]} " if options[:order] add_limit!(sql, options, scope) - sql << ')' if use_workaround + sql << ') AS #{aggregate_alias}_subquery' if use_workaround sql end diff --git a/activerecord/lib/active_record/dirty.rb b/activerecord/lib/active_record/dirty.rb index 4ce0356457..63bf8c8f5b 100644 --- a/activerecord/lib/active_record/dirty.rb +++ b/activerecord/lib/active_record/dirty.rb @@ -134,7 +134,9 @@ module ActiveRecord def update_with_dirty if partial_updates? - update_without_dirty(changed) + # Serialized attributes should always be written in case they've been + # changed in place. + update_without_dirty(changed | self.class.serialized_attributes.keys) else update_without_dirty end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 731a350854..fd77f27b77 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -349,6 +349,27 @@ module ActiveRecord end end + # MigrationProxy is used to defer loading of the actual migration classes + # until they are needed + class MigrationProxy + + attr_accessor :name, :version, :filename + + delegate :migrate, :announce, :write, :to=>:migration + + private + + def migration + @migration ||= load_migration + end + + def load_migration + load(filename) + name.constantize + end + + end + class Migrator#:nodoc: class << self def migrate(migrations_path, target_version = nil) @@ -437,7 +458,7 @@ module ActiveRecord runnable.pop if down? && !target.nil? runnable.each do |migration| - Base.logger.info "Migrating to #{migration} (#{migration.version})" + Base.logger.info "Migrating to #{migration.name} (#{migration.version})" # On our way up, we skip migrating the ones we've already migrated # On our way down, we skip reverting the ones we've never migrated @@ -470,11 +491,10 @@ module ActiveRecord raise DuplicateMigrationNameError.new(name.camelize) end - load(file) - - klasses << returning(name.camelize.constantize) do |klass| - class << klass; attr_accessor :version end - klass.version = version + klasses << returning(MigrationProxy.new) do |migration| + migration.name = name.camelize + migration.version = version + migration.filename = file end end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index 7f274543b6..eb887ee550 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -103,7 +103,7 @@ module ActiveRecord attr_reader :proxy_scope, :proxy_options [].methods.each do |m| - unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?)/ + unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?|any?|respond_to?)/ delegate m, :to => :proxy_found end end @@ -140,6 +140,10 @@ module ActiveRecord @found ? @found.empty? : count.zero? end + def respond_to?(method) + super || @proxy_scope.respond_to?(method) + end + def any? if block_given? proxy_found.any? { |*block_args| yield(*block_args) } diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 3631be76a0..1f8a1090eb 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -9,7 +9,7 @@ require 'models/topic' require 'models/reply' class CascadedEagerLoadingTest < ActiveRecord::TestCase - fixtures :authors, :mixins, :companies, :posts, :topics + fixtures :authors, :mixins, :companies, :posts, :topics, :accounts, :comments, :categorizations def test_eager_association_loading_with_cascaded_two_levels authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id") diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index f65ada550b..58506574f8 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -21,7 +21,7 @@ class EagerAssociationTest < ActiveRecord::TestCase fixtures :posts, :comments, :authors, :categories, :categories_posts, :companies, :accounts, :tags, :taggings, :people, :readers, :owners, :pets, :author_favorites, :jobs, :references, :subscribers, :subscriptions, :books, - :developers, :projects + :developers, :projects, :developers_projects def test_loading_with_one_association posts = Post.find(:all, :include => :comments) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index b29df68d22..f71b122ff0 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -70,7 +70,7 @@ end class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :categories, :posts, :categories_posts, :developers, :projects, :developers_projects, - :parrots, :pirates, :treasures, :price_estimates + :parrots, :pirates, :treasures, :price_estimates, :tags, :taggings def test_has_and_belongs_to_many david = Developer.find(1) @@ -299,6 +299,17 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_equal 3, projects(:active_record, :reload).developers.size end + def test_uniq_option_prevents_duplicate_push + project = projects(:active_record) + project.developers << developers(:jamis) + project.developers << developers(:david) + assert_equal 3, project.developers.size + + project.developers << developers(:david) + project.developers << developers(:jamis) + assert_equal 3, project.developers.size + end + def test_deleting david = Developer.find(1) active_record = Project.find(1) diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 47e4b3527d..b806e885e1 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -14,7 +14,7 @@ require 'models/reader' class HasManyAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :categories, :companies, :developers, :projects, :developers_projects, :topics, :authors, :comments, :author_addresses, - :people, :posts + :people, :posts, :readers def setup Client.destroyed_client_ids.clear diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index e6d1b5ddfd..36d30ade5e 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -76,7 +76,7 @@ class TopicWithProtectedContentAndAccessibleAuthorName < ActiveRecord::Base end class BasicsTest < ActiveRecord::TestCase - fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations + fixtures :topics, :companies, :developers, :projects, :computers, :accounts, :minimalistics, 'warehouse-things', :authors, :categorizations, :categories def test_table_exists assert !NonExistentTable.table_exists? @@ -1361,6 +1361,12 @@ class BasicsTest < ActiveRecord::TestCase assert_equal(myobj, topic.content) end + def test_serialized_time_attribute + myobj = Time.local(2008,1,1,1,0) + topic = Topic.create("content" => myobj).reload + assert_equal(myobj, topic.content) + end + def test_nil_serialized_attribute_with_class_constraint myobj = MyObject.new('value1', 'value2') topic = Topic.new diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index e5e022050d..feb47a15a8 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -191,6 +191,18 @@ class DirtyTest < ActiveRecord::TestCase assert !pirate.changed? end + def test_save_should_store_serialized_attributes_even_with_partial_updates + with_partial_updates(Topic) do + topic = Topic.create!(:content => {:a => "a"}) + topic.content[:b] = "b" + #assert topic.changed? # Known bug, will fail + topic.save! + assert_equal "b", topic.content[:b] + topic.reload + assert_equal "b", topic.content[:b] + end + end + private def with_partial_updates(klass, on = true) old = klass.partial_updates? diff --git a/activerecord/test/cases/lifecycle_test.rb b/activerecord/test/cases/lifecycle_test.rb index ab005c6b00..54fb3d8c39 100644 --- a/activerecord/test/cases/lifecycle_test.rb +++ b/activerecord/test/cases/lifecycle_test.rb @@ -74,7 +74,7 @@ class MultiObserver < ActiveRecord::Observer end class LifecycleTest < ActiveRecord::TestCase - fixtures :topics, :developers + fixtures :topics, :developers, :minimalistics def test_before_destroy original_count = Topic.count diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index d6b3e341df..ee66ac948d 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -6,7 +6,7 @@ require 'models/post' require 'models/category' class MethodScopingTest < ActiveRecord::TestCase - fixtures :developers, :projects, :comments, :posts + fixtures :developers, :projects, :comments, :posts, :developers_projects def test_set_conditions Developer.with_scope(:find => { :conditions => 'just a test...' }) do diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 7ecf755ef8..920f719995 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -922,6 +922,26 @@ if ActiveRecord::Base.connection.supports_migrations? migrations[0].name == 'innocent_jointable' end + def test_only_loads_pending_migrations + # migrate up to 1 + ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) + + # now unload the migrations that have been defined + PeopleHaveLastNames.unloadable + ActiveSupport::Dependencies.remove_unloadable_constants! + + ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid", nil) + + assert !defined? PeopleHaveLastNames + + %w(WeNeedReminders, InnocentJointable).each do |migration| + assert defined? migration + end + + ensure + load(MIGRATIONS_ROOT + "/valid/1_people_have_last_names.rb") + end + def test_migrator_interleaved_migrations ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1") diff --git a/activerecord/test/cases/named_scope_test.rb b/activerecord/test/cases/named_scope_test.rb index e21ffbbdba..bd6ec23853 100644 --- a/activerecord/test/cases/named_scope_test.rb +++ b/activerecord/test/cases/named_scope_test.rb @@ -45,6 +45,12 @@ class NamedScopeTest < ActiveRecord::TestCase assert_equal Topic.average(:replies_count), Topic.base.average(:replies_count) end + def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy + assert Topic.approved.respond_to?(:proxy_found) + assert Topic.approved.respond_to?(:count) + assert Topic.approved.respond_to?(:length) + end + def test_subclasses_inherit_scopes assert Topic.scopes.include?(:base) @@ -186,9 +192,10 @@ class NamedScopeTest < ActiveRecord::TestCase def test_any_should_not_load_results topics = Topic.base - assert_queries(1) do - topics.expects(:empty?).returns(true) - assert !topics.any? + assert_queries(2) do + topics.any? # use count query + topics.collect # force load + topics.any? # use loaded (no query) end end diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index dc9eeec281..eae2104531 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -58,7 +58,7 @@ end uses_mocha 'QueryCacheExpiryTest' do class QueryCacheExpiryTest < ActiveRecord::TestCase - fixtures :tasks + fixtures :tasks, :posts, :categories, :categories_posts def test_find Task.connection.expects(:clear_query_cache).times(1) |