diff options
Diffstat (limited to 'activerecord')
32 files changed, 344 insertions, 66 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 291e3539c4..2ac5287268 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,4 +1,132 @@ -## Rails 3.2.12 (unreleased) ## +## unreleased ## + +* Reload the association target if it's stale. `@stale_state` should be nil + when a model isn't saved. + Fixes #7526. + + *Larry Lv* + +* Don't read CSV files during execution of `db:fixtures:load`. CSV support for + fixtures was removed some time ago but the task was still loading them, even + though later the code was looking for the related yaml file instead. + + *kennyj* + + +## Rails 3.2.13.rc1 (Feb 17, 2013) ## + +* Reverted 921a296a3390192a71abeec6d9a035cc6d1865c8, 'Quote numeric values + compared to string columns.' This caused several regressions. + + *Steve Klabnik* + +* Fix overriding of attributes by `default_scope` on `ActiveRecord::Base#dup`. + + *Hiroshige UMINO* + +* Fix issue with overriding Active Record reader methods with a composed object + and using that attribute as the scope of a `uniqueness_of` validation. + Backport #7072. + + *Peter Brown* + +* Sqlite now preserves custom primary keys when copying or altering tables. + Fixes #9367. + Backport #2312. + + *Sean Scally + Yves Senn* + +* Preloading `has_many :through` associations with conditions won't + cache the `:through` association. This will prevent invalid + subsets to be cached. + Fixes #8423. + Backport #9252. + + Example: + + class User + has_many :posts + has_many :recent_comments, -> { where('created_at > ?', 1.week.ago) }, :through => :posts + end + + a_user = User.includes(:recent_comments).first + + # this is preloaded + a_user.recent_comments + + # fetching the recent_comments through the posts association won't preload it. + a_user.posts + + *Yves Senn* + +* Fix handling of dirty time zone aware attributes + + Previously, when `time_zone_aware_attributes` were enabled, after + changing a datetime or timestamp attribute and then changing it back + to the original value, `changed_attributes` still tracked the + attribute as changed. This caused `[attribute]_changed?` and + `changed?` methods to return true incorrectly. + + Example: + + in_time_zone 'Paris' do + order = Order.new + original_time = Time.local(2012, 10, 10) + order.shipped_at = original_time + order.save + order.changed? # => false + + # changing value + order.shipped_at = Time.local(2013, 1, 1) + order.changed? # => true + + # reverting to original value + order.shipped_at = original_time + order.changed? # => false, used to return true + end + + Backport of #9073 + Fixes #8898 + + *Lilibeth De La Cruz* + +* Fix counter cache columns not updated when replacing `has_many :through` + associations. + Backport #8400. + Fix #7630. + + *Matthew Robertson* + +* Don't update `column_defaults` when calling destructive methods on column with default value. + Backport c517602. + Fix #6115. + + *Piotr Sarnacki + Aleksey Magusev + Alan Daud* + +* When `#count` is used in conjunction with `#uniq` we perform `count(:distinct => true)`. + Fix #6865. + + Example: + + relation.uniq.count # => SELECT COUNT(DISTINCT *) + + *Yves Senn + Kaspar Schiess* + +* Fix `ActiveRecord::Relation#pluck` when columns or tables are reserved words. + Backport #7536. + Fix #8968. + + *Ian Lesperance + Yves Senn + Kaspar Schiess* + +* Don't run explain on slow queries for database adapters that don't support it. + Backport #6197. + + *Blake Smith* + +* Revert round usec when comparing timestamp attributes in the dirty tracking. + Fixes #8460. + + *Andrew White* * Revert creation of through association models when using `collection=[]` on a `has_many :through` association from an unsaved model. @@ -179,6 +307,20 @@ *Gabriel Sobrinho, Ricardo Henrique* + +## Rails 3.2.12 (Feb 11, 2013) ## + +* Quote numeric values being compared to non-numeric columns. Otherwise, + in some database, the string column values will be coerced to a numeric + allowing 0, 0.0 or false to match any string starting with a non-digit. + + Example: + + App.where(apikey: 0) # => SELECT * FROM users WHERE apikey = '0' + + *Dylan Smith* + + ## Rails 3.2.11 (Jan 8, 2013) ## * Fix querying with an empty hash *Damien Mathieu* [CVE-2013-0155] diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index ab0d888b16..99f307922e 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -46,6 +46,7 @@ module ActiveRecord @loaded = false IdentityMap.remove(target) if IdentityMap.enabled? && target @target = nil + @stale_state = nil end # Reloads the \target and returns +self+ on success. @@ -128,16 +129,21 @@ module ActiveRecord # This method is abstract in the sense that it relies on +find_target+, # which is expected to be provided by descendants. # - # If the \target is already \loaded it is just returned. Thus, you can call - # +load_target+ unconditionally to get the \target. + # If the \target is stale(the target no longer points to the record(s) that the + # relevant foreign_key(s) refers to.), force reload the \target. + # + # Otherwise if the \target is already \loaded it is just returned. Thus, you can + # call +load_target+ unconditionally to get the \target. # # ActiveRecord::RecordNotFound is rescued within the method, and it is # not reraised. The proxy is \reset and +nil+ is the return value. def load_target - if find_target? + if (@stale_state && stale_target?) || find_target? begin if IdentityMap.enabled? && association_class && association_class.respond_to?(:base_class) @target = IdentityMap.get(association_class, owner[reflection.foreign_key]) + elsif @stale_state && stale_target? + @target = find_target end rescue NameError nil diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index 97f531d064..52c67df646 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -72,7 +72,7 @@ module ActiveRecord end def stale_state - owner[reflection.foreign_key].to_s + owner[reflection.foreign_key] && owner[reflection.foreign_key].to_s end end end diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb index 2ee5dbbd70..88ce03a3cd 100644 --- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb @@ -27,7 +27,8 @@ module ActiveRecord end def stale_state - [super, owner[reflection.foreign_type].to_s] + foreign_key = super + foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s] end end end diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb index 53d49fef2e..2b9e7ea55d 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -139,6 +139,11 @@ module ActiveRecord delete_through_records(records) + if source_reflection.options[:counter_cache] + counter = source_reflection.counter_cache_column + klass.decrement_counter counter, records.map(&:id) + end + if through_reflection.macro == :has_many && update_through_counter?(method) update_counter(-count, through_reflection) end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index ad6374d09a..4cb7b56b57 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -37,7 +37,8 @@ module ActiveRecord through_records = Array.wrap(owner.send(through_reflection.name)) # Dont cache the association - we would only be caching a subset - if reflection.options[:source_type] && through_reflection.collection? + if (preload_options != through_options) || + (reflection.options[:source_type] && through_reflection.collection?) owner.association(through_reflection.name).reset end diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index fd0e90aaf0..be890e5767 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -62,7 +62,7 @@ module ActiveRecord # properly support stale-checking for nested associations. def stale_state if through_reflection.macro == :belongs_to - owner[through_reflection.foreign_key].to_s + owner[through_reflection.foreign_key] && owner[through_reflection.foreign_key].to_s end end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 39d81cf6ef..6f3112e654 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -42,14 +42,11 @@ module ActiveRecord unless time.acts_like?(:time) time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time end - zoned_time = time && time.in_time_zone rescue nil - rounded_time = round_usec(zoned_time) - rounded_value = round_usec(read_attribute("#{attr_name}")) - if (rounded_value != rounded_time) || (!rounded_value && original_time) - write_attribute("#{attr_name}", original_time) - #{attr_name}_will_change! - @attributes_cache["#{attr_name}"] = zoned_time - end + time = time.in_time_zone rescue nil if time + previous_time = attribute_changed?("#{attr_name}") ? changed_attributes["#{attr_name}"] : read_attribute(:#{attr_name}) + write_attribute(:#{attr_name}, original_time) + #{attr_name}_will_change! if previous_time != time + @attributes_cache["#{attr_name}"] = time end EOV generated_attribute_methods.module_eval(method_body, __FILE__, line) @@ -63,12 +60,6 @@ module ActiveRecord time_zone_aware_attributes && !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && column.type.in?([:datetime, :timestamp]) end end - - private - def round_usec(value) - return unless value - value.change(:usec => 0) - end end end end diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 612114bbce..5bd1721439 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -479,7 +479,8 @@ module ActiveRecord #:nodoc: # # Instantiates a single new object bypassing mass-assignment security # User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true) def initialize(attributes = nil, options = {}) - @attributes = self.class.initialize_attributes(self.class.column_defaults.dup) + defaults = Hash[self.class.column_defaults.map { |k, v| [k, v.duplicable? ? v.dup : v] }] + @attributes = self.class.initialize_attributes(defaults) @association_cache = {} @aggregation_cache = {} @attributes_cache = {} @@ -552,7 +553,6 @@ module ActiveRecord #:nodoc: @new_record = true ensure_proper_type - populate_with_current_scope_attributes super end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index ddb6896257..6a5cff6acd 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -42,8 +42,8 @@ module ActiveRecord # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # - # Inside migration files, the +t+ object in +create_table+ and - # +change_table+ is actually of this type: + # Inside migration files, the +t+ object in +create_table+ + # is actually of this type: # # class SomeMigration < ActiveRecord::Migration # def up diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index ca84c95bdc..455560aa55 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -490,7 +490,11 @@ module ActiveRecord end def copy_table(from, to, options = {}) #:nodoc: - options = options.merge(:id => (!columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == primary_key(from).to_s)) + from_primary_key = primary_key(from) + options[:primary_key] = from_primary_key if from_primary_key != 'id' + unless options[:primary_key] + options[:id] = !columns(from).detect{|c| c.name == 'id'}.nil? && 'id' == from_primary_key + end create_table(to, options) do |definition| @definition = definition columns(from).each do |column| @@ -504,7 +508,7 @@ module ActiveRecord :precision => column.precision, :scale => column.scale, :null => column.null) end - @definition.primary_key(primary_key(from)) if primary_key(from) + @definition.primary_key(from_primary_key) if from_primary_key yield @definition if block_given? end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index bdccf535ea..236834fd84 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -11,11 +11,12 @@ module ActiveRecord end end - # If auto explain is enabled, this method triggers EXPLAIN logging for the - # queries triggered by the block if it takes more than the threshold as a - # whole. That is, the threshold is not checked against each individual - # query, but against the duration of the entire block. This approach is - # convenient for relations. + # If the database adapter supports explain and auto explain is enabled, + # this method triggers EXPLAIN logging for the queries triggered by the + # block if it takes more than the threshold as a whole. That is, the + # threshold is not checked against each individual query, but against the + # duration of the entire block. This approach is convenient for relations. + # # The available_queries_for_explain thread variable collects the queries # to be explained. If the value is nil, it means queries are not being @@ -26,7 +27,7 @@ module ActiveRecord threshold = auto_explain_threshold_in_seconds current = Thread.current - if threshold && current[:available_queries_for_explain].nil? + if connection.supports_explain? && threshold && current[:available_queries_for_explain].nil? begin queries = current[:available_queries_for_explain] = [] start = Time.now diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 13b7c6e214..055d27d85c 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -83,6 +83,13 @@ module ActiveRecord end end + initializer "active_record.validate_explain_support" do |app| + if app.config.active_record[:auto_explain_threshold_in_seconds] && + !ActiveRecord::Base.connection.supports_explain? + warn "auto_explain_threshold_in_seconds is set but will be ignored because your adapter does not support this feature. Please unset the configuration to avoid this warning." + end + end + # Expose database runtime to controller for logging. initializer "active_record.log_runtime" do |app| require "active_record/railties/controller_runtime" diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 1bdb841398..7f1b84b848 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -355,7 +355,7 @@ db_namespace = namespace :db do base_dir = File.join [Rails.root, ENV['FIXTURES_PATH'] || %w{test fixtures}].flatten fixtures_dir = File.join [base_dir, ENV['FIXTURES_DIR']].compact - (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.{yml,csv}"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| + (ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir["#{fixtures_dir}/**/*.yml"].map {|f| f[(fixtures_dir.size + 1)..-5] }).each do |fixture_file| ActiveRecord::Fixtures.create_fixtures(fixtures_dir, fixture_file) end end @@ -433,11 +433,11 @@ db_namespace = namespace :db do when 'sqlserver' `smoscript -s #{config['host']} -d #{config['database']} -u #{config['username']} -p #{config['password']} -f #{filename} -A -U` when "firebird" - set_firebird_env(abcs[Rails.env]) - db_string = firebird_db_string(abcs[Rails.env]) + set_firebird_env(config) + db_string = firebird_db_string(config) sh "isql -a #{db_string} > #{filename}" else - raise "Task not supported by '#{abcs[Rails.env]["adapter"]}'" + raise "Task not supported by '#{config['adapter']}'" end if ActiveRecord::Base.connection.supports_migrations? diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index ceacf93b60..1f9dbdc3d4 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -178,7 +178,7 @@ module ActiveRecord # def pluck(column_name) if column_name.is_a?(Symbol) && column_names.include?(column_name.to_s) - column_name = "#{table_name}.#{column_name}" + column_name = "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}" else column_name = column_name.to_s end @@ -195,7 +195,8 @@ module ActiveRecord def perform_calculation(operation, column_name, options = {}) operation = operation.to_s.downcase - distinct = options[:distinct] + # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count) + distinct = options[:distinct] || self.uniq_value if operation == "count" column_name ||= (select_for_count || :all) diff --git a/activerecord/lib/active_record/result.rb b/activerecord/lib/active_record/result.rb index 9ceab2eabc..b8d2cd2866 100644 --- a/activerecord/lib/active_record/result.rb +++ b/activerecord/lib/active_record/result.rb @@ -26,9 +26,15 @@ module ActiveRecord private def hash_rows - @hash_rows ||= @rows.map { |row| - Hash[@columns.zip(row)] - } + @hash_rows ||= + begin + # We freeze the strings to prevent them getting duped when + # used as keys in ActiveRecord::Model's @attributes hash + columns = @columns.map { |c| c.dup.freeze } + @rows.map { |row| + Hash[columns.zip(row)] + } + end end end end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index 154aacef9f..795088530e 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -26,7 +26,7 @@ module ActiveRecord relation = relation.and(table[finder_class.primary_key.to_sym].not_eq(record.send(:id))) if record.persisted? Array.wrap(options[:scope]).each do |scope_item| - scope_value = record.send(scope_item) + scope_value = record.read_attribute(scope_item) relation = relation.and(table[scope_item].eq(scope_value)) end diff --git a/activerecord/lib/active_record/version.rb b/activerecord/lib/active_record/version.rb index ff9fa279f4..a340cfaf7d 100644 --- a/activerecord/lib/active_record/version.rb +++ b/activerecord/lib/active_record/version.rb @@ -2,7 +2,7 @@ module ActiveRecord module VERSION #:nodoc: MAJOR = 3 MINOR = 2 - TINY = 11 + TINY = 12 PRE = nil STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') diff --git a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb index 575b4806c1..74288a98d1 100644 --- a/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb +++ b/activerecord/test/cases/adapters/sqlite3/copy_table_test.rb @@ -57,6 +57,14 @@ class CopyTableTest < ActiveRecord::TestCase end end + def test_copy_table_with_unconventional_primary_key + test_copy_table('owners', 'owners_unconventional') do |from, to, options| + original_pk = @connection.primary_key('owners') + copied_pk = @connection.primary_key('owners_unconventional') + assert_equal original_pk, copied_pk + end + end + protected def copy_table(from, to, options = {}) @connection.copy_table(from, to, {:temporary => true}.merge(options)) diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index ee338a3b99..6556219205 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -11,8 +11,8 @@ if ActiveRecord::Base.connection.supports_migrations? def teardown @connection.drop_table :fruits rescue nil - @connection.drop_table :"_pre_fruits_suf_" rescue nil - @connection.drop_table :"_pre_schema_migrations_suf_" rescue nil + @connection.drop_table :"_p_fruits_s_" rescue nil + @connection.drop_table :"_p_schema_migrations_s_" rescue nil end def test_schema_define @@ -24,8 +24,9 @@ if ActiveRecord::Base.connection.supports_migrations? end def test_schema_define_with_table_prefix_and_suffix - ActiveRecord::Base.table_name_prefix = '_pre_' - ActiveRecord::Base.table_name_suffix = '_suf_' + # Use shorter prefix and suffix as in Oracle database identifier cannot be larger than 30 characters + ActiveRecord::Base.table_name_prefix = '_p_' + ActiveRecord::Base.table_name_suffix = '_s_' perform_schema_define! diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb index f392366c19..c9b26895ae 100644 --- a/activerecord/test/cases/associations/belongs_to_associations_test.rb +++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb @@ -14,6 +14,8 @@ require 'models/sponsor' require 'models/member' require 'models/essay' require 'models/toy' +require 'models/person' +require 'models/reader' class BelongsToAssociationsTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, @@ -716,4 +718,16 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase assert_equal toy, sponsor.reload.sponsorable end + + def test_saving_nested_association + post1, post2 = Post.limit(2) + person = Person.new(:first_name => 'foo') + reader = Reader.new(:post => post1) + + reader.post_id = post2.id + person.readers = [reader] + + assert person.save + assert_equal reader.post_id, post2.id + end end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 20c6b691fc..944f135153 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -1112,4 +1112,10 @@ class EagerAssociationTest < ActiveRecord::TestCase Post.includes(:comments).order(nil).where(:comments => {:body => "Thank you for the welcome"}).first end end + + test "preloading does not cache has many association subset when preloaded with a through association" do + author = Author.order(:id).includes(:comments_with_order_and_conditions, :posts).first + assert_no_queries { assert_equal 2, author.comments_with_order_and_conditions.size } + assert_no_queries { assert_equal 5, author.posts.size, "should not cache a subset of the association" } + end end 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 40d2e9568d..277a3bddaa 100644 --- a/activerecord/test/cases/associations/has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb @@ -346,6 +346,17 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_update_counter_caches_on_replace_association + post = posts(:welcome) + tag = post.tags.create!(:name => 'doomed') + tag.tagged_posts << posts(:thinking) + + tag.tagged_posts = [] + post.reload + + assert_equal(post.taggings.count, post.taggings_count) + end + def test_replace_association assert_queries(4){posts(:welcome);people(:david);people(:michael); posts(:welcome).people(true)} diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index b849eead26..97d6c0cf88 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -325,6 +325,12 @@ class BasicsTest < ActiveRecord::TestCase assert parrot.valid? end + def test_default_values_are_deeply_dupped + company = Company.new + company.description << "foo" + assert_equal "", Company.new.description + end + def test_load topics = Topic.find(:all, :order => 'id') assert_equal(4, topics.size) @@ -2148,7 +2154,7 @@ class BasicsTest < ActiveRecord::TestCase end def test_attribute_names - assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id"], + assert_equal ["id", "type", "ruby_type", "firm_id", "firm_name", "name", "client_of", "rating", "account_id", "description"], Company.attribute_names end diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb index f931b39998..a1dc1de38d 100644 --- a/activerecord/test/cases/calculations_test.rb +++ b/activerecord/test/cases/calculations_test.rb @@ -1,10 +1,11 @@ require "cases/helper" +require 'models/club' require 'models/company' require "models/contract" -require 'models/topic' require 'models/edge' -require 'models/club' require 'models/organization' +require 'models/possession' +require 'models/topic' Company.has_many :accounts @@ -368,6 +369,10 @@ class CalculationsTest < ActiveRecord::TestCase assert_equal 5, Account.count(:firm_id) end + def test_count_with_uniq + assert_equal 4, Account.select(:credit_limit).uniq.count + end + def test_count_with_column_and_options_parameter assert_equal 2, Account.count(:firm_id, :conditions => "credit_limit = 50 AND firm_id IS NOT NULL") end @@ -503,4 +508,10 @@ class CalculationsTest < ActiveRecord::TestCase Company.create!(:name => "test", :contracts => [Contract.new(:developer_id => 7)]) assert_equal [7], Company.joins(:contracts).pluck(:developer_id).map(&:to_i) end + + def test_pluck_with_reserved_words + Possession.create!(:where => "Over There") + + assert_equal ["Over There"], Possession.pluck(:where) + end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 86a28d95ad..9756c182b9 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -76,6 +76,8 @@ class DirtyTest < ActiveRecord::TestCase assert pirate.created_on_changed? assert_kind_of ActiveSupport::TimeWithZone, pirate.created_on_was assert_equal old_created_on, pirate.created_on_was + pirate.created_on = old_created_on + assert !pirate.created_on_changed? end end @@ -549,18 +551,17 @@ class DirtyTest < ActiveRecord::TestCase end end - def test_setting_time_attributes_with_time_zone_field_to_same_time_should_not_be_marked_as_a_change + def test_datetime_attribute_can_be_updated_with_fractional_seconds in_time_zone 'Paris' do target = Class.new(ActiveRecord::Base) - target.table_name = 'pirates' + target.table_name = 'topics' - created_on = Time.now + written_on = Time.utc(2012, 12, 1, 12, 0, 0).in_time_zone('Paris') - pirate = target.create(:created_on => created_on) - pirate.reload # Here mysql truncate the usec value to 0 + topic = target.create(:written_on => written_on) + topic.written_on += 0.3 - pirate.created_on = created_on - assert !pirate.created_on_changed? + assert topic.written_on_changed?, 'Fractional second update not detected' end end diff --git a/activerecord/test/cases/dup_test.rb b/activerecord/test/cases/dup_test.rb index b2a3cb5733..17a02f139a 100644 --- a/activerecord/test/cases/dup_test.rb +++ b/activerecord/test/cases/dup_test.rb @@ -113,5 +113,14 @@ module ActiveRecord assert topic.invalid? assert duped.valid? end + + def test_dup_with_default_scope + prev_default_scopes = Topic.default_scopes + Topic.default_scopes = [Topic.where(:approved => true)] + topic = Topic.new(:approved => false) + assert !topic.dup.approved?, "should not be overriden by default scopes" + ensure + Topic.default_scopes = prev_default_scopes + end end end diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index cb7781f8e7..bdeb0a033c 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -98,6 +98,16 @@ if ActiveRecord::Base.connection.supports_explain? assert_equal expected, base.exec_explain(queries) end + def test_unsupported_connection_adapter + connection.stubs(:supports_explain?).returns(false) + + base.logger.expects(:warn).never + + with_threshold(0) do + Car.where(:name => 'honda').to_a + end + end + def test_silence_auto_explain base.expects(:collecting_sqls_for_explain).never base.logger.expects(:warn).never diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index 7bb71b7b6e..7f0d921545 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -61,7 +61,7 @@ if ActiveRecord::Base.connection.supports_migrations? ActiveRecord::Base.connection.initialize_schema_migrations_table ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}" - %w(things awesome_things prefix_things_suffix prefix_awesome_things_suffix).each do |table| + %w(things awesome_things prefix_things_suffix p_awesome_things_s).each do |table| Thing.connection.drop_table(table) rescue nil end Thing.reset_column_information @@ -874,8 +874,6 @@ if ActiveRecord::Base.connection.supports_migrations? end def test_remove_column_with_array_as_an_argument_is_deprecated - return skip "remove_column with array as argument is not supported with OracleAdapter" if current_adapter? :OracleAdapter - ActiveRecord::Base.connection.create_table(:hats) do |table| table.column :hat_name, :string, :limit => 100 table.column :hat_size, :integer @@ -886,7 +884,21 @@ if ActiveRecord::Base.connection.supports_migrations? Person.connection.remove_column("hats", ["hat_name", "hat_size"]) end ensure - ActiveRecord::Base.connection.drop_table(:hats) rescue nil + ActiveRecord::Base.connection.drop_table(:hats) + end + + def test_removing_and_renaming_column_preserves_custom_primary_key + ActiveRecord::Base.connection.create_table "my_table", :primary_key => "my_table_id", :force => true do |t| + t.integer "col_one" + t.string "col_two", :limit => 128, :null => false + end + + ActiveRecord::Base.connection.remove_column("my_table", "col_two") + ActiveRecord::Base.connection.rename_column("my_table", "col_one", "col_three") + + assert_equal 'my_table_id', ActiveRecord::Base.connection.primary_key('my_table') + ensure + ActiveRecord::Base.connection.drop_table(:my_table) rescue nil end def test_change_type_of_not_null_column @@ -1633,8 +1645,8 @@ if ActiveRecord::Base.connection.supports_migrations? def test_rename_table_with_prefix_and_suffix assert !Thing.table_exists? - ActiveRecord::Base.table_name_prefix = 'prefix_' - ActiveRecord::Base.table_name_suffix = '_suffix' + ActiveRecord::Base.table_name_prefix = 'p_' + ActiveRecord::Base.table_name_suffix = '_s' Thing.reset_table_name Thing.reset_sequence_name WeNeedThings.up @@ -1643,7 +1655,7 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "hello world", Thing.find(:first).content RenameThings.up - Thing.table_name = "prefix_awesome_things_suffix" + Thing.table_name = "p_awesome_things_s" assert_equal "hello world", Thing.find(:first).content ensure diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 8708117129..0f9cb10f0f 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -22,6 +22,14 @@ end class Thaumaturgist < IneptWizard end +class ReplyTitle; end + +class ReplyWithTitleObject < Reply + validates_uniqueness_of :content, :scope => :title + + def title; ReplyTitle.new; end +end + class UniquenessValidationTest < ActiveRecord::TestCase fixtures :topics, 'warehouse-things', :developers @@ -80,6 +88,14 @@ class UniquenessValidationTest < ActiveRecord::TestCase assert r3.valid?, "Saving r3" end + def test_validate_uniqueness_with_composed_attribute_scope + r1 = ReplyWithTitleObject.create "title" => "r1", "content" => "hello world" + assert r1.valid?, "Saving r1" + + r2 = ReplyWithTitleObject.create "title" => "r1", "content" => "hello world" + assert !r2.valid?, "Saving r2 first time" + end + def test_validate_uniqueness_scoped_to_defining_class t = Topic.create("title" => "What, me worry?") diff --git a/activerecord/test/models/possession.rb b/activerecord/test/models/possession.rb new file mode 100644 index 0000000000..ddf759113b --- /dev/null +++ b/activerecord/test/models/possession.rb @@ -0,0 +1,3 @@ +class Possession < ActiveRecord::Base + self.table_name = 'having' +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 1a993fef11..ab62fb82b6 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -176,6 +176,7 @@ ActiveRecord::Schema.define do t.integer :client_of t.integer :rating, :default => 1 t.integer :account_id + t.string :description, :default => "" end add_index :companies, [:firm_id, :type, :rating, :ruby_type], :name => "company_index" @@ -281,6 +282,10 @@ ActiveRecord::Schema.define do t.string :info end + create_table :having, :force => true do |t| + t.string :where + end + create_table :guids, :force => true do |t| t.column :key, :string end |