From b892d20c610af78a94ba6c9488092ef76845242a Mon Sep 17 00:00:00 2001 From: Potapov Sergey Date: Sun, 16 Feb 2014 14:40:23 +0200 Subject: Do not try to write timestamps if they are missing #8813 --- activerecord/CHANGELOG.md | 6 ++++++ activerecord/lib/active_record/timestamp.rb | 2 +- activerecord/test/cases/timestamp_test.rb | 22 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index f1f9cf1ffd..460eeb5dfd 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Do not try to write timestamps when a table has no timestamps columns. + + Fixes #8813. + + *Sergey Potapov* + * Properly detect if a connection is still active before using it in multi-threaded environments. diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 7178bed560..271f58cbe0 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -48,7 +48,7 @@ module ActiveRecord current_time = current_time_from_proper_timezone all_timestamp_attributes.each do |column| - if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil? + if attributes.has_key?(column.to_s) && self.send(column).nil? write_attribute(column.to_s, current_time) end end diff --git a/activerecord/test/cases/timestamp_test.rb b/activerecord/test/cases/timestamp_test.rb index 717e0e1866..8195e8a08a 100644 --- a/activerecord/test/cases/timestamp_test.rb +++ b/activerecord/test/cases/timestamp_test.rb @@ -9,11 +9,26 @@ require 'models/task' class TimestampTest < ActiveRecord::TestCase fixtures :developers, :owners, :pets, :toys, :cars, :tasks + # Model with timestamp attribute accessors, but without timestamps columns. + class TimestampAttributePost < ActiveRecord::Base + attr_accessor :created_at, :updated_at + end + + def setup @developer = Developer.first @owner = Owner.first @developer.update_columns(updated_at: Time.now.prev_month) @previously_updated_at = @developer.updated_at + + @connection = ActiveRecord::Base.connection + @connection.create_table 'timestamp_attribute_posts', :force => true do |t| + # Must have no timestamps + end + end + + def teardown + @connection.execute 'drop table if exists timestamp_attribute_posts' end def test_saving_a_changed_record_updates_its_timestamp @@ -379,4 +394,11 @@ class TimestampTest < ActiveRecord::TestCase toy = Toy.first assert_equal [:created_at, :updated_at], toy.send(:all_timestamp_attributes_in_model) end + + def test_do_not_write_timestamps_on_save_if_they_are_not_attributes + post = TimestampAttributePost.new + assert_nothing_raised ActiveModel::MissingAttributeError do + post.run_callbacks :save + end + end end -- cgit v1.2.3 From 91d199259bc7a85763d487a1d0fbf8cd50fe29a0 Mon Sep 17 00:00:00 2001 From: Benjamin Fleischer Date: Thu, 27 Jun 2013 23:18:30 -0500 Subject: Encapsulate rake lines from ActiveRecord/ActionPack as CodeTools::LineStatistics [ci skip] --- activerecord/Rakefile | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) (limited to 'activerecord') diff --git a/activerecord/Rakefile b/activerecord/Rakefile index 7769966a22..b1069e5dcc 100644 --- a/activerecord/Rakefile +++ b/activerecord/Rakefile @@ -146,27 +146,9 @@ task :drop_postgresql_databases => 'db:postgresql:drop' task :rebuild_postgresql_databases => 'db:postgresql:rebuild' task :lines do - lines, codelines, total_lines, total_codelines = 0, 0, 0, 0 - - FileList["lib/active_record/**/*.rb"].each do |file_name| - next if file_name =~ /vendor/ - File.open(file_name, 'r') do |f| - while line = f.gets - lines += 1 - next if line =~ /^\s*$/ - next if line =~ /^\s*#/ - codelines += 1 - end - end - puts "L: #{sprintf("%4d", lines)}, LOC #{sprintf("%4d", codelines)} | #{file_name}" - - total_lines += lines - total_codelines += codelines - - lines, codelines = 0, 0 - end - - puts "Total: Lines #{total_lines}, LOC #{total_codelines}" + load File.expand_path('..', File.dirname(__FILE__)) + '/tools/line_statistics' + files = FileList["lib/active_record/**/*.rb"] + CodeTools::LineStatistics.new(files).print_loc end spec = eval(File.read('activerecord.gemspec')) -- cgit v1.2.3 From 2638d5c72444db1dc73c0593cb35f9916fc6284c Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Mon, 11 Aug 2014 00:00:23 -0700 Subject: Fixed issue w/custom accessors + reserved name + inheritance Fixed an issue where custom accessor methods (such as those generated by `enum`) with the same name as a global method are incorrectly overridden when subclassing. This was partially fixed in 4155431 then broken again by e5f15a8. Fixes #16288. --- activerecord/CHANGELOG.md | 8 ++++++++ activerecord/lib/active_record/attribute_methods.rb | 13 ++++++++----- activerecord/test/cases/attribute_methods_test.rb | 18 ++++++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 4b70d883fe..cbed360fff 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,11 @@ +* Fixed an issue where custom accessor methods (such as those generated by + `enum`) with the same name as a global method are incorrectly overridden + when subclassing. + + Fixes #16288. + + *Godfrey Chan* + * Define `id_was` to get the previous value of the primary key. Currently when we call id_was and we have a custom primary key name diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index a2bb78dfcc..82f1682b33 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -57,6 +57,8 @@ module ActiveRecord end end + class GeneratedAttributeMethods < Module; end # :nodoc: + module ClassMethods def inherited(child_class) #:nodoc: child_class.initialize_generated_modules @@ -64,7 +66,7 @@ module ActiveRecord end def initialize_generated_modules # :nodoc: - @generated_attribute_methods = Module.new { extend Mutex_m } + @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m } @attribute_methods_generated = false include @generated_attribute_methods end @@ -113,10 +115,11 @@ module ActiveRecord if superclass == Base super else - # If B < A and A defines its own attribute method, then we don't want to overwrite that. - defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods) - base_defined = Base.method_defined?(method_name) || Base.private_method_defined?(method_name) - defined && !base_defined || super + # If ThisClass < ... < SomeSuperClass < ... < Base and SomeSuperClass + # defines its own attribute method, then we don't want to overwrite that. + defined = method_defined_within?(method_name, superclass, Base) && + ! superclass.instance_method(method_name).owner.is_a?(GeneratedAttributeMethods) + defined || super end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ab67cf4085..2c07b5cbad 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -810,6 +810,24 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal "lol", topic.author_name end + def test_inherited_custom_accessors_with_reserved_names + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'computers' + self.abstract_class = true + def system; "omg"; end + def system=(val); self.developer = val; end + end + + subklass = Class.new(klass) + [klass, subklass].each(&:define_attribute_methods) + + computer = subklass.find(1) + assert_equal "omg", computer.system + + computer.developer = 99 + assert_equal 99, computer.developer + end + def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing klass = new_topic_like_ar_class do def title -- cgit v1.2.3 From ea3ba34506c72d636096245016b5ef9cfe27c566 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 12 Aug 2014 14:23:14 -0600 Subject: Change the default `null` value for timestamps As per discussion, this changes the model generators to specify `null: false` for timestamp columns. A warning is now emitted if `timestamps` is called without a `null` option specified, so we can safely change the behavior when no option is specified in Rails 5. --- .../abstract/schema_definitions.rb | 24 +++++++-- .../abstract/schema_statements.rb | 6 +-- .../connection_adapters/abstract_mysql_adapter.rb | 4 +- .../migration/templates/create_table_migration.rb | 2 +- .../cases/adapters/mysql/active_schema_test.rb | 2 +- .../cases/adapters/mysql2/active_schema_test.rb | 2 +- .../cases/adapters/postgresql/timestamp_test.rb | 8 +-- activerecord/test/cases/ar_schema_test.rb | 57 ++++++++++++++++++++++ .../test/cases/migration/change_schema_test.rb | 7 ++- .../test/cases/migration/change_table_test.rb | 4 +- activerecord/test/cases/migration/helper.rb | 2 +- activerecord/test/cases/migration_test.rb | 2 +- activerecord/test/schema/schema.rb | 10 ++-- 13 files changed, 104 insertions(+), 26 deletions(-) (limited to 'activerecord') 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 e44ccb7d81..9e07e9a5c4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -56,6 +56,19 @@ module ActiveRecord end end + module TimestampDefaultDeprecation # :nodoc: + def emit_warning_if_null_unspecified(options) + return if options.key?(:null) + + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `timestamp` was called without specifying an option for `null`. In Rails + 5.0, this behavior will change to `null: false`. You should manually + specify `null: true` to prevent the behavior of your existing migrations + from changing. + MESSAGE + end + end + # Represents the schema of an SQL table in an abstract way. This class # provides methods for manipulating the schema representation. # @@ -77,6 +90,8 @@ module ActiveRecord # The table definitions # The Columns are stored as a ColumnDefinition in the +columns+ attribute. class TableDefinition + include TimestampDefaultDeprecation + # An array of ColumnDefinition objects, representing the column changes # that have been defined. attr_accessor :indexes @@ -276,6 +291,7 @@ module ActiveRecord # :updated_at to the table. def timestamps(*args) options = args.extract_options! + emit_warning_if_null_unspecified(options) column(:created_at, :datetime, options) column(:updated_at, :datetime, options) end @@ -405,6 +421,8 @@ module ActiveRecord # end # class Table + include TimestampDefaultDeprecation + def initialize(table_name, base) @table_name = table_name @base = base @@ -452,8 +470,9 @@ module ActiveRecord # Adds timestamps (+created_at+ and +updated_at+) columns to the table. See SchemaStatements#add_timestamps # # t.timestamps - def timestamps - @base.add_timestamps(@table_name) + def timestamps(options = {}) + emit_warning_if_null_unspecified(options) + @base.add_timestamps(@table_name, options) end # Changes the column's definition according to the new options. @@ -559,6 +578,5 @@ module ActiveRecord @base.native_database_types end end - end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 10753defc2..84e6a27cd1 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -838,9 +838,9 @@ module ActiveRecord # # add_timestamps(:suppliers) # - def add_timestamps(table_name) - add_column table_name, :created_at, :datetime - add_column table_name, :updated_at, :datetime + def add_timestamps(table_name, options = {}) + add_column table_name, :created_at, :datetime, options + add_column table_name, :updated_at, :datetime, options end # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition. diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index e5417a9556..a1c370b05d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -764,8 +764,8 @@ module ActiveRecord "DROP INDEX #{index_name}" end - def add_timestamps_sql(table_name) - [add_column_sql(table_name, :created_at, :datetime), add_column_sql(table_name, :updated_at, :datetime)] + def add_timestamps_sql(table_name, options = {}) + [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] end def remove_timestamps_sql(table_name) diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb index fd94a2d038..f7bf6987c4 100644 --- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb @@ -9,7 +9,7 @@ class <%= migration_class_name %> < ActiveRecord::Migration <% end -%> <% end -%> <% if options[:timestamps] %> - t.timestamps + t.timestamps null: false <% end -%> end <% attributes_with_index.each do |attribute| -%> diff --git a/activerecord/test/cases/adapters/mysql/active_schema_test.rb b/activerecord/test/cases/adapters/mysql/active_schema_test.rb index 7c0f11b033..a84673e452 100644 --- a/activerecord/test/cases/adapters/mysql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql/active_schema_test.rb @@ -105,7 +105,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase with_real_execute do begin ActiveRecord::Base.connection.create_table :delete_me do |t| - t.timestamps + t.timestamps null: true end ActiveRecord::Base.connection.remove_timestamps :delete_me assert !column_present?('delete_me', 'updated_at', 'datetime') diff --git a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb index cefc3e3c7e..49cfafd7a5 100644 --- a/activerecord/test/cases/adapters/mysql2/active_schema_test.rb +++ b/activerecord/test/cases/adapters/mysql2/active_schema_test.rb @@ -105,7 +105,7 @@ class ActiveSchemaTest < ActiveRecord::TestCase with_real_execute do begin ActiveRecord::Base.connection.create_table :delete_me do |t| - t.timestamps + t.timestamps null: true end ActiveRecord::Base.connection.remove_timestamps :delete_me assert !column_present?('delete_me', 'updated_at', 'datetime') diff --git a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb index 3614b29190..eb32c4d2c2 100644 --- a/activerecord/test/cases/adapters/postgresql/timestamp_test.rb +++ b/activerecord/test/cases/adapters/postgresql/timestamp_test.rb @@ -87,7 +87,7 @@ class TimestampTest < ActiveRecord::TestCase def test_timestamps_helper_with_custom_precision ActiveRecord::Base.connection.create_table(:foos) do |t| - t.timestamps :precision => 4 + t.timestamps :null => true, :precision => 4 end assert_equal 4, activerecord_column_option('foos', 'created_at', 'precision') assert_equal 4, activerecord_column_option('foos', 'updated_at', 'precision') @@ -95,7 +95,7 @@ class TimestampTest < ActiveRecord::TestCase def test_passing_precision_to_timestamp_does_not_set_limit ActiveRecord::Base.connection.create_table(:foos) do |t| - t.timestamps :precision => 4 + t.timestamps :null => true, :precision => 4 end assert_nil activerecord_column_option("foos", "created_at", "limit") assert_nil activerecord_column_option("foos", "updated_at", "limit") @@ -104,14 +104,14 @@ class TimestampTest < ActiveRecord::TestCase def test_invalid_timestamp_precision_raises_error assert_raises ActiveRecord::ActiveRecordError do ActiveRecord::Base.connection.create_table(:foos) do |t| - t.timestamps :precision => 7 + t.timestamps :null => true, :precision => 7 end end end def test_postgres_agrees_with_activerecord_about_precision ActiveRecord::Base.connection.create_table(:foos) do |t| - t.timestamps :precision => 4 + t.timestamps :null => true, :precision => 4 end assert_equal '4', pg_datetime_precision('foos', 'created_at') assert_equal '4', pg_datetime_precision('foos', 'updated_at') diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb index 8700b20dee..3f5858714a 100644 --- a/activerecord/test/cases/ar_schema_test.rb +++ b/activerecord/test/cases/ar_schema_test.rb @@ -14,6 +14,7 @@ if ActiveRecord::Base.connection.supports_migrations? @connection.drop_table :fruits rescue nil @connection.drop_table :nep_fruits rescue nil @connection.drop_table :nep_schema_migrations rescue nil + @connection.drop_table :has_timestamps rescue nil ActiveRecord::SchemaMigration.delete_all rescue nil end @@ -88,5 +89,61 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal "017", ActiveRecord::SchemaMigration.normalize_migration_number("0017") assert_equal "20131219224947", ActiveRecord::SchemaMigration.normalize_migration_number("20131219224947") end + + def test_timestamps_without_null_is_deprecated_on_create_table + assert_deprecated do + ActiveRecord::Schema.define do + create_table :has_timestamps do |t| + t.timestamps + end + end + end + end + + def test_timestamps_without_null_is_deprecated_on_change_table + assert_deprecated do + ActiveRecord::Schema.define do + create_table :has_timestamps + + change_table :has_timestamps do |t| + t.timestamps + end + end + end + end + + def test_no_deprecation_warning_from_timestamps_on_create_table + assert_not_deprecated do + ActiveRecord::Schema.define do + create_table :has_timestamps do |t| + t.timestamps null: true + end + + drop_table :has_timestamps + + create_table :has_timestamps do |t| + t.timestamps null: false + end + end + end + end + + def test_no_deprecation_warning_from_timestamps_on_change_table + assert_not_deprecated do + ActiveRecord::Schema.define do + create_table :has_timestamps + change_table :has_timestamps do |t| + t.timestamps null: true + end + + drop_table :has_timestamps + + create_table :has_timestamps + change_table :has_timestamps do |t| + t.timestamps null: false, default: Time.now + end + end + end + end end end diff --git a/activerecord/test/cases/migration/change_schema_test.rb b/activerecord/test/cases/migration/change_schema_test.rb index c66eaf1ee1..bd3dd29f4d 100644 --- a/activerecord/test/cases/migration/change_schema_test.rb +++ b/activerecord/test/cases/migration/change_schema_test.rb @@ -176,8 +176,11 @@ module ActiveRecord end def test_create_table_with_timestamps_should_create_datetime_columns - connection.create_table table_name do |t| - t.timestamps + # FIXME: Remove the silence when we change the default `null` behavior + ActiveSupport::Deprecation.silence do + connection.create_table table_name do |t| + t.timestamps + end end created_columns = connection.columns(table_name) diff --git a/activerecord/test/cases/migration/change_table_test.rb b/activerecord/test/cases/migration/change_table_test.rb index 3e9d957ed3..777a48ad14 100644 --- a/activerecord/test/cases/migration/change_table_test.rb +++ b/activerecord/test/cases/migration/change_table_test.rb @@ -88,8 +88,8 @@ module ActiveRecord def test_timestamps_creates_updated_at_and_created_at with_change_table do |t| - @connection.expect :add_timestamps, nil, [:delete_me] - t.timestamps + @connection.expect :add_timestamps, nil, [:delete_me, null: true] + t.timestamps null: true end end diff --git a/activerecord/test/cases/migration/helper.rb b/activerecord/test/cases/migration/helper.rb index e28feedcf9..4dad77e8fd 100644 --- a/activerecord/test/cases/migration/helper.rb +++ b/activerecord/test/cases/migration/helper.rb @@ -22,7 +22,7 @@ module ActiveRecord super @connection = ActiveRecord::Base.connection connection.create_table :test_models do |t| - t.timestamps + t.timestamps null: true end TestModel.reset_column_information diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index ef3f073472..11338e1fb6 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -561,7 +561,7 @@ if ActiveRecord::Base.connection.supports_bulk_alter? t.string :qualification, :experience t.integer :age, :default => 0 t.date :birthdate - t.timestamps + t.timestamps null: true end end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index a8b21904ac..98f2492ef8 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -138,7 +138,7 @@ ActiveRecord::Schema.define do t.integer :engines_count t.integer :wheels_count t.column :lock_version, :integer, null: false, default: 0 - t.timestamps + t.timestamps null: false end create_table :categories, force: true do |t| @@ -537,7 +537,7 @@ ActiveRecord::Schema.define do t.references :best_friend_of t.integer :insures, null: false, default: 0 t.timestamp :born_at - t.timestamps + t.timestamps null: false end create_table :peoples_treasures, id: false, force: true do |t| @@ -548,7 +548,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 + t.timestamps null: false end create_table :pirates, force: true do |t| @@ -726,13 +726,13 @@ ActiveRecord::Schema.define do t.string :parent_title t.string :type t.string :group - t.timestamps + t.timestamps null: true end create_table :toys, primary_key: :toy_id, force: true do |t| t.string :name t.integer :pet_id, :integer - t.timestamps + t.timestamps null: false end create_table :traffic_lights, force: true do |t| -- cgit v1.2.3 From 8f4e24c1ba247087ae33871e48eae635c272d19d Mon Sep 17 00:00:00 2001 From: Tom Kadwill Date: Wed, 13 Aug 2014 07:36:43 +0100 Subject: [ci skip] Updated where scope to conform to new style --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 1c5a737696..830c633c36 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1435,7 +1435,7 @@ module ActiveRecord # belongs_to :firm, foreign_key: "client_of" # belongs_to :person, primary_key: "name", foreign_key: "person_name" # belongs_to :author, class_name: "Person", foreign_key: "author_id" - # belongs_to :valid_coupon, ->(o) { where "discounts > #{o.payments_count}" }, + # belongs_to :valid_coupon, ->(o) { where "discounts > ?", o.payments_count }, # class_name: "Coupon", foreign_key: "coupon_id" # belongs_to :attachable, polymorphic: true # belongs_to :project, readonly: true -- cgit v1.2.3 From ecfce561e415d99df48eebefc1b444dd53580d1a Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Wed, 13 Aug 2014 11:44:58 +0200 Subject: `index_exists?` with `:name` checks specified columns. [Yves Senn & Matthew Draper] The column check was embodied in the defaul index name. If the :name option was used, the specified columns were not verified at all. Given: ``` assert connection.index_exists?(table_name, :foo_id, :name => :index_testings_on_yo_momma) ``` That index could have been defined on any field, not necessarily on `:foo_id`. --- activerecord/CHANGELOG.md | 16 ++++++++++++++++ .../connection_adapters/abstract/schema_statements.rb | 15 ++++++++------- activerecord/test/cases/migration/index_test.rb | 6 ++++++ 3 files changed, 30 insertions(+), 7 deletions(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index ea951fdfd1..b70d16f97b 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,19 @@ +* `index_exists?` with `:name` option does verify specified columns. + + Example: + + add_index :articles, :title, name: "idx_title" + + # Before: + index_exists? :articles, :title, name: "idx_title" # => `true` + index_exists? :articles, :body, name: "idx_title" # => `true` + + # After: + index_exists? :articles, :title, name: "idx_title" # => `true` + index_exists? :articles, :body, name: "idx_title" # => `false` + + *Yves Senn*, *Matthew Draper* + * When calling `update_columns` on a record that is not persisted, the error message now reflects whether that object is a new record or has been destroyed. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 10753defc2..4957e1ac80 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -43,13 +43,14 @@ module ActiveRecord # index_exists?(:suppliers, :company_id, name: "idx_company_id") # def index_exists?(table_name, column_name, options = {}) - column_names = Array(column_name) - index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, :column => column_names) - if options[:unique] - indexes(table_name).any?{ |i| i.unique && i.name == index_name } - else - indexes(table_name).any?{ |i| i.name == index_name } - end + column_names = Array(column_name).map(&:to_s) + index_name = options.key?(:name) ? options[:name].to_s : index_name(table_name, column: column_names) + checks = [] + checks << lambda { |i| i.name == index_name } + checks << lambda { |i| i.columns == column_names } + checks << lambda { |i| i.unique } if options[:unique] + + indexes(table_name).any? { |i| checks.all? { |check| check[i] } } end # Returns an array of Column objects for the table specified by +table_name+. diff --git a/activerecord/test/cases/migration/index_test.rb b/activerecord/test/cases/migration/index_test.rb index 93c3bfae7a..ac932378fd 100644 --- a/activerecord/test/cases/migration/index_test.rb +++ b/activerecord/test/cases/migration/index_test.rb @@ -95,6 +95,12 @@ module ActiveRecord assert connection.index_exists?(:testings, [:foo, :bar]) end + def test_index_exists_with_custom_name_checks_columns + connection.add_index :testings, [:foo, :bar], name: "my_index" + assert connection.index_exists?(:testings, [:foo, :bar], name: "my_index") + assert_not connection.index_exists?(:testings, [:foo], name: "my_index") + end + def test_valid_index_options assert_raise ArgumentError do connection.add_index :testings, :foo, unqiue: true -- cgit v1.2.3 From 12ce5b1cf6be926d6ca22474d1d77ca83b9da2bf Mon Sep 17 00:00:00 2001 From: Akshay Vishnoi Date: Wed, 13 Aug 2014 16:47:28 +0530 Subject: [ci skip] fix spelling of override --- activerecord/lib/active_record/type/value.rb | 4 ++-- activerecord/test/cases/adapters/mysql/connection_test.rb | 2 +- activerecord/test/cases/adapters/mysql2/connection_test.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index e0a783fb45..475e130013 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -69,8 +69,8 @@ module ActiveRecord end # Determines whether the mutable value has been modified since it was - # read. Returns +false+ by default. This method should not need to be - # overriden directly. Types which return a mutable value should include + # read. Returns +false+ by default. This method should not be overridden + # directly. Types which return a mutable value should include # +Type::Mutable+, which will define this method. def changed_in_place?(*) false diff --git a/activerecord/test/cases/adapters/mysql/connection_test.rb b/activerecord/test/cases/adapters/mysql/connection_test.rb index b0759dffde..a7b0addc1b 100644 --- a/activerecord/test/cases/adapters/mysql/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql/connection_test.rb @@ -150,7 +150,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end - def test_mysql_sql_mode_variable_overides_strict_mode + def test_mysql_sql_mode_variable_overrides_strict_mode run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' diff --git a/activerecord/test/cases/adapters/mysql2/connection_test.rb b/activerecord/test/cases/adapters/mysql2/connection_test.rb index 3b35e69e0d..beedb4f3a1 100644 --- a/activerecord/test/cases/adapters/mysql2/connection_test.rb +++ b/activerecord/test/cases/adapters/mysql2/connection_test.rb @@ -76,7 +76,7 @@ class MysqlConnectionTest < ActiveRecord::TestCase end end - def test_mysql_sql_mode_variable_overides_strict_mode + def test_mysql_sql_mode_variable_overrides_strict_mode run_without_connection do |orig_connection| ActiveRecord::Base.establish_connection(orig_connection.deep_merge(variables: { 'sql_mode' => 'ansi' })) result = ActiveRecord::Base.connection.exec_query 'SELECT @@SESSION.sql_mode' -- cgit v1.2.3 From 2440933fe2c27b27bcafcd9019717800db2641aa Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 14 Aug 2014 11:21:15 +0900 Subject: Finally! None of our tests are order_dependent! --- activerecord/test/cases/helper.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb index a35ddf5632..6a8aff4b69 100644 --- a/activerecord/test/cases/helper.rb +++ b/activerecord/test/cases/helper.rb @@ -201,8 +201,3 @@ module InTimeZone end require 'mocha/setup' # FIXME: stop using mocha - -# FIXME: we have tests that depend on run order, we should fix that and -# remove this method call. -require 'active_support/test_case' -ActiveSupport::TestCase.my_tests_are_order_dependent! -- cgit v1.2.3 From e76379b04a0934c5d65d3b04fb00fa3b8221c70f Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 14 Aug 2014 16:10:29 +0900 Subject: Clear validators before and after each test Or some tests fail when run in random order --- activerecord/test/cases/validations/i18n_validation_test.rb | 1 + activerecord/test/cases/validations_repair_helper.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/validations/i18n_validation_test.rb b/activerecord/test/cases/validations/i18n_validation_test.rb index 3db742c15b..268d7914b5 100644 --- a/activerecord/test/cases/validations/i18n_validation_test.rb +++ b/activerecord/test/cases/validations/i18n_validation_test.rb @@ -6,6 +6,7 @@ class I18nValidationTest < ActiveRecord::TestCase repair_validations(Topic, Reply) def setup + repair_validations(Topic, Reply) Reply.validates_presence_of(:title) @topic = Topic.new @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend diff --git a/activerecord/test/cases/validations_repair_helper.rb b/activerecord/test/cases/validations_repair_helper.rb index c02b3241cd..2bbf0f23b3 100644 --- a/activerecord/test/cases/validations_repair_helper.rb +++ b/activerecord/test/cases/validations_repair_helper.rb @@ -13,7 +13,7 @@ module ActiveRecord end def repair_validations(*model_classes) - yield + yield if block_given? ensure model_classes.each do |k| k.clear_validators! -- cgit v1.2.3 From 4bb823bd1a02e37c105ab21420d37f93df692f28 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 14 Aug 2014 16:37:08 +0900 Subject: Format --- activerecord/test/cases/scoping/relation_scoping_test.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index d8a467ec4d..d6a1cd273d 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -260,7 +260,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase end end -class HasManyScopingTest< ActiveRecord::TestCase +class HasManyScopingTest < ActiveRecord::TestCase fixtures :comments, :posts, :people, :references def setup @@ -306,7 +306,7 @@ class HasManyScopingTest< ActiveRecord::TestCase end end -class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase +class HasAndBelongsToManyScopingTest < ActiveRecord::TestCase fixtures :posts, :categories, :categories_posts def setup -- cgit v1.2.3 From 76ea9f9714ff08cfffec16fc3672480a9f46f5d9 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 14 Aug 2014 17:08:20 +0900 Subject: Make sure that fixtures are loaded before finding --- activerecord/test/cases/scoping/relation_scoping_test.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index d6a1cd273d..8e512e118a 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -11,6 +11,10 @@ require 'models/reference' class RelationScopingTest < ActiveRecord::TestCase fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects + setup do + developers(:david) + end + def test_reverse_order assert_equal Developer.order("id DESC").to_a.reverse, Developer.order("id DESC").reverse_order end -- cgit v1.2.3 From 006225b01517628421964a038bbded3dc7372a30 Mon Sep 17 00:00:00 2001 From: Tom Kadwill Date: Thu, 14 Aug 2014 09:05:10 +0100 Subject: [ci skip] updated 'where' in association documention to new style syntax --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 1c5a737696..735084424e 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1209,7 +1209,7 @@ module ActiveRecord # Option examples: # has_many :comments, -> { order "posted_on" } # has_many :comments, -> { includes :author } - # has_many :people, -> { where("deleted = 0").order("name") }, class_name: "Person" + # has_many :people, -> { where(deleted: false).order(:name) }, class_name: "Person" # has_many :tracks, -> { order "position" }, dependent: :destroy # has_many :comments, dependent: :nullify # has_many :tags, as: :taggable -- cgit v1.2.3 From b5c4c50aa9f93a5602b895a9931ad529cccf2048 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Thu, 14 Aug 2014 19:15:58 +0900 Subject: Ignore SCHEMA queries in some habtm tests --- .../associations/has_and_belongs_to_many_associations_test.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord') 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 cc58a4a1a2..859310575e 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 @@ -254,7 +254,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_build devel = Developer.find(1) - proj = assert_no_queries { devel.projects.build("name" => "Projekt") } + proj = assert_no_queries(ignore_none: false) { devel.projects.build("name" => "Projekt") } assert !devel.projects.loaded? assert_equal devel.projects.last, proj @@ -269,7 +269,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_new_aliased_to_build devel = Developer.find(1) - proj = assert_no_queries { devel.projects.new("name" => "Projekt") } + proj = assert_no_queries(ignore_none: false) { devel.projects.new("name" => "Projekt") } assert !devel.projects.loaded? assert_equal devel.projects.last, proj @@ -503,7 +503,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase developer = project.developers.first - assert_no_queries do + assert_no_queries(ignore_none: false) do assert project.developers.loaded? assert project.developers.include?(developer) end @@ -824,7 +824,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations projects = Developer.new.projects - assert_no_queries do + assert_no_queries(ignore_none: false) do assert_equal [], projects assert_equal [], projects.where(title: 'omg') assert_equal [], projects.pluck(:title) -- cgit v1.2.3 From fe67dfbbeea092f0f42e81e4901fe9a949cf9484 Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Thu, 14 Aug 2014 13:44:29 +0300 Subject: Fixed AR::Relation#where edge case with Hash and other Relation Example: Author.where(posts: { author_id: Author.where(country_id: 1) }).joins(:posts) --- .../lib/active_record/relation/query_methods.rb | 18 +++++++++++++++--- activerecord/test/cases/relation/where_test.rb | 9 +++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 1262b2c291..c8f382ad78 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -952,9 +952,7 @@ WARNING self.bind_values += bind_values attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts) - attributes.values.grep(ActiveRecord::Relation) do |rel| - self.bind_values += rel.bind_values - end + add_relations_to_bind_values(attributes) PredicateBuilder.build_from_hash(klass, attributes, table) else @@ -1137,5 +1135,19 @@ WARNING raise ArgumentError, "The method .#{method_name}() must contain arguments." end end + + # This function is recursive just for better readablity. + # #where argument doesn't support more than one level nested hash in real world. + def add_relations_to_bind_values(attributes) + if attributes.is_a?(Hash) + attributes.each_value do |value| + if value.is_a?(ActiveRecord::Relation) + self.bind_values += value.bind_values + else + add_relations_to_bind_values(value) + end + end + end + end end end diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb index a6a36a6fd9..b4804aa9d7 100644 --- a/activerecord/test/cases/relation/where_test.rb +++ b/activerecord/test/cases/relation/where_test.rb @@ -61,6 +61,15 @@ module ActiveRecord assert_equal expected.to_sql, actual.to_sql end + def test_belongs_to_nested_where_with_relation + author = authors(:david) + + expected = Author.where(id: author ).joins(:posts) + actual = Author.where(posts: { author_id: Author.where(id: author.id) }).joins(:posts) + + assert_equal expected.to_a, actual.to_a + end + def test_polymorphic_shallow_where treasure = Treasure.new treasure.id = 1 -- cgit v1.2.3 From 9a7d7624a1e20094fddd0ff90d67e4049c09a568 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 15 Aug 2014 00:08:47 +0900 Subject: Warm up Symbols with where method Looks like #first wasn't warm enough... --- activerecord/test/cases/finder_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 40e51a0cdc..95d006279d 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -51,7 +51,7 @@ class FinderTest < ActiveRecord::TestCase end def test_symbols_table_ref - Post.first # warm up + Post.where("author_id" => nil) # warm up x = Symbol.all_symbols.count Post.where("title" => {"xxxqqqq" => "bar"}) assert_equal x, Symbol.all_symbols.count -- cgit v1.2.3 From fdd4b73cb8ddf35c941b96033cab15e5080f54ac Mon Sep 17 00:00:00 2001 From: Zachary Scott Date: Thu, 14 Aug 2014 08:57:19 -0700 Subject: Use string for order argument, fixed from #16501 [ci skip] --- activerecord/lib/active_record/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 6a1ea9623b..d3b9b8251a 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1209,7 +1209,7 @@ module ActiveRecord # Option examples: # has_many :comments, -> { order "posted_on" } # has_many :comments, -> { includes :author } - # has_many :people, -> { where(deleted: false).order(:name) }, class_name: "Person" + # has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person" # has_many :tracks, -> { order "position" }, dependent: :destroy # has_many :comments, dependent: :nullify # has_many :tags, as: :taggable -- cgit v1.2.3 From cf38bda8e2a16ce205ec88207687b8091ee67617 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 15 Aug 2014 01:27:50 +0900 Subject: Be sure to reset PK name renamed in the test --- activerecord/test/cases/migration/columns_test.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/migration/columns_test.rb b/activerecord/test/cases/migration/columns_test.rb index 4e6d7963aa..e6aa901814 100644 --- a/activerecord/test/cases/migration/columns_test.rb +++ b/activerecord/test/cases/migration/columns_test.rb @@ -66,6 +66,9 @@ module ActiveRecord def test_mysql_rename_column_preserves_auto_increment rename_column "test_models", "id", "id_test" assert_equal "auto_increment", connection.columns("test_models").find { |c| c.name == "id_test" }.extra + TestModel.reset_column_information + ensure + rename_column "test_models", "id_test", "id" end end -- cgit v1.2.3 From c8ede235374e948f2922250b2c322b6bc70a2449 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 15 Aug 2014 02:41:17 +0900 Subject: Ignore MySQL "SHOW VARIABLES" when counting queries --- activerecord/test/cases/test_case.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb index 23a170388e..4070216733 100644 --- a/activerecord/test/cases/test_case.rb +++ b/activerecord/test/cases/test_case.rb @@ -93,7 +93,7 @@ module ActiveRecord # ignored SQL, or better yet, use a different notification for the queries # instead examining the SQL content. oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im, /^\s*select .* from all_constraints/im, /^\s*select .* from all_tab_cols/im] - mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i] + mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/, /^SHOW CREATE TABLE /i, /^SHOW VARIABLES /] postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i] sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im] -- cgit v1.2.3 From 20f32bbc3b93ef220ad75303a403fb221a5a2fea Mon Sep 17 00:00:00 2001 From: jbsmith86 Date: Thu, 14 Aug 2014 17:46:50 -0700 Subject: Spelling errors --- .../lib/active_record/associations/preloader/through_association.rb | 2 +- activerecord/lib/active_record/attribute_methods.rb | 4 ++-- .../lib/active_record/connection_adapters/postgresql/oid/jsonb.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 1fed7f74e7..d57da366bd 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -63,7 +63,7 @@ module ActiveRecord should_reset = (through_scope != through_reflection.klass.unscoped) || (reflection.options[:source_type] && through_reflection.collection?) - # Dont cache the association - we would only be caching a subset + # Don't cache the association - we would only be caching a subset if should_reset owners.each { |owner| owner.association(association_name).reset diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index a2bb78dfcc..09e2faee86 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -279,9 +279,9 @@ module ActiveRecord end # Returns an #inspect-like string for the value of the - # attribute +attr_name+. String attributes are truncated upto 50 + # attribute +attr_name+. String attributes are truncated up to 50 # characters, Date and Time attributes are returned in the - # :db format, Array attributes are truncated upto 10 values. + # :db format, Array attributes are truncated up to 10 values. # Other attributes return the value of #inspect without # modification. # diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb index 34ed32ad35..380c50fc14 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb @@ -12,7 +12,7 @@ module ActiveRecord # roundtripping jsonb columns. This causes some false positives for # the comparison here. Therefore, we need to parse and re-dump the # raw value here to ensure the insignificant whitespaces are - # consitent with our encoder's output. + # consistent with our encoder's output. raw_old_value = type_cast_for_database(type_cast_from_database(raw_old_value)) super(raw_old_value, new_value) end -- cgit v1.2.3 From 4954de65a62941126a7aa194aa83275c0e98d486 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Fri, 15 Aug 2014 20:10:42 +0900 Subject: create_table + transactional_fixtures = :bomb: --- activerecord/test/cases/schema_dumper_test.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord') diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb index 4e71d04bc0..066e6b6468 100644 --- a/activerecord/test/cases/schema_dumper_test.rb +++ b/activerecord/test/cases/schema_dumper_test.rb @@ -2,6 +2,8 @@ require "cases/helper" require 'support/schema_dumping_helper' class SchemaDumperTest < ActiveRecord::TestCase + self.use_transactional_fixtures = false + setup do ActiveRecord::SchemaMigration.create_table end @@ -21,6 +23,8 @@ class SchemaDumperTest < ActiveRecord::TestCase schema_info = ActiveRecord::Base.connection.dump_schema_information assert_match(/20100201010101.*20100301010101/m, schema_info) + ensure + ActiveRecord::SchemaMigration.delete_all end def test_magic_comment -- cgit v1.2.3 From a3ee03083b50e1458c1276f499226ec59c0fb73d Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Thu, 14 Aug 2014 12:43:57 -0600 Subject: Use the method for determining attribute methods rather than duplicating I've been trying to reduce the number of places that care about `attributes`, and its existence. We have a method for this check, let's use it instead. --- activerecord/lib/active_record/timestamp.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 5ef98ed820..417cd61f0c 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -47,8 +47,9 @@ module ActiveRecord current_time = current_time_from_proper_timezone all_timestamp_attributes.each do |column| - if attributes.key?(column.to_s) && self.send(column).nil? - write_attribute(column.to_s, current_time) + column = column.to_s + if has_attribute?(column) && !attribute_present?(column) + write_attribute(column, current_time) end end end -- cgit v1.2.3 From 78f2ee50f00d670f6a075ca4325f8bcf4cf2d24b Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Fri, 15 Aug 2014 14:13:11 +0200 Subject: prefer `has_attribute?` over `attributes.key?`. Follow up to the discussion on #16505. --- activerecord/test/cases/attribute_methods_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index ab67cf4085..68aa6b8caf 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -263,7 +263,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end assert_equal klass.column_names, klass.new.attributes.keys - assert_not klass.new.attributes.key?('id') + assert_not klass.new.has_attributes?('id') end def test_hashes_not_mangled -- cgit v1.2.3 From 60b2d29ee155707946dec8c0c1faf0cf7ba4aab5 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Fri, 15 Aug 2014 14:17:56 +0200 Subject: fix typo in method name (broken build :sweat:) --- activerecord/test/cases/attribute_methods_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 68aa6b8caf..535928783f 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -263,7 +263,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase end assert_equal klass.column_names, klass.new.attributes.keys - assert_not klass.new.has_attributes?('id') + assert_not klass.new.has_attribute?('id') end def test_hashes_not_mangled -- cgit v1.2.3 From bc153cff9156e7e19b3587f0bb8062d238644b1d Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 15 Aug 2014 13:37:53 -0600 Subject: Implement `==` on `Type::Value` and `Attribute` This was a small self contained piece of the refactoring that I am working on, which required these objects to be comparable. --- activerecord/lib/active_record/attribute.rb | 7 +++++++ activerecord/lib/active_record/type/value.rb | 7 +++++++ activerecord/test/cases/attribute_test.rb | 30 ++++++++++++++++++++++++++++ activerecord/test/cases/types_test.rb | 6 ++++++ 4 files changed, 50 insertions(+) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 6d38224830..15cbbcff68 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -62,6 +62,13 @@ module ActiveRecord true end + def ==(other) + self.class == other.class && + name == other.name && + value_before_type_cast == other.value_before_type_cast && + type == other.type + end + protected def initialize_dup(other) diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 475e130013..9456a4a56c 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -76,6 +76,13 @@ module ActiveRecord false end + def ==(other) + self.class == other.class && + precision == other.precision && + scale == other.scale && + limit == other.limit + end + private def type_cast(value) diff --git a/activerecord/test/cases/attribute_test.rb b/activerecord/test/cases/attribute_test.rb index 91f6aee931..7b325abf1d 100644 --- a/activerecord/test/cases/attribute_test.rb +++ b/activerecord/test/cases/attribute_test.rb @@ -138,5 +138,35 @@ module ActiveRecord test "uninitialized attributes have no value" do assert_nil Attribute.uninitialized(:foo, nil).value end + + test "attributes equal other attributes with the same constructor arguments" do + first = Attribute.from_database(:foo, 1, Type::Integer.new) + second = Attribute.from_database(:foo, 1, Type::Integer.new) + assert_equal first, second + end + + test "attributes do not equal attributes with different names" do + first = Attribute.from_database(:foo, 1, Type::Integer.new) + second = Attribute.from_database(:bar, 1, Type::Integer.new) + assert_not_equal first, second + end + + test "attributes do not equal attributes with different types" do + first = Attribute.from_database(:foo, 1, Type::Integer.new) + second = Attribute.from_database(:foo, 1, Type::Float.new) + assert_not_equal first, second + end + + test "attributes do not equal attributes with different values" do + first = Attribute.from_database(:foo, 1, Type::Integer.new) + second = Attribute.from_database(:foo, 2, Type::Integer.new) + assert_not_equal first, second + end + + test "attributes do not equal attributes of other classes" do + first = Attribute.from_database(:foo, 1, Type::Integer.new) + second = Attribute.from_user(:foo, 1, Type::Integer.new) + assert_not_equal first, second + end end end diff --git a/activerecord/test/cases/types_test.rb b/activerecord/test/cases/types_test.rb index 5c54812f30..db4f78d354 100644 --- a/activerecord/test/cases/types_test.rb +++ b/activerecord/test/cases/types_test.rb @@ -149,6 +149,12 @@ module ActiveRecord end end + def test_type_equality + assert_equal Type::Value.new, Type::Value.new + assert_not_equal Type::Value.new, Type::Integer.new + assert_not_equal Type::Value.new(precision: 1), Type::Value.new(precision: 2) + end + if current_adapter?(:SQLite3Adapter) def test_binary_encoding type = SQLite3Binary.new -- cgit v1.2.3 From 0002954512364f2f69d28798f7a79aa8e27d7b6b Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Fri, 8 Aug 2014 15:37:38 -0400 Subject: Use *_transaction methods in TransactionManager Use `commit_transaction`/`rollback_transaction` on `within_new_transaction` method, so they make sure they `pop` the transaction from the stack before calling the methods `commit`/`rollback`. --- .../connection_adapters/abstract/transaction.rb | 6 ++---- activerecord/test/cases/transactions_test.rb | 24 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 4 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 4a7f2aaca8..3fce32211d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -169,16 +169,14 @@ module ActiveRecord transaction = begin_transaction options yield rescue Exception => error - transaction.rollback if transaction + rollback_transaction if transaction raise ensure begin - transaction.commit unless error + commit_transaction unless error rescue Exception transaction.rollback raise - ensure - @stack.pop if transaction end end diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index b4849222b8..9cfe041de2 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -80,6 +80,30 @@ class TransactionTest < ActiveRecord::TestCase end end + def test_number_of_transactions_in_commit + num = nil + + Topic.connection.class_eval do + alias :real_commit_db_transaction :commit_db_transaction + define_method(:commit_db_transaction) do + num = transaction_manager.open_transactions + real_commit_db_transaction + end + end + + Topic.transaction do + @first.approved = true + @first.save! + end + + assert_equal 0, num + ensure + Topic.connection.class_eval do + remove_method :commit_db_transaction + alias :commit_db_transaction :real_commit_db_transaction rescue nil + end + end + def test_successful_with_instance_method @first.transaction do @first.approved = true -- cgit v1.2.3 From 2e90fe736de73cfd89eed68b38e03eb6175314bc Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Fri, 8 Aug 2014 15:46:00 -0400 Subject: Fix regression on after_commit in nested transactions. after_commit should not run in nested transactions, however they should run once the outermost transaction gets committed. This patch fixes the problem copying the records from the Savepoint to its parent. So the RealTransaction will have all records that needs to run callbacks on it. [fixes #16425] --- activerecord/CHANGELOG.md | 6 ++++++ .../connection_adapters/abstract/transaction.rb | 2 ++ activerecord/test/cases/transaction_callbacks_test.rb | 13 +++++++++++++ 3 files changed, 21 insertions(+) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 7d7870fe13..02660ae4c2 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,9 @@ +* Fix regression on after_commit that didnt fire when having nested transactions. + + Fixes #16425 + + *arthurnn* + * Do not try to write timestamps when a table has no timestamps columns. Fixes #8813. diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 3fce32211d..8f06cf3a1f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -111,6 +111,8 @@ module ActiveRecord def commit super connection.release_savepoint(savepoint_name) + parent = connection.transaction_manager.current_transaction + records.each { |r| parent.add_record(r) } end def full_rollback?; false; end diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb index 3d64ecb464..a3f39804b7 100644 --- a/activerecord/test/cases/transaction_callbacks_test.rb +++ b/activerecord/test/cases/transaction_callbacks_test.rb @@ -129,6 +129,19 @@ class TransactionCallbacksTest < ActiveRecord::TestCase assert_equal [:commit_on_update], @first.history end + def test_only_call_after_commit_on_top_level_transactions + @first.after_commit_block{|r| r.history << :after_commit} + assert @first.history.empty? + + @first.transaction do + @first.transaction(requires_new: true) do + @first.touch + end + assert @first.history.empty? + end + assert_equal [:after_commit], @first.history + end + def test_call_after_rollback_after_transaction_rollsback @first.after_commit_block{|r| r.history << :after_commit} @first.after_rollback_block{|r| r.history << :after_rollback} -- cgit v1.2.3 From 877ea784e4cd0d539bdfbd15839ae3d28169b156 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 12 Jul 2014 18:30:49 -0600 Subject: Implement `_was` and `changes` for in-place mutations of AR attributes --- .../associations/has_many_association.rb | 2 +- activerecord/lib/active_record/attribute.rb | 8 +++++-- .../lib/active_record/attribute_methods/dirty.rb | 25 ++++++++++++---------- activerecord/lib/active_record/enum.rb | 4 ++-- activerecord/lib/active_record/persistence.rb | 2 +- activerecord/lib/active_record/timestamp.rb | 2 +- activerecord/test/cases/dirty_test.rb | 21 ++++++++++++++++++ 7 files changed, 46 insertions(+), 18 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 79c3d2b0f5..cf59699228 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -103,7 +103,7 @@ module ActiveRecord if has_cached_counter?(reflection) counter = cached_counter_attribute_name(reflection) owner[counter] += difference - owner.changed_attributes.delete(counter) # eww + owner.clear_attribute_changes([counter]) # eww end end diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 15cbbcff68..8cc1904575 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -30,10 +30,14 @@ module ActiveRecord def value # `defined?` is cheaper than `||=` when we get back falsy values - @value = type_cast(value_before_type_cast) unless defined?(@value) + @value = original_value unless defined?(@value) @value end + def original_value + type_cast(value_before_type_cast) + end + def value_for_database type.type_cast_for_database(value) end @@ -54,7 +58,7 @@ module ActiveRecord self.class.from_database(name, value, type) end - def type_cast + def type_cast(*) raise NotImplementedError end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index b58295a106..d3f4e51c33 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -51,14 +51,6 @@ module ActiveRecord super | changed_in_place end - def attribute_changed?(attr_name, options = {}) - result = super - # We can't change "from" something in place. Only setters can define - # "from" and "to" - result ||= changed_in_place?(attr_name) unless options.key?(:from) - result - end - def changes_applied super store_original_raw_attributes @@ -69,12 +61,16 @@ module ActiveRecord original_raw_attributes.clear end + def changed_attributes + super.reverse_merge(attributes_changed_in_place).freeze + end + private def calculate_changes_from_defaults @changed_attributes = nil self.class.column_defaults.each do |attr, orig_value| - changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value) + set_attribute_was(attr, orig_value) if _field_changed?(attr, orig_value) end end @@ -100,9 +96,9 @@ module ActiveRecord def save_changed_attribute(attr, old_value) if attribute_changed?(attr) - changed_attributes.delete(attr) unless _field_changed?(attr, old_value) + clear_attribute_changes(attr) unless _field_changed?(attr, old_value) else - changed_attributes[attr] = old_value if _field_changed?(attr, old_value) + set_attribute_was(attr, old_value) if _field_changed?(attr, old_value) end end @@ -132,6 +128,13 @@ module ActiveRecord @attributes[attr].changed_from?(old_value) end + def attributes_changed_in_place + changed_in_place.each_with_object({}) do |attr_name, h| + orig = @attributes[attr_name].original_value + h[attr_name] = orig + end + end + def changed_in_place self.class.attribute_names.select do |attr_name| changed_in_place?(attr_name) diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index f0ee433d0b..5958373e88 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -145,11 +145,11 @@ module ActiveRecord value = read_attribute(attr_name) if attribute_changed?(attr_name) if mapping[old] == value - changed_attributes.delete(attr_name) + clear_attribute_changes([attr_name]) end else if old != value - changed_attributes[attr_name] = mapping.key old + set_attribute_was(attr_name, mapping.key(old)) end end else diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 51b1931ed5..755ff2b2f1 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -466,7 +466,7 @@ module ActiveRecord changes[self.class.locking_column] = increment_lock if locking_enabled? - changed_attributes.except!(*changes.keys) + clear_attribute_changes(changes.keys) primary_key = self.class.primary_key self.class.unscoped.where(primary_key => self[primary_key]).update_all(changes) == 1 else diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 417cd61f0c..936a18d99a 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -114,7 +114,7 @@ module ActiveRecord def clear_timestamp_attributes all_timestamp_attributes_in_model.each do |attribute_name| self[attribute_name] = nil - changed_attributes.delete(attribute_name) + clear_attribute_changes([attribute_name]) end end end diff --git a/activerecord/test/cases/dirty_test.rb b/activerecord/test/cases/dirty_test.rb index 69a7f25213..0c77eedb52 100644 --- a/activerecord/test/cases/dirty_test.rb +++ b/activerecord/test/cases/dirty_test.rb @@ -661,6 +661,27 @@ class DirtyTest < ActiveRecord::TestCase assert_not model.foo_changed? end + test "in place mutation detection" do + pirate = Pirate.create!(catchphrase: "arrrr") + pirate.catchphrase << " matey!" + + assert pirate.catchphrase_changed? + expected_changes = { + "catchphrase" => ["arrrr", "arrrr matey!"] + } + assert_equal(expected_changes, pirate.changes) + assert_equal("arrrr", pirate.catchphrase_was) + assert pirate.catchphrase_changed?(from: "arrrr") + assert_not pirate.catchphrase_changed?(from: "anything else") + assert pirate.changed_attributes.include?(:catchphrase) + + pirate.save! + pirate.reload + + assert_equal "arrrr matey!", pirate.catchphrase + assert_not pirate.changed? + end + private def with_partial_writes(klass, on = true) old = klass.partial_writes? -- cgit v1.2.3 From 008f3da3835e47f719ba6820703ba404ff363640 Mon Sep 17 00:00:00 2001 From: Godfrey Chan Date: Sat, 16 Aug 2014 22:55:01 -0700 Subject: Don't expose these new APIs yet (added in 877ea78 / #16189) WARNING: don't use them! They might change or go away between future beta/RC/ patch releases! Also added a CHANGELOG entry for this. --- activerecord/CHANGELOG.md | 5 +++++ activerecord/lib/active_record/associations/has_many_association.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'activerecord') diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 02660ae4c2..495e9c77d7 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,8 @@ +* `*_was` and `changes` now work correctly for in-place attribute changes as + well. + + *Sean Griffin* + * Fix regression on after_commit that didnt fire when having nested transactions. Fixes #16425 diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index cf59699228..1413efaf7f 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -103,7 +103,7 @@ module ActiveRecord if has_cached_counter?(reflection) counter = cached_counter_attribute_name(reflection) owner[counter] += difference - owner.clear_attribute_changes([counter]) # eww + owner.send(:clear_attribute_changes, counter) # eww end end -- cgit v1.2.3