From b095bf890f2e000baa2daec9b2d919598947b246 Mon Sep 17 00:00:00 2001 From: claudiob Date: Wed, 31 Dec 2014 18:05:51 +0100 Subject: Fix doc formatting for `count_by_sql` Before: ![before](https://cloud.githubusercontent.com/assets/10076/5592809/25ce08e8-9199-11e4-9dfe-5baa8bd6b658.png) After: ![after](https://cloud.githubusercontent.com/assets/10076/5592810/25ceef9c-9199-11e4-88f4-d286203d7f6f.png) [ci skip] --- activerecord/lib/active_record/querying.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index e8de4db3a7..91c9a0db99 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -55,11 +55,12 @@ module ActiveRecord # The use of this method should be restricted to complicated SQL queries that can't be executed # using the ActiveRecord::Calculations class methods. Look into those before using this. # - # ==== Parameters + # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" + # # => 12 # - # * +sql+ - An SQL statement which should return a count query from the database, see the example below. + # ==== Parameters # - # Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id" + # * +sql+ - An SQL statement which should return a count query from the database, see the example above. def count_by_sql(sql) sql = sanitize_conditions(sql) connection.select_value(sql, "#{name} Count").to_i -- cgit v1.2.3 From 2386daabe7f8c979b453010dc0de3e1f6bbf859d Mon Sep 17 00:00:00 2001 From: claudiob Date: Thu, 16 Oct 2014 16:21:24 -0700 Subject: Throw :abort halts default CallbackChains This commit changes arguments and default value of CallbackChain's :terminator option. After this commit, Chains of callbacks defined **without** an explicit `:terminator` option will be halted as soon as a `before_` callback throws `:abort`. Chains of callbacks defined **with** a `:terminator` option will maintain their existing behavior of halting as soon as a `before_` callback matches the terminator's expectation. For instance, ActiveModel's callbacks will still halt the chain when a `before_` callback returns `false`. --- activerecord/lib/active_record/transactions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index de701edca0..95e9a8646e 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -17,7 +17,7 @@ module ActiveRecord included do define_callbacks :commit, :rollback, - terminator: ->(_, result) { result == false }, + terminator: ->(_, result_lambda) { result_lambda.call == false }, scope: [:kind, :name] mattr_accessor :raise_in_transactional_callbacks, instance_writer: false -- cgit v1.2.3 From d217daf6a740de7e4925872abe632982cfaab89b Mon Sep 17 00:00:00 2001 From: claudiob Date: Sun, 14 Dec 2014 22:46:23 -0800 Subject: Deprecate `false` as the way to halt AS callbacks After this commit, returning `false` in a callback will display a deprecation warning to make developers aware of the fact that they need to explicitly `throw(:abort)` if their intention is to halt a callback chain. This commit also patches two internal uses of AS::Callbacks (inside ActiveRecord and ActionDispatch) which sometimes return `false` but whose returned value is not meaningful for the purpose of execution. In both cases, the returned value is set to `true`, which does not affect the execution of the callbacks but prevents unrequested deprecation warnings from showing up. --- activerecord/lib/active_record/autosave_association.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index c39b045a5e..5045e3065e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -206,7 +206,13 @@ module ActiveRecord if reflection.validate? && !method_defined?(validation_method) method = (collection ? :validate_collection_association : :validate_single_association) - define_non_cyclic_method(validation_method) { send(method, reflection) } + define_non_cyclic_method(validation_method) do + send(method, reflection) + # TODO: remove the following line as soon as the return value of + # callbacks is ignored, that is, returning `false` does not + # display a deprecation warning or halts the callback chain. + true + end validate validation_method end end -- cgit v1.2.3 From bb78af73ab7e86fd9662e8810e346b082a1ae193 Mon Sep 17 00:00:00 2001 From: claudiob Date: Sun, 14 Dec 2014 22:10:15 -0800 Subject: Deprecate `false` as the way to halt AR callbacks Before this commit, returning `false` in an ActiveRecord `before_` callback such as `before_create` would halt the callback chain. After this commit, the behavior is deprecated: will still work until the next release of Rails but will also display a deprecation warning. The preferred way to halt a callback chain is to explicitly `throw(:abort)`. --- .../associations/has_many_association.rb | 2 +- .../associations/has_one_association.rb | 2 +- .../lib/active_record/autosave_association.rb | 2 +- activerecord/lib/active_record/callbacks.rb | 6 ++--- activerecord/lib/active_record/persistence.rb | 28 +++++++++++----------- activerecord/lib/active_record/transactions.rb | 1 - 6 files changed, 20 insertions(+), 21 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index d7f655d00c..2a782c06d0 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -17,7 +17,7 @@ module ActiveRecord unless empty? record = klass.human_attribute_name(reflection.name).downcase owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record) - false + throw(:abort) end else diff --git a/activerecord/lib/active_record/associations/has_one_association.rb b/activerecord/lib/active_record/associations/has_one_association.rb index 74b8c53758..41a75b820e 100644 --- a/activerecord/lib/active_record/associations/has_one_association.rb +++ b/activerecord/lib/active_record/associations/has_one_association.rb @@ -13,7 +13,7 @@ module ActiveRecord if load_target record = klass.human_attribute_name(reflection.name).downcase owner.errors.add(:base, :"restrict_dependent_destroy.one", record: record) - false + throw(:abort) end else diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 5045e3065e..fa6c5e9e8c 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -200,7 +200,7 @@ module ActiveRecord after_create save_method after_update save_method else - define_non_cyclic_method(save_method) { save_belongs_to_association(reflection) } + define_non_cyclic_method(save_method) { throw(:abort) if save_belongs_to_association(reflection) == false } before_save save_method end diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index 497ce8c15c..f44e5af5de 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -192,14 +192,14 @@ module ActiveRecord # # == before_validation* returning statements # - # If the returning value of a +before_validation+ callback can be evaluated to +false+, the process will be + # If the +before_validation+ callback throws +:abort+, the process will be # aborted and Base#save will return +false+. If Base#save! is called it will raise a # ActiveRecord::RecordInvalid exception. Nothing will be appended to the errors object. # # == Canceling callbacks # - # If a before_* callback returns +false+, all the later callbacks and the associated action are - # cancelled. If an after_* callback returns +false+, all the later callbacks are cancelled. + # If a before_* callback throws +:abort+, all the later callbacks and + # the associated action are cancelled. # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as # methods on the model, which are called last. # diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index f53c5f17ef..cf6673db2e 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -113,9 +113,9 @@ module ActiveRecord # the current time. However, if you supply touch: false, these # timestamps will not be updated. # - # There's a series of callbacks associated with +save+. If any of the - # before_* callbacks return +false+ the action is cancelled and - # +save+ returns +false+. See ActiveRecord::Callbacks for further + # There's a series of callbacks associated with #save. If any of the + # before_* callbacks throws +:abort+ the action is cancelled and + # #save returns +false+. See ActiveRecord::Callbacks for further # details. # # Attributes marked as readonly are silently ignored if the record is @@ -139,9 +139,9 @@ module ActiveRecord # the current time. However, if you supply touch: false, these # timestamps will not be updated. # - # There's a series of callbacks associated with save!. If any of - # the before_* callbacks return +false+ the action is cancelled - # and save! raises ActiveRecord::RecordNotSaved. See + # There's a series of callbacks associated with #save!. If any of + # the before_* callbacks throws +:abort+ the action is cancelled + # and #save! raises ActiveRecord::RecordNotSaved. See # ActiveRecord::Callbacks for further details. # # Attributes marked as readonly are silently ignored if the record is @@ -171,10 +171,10 @@ module ActiveRecord # Deletes the record in the database and freezes this instance to reflect # that no changes should be made (since they can't be persisted). # - # There's a series of callbacks associated with destroy. If - # the before_destroy callback return +false+ the action is cancelled - # and destroy returns +false+. See - # ActiveRecord::Callbacks for further details. + # There's a series of callbacks associated with #destroy. If the + # before_destroy callback throws +:abort+ the action is cancelled + # and #destroy returns +false+. + # See ActiveRecord::Callbacks for further details. def destroy raise ReadOnlyRecord, "#{self.class} is marked as readonly" if readonly? destroy_associations @@ -186,10 +186,10 @@ module ActiveRecord # Deletes the record in the database and freezes this instance to reflect # that no changes should be made (since they can't be persisted). # - # There's a series of callbacks associated with destroy!. If - # the before_destroy callback return +false+ the action is cancelled - # and destroy! raises ActiveRecord::RecordNotDestroyed. See - # ActiveRecord::Callbacks for further details. + # There's a series of callbacks associated with #destroy!. If the + # before_destroy callback throws +:abort+ the action is cancelled + # and #destroy! raises ActiveRecord::RecordNotDestroyed. + # See ActiveRecord::Callbacks for further details. def destroy! destroy || raise(ActiveRecord::RecordNotDestroyed, self) end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 95e9a8646e..31ca90fb58 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -17,7 +17,6 @@ module ActiveRecord included do define_callbacks :commit, :rollback, - terminator: ->(_, result_lambda) { result_lambda.call == false }, scope: [:kind, :name] mattr_accessor :raise_in_transactional_callbacks, instance_writer: false -- cgit v1.2.3 From abab2bf4af7ddd6168e2fe329a67f77d9afab53d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 4 Jan 2015 06:46:49 +0900 Subject: Stop passing the column to the `quote` method when quoting defaults Related the commit 8f8f8058e58dda20259c1caa61ec92542573643d. --- .../lib/active_record/connection_adapters/postgresql/quoting.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 607848884b..9de9e2c7dc 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -56,7 +56,8 @@ module ActiveRecord if column.type == :uuid && value =~ /\(\)/ value else - quote(value, column) + value = column.cast_type.type_cast_for_database(value) + quote(value) end end -- cgit v1.2.3 From 299e5f9c5758866ba4e580349da323fdda22c670 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 4 Jan 2015 07:07:35 +0900 Subject: `sql_type` has been determined already when quoting defaults No need to call `type_to_sql` again. --- .../active_record/connection_adapters/abstract/schema_creation.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 18ff869ea6..db20b60d60 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -28,8 +28,8 @@ module ActiveRecord end def visit_ColumnDefinition(o) - sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale) - column_sql = "#{quote_column_name(o.name)} #{sql_type}" + o.sql_type = type_to_sql(o.type, o.limit, o.precision, o.scale) + column_sql = "#{quote_column_name(o.name)} #{o.sql_type}" add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key column_sql end @@ -98,9 +98,7 @@ module ActiveRecord end def quote_default_expression(value, column) - column.sql_type ||= type_to_sql(column.type, column.limit, column.precision, column.scale) value = type_for_column(column).type_cast_for_database(value) - @conn.quote(value) end -- cgit v1.2.3 From 3225ebfa0632cd42a0fbcf0cbca36c7c06e54844 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Thu, 1 Jan 2015 00:00:00 -0800 Subject: Prefer `array?` rather than `array` Slightly refactoring `PostgreSQLColumn`. `array` should be readonly. `default_function` should be initialized by `super`. `sql_type` has been removed `[]`. Since we already choose to remove it we should not change. --- activerecord/lib/active_record/connection_adapters/column.rb | 4 ++-- .../lib/active_record/connection_adapters/postgresql/column.rb | 9 ++++----- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index af307b57a4..65d8b1a8ab 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -30,13 +30,13 @@ module ActiveRecord # company_name varchar(60). # It will be mapped to one of the standard Rails SQL types in the type attribute. # +null+ determines if this column allows +NULL+ values. - def initialize(name, default, cast_type, sql_type = nil, null = true) + def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil) @name = name @cast_type = cast_type @sql_type = sql_type @null = null @default = default - @default_function = nil + @default_function = default_function end def has_default? diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 1458fbf496..acb1278499 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -2,18 +2,17 @@ module ActiveRecord module ConnectionAdapters # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: - attr_accessor :array + attr_reader :array + alias :array? :array def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil) if sql_type =~ /\[\]$/ @array = true - super(name, default, cast_type, sql_type[0..sql_type.length - 3], null) + sql_type = sql_type[0..sql_type.length - 3] else @array = false - super(name, default, cast_type, sql_type, null) end - - @default_function = default_function + super end def serial? diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 13bb5c187e..5b070cae4f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -144,7 +144,7 @@ module ActiveRecord # AbstractAdapter def prepare_column_options(column) # :nodoc: spec = super - spec[:array] = 'true' if column.respond_to?(:array) && column.array + spec[:array] = 'true' if column.array? spec[:default] = "\"#{column.default_function}\"" if column.default_function spec end -- cgit v1.2.3 From 87c8ce340c6c83342df988df247e9035393ed7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 3 Jan 2015 23:19:29 -0300 Subject: Remove deprecated automatic counter caches on `has_many :through` --- .../associations/has_many_through_association.rb | 14 -------------- 1 file changed, 14 deletions(-) (limited to 'activerecord/lib/active_record') 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 7a050ca224..0dbd32ae83 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -49,16 +49,7 @@ module ActiveRecord end save_through_record(record) - if has_cached_counter? && !through_reflection_updates_counter_cache? - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Automatic updating of counter caches on through associations has been - deprecated, and will be removed in Rails 5. Instead, please set the - appropriate `counter_cache` options on the `has_many` and `belongs_to` - for your associations to #{through_reflection.name}. - MSG - update_counter_in_database(1) - end record end @@ -211,11 +202,6 @@ module ActiveRecord def invertible_for?(record) false end - - def through_reflection_updates_counter_cache? - counter_name = cached_counter_attribute_name - inverse_updates_counter_named?(counter_name, through_reflection) - end end end end -- cgit v1.2.3 From eef8a2c6f249d923f191491ddb10d20faa4e170a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 3 Jan 2015 23:24:04 -0300 Subject: Remove `cache_attributes` and friends --- activerecord/lib/active_record/attribute_methods/read.rb | 11 ----------- 1 file changed, 11 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 20f0936e52..f6ab543015 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -36,19 +36,8 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - [:cache_attributes, :cached_attributes, :cache_attribute?].each do |method_name| - define_method method_name do |*| - cached_attributes_deprecation_warning(method_name) - true - end - end - protected - def cached_attributes_deprecation_warning(method_name) - ActiveSupport::Deprecation.warn "Calling `#{method_name}` is no longer necessary. All attributes are cached." - end - if Module.methods_transplantable? def define_method_attribute(name) method = ReaderMethodCache[name] -- cgit v1.2.3 From 82043ab53cb186d59b1b3be06122861758f814b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 3 Jan 2015 23:36:27 -0300 Subject: Remove deprecated `serialized_attributes` --- .../lib/active_record/attribute_methods/serialization.rb | 13 ------------- 1 file changed, 13 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index e5ec5ddca5..7c433dd366 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -51,19 +51,6 @@ module ActiveRecord Type::Serialized.new(type, coder) end end - - def serialized_attributes - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `serialized_attributes` is deprecated without replacement, and will - be removed in Rails 5.0. - MSG - - @serialized_attributes ||= Hash[ - columns.select { |t| t.cast_type.is_a?(Type::Serialized) }.map { |c| - [c.name, c.cast_type.coder] - } - ] - end end end end -- cgit v1.2.3 From 634ecdbf1b6401ded0e145b1e7c7f4808ad89398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 3 Jan 2015 23:42:59 -0300 Subject: Return a null column from `column_for_attribute` when no column exists. This reverts commit ae96f229f6501d8635811d6b22d75d43cdb880a4. Conflicts: activerecord/CHANGELOG.md activerecord/lib/active_record/attribute_methods.rb --- .../active_record/associations/preloader/association.rb | 4 ++-- activerecord/lib/active_record/attribute_methods.rb | 16 ++++++---------- .../lib/active_record/connection_adapters/column.rb | 6 ++++++ 3 files changed, 14 insertions(+), 12 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index a6e1a24360..afcaa5d55a 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -104,11 +104,11 @@ module ActiveRecord end def association_key_type - @klass.type_for_attribute(association_key_name.to_s).type + @klass.column_for_attribute(association_key_name).type end def owner_key_type - @model.type_for_attribute(owner_key_name.to_s).type + @model.column_for_attribute(owner_key_name).type end def load_slices(slices) diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index b7edac791e..8f165fb1dc 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -192,7 +192,8 @@ module ActiveRecord end # Returns the column object for the named attribute. - # Returns nil if the named attribute does not exist. + # Returns a +ActiveRecord::ConnectionAdapters::NullColumn+ if the + # named attribute does not exist. # # class Person < ActiveRecord::Base # end @@ -202,17 +203,12 @@ module ActiveRecord # # => # # # person.column_for_attribute(:nothing) - # # => nil + # # => #, ...> def column_for_attribute(name) - column = columns_hash[name.to_s] - if column.nil? - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#column_for_attribute` will return a null object for non-existent - columns in Rails 5. Use `#has_attribute?` if you need to check for - an attribute's existence. - MSG + name = name.to_s + columns_hash.fetch(name) do + ConnectionAdapters::NullColumn.new(name) end - column end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 65d8b1a8ab..d7f999c3c6 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -77,6 +77,12 @@ module ActiveRecord [self.class, name, default, cast_type, sql_type, null, default_function] end end + + class NullColumn < Column + def initialize(name) + super name, nil, Type::Value.new + end + end end # :startdoc: end -- cgit v1.2.3 From 94c87156fd67047d96a6805f2f7b8db0a59cd37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sat, 3 Jan 2015 23:49:41 -0300 Subject: Return an array of pools from `connection_pools` --- .../connection_adapters/abstract/connection_pool.rb | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 6235745fb2..1371317e3c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -2,7 +2,6 @@ require 'thread' require 'thread_safe' require 'monitor' require 'set' -require 'active_support/core_ext/string/filters' module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -517,15 +516,7 @@ module ActiveRecord def connection_pool_list owner_to_pool.values.compact end - - def connection_pools - ActiveSupport::Deprecation.warn(<<-MSG.squish) - In the next release, this will return the same as `#connection_pool_list`. - (An array of pools, rather than a hash mapping specs to pools.) - MSG - - Hash[connection_pool_list.map { |pool| [pool.spec, pool] }] - end + alias :connection_pools :connection_pool_list def establish_connection(owner, spec) @class_to_pool.clear -- cgit v1.2.3 From a939506f297b667291480f26fa32a373a18ae06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 00:24:40 -0300 Subject: Change the default `null` value for `timestamps` to `false` --- .../connection_adapters/abstract/schema_definitions.rb | 18 +++--------------- .../connection_adapters/abstract/schema_statements.rb | 4 ++-- .../connection_adapters/abstract_adapter.rb | 1 - 3 files changed, 5 insertions(+), 18 deletions(-) (limited to 'activerecord/lib/active_record') 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 68d8a2cd6a..7eaa89c9a7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -56,18 +56,6 @@ module ActiveRecord end end - module TimestampDefaultDeprecation # :nodoc: - def emit_warning_if_null_unspecified(options) - return if options.key?(:null) - - ActiveSupport::Deprecation.warn(<<-MSG.squish) - `#timestamp` was called without specifying an option for `null`. In Rails 5, - this behavior will change to `null: false`. You should manually specify - `null: true` to prevent the behavior of your existing migrations from changing. - MSG - end - end - class ReferenceDefinition # :nodoc: def initialize( name, @@ -167,8 +155,6 @@ 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 @@ -375,7 +361,9 @@ module ActiveRecord # t.timestamps null: false def timestamps(*args) options = args.extract_options! - emit_warning_if_null_unspecified(options) + + options[:null] = false if options[:null].nil? + column(:created_at, :datetime, options) column(:updated_at, :datetime, options) 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 34d60493ea..0f44c332ae 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -851,14 +851,14 @@ module ActiveRecord columns end - include TimestampDefaultDeprecation # Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+. # Additional options (like null: false) are forwarded to #add_column. # # add_timestamps(:suppliers, null: false) # def add_timestamps(table_name, options = {}) - emit_warning_if_null_unspecified(options) + options[:null] = false if options[:null].nil? + add_column table_name, :created_at, :datetime, options add_column table_name, :updated_at, :datetime, options end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index fa24d9b43f..c3a8bf5c74 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -25,7 +25,6 @@ module ActiveRecord autoload :TableDefinition autoload :Table autoload :AlterTable - autoload :TimestampDefaultDeprecation end autoload_at 'active_record/connection_adapters/abstract/connection_pool' do -- cgit v1.2.3 From efdc20f36ccc37afbb2705eb9acca76dd8aabd4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 00:32:18 -0300 Subject: Remove deprecated access to connection specification using a string acessor. Now all strings will be handled as a URL. --- .../connection_adapters/connection_specification.rb | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index e54e3199ff..08d46fca96 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -1,5 +1,4 @@ require 'uri' -require 'active_support/core_ext/string/filters' module ActiveRecord module ConnectionAdapters @@ -210,30 +209,12 @@ module ActiveRecord when Symbol resolve_symbol_connection spec when String - resolve_string_connection spec + resolve_url_connection spec when Hash resolve_hash_connection spec end end - def resolve_string_connection(spec) - # Rails has historically accepted a string to mean either - # an environment key or a URL spec, so we have deprecated - # this ambiguous behaviour and in the future this function - # can be removed in favor of resolve_url_connection. - if configurations.key?(spec) || spec !~ /:/ - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing a string to ActiveRecord::Base.establish_connection for a - configuration lookup is deprecated, please pass a symbol - (#{spec.to_sym.inspect}) instead. - MSG - - resolve_symbol_connection(spec) - else - resolve_url_connection(spec) - end - end - # Takes the environment such as +:production+ or +:development+. # This requires that the @configurations was initialized with a key that # matches. -- cgit v1.2.3 From 0fbd1fc888ffb8cbe1191193bf86933110693dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 00:45:54 -0300 Subject: Remove deprecated `ActiveRecord::Base.disable_implicit_join_references=` --- activerecord/lib/active_record/core.rb | 7 ------- 1 file changed, 7 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 38b2d632d2..5c7c0feeb7 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -87,13 +87,6 @@ module ActiveRecord mattr_accessor :maintain_test_schema, instance_accessor: false - def self.disable_implicit_join_references=(value) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Implicit join references were removed with Rails 4.1. - Make sure to remove this configuration because it does nothing. - MSG - end - class_attribute :default_connection_handler, instance_writer: false class_attribute :find_by_statement_cache -- cgit v1.2.3 From c8e6a76e5059e39a20ee7a7b1b4ddd22af571989 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 01:00:00 -0300 Subject: Remove deprecated `ActiveRecord::Fixtures` constant --- activerecord/lib/active_record/fixtures.rb | 6 ------ 1 file changed, 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 4732462b05..3c71936c3b 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -768,12 +768,6 @@ module ActiveRecord end - #-- - # Deprecate 'Fixtures' in favor of 'FixtureSet'. - #++ - # :nodoc: - Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet') - class Fixture #:nodoc: include Enumerable -- cgit v1.2.3 From 9013e28e52eba3a6ffcede26f85df48d264b8951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 01:03:53 -0300 Subject: Remove deprecated `symbolized_base_class` and `symbolized_sti_name` --- activerecord/lib/active_record/inheritance.rb | 10 ---------- 1 file changed, 10 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index b91e9ac137..fd1e22349b 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -79,16 +79,6 @@ module ActiveRecord :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true) end - def symbolized_base_class - ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_base_class` is deprecated and will be removed without replacement.') - @symbolized_base_class ||= base_class.to_s.to_sym - end - - def symbolized_sti_name - ActiveSupport::Deprecation.warn('`ActiveRecord::Base.symbolized_sti_name` is deprecated and will be removed without replacement.') - @symbolized_sti_name ||= sti_name.present? ? sti_name.to_sym : symbolized_base_class - end - # Returns the class descending directly from ActiveRecord::Base, or # an abstract class, if any, in the inheritance hierarchy. # -- cgit v1.2.3 From ede8c199a85cfbb6457d5630ec1e285e5ec49313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 10:49:49 -0300 Subject: Remove deprecated `Reflection#source_macro` --- activerecord/lib/active_record/reflection.rb | 19 ------------------- 1 file changed, 19 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 7696ef13c7..66f2b6b768 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -153,15 +153,6 @@ module ActiveRecord JoinKeys.new(foreign_key, active_record_primary_key) end - def source_macro - ActiveSupport::Deprecation.warn(<<-MSG.squish) - ActiveRecord::Base.source_macro is deprecated and will be removed - without replacement. - MSG - - macro - end - def constraints scope_chain.flatten end @@ -763,16 +754,6 @@ module ActiveRecord source_reflection.join_keys(association_klass) end - # The macro used by the source association - def source_macro - ActiveSupport::Deprecation.warn(<<-MSG.squish) - ActiveRecord::Base.source_macro is deprecated and will be removed - without replacement. - MSG - - source_reflection.source_macro - end - # A through association is nested if there would be more than one join table def nested? chain.length > 2 -- cgit v1.2.3 From 3a59dd212315ebb9bae8338b98af259ac00bbef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 11:07:03 -0300 Subject: Remove deprecated `sanitize_sql_hash_for_conditions` --- activerecord/lib/active_record/sanitization.rb | 29 -------------------------- 1 file changed, 29 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index f5aa60a69a..768a72a947 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -72,35 +72,6 @@ module ActiveRecord expanded_attrs end - # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause. - # { name: "foo'bar", group_id: 4 } - # # => "name='foo''bar' and group_id= 4" - # { status: nil, group_id: [1,2,3] } - # # => "status IS NULL and group_id IN (1,2,3)" - # { age: 13..18 } - # # => "age BETWEEN 13 AND 18" - # { 'other_records.id' => 7 } - # # => "`other_records`.`id` = 7" - # { other_records: { id: 7 } } - # # => "`other_records`.`id` = 7" - # And for value objects on a composed_of relationship: - # { address: Address.new("123 abc st.", "chicago") } - # # => "address_street='123 abc st.' and address_city='chicago'" - def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name) - table = Arel::Table.new(table_name).alias(default_table_name) - predicate_builder = PredicateBuilder.new(TableMetadata.new(self, table)) - ActiveSupport::Deprecation.warn(<<-EOWARN) -sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0 - EOWARN - attrs = predicate_builder.resolve_column_aliases(attrs) - attrs = expand_hash_conditions_for_aggregates(attrs) - - predicate_builder.build_from_hash(attrs).map { |b| - connection.visitor.compile b - }.join(' AND ') - end - alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions - # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause. # { status: nil, group_id: 1 } # # => "status = NULL , group_id = 1" -- cgit v1.2.3 From 07d3d402341e81ada0214f2cb2be1da69eadfe72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 11:23:57 -0300 Subject: Change transaction callbacks to not swallowing errors. Before this change any error raised inside a transaction callback are rescued and printed in the logs. Now these errors are not rescue anymore and just bubble up, as the other callbacks. --- .../connection_adapters/abstract/transaction.rb | 14 ++--------- activerecord/lib/active_record/transactions.rb | 29 ++++++++-------------- 2 files changed, 12 insertions(+), 31 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index fd666c8c39..f6ef3b0675 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -69,12 +69,7 @@ module ActiveRecord def rollback_records ite = records.uniq while record = ite.shift - begin - record.rolledback! full_rollback? - rescue => e - raise if ActiveRecord::Base.raise_in_transactional_callbacks - record.logger.error(e) if record.respond_to?(:logger) && record.logger - end + record.rolledback! full_rollback? end ensure ite.each do |i| @@ -89,12 +84,7 @@ module ActiveRecord def commit_records ite = records.uniq while record = ite.shift - begin - record.committed! - rescue => e - raise if ActiveRecord::Base.raise_in_transactional_callbacks - record.logger.error(e) if record.respond_to?(:logger) && record.logger - end + record.committed! end ensure ite.each do |i| diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 31ca90fb58..1c48196762 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -4,23 +4,10 @@ module ActiveRecord extend ActiveSupport::Concern #:nodoc: ACTIONS = [:create, :destroy, :update] - #:nodoc: - CALLBACK_WARN_MESSAGE = "Currently, Active Record suppresses errors raised " \ - "within `after_rollback`/`after_commit` callbacks and only print them to " \ - "the logs. In the next version, these errors will no longer be suppressed. " \ - "Instead, the errors will propagate normally just like in other Active " \ - "Record callbacks.\n" \ - "\n" \ - "You can opt into the new behavior and remove this warning by setting:\n" \ - "\n" \ - " config.active_record.raise_in_transactional_callbacks = true\n\n" included do define_callbacks :commit, :rollback, scope: [:kind, :name] - - mattr_accessor :raise_in_transactional_callbacks, instance_writer: false - self.raise_in_transactional_callbacks = false end # = Active Record Transactions @@ -236,9 +223,6 @@ module ActiveRecord def after_commit(*args, &block) set_options_for_callbacks!(args) set_callback(:commit, :after, *args, &block) - unless ActiveRecord::Base.raise_in_transactional_callbacks - ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE) - end end # This callback is called after a create, update, or destroy are rolled back. @@ -247,9 +231,16 @@ module ActiveRecord def after_rollback(*args, &block) set_options_for_callbacks!(args) set_callback(:rollback, :after, *args, &block) - unless ActiveRecord::Base.raise_in_transactional_callbacks - ActiveSupport::Deprecation.warn(CALLBACK_WARN_MESSAGE) - end + end + + def raise_in_transactional_callbacks + ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks is deprecated and will be removed without replacement.') + true + end + + def raise_in_transactional_callbacks=(value) + ActiveSupport::Deprecation.warn('ActiveRecord::Base.raise_in_transactional_callbacks= is deprecated, has no effect and will be removed without replacement.') + value end private -- cgit v1.2.3 From a502703c3d2151d4d3b421b29fefdac5ad05df61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 11:31:58 -0300 Subject: Change the behavior of boolean columns to be closer to Ruby's semantics. Before this change we had a small set of "truthy", and all others are "falsy". Now, we have a small set of "falsy" values and all others are "truthy" matching Ruby's semantics. --- .../lib/active_record/connection_adapters/column.rb | 1 - activerecord/lib/active_record/type/boolean.rb | 15 +++------------ 2 files changed, 3 insertions(+), 13 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index d7f999c3c6..e74de60a83 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -5,7 +5,6 @@ module ActiveRecord module ConnectionAdapters # An abstract definition of a column in a table. class Column - TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON'].to_set FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set module Format diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb index 978d16d524..2e24afc7c0 100644 --- a/activerecord/lib/active_record/type/boolean.rb +++ b/activerecord/lib/active_record/type/boolean.rb @@ -10,19 +10,10 @@ module ActiveRecord def cast_value(value) if value == '' nil - elsif ConnectionAdapters::Column::TRUE_VALUES.include?(value) - true - else - if !ConnectionAdapters::Column::FALSE_VALUES.include?(value) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - You attempted to assign a value which is not explicitly `true` or `false` - to a boolean column. Currently this value casts to `false`. This will - change to match Ruby's semantics, and will cast to `true` in Rails 5. - If you would like to maintain the current behavior, you should - explicitly handle the values you would like cast to `false`. - MSG - end + elsif ConnectionAdapters::Column::FALSE_VALUES.include?(value) false + else + true end end end -- cgit v1.2.3 From bf7b8c193ffe2d6a05272a6ed763d87cfe743bb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Sun, 4 Jan 2015 12:08:51 -0300 Subject: Remove unneeded requires These requires were added only to change deprecation message --- .../lib/active_record/associations/has_many_through_association.rb | 2 -- activerecord/lib/active_record/attribute_methods/serialization.rb | 2 -- .../lib/active_record/relation/predicate_builder/array_handler.rb | 2 -- 3 files changed, 6 deletions(-) (limited to 'activerecord/lib/active_record') 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 0dbd32ae83..f1e784d771 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/filters' - module ActiveRecord # = Active Record Has Many Through Association module Associations diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index 7c433dd366..d0d8a968c5 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/filters' - module ActiveRecord module AttributeMethods module Serialization diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb index 4b5f5773a0..95dbd6a77f 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/string/filters' - module ActiveRecord class PredicateBuilder class ArrayHandler # :nodoc: -- cgit v1.2.3 From adfeeb01e798278ad43b67d882bdd1f54aff3b3f Mon Sep 17 00:00:00 2001 From: claudiob Date: Sun, 4 Jan 2015 07:45:07 -0800 Subject: Remove unneeded `require 'as/deprecation'` Tests should still pass after removing `require 'active_support/deprecation'` from these files since the related deprecations have been removed. --- activerecord/lib/active_record/relation/delegation.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 50f4d5c7ab..d4a8823cfe 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -1,6 +1,5 @@ require 'set' require 'active_support/concern' -require 'active_support/deprecation' module ActiveRecord module Delegation # :nodoc: -- cgit v1.2.3 From 5a58ba3366ec6092fcd0e69340acd93f347d2576 Mon Sep 17 00:00:00 2001 From: robertomiranda Date: Sun, 28 Dec 2014 17:24:10 -0500 Subject: Add has_secure_token to Active Record Update SecureToken Docs Add Changelog entry for has_secure_token [ci skip] --- activerecord/lib/active_record/base.rb | 1 + activerecord/lib/active_record/secure_token.rb | 49 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 activerecord/lib/active_record/secure_token.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index bb01231bca..100d3780f6 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -312,6 +312,7 @@ module ActiveRecord #:nodoc: include Reflection include Serialization include Store + include SecureToken end ActiveSupport.run_load_hooks(:active_record, Base) diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb new file mode 100644 index 0000000000..23d4292cbb --- /dev/null +++ b/activerecord/lib/active_record/secure_token.rb @@ -0,0 +1,49 @@ +module ActiveRecord + module SecureToken + extend ActiveSupport::Concern + + module ClassMethods + # Example using has_secure_token + # + # # Schema: User(toke:string, auth_token:string) + # class User < ActiveRecord::Base + # has_secure_token + # has_secure_token :auth_token + # end + # + # user = User.new + # user.save + # user.token # => "44539a6a59835a4ee9d7b112" + # user.auth_token # => "e2426a93718d1817a43abbaa" + # user.regenerate_token # => true + # user.regenerate_auth_token # => true + # + # SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely. + # We'll check to see if the generated token has been used already using #exists?, and retry up to 10 + # times to find another unused token. After that a RuntimeError is raised if the problem persists. + # + # Note that it's still possible to generate a race condition in the database in the same way that + # validates_presence_of can. You're encouraged to add a unique index in the database to deal with + # this even more unlikely scenario. + def has_secure_token(attribute = :token) + # Load securerandom only when has_secure_key is used. + require 'securerandom' + define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) } + before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) } + end + + def generate_unique_secure_token(attribute) + 10.times do |i| + SecureRandom.hex(12).tap do |token| + if exists?(attribute => token) + raise "Couldn't generate a unique token in 10 attempts!" if i == 9 + else + return token + end + end + end + end + end + end +end + -- cgit v1.2.3 From 8c5983c5f00f6b77bd88f6ebad0ae31573f0ef96 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Sun, 4 Jan 2015 14:54:56 -0200 Subject: Simplify boolean casting logic --- activerecord/lib/active_record/type/boolean.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/type/boolean.rb b/activerecord/lib/active_record/type/boolean.rb index 2e24afc7c0..f6a75512fd 100644 --- a/activerecord/lib/active_record/type/boolean.rb +++ b/activerecord/lib/active_record/type/boolean.rb @@ -10,10 +10,8 @@ module ActiveRecord def cast_value(value) if value == '' nil - elsif ConnectionAdapters::Column::FALSE_VALUES.include?(value) - false else - true + !ConnectionAdapters::Column::FALSE_VALUES.include?(value) end end end -- cgit v1.2.3 From b2bb2a462f8950522688e8e0d87a7d32a5863438 Mon Sep 17 00:00:00 2001 From: Kuldeep Aggarwal Date: Sat, 29 Nov 2014 01:02:01 +0530 Subject: remove files which is dependent on ruby1.9 as we do not support Ruby1.9 Conflicts: activerecord/lib/active_record/attribute_methods/read.rb --- .../lib/active_record/attribute_methods/read.rb | 35 ++++++++-------------- .../lib/active_record/attribute_methods/write.rb | 33 +++++++------------- 2 files changed, 24 insertions(+), 44 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index f6ab543015..4b72fe7d7e 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/module/method_transplanting' - module ActiveRecord module AttributeMethods module Read @@ -38,29 +36,22 @@ module ActiveRecord module ClassMethods protected - if Module.methods_transplantable? - def define_method_attribute(name) - method = ReaderMethodCache[name] - generated_attribute_methods.module_eval { define_method name, method } - end - else - def define_method_attribute(name) - safe_name = name.unpack('h*').first - temp_method = "__temp__#{safe_name}" - - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + def define_method_attribute(name) + safe_name = name.unpack('h*').first + temp_method = "__temp__#{safe_name}" - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def #{temp_method} - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - _read_attribute(name) { |n| missing_attribute(n, caller) } - end - STR + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - generated_attribute_methods.module_eval do - alias_method name, temp_method - undef_method temp_method + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def #{temp_method} + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + _read_attribute(name) { |n| missing_attribute(n, caller) } end + STR + + generated_attribute_methods.module_eval do + alias_method name, temp_method + undef_method temp_method end end end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 16804f86bf..ab017c7b54 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/module/method_transplanting' - module ActiveRecord module AttributeMethods module Write @@ -25,27 +23,18 @@ module ActiveRecord module ClassMethods protected - if Module.methods_transplantable? - def define_method_attribute=(name) - method = WriterMethodCache[name] - generated_attribute_methods.module_eval { - define_method "#{name}=", method - } - end - else - def define_method_attribute=(name) - safe_name = name.unpack('h*').first - ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name + def define_method_attribute=(name) + safe_name = name.unpack('h*').first + ActiveRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name - generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 - def __temp__#{safe_name}=(value) - name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - write_attribute(name, value) - end - alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= - undef_method :__temp__#{safe_name}= - STR - end + generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 + def __temp__#{safe_name}=(value) + name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} + write_attribute(name, value) + end + alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= + undef_method :__temp__#{safe_name}= + STR end end -- cgit v1.2.3 From fb71fa695d214eb5aaa6f95440347e3a08f03b38 Mon Sep 17 00:00:00 2001 From: Miklos Fazkeas Date: Fri, 22 Aug 2014 16:41:40 +0200 Subject: Fix potenital stack level too deep with autosave or validation When associations checked for autosave have a cycle, and none of them is dirty, then changed_for_autosave? will be an infinite loop. We now remember if we're in the check and will short circuit the recursion. --- activerecord/lib/active_record/autosave_association.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index fa6c5e9e8c..fcaaffb852 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -278,11 +278,18 @@ module ActiveRecord # go through nested autosave associations that are loaded in memory (without loading # any new ones), and return true if is changed for autosave def nested_records_changed_for_autosave? - self.class._reflections.values.any? do |reflection| - if reflection.options[:autosave] - association = association_instance_get(reflection.name) - association && Array.wrap(association.target).any?(&:changed_for_autosave?) + @_nested_records_changed_for_autosave_already_called ||= false + return false if @_nested_records_changed_for_autosave_already_called + begin + @_nested_records_changed_for_autosave_already_called = true + self.class._reflections.values.any? do |reflection| + if reflection.options[:autosave] + association = association_instance_get(reflection.name) + association && Array.wrap(association.target).any?(&:changed_for_autosave?) + end end + ensure + @_nested_records_changed_for_autosave_already_called = false end end -- cgit v1.2.3 From 3ae98181433dda1b5e19910e107494762512a86c Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 5 Jan 2015 10:08:06 +0100 Subject: remove deprecation warning when modifying a Relation with cached arel. This adresses https://github.com/rails/rails/commit/1b7aa62b184c4410c99208f71b59bbac5c5f03be#commitcomment-9147803 --- .../lib/active_record/relation/query_methods.rb | 35 +++++++++------------- 1 file changed, 14 insertions(+), 21 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index ef380abfe8..6892cc114c 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -62,15 +62,14 @@ module ActiveRecord Relation::MULTI_VALUE_METHODS.each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 - def #{name}_values # def select_values - @values[:#{name}] || [] # @values[:select] || [] - end # end - # - def #{name}_values=(values) # def select_values=(values) - raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded - check_cached_relation - @values[:#{name}] = values # @values[:select] = values - end # end + def #{name}_values # def select_values + @values[:#{name}] || [] # @values[:select] || [] + end # end + # + def #{name}_values=(values) # def select_values=(values) + assert_mutability! # assert_mutability! + @values[:#{name}] = values # @values[:select] = values + end # end CODE end @@ -85,23 +84,12 @@ module ActiveRecord Relation::SINGLE_VALUE_METHODS.each do |name| class_eval <<-CODE, __FILE__, __LINE__ + 1 def #{name}_value=(value) # def readonly_value=(value) - raise ImmutableRelation if @loaded # raise ImmutableRelation if @loaded - check_cached_relation + assert_mutability! # assert_mutability! @values[:#{name}] = value # @values[:readonly] = value end # end CODE end - def check_cached_relation # :nodoc: - if defined?(@arel) && @arel - @arel = nil - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Modifying already cached Relation. The cache will be reset. Use a - cloned Relation to prevent this warning. - MSG - end - end - def create_with_value # :nodoc: @values[:create_with] || {} end @@ -857,6 +845,11 @@ module ActiveRecord private + def assert_mutability! + raise ImmutableRelation if @loaded + raise ImmutableRelation if defined?(@arel) && @arel + end + def build_arel arel = Arel::SelectManager.new(table) -- cgit v1.2.3 From a076256d63f64d194b8f634890527a5ed2651115 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 5 Jan 2015 11:09:29 +0100 Subject: remove deprecated support for PG ranges with exclusive lower bounds. addresses https://github.com/rails/rails/commit/91949e48cf41af9f3e4ffba3e5eecf9b0a08bfc3#commitcomment-9144563 --- .../active_record/connection_adapters/postgresql/oid/range.rb | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb index 961e6224c4..3adfb8b9d8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -25,16 +25,7 @@ module ActiveRecord to = type_cast_single extracted[:to] if !infinity?(from) && extracted[:exclude_start] - if from.respond_to?(:succ) - from = from.succ - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Excluding the beginning of a Range is only partialy supported - through `#succ`. This is not reliable and will be removed in - the future. - MSG - else - raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" - end + raise ArgumentError, "The Ruby Range object does not support excluding the beginning of a Range. (unsupported value: '#{value}')" end ::Range.new(from, to, extracted[:exclude_end]) end -- cgit v1.2.3 From 4ed97979d14c5e92eb212b1a629da0a214084078 Mon Sep 17 00:00:00 2001 From: Yves Senn Date: Mon, 5 Jan 2015 11:44:14 +0100 Subject: remove deprecated support to preload instance-dependent associaitons. Addresses https://github.com/rails/rails/commit/ed56e596a0467390011bc9d56d462539776adac1#commitcomment-9145960 --- activerecord/lib/active_record/reflection.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 66f2b6b768..dab5a502a5 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -343,13 +343,10 @@ module ActiveRecord return unless scope if scope.arity > 0 - ActiveSupport::Deprecation.warn(<<-MSG.squish) + raise ArgumentError, <<-MSG.squish The association scope '#{name}' is instance dependent (the scope - block takes an argument). Preloading happens before the individual - instances are created. This means that there is no instance being - passed to the association scope. This will most likely result in - broken or incorrect behavior. Joining, Preloading and eager loading - of these associations is deprecated and will be removed in the future. + block takes an argument). Preloading instance dependent scopes is + not supported. MSG end end -- cgit v1.2.3 From 7b910917d39bb7d7c5b1b7cdbfb14ff001cac7cc Mon Sep 17 00:00:00 2001 From: Matt Hogan Date: Mon, 5 Jan 2015 09:02:44 -0600 Subject: Fix TypeError in Fixture creation Ruby 4.2 started doing `value.gsub('$LABEL', label)` for fixture label interpolation, but you can have have valid YAML where `label` isn't a String. For example: ```YAML 0: name: John email: johndoe@gmail.com 1: name: Jane email: janedoe@gmail.com ``` This YAML will create a label that is a Fixnum, causing `TypeError: no implicit conversion of Fixnum into String.` --- activerecord/lib/active_record/fixtures.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 3c71936c3b..5f6a75ebef 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -633,7 +633,7 @@ module ActiveRecord # interpolate the fixture label row.each do |key, value| - row[key] = value.gsub("$LABEL", label) if value.is_a?(String) + row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String) end # generate a primary key if necessary -- cgit v1.2.3 From f634c1fcf4796f633685f6801e260e0e792e547e Mon Sep 17 00:00:00 2001 From: Jonathan del Strother Date: Mon, 5 Jan 2015 19:42:39 +0000 Subject: Fix rollback of primarykey-less tables MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If you have a table without a primary key, and an `after_commit` callback on that table (ie `has_transactional_callbacks?` returns true), then trying to rollback a transaction involving that record would result in “ActiveModel::MissingAttributeError: can't write unknown attribute ``” --- activerecord/lib/active_record/transactions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 31ca90fb58..97648ec898 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -387,7 +387,7 @@ module ActiveRecord thaw unless restore_state[:frozen?] @new_record = restore_state[:new_record] @destroyed = restore_state[:destroyed] - write_attribute(self.class.primary_key, restore_state[:id]) + write_attribute(self.class.primary_key, restore_state[:id]) if self.class.primary_key end end end -- cgit v1.2.3 From 00dad0343b3aa6cf019afe3c727d4a3d95ddc383 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 5 Jan 2015 14:45:14 -0700 Subject: Define attribute methods before attempting to populate records `initialize_internals_callback` will attempt to assign attributes from the current scope, which will fail if something defined the method and calls super (meaning it won't hit `method_missing`). Fixes #18339 --- activerecord/lib/active_record/core.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 5c7c0feeb7..5a5139256d 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -276,11 +276,11 @@ module ActiveRecord # User.new(first_name: 'Jamie') def initialize(attributes = nil, options = {}) @attributes = self.class._default_attributes.dup + self.class.define_attribute_methods init_internals initialize_internals_callback - self.class.define_attribute_methods # +options+ argument is only needed to make protected_attributes gem easier to hook. # Remove it when we drop support to this gem. init_attributes(attributes, options) if attributes -- cgit v1.2.3 From 42e386173f888c5762130db8db132bc33d739e4f Mon Sep 17 00:00:00 2001 From: brainopia Date: Tue, 6 Jan 2015 02:25:19 +0300 Subject: Correctly fetch bind_values from join in subquery --- activerecord/lib/active_record/relation/query_methods.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 6892cc114c..f054e17017 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -745,6 +745,9 @@ module ActiveRecord def from!(value, subquery_name = nil) # :nodoc: self.from_value = [value, subquery_name] + if value.is_a? Relation + self.bind_values = value.arel.bind_values + value.bind_values + bind_values + end self end @@ -999,7 +1002,6 @@ module ActiveRecord case opts when Relation name ||= 'subquery' - self.bind_values = opts.bind_values + self.bind_values opts.arel.as(name.to_s) else opts -- cgit v1.2.3 From 8da936a5d370aa2616ef532e1b65a32c514f518d Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Tue, 6 Jan 2015 14:54:41 +0530 Subject: Fix lookup of fixtures with non-string label - Fixtures with non-string labels such as integers should be accessed using integer label as key. For eg. pirates(1) or pirates(42). - But this results in NotFound error because the label is converted into string before looking up into the fixtures hash. - After this commit, the label is converted into string only if its a symbol. - This issue was fount out while adding a test case for https://github.com/rails/rails/commit/7b910917. --- activerecord/lib/active_record/fixtures.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 5f6a75ebef..10e9be20b5 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -882,7 +882,7 @@ module ActiveRecord @fixture_cache[fs_name] ||= {} instances = fixture_names.map do |f_name| - f_name = f_name.to_s + f_name = f_name.to_s if f_name.is_a?(Symbol) @fixture_cache[fs_name].delete(f_name) if force_reload if @loaded_fixtures[fs_name][f_name] -- cgit v1.2.3 From ff286eb3e3c063c30f236322e1f8b6dd3c7cf376 Mon Sep 17 00:00:00 2001 From: George Millo Date: Tue, 6 Jan 2015 12:20:58 +0000 Subject: replacing 'attribute' method with an alias --- activerecord/lib/active_record/attribute_methods/read.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 4b72fe7d7e..17f287c1b7 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -75,9 +75,8 @@ module ActiveRecord private - def attribute(attribute_name) - _read_attribute(attribute_name) - end + alias :attribute :_read_attribute + end end end -- cgit v1.2.3 From d4c5406279551919920ddf3fa586a8a30357df25 Mon Sep 17 00:00:00 2001 From: Kuldeep Aggarwal Date: Tue, 6 Jan 2015 21:46:35 +0530 Subject: fix `attribute` method scoping(i.e. private) ref #52f641264b1325a4c2bdce7971b14524bd4905f1 --- activerecord/lib/active_record/attribute_methods/read.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 17f287c1b7..24e30b6608 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -72,10 +72,8 @@ module ActiveRecord def _read_attribute(attr_name) # :nodoc: @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? } end - - private - alias :attribute :_read_attribute + private :attribute end end -- cgit v1.2.3 From 824862807be47bee5adbd15e365a2add57df5301 Mon Sep 17 00:00:00 2001 From: brainopia Date: Wed, 7 Jan 2015 03:29:26 +0300 Subject: Fix count on a separate connection (fixes #18359) Previosly count and other AR calculations would convert column_name_for_operation to sql on a default Arel::Table.engine (AR::Base) connection. That could lead to trouble if current model has a connection to a different adapter or Base connection is inaccessible. --- activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c3a8bf5c74..e52b666296 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -392,7 +392,7 @@ module ActiveRecord end def column_name_for_operation(operation, node) # :nodoc: - node.to_sql + visitor.accept(node, collector).value end protected -- cgit v1.2.3 From 17136c201320e9a041f2d98eacd299f2ce3eb154 Mon Sep 17 00:00:00 2001 From: Caleb Thompson Date: Thu, 8 Jan 2015 12:03:17 -0600 Subject: Document has_many :extend option https://github.com/rails/rails/commit/5937bd02dee112646469848d7fe8a8bfcef5b4c1#commitcomment-9205786 --- activerecord/lib/active_record/associations.rb | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 14af55f327..35bc09bb10 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1245,6 +1245,10 @@ module ActiveRecord # that is the inverse of this has_many association. Does not work in combination # with :through or :as options. # See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail. + # [:extend] + # Specifies a module or array of modules that will be extended into the association object returned. + # Useful for defining methods on associations, especially when they should be shared between multiple + # association objects. # # Option examples: # has_many :comments, -> { order "posted_on" } -- cgit v1.2.3 From 205a561e50c89f7b81336a295f97209d741cfa67 Mon Sep 17 00:00:00 2001 From: Yoshioka Tsuneo Date: Fri, 9 Jan 2015 14:33:17 +0900 Subject: ActiveRecord: release connection on reconnect failure. When trying to checkout connection from connection pool, checkout()(and checkout_and_verify) verify whether the connection is active or not. And, if the connection is not active, connection adapters try to reconnect to server. And, if database is down at this moment, reconnect fails and exception is raised. (Ex: Mysql2::Error: Can't connect to local MySQL server through socket xxx) But, ConnectionPool does not catch the exception, but leaks current disconnected connection to @connection. So, if database's temporary down happens several times and exceeds the number of connection pool(5 by default), activerecord will be no more available, even if database server is already recovered. This patch fix it by catching exception and releasing connection. --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 3 +++ 1 file changed, 3 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 1371317e3c..6b5081b7a9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -453,6 +453,9 @@ module ActiveRecord c.verify! end c + rescue + disconnect! + raise end end -- cgit v1.2.3 From 13772bfa49325a8dc26410d2dcb555665a320f92 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 9 Jan 2015 09:49:21 -0700 Subject: Properly persist `lock_version` as 0 if the DB has no default The reason this bug occured is that we never actually check to see if this column has changed from it's default, since it was never assigned and is not mutable. It appears I was wrong in b301c40224c6d15b539dbcc7485adb44d810f88c, with my statement of "there is no longer a case where a given value would differ from the default, but would not already be marked as changed." However, I chose not to revert the deletion of `initialize_internals_callback` from that commit, as I think a solution closer to where the problem lies is less likely to get erroneously removed. I'm not super happy with this solution, but it mirrors what is being done in `_update_record`, and a fix for one should work for the other. I toyed with the idea of changing the definition of `changed?` on the type to `changed_in_place?`. If we type cast the raw value, it'll break a test about updating not modifying the lock column if nothing else was changed. We could have the definition check if `raw_old_value` is `nil`, but this feels fragile and less intention revealing. It would, however, have the benefit of cleaning up old data that incorrectly persisted as `nil`. Fixes #18422 --- activerecord/lib/active_record/locking/optimistic.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 9f053453bd..aeb1a4ddc6 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -66,6 +66,15 @@ module ActiveRecord send(lock_col + '=', previous_lock_value + 1) end + def _create_record(attribute_names = self.attribute_names, *) # :nodoc: + if locking_enabled? + # We always want to persist the locking version, even if we don't detect + # a change from the default, since the database might have no default + attribute_names |= [self.class.locking_column] + end + super + end + def _update_record(attribute_names = self.attribute_names) #:nodoc: return super unless locking_enabled? return 0 if attribute_names.empty? @@ -185,11 +194,6 @@ module ActiveRecord super.to_i end - def changed?(old_value, *) - # Ensure we save if the default was `nil` - super || old_value == 0 - end - def init_with(coder) __setobj__(coder['subtype']) end -- cgit v1.2.3 From ec475547a94315ac207ac4e9bd8f2ac04576230c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 9 Jan 2015 10:29:13 -0700 Subject: Properly copy nested bind values from subqueried relations This is cropping up all over the place. After a brief dive, I'm really not sure why we have `arel.bind_values` at all. A cursory grep didn't reveal where they're actually being assigned (it's definitely in AR, not in Arel). I'd like to dig further into it, as I'm fairly certain we don't actually need it, we just need a way for the predicate builder to communicate merged binds upstream. Fixes #18414 --- activerecord/lib/active_record/relation/query_methods.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index f054e17017..d6e6cb4d05 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1154,13 +1154,11 @@ module ActiveRecord 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 + self.bind_values += value.arel.bind_values + value.bind_values else add_relations_to_bind_values(value) end -- cgit v1.2.3 From c7f56d99e51a62326c3639ca5b95e5416cb1f1d5 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Fri, 9 Jan 2015 15:35:27 -0500 Subject: Use keyword args on committed! and rolledback! As discussed before, those methods should receive a keyword args instead of just parameters --- .../lib/active_record/connection_adapters/abstract/transaction.rb | 6 +++--- activerecord/lib/active_record/transactions.rb | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index f6ef3b0675..335eb876cf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -69,11 +69,11 @@ module ActiveRecord def rollback_records ite = records.uniq while record = ite.shift - record.rolledback! full_rollback? + record.rolledback!(force_restore_state: full_rollback?) end ensure ite.each do |i| - i.rolledback!(full_rollback?, false) + i.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: false) end end @@ -88,7 +88,7 @@ module ActiveRecord end ensure ite.each do |i| - i.committed!(false) + i.committed!(should_run_callbacks: false) end end diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 9cef50029b..0fd2862b2c 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -300,7 +300,7 @@ module ActiveRecord # # Ensure that it is not called if the object was never persisted (failed create), # but call it after the commit of a destroyed object. - def committed!(should_run_callbacks = true) #:nodoc: + def committed!(should_run_callbacks: true) #:nodoc: _run_commit_callbacks if should_run_callbacks && destroyed? || persisted? ensure force_clear_transaction_record_state @@ -308,7 +308,7 @@ module ActiveRecord # Call the +after_rollback+ callbacks. The +force_restore_state+ argument indicates if the record # state should be rolled back to the beginning or just to the last savepoint. - def rolledback!(force_restore_state = false, should_run_callbacks = true) #:nodoc: + def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc: _run_rollback_callbacks if should_run_callbacks ensure restore_transaction_record_state(force_restore_state) -- cgit v1.2.3 From 86a853e4ec5c8adfa38a4faa5fa5bd68d992c00d Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Fri, 9 Jan 2015 15:54:20 -0500 Subject: Copy records to parent transaction should happen on TransactionManager It is up to the TransactionManager keep the state of current transaction, so after it commits it needs to copy any remaning record to the next current transaction --- .../lib/active_record/connection_adapters/abstract/transaction.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 335eb876cf..7535e9147a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -117,8 +117,6 @@ module ActiveRecord def commit connection.release_savepoint(savepoint_name) super - parent = connection.transaction_manager.current_transaction - records.each { |r| parent.add_record(r) } end def full_rollback?; false; end @@ -166,7 +164,9 @@ module ActiveRecord end def commit_transaction - @stack.pop.commit + transaction = @stack.pop + transaction.commit + transaction.records.each { |r| current_transaction.add_record(r) } end def rollback_transaction -- cgit v1.2.3 From 47316feee0f061f80e26c51fb0d41f537407ab9c Mon Sep 17 00:00:00 2001 From: robertomiranda Date: Fri, 9 Jan 2015 18:10:38 -0500 Subject: Switch Secure Token generation to Base58 Update Secure Token Doc [ci skip] remove require securerandom, core_ext/securerandom already do that ref 7e006057 --- activerecord/lib/active_record/secure_token.rb | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb index 23d4292cbb..b1a13fe673 100644 --- a/activerecord/lib/active_record/secure_token.rb +++ b/activerecord/lib/active_record/secure_token.rb @@ -13,35 +13,25 @@ module ActiveRecord # # user = User.new # user.save - # user.token # => "44539a6a59835a4ee9d7b112" - # user.auth_token # => "e2426a93718d1817a43abbaa" + # user.token # => "4kUgL2pdQMSCQtjE" + # user.auth_token # => "77TMHrHJFvFDwodq8w7Ev2m7" # user.regenerate_token # => true # user.regenerate_auth_token # => true # - # SecureRandom is used to generate the 24-character unique token, so collisions are highly unlikely. - # We'll check to see if the generated token has been used already using #exists?, and retry up to 10 - # times to find another unused token. After that a RuntimeError is raised if the problem persists. + # SecureRandom::base58 is used to generate the 24-character unique token, so collisions are highly unlikely. # # Note that it's still possible to generate a race condition in the database in the same way that # validates_presence_of can. You're encouraged to add a unique index in the database to deal with # this even more unlikely scenario. def has_secure_token(attribute = :token) # Load securerandom only when has_secure_key is used. - require 'securerandom' - define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token(attribute) } - before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token(attribute)) } + require 'active_support/core_ext/securerandom' + define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token } + before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) } end - def generate_unique_secure_token(attribute) - 10.times do |i| - SecureRandom.hex(12).tap do |token| - if exists?(attribute => token) - raise "Couldn't generate a unique token in 10 attempts!" if i == 9 - else - return token - end - end - end + def generate_unique_secure_token + SecureRandom.base58(24) end end end -- cgit v1.2.3 From f4fbc0301021f13ae05c8e941c8efc4ae351fdf9 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Fri, 9 Jan 2015 21:55:52 -0200 Subject: Remove support for the protected attributes gem Related to #10690. --- activerecord/lib/active_record/core.rb | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 5a5139256d..a7aff9f724 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -274,16 +274,14 @@ module ActiveRecord # ==== Example: # # Instantiates a single new object # User.new(first_name: 'Jamie') - def initialize(attributes = nil, options = {}) + def initialize(attributes = nil) @attributes = self.class._default_attributes.dup self.class.define_attribute_methods init_internals initialize_internals_callback - # +options+ argument is only needed to make protected_attributes gem easier to hook. - # Remove it when we drop support to this gem. - init_attributes(attributes, options) if attributes + assign_attributes(attributes) if attributes yield self if block_given? _run_initialize_callbacks @@ -557,12 +555,6 @@ module ActiveRecord def initialize_internals_callback end - # This method is needed to make protected_attributes gem easier to hook. - # Remove it when we drop support to this gem. - def init_attributes(attributes, options) - assign_attributes(attributes) - end - def thaw if frozen? @attributes = @attributes.dup -- cgit v1.2.3 From a4139a167421e2c8ca3b84a333d337006282e928 Mon Sep 17 00:00:00 2001 From: Sebastian Staudt Date: Sat, 10 Jan 2015 13:15:03 +0100 Subject: Fix typo in PostresSQLAdapter's documentation --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5b070cae4f..f4f9747359 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -64,7 +64,7 @@ module ActiveRecord # SET client_min_messages TO call on the connection. # * :variables - An optional hash of additional parameters that # will be used in SET SESSION key = val calls on the connection. - # * :insert_returning - An optional boolean to control the use or RETURNING for INSERT statements + # * :insert_returning - An optional boolean to control the use of RETURNING for INSERT statements # defaults to true. # # Any further options are used as connection parameters to libpq. See -- cgit v1.2.3 From 4d5e6607899832bde1365bdd4f7617a24bca4561 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 10 Jan 2015 11:57:14 -0700 Subject: Don't attempt to save dirty attributes which are not persistable This sets a precident for how we handle `attribute` calls, which aren't backed by a database column. We should not take this as a conscious decision on how to handle them, and this can change when we make `attribute` public if we have better ideas in the future. As the composed attributes API gets fleshed out, I expect the `persistable_attributes` method to change to `@attributes.select(&:persistable).keys`, or some more performant variant there-of. This can probably go away completely once we fully move dirty checking into the attribute objects once it gets moved up to Active Model. Fixes #18407 --- activerecord/lib/active_record/attribute_methods/dirty.rb | 2 +- activerecord/lib/active_record/attributes.rb | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index d5702accaf..ce7f575150 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -134,7 +134,7 @@ module ActiveRecord # Serialized attributes should always be written in case they've been # changed in place. def keys_for_partial_write - changed + changed & persistable_attribute_names end def _field_changed?(attr, old_value) diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index aafb990bc1..b263a89d79 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -9,6 +9,8 @@ module ActiveRecord class_attribute :user_provided_defaults, instance_accessor: false # :internal: self.user_provided_columns = {} self.user_provided_defaults = {} + + delegate :persistable_attribute_names, to: :class end module ClassMethods # :nodoc: @@ -96,6 +98,10 @@ module ActiveRecord @columns_hash ||= Hash[columns.map { |c| [c.name, c] }] end + def persistable_attribute_names # :nodoc: + @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys + end + def reset_column_information # :nodoc: super clear_caches_calculated_from_columns @@ -130,6 +136,7 @@ module ActiveRecord @columns_hash = nil @content_columns = nil @default_attributes = nil + @persistable_attribute_names = nil end def raw_default_values -- cgit v1.2.3 From 7a09fc55c0d89f6a672c79fdd9787c51a85a06fc Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 10 Jan 2015 12:24:10 -0700 Subject: Stop special casing null binary data in logging There's very little value in logging "" instead of just "nil". I'd like to remove the column from the equation entirely, and this case is preventing us from doing so. --- activerecord/lib/active_record/log_subscriber.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index eb64d197f0..a5c7279db9 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -22,10 +22,10 @@ module ActiveRecord def render_bind(column, value) if column - if column.binary? + if column.binary? && value # This specifically deals with the PG adapter that casts bytea columns into a Hash. value = value[:value] if value.is_a?(Hash) - value = value ? "<#{value.bytesize} bytes of binary data>" : "" + value = "<#{value.bytesize} bytes of binary data>" end [column.name, value] -- cgit v1.2.3 From 134abedf198423e6b13d5582719bd9daabbbfa5a Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 10 Jan 2015 12:41:05 -0700 Subject: Stop passing a column to `quote` in `insert_fixture` I'm planning on deprecating the column argument to mirror the deprecation in [arel]. [arel]: https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 --- .../connection_adapters/abstract/database_statements.rb | 8 ++++---- .../lib/active_record/connection_adapters/abstract/quoting.rb | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 59cdd8e98c..6e631ed9f7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -285,11 +285,11 @@ module ActiveRecord def insert_fixture(fixture, table_name) columns = schema_cache.columns_hash(table_name) - key_list = [] - value_list = fixture.map do |name, value| - key_list << quote_column_name(name) - quote(value, columns[name]) + binds = fixture.map do |name, value| + [columns[name], value] end + key_list = fixture.keys.map { |name| quote_column_name(name) } + value_list = prepare_binds_for_database(binds).map { |_, value| quote(value) } execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 143d7d9574..fb68ad17b2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -92,6 +92,16 @@ module ActiveRecord private + def prepare_binds_for_database(binds) + binds.map do |column, value| + if column + column_name = column.name + value = column.cast_type.type_cast_for_database(value) + end + [column_name, value] + end + end + def types_which_need_no_typecasting [nil, Numeric, String] end -- cgit v1.2.3 From 0f931f2ff037a4e623473decad1482721af0cbc5 Mon Sep 17 00:00:00 2001 From: Roberto Miranda Date: Sat, 10 Jan 2015 16:56:21 -0500 Subject: Fix Typo SecureToken [ci skip] --- activerecord/lib/active_record/secure_token.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb index b1a13fe673..be3c3bc847 100644 --- a/activerecord/lib/active_record/secure_token.rb +++ b/activerecord/lib/active_record/secure_token.rb @@ -24,7 +24,7 @@ module ActiveRecord # validates_presence_of can. You're encouraged to add a unique index in the database to deal with # this even more unlikely scenario. def has_secure_token(attribute = :token) - # Load securerandom only when has_secure_key is used. + # Load securerandom only when has_secure_token is used. require 'active_support/core_ext/securerandom' define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token } before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) } -- cgit v1.2.3 From e6ac56a848eb253a2162d7b18ad6380af22971bd Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 10 Jan 2015 12:57:44 -0700 Subject: Stop passing a column to `quote` when prepared statements are turned off I'm planning on deprecating the column argument to mirror the deprecation in [arel]. [arel]: https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 --- .../lib/active_record/connection_adapters/abstract/quoting.rb | 6 +++--- .../lib/active_record/connection_adapters/abstract_adapter.rb | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index fb68ad17b2..1f265a55df 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -90,9 +90,7 @@ module ActiveRecord value.to_s(:db) end - private - - def prepare_binds_for_database(binds) + def prepare_binds_for_database(binds) # :nodoc: binds.map do |column, value| if column column_name = column.name @@ -102,6 +100,8 @@ module ActiveRecord end end + private + def types_which_need_no_typecasting [nil, Numeric, String] end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index e52b666296..c941c9f1eb 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -113,7 +113,8 @@ module ActiveRecord class BindCollector < Arel::Collectors::Bind def compile(bvs, conn) - super(bvs.map { |bv| conn.quote(*bv.reverse) }) + casted_binds = conn.prepare_binds_for_database(bvs) + super(casted_binds.map { |_, value| conn.quote(value) }) end end -- cgit v1.2.3 From a00854e51549460c5a865c7ba9ee02a442b917c5 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 10 Jan 2015 13:07:40 -0700 Subject: Stop passing a column to `quote` in `Relation#to_sql` I'm planning on deprecating the column argument to mirror the deprecation in [arel]. [arel]: https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 --- activerecord/lib/active_record/relation.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index ab3debc03b..dd78814c6a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -558,8 +558,9 @@ module ActiveRecord end arel = relation.arel - binds = (arel.bind_values + relation.bind_values).dup - binds.map! { |bv| connection.quote(*bv.reverse) } + binds = arel.bind_values + relation.bind_values + binds = connection.prepare_binds_for_database(binds) + binds.map! { |_, value| connection.quote(value) } collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new) collect.substitute_binds(binds).join end -- cgit v1.2.3 From 0b4fd69b8b2f0f169899978fd260459c16a82030 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 10 Jan 2015 13:08:25 -0700 Subject: Stop passing a column to `quote` when executing from a statement cache I'm planning on deprecating the column argument to mirror the deprecation in [arel]. [arel]: https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 --- activerecord/lib/active_record/statement_cache.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index 192a19f05d..3047a81ec4 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -47,8 +47,8 @@ module ActiveRecord def sql_for(binds, connection) val = @values.dup - binds = binds.dup - @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) } + binds = connection.prepare_binds_for_database(binds) + @indexes.each { |i| val[i] = connection.quote(binds.shift.last) } val.join end end -- cgit v1.2.3 From 268ee64e6362467fc6d2e54df703c620af78f5fb Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 10 Jan 2015 13:08:59 -0700 Subject: Stop passing a column to `quote` when finding by AR models I'm planning on deprecating the column argument to mirror the deprecation in [arel]. [arel]: https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 --- activerecord/lib/active_record/sanitization.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 768a72a947..313e767dcb 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -3,14 +3,11 @@ module ActiveRecord extend ActiveSupport::Concern module ClassMethods - def quote_value(value, column) #:nodoc: - connection.quote(value, column) - end - # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to connection.quote. def sanitize(object) #:nodoc: connection.quote(object) end + alias_method :quote_value, :sanitize protected @@ -156,7 +153,7 @@ module ActiveRecord # TODO: Deprecate this def quoted_id - self.class.quote_value(id, column_for_attribute(self.class.primary_key)) + self.class.quote_value(@attributes[self.class.primary_key].value_for_database) end end end -- cgit v1.2.3 From 7bb620869725ad6de603f6a5393ee17df13aa96c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 10 Jan 2015 15:45:10 -0700 Subject: Deprecate passing a column to `quote` It's only used to grab the type for type casting purposes, and we would like to remove the type from the columns entirely. --- .../lib/active_record/connection_adapters/abstract/quoting.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 1f265a55df..7c1a779577 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -10,6 +10,12 @@ module ActiveRecord return value.quoted_id if value.respond_to?(:quoted_id) if column + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Passing a column to `quote` has been deprecated. It is only used + for type casting, which should be handled elsewhere. See + https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 + for more information. + MSG value = column.cast_type.type_cast_for_database(value) end -- cgit v1.2.3 From f5c2bf10979a3e0d5f33665660c06423b967e4ec Mon Sep 17 00:00:00 2001 From: Ethan Date: Mon, 12 Jan 2015 15:11:23 -0800 Subject: pretty_print will use #inspect if a subclass redefines it --- activerecord/lib/active_record/core.rb | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index a7aff9f724..a5eb18246f 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -456,22 +456,26 @@ module ActiveRecord # Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record` # when pp is required. def pretty_print(pp) - pp.object_address_group(self) do - if defined?(@attributes) && @attributes - column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } - pp.seplist(column_names, proc { pp.text ',' }) do |column_name| - column_value = read_attribute(column_name) - pp.breakable ' ' - pp.group(1) do - pp.text column_name - pp.text ':' - pp.breakable - pp.pp column_value + if self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner + pp.text inspect + else + pp.object_address_group(self) do + if defined?(@attributes) && @attributes + column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? } + pp.seplist(column_names, proc { pp.text ',' }) do |column_name| + column_value = read_attribute(column_name) + pp.breakable ' ' + pp.group(1) do + pp.text column_name + pp.text ':' + pp.breakable + pp.pp column_value + end end + else + pp.breakable ' ' + pp.text 'not initialized' end - else - pp.breakable ' ' - pp.text 'not initialized' end end end -- cgit v1.2.3 From 33fdb7f2929f57f7453299f231b2762954212611 Mon Sep 17 00:00:00 2001 From: Yoshioka Tsuneo Date: Tue, 13 Jan 2015 13:49:40 +0900 Subject: ActiveRecord: On reconnection failure, release only failed connetion. On reconnection failure, all the connection was released. But, it is better to release only failed connection. This patch changes not to release all the connection but release only failed connection. --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 6b5081b7a9..d99dc9a5db 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -454,7 +454,8 @@ module ActiveRecord end c rescue - disconnect! + remove c + c.disconnect! raise end end -- cgit v1.2.3 From 4ae59ebee8d404def8f9f7c716d1869fb7bb6f01 Mon Sep 17 00:00:00 2001 From: Vipul A M Date: Wed, 14 Jan 2015 18:31:30 +0530 Subject: Fixes #18492 - Add check for not deleting previously created fixtures, to overcome sti fixtures from multiple files - Added fixtures and fixtures test to verify the same - Fixed wrong fixtures duplicating data insertion in same table --- activerecord/lib/active_record/fixtures.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 10e9be20b5..fc9c0aa900 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -521,12 +521,16 @@ module ActiveRecord update_all_loaded_fixtures fixtures_map connection.transaction(:requires_new => true) do + deleted_tables = [] fixture_sets.each do |fs| conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection table_rows = fs.table_rows table_rows.each_key do |table| - conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' + unless deleted_tables.include? table + conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete' + end + deleted_tables << table end table_rows.each do |fixture_set_name, rows| -- cgit v1.2.3 From aa31d21f5f4fc4d679e74a60f9df9706da7de373 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 14 Jan 2015 13:55:29 -0700 Subject: Don't default to YAML dumping when quoting values This behavior exists only to support fixtures, so we should handle it there. Leaving it in `#quote` can cause very subtle bugs to slip through, by things appearing to work when they should be blowing up loudly, such as #18385. --- .../connection_adapters/abstract/database_statements.rb | 8 +++++++- .../lib/active_record/connection_adapters/abstract/quoting.rb | 3 +-- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 6e631ed9f7..787d07c4c2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -289,7 +289,13 @@ module ActiveRecord [columns[name], value] end key_list = fixture.keys.map { |name| quote_column_name(name) } - value_list = prepare_binds_for_database(binds).map { |_, value| quote(value) } + value_list = prepare_binds_for_database(binds).map do |_, value| + begin + quote(value) + rescue TypeError + quote(YAML.dump(value)) + end + end execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", 'Fixture Insert' end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 7c1a779577..c18caa2a2f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -125,8 +125,7 @@ module ActiveRecord when Date, Time then "'#{quoted_date(value)}'" when Symbol then "'#{quote_string(value.to_s)}'" when Class then "'#{value}'" - else - "'#{quote_string(YAML.dump(value))}'" + else raise TypeError, "can't quote #{value.class.name}" end end -- cgit v1.2.3 From 9a21774d73ebb1eece5ef29d71de17147be99992 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 14 Jan 2015 15:08:03 -0700 Subject: Go through normal where logic in destroy Building the Arel AST, and manipulating the relation manually like this is prone to errors and breakage as implementation details change from underneath it. --- activerecord/lib/active_record/persistence.rb | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index cf6673db2e..6306a25745 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -495,15 +495,7 @@ module ActiveRecord end def relation_for_destroy - pk = self.class.primary_key - column = self.class.columns_hash[pk] - substitute = self.class.connection.substitute_at(column) - - relation = self.class.unscoped.where( - self.class.arel_table[pk].eq(substitute)) - - relation.bind_values = [[column, id]] - relation + self.class.unscoped.where(self.class.primary_key => id) end def create_or_update(*args) -- cgit v1.2.3 From df73d696f5a965b67c9f177bf4dd8d85921d0214 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 14 Jan 2015 15:25:15 -0700 Subject: Go through normal where logic in destroy with locking Building the Arel AST, and manipulating the relation manually like this is prone to errors and breakage as implementation details change from underneath it. --- activerecord/lib/active_record/locking/optimistic.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index aeb1a4ddc6..6f2b65c137 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -125,12 +125,8 @@ module ActiveRecord relation = super if locking_enabled? - column_name = self.class.locking_column - column = self.class.columns_hash[column_name] - substitute = self.class.connection.substitute_at(column) - - relation = relation.where(self.class.arel_table[column_name].eq(substitute)) - relation.bind_values << [column, self[column_name].to_i] + locking_column = self.class.locking_column + relation = relation.where(locking_column => _read_attribute(locking_column)) end relation -- cgit v1.2.3 From d8e710410ea300ec4626250c0b35946cb52bc38c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 14 Jan 2015 17:08:25 -0700 Subject: Only use the `_before_type_cast` in the form when from user input While we don't want to change the form input when validations fail, blindly using `_before_type_cast` will cause the input to display the wrong data for any type which does additional work on database values. --- activerecord/lib/active_record/attribute.rb | 8 ++++++++ .../lib/active_record/attribute_methods/before_type_cast.rb | 5 +++++ 2 files changed, 13 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 88536eaac0..51e4fae62e 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -74,6 +74,10 @@ module ActiveRecord true end + def came_from_user? + false + end + def ==(other) self.class == other.class && name == other.name && @@ -99,6 +103,10 @@ module ActiveRecord def type_cast(value) type.type_cast_from_user(value) end + + def came_from_user? + true + end end class WithCastValue < Attribute # :nodoc: diff --git a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb index fd61febd57..56c1898551 100644 --- a/activerecord/lib/active_record/attribute_methods/before_type_cast.rb +++ b/activerecord/lib/active_record/attribute_methods/before_type_cast.rb @@ -28,6 +28,7 @@ module ActiveRecord included do attribute_method_suffix "_before_type_cast" + attribute_method_suffix "_came_from_user?" end # Returns the value of the attribute identified by +attr_name+ before @@ -66,6 +67,10 @@ module ActiveRecord def attribute_before_type_cast(attribute_name) read_attribute_before_type_cast(attribute_name) end + + def attribute_came_from_user?(attribute_name) + @attributes[attribute_name].came_from_user? + end end end end -- cgit v1.2.3 From 5cd3bbbb832b58d2a0092f527d83312df4271de7 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 14 Jun 2014 08:06:51 -0600 Subject: Time columns should support time zone aware attributes The types that are affected by `time_zone_aware_attributes` (which is on by default) have been made configurable, in case this is a breaking change for existing applications. --- .../attribute_methods/time_zone_conversion.rb | 33 +++++++++++++++++++--- .../connection_adapters/postgresql/oid/array.rb | 2 +- activerecord/lib/active_record/type/time.rb | 13 +++++++++ activerecord/lib/active_record/type/time_value.rb | 4 +++ 4 files changed, 47 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record') 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 777f7ab4d7..e10024b2ec 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -13,7 +13,7 @@ module ActiveRecord value.map { |v| type_cast_from_user(v) } elsif value.respond_to?(:in_time_zone) begin - value.in_time_zone || super + user_input_in_time_zone(value) || super rescue ArgumentError nil end @@ -39,6 +39,9 @@ module ActiveRecord class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false self.skip_time_zone_conversion_for_attributes = [] + + class_attribute :time_zone_aware_types, instance_writer: false + self.time_zone_aware_types = [:datetime, :not_explicitly_configured] end module ClassMethods @@ -59,9 +62,31 @@ module ActiveRecord end def create_time_zone_conversion_attribute?(name, cast_type) - time_zone_aware_attributes && - !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) && - (:datetime == cast_type.type) + enabled_for_column = time_zone_aware_attributes && + !self.skip_time_zone_conversion_for_attributes.include?(name.to_sym) + result = enabled_for_column && + time_zone_aware_types.include?(cast_type.type) + + if enabled_for_column && + !result && + cast_type.type == :time && + time_zone_aware_types.include?(:not_explicitly_configured) + ActiveSupport::Deprecation.warn(<<-MESSAGE) + Time columns will become time zone aware in Rails 5.1. This + sill cause `String`s to be parsed as if they were in `Time.zone`, + and `Time`s to be converted to `Time.zone`. + + To keep the old behavior, you must add the following to your initializer: + + config.active_record.time_zone_aware_types = [:datetime] + + To silence this deprecation warning, add the following: + + config.active_record.time_zone_aware_types << :time + MESSAGE + end + + result end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb index c203e6c604..e45a2f59d9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/array.rb @@ -18,7 +18,7 @@ module ActiveRecord end attr_reader :subtype, :delimiter - delegate :type, to: :subtype + delegate :type, :user_input_in_time_zone, to: :subtype def initialize(subtype, delimiter = ',') @subtype = subtype diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb index 41f7d97f0c..cab1c7bf1e 100644 --- a/activerecord/lib/active_record/type/time.rb +++ b/activerecord/lib/active_record/type/time.rb @@ -7,6 +7,19 @@ module ActiveRecord :time end + def user_input_in_time_zone(value) + return unless value.present? + + case value + when ::String + value = "2000-01-01 #{value}" + when ::Time + value = value.change(year: 2000, day: 1, month: 1) + end + + super(value) + end + private def cast_value(value) diff --git a/activerecord/lib/active_record/type/time_value.rb b/activerecord/lib/active_record/type/time_value.rb index d611d72dd4..8d9ac25643 100644 --- a/activerecord/lib/active_record/type/time_value.rb +++ b/activerecord/lib/active_record/type/time_value.rb @@ -9,6 +9,10 @@ module ActiveRecord "'#{value.to_s(:db)}'" end + def user_input_in_time_zone(value) + value.in_time_zone + end + private def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil) -- cgit v1.2.3 From 4bed3bc0871ae3f28270f171915544b768018776 Mon Sep 17 00:00:00 2001 From: "Jeroen K." Date: Thu, 15 Jan 2015 16:06:01 +0100 Subject: Fix Typo SecureToken for schema sample [ci skip] --- activerecord/lib/active_record/secure_token.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb index be3c3bc847..07031b6371 100644 --- a/activerecord/lib/active_record/secure_token.rb +++ b/activerecord/lib/active_record/secure_token.rb @@ -5,7 +5,7 @@ module ActiveRecord module ClassMethods # Example using has_secure_token # - # # Schema: User(toke:string, auth_token:string) + # # Schema: User(token:string, auth_token:string) # class User < ActiveRecord::Base # has_secure_token # has_secure_token :auth_token -- cgit v1.2.3 From 3818dd4efb0cdee7a0d9c6dc0a0dbaa2c02c7225 Mon Sep 17 00:00:00 2001 From: Aditya Kapoor Date: Thu, 15 Jan 2015 22:37:24 +0530 Subject: [ci skip] fix typo sill -> still --- .../lib/active_record/attribute_methods/time_zone_conversion.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') 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 e10024b2ec..98671178cb 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -73,7 +73,7 @@ module ActiveRecord time_zone_aware_types.include?(:not_explicitly_configured) ActiveSupport::Deprecation.warn(<<-MESSAGE) Time columns will become time zone aware in Rails 5.1. This - sill cause `String`s to be parsed as if they were in `Time.zone`, + still cause `String`s to be parsed as if they were in `Time.zone`, and `Time`s to be converted to `Time.zone`. To keep the old behavior, you must add the following to your initializer: -- cgit v1.2.3 From 4111de4e152d678de30afb7f87dcec9d65ab0756 Mon Sep 17 00:00:00 2001 From: Henrik Nyh Date: Thu, 15 Jan 2015 20:02:37 +0100 Subject: Remove incorrect comment in ActiveRecord::Type::Value Says it's only used for the schema, but they are in fact used for other things. Integer verifies against the limit during casting, and Decimal uses precision during casting. It may be true that scale is only used for the schema. --- activerecord/lib/active_record/type/value.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/type/value.rb b/activerecord/lib/active_record/type/value.rb index 60ae47db3d..859b51ca90 100644 --- a/activerecord/lib/active_record/type/value.rb +++ b/activerecord/lib/active_record/type/value.rb @@ -3,8 +3,7 @@ module ActiveRecord class Value # :nodoc: attr_reader :precision, :scale, :limit - # Valid options are +precision+, +scale+, and +limit+. They are only - # used when dumping schema. + # Valid options are +precision+, +scale+, and +limit+. def initialize(options = {}) options.assert_valid_keys(:precision, :scale, :limit) @precision = options[:precision] -- cgit v1.2.3 From 1be562db9501007bd82d9385ffc8ca59e23daba9 Mon Sep 17 00:00:00 2001 From: Henrik Nyh Date: Thu, 15 Jan 2015 19:58:01 +0100 Subject: DRY default limit in ActiveRecord::Type::Integer --- activerecord/lib/active_record/type/integer.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb index fc260a081a..394fe008f9 100644 --- a/activerecord/lib/active_record/type/integer.rb +++ b/activerecord/lib/active_record/type/integer.rb @@ -3,6 +3,10 @@ module ActiveRecord class Integer < Value # :nodoc: include Numeric + # Column storage size in bytes. + # 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc. + DEFAULT_LIMIT = 4 + def initialize(*) super @range = min_value...max_value @@ -38,12 +42,12 @@ module ActiveRecord def ensure_in_range(value) unless range.cover?(value) - raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || 4}" + raise RangeError, "#{value} is out of range for #{self.class} with limit #{limit || DEFAULT_LIMIT}" end end def max_value - limit = self.limit || 4 + limit = self.limit || DEFAULT_LIMIT 1 << (limit * 8 - 1) # 8 bits per byte with one bit for sign end -- cgit v1.2.3 From eb72e349b205c47a64faa5d6fe9f831aa7fdddf3 Mon Sep 17 00:00:00 2001 From: brainopia Date: Mon, 12 Jan 2015 02:38:22 +0300 Subject: after_commit runs after transactions with non-joinable parents after_commit callbacks run after committing a transaction whose parent is not `joinable?`: un-nested transactions, transactions within test cases, and transactions in `console --sandbox`. --- .../connection_adapters/abstract/transaction.rb | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 7535e9147a..84e5386c20 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -142,7 +142,6 @@ module ActiveRecord def commit connection.commit_db_transaction super - commit_records end end @@ -159,14 +158,22 @@ module ActiveRecord else SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options) end + @stack.push(transaction) transaction end def commit_transaction - transaction = @stack.pop - transaction.commit - transaction.records.each { |r| current_transaction.add_record(r) } + inner_transaction = @stack.pop + inner_transaction.commit + + if current_transaction.joinable? + inner_transaction.records.each do |r| + current_transaction.add_record(r) + end + else + inner_transaction.commit_records + end end def rollback_transaction -- cgit v1.2.3 From 0fcd4cf5c26c470623eef9af72a134ef6ba1a701 Mon Sep 17 00:00:00 2001 From: Prathamesh Sonpatki Date: Sun, 11 Jan 2015 13:53:28 +0530 Subject: Run SQL only if attribute changed for update_attribute method - This is based on https://github.com/rails/rails/issues/18400 but tackling same issue with update_attribute method instead of update method. --- activerecord/lib/active_record/persistence.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 6306a25745..714d36e7c0 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -246,7 +246,7 @@ module ActiveRecord name = name.to_s verify_readonly_attribute(name) send("#{name}=", value) - save(validate: false) + save(validate: false) if changed? end # Updates the attributes of the model from the passed-in hash and saves the -- cgit v1.2.3 From ea721d7027c16f64c47395546c67858060a273e3 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 18 Jan 2015 13:43:31 -0700 Subject: Don't calculate in-place changes on attribute assignment When an attribute is assigned, we determine if it was already marked as changed so we can determine if we need to clear the changes, or mark it as changed. Since this only affects the `attributes_changed_by_setter` hash, in-place changes are irrelevant to this process. Since calculating in-place changes can be expensive, we can just skip it here. I also added a test for the only edge case I could think of that would be affected by this change. --- activerecord/lib/active_record/attribute_methods/dirty.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index ce7f575150..bce9c5e1e3 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -108,7 +108,7 @@ module ActiveRecord end def save_changed_attribute(attr, old_value) - if attribute_changed?(attr) + if attribute_changed_by_setter?(attr) clear_attribute_changes(attr) unless _field_changed?(attr, old_value) else set_attribute_was(attr, old_value) if _field_changed?(attr, old_value) -- cgit v1.2.3 From 48e99a45310f5fec515305e90ff8ee2a6a61bada Mon Sep 17 00:00:00 2001 From: Stefan Kanev Date: Fri, 1 Aug 2014 16:13:38 +0200 Subject: Add an `:if_exists` option to `drop_table` If set to `if_exists: true`, it generates a statement like: DROP TABLE IF EXISTS posts This syntax is supported in the popular SQL servers, that is (at least) SQLite, PostgreSQL, MySQL, Oracle and MS SQL Sever. Closes #16366. --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 +- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 2 +- .../active_record/connection_adapters/postgresql/schema_statements.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') 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 0f44c332ae..f905669a24 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -380,7 +380,7 @@ module ActiveRecord # it can be helpful to provide these in a migration's +change+ method so it can be reverted. # In that case, +options+ and the block will be used by create_table. def drop_table(table_name, options = {}) - execute "DROP TABLE #{quote_table_name(table_name)}" + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}" end # Adds a new column to the named table. 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 e9a3c26c32..b61b717a61 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -502,7 +502,7 @@ module ActiveRecord end def drop_table(table_name, options = {}) - execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" + execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end def rename_index(table_name, old_name, new_name) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index a90adcf4aa..afd7a17c03 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -112,7 +112,7 @@ module ActiveRecord end def drop_table(table_name, options = {}) - execute "DROP TABLE #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" + execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}" end # Returns true if schema exists. -- cgit v1.2.3 From 76d7d957900d8fe297c027604ae5d8364b3389d6 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 19 Jan 2015 13:21:18 -0700 Subject: Don't mutate bind values in `Relation` In order to better facilitate refactoring, most places that mutated `bind_values` have already been removed. One last spot snuck through. Since we're no longer mutating the array, it also does not need to be duped in `initialize_copy`. --- activerecord/lib/active_record/relation.rb | 1 - activerecord/lib/active_record/relation/query_methods.rb | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index dd78814c6a..9c4db8a05e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -33,7 +33,6 @@ module ActiveRecord # This method is a hot spot, so for now, use Hash[] to dup the hash. # https://bugs.ruby-lang.org/issues/7166 @values = Hash[@values] - @values[:bind] = @values[:bind].dup if @values.key? :bind reset end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d6e6cb4d05..1c36d51cd8 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -909,7 +909,7 @@ module ActiveRecord end end - bind_values.reject! { |col,_| col.name == target_value } + self.bind_values = bind_values.reject { |col,_| col.name == target_value } end def custom_join_ast(table, joins) -- cgit v1.2.3 From 40887135f6c4e7e9feee03b4dac791ec1d54b3c6 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 19 Jan 2015 14:14:24 -0700 Subject: Whether a column exists or not doesn't affect whether we can use binds Looking through the blame, this logic used to be when we actually created the bind tuple. My guess is that `nil` couldn't be handled there at that time. It can, now. --- activerecord/lib/active_record/relation/query_methods.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 1c36d51cd8..0e50534d47 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -959,12 +959,9 @@ module ActiveRecord def create_binds(opts) bindable, non_binds = opts.partition do |column, value| - case value - when String, Integer, ActiveRecord::StatementCache::Substitute - @klass.columns_hash.include? column.to_s - else - false - end + value.is_a?(String) || + value.is_a?(Integer) || + value.is_a?(ActiveRecord::StatementCache::Substitute) end association_binds, non_binds = non_binds.partition do |column, value| -- cgit v1.2.3 From 50a8cdf0e2cff604b0361a370afc8becf2579d94 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 19 Jan 2015 15:09:47 -0700 Subject: Move `create_binds` over to the `PredicateBuilder` I'm looking to introduce a `WhereClause` class to handle most of this logic, and this method will eventually move over to there. However, this intermediate refactoring should make that easier to do. --- .../active_record/relation/predicate_builder.rb | 26 +++++++++++++++-- .../lib/active_record/relation/query_methods.rb | 33 +--------------------- activerecord/lib/active_record/table_metadata.rb | 6 ++++ 3 files changed, 31 insertions(+), 34 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 567efce8ae..f1cb4e1bee 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -28,6 +28,25 @@ module ActiveRecord expand_from_hash(attributes) end + def create_binds(attributes) + result = attributes.dup + binds = [] + + attributes.each do |column_name, value| + case value + when String, Integer, ActiveRecord::StatementCache::Substitute + result[column_name] = Arel::Nodes::BindParam.new + binds.push([table.column(column_name), value]) + when Hash + attrs, bvs = associated_predicate_builder(column_name).create_binds(value) + result[column_name] = attrs + binds += bvs + end + end + + [result, binds] + end + def expand(column, value) # Find the foreign key when using queries such as: # Post.where(author: author) @@ -80,8 +99,7 @@ module ActiveRecord attributes.flat_map do |key, value| if value.is_a?(Hash) - builder = self.class.new(table.associated_table(key)) - builder.expand_from_hash(value) + associated_predicate_builder(key).expand_from_hash(value) else expand(key, value) end @@ -90,6 +108,10 @@ module ActiveRecord private + def associated_predicate_builder(association_name) + self.class.new(table.associated_table(association_name)) + end + def convert_dot_notation_to_hash(attributes) dot_notation = attributes.keys.select { |s| s.include?(".") } diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 0e50534d47..c3c1b6c961 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -945,7 +945,7 @@ module ActiveRecord when Hash opts = predicate_builder.resolve_column_aliases(opts) - tmp_opts, bind_values = create_binds(opts) + tmp_opts, bind_values = predicate_builder.create_binds(opts) self.bind_values += bind_values attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts) @@ -957,37 +957,6 @@ module ActiveRecord end end - def create_binds(opts) - bindable, non_binds = opts.partition do |column, value| - value.is_a?(String) || - value.is_a?(Integer) || - value.is_a?(ActiveRecord::StatementCache::Substitute) - end - - association_binds, non_binds = non_binds.partition do |column, value| - value.is_a?(Hash) && association_for_table(column) - end - - new_opts = {} - binds = [] - - bindable.each do |(column,value)| - binds.push [@klass.columns_hash[column.to_s], value] - new_opts[column] = connection.substitute_at(column) - end - - association_binds.each do |(column, value)| - association_relation = association_for_table(column).klass.send(:relation) - association_new_opts, association_bind = association_relation.send(:create_binds, value) - new_opts[column] = association_new_opts - binds += association_bind - end - - non_binds.each { |column,value| new_opts[column] = value } - - [new_opts, binds] - end - def association_for_table(table_name) table_name = table_name.to_s @klass._reflect_on_association(table_name) || diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 11e33e8dfe..31a40adb67 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -22,6 +22,12 @@ module ActiveRecord arel_table[column_name] end + def column(column_name) + if klass + klass.columns_hash[column_name.to_s] + end + end + def associated_with?(association_name) klass && klass._reflect_on_association(association_name) end -- cgit v1.2.3 From 04d1c3716b5bfa133a0b1ed937649a0829ac5f22 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 19 Jan 2015 15:17:43 -0700 Subject: Fix bind value copying from subqueried relations With the old implementation, the bind values were created, and then we search the attributes for `Relation` objects, and merge them. This completely ignores the order that the actual `where` clause will use. If all non-relation where parameters are before the relations, it will work. However, if we query on both a relation and a value, with the value coming second, it breaks. The order of the hash should not affect the final query (especially since hashes being ordered is an implementation detail) --- .../lib/active_record/relation/predicate_builder.rb | 2 ++ activerecord/lib/active_record/relation/query_methods.rb | 13 ------------- 2 files changed, 2 insertions(+), 13 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index f1cb4e1bee..2860a30f99 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -41,6 +41,8 @@ module ActiveRecord attrs, bvs = associated_predicate_builder(column_name).create_binds(value) result[column_name] = attrs binds += bvs + when Relation + binds += value.arel.bind_values + value.bind_values end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c3c1b6c961..c34e4bfb9b 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -949,7 +949,6 @@ module ActiveRecord self.bind_values += bind_values attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts) - add_relations_to_bind_values(attributes) predicate_builder.build_from_hash(attributes) else @@ -1119,17 +1118,5 @@ module ActiveRecord raise ArgumentError, "The method .#{method_name}() must contain arguments." end end - - 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.arel.bind_values + value.bind_values - else - add_relations_to_bind_values(value) - end - end - end - end end end -- cgit v1.2.3 From 2e85224fb6ab2c9289287e97f825192e750e08d1 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Tue, 20 Jan 2015 12:23:17 -0500 Subject: TransactionManager should call rollback records --- .../active_record/connection_adapters/abstract/transaction.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 84e5386c20..7f738e89c9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -111,7 +111,6 @@ module ActiveRecord def rollback connection.rollback_to_savepoint(savepoint_name) super - rollback_records end def commit @@ -136,7 +135,6 @@ module ActiveRecord def rollback connection.rollback_db_transaction super - rollback_records end def commit @@ -176,8 +174,10 @@ module ActiveRecord end end - def rollback_transaction - @stack.pop.rollback + def rollback_transaction(transaction = nil) + transaction ||= @stack.pop + transaction.rollback + transaction.rollback_records end def within_new_transaction(options = {}) @@ -194,7 +194,7 @@ module ActiveRecord begin commit_transaction rescue Exception - transaction.rollback unless transaction.state.completed? + rollback_transaction(transaction) unless transaction.state.completed? raise end end -- cgit v1.2.3 From be9b68038e83a617eb38c26147659162e4ac3d2c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 20 Jan 2015 14:09:53 -0700 Subject: Introduce `ActiveRecord::Base#accessed_fields` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This method can be used to see all of the fields on a model which have been read. This can be useful during development mode to quickly find out which fields need to be selected. For performance critical pages, if you are not using all of the fields of a database, an easy performance win is only selecting the fields which you need. By calling this method at the end of a controller action, it's easy to determine which fields need to be selected. While writing this, I also noticed a place for an easy performance win internally which I had been wanting to introduce. You cannot mutate a field which you have not read. Therefore, we can skip the calculation of in place changes if we have never read from the field. This can significantly speed up methods like `#changed?` if any of the fields have an expensive mutable type (like `serialize`) ``` Calculating ------------------------------------- #changed? with serialized column (before) 391.000 i/100ms #changed? with serialized column (after) 1.514k i/100ms ------------------------------------------------- #changed? with serialized column (before) 4.243k (± 3.7%) i/s - 21.505k #changed? with serialized column (after) 16.789k (± 3.2%) i/s - 84.784k ``` --- activerecord/lib/active_record/attribute.rb | 6 +++- .../lib/active_record/attribute_methods.rb | 33 ++++++++++++++++++++++ activerecord/lib/active_record/attribute_set.rb | 4 +++ 3 files changed, 42 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 51e4fae62e..73bb059495 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -51,7 +51,7 @@ module ActiveRecord end def changed_in_place_from?(old_value) - type.changed_in_place?(old_value, value) + has_been_read? && type.changed_in_place?(old_value, value) end def with_value_from_user(value) @@ -78,6 +78,10 @@ module ActiveRecord false end + def has_been_read? + defined?(@value) + end + def ==(other) self.class == other.class && name == other.name && diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 8f165fb1dc..b2db0ceae7 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -369,6 +369,39 @@ module ActiveRecord write_attribute(attr_name, value) end + # Returns the name of all database fields which have been read from this + # model. This can be useful in devleopment mode to determine which fields + # need to be selected. For performance critical pages, selecting only the + # required fields can be an easy performance win (assuming you aren't using + # all of the fields on the model). + # + # For example: + # + # class PostsController < ActionController::Base + # after_action :print_accessed_fields, only: :index + # + # def index + # @posts = Post.all + # end + # + # private + # + # def print_accessed_fields + # p @posts.first.accessed_fields + # end + # end + # + # Which allows you to quickly change your code to: + # + # class PostsController < ActionController::Base + # def index + # @posts = Post.select(:id, :title, :author_id, :updated_at) + # end + # end + def accessed_fields + @attributes.accessed + end + protected def clone_attribute_value(reader_method, attribute_name) # :nodoc: diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 66fcaf6945..fdce68ce45 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -64,6 +64,10 @@ module ActiveRecord end end + def accessed + attributes.select { |_, attr| attr.has_been_read? }.keys + end + protected attr_reader :attributes -- cgit v1.2.3 From 797f78d08d56f0c1f5b0e00fe65987c248409c79 Mon Sep 17 00:00:00 2001 From: Santosh Wadghule Date: Wed, 21 Jan 2015 13:44:22 +0530 Subject: Change 'a' to 'an' for 'attribute' word [ci skip] --- activerecord/lib/active_record/attributes.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index b263a89d79..faf5d632ec 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -14,7 +14,7 @@ module ActiveRecord end module ClassMethods # :nodoc: - # Defines or overrides a attribute on this model. This allows customization of + # Defines or overrides an attribute on this model. This allows customization of # Active Record's type casting behavior, as well as adding support for user defined # types. # -- cgit v1.2.3 From e8460f8bbe73537897f2162d1cccda943d8c0f4a Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 21 Jan 2015 11:47:11 -0700 Subject: Don't error when invalid json is assigned to a JSON column Keeping with our behavior elsewhere in the system, invalid input is assumed to be `nil`. Fixes #18629. --- .../lib/active_record/connection_adapters/postgresql/oid/json.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb index e12ddd9901..7dadc09a44 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb @@ -11,7 +11,7 @@ module ActiveRecord def type_cast_from_database(value) if value.is_a?(::String) - ::ActiveSupport::JSON.decode(value) + ::ActiveSupport::JSON.decode(value) rescue nil else super end -- cgit v1.2.3 From b15a47deff7d29dc91d59b1fef983bb12e08fb6d Mon Sep 17 00:00:00 2001 From: Santosh Wadghule Date: Fri, 23 Jan 2015 11:08:50 +0530 Subject: Use 'public_send' over the 'send' method for object's properties. --- activerecord/lib/active_record/persistence.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 714d36e7c0..22112fe8ff 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -245,7 +245,7 @@ module ActiveRecord def update_attribute(name, value) name = name.to_s verify_readonly_attribute(name) - send("#{name}=", value) + public_send("#{name}=", value) save(validate: false) if changed? end @@ -352,7 +352,7 @@ module ActiveRecord # method toggles directly the underlying value without calling any setter. # Returns +self+. def toggle(attribute) - self[attribute] = !send("#{attribute}?") + self[attribute] = !public_send("#{attribute}?") self end -- cgit v1.2.3 From b9d668f8cb466ab70e107e8ed6e1df2d28c25f31 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 23 Jan 2015 12:04:13 -0700 Subject: Don't remove join dependencies in `Relation#exists?` Fixes #18632 --- activerecord/lib/active_record/relation/finder_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 088bc203b7..c83abfba06 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -307,7 +307,7 @@ module ActiveRecord relation = relation.where(conditions) else unless conditions == :none - relation = where(primary_key => conditions) + relation = relation.where(primary_key => conditions) end end -- cgit v1.2.3 From 96e504ec8af149962312c13dd418e13e4c74ce86 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 23 Jan 2015 12:47:41 -0700 Subject: Errors raised in `type_cast_for_database` no longer raise on assignment Fixes #18580. --- activerecord/lib/active_record/attribute_methods/dirty.rb | 2 +- activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index bce9c5e1e3..06d87ee01e 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -165,7 +165,7 @@ module ActiveRecord end def store_original_raw_attribute(attr_name) - original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database + original_raw_attributes[attr_name] = @attributes[attr_name].value_for_database rescue nil end def store_original_raw_attributes diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 03dfd29a0a..52dce6291a 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -53,7 +53,7 @@ module ActiveRecord class SQLite3String < Type::String # :nodoc: def type_cast_for_database(value) if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT - value.encode(Encoding::UTF_8) + value.encode(Encoding::UTF_8, undef: :replace) else super end -- cgit v1.2.3 From 7c6f3938dee47f0932c2a1d4924adaebc25517ac Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 23 Jan 2015 14:22:53 -0700 Subject: Move integer range validation to never raise on assignment Given that this was originally added to normalize an error that would have otherwise come from the database (inconsistently), it's more natural for us to raise in `type_cast_for_database`, rather than `type_cast_from_user`. This way, things like numericality validators can handle it instead if the user chooses to do so. It also fixes an issue where assigning an out of range value would make it impossible to assign a new value later. This fixes several vague issues, none of which were ever directly reported, so I have no issue number to give. Places it was mentioned which I can remember: - https://github.com/thoughtbot/shoulda-matchers/blob/9ba21381d7caf045053a81f32df7de2f49687820/lib/shoulda/matchers/active_model/allow_value_matcher.rb#L261-L263 - https://github.com/rails/rails/issues/18653#issuecomment-71197026 --- activerecord/lib/active_record/type/integer.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/type/integer.rb b/activerecord/lib/active_record/type/integer.rb index 394fe008f9..90ca9f88da 100644 --- a/activerecord/lib/active_record/type/integer.rb +++ b/activerecord/lib/active_record/type/integer.rb @@ -16,13 +16,19 @@ module ActiveRecord :integer end - alias type_cast_for_database type_cast - def type_cast_from_database(value) return if value.nil? value.to_i end + def type_cast_for_database(value) + result = type_cast(value) + if result + ensure_in_range(result) + end + result + end + protected attr_reader :range @@ -34,9 +40,7 @@ module ActiveRecord when true then 1 when false then 0 else - result = value.to_i rescue nil - ensure_in_range(result) if result - result + value.to_i rescue nil end end -- cgit v1.2.3 From 2606fb339797a99c50e531105fc92071cef3db01 Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Fri, 23 Jan 2015 13:57:34 +0200 Subject: Extracted `ActiveRecord::AttributeAssignment` to `ActiveModel::AttributesAssignment` Allows to use it for any object as an includable module. --- .../lib/active_record/attribute_assignment.rb | 57 ++++++++-------------- activerecord/lib/active_record/errors.rb | 16 ++---- 2 files changed, 23 insertions(+), 50 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index bf64830417..e283db0386 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -3,7 +3,24 @@ require 'active_model/forbidden_attributes_protection' module ActiveRecord module AttributeAssignment extend ActiveSupport::Concern - include ActiveModel::ForbiddenAttributesProtection + include ActiveModel::AttributeAssignment + + def _assign_attributes(attributes) # :nodoc: + multi_parameter_attributes = {} + nested_parameter_attributes = {} + + attributes.each do |k, v| + if k.include?("(") + multi_parameter_attributes[k] = attributes.delete(k) + elsif v.is_a?(Hash) + nested_parameter_attributes[k] = attributes.delete(k) + end + end + super(attributes) + + assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? + assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? + end # Allows you to set all the attributes by passing in a hash of attributes with # keys matching the attribute names (which again matches the column names). @@ -19,47 +36,11 @@ module ActiveRecord # # New attributes will be persisted in the database when the object is saved. # - # Aliased to attributes=. - def assign_attributes(new_attributes) - if !new_attributes.respond_to?(:stringify_keys) - raise ArgumentError, "When assigning attributes, you must pass a hash as an argument." - end - return if new_attributes.blank? - - attributes = new_attributes.stringify_keys - multi_parameter_attributes = [] - nested_parameter_attributes = [] - - attributes = sanitize_for_mass_assignment(attributes) - - attributes.each do |k, v| - if k.include?("(") - multi_parameter_attributes << [ k, v ] - elsif v.is_a?(Hash) - nested_parameter_attributes << [ k, v ] - else - _assign_attribute(k, v) - end - end - - assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty? - assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? - end - + # Aliased to assign_attributes. alias attributes= assign_attributes private - def _assign_attribute(k, v) - public_send("#{k}=", v) - rescue NoMethodError - if respond_to?("#{k}=") - raise - else - raise UnknownAttributeError.new(self, k) - end - end - # Assign any deferred nested attributes after the base attributes have been set. def assign_nested_parameter_attributes(pairs) pairs.each { |k, v| _assign_attribute(k, v) } diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index fc28ab585f..d710d96a9a 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -178,18 +178,10 @@ module ActiveRecord class DangerousAttributeError < ActiveRecordError end - # Raised when unknown attributes are supplied via mass assignment. - class UnknownAttributeError < NoMethodError - - attr_reader :record, :attribute - - def initialize(record, attribute) - @record = record - @attribute = attribute.to_s - super("unknown attribute '#{attribute}' for #{@record.class}.") - end - - end + UnknownAttributeError = ActiveSupport::Deprecation::DeprecatedConstantProxy.new( # :nodoc: + 'ActiveRecord::UnknownAttributeError', + 'ActiveModel::AttributeAssignment::UnknownAttributeError' + ) # Raised when an error occurred while doing a mass assignment to an attribute through the # +attributes=+ method. The exception has an +attribute+ property that is the name of the -- cgit v1.2.3 From a225d4bec51778d99ccba5f0d6700dd00d2474f4 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 23 Jan 2015 14:51:59 -0700 Subject: =?UTF-8?q?=E2=9C=82=EF=B8=8F=20and=20=F0=9F=92=85=20for=20#10776?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minor style changes across the board. Changed an alias to an explicit method declaration, since the alias will not be documented otherwise. --- .../lib/active_record/attribute_assignment.rb | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index e283db0386..e368fdfff9 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -22,22 +22,10 @@ module ActiveRecord assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? end - # Allows you to set all the attributes by passing in a hash of attributes with - # keys matching the attribute names (which again matches the column names). - # - # If the passed hash responds to permitted? method and the return value - # of this method is +false+ an ActiveModel::ForbiddenAttributesError - # exception is raised. - # - # cat = Cat.new(name: "Gorby", status: "yawning") - # cat.attributes # => { "name" => "Gorby", "status" => "yawning", "created_at" => nil, "updated_at" => nil} - # cat.assign_attributes(status: "sleeping") - # cat.attributes # => { "name" => "Gorby", "status" => "sleeping", "created_at" => nil, "updated_at" => nil } - # - # New attributes will be persisted in the database when the object is saved. - # - # Aliased to assign_attributes. - alias attributes= assign_attributes + # Alias for `assign_attributes`. See +ActiveModel::AttributeAssignment+ + def attributes=(attributes) + assign_attributes(attributes) + end private -- cgit v1.2.3 From 4c0a9922d6bc608e330740b4384c418fe69fc587 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 24 Jan 2015 18:49:49 -0700 Subject: Don't duplicate `Relation::VALUE_METHODS` in `Relation::Merger` --- activerecord/lib/active_record/relation/merger.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index afb0b208c3..4ee37be6be 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -49,8 +49,7 @@ module ActiveRecord @other = other end - NORMAL_VALUES = Relation::SINGLE_VALUE_METHODS + - Relation::MULTI_VALUE_METHODS - + NORMAL_VALUES = Relation::VALUE_METHODS - [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: def normal_values -- cgit v1.2.3 From c80487eb1ae7f35cc780745b4f951a79e1a3482b Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 24 Jan 2015 19:51:26 -0700 Subject: Don't rely on relation mutability when building through associations Specifically, the issue is relying on `where_unscoping` mutating the where values. It does not, however, mutate the bind values, which could cause an error under certain circumstances. This was not exposed by the tests, since the only place which would have been affected is unscoping a boolean, which doesn't go through prepared statements. I had a hard time getting better test coverage to demonstrate the issue. This in turn, caused `merge` to go through proper logic, and try to clear out the binds associated with the unscoped relation, which then exposed a source of `nil` for the columns, as binds weren't expanding `{ "posts.id" => 1 }` to `{ "posts" => { "id" => 1 } }`. This has been fixed. The bulk of `create_binds` needed to be moved to a separate method, since the dot notation should not be expanded recursively. I'm pretty sure this removes a subtle quirk that a ton of code in `Relation::Merger` is working around, and I suspect that code can be greatly simplified. However, unraveling that rats nest is no small task. --- .../associations/through_association.rb | 2 +- .../active_record/relation/predicate_builder.rb | 42 ++++++++++++---------- 2 files changed, 25 insertions(+), 19 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/through_association.rb b/activerecord/lib/active_record/associations/through_association.rb index 09828dbd9b..3ce9cffdbc 100644 --- a/activerecord/lib/active_record/associations/through_association.rb +++ b/activerecord/lib/active_record/associations/through_association.rb @@ -18,7 +18,7 @@ module ActiveRecord reflection_scope = reflection.scope if reflection_scope && reflection_scope.arity.zero? - relation.merge!(reflection_scope) + relation = relation.merge(reflection_scope) end scope.merge!( diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 2860a30f99..4c49d3eb81 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -29,24 +29,8 @@ module ActiveRecord end def create_binds(attributes) - result = attributes.dup - binds = [] - - attributes.each do |column_name, value| - case value - when String, Integer, ActiveRecord::StatementCache::Substitute - result[column_name] = Arel::Nodes::BindParam.new - binds.push([table.column(column_name), value]) - when Hash - attrs, bvs = associated_predicate_builder(column_name).create_binds(value) - result[column_name] = attrs - binds += bvs - when Relation - binds += value.arel.bind_values + value.bind_values - end - end - - [result, binds] + attributes = convert_dot_notation_to_hash(attributes.stringify_keys) + create_binds_for_hash(attributes) end def expand(column, value) @@ -108,6 +92,28 @@ module ActiveRecord end end + + def create_binds_for_hash(attributes) + result = attributes.dup + binds = [] + + attributes.each do |column_name, value| + case value + when String, Integer, ActiveRecord::StatementCache::Substitute + result[column_name] = Arel::Nodes::BindParam.new + binds.push([table.column(column_name), value]) + when Hash + attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) + result[column_name] = attrs + binds += bvs + when Relation + binds += value.arel.bind_values + value.bind_values + end + end + + [result, binds] + end + private def associated_predicate_builder(association_name) -- cgit v1.2.3 From ae8cd56c7b1f3130eb3b8f2632a528a8fc78e56c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 24 Jan 2015 20:02:46 -0700 Subject: Don't mutate `where_values` This is to help facilitate future refactorings, as the internal representation is changed. I'm planning on having `where_values` return an array that's computed on call, which means that mutation will have no affect. This is the only remaining place that was mutating (tested by replacing the method with calling `dup`) --- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c34e4bfb9b..53ff48d82e 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -901,7 +901,7 @@ module ActiveRecord def where_unscoping(target_value) target_value = target_value.to_s - where_values.reject! do |rel| + self.where_values = where_values.reject do |rel| case rel when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) -- cgit v1.2.3 From 3327cd3f61a22a5834dcbf9bd24ecbc3a23de3de Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 24 Jan 2015 21:07:55 -0700 Subject: Expand the number of types which can use prepared statements This will allow all types which require no additional handling to use prepared statements. Specifically, this will allow for `true`, `false`, `Date`, `Time`, and any custom PG type to use prepared statements. This also revealed another source of nil columns in bind params, and an inconsistency in their use. The specific inconsistency comes from a nested query coming from a through association, where one of the inversed associations is not bi-directional. The stop-gap is to simply construct the column at the site it is being used. This should simply go away on its own once we use `Attribute` to represent them instead, since we already have all of the information we need. --- .../lib/active_record/relation/predicate_builder.rb | 14 +++++++++++--- activerecord/lib/active_record/relation/query_methods.rb | 5 ++--- activerecord/lib/active_record/table_metadata.rb | 3 +++ 3 files changed, 16 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 4c49d3eb81..490158f0d5 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -99,15 +99,17 @@ module ActiveRecord attributes.each do |column_name, value| case value - when String, Integer, ActiveRecord::StatementCache::Substitute - result[column_name] = Arel::Nodes::BindParam.new - binds.push([table.column(column_name), value]) when Hash attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value) result[column_name] = attrs binds += bvs when Relation binds += value.arel.bind_values + value.bind_values + else + if can_be_bound?(column_name, value) + result[column_name] = Arel::Nodes::BindParam.new + binds.push([table.column(column_name), value]) + end end end @@ -137,5 +139,11 @@ module ActiveRecord def handler_for(object) @handlers.detect { |klass, _| klass === object }.last end + + def can_be_bound?(column_name, value) + !value.nil? && + handler_for(value).is_a?(BasicObjectHandler) && + !table.associated_with?(column_name) + end end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 53ff48d82e..e1c74ce90a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -944,12 +944,11 @@ module ActiveRecord [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] when Hash opts = predicate_builder.resolve_column_aliases(opts) + opts = @klass.send(:expand_hash_conditions_for_aggregates, opts) - tmp_opts, bind_values = predicate_builder.create_binds(opts) + attributes, bind_values = predicate_builder.create_binds(opts) self.bind_values += bind_values - attributes = @klass.send(:expand_hash_conditions_for_aggregates, tmp_opts) - predicate_builder.build_from_hash(attributes) else [opts] diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 31a40adb67..62a0c04a0f 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -25,6 +25,9 @@ module ActiveRecord def column(column_name) if klass klass.columns_hash[column_name.to_s] + else + # FIXME: We really shouldn't need to do this. + ConnectionAdapters::Column.new(column_name.to_s, nil, Type::Value.new) end end -- cgit v1.2.3 From cb18c07f6de0c15a5fb48b3ccc2d18db8a6d15ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Sun, 25 Jan 2015 16:07:54 +0100 Subject: Fix a typo "devleopment" => "development" [ci skip] --- activerecord/lib/active_record/attribute_methods.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index b2db0ceae7..6de71896ee 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -370,7 +370,7 @@ module ActiveRecord end # Returns the name of all database fields which have been read from this - # model. This can be useful in devleopment mode to determine which fields + # model. This can be useful in development mode to determine which fields # need to be selected. For performance critical pages, selecting only the # required fields can be an easy performance win (assuming you aren't using # all of the fields on the model). -- cgit v1.2.3 From 4d7a62293e148604045a5f78a9d4312e79e90d13 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 16:10:56 -0700 Subject: Don't rely as much on the structure of the values hash in associations The structure of `values[:where]` is going to change, with an intermediate definition of `where_values` to aid the refactoring. Accessing `values[:where]` directly messes with that, signficantly. The array wrapping is no longer necessary, since `where_values` will always return an array. --- activerecord/lib/active_record/associations/preloader.rb | 2 +- activerecord/lib/active_record/associations/preloader/association.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 4358f3b581..0a5ef601a2 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -89,7 +89,7 @@ module ActiveRecord # { author: :avatar } # [ :books, { author: :avatar } ] - NULL_RELATION = Struct.new(:values, :bind_values).new({}, []) + NULL_RELATION = Struct.new(:values, :bind_values, :where_values).new({}, [], []) def preload(records, associations, preload_scope = nil) records = Array.wrap(records).compact.uniq diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index afcaa5d55a..8f001770f7 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -136,7 +136,7 @@ module ActiveRecord preload_values = preload_scope.values preload_binds = preload_scope.bind_values - scope.where_values = Array(values[:where]) + Array(preload_values[:where]) + scope.where_values = reflection_scope.where_values + preload_scope.where_values scope.references_values = Array(values[:references]) + Array(preload_values[:references]) scope.bind_values = (reflection_binds + preload_binds) -- cgit v1.2.3 From 79f71d35e957772df7454212b3f235423064832a Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 16:13:27 -0700 Subject: Don't access the where values hash directly in through associations See 4d7a62293e148604045a5f78a9d4312e79e90d13 for the reasoning --- .../lib/active_record/associations/preloader/through_association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 12bf3ef138..816a807f55 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -80,7 +80,7 @@ module ActiveRecord else unless reflection_scope.where_values.empty? scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) - scope.where_values = reflection_scope.values[:where] + scope.where_values = reflection_scope.where_values scope.bind_values = reflection_scope.bind_values end -- cgit v1.2.3 From 2c46d6db4feaf4284415f2fb6ceceb1bb535f278 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 14:54:18 -0700 Subject: Introduce `Relation::WhereClause` The way that bind values are currently stored on Relation is a mess. They can come from `having`, `where`, or `join`. I'm almost certain that `having` is actually broken, and calling `where` followed by `having` followed by `where` will completely scramble the binds. Joins don't actually add the bind parameters to the relation itself, but instead add it onto an accessor on the arel AST which is undocumented, and unused in Arel itself. This means that the bind values must always be accessed as `relation.arel.bind_values + relation.bind_values`. Anything that doesn't is likely broken (and tons of bugs have come up for exactly that reason) The result is that everything dealing with `Relation` instances has to know far too much about the internals. The binds are split, combined, and re-stored in non-obvious ways that makes it difficult to change anything about the internal representation of `bind_values`, and is extremely prone to bugs. So the goal is to move a lot of logic off of `Relation`, and into separate objects. This is not the same as what is currently done with `JoinDependency`, as `Relation` knows far too much about its internals, and vice versa. Instead these objects need to be black boxes that can have their implementations swapped easily. The end result will be two classes, `WhereClause` and `JoinClause` (`having` will just re-use `WhereClause`), and there will be a single method to access the bind values of a `Relation` which will be implemented as ``` join_clause.binds + where_clause.binds + having_clause.binds ``` This is the first step towards that refactoring, with the internal representation of where changed, and an intermediate representation of `where_values` and `bind_values` to let the refactoring take small steps. These will be removed shortly. --- activerecord/lib/active_record/relation.rb | 7 +++-- .../lib/active_record/relation/query_methods.rb | 34 ++++++++++++++++++++++ .../lib/active_record/relation/where_clause.rb | 29 ++++++++++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 activerecord/lib/active_record/relation/where_clause.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 9c4db8a05e..54cb6861b8 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -1,18 +1,19 @@ # -*- coding: utf-8 -*- -require 'arel/collectors/bind' +require "arel/collectors/bind" module ActiveRecord # = Active Record Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, - :order, :joins, :where, :having, :bind, :references, + :order, :joins, :having, :references, :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :distinct, :create_with, :uniq] + CLAUSE_METHODS = [:where] INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having] - VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e1c74ce90a..9a636bc527 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/filters' require 'active_model/forbidden_attributes_protection' +require "active_record/relation/where_clause" module ActiveRecord module QueryMethods @@ -90,6 +91,35 @@ module ActiveRecord CODE end + Relation::CLAUSE_METHODS.each do |name| + class_eval <<-CODE, __FILE__, __LINE__ + 1 + def #{name}_clause # def where_clause + @values[:#{name}] || new_#{name}_clause # @values[:where] || new_where_clause + end # end + # + def #{name}_clause=(value) # def where_clause=(value) + assert_mutability! # assert_mutability! + @values[:#{name}] = value # @values[:where] = value + end # end + CODE + end + + def where_values + where_clause.parts + end + + def where_values=(values) + self.where_clause = Relation::WhereClause.new(values || [], where_clause.binds) + end + + def bind_values + where_clause.binds + end + + def bind_values=(values) + self.where_clause = Relation::WhereClause.new(where_clause.parts, values || []) + end + def create_with_value # :nodoc: @values[:create_with] || {} end @@ -1117,5 +1147,9 @@ module ActiveRecord raise ArgumentError, "The method .#{method_name}() must contain arguments." end end + + def new_where_clause + Relation::WhereClause.empty + end end end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb new file mode 100644 index 0000000000..b39fc1fed1 --- /dev/null +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -0,0 +1,29 @@ +module ActiveRecord + class Relation + class WhereClause + attr_reader :parts, :binds + + def initialize(parts, binds) + @parts = parts + @binds = binds + end + + def +(other) + WhereClause.new( + parts + other.parts, + binds + other.binds, + ) + end + + def ==(other) + other.is_a?(WhereClause) && + parts == other.parts && + binds == other.binds + end + + def self.empty + new([], []) + end + end + end +end -- cgit v1.2.3 From def2879d7d187df945d77c1028d4cef588cbc8a0 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 15:27:43 -0700 Subject: Move where merging logic over to `WhereClause` This object being a black box, it knows the details of how to merge itself with another where clause. This removes all references to where values or bind values in `Relation::Merger` --- activerecord/lib/active_record/relation/merger.rb | 41 ++-------------------- .../lib/active_record/relation/where_clause.rb | 36 ++++++++++++++++++- 2 files changed, 37 insertions(+), 40 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 4ee37be6be..2f3ca539a7 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -50,7 +50,7 @@ module ActiveRecord end NORMAL_VALUES = Relation::VALUE_METHODS - - [:joins, :where, :order, :bind, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: + [:joins, :where, :order, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: def normal_values NORMAL_VALUES @@ -106,19 +106,7 @@ module ActiveRecord end def merge_multi_values - lhs_wheres = relation.where_values - rhs_wheres = other.where_values - - lhs_binds = relation.bind_values - rhs_binds = other.bind_values - - removed, kept = partition_overwrites(lhs_wheres, rhs_wheres) - - where_values = kept + rhs_wheres - bind_values = filter_binds(lhs_binds, removed) + rhs_binds - - relation.where_values = where_values - relation.bind_values = bind_values + relation.where_clause = relation.where_clause.merge(other.where_clause) if other.reordering_value # override any order specified in the original relation @@ -139,31 +127,6 @@ module ActiveRecord relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) end end - - def filter_binds(lhs_binds, removed_wheres) - return lhs_binds if removed_wheres.empty? - - set = Set.new removed_wheres.map { |x| x.left.name.to_s } - lhs_binds.dup.delete_if { |col,_| set.include? col.name } - end - - # Remove equalities from the existing relation with a LHS which is - # present in the relation being merged in. - # returns [things_to_remove, things_to_keep] - def partition_overwrites(lhs_wheres, rhs_wheres) - if lhs_wheres.empty? || rhs_wheres.empty? - return [[], lhs_wheres] - end - - nodes = rhs_wheres.find_all do |w| - w.respond_to?(:operator) && w.operator == :== - end - seen = Set.new(nodes) { |node| node.left } - - lhs_wheres.partition do |w| - w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left) - end - end end end end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index b39fc1fed1..cd6da052a9 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -1,6 +1,6 @@ module ActiveRecord class Relation - class WhereClause + class WhereClause # :nodoc: attr_reader :parts, :binds def initialize(parts, binds) @@ -15,6 +15,13 @@ module ActiveRecord ) end + def merge(other) + WhereClause.new( + parts_unreferenced_by(other) + other.parts, + non_conflicting_binds(other) + other.binds, + ) + end + def ==(other) other.is_a?(WhereClause) && parts == other.parts && @@ -24,6 +31,33 @@ module ActiveRecord def self.empty new([], []) end + + protected + + def referenced_columns + @referenced_columns ||= begin + equality_nodes = parts.select { |n| equality_node?(n) } + Set.new(equality_nodes, &:left) + end + end + + private + + def parts_unreferenced_by(other) + parts.reject do |n| + equality_node?(n) && other.referenced_columns.include?(n.left) + end + end + + def equality_node?(node) + node.respond_to?(:operator) && node.operator == :== + end + + def non_conflicting_binds(other) + conflicts = referenced_columns & other.referenced_columns + conflicts.map! { |node| node.name.to_s } + binds.reject { |col, _| conflicts.include?(col.name) } + end end end end -- cgit v1.2.3 From 87726b93d4fd447671d0b20ae2ec37a9eb4f2fd4 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 16:40:23 -0700 Subject: Remove references to `:bind` in `except` Bind values are no longer a thing, so this is unnecessary. --- activerecord/lib/active_record/associations/association_scope.rb | 2 +- activerecord/lib/active_record/relation/spawn_methods.rb | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index d06b7b3508..3d71e73b3c 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -160,7 +160,7 @@ module ActiveRecord item = eval_scope(reflection.klass, scope_chain_item, owner) if scope_chain_item == refl.scope - scope.merge! item.except(:where, :includes, :bind) + scope.merge! item.except(:where, :includes) end reflection.all_includes do diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 01bddea6c9..dd3610d7aa 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -58,9 +58,6 @@ module ActiveRecord # Post.order('id asc').only(:where) # discards the order condition # Post.order('id asc').only(:where, :order) # uses the specified order def only(*onlies) - if onlies.any? { |o| o == :where } - onlies << :bind - end relation_with values.slice(*onlies) end -- cgit v1.2.3 From 320600c773a16418fe37eccea2eb1082f58062f7 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 16:44:04 -0700 Subject: Remove all references to `where_values` in association code --- activerecord/lib/active_record/associations/association_scope.rb | 3 +-- activerecord/lib/active_record/associations/preloader.rb | 2 +- .../lib/active_record/associations/preloader/association.rb | 9 +++------ .../active_record/associations/preloader/through_association.rb | 5 ++--- activerecord/lib/active_record/relation/where_clause.rb | 2 ++ 5 files changed, 9 insertions(+), 12 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 3d71e73b3c..a2550fa382 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -167,8 +167,7 @@ module ActiveRecord scope.includes! item.includes_values end - scope.where_values += item.where_values - scope.bind_values += item.bind_values + scope.where_clause += item.where_clause scope.order_values |= item.order_values end diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 0a5ef601a2..c3e49007ea 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -89,7 +89,7 @@ module ActiveRecord # { author: :avatar } # [ :books, { author: :avatar } ] - NULL_RELATION = Struct.new(:values, :bind_values, :where_values).new({}, [], []) + NULL_RELATION = Struct.new(:values, :where_clause).new({}, Relation::WhereClause.empty) def preload(records, associations, preload_scope = nil) records = Array.wrap(records).compact.uniq diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 8f001770f7..23848f5e54 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -131,16 +131,13 @@ module ActiveRecord def build_scope scope = klass.unscoped - values = reflection_scope.values - reflection_binds = reflection_scope.bind_values + values = reflection_scope.values preload_values = preload_scope.values - preload_binds = preload_scope.bind_values - scope.where_values = reflection_scope.where_values + preload_scope.where_values + scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause scope.references_values = Array(values[:references]) + Array(preload_values[:references]) - scope.bind_values = (reflection_binds + preload_binds) - scope._select! preload_values[:select] || values[:select] || table[Arel.star] + scope._select! preload_values[:select] || values[:select] || table[Arel.star] scope.includes! preload_values[:includes] || values[:includes] scope.joins! preload_values[:joins] || values[:joins] scope.order! preload_values[:order] || values[:order] diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 816a807f55..56aa23b173 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -78,10 +78,9 @@ module ActiveRecord if options[:source_type] scope.where! reflection.foreign_type => options[:source_type] else - unless reflection_scope.where_values.empty? + unless reflection_scope.where_clause.empty? scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) - scope.where_values = reflection_scope.where_values - scope.bind_values = reflection_scope.bind_values + scope.where_clause = reflection_scope.where_clause end scope.references! reflection_scope.values[:references] diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index cd6da052a9..d1469d0a7a 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -3,6 +3,8 @@ module ActiveRecord class WhereClause # :nodoc: attr_reader :parts, :binds + delegate :empty?, to: :parts + def initialize(parts, binds) @parts = parts @binds = binds -- cgit v1.2.3 From 2da8f2154b2f4c6beac5e50225742beb3caea996 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 16:53:46 -0700 Subject: Move the construction of `WhereClause` objects out of `Relation` Yes, I know, I called it a factory so I'm basically the worst person ever who loves Java and worships the Gang of Four. --- .../lib/active_record/relation/query_methods.rb | 22 +++++--------- .../active_record/relation/where_clause_factory.rb | 34 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 14 deletions(-) create mode 100644 activerecord/lib/active_record/relation/where_clause_factory.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9a636bc527..863d7bb1aa 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -2,6 +2,7 @@ require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/filters' require 'active_model/forbidden_attributes_protection' require "active_record/relation/where_clause" +require "active_record/relation/where_clause_factory" module ActiveRecord module QueryMethods @@ -969,20 +970,9 @@ module ActiveRecord end def build_where(opts, other = []) - case opts - when String, Array - [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] - when Hash - opts = predicate_builder.resolve_column_aliases(opts) - opts = @klass.send(:expand_hash_conditions_for_aggregates, opts) - - attributes, bind_values = predicate_builder.create_binds(opts) - self.bind_values += bind_values - - predicate_builder.build_from_hash(attributes) - else - [opts] - end + where_clause = where_clause_factory.build(opts, other) + self.bind_values += where_clause.binds + where_clause.parts end def association_for_table(table_name) @@ -1151,5 +1141,9 @@ module ActiveRecord def new_where_clause Relation::WhereClause.empty end + + def where_clause_factory + @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder) + end end end diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb new file mode 100644 index 0000000000..0430922be3 --- /dev/null +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -0,0 +1,34 @@ +module ActiveRecord + class Relation + class WhereClauseFactory + def initialize(klass, predicate_builder) + @klass = klass + @predicate_builder = predicate_builder + end + + def build(opts, other) + binds = [] + + case opts + when String, Array + parts = [klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))] + when Hash + attributes = predicate_builder.resolve_column_aliases(opts) + attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes) + + attributes, binds = predicate_builder.create_binds(attributes) + + parts = predicate_builder.build_from_hash(attributes) + else + parts = [opts] + end + + WhereClause.new(parts, binds) + end + + protected + + attr_reader :klass, :predicate_builder + end + end +end -- cgit v1.2.3 From 2ae49dd073a8037ef7ea69e7b1f5507ff514a051 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 17:06:13 -0700 Subject: Move `where.not` logic into `WhereClause` --- .../lib/active_record/relation/query_methods.rb | 17 ++-------------- .../lib/active_record/relation/where_clause.rb | 23 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 15 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 863d7bb1aa..d2473188cb 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -41,23 +41,10 @@ module ActiveRecord # User.where.not(name: "Jon", role: "admin") # # SELECT * FROM users WHERE name != 'Jon' AND role != 'admin' def not(opts, *rest) - where_value = @scope.send(:build_where, opts, rest).map do |rel| - case rel - when NilClass - raise ArgumentError, 'Invalid argument for .where.not(), got nil.' - when Arel::Nodes::In - Arel::Nodes::NotIn.new(rel.left, rel.right) - when Arel::Nodes::Equality - Arel::Nodes::NotEqual.new(rel.left, rel.right) - when String - Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(rel)) - else - Arel::Nodes::Not.new(rel) - end - end + where_clause = @scope.send(:where_clause_factory).build(opts, rest) @scope.references!(PredicateBuilder.references(opts)) if Hash === opts - @scope.where_values += where_value + @scope.where_clause += where_clause.invert @scope end end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index d1469d0a7a..676b0d47d3 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -30,6 +30,10 @@ module ActiveRecord binds == other.binds end + def invert + WhereClause.new(inverted_parts, binds) + end + def self.empty new([], []) end @@ -60,6 +64,25 @@ module ActiveRecord conflicts.map! { |node| node.name.to_s } binds.reject { |col, _| conflicts.include?(col.name) } end + + def inverted_parts + parts.map { |node| invert_predicate(node) } + end + + def invert_predicate(node) + case node + when NilClass + raise ArgumentError, 'Invalid argument for .where.not(), got nil.' + when Arel::Nodes::In + Arel::Nodes::NotIn.new(node.left, node.right) + when Arel::Nodes::Equality + Arel::Nodes::NotEqual.new(node.left, node.right) + when String + Arel::Nodes::Not.new(Arel::Nodes::SqlLiteral.new(node)) + else + Arel::Nodes::Not.new(node) + end + end end end end -- cgit v1.2.3 From 924127e21f13cadb6299a37df5bcd62a3b979ce7 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 17:09:14 -0700 Subject: Rename `WhereClause#parts` to `WhereClause#predicates` --- .../lib/active_record/relation/query_methods.rb | 6 ++--- .../lib/active_record/relation/where_clause.rb | 26 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index d2473188cb..e277282ecd 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -93,7 +93,7 @@ module ActiveRecord end def where_values - where_clause.parts + where_clause.predicates end def where_values=(values) @@ -105,7 +105,7 @@ module ActiveRecord end def bind_values=(values) - self.where_clause = Relation::WhereClause.new(where_clause.parts, values || []) + self.where_clause = Relation::WhereClause.new(where_clause.predicates, values || []) end def create_with_value # :nodoc: @@ -959,7 +959,7 @@ module ActiveRecord def build_where(opts, other = []) where_clause = where_clause_factory.build(opts, other) self.bind_values += where_clause.binds - where_clause.parts + where_clause.predicates end def association_for_table(table_name) diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 676b0d47d3..f03f95df0b 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -1,37 +1,37 @@ module ActiveRecord class Relation class WhereClause # :nodoc: - attr_reader :parts, :binds + attr_reader :predicates, :binds - delegate :empty?, to: :parts + delegate :empty?, to: :predicates - def initialize(parts, binds) - @parts = parts + def initialize(predicates, binds) + @predicates = predicates @binds = binds end def +(other) WhereClause.new( - parts + other.parts, + predicates + other.predicates, binds + other.binds, ) end def merge(other) WhereClause.new( - parts_unreferenced_by(other) + other.parts, + predicates_unreferenced_by(other) + other.predicates, non_conflicting_binds(other) + other.binds, ) end def ==(other) other.is_a?(WhereClause) && - parts == other.parts && + predicates == other.predicates && binds == other.binds end def invert - WhereClause.new(inverted_parts, binds) + WhereClause.new(inverted_predicates, binds) end def self.empty @@ -42,15 +42,15 @@ module ActiveRecord def referenced_columns @referenced_columns ||= begin - equality_nodes = parts.select { |n| equality_node?(n) } + equality_nodes = predicates.select { |n| equality_node?(n) } Set.new(equality_nodes, &:left) end end private - def parts_unreferenced_by(other) - parts.reject do |n| + def predicates_unreferenced_by(other) + predicates.reject do |n| equality_node?(n) && other.referenced_columns.include?(n.left) end end @@ -65,8 +65,8 @@ module ActiveRecord binds.reject { |col, _| conflicts.include?(col.name) } end - def inverted_parts - parts.map { |node| invert_predicate(node) } + def inverted_predicates + predicates.map { |node| invert_predicate(node) } end def invert_predicate(node) -- cgit v1.2.3 From b6a9c620aa58ac0b60ec3b18c380d296c0ec408d Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 17:16:30 -0700 Subject: `Relation#Merger` can merge all clause methods This will make it easy to add `having_clause` and `join_clause` later. --- activerecord/lib/active_record/relation/merger.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 2f3ca539a7..82b38b6ec6 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -50,7 +50,8 @@ module ActiveRecord end NORMAL_VALUES = Relation::VALUE_METHODS - - [:joins, :where, :order, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: + Relation::CLAUSE_METHODS - + [:joins, :order, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: def normal_values NORMAL_VALUES @@ -74,6 +75,7 @@ module ActiveRecord merge_multi_values merge_single_values + merge_clauses merge_joins relation @@ -106,8 +108,6 @@ module ActiveRecord end def merge_multi_values - relation.where_clause = relation.where_clause.merge(other.where_clause) - if other.reordering_value # override any order specified in the original relation relation.reorder! other.order_values @@ -127,6 +127,14 @@ module ActiveRecord relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) end end + + def merge_clauses + CLAUSE_METHODS.each do |name| + clause = relation.send("#{name}_clause") + other_clause = other.send("#{name}_clause") + relation.send("#{name}_clause=", clause.merge(other_clause)) + end + end end end end -- cgit v1.2.3 From 7227e4fba17f4a50e53a5486a3b956a6c7c26697 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 17:20:04 -0700 Subject: Remove most references to `where_values` in `QueryMethods` We're still using it in `where_unscoping`, which will require moving additional logic. --- activerecord/lib/active_record/relation/query_methods.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e277282ecd..c21730e6ae 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -587,7 +587,7 @@ module ActiveRecord references!(PredicateBuilder.references(opts)) end - self.where_values += build_where(opts, rest) + self.where_clause += where_clause_factory.build(opts, rest) self end @@ -876,7 +876,7 @@ module ActiveRecord build_joins(arel, joins_values.flatten) unless joins_values.empty? - collapse_wheres(arel, (where_values - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds + collapse_wheres(arel, (where_clause.predicates - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty? -- cgit v1.2.3 From d6110799c2573080897410f9836f5aa623b197ea Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 17:30:42 -0700 Subject: Move `where_unscoping` logic over to `WhereClause` --- .../lib/active_record/relation/query_methods.rb | 19 ++---------------- .../lib/active_record/relation/where_clause.rb | 23 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 17 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c21730e6ae..1568064e18 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -410,9 +410,8 @@ module ActiveRecord raise ArgumentError, "Hash arguments in .unscope(*args) must have :where as the key." end - Array(target_value).each do |val| - where_unscoping(val) - end + target_values = Array(target_value).map(&:to_s) + self.where_clause = where_clause.except(*target_values) end else raise ArgumentError, "Unrecognized scoping: #{args.inspect}. Use .unscope(where: :attribute_name) or .unscope(:order), for example." @@ -916,20 +915,6 @@ module ActiveRecord self.send(unscope_code, result) end - def where_unscoping(target_value) - target_value = target_value.to_s - - self.where_values = where_values.reject do |rel| - case rel - when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual - subrelation = (rel.left.kind_of?(Arel::Attributes::Attribute) ? rel.left : rel.right) - subrelation.name.to_s == target_value - end - end - - self.bind_values = bind_values.reject { |col,_| col.name == target_value } - end - def custom_join_ast(table, joins) joins = joins.reject(&:blank?) diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index f03f95df0b..ca9c7d19a7 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -24,6 +24,13 @@ module ActiveRecord ) end + def except(*columns) + WhereClause.new( + predicates_except(columns), + binds_except(columns), + ) + end + def ==(other) other.is_a?(WhereClause) && predicates == other.predicates && @@ -83,6 +90,22 @@ module ActiveRecord Arel::Nodes::Not.new(node) end end + + def predicates_except(columns) + predicates.reject do |node| + case node + when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThanOrEqual + subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) + columns.include?(subrelation.name.to_s) + end + end + end + + def binds_except(columns) + binds.reject do |column, _| + columns.include?(column.name) + end + end end end end -- cgit v1.2.3 From 4b71ab089ce57cf063005f014e1ff1bb675c46c7 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 17:44:09 -0700 Subject: Move `where_values_hash` over to `WhereClause` --- activerecord/lib/active_record/relation.rb | 17 +---------------- .../lib/active_record/relation/where_clause.rb | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 16 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 54cb6861b8..5aee74ae61 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -571,22 +571,7 @@ module ActiveRecord # User.where(name: 'Oscar').where_values_hash # # => {name: "Oscar"} def where_values_hash(relation_table_name = table_name) - equalities = where_values.grep(Arel::Nodes::Equality).find_all { |node| - node.left.relation.name == relation_table_name - } - - binds = Hash[bind_values.find_all(&:first).map { |column, v| [column.name, v] }] - - Hash[equalities.map { |where| - name = where.left.name - [name, binds.fetch(name.to_s) { - case where.right - when Array then where.right.map(&:val) - when Arel::Nodes::Casted, Arel::Nodes::Quoted - where.right.val - end - }] - }] + where_clause.to_h(relation_table_name) end def scope_for_create diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index ca9c7d19a7..90fb85cbf1 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -31,6 +31,28 @@ module ActiveRecord ) end + def to_h(table_name = nil) + equalities = predicates.grep(Arel::Nodes::Equality) + if table_name + equalities = equalities.select do |node| + node.left.relation.name == table_name + end + end + + binds = self.binds.select(&:first).to_h.transform_keys(&:name) + + equalities.map { |node| + name = node.left.name + [name, binds.fetch(name.to_s) { + case node.right + when Array then node.right.map(&:val) + when Arel::Nodes::Casted, Arel::Nodes::Quoted + node.right.val + end + }] + }.to_h + end + def ==(other) other.is_a?(WhereClause) && predicates == other.predicates && -- cgit v1.2.3 From fcb95d67440d7e296dfeaa343ecf67a05216dca0 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 17:48:11 -0700 Subject: Correct the implementation for `unscope(:where)` The code assumes that non-single-value methods mean multi value methods. That is not the case. We need to change the accessor name, and only assign an array for multi value methods --- activerecord/lib/active_record/relation/query_methods.rb | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 1568064e18..807de3fc9f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -900,16 +900,19 @@ module ActiveRecord raise ArgumentError, "Called unscope() with invalid unscoping argument ':#{scope}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}." end - single_val_method = Relation::SINGLE_VALUE_METHODS.include?(scope) - unscope_code = "#{scope}_value#{'s' unless single_val_method}=" + clause_method = Relation::CLAUSE_METHODS.include?(scope) + multi_val_method = Relation::MULTI_VALUE_METHODS.include?(scope) + if clause_method + unscope_code = "#{scope}_clause=" + else + unscope_code = "#{scope}_value#{'s' if multi_val_method}=" + end case scope when :order result = [] - when :where - self.bind_values = [] else - result = [] unless single_val_method + result = [] if multi_val_method end self.send(unscope_code, result) -- cgit v1.2.3 From c414fc60ac66ed7581645fb0541072882d5d5354 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sun, 25 Jan 2015 17:44:39 -0700 Subject: Remove `where_values` and `where_values=` We've now removed all uses of them across the board. All logic lives on `WhereClause`. --- activerecord/lib/active_record/relation/query_methods.rb | 8 -------- 1 file changed, 8 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 807de3fc9f..31533b0939 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -92,14 +92,6 @@ module ActiveRecord CODE end - def where_values - where_clause.predicates - end - - def where_values=(values) - self.where_clause = Relation::WhereClause.new(values || [], where_clause.binds) - end - def bind_values where_clause.binds end -- cgit v1.2.3 From 71003d63b6e217625450b0942a7afb8d7d1d14d9 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Mon, 26 Jan 2015 08:17:53 -0200 Subject: Move method to private section It's under private in Active Model as well. --- activerecord/lib/active_record/attribute_assignment.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb index e368fdfff9..fdc90df5b6 100644 --- a/activerecord/lib/active_record/attribute_assignment.rb +++ b/activerecord/lib/active_record/attribute_assignment.rb @@ -5,6 +5,13 @@ module ActiveRecord extend ActiveSupport::Concern include ActiveModel::AttributeAssignment + # Alias for `assign_attributes`. See +ActiveModel::AttributeAssignment+. + def attributes=(attributes) + assign_attributes(attributes) + end + + private + def _assign_attributes(attributes) # :nodoc: multi_parameter_attributes = {} nested_parameter_attributes = {} @@ -22,13 +29,6 @@ module ActiveRecord assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty? end - # Alias for `assign_attributes`. See +ActiveModel::AttributeAssignment+ - def attributes=(attributes) - assign_attributes(attributes) - end - - private - # Assign any deferred nested attributes after the base attributes have been set. def assign_nested_parameter_attributes(pairs) pairs.each { |k, v| _assign_attribute(k, v) } -- cgit v1.2.3 From 025187d9806ddfbdded15d0c7bd8341665ee40e9 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 10:50:46 -0700 Subject: Move flattening records added to an association farther out There are many ways that things end up getting passed to `concat`. Not all of those entry points called `flatten` on their input. It seems that just about every method that is meant to take a single record, or that splats its input, is meant to also take an array. `concat` is the earliest point that is common to all of the methods which add records to the association. Partially fixes #18689 --- activerecord/lib/active_record/associations/collection_association.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index f2c96e9a2a..4b7591e15c 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -151,6 +151,7 @@ module ActiveRecord # be chained. Since << flattens its argument list and inserts each record, # +push+ and +concat+ behave identically. def concat(*records) + records = records.flatten if owner.new_record? load_target concat_records(records) @@ -549,7 +550,7 @@ module ActiveRecord def concat_records(records, should_raise = false) result = true - records.flatten.each do |record| + records.each do |record| raise_on_type_mismatch!(record) add_to_target(record) do |rec| result &&= insert_record(rec, true, should_raise) unless owner.new_record? -- cgit v1.2.3 From 1152219fa2d7968f864b3a5ebb78d2f95ca4fb5c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 12:37:29 -0700 Subject: Improve consistency of counter caches updating in memory When we made sure that the counter gets updated in memory, we only did it on the has many side. The has many side only does the update if the belongs to cannot. The belongs to side was updated to update the counter cache (if it is able). This means that we need to check if the belongs_to is able to update in memory on the has_many side. We also found an inconsistency where the reflection names were used to grab the association which should update the counter cache. Since reflection names are now strings, this means it was using a different instance than the one which would have the inverse instance set. Fixes #18689 [Sean Griffin & anthonynavarre] --- .../associations/belongs_to_association.rb | 3 +++ .../associations/has_many_association.rb | 20 +++++++++++++++----- activerecord/lib/active_record/counter_cache.rb | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb index c63b42e2a0..265a65c4c1 100644 --- a/activerecord/lib/active_record/associations/belongs_to_association.rb +++ b/activerecord/lib/active_record/associations/belongs_to_association.rb @@ -68,6 +68,9 @@ module ActiveRecord def increment_counter(counter_cache_name) if foreign_key_present? klass.increment_counter(counter_cache_name, target_id) + if target && !stale_target? + target.increment(counter_cache_name) + end end end diff --git a/activerecord/lib/active_record/associations/has_many_association.rb b/activerecord/lib/active_record/associations/has_many_association.rb index 2a782c06d0..b574185561 100644 --- a/activerecord/lib/active_record/associations/has_many_association.rb +++ b/activerecord/lib/active_record/associations/has_many_association.rb @@ -101,7 +101,7 @@ module ActiveRecord end def update_counter_in_memory(difference, reflection = reflection()) - if has_cached_counter?(reflection) + if counter_must_be_updated_by_has_many?(reflection) counter = cached_counter_attribute_name(reflection) owner[counter] += difference owner.send(:clear_attribute_changes, counter) # eww @@ -118,18 +118,28 @@ module ActiveRecord # it will be decremented twice. # # Hence this method. - def inverse_updates_counter_cache?(reflection = reflection()) + def inverse_which_updates_counter_cache(reflection = reflection()) counter_name = cached_counter_attribute_name(reflection) - inverse_updates_counter_named?(counter_name, reflection) + inverse_which_updates_counter_named(counter_name, reflection) end + alias inverse_updates_counter_cache? inverse_which_updates_counter_cache - def inverse_updates_counter_named?(counter_name, reflection = reflection()) - reflection.klass._reflections.values.any? { |inverse_reflection| + def inverse_which_updates_counter_named(counter_name, reflection) + reflection.klass._reflections.values.find { |inverse_reflection| inverse_reflection.belongs_to? && inverse_reflection.counter_cache_column == counter_name } end + def inverse_updates_counter_in_memory?(reflection) + inverse = inverse_which_updates_counter_cache(reflection) + inverse && inverse == reflection.inverse_of + end + + def counter_must_be_updated_by_has_many?(reflection) + !inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection) + end + def delete_count(method, scope) if method == :delete_all scope.delete_all diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 7d8e0a2063..82596b63df 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -156,7 +156,7 @@ module ActiveRecord def each_counter_cached_associations _reflections.each do |name, reflection| - yield association(name) if reflection.belongs_to? && reflection.counter_cache_column + yield association(name.to_sym) if reflection.belongs_to? && reflection.counter_cache_column end end -- cgit v1.2.3 From 39f2c3b3ea6fac371e79c284494e3d4cfdc1e929 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 14:44:05 -0700 Subject: Change `having_values` to use the `WhereClause` class This fixed an issue where `having` can only be called after the last call to `where`, because it messes with the same `bind_values` array. With this change, the two can be called as many times as needed, in any order, and the final query will be correct. However, once something assigns `bind_values`, that stops. This is because we have to move all of the bind values from the having clause over to the where clause since we can't differentiate the two, and assignment was likely in the form of: `relation.bind_values += other.bind_values` This will go away once we remove all places that are assigning `bind_values`, which is next on the list. While this fixes a bug that was present in at least 4.2 (more likely present going back as far as 3.0, becoming more likely in 4.1 and later as we switched to prepared statements in more cases), I don't think this can be easily backported. The internal changes to `Relation` are non-trivial, anything that involves modifying the `bind_values` array would need to change, and I'm not confident that we have sufficient test coverage of all of those locations (when `having` was called with a hash that could generate bind values). [Sean Griffin & anthonynavarre] --- activerecord/lib/active_record/relation.rb | 8 +++++--- activerecord/lib/active_record/relation/calculations.rb | 2 +- activerecord/lib/active_record/relation/query_methods.rb | 9 ++++++--- activerecord/lib/active_record/relation/where_clause.rb | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 5aee74ae61..8789bdc9ca 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -5,12 +5,12 @@ module ActiveRecord # = Active Record Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, - :order, :joins, :having, :references, + :order, :joins, :references, :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :distinct, :create_with, :uniq] - CLAUSE_METHODS = [:where] + CLAUSE_METHODS = [:where, :having] INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having] VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS @@ -467,8 +467,10 @@ module ActiveRecord invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method| if MULTI_VALUE_METHODS.include?(method) send("#{method}_values").any? - else + elsif SINGLE_VALUE_METHODS.include?(method) send("#{method}_value") + elsif CLAUSE_METHODS.include?(method) + send("#{method}_clause").any? end } if invalid_methods.any? diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 1d4cb1a83b..67ac350b76 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -290,7 +290,7 @@ module ActiveRecord operation, distinct).as(aggregate_alias) ] - select_values += select_values unless having_values.empty? + select_values += select_values unless having_clause.empty? select_values.concat group_fields.zip(group_aliases).map { |field,aliaz| if field.respond_to?(:as) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 31533b0939..9e72dd319e 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -93,10 +93,11 @@ module ActiveRecord end def bind_values - where_clause.binds + where_clause.binds + having_clause.binds end def bind_values=(values) + self.having_clause = Relation::WhereClause.new(having_clause.predicates, []) self.where_clause = Relation::WhereClause.new(where_clause.predicates, values || []) end @@ -605,7 +606,7 @@ module ActiveRecord def having!(opts, *rest) # :nodoc: references!(PredicateBuilder.references(opts)) if Hash === opts - self.having_values += build_where(opts, rest) + self.having_clause += having_clause_factory.build(opts, rest) self end @@ -869,7 +870,7 @@ module ActiveRecord collapse_wheres(arel, (where_clause.predicates - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds - arel.having(*having_values.uniq.reject(&:blank?)) unless having_values.empty? + arel.having(*having_clause.predicates.uniq.reject(&:blank?)) if having_clause.any? arel.take(connection.sanitize_limit(limit_value)) if limit_value arel.skip(offset_value.to_i) if offset_value @@ -1108,9 +1109,11 @@ module ActiveRecord def new_where_clause Relation::WhereClause.empty end + alias new_having_clause new_where_clause def where_clause_factory @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder) end + alias having_clause_factory where_clause_factory end end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 90fb85cbf1..8b9ba3e633 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -3,7 +3,7 @@ module ActiveRecord class WhereClause # :nodoc: attr_reader :predicates, :binds - delegate :empty?, to: :predicates + delegate :any?, :empty?, to: :predicates def initialize(predicates, binds) @predicates = predicates -- cgit v1.2.3 From 9d4d2e7fc69e5eb0586e57259c2993143346a1b9 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 15:47:43 -0700 Subject: Ensure the type caster object given to Arel is always marshallable The Relation will ultimately end up holding a reference to the arel table object, and its associated type caster. If this is a `TypeCaster::Connection`, that means it'll hold a reference to the connection adapter, which cannot be marshalled. We can work around this by just holding onto the class object instead. It's ugly, but I'm hoping to remove the need for the connection adapter type caster in the future anyway. [Sean Griffin & anthonynavarre] --- activerecord/lib/active_record/type_caster/connection.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb index 9e4a130b40..1d204edb76 100644 --- a/activerecord/lib/active_record/type_caster/connection.rb +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -1,8 +1,8 @@ module ActiveRecord module TypeCaster class Connection - def initialize(connection, table_name) - @connection = connection + def initialize(klass, table_name) + @klass = klass @table_name = table_name end @@ -14,7 +14,8 @@ module ActiveRecord protected - attr_reader :connection, :table_name + attr_reader :table_name + delegate :connection, to: :@klass private -- cgit v1.2.3 From 6a7ac40dabf4a8948418c61f307ffe52ddcb48f2 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 15:49:29 -0700 Subject: Go through normal `where` logic in `AssociationScope` This removes the need to duplicate much of the logic in `WhereClause` and `PredicateBuilder`, simplifies the code, removes the need for the connection adapter to be continuously passed around, and removes one place that cares about the internal representation of `bind_values` Part of the larger refactoring to change how binds are represented internally [Sean Griffin & anthonynavarre] --- .../associations/association_scope.rb | 82 ++++++++-------------- activerecord/lib/active_record/table_metadata.rb | 2 +- 2 files changed, 32 insertions(+), 52 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index a2550fa382..2416167834 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -2,42 +2,30 @@ module ActiveRecord module Associations class AssociationScope #:nodoc: def self.scope(association, connection) - INSTANCE.scope association, connection - end - - class BindSubstitution - def initialize(block) - @block = block - end - - def bind_value(scope, column, value, connection) - substitute = connection.substitute_at(column) - scope.bind_values += [[column, @block.call(value)]] - substitute - end + INSTANCE.scope(association, connection) end def self.create(&block) - block = block ? block : lambda { |val| val } - new BindSubstitution.new(block) + block ||= lambda { |val| val } + new(block) end - def initialize(bind_substitution) - @bind_substitution = bind_substitution + def initialize(value_transformation) + @value_transformation = value_transformation end INSTANCE = create def scope(association, connection) - klass = association.klass - reflection = association.reflection - scope = klass.unscoped - owner = association.owner + klass = association.klass + reflection = association.reflection + scope = klass.unscoped + owner = association.owner alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster chain_head, chain_tail = get_chain(reflection, association, alias_tracker) scope.extending! Array(reflection.options[:extend]) - add_constraints(scope, owner, klass, reflection, connection, chain_head, chain_tail) + add_constraints(scope, owner, klass, reflection, chain_head, chain_tail) end def join_type @@ -61,43 +49,36 @@ module ActiveRecord binds end + protected + + attr_reader :value_transformation + private def join(table, constraint) table.create_join(table, table.create_on(constraint), join_type) end - def column_for(table_name, column_name, connection) - columns = connection.schema_cache.columns_hash(table_name) - columns[column_name] - end - - def bind_value(scope, column, value, connection) - @bind_substitution.bind_value scope, column, value, connection - end - - def bind(scope, table_name, column_name, value, connection) - column = column_for table_name, column_name, connection - bind_value scope, column, value, connection - end - - def last_chain_scope(scope, table, reflection, owner, connection, association_klass) + def last_chain_scope(scope, table, reflection, owner, association_klass) join_keys = reflection.join_keys(association_klass) key = join_keys.key foreign_key = join_keys.foreign_key - bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], connection - scope = scope.where(table[key].eq(bind_val)) + value = transform_value(owner[foreign_key]) + scope = scope.where(table.name => { key => value }) if reflection.type - value = owner.class.base_class.name - bind_val = bind scope, table.table_name, reflection.type, value, connection - scope = scope.where(table[reflection.type].eq(bind_val)) - else - scope + polymorphic_type = transform_value(owner.class.base_class.name) + scope = scope.where(table.name => { reflection.type => polymorphic_type }) end + + scope + end + + def transform_value(value) + value_transformation.call(value) end - def next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection) + def next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) join_keys = reflection.join_keys(association_klass) key = join_keys.key foreign_key = join_keys.foreign_key @@ -105,9 +86,8 @@ module ActiveRecord constraint = table[key].eq(foreign_table[foreign_key]) if reflection.type - value = next_reflection.klass.base_class.name - bind_val = bind scope, table.table_name, reflection.type, value, connection - scope = scope.where(table[reflection.type].eq(bind_val)) + value = transform_value(next_reflection.klass.base_class.name) + scope = scope.where(table.name => { reflection.type => value }) end scope = scope.joins(join(foreign_table, constraint)) @@ -138,10 +118,10 @@ module ActiveRecord [runtime_reflection, previous_reflection] end - def add_constraints(scope, owner, association_klass, refl, connection, chain_head, chain_tail) + def add_constraints(scope, owner, association_klass, refl, chain_head, chain_tail) owner_reflection = chain_tail table = owner_reflection.alias_name - scope = last_chain_scope(scope, table, owner_reflection, owner, connection, association_klass) + scope = last_chain_scope(scope, table, owner_reflection, owner, association_klass) reflection = chain_head loop do @@ -151,7 +131,7 @@ module ActiveRecord unless reflection == chain_tail next_reflection = reflection.next foreign_table = next_reflection.alias_name - scope = next_chain_scope(scope, table, reflection, connection, association_klass, foreign_table, next_reflection) + scope = next_chain_scope(scope, table, reflection, association_klass, foreign_table, next_reflection) end # Exclude the scope of the association itself, because that diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 62a0c04a0f..3707fd3f04 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -43,7 +43,7 @@ module ActiveRecord association_klass = association.klass arel_table = association_klass.arel_table else - type_caster = TypeCaster::Connection.new(klass.connection, table_name) + type_caster = TypeCaster::Connection.new(klass, table_name) association_klass = nil arel_table = Arel::Table.new(table_name, type_caster: type_caster) end -- cgit v1.2.3 From 76661dc64f843085102070a650d426e155fc9a8e Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 15:55:22 -0700 Subject: Remove `Relation#build_where` All of its uses have been moved to better places --- activerecord/lib/active_record/relation/query_methods.rb | 6 ------ 1 file changed, 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9e72dd319e..2d6c167c69 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -937,12 +937,6 @@ module ActiveRecord arel.where(Arel::Nodes::And.new(predicates)) if predicates.present? end - def build_where(opts, other = []) - where_clause = where_clause_factory.build(opts, other) - self.bind_values += where_clause.binds - where_clause.predicates - end - def association_for_table(table_name) table_name = table_name.to_s @klass._reflect_on_association(table_name) || -- cgit v1.2.3 From 765a3123c840a24980aea69154cfea55967edd37 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 16:12:59 -0700 Subject: Remove unused `bind` and `bind!` methods from `Relation` --- activerecord/lib/active_record/relation/query_methods.rb | 9 --------- 1 file changed, 9 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 2d6c167c69..c42882a947 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -435,15 +435,6 @@ module ActiveRecord self end - def bind(value) # :nodoc: - spawn.bind!(value) - end - - def bind!(value) # :nodoc: - self.bind_values += [value] - self - end - # Returns a new relation, which is the result of filtering the current relation # according to the conditions in the arguments. # -- cgit v1.2.3 From 8436e2c2bd91c1a57fb1273218a5428cc2c6b45a Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 16:20:58 -0700 Subject: Remove `Relation#bind_values=` The last place that was assigning it was when `from` is called with a relation to use as a subquery. The implementation was actually completely broken, and would break if you called `from` more than once, or if you called it on a relation, which also had its own join clause, as the bind values would get completely scrambled. The simplest solution was to just move it into its own array, since creating a `FromClause` class for this would be overkill. --- activerecord/lib/active_record/relation.rb | 2 +- activerecord/lib/active_record/relation/merger.rb | 7 ++++--- activerecord/lib/active_record/relation/query_methods.rb | 11 ++++------- 3 files changed, 9 insertions(+), 11 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8789bdc9ca..d4cd63155c 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -5,7 +5,7 @@ module ActiveRecord # = Active Record Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, - :order, :joins, :references, + :order, :joins, :references, :from_bind, :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 82b38b6ec6..1a7b180e77 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -51,7 +51,7 @@ module ActiveRecord NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS - - [:joins, :order, :reverse_order, :lock, :create_with, :reordering, :from] # :nodoc: + [:joins, :order, :reverse_order, :lock, :create_with, :reordering, :from, :from_bind] # :nodoc: def normal_values NORMAL_VALUES @@ -120,8 +120,9 @@ module ActiveRecord end def merge_single_values - relation.from_value = other.from_value unless relation.from_value - relation.lock_value = other.lock_value unless relation.lock_value + relation.from_value ||= other.from_value + relation.from_bind_values = other.from_bind_values unless relation.from_value + relation.lock_value ||= other.lock_value unless other.create_with_value.blank? relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c42882a947..3e33eb8b06 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -93,12 +93,7 @@ module ActiveRecord end def bind_values - where_clause.binds + having_clause.binds - end - - def bind_values=(values) - self.having_clause = Relation::WhereClause.new(having_clause.predicates, []) - self.where_clause = Relation::WhereClause.new(where_clause.predicates, values || []) + from_bind_values + where_clause.binds + having_clause.binds end def create_with_value # :nodoc: @@ -747,7 +742,9 @@ module ActiveRecord def from!(value, subquery_name = nil) # :nodoc: self.from_value = [value, subquery_name] if value.is_a? Relation - self.bind_values = value.arel.bind_values + value.bind_values + bind_values + self.from_bind_values = value.arel.bind_values + value.bind_values + else + self.from_bind_values = [] end self end -- cgit v1.2.3 From bdc5141652770fd227455681cde1f9899f55b0b9 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 26 Jan 2015 16:36:14 -0700 Subject: Move the `from` bind logic to a `FromClause` class Contrary to my previous commit message, it wasn't overkill, and led to much cleaner code. [Sean Griffin & anthonynavarre] --- activerecord/lib/active_record/relation.rb | 6 ++-- .../lib/active_record/relation/from_clause.rb | 32 ++++++++++++++++++++++ activerecord/lib/active_record/relation/merger.rb | 6 ++-- .../lib/active_record/relation/query_methods.rb | 25 +++++++++-------- 4 files changed, 50 insertions(+), 19 deletions(-) create mode 100644 activerecord/lib/active_record/relation/from_clause.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d4cd63155c..a9b43ac816 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -5,12 +5,12 @@ module ActiveRecord # = Active Record Relation class Relation MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group, - :order, :joins, :references, :from_bind, + :order, :joins, :references, :extending, :unscope] - SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, + SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, :reverse_order, :distinct, :create_with, :uniq] - CLAUSE_METHODS = [:where, :having] + CLAUSE_METHODS = [:where, :having, :from] INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having] VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS + CLAUSE_METHODS diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb new file mode 100644 index 0000000000..fc16ac58b4 --- /dev/null +++ b/activerecord/lib/active_record/relation/from_clause.rb @@ -0,0 +1,32 @@ +module ActiveRecord + class Relation + class FromClause + attr_reader :value, :name + + def initialize(value, name) + @value = value + @name = name + end + + def binds + if value.is_a?(Relation) + value.arel.bind_values + value.bind_values + else + [] + end + end + + def merge(other) + self + end + + def empty? + value.nil? + end + + def self.empty + new(nil, nil) + end + end + end +end diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 1a7b180e77..65b607ff1c 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -51,7 +51,7 @@ module ActiveRecord NORMAL_VALUES = Relation::VALUE_METHODS - Relation::CLAUSE_METHODS - - [:joins, :order, :reverse_order, :lock, :create_with, :reordering, :from, :from_bind] # :nodoc: + [:joins, :order, :reverse_order, :lock, :create_with, :reordering] # :nodoc: def normal_values NORMAL_VALUES @@ -120,9 +120,7 @@ module ActiveRecord end def merge_single_values - relation.from_value ||= other.from_value - relation.from_bind_values = other.from_bind_values unless relation.from_value - relation.lock_value ||= other.lock_value + relation.lock_value ||= other.lock_value unless other.create_with_value.blank? relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 3e33eb8b06..6d300372cb 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,8 +1,9 @@ -require 'active_support/core_ext/array/wrap' -require 'active_support/core_ext/string/filters' -require 'active_model/forbidden_attributes_protection' +require "active_record/relation/from_clause" require "active_record/relation/where_clause" require "active_record/relation/where_clause_factory" +require 'active_model/forbidden_attributes_protection' +require 'active_support/core_ext/array/wrap' +require 'active_support/core_ext/string/filters' module ActiveRecord module QueryMethods @@ -93,7 +94,7 @@ module ActiveRecord end def bind_values - from_bind_values + where_clause.binds + having_clause.binds + from_clause.binds + where_clause.binds + having_clause.binds end def create_with_value # :nodoc: @@ -740,12 +741,7 @@ module ActiveRecord end def from!(value, subquery_name = nil) # :nodoc: - self.from_value = [value, subquery_name] - if value.is_a? Relation - self.from_bind_values = value.arel.bind_values + value.bind_values - else - self.from_bind_values = [] - end + self.from_clause = Relation::FromClause.new(value, subquery_name) self end @@ -870,7 +866,7 @@ module ActiveRecord build_select(arel, select_values.uniq) arel.distinct(distinct_value) - arel.from(build_from) if from_value + arel.from(build_from) unless from_clause.empty? arel.lock(lock_value) if lock_value arel @@ -932,7 +928,8 @@ module ActiveRecord end def build_from - opts, name = from_value + opts = from_clause.value + name = from_clause.name case opts when Relation name ||= 'subquery' @@ -1097,5 +1094,9 @@ module ActiveRecord @where_clause_factory ||= Relation::WhereClauseFactory.new(klass, predicate_builder) end alias having_clause_factory where_clause_factory + + def new_from_clause + Relation::FromClause.empty + end end end -- cgit v1.2.3 From 3729103e17e00494c8eae76e8a4ee1ac990d3450 Mon Sep 17 00:00:00 2001 From: Takehiro Adachi Date: Wed, 21 Jan 2015 18:33:32 +0900 Subject: Update model_schema.rb [ci skip] Overriding these methods may cause unexpected results since "table_name=" does more then just setting the "@table_name". ref: https://github.com/rails/rails/pull/18622#issuecomment-70874358 --- activerecord/lib/active_record/model_schema.rb | 20 -------------------- 1 file changed, 20 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 641512d323..a8f326a218 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -105,23 +105,6 @@ module ActiveRecord # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix, # the table name guess for an Invoice class becomes "myapp_invoices". # Invoice::Lineitem becomes "myapp_invoice_lineitems". - # - # You can also set your own table name explicitly: - # - # class Mouse < ActiveRecord::Base - # self.table_name = "mice" - # end - # - # Alternatively, you can override the table_name method to define your - # own computation. (Possibly using super to manipulate the default - # table name.) Example: - # - # class Post < ActiveRecord::Base - # def self.table_name - # "special_" + super - # end - # end - # Post.table_name # => "special_posts" def table_name reset_table_name unless defined?(@table_name) @table_name @@ -132,9 +115,6 @@ module ActiveRecord # class Project < ActiveRecord::Base # self.table_name = "project" # end - # - # You can also just define your own self.table_name method; see - # the documentation for ActiveRecord::Base#table_name. def table_name=(value) value = value && value.to_s -- cgit v1.2.3 From 1bc30b18b729d2decd626cfa8383ed4eb4ee29b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 27 Jan 2015 11:21:42 -0200 Subject: Restore useful documentation removed at 3729103e17e00494c8eae76e8a4ee1ac990d3450 [ci skip] --- activerecord/lib/active_record/model_schema.rb | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index a8f326a218..af0b667262 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -105,6 +105,12 @@ module ActiveRecord # +table_name_suffix+ is appended. So if you have "myapp_" as a prefix, # the table name guess for an Invoice class becomes "myapp_invoices". # Invoice::Lineitem becomes "myapp_invoice_lineitems". + # + # You can also set your own table name explicitly: + # + # class Mouse < ActiveRecord::Base + # self.table_name = "mice" + # end def table_name reset_table_name unless defined?(@table_name) @table_name -- cgit v1.2.3 From 16ce2eecd3eb23034555bb37b29c12985243d908 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 09:15:22 -0700 Subject: Unify access to bind values on Relation The bind values can come from four places. `having`, `where`, `joins`, and `from` when selecting from a subquery that contains binds. These need to be kept in a specific order, since the clauses will always appear in that order. Up until recently, they were not. Additionally, `joins` actually did keep its bind values in a separate location (presumably because it's the only case that people noticed was broken). However, this meant that anything accessing just `bind_values` was broken (which most places were). This is no longer possible, there is only a single way to access the bind values, and it includes joins in the proper location. The setter was removed yesterday, so breaking `+=` cases is not possible. I'm still not happy that `joins` is putting it's bind values on the Arel AST, and I'm planning on refactoring it further, but this removes a ton of bug cases. --- activerecord/lib/active_record/relation.rb | 10 ++++------ activerecord/lib/active_record/relation/calculations.rb | 12 +++--------- activerecord/lib/active_record/relation/finder_methods.rb | 6 +++--- activerecord/lib/active_record/relation/from_clause.rb | 2 +- activerecord/lib/active_record/relation/predicate_builder.rb | 2 +- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 6 files changed, 13 insertions(+), 21 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index a9b43ac816..07e27fe59e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -342,8 +342,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - bvs = arel.bind_values + bind_values - @klass.connection.update stmt, 'SQL', bvs + @klass.connection.update stmt, 'SQL', bind_values end # Updates an object (or multiple objects) and saves it to the database, if validations pass. @@ -559,11 +558,10 @@ module ActiveRecord find_with_associations { |rel| relation = rel } end - arel = relation.arel - binds = arel.bind_values + relation.bind_values + binds = relation.bind_values binds = connection.prepare_binds_for_database(binds) binds.map! { |_, value| connection.quote(value) } - collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new) + collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new) collect.substitute_binds(binds).join end end @@ -636,7 +634,7 @@ module ActiveRecord private def exec_queries - @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values) + @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values) preload = preload_values preload += includes_values unless eager_loading? diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 67ac350b76..724bba5f87 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -166,7 +166,7 @@ module ActiveRecord relation.select_values = column_names.map { |cn| columns_hash.key?(cn) ? arel_table[cn] : cn } - result = klass.connection.select_all(relation.arel, nil, relation.arel.bind_values + bind_values) + result = klass.connection.select_all(relation.arel, nil, bind_values) result.cast_values(klass.column_types) end end @@ -227,14 +227,11 @@ module ActiveRecord column_alias = column_name - bind_values = nil - if operation == "count" && (relation.limit_value || relation.offset_value) # Shortcut when limit is zero. return 0 if relation.limit_value == 0 query_builder = build_count_subquery(relation, column_name, distinct) - bind_values = query_builder.bind_values + relation.bind_values else column = aggregate_column(column_name) @@ -245,7 +242,6 @@ module ActiveRecord relation.select_values = [select_value] query_builder = relation.arel - bind_values = query_builder.bind_values + relation.bind_values end result = @klass.connection.select_all(query_builder, nil, bind_values) @@ -304,7 +300,7 @@ module ActiveRecord relation.group_values = group relation.select_values = select_values - calculated_data = @klass.connection.select_all(relation, nil, relation.arel.bind_values + bind_values) + calculated_data = @klass.connection.select_all(relation, nil, relation.bind_values) if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } @@ -378,11 +374,9 @@ module ActiveRecord aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias) relation.select_values = [aliased_column] - arel = relation.arel - subquery = arel.as(subquery_alias) + subquery = relation.arel.as(subquery_alias) sm = Arel::SelectManager.new relation.engine - sm.bind_values = arel.bind_values select_value = operation_over_aggregate_column(column_alias, 'count', distinct) sm.project(select_value).from(subquery) end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c83abfba06..77bf1217e6 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -311,7 +311,7 @@ module ActiveRecord end end - connection.select_value(relation, "#{name} Exists", relation.arel.bind_values + relation.bind_values) ? true : false + connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false end # This method is called whenever no records are found with either a single @@ -365,7 +365,7 @@ module ActiveRecord [] else arel = relation.arel - rows = connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values) + rows = connection.select_all(arel, 'SQL', relation.bind_values) join_dependency.instantiate(rows, aliases) end end @@ -410,7 +410,7 @@ module ActiveRecord relation = relation.except(:select).select(values).distinct! arel = relation.arel - id_rows = @klass.connection.select_all(arel, 'SQL', arel.bind_values + relation.bind_values) + id_rows = @klass.connection.select_all(arel, 'SQL', relation.bind_values) id_rows.map {|row| row[primary_key]} end diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb index fc16ac58b4..8022901524 100644 --- a/activerecord/lib/active_record/relation/from_clause.rb +++ b/activerecord/lib/active_record/relation/from_clause.rb @@ -10,7 +10,7 @@ module ActiveRecord def binds if value.is_a?(Relation) - value.arel.bind_values + value.bind_values + value.bind_values else [] end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 490158f0d5..90790322b4 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -104,7 +104,7 @@ module ActiveRecord result[column_name] = attrs binds += bvs when Relation - binds += value.arel.bind_values + value.bind_values + binds += value.bind_values else if can_be_bound?(column_name, value) result[column_name] = Arel::Nodes::BindParam.new diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 6d300372cb..5df1269890 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -94,7 +94,7 @@ module ActiveRecord end def bind_values - from_clause.binds + where_clause.binds + having_clause.binds + from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds end def create_with_value # :nodoc: -- cgit v1.2.3 From a5fcdae0a04d97dbd8fd05af6f352d3e09b2815f Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 09:41:25 -0700 Subject: Move where grouping into `WhereClause` --- .../lib/active_record/relation/query_methods.rb | 12 +---------- .../lib/active_record/relation/where_clause.rb | 25 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 11 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 5df1269890..c8b74490c4 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -852,7 +852,7 @@ module ActiveRecord build_joins(arel, joins_values.flatten) unless joins_values.empty? - collapse_wheres(arel, (where_clause.predicates - [''])) #TODO: Add uniq with real value comparison / ignore uniqs that have binds + arel.where(where_clause.ast) unless where_clause.empty? arel.having(*having_clause.predicates.uniq.reject(&:blank?)) if having_clause.any? @@ -911,16 +911,6 @@ module ActiveRecord end end - def collapse_wheres(arel, wheres) - predicates = wheres.map do |where| - next where if ::Arel::Nodes::Equality === where - where = Arel.sql(where) if String === where - Arel::Nodes::Grouping.new(where) - end - - arel.where(Arel::Nodes::And.new(predicates)) if predicates.present? - end - def association_for_table(table_name) table_name = table_name.to_s @klass._reflect_on_association(table_name) || diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 8b9ba3e633..6c62332609 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -53,6 +53,10 @@ module ActiveRecord }.to_h end + def ast + Arel::Nodes::And.new(predicates_with_wrapped_sql_literals) + end + def ==(other) other.is_a?(WhereClause) && predicates == other.predicates && @@ -128,6 +132,27 @@ module ActiveRecord columns.include?(column.name) end end + + def predicates_with_wrapped_sql_literals + non_empty_predicates.map do |node| + if Arel::Nodes::Equality === node + node + else + wrap_sql_literal(node) + end + end + end + + def non_empty_predicates + predicates - [''] + end + + def wrap_sql_literal(node) + if ::String === node + node = Arel.sql(node) + end + Arel::Nodes::Grouping.new(node) + end end end end -- cgit v1.2.3 From c2c95cd279c988e7bd94008264e956e30b78c9ec Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 10:03:25 -0700 Subject: Use the `WhereClause` ast building logic for having --- activerecord/lib/active_record/relation/query_methods.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index c8b74490c4..9bd55de347 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -853,12 +853,9 @@ module ActiveRecord build_joins(arel, joins_values.flatten) unless joins_values.empty? arel.where(where_clause.ast) unless where_clause.empty? - - arel.having(*having_clause.predicates.uniq.reject(&:blank?)) if having_clause.any? - + arel.having(having_clause.ast) unless having_clause.empty? arel.take(connection.sanitize_limit(limit_value)) if limit_value arel.skip(offset_value.to_i) if offset_value - arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty? build_order(arel) -- cgit v1.2.3 From d26dd00854c783bcb1249168bb3f4adf9f99be6c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 10:30:38 -0700 Subject: `WhereClause#predicates` does not need to be public The only place it was accessed was in tests. Many of them have another way that they can test their behavior, that doesn't involve reaching into internals as far as they did. `AssociationScopeTest` is testing a situation where the where clause would have one bind param per predicate, so it can just ignore the predicates entirely. The where chain test was primarly duplicating the logic tested on `WhereClause` directly, so I instead just make sure it calls the appropriate method which is fully tested in isolation. --- activerecord/lib/active_record/relation/where_clause.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 6c62332609..1a70dc447e 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -1,7 +1,7 @@ module ActiveRecord class Relation class WhereClause # :nodoc: - attr_reader :predicates, :binds + attr_reader :binds delegate :any?, :empty?, to: :predicates @@ -73,6 +73,8 @@ module ActiveRecord protected + attr_reader :predicates + def referenced_columns @referenced_columns ||= begin equality_nodes = predicates.select { |n| equality_node?(n) } -- cgit v1.2.3 From ae299dd45d1fff2775d631e240593d4d3684673e Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 10:40:13 -0700 Subject: Minor refactorings on `Relation#build_joins` Attempting to grok this code by refactoring it as I go through it. --- .../lib/active_record/relation/query_methods.rb | 39 ++++++++-------------- 1 file changed, 13 insertions(+), 26 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9bd55de347..a270f23d7d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -2,7 +2,6 @@ require "active_record/relation/from_clause" require "active_record/relation/where_clause" require "active_record/relation/where_clause_factory" require 'active_model/forbidden_attributes_protection' -require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/string/filters' module ActiveRecord @@ -892,22 +891,6 @@ module ActiveRecord self.send(unscope_code, result) end - def custom_join_ast(table, joins) - joins = joins.reject(&:blank?) - - return [] if joins.empty? - - joins.map! do |join| - case join - when Array - join = Arel.sql(join.join(' ')) if array_of_strings?(join) - when String - join = Arel.sql(join) - end - table.create_string_join(join) - end - end - def association_for_table(table_name) table_name = table_name.to_s @klass._reflect_on_association(table_name) || @@ -941,13 +924,14 @@ module ActiveRecord raise 'unknown class: %s' % join.class.name end end + buckets.default = [] - association_joins = buckets[:association_join] || [] - stashed_association_joins = buckets[:stashed_join] || [] - join_nodes = (buckets[:join_node] || []).uniq - string_joins = (buckets[:string_join] || []).map(&:strip).uniq + association_joins = buckets[:association_join] + stashed_association_joins = buckets[:stashed_join] + join_nodes = buckets[:join_node].uniq + string_joins = buckets[:string_join].map(&:strip).uniq - join_list = join_nodes + custom_join_ast(manager, string_joins) + join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( @klass, @@ -967,6 +951,13 @@ module ActiveRecord manager end + def convert_join_strings_to_ast(table, joins) + joins + .flatten + .reject(&:blank?) + .map { |join| table.create_string_join(Arel.sql(join)) } + end + def build_select(arel, selects) if !selects.empty? expanded_select = selects.map do |field| @@ -1001,10 +992,6 @@ module ActiveRecord end end - def array_of_strings?(o) - o.is_a?(Array) && o.all? { |obj| obj.is_a?(String) } - end - def build_order(arel) orders = order_values.uniq orders.reject!(&:blank?) -- cgit v1.2.3 From 102a5272c5944a6f715da8332d18e7e0380727d1 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 11:04:56 -0700 Subject: Don't rely on the internal representation of join values I'm going to be extracting this logic into a clause class, things need to go through a method and not access the values hash directly. --- activerecord/lib/active_record/associations/preloader.rb | 2 +- .../lib/active_record/associations/preloader/association.rb | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index c3e49007ea..97f4bd3811 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -89,7 +89,7 @@ module ActiveRecord # { author: :avatar } # [ :books, { author: :avatar } ] - NULL_RELATION = Struct.new(:values, :where_clause).new({}, Relation::WhereClause.empty) + NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, []) def preload(records, associations, preload_scope = nil) records = Array.wrap(records).compact.uniq diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 23848f5e54..9e4a2b925c 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -139,7 +139,11 @@ module ActiveRecord scope._select! preload_values[:select] || values[:select] || table[Arel.star] scope.includes! preload_values[:includes] || values[:includes] - scope.joins! preload_values[:joins] || values[:joins] + if preload_scope.joins_values.any? + scope.joins!(preload_scope.joins_values) + else + scope.joins!(reflection_scope.joins_values) + end scope.order! preload_values[:order] || values[:order] if preload_values[:readonly] || values[:readonly] -- cgit v1.2.3 From 6c235dd95812a7c4e8123b6d34d6f3433805ee1c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 12:07:06 -0700 Subject: Use an `Attribute` object to represent a bind value The column is primarily used for type casting, which we're trying to separate from the idea of a column. Since what we really need is the combination of a name, type, and value, let's use the object that we already have to represent that concept, rather than this tuple. No consumers of the bind values have been changed, only the producers (outside of tests which care too much about internals). This is *finally* possible since the bind values are now produced from a reasonable number of lcoations. --- .../associations/join_dependency/join_association.rb | 8 ++++---- activerecord/lib/active_record/relation/from_clause.rb | 2 +- activerecord/lib/active_record/relation/predicate_builder.rb | 4 ++-- activerecord/lib/active_record/relation/query_methods.rb | 12 +++++++++++- activerecord/lib/active_record/relation/where_clause.rb | 8 ++++---- activerecord/lib/active_record/table_metadata.rb | 7 +++---- 6 files changed, 25 insertions(+), 16 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index c1ef86a95b..3a184cee37 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -25,7 +25,7 @@ module ActiveRecord def join_constraints(foreign_table, foreign_klass, node, join_type, tables, scope_chain, chain) joins = [] - bind_values = [] + binds = [] tables = tables.reverse scope_chain_index = 0 @@ -66,7 +66,7 @@ module ActiveRecord end if rel && !rel.arel.constraints.empty? - bind_values.concat rel.bind_values + binds += rel.bound_attributes constraint = constraint.and rel.arel.constraints end @@ -75,7 +75,7 @@ module ActiveRecord column = klass.columns_hash[reflection.type.to_s] substitute = klass.connection.substitute_at(column) - bind_values.push [column, value] + binds << Attribute.with_cast_value(column.name, value, klass.type_for_attribute(column.name)) constraint = constraint.and table[reflection.type].eq substitute end @@ -85,7 +85,7 @@ module ActiveRecord foreign_table, foreign_klass = table, klass end - JoinInformation.new joins, bind_values + JoinInformation.new joins, binds end # Builds equality condition. diff --git a/activerecord/lib/active_record/relation/from_clause.rb b/activerecord/lib/active_record/relation/from_clause.rb index 8022901524..a93952fa30 100644 --- a/activerecord/lib/active_record/relation/from_clause.rb +++ b/activerecord/lib/active_record/relation/from_clause.rb @@ -10,7 +10,7 @@ module ActiveRecord def binds if value.is_a?(Relation) - value.bind_values + value.bound_attributes else [] end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 90790322b4..7b605db88c 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -104,11 +104,11 @@ module ActiveRecord result[column_name] = attrs binds += bvs when Relation - binds += value.bind_values + binds += value.bound_attributes else if can_be_bound?(column_name, value) result[column_name] = Arel::Nodes::BindParam.new - binds.push([table.column(column_name), value]) + binds << Attribute.with_cast_value(column_name.to_s, value, table.type(column_name)) end end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index a270f23d7d..bc5f126a23 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -92,10 +92,20 @@ module ActiveRecord CODE end - def bind_values + def bound_attributes from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds end + def bind_values + # convert to old style + bound_attributes.map do |attribute| + if attribute.name + column = ConnectionAdapters::Column.new(attribute.name, nil, attribute.type) + end + [column, attribute.value_before_type_cast] + end + end + def create_with_value # :nodoc: @values[:create_with] || {} end diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index 1a70dc447e..ae5667dfd6 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -39,7 +39,7 @@ module ActiveRecord end end - binds = self.binds.select(&:first).to_h.transform_keys(&:name) + binds = self.binds.map { |attr| [attr.name, attr.value] }.to_h equalities.map { |node| name = node.left.name @@ -97,7 +97,7 @@ module ActiveRecord def non_conflicting_binds(other) conflicts = referenced_columns & other.referenced_columns conflicts.map! { |node| node.name.to_s } - binds.reject { |col, _| conflicts.include?(col.name) } + binds.reject { |attr| conflicts.include?(attr.name) } end def inverted_predicates @@ -130,8 +130,8 @@ module ActiveRecord end def binds_except(columns) - binds.reject do |column, _| - columns.include?(column.name) + binds.reject do |attr| + columns.include?(attr.name) end end diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index 3707fd3f04..6c8792ee80 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -22,12 +22,11 @@ module ActiveRecord arel_table[column_name] end - def column(column_name) + def type(column_name) if klass - klass.columns_hash[column_name.to_s] + klass.type_for_attribute(column_name.to_s) else - # FIXME: We really shouldn't need to do this. - ConnectionAdapters::Column.new(column_name.to_s, nil, Type::Value.new) + Type::Value.new end end -- cgit v1.2.3 From 3a551b9777e7bb16caa3bba63c2412d450126f5f Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 13:08:00 -0700 Subject: All subclasses of `Attribute` should be private constants --- activerecord/lib/active_record/attribute.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 73bb059495..eadc17035e 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -156,6 +156,6 @@ module ActiveRecord false end end - private_constant :FromDatabase, :FromUser, :Null, :Uninitialized + private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue end end -- cgit v1.2.3 From 31dd1ca59f13f37c46027e5006324d2060395765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Sch=C3=A4fer?= Date: Sun, 18 Jan 2015 23:44:07 +0100 Subject: fix typo still cause -> still causes --- .../lib/active_record/attribute_methods/time_zone_conversion.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') 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 98671178cb..75cc88d4ee 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -73,7 +73,7 @@ module ActiveRecord time_zone_aware_types.include?(:not_explicitly_configured) ActiveSupport::Deprecation.warn(<<-MESSAGE) Time columns will become time zone aware in Rails 5.1. This - still cause `String`s to be parsed as if they were in `Time.zone`, + still causes `String`s to be parsed as if they were in `Time.zone`, and `Time`s to be converted to `Time.zone`. To keep the old behavior, you must add the following to your initializer: -- cgit v1.2.3 From b06f64c3480cd389d14618540d62da4978918af0 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 27 Jan 2015 13:42:02 -0700 Subject: Remove Relation#bind_params `bound_attributes` is now used universally across the board, removing the need for the conversion layer. These changes are mostly mechanical, with the exception of the log subscriber. Additional, we had to implement `hash` on the attribute objects, so they could be used as a key for query caching. --- .../associations/has_many_through_association.rb | 2 +- .../associations/join_dependency/join_association.rb | 2 +- activerecord/lib/active_record/attribute.rb | 5 +++++ .../abstract/database_statements.rb | 6 +++--- .../connection_adapters/abstract/quoting.rb | 8 +------- .../connection_adapters/abstract_adapter.rb | 2 +- .../connection_adapters/mysql_adapter.rb | 8 +++----- .../connection_adapters/postgresql_adapter.rb | 8 +++----- .../connection_adapters/sqlite3_adapter.rb | 8 +++----- activerecord/lib/active_record/log_subscriber.rb | 20 +++++++------------- activerecord/lib/active_record/relation.rb | 18 +++++++++--------- .../lib/active_record/relation/calculations.rb | 6 +++--- .../lib/active_record/relation/finder_methods.rb | 6 +++--- .../lib/active_record/relation/predicate_builder.rb | 2 +- .../lib/active_record/relation/query_attribute.rb | 19 +++++++++++++++++++ .../lib/active_record/relation/query_methods.rb | 11 +---------- activerecord/lib/active_record/statement_cache.rb | 18 +++++++++--------- 17 files changed, 73 insertions(+), 76 deletions(-) create mode 100644 activerecord/lib/active_record/relation/query_attribute.rb (limited to 'activerecord/lib/active_record') 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 f1e784d771..4fcbc98c62 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -143,7 +143,7 @@ module ActiveRecord stmt.from scope.klass.arel_table stmt.wheres = arel.constraints - count = scope.klass.connection.delete(stmt, 'SQL', scope.bind_values) + count = scope.klass.connection.delete(stmt, 'SQL', scope.bound_attributes) end when :nullify count = scope.update_all(source_reflection.foreign_key => nil) diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb index 3a184cee37..a6ad09a38a 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -75,7 +75,7 @@ module ActiveRecord column = klass.columns_hash[reflection.type.to_s] substitute = klass.connection.substitute_at(column) - binds << Attribute.with_cast_value(column.name, value, klass.type_for_attribute(column.name)) + binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name)) constraint = constraint.and table[reflection.type].eq substitute end diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index eadc17035e..48b50d9017 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -88,6 +88,11 @@ module ActiveRecord value_before_type_cast == other.value_before_type_cast && type == other.type end + alias eql? == + + def hash + [self.class, name, value_before_type_cast, type].hash + end protected diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 787d07c4c2..6022d0102b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -286,10 +286,10 @@ module ActiveRecord columns = schema_cache.columns_hash(table_name) binds = fixture.map do |name, value| - [columns[name], value] + Relation::QueryAttribute.new(name, value, columns[name].cast_type) end key_list = fixture.keys.map { |name| quote_column_name(name) } - value_list = prepare_binds_for_database(binds).map do |_, value| + value_list = prepare_binds_for_database(binds).map do |value| begin quote(value) rescue TypeError @@ -381,7 +381,7 @@ module ActiveRecord def binds_from_relation(relation, binds) if relation.is_a?(Relation) && binds.empty? - relation, binds = relation.arel, relation.bind_values + relation, binds = relation.arel, relation.bound_attributes end [relation, binds] end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index c18caa2a2f..62123cdc1f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -97,13 +97,7 @@ module ActiveRecord end def prepare_binds_for_database(binds) # :nodoc: - binds.map do |column, value| - if column - column_name = column.name - value = column.cast_type.type_cast_for_database(value) - end - [column_name, value] - end + binds.map(&:value_for_database) end private diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index c941c9f1eb..436552d300 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -114,7 +114,7 @@ module ActiveRecord class BindCollector < Arel::Collectors::Bind def compile(bvs, conn) casted_binds = conn.prepare_binds_for_database(bvs) - super(casted_binds.map { |_, value| conn.quote(value) }) + super(casted_binds.map { |value| conn.quote(value) }) end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index 23d8389abb..16f50fc594 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -395,11 +395,9 @@ module ActiveRecord def exec_stmt(sql, name, binds) cache = {} - type_casted_binds = binds.map { |col, val| - [col, type_cast(val, col)] - } + type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - log(sql, name, type_casted_binds) do + log(sql, name, binds) do if binds.empty? stmt = @connection.prepare(sql) else @@ -410,7 +408,7 @@ module ActiveRecord end begin - stmt.execute(*type_casted_binds.map { |_, val| val }) + stmt.execute(*type_casted_binds) rescue Mysql::Error => e # Older versions of MySQL leave the prepared statement in a bad # place when an error occurs. To support older MySQL versions, we diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index f4f9747359..d789069f0b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -609,12 +609,10 @@ module ActiveRecord def exec_cache(sql, name, binds) stmt_key = prepare_statement(sql) - type_casted_binds = binds.map { |col, val| - [col, type_cast(val, col)] - } + type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - log(sql, name, type_casted_binds, stmt_key) do - @connection.exec_prepared(stmt_key, type_casted_binds.map { |_, val| val }) + log(sql, name, binds, stmt_key) do + @connection.exec_prepared(stmt_key, type_casted_binds) end rescue ActiveRecord::StatementInvalid => e pgerror = e.original_exception diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 52dce6291a..225c2a8587 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -280,11 +280,9 @@ module ActiveRecord end def exec_query(sql, name = nil, binds = []) - type_casted_binds = binds.map { |col, val| - [col, type_cast(val, col)] - } + type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) } - log(sql, name, type_casted_binds) do + log(sql, name, binds) do # Don't cache statements if they are not prepared if without_prepared_statement?(binds) stmt = @connection.prepare(sql) @@ -302,7 +300,7 @@ module ActiveRecord stmt = cache[:stmt] cols = cache[:cols] ||= stmt.columns stmt.reset! - stmt.bind_params type_casted_binds.map { |_, val| val } + stmt.bind_params type_casted_binds end ActiveRecord::Result.new(cols, stmt.to_a) diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index a5c7279db9..6b26d7be78 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -20,18 +20,14 @@ module ActiveRecord @odd = false end - def render_bind(column, value) - if column - if column.binary? && value - # This specifically deals with the PG adapter that casts bytea columns into a Hash. - value = value[:value] if value.is_a?(Hash) - value = "<#{value.bytesize} bytes of binary data>" - end - - [column.name, value] + def render_bind(attribute) + value = if attribute.type.binary? && attribute.value + "<#{attribute.value.bytesize} bytes of binary data>" else - [nil, value] + attribute.value_for_database end + + [attribute.name, value] end def sql(event) @@ -47,9 +43,7 @@ module ActiveRecord binds = nil unless (payload[:binds] || []).empty? - binds = " " + payload[:binds].map { |col,v| - render_bind(col, v) - }.inspect + binds = " " + payload[:binds].map { |attr| render_bind(attr) }.inspect end if odd? diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 07e27fe59e..8073eb99dd 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -81,7 +81,7 @@ module ActiveRecord end relation = scope.where(@klass.primary_key => (id_was || id)) - bvs = binds + relation.bind_values + bvs = binds + relation.bound_attributes um = relation .arel .compile_update(substitutes, @klass.primary_key) @@ -95,11 +95,11 @@ module ActiveRecord def substitute_values(values) # :nodoc: binds = values.map do |arel_attr, value| - [@klass.columns_hash[arel_attr.name], value] + QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name)) end - substitutes = values.each_with_index.map do |(arel_attr, _), i| - [arel_attr, @klass.connection.substitute_at(binds[i][0])] + substitutes = values.map do |(arel_attr, _)| + [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])] end [substitutes, binds] @@ -342,7 +342,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - @klass.connection.update stmt, 'SQL', bind_values + @klass.connection.update stmt, 'SQL', bound_attributes end # Updates an object (or multiple objects) and saves it to the database, if validations pass. @@ -488,7 +488,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - affected = @klass.connection.delete(stmt, 'SQL', bind_values) + affected = @klass.connection.delete(stmt, 'SQL', bound_attributes) reset affected @@ -558,9 +558,9 @@ module ActiveRecord find_with_associations { |rel| relation = rel } end - binds = relation.bind_values + binds = relation.bound_attributes binds = connection.prepare_binds_for_database(binds) - binds.map! { |_, value| connection.quote(value) } + binds.map! { |value| connection.quote(value) } collect = visitor.accept(relation.arel.ast, Arel::Collectors::Bind.new) collect.substitute_binds(binds).join end @@ -634,7 +634,7 @@ module ActiveRecord private def exec_queries - @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values) + @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes) preload = preload_values preload += includes_values unless eager_loading? diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 724bba5f87..83745bc169 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -166,7 +166,7 @@ module ActiveRecord relation.select_values = column_names.map { |cn| columns_hash.key?(cn) ? arel_table[cn] : cn } - result = klass.connection.select_all(relation.arel, nil, bind_values) + result = klass.connection.select_all(relation.arel, nil, bound_attributes) result.cast_values(klass.column_types) end end @@ -244,7 +244,7 @@ module ActiveRecord query_builder = relation.arel end - result = @klass.connection.select_all(query_builder, nil, bind_values) + result = @klass.connection.select_all(query_builder, nil, bound_attributes) row = result.first value = row && row.values.first column = result.column_types.fetch(column_alias) do @@ -300,7 +300,7 @@ module ActiveRecord relation.group_values = group relation.select_values = select_values - calculated_data = @klass.connection.select_all(relation, nil, relation.bind_values) + calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes) if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 77bf1217e6..fb47d915ff 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -311,7 +311,7 @@ module ActiveRecord end end - connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false + connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false end # This method is called whenever no records are found with either a single @@ -365,7 +365,7 @@ module ActiveRecord [] else arel = relation.arel - rows = connection.select_all(arel, 'SQL', relation.bind_values) + rows = connection.select_all(arel, 'SQL', relation.bound_attributes) join_dependency.instantiate(rows, aliases) end end @@ -410,7 +410,7 @@ module ActiveRecord relation = relation.except(:select).select(values).distinct! arel = relation.arel - id_rows = @klass.connection.select_all(arel, 'SQL', relation.bind_values) + id_rows = @klass.connection.select_all(arel, 'SQL', relation.bound_attributes) id_rows.map {|row| row[primary_key]} end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index 7b605db88c..43e9afe853 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -108,7 +108,7 @@ module ActiveRecord else if can_be_bound?(column_name, value) result[column_name] = Arel::Nodes::BindParam.new - binds << Attribute.with_cast_value(column_name.to_s, value, table.type(column_name)) + binds << Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) end end end diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb new file mode 100644 index 0000000000..e69319b4de --- /dev/null +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -0,0 +1,19 @@ +require 'active_record/attribute' + +module ActiveRecord + class Relation + class QueryAttribute < Attribute + def type_cast(value) + value + end + + def value_for_database + @value_for_database ||= super + end + + def with_cast_value(value) + QueryAttribute.new(name, value, type) + end + end + end +end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index bc5f126a23..0078b0f32e 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,4 +1,5 @@ require "active_record/relation/from_clause" +require "active_record/relation/query_attribute" require "active_record/relation/where_clause" require "active_record/relation/where_clause_factory" require 'active_model/forbidden_attributes_protection' @@ -96,16 +97,6 @@ module ActiveRecord from_clause.binds + arel.bind_values + where_clause.binds + having_clause.binds end - def bind_values - # convert to old style - bound_attributes.map do |attribute| - if attribute.name - column = ConnectionAdapters::Column.new(attribute.name, nil, attribute.type) - end - [column, attribute.value_before_type_cast] - end - end - def create_with_value # :nodoc: @values[:create_with] || {} end diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb index 3047a81ec4..95986c820c 100644 --- a/activerecord/lib/active_record/statement_cache.rb +++ b/activerecord/lib/active_record/statement_cache.rb @@ -48,7 +48,7 @@ module ActiveRecord def sql_for(binds, connection) val = @values.dup binds = connection.prepare_binds_for_database(binds) - @indexes.each { |i| val[i] = connection.quote(binds.shift.last) } + @indexes.each { |i| val[i] = connection.quote(binds.shift) } val.join end end @@ -67,21 +67,21 @@ module ActiveRecord end class BindMap # :nodoc: - def initialize(bind_values) + def initialize(bound_attributes) @indexes = [] - @bind_values = bind_values + @bound_attributes = bound_attributes - bind_values.each_with_index do |(_, value), i| - if Substitute === value + bound_attributes.each_with_index do |attr, i| + if Substitute === attr.value @indexes << i end end end def bind(values) - bvs = @bind_values.map(&:dup) - @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] } - bvs + bas = @bound_attributes.dup + @indexes.each_with_index { |offset,i| bas[offset] = bas[offset].with_cast_value(values[i]) } + bas end end @@ -89,7 +89,7 @@ module ActiveRecord def self.create(connection, block = Proc.new) relation = block.call Params.new - bind_map = BindMap.new relation.bind_values + bind_map = BindMap.new relation.bound_attributes query_builder = connection.cacheable_query relation.arel new query_builder, bind_map end -- cgit v1.2.3 From 9a6c6c6f094ce965cc251865bdc1828bc4f38039 Mon Sep 17 00:00:00 2001 From: Henrik Nygren Date: Tue, 27 Jan 2015 15:52:52 +0200 Subject: Provide a better error message on :required association Fixes #18696. --- .../lib/active_record/associations/builder/singular_association.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/builder/singular_association.rb b/activerecord/lib/active_record/associations/builder/singular_association.rb index 1369212837..f6274c027e 100644 --- a/activerecord/lib/active_record/associations/builder/singular_association.rb +++ b/activerecord/lib/active_record/associations/builder/singular_association.rb @@ -31,7 +31,7 @@ module ActiveRecord::Associations::Builder def self.define_validations(model, reflection) super if reflection.options[:required] - model.validates_presence_of reflection.name + model.validates_presence_of reflection.name, message: :required end end end -- cgit v1.2.3 From 56a3d5ec9183a9bcbf140d4102d45e3928f2617a Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 28 Jan 2015 09:36:42 -0700 Subject: Don't redefine autosave association callbacks in nested attrs These callbacks will already have been defined when the association was built. The check against `reflection.autosave` happens at call time, not at define time, so simply modifying the reflection is sufficient. Fixes #18704 --- activerecord/lib/active_record/nested_attributes.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 846e1162a9..919bbfa649 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -312,7 +312,6 @@ module ActiveRecord attr_names.each do |association_name| if reflection = _reflect_on_association(association_name) reflection.autosave = true - add_autosave_association_callbacks(reflection) nested_attributes_options = self.nested_attributes_options.dup nested_attributes_options[association_name.to_sym] = options -- cgit v1.2.3 From b0b37942d729b6bdcd2e3178eda7fa1de203b3d0 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Mon, 23 Jun 2014 11:42:46 +0800 Subject: Added #or to ActiveRecord::Relation Post.where('id = 1').or(Post.where('id = 2')) # => SELECT * FROM posts WHERE (id = 1) OR (id = 2) [Matthew Draper & Gael Muller] --- activerecord/lib/active_record/null_relation.rb | 8 +++ activerecord/lib/active_record/querying.rb | 2 +- .../lib/active_record/relation/query_methods.rb | 59 ++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index b406da14dc..802adca908 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -75,5 +75,13 @@ module ActiveRecord def exists?(_id = false) false end + + def or(other) + if other.is_a?(NullRelation) + super + else + other.or(self) + end + end end end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 91c9a0db99..4e597590e9 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -7,7 +7,7 @@ module ActiveRecord delegate :find_by, :find_by!, to: :all delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all delegate :find_each, :find_in_batches, to: :all - delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :or, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :uniq, :distinct, :references, :none, :unscope, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 0078b0f32e..78ee8b4580 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -582,6 +582,65 @@ module ActiveRecord unscope(where: conditions.keys).where(conditions) end + # Returns a new relation, which is the logical union of this relation and the one passed as an + # argument. + # + # The two relations must be structurally compatible: they must be scoping the same model, and + # they must differ only by +where+ (if no +group+ has been defined) or +having+ (if a +group+ is + # present). Neither relation may have a +limit+, +offset+, or +uniq+ set. + # + # Post.where("id = 1").or(Post.where("id = 2")) + # # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'id = 2')) + # + def or(other) + spawn.or!(other) + end + + def or!(other) + combining = group_values.any? ? :having : :where + + unless structurally_compatible?(other, combining) + raise ArgumentError, 'Relation passed to #or must be structurally compatible' + end + + unless other.is_a?(NullRelation) + left_values = send("#{combining}_values") + right_values = other.send("#{combining}_values") + + common = left_values & right_values + mine = left_values - common + theirs = right_values - common + + if mine.any? && theirs.any? + mine = mine.map { |x| String === x ? Arel.sql(x) : x } + theirs = theirs.map { |x| String === x ? Arel.sql(x) : x } + + mine = [Arel::Nodes::And.new(mine)] if mine.size > 1 + theirs = [Arel::Nodes::And.new(theirs)] if theirs.size > 1 + + common << Arel::Nodes::Or.new(mine.first, theirs.first) + end + + send("#{combining}_values=", common) + end + + self + end + + def structurally_compatible?(other, allowed_to_vary) + Relation::SINGLE_VALUE_METHODS.all? do |name| + send("#{name}_value") == other.send("#{name}_value") + end && + (Relation::MULTI_VALUE_METHODS - [allowed_to_vary, :extending]).all? do |name| + send("#{name}_values") == other.send("#{name}_values") + end && + (extending_values - [NullRelation]) == (other.extending_values - [NullRelation]) && + !limit_value && + !offset_value && + !uniq_value + end + private :structurally_compatible? + # Allows to specify a HAVING clause. Note that you can't use HAVING # without also specifying a GROUP clause. # -- cgit v1.2.3 From ff45b9e9f7c4ff0fb4fdab8beb539913b876d63b Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 28 Jan 2015 14:04:26 -0700 Subject: Bring the implementation of Relation#or up to speed --- activerecord/lib/active_record/null_relation.rb | 6 +-- .../lib/active_record/relation/query_methods.rb | 44 ++++------------------ .../lib/active_record/relation/where_clause.rb | 13 +++++++ 3 files changed, 22 insertions(+), 41 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 802adca908..63ca29305d 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -77,11 +77,7 @@ module ActiveRecord end def or(other) - if other.is_a?(NullRelation) - super - else - other.or(self) - end + other.spawn end end end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 78ee8b4580..b0edb3b1f2 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -596,50 +596,22 @@ module ActiveRecord spawn.or!(other) end - def or!(other) - combining = group_values.any? ? :having : :where - - unless structurally_compatible?(other, combining) + def or!(other) # :nodoc: + unless structurally_compatible_for_or?(other) raise ArgumentError, 'Relation passed to #or must be structurally compatible' end - unless other.is_a?(NullRelation) - left_values = send("#{combining}_values") - right_values = other.send("#{combining}_values") - - common = left_values & right_values - mine = left_values - common - theirs = right_values - common - - if mine.any? && theirs.any? - mine = mine.map { |x| String === x ? Arel.sql(x) : x } - theirs = theirs.map { |x| String === x ? Arel.sql(x) : x } - - mine = [Arel::Nodes::And.new(mine)] if mine.size > 1 - theirs = [Arel::Nodes::And.new(theirs)] if theirs.size > 1 - - common << Arel::Nodes::Or.new(mine.first, theirs.first) - end - - send("#{combining}_values=", common) - end + self.where_clause = self.where_clause.or(other.where_clause) + self.having_clause = self.having_clause.or(other.having_clause) self end - def structurally_compatible?(other, allowed_to_vary) - Relation::SINGLE_VALUE_METHODS.all? do |name| - send("#{name}_value") == other.send("#{name}_value") - end && - (Relation::MULTI_VALUE_METHODS - [allowed_to_vary, :extending]).all? do |name| - send("#{name}_values") == other.send("#{name}_values") - end && - (extending_values - [NullRelation]) == (other.extending_values - [NullRelation]) && - !limit_value && - !offset_value && - !uniq_value + private def structurally_compatible_for_or?(other) # :nodoc: + Relation::SINGLE_VALUE_METHODS.all? { |m| send("#{m}_value") == other.send("#{m}_value") } && + (Relation::MULTI_VALUE_METHODS - [:extending]).all? { |m| send("#{m}_values") == other.send("#{m}_values") } && + (Relation::CLAUSE_METHODS - [:having, :where]).all? { |m| send("#{m}_clause") != other.send("#{m}_clause") } end - private :structurally_compatible? # Allows to specify a HAVING clause. Note that you can't use HAVING # without also specifying a GROUP clause. diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index ae5667dfd6..ce307e4f0c 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -31,6 +31,19 @@ module ActiveRecord ) end + def or(other) + if empty? + other + elsif other.empty? + self + else + WhereClause.new( + [ast.or(other.ast)], + binds + other.binds + ) + end + end + def to_h(table_name = nil) equalities = predicates.grep(Arel::Nodes::Equality) if table_name -- cgit v1.2.3 From 96ac14a3856b9e48e11c7f1e0552ef2f3a87e4d6 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 28 Jan 2015 15:17:17 -0700 Subject: Always convert strings to UTF-8, regardless of column type in SQLite All columns which would map to a string primitive need this behavior. Binary has it's own marker type, so it won't go through this conversion. String and text, which need this, will. Fixes #18585. --- .../connection_adapters/sqlite3_adapter.rb | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 225c2a8587..02a3b65934 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -50,16 +50,6 @@ module ActiveRecord end end - class SQLite3String < Type::String # :nodoc: - def type_cast_for_database(value) - if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT - value.encode(Encoding::UTF_8, undef: :replace) - else - super - end - end - end - # The SQLite3 adapter works SQLite 3.6.16 or newer # with the sqlite3-ruby drivers (available as gem from https://rubygems.org/gems/sqlite3). # @@ -239,6 +229,12 @@ module ActiveRecord case value when BigDecimal value.to_f + when String + if value.encoding == Encoding::ASCII_8BIT + super(value.encode(Encoding::UTF_8)) + else + super + end else super end @@ -496,7 +492,6 @@ module ActiveRecord def initialize_type_map(m) super m.register_type(/binary/i, SQLite3Binary.new) - register_class_with_limit m, %r(char)i, SQLite3String end def table_structure(table_name) -- cgit v1.2.3 From 74c2961bd864f633c79c03e62c2cb142642201c5 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Wed, 28 Jan 2015 16:01:12 -0700 Subject: Don't error when grouped calculations return 0 records Fixes #18717 --- activerecord/lib/active_record/relation/calculations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 83745bc169..c3c4d7f1ce 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -304,7 +304,7 @@ module ActiveRecord if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } - key_records = association.klass.base_class.find(key_ids) + key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids) key_records = Hash[key_records.map { |r| [r.id, r] }] end -- cgit v1.2.3 From e94330fe40b472aec25a7b74b1294cbdc449b730 Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Thu, 29 Jan 2015 17:02:12 +0200 Subject: Fixed AR::Relation#group method when argument is a SQL reserved keyword --- .../lib/active_record/relation/query_methods.rb | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index b0edb3b1f2..7514401072 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -887,11 +887,11 @@ module ActiveRecord arel.having(having_clause.ast) unless having_clause.empty? arel.take(connection.sanitize_limit(limit_value)) if limit_value arel.skip(offset_value.to_i) if offset_value - arel.group(*group_values.uniq.reject(&:blank?)) unless group_values.empty? + arel.group(*arel_columns(group_values.uniq.reject(&:blank?))) unless group_values.empty? build_order(arel) - build_select(arel, select_values.uniq) + build_select(arel) arel.distinct(distinct_value) arel.from(build_from) unless from_clause.empty? @@ -990,22 +990,24 @@ module ActiveRecord .map { |join| table.create_string_join(Arel.sql(join)) } end - def build_select(arel, selects) - if !selects.empty? - expanded_select = selects.map do |field| - if (Symbol === field || String === field) && columns_hash.key?(field.to_s) - arel_table[field] - else - field - end - end - - arel.project(*expanded_select) + def build_select(arel) + if select_values.any? + arel.project(*arel_columns(select_values.uniq)) else arel.project(@klass.arel_table[Arel.star]) end end + def arel_columns(columns) + columns.map do |field| + if (Symbol === field || String === field) && columns_hash.key?(field.to_s) + arel_table[field] + else + field + end + end + end + def reverse_sql_order(order_query) order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty? -- cgit v1.2.3 From 38dd7939e67c127ebbd6cfbb81107b41474d744a Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Thu, 29 Jan 2015 13:57:13 -0700 Subject: Post.all.or(anything) == Post.all --- activerecord/lib/active_record/relation/where_clause.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb index ce307e4f0c..f9b9e640ec 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -33,9 +33,9 @@ module ActiveRecord def or(other) if empty? - other - elsif other.empty? self + elsif other.empty? + other else WhereClause.new( [ast.or(other.ast)], -- cgit v1.2.3 From 85465ed3e6c582d25f0c8fafe21f7a2c182c2f67 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 30 Jan 2015 07:26:33 -0700 Subject: Always perform validations on nested attribute associations Collection associations would have already been validated, but singular associations were not. Fixes #18735. --- activerecord/lib/active_record/autosave_association.rb | 16 ++++++++++++---- activerecord/lib/active_record/nested_attributes.rb | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index fcaaffb852..0792d19c3e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -177,10 +177,8 @@ module ActiveRecord # before actually defining them. def add_autosave_association_callbacks(reflection) save_method = :"autosave_associated_records_for_#{reflection.name}" - validation_method = :"validate_associated_records_for_#{reflection.name}" - collection = reflection.collection? - if collection + if reflection.collection? before_save :before_save_collection_association define_non_cyclic_method(save_method) { save_collection_association(reflection) } @@ -204,8 +202,18 @@ module ActiveRecord before_save save_method end + define_autosave_validation_callbacks(reflection) + end + + def define_autosave_validation_callbacks(reflection) + validation_method = :"validate_associated_records_for_#{reflection.name}" if reflection.validate? && !method_defined?(validation_method) - method = (collection ? :validate_collection_association : :validate_single_association) + if reflection.collection? + method = :validate_collection_association + else + method = :validate_single_association + end + define_non_cyclic_method(validation_method) do send(method, reflection) # TODO: remove the following line as soon as the return value of diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb index 919bbfa649..117a128579 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -312,6 +312,7 @@ module ActiveRecord attr_names.each do |association_name| if reflection = _reflect_on_association(association_name) reflection.autosave = true + define_autosave_validation_callbacks(reflection) nested_attributes_options = self.nested_attributes_options.dup nested_attributes_options[association_name.to_sym] = options -- cgit v1.2.3 From 155b1b7fe3a1d231fb98a6fb04a21f6eb190b98f Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 30 Jan 2015 10:13:42 -0700 Subject: Remove most uses of `Column#cast_type` The goal is to remove the type object from the column, and remove columns from the type casting process entirely. The primary motivation for this is clarity. The connection adapter does not have sufficient type information, since the type we want to work with might have been overriden at the class level. By taking this object from the column, it is easy to mistakenly think that the column object which exists on the connection adapter is sufficient. It isn't. A concrete example of this is `serialize`. In 4.2 and earlier, `where` worked in a very inconsistent and confusing manner. If you passed a single value to `where`, it would serialize it before querying, and do the right thing. However, passing it as part of an array, hash, or range would cause it to not work. This is because it would stop using prepared statements, so the type casting would come from arel. Arel would have no choice but to get the column from the connection adapter, which would treat it as any other string column, and query for the wrong value. There are a handful of cases where using the column object to find the cast type is appropriate. These are cases where there is not actually a class involved, such as the migration DSL, or fixtures. For all other cases, the API should be designed as such that the type is provided before we get to the connection adapter. (For an example of this, see the work done to decorate the arel table object with a type caster, or the introduction of `QueryAttribute` to `Relation`). There are times that it is appropriate to use information from the column to change behavior in the connection adapter. These cases are when the primitive used to represent that type before it goes to the database does not sufficiently express what needs to happen. An example of this that affects every adapter is binary vs varchar, where the primitive used for both is a string. In this case it is appropriate to look at the column object to determine which quoting method to use, as this is something schema dependent. An example of something which would not be appropriate is to look at the type and see that it is a datetime, and performing string parsing when given a string instead of a date. This is the type of logic that should live entirely on the type. The value which comes out of the type should be a sufficiently generic primitive that the adapter can be expected to know how to work with it. The one place that is still using the column for type information which should not be necessary is the connection adapter type caster which is sometimes given to the arel table when we can't find the associated table. This will hopefully go away in the near future. --- .../abstract/database_statements.rb | 3 ++- .../connection_adapters/abstract/quoting.rb | 27 ++++++++++++++++++++-- .../connection_adapters/postgresql/column.rb | 8 ++++--- .../connection_adapters/postgresql/quoting.rb | 6 ++++- .../postgresql/schema_statements.rb | 12 ++++++---- .../lib/active_record/type_caster/connection.rb | 14 ++++------- 6 files changed, 48 insertions(+), 22 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb index 6022d0102b..c3e5e9ad61 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -286,7 +286,8 @@ module ActiveRecord columns = schema_cache.columns_hash(table_name) binds = fixture.map do |name, value| - Relation::QueryAttribute.new(name, value, columns[name].cast_type) + type = lookup_cast_type_from_column(columns[name]) + Relation::QueryAttribute.new(name, value, type) end key_list = fixture.keys.map { |name| quote_column_name(name) } value_list = prepare_binds_for_database(binds).map do |value| diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb index 62123cdc1f..2456376d6d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -16,7 +16,7 @@ module ActiveRecord https://github.com/rails/arel/commit/6160bfbda1d1781c3b08a33ec4955f170e95be11 for more information. MSG - value = column.cast_type.type_cast_for_database(value) + value = type_cast_from_column(column, value) end _quote(value) @@ -31,7 +31,7 @@ module ActiveRecord end if column - value = column.cast_type.type_cast_for_database(value) + value = type_cast_from_column(column, value) end _type_cast(value) @@ -40,6 +40,29 @@ module ActiveRecord raise TypeError, "can't cast #{value.class}#{to_type}" end + # If you are having to call this function, you are likely doing something + # wrong. The column does not have sufficient type information if the user + # provided a custom type on the class level either explicitly (via + # `attribute`) or implicitly (via `serialize`, + # `time_zone_aware_attributes`). In almost all cases, the sql type should + # only be used to change quoting behavior, when the primitive to + # represent the type doesn't sufficiently reflect the differences + # (varchar vs binary) for example. The type used to get this primitive + # should have been provided before reaching the connection adapter. + def type_cast_from_column(column, value) # :nodoc: + if column + type = lookup_cast_type_from_column(column) + type.type_cast_for_database(value) + else + value + end + end + + # See docs for +type_cast_from_column+ + def lookup_cast_type_from_column(column) # :nodoc: + lookup_cast_type(column.sql_type) + end + # Quotes a string, escaping any ' (single quote) and \ (backslash) # characters. def quote_string(s) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index acb1278499..8f3628ec0e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -2,17 +2,19 @@ module ActiveRecord module ConnectionAdapters # PostgreSQL-specific extensions to column definitions in a table. class PostgreSQLColumn < Column #:nodoc: - attr_reader :array + attr_reader :array, :oid, :fmod alias :array? :array - def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil) + def initialize(name, default, cast_type, sql_type = nil, null = true, default_function = nil, oid = nil, fmod = nil) if sql_type =~ /\[\]$/ @array = true sql_type = sql_type[0..sql_type.length - 3] else @array = false end - super + @oid = oid + @fmod = fmod + super(name, default, cast_type, sql_type, null, default_function) end def serial? diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 9de9e2c7dc..464adb4e23 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -56,11 +56,15 @@ module ActiveRecord if column.type == :uuid && value =~ /\(\)/ value else - value = column.cast_type.type_cast_for_database(value) + value = type_cast_from_column(column, value) quote(value) end end + def lookup_cast_type_from_column(column) # :nodoc: + type_map.lookup(column.oid, column.fmod, column.sql_type) + end + private def _quote(value) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index afd7a17c03..519a9727fb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -183,15 +183,17 @@ module ActiveRecord def columns(table_name) # Limit, precision, and scale are all handled by the superclass. column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod| - oid = get_oid_type(oid.to_i, fmod.to_i, column_name, type) - default_value = extract_value_from_default(oid, default) + oid = oid.to_i + fmod = fmod.to_i + cast_type = get_oid_type(oid, fmod, column_name, type) + default_value = extract_value_from_default(cast_type, default) default_function = extract_default_function(default_value, default) - new_column(column_name, default_value, oid, type, notnull == 'f', default_function) + new_column(column_name, default_value, cast_type, type, notnull == 'f', default_function, oid, fmod) end end - def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil) # :nodoc: - PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function) + def new_column(name, default, cast_type, sql_type = nil, null = true, default_function = nil, oid = nil, fmod = nil) # :nodoc: + PostgreSQLColumn.new(name, default, cast_type, sql_type, null, default_function, oid, fmod) end # Returns the current database name. diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb index 1d204edb76..3878270770 100644 --- a/activerecord/lib/active_record/type_caster/connection.rb +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -8,8 +8,8 @@ module ActiveRecord def type_cast_for_database(attribute_name, value) return value if value.is_a?(Arel::Nodes::BindParam) - type = type_for(attribute_name) - type.type_cast_for_database(value) + column = column_for(attribute_name) + connection.type_cast_from_column(column, value) end protected @@ -19,17 +19,11 @@ module ActiveRecord private - def type_for(attribute_name) + def column_for(attribute_name) if connection.schema_cache.table_exists?(table_name) - column_for(attribute_name).cast_type - else - Type::Value.new + connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] end end - - def column_for(attribute_name) - connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] - end end end end -- cgit v1.2.3 From b93b39eff6829ee05ffec1cc8c505f69cbb53fdc Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 30 Jan 2015 11:42:54 -0700 Subject: Remove most type related predicates from `Column` Remaining are `limit`, `precision`, `scale`, and `type` (the symbol version). These will remain on the column, since they mirror the options to the `column` method in the schema definition DSL --- .../lib/active_record/associations/preloader/association.rb | 4 ++-- activerecord/lib/active_record/attribute_methods/query.rb | 3 ++- .../lib/active_record/connection_adapters/abstract/schema_dumper.rb | 5 +++-- activerecord/lib/active_record/connection_adapters/column.rb | 6 +----- activerecord/lib/active_record/core.rb | 2 +- activerecord/lib/active_record/fixtures.rb | 6 +++--- activerecord/lib/active_record/serializers/xml_serializer.rb | 4 ++-- activerecord/lib/active_record/validations/uniqueness.rb | 5 +++-- 8 files changed, 17 insertions(+), 18 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 9e4a2b925c..1dc8bff193 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -104,11 +104,11 @@ module ActiveRecord end def association_key_type - @klass.column_for_attribute(association_key_name).type + @klass.type_for_attribute(association_key_name.to_s).type end def owner_key_type - @model.column_for_attribute(owner_key_name).type + @model.type_for_attribute(owner_key_name.to_s).type end def load_slices(slices) diff --git a/activerecord/lib/active_record/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb index dc689f399a..83b858aae7 100644 --- a/activerecord/lib/active_record/attribute_methods/query.rb +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -15,6 +15,7 @@ module ActiveRecord when false, nil then false else column = self.class.columns_hash[attr_name] + type = self.class.type_for_attribute(attr_name) if column.nil? if Numeric === value || value !~ /[^0-9]/ !value.to_i.zero? @@ -22,7 +23,7 @@ module ActiveRecord return false if ActiveRecord::ConnectionAdapters::Column::FALSE_VALUES.include?(value) !value.blank? end - elsif column.number? + elsif type.number? !value.zero? else !value.blank? diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb index 42ea599a74..932aaf7aa7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb @@ -46,9 +46,10 @@ module ActiveRecord private def schema_default(column) - default = column.type_cast_from_database(column.default) + type = lookup_cast_type_from_column(column) + default = type.type_cast_from_database(column.default) unless default.nil? - column.type_cast_for_schema(default) + type.type_cast_for_schema(default) end end end diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index e74de60a83..a489141d1a 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -14,11 +14,7 @@ module ActiveRecord attr_reader :name, :cast_type, :null, :sql_type, :default, :default_function - delegate :type, :precision, :scale, :limit, :klass, :accessor, - :text?, :number?, :binary?, :changed?, - :type_cast_from_user, :type_cast_from_database, :type_cast_for_database, - :type_cast_for_schema, - to: :cast_type + delegate :precision, :scale, :limit, :type, to: :cast_type # Instantiates a new column in the table. # diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 4705f129f2..e68b2c399c 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -210,7 +210,7 @@ module ActiveRecord elsif !connected? "#{super} (call '#{super}.connection' to establish a connection)" elsif table_exists? - attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', ' + attr_list = column_types.map { |name, type| "#{name}: #{type.type}" } * ', ' "#{super}(#{attr_list})" else "#{super}(Table doesn't exist)" diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 10e9be20b5..5b1b7fe73d 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -661,7 +661,7 @@ module ActiveRecord row[association.foreign_type] = $1 end - fk_type = association.active_record.columns_hash[fk_name].type + fk_type = association.active_record.type_for_attribute(fk_name).type row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type) end when :has_many @@ -691,7 +691,7 @@ module ActiveRecord end def primary_key_type - @association.klass.column_types[@association.klass.primary_key].type + @association.klass.type_for_attribute(@association.klass.primary_key).type end end @@ -711,7 +711,7 @@ module ActiveRecord end def primary_key_type - @primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type + @primary_key_type ||= model_class && model_class.type_for_attribute(model_class.primary_key).type end def add_join_records(rows, row, association) diff --git a/activerecord/lib/active_record/serializers/xml_serializer.rb b/activerecord/lib/active_record/serializers/xml_serializer.rb index c2484d02ed..89b7e0be82 100644 --- a/activerecord/lib/active_record/serializers/xml_serializer.rb +++ b/activerecord/lib/active_record/serializers/xml_serializer.rb @@ -180,9 +180,9 @@ module ActiveRecord #:nodoc: class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc: def compute_type klass = @serializable.class - column = klass.columns_hash[name] || Type::Value.new + cast_type = klass.type_for_attribute(name) - type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type + type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || cast_type.type { :text => :string, :time => :datetime }[type] || type diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index f52f91e89c..8f7a46f2c7 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -59,7 +59,8 @@ module ActiveRecord end column = klass.columns_hash[attribute_name] - value = klass.type_for_attribute(attribute_name).type_cast_for_database(value) + cast_type = klass.type_for_attribute(attribute_name) + value = cast_type.type_cast_for_database(value) value = klass.connection.type_cast(value) if value.is_a?(String) && column.limit value = value.to_s[0, column.limit] @@ -67,7 +68,7 @@ module ActiveRecord value = Arel::Nodes::Quoted.new(value) - comparison = if !options[:case_sensitive] && value && column.text? + comparison = if !options[:case_sensitive] && value && cast_type.text? # will use SQL LOWER function before comparison, unless it detects a case insensitive collation klass.connection.case_insensitive_comparison(table, attribute, column, value) else -- cgit v1.2.3 From aebba01f437d864baf7165f19737440002016413 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Sat, 31 Jan 2015 17:28:04 -0700 Subject: Remove `AttributeSet#initialized_keys` This method doesn't need to be lazy, as it is never called from reads. The only time it is called are in write cases, where we're about to loop through the results of it, and build the attribute objects anyway. So we don't gain anything by dodging the instantiation here. This is the only method that coupled `AttributeSet` to `LazyAttributeHash`, so removing it puts us back in a place where we can use a normal hash instead. --- activerecord/lib/active_record/attribute_set.rb | 2 +- activerecord/lib/active_record/attribute_set/builder.rb | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index fdce68ce45..0c9793d470 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -24,7 +24,7 @@ module ActiveRecord end def keys - attributes.initialized_keys + attributes.each_key.select { |name| self[name].initialized? } end def fetch_value(name) diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index 3a76f5262d..0f3c285a80 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -20,7 +20,7 @@ module ActiveRecord end class LazyAttributeHash # :nodoc: - delegate :select, :transform_values, to: :materialize + delegate :select, :transform_values, :each_key, to: :materialize def initialize(types, values, additional_types) @types = types @@ -45,10 +45,6 @@ module ActiveRecord delegate_hash[key] = value end - def initialized_keys - delegate_hash.keys | values.keys - end - def initialize_dup(_) @delegate_hash = delegate_hash.transform_values(&:dup) super -- cgit v1.2.3 From e773ca99f27a8051828ef29e170478ba8dc7e491 Mon Sep 17 00:00:00 2001 From: Hyonjee Joo Date: Sat, 31 Jan 2015 18:13:39 -0800 Subject: changed deleted_tables list to set --- activerecord/lib/active_record/fixtures.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index fc7b2ec850..57db2faaff 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -1,6 +1,7 @@ require 'erb' require 'yaml' require 'zlib' +require 'set' require 'active_support/dependencies' require 'active_support/core_ext/digest/uuid' require 'active_record/fixture_set/file' @@ -521,7 +522,7 @@ module ActiveRecord update_all_loaded_fixtures fixtures_map connection.transaction(:requires_new => true) do - deleted_tables = [] + deleted_tables = Set.new fixture_sets.each do |fs| conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection table_rows = fs.table_rows -- cgit v1.2.3 From 70ac072976c8cc6f013f0df3777e54ccae3f4f8c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Fri, 30 Jan 2015 14:03:36 -0700 Subject: Attribute assignment and type casting has nothing to do with columns It's finally finished!!!!!!! The reason the Attributes API was kept private in 4.2 was due to some publicly visible implementation details. It was previously implemented by overloading `columns` and `columns_hash`, to make them return column objects which were modified with the attribute information. This meant that those methods LIED! We didn't change the database schema. We changed the attribute information on the class. That is wrong! It should be the other way around, where schema loading just calls the attributes API for you. And now it does! Yes, this means that there is nothing that happens in automatic schema loading that you couldn't manually do yourself. (There's still some funky cases where we hit the connection adapter that I need to handle, before we can turn off automatic schema detection entirely.) There were a few weird test failures caused by this that had to be fixed. The main source came from the fact that the attribute methods are now defined in terms of `attribute_names`, which has a clause like `return [] unless table_exists?`. I don't *think* this is an issue, since the only place this caused failures were in a fake adapter which didn't override `table_exists?`. Additionally, there were a few cases where tests were failing because a migration was run, but the model was not reloaded. I'm not sure why these started failing from this change, I might need to clear an additional cache in `reload_schema_from_cache`. Again, since this is not normal usage, and it's expected that `reset_column_information` will be called after the table is modified, I don't think it's a problem. Still, test failures that were unrelated to the change are worrying, and I need to dig into them further. Finally, I spent a lot of time debugging issues with the mutex used in `define_attribute_methods`. I think we can just remove that method entirely, and define the attribute methods *manually* in the call to `define_attribute`, which would simplify the code *tremendously*. Ok. now to make this damn thing public, and work on moving it up to Active Model. --- activerecord/lib/active_record/attribute.rb | 8 +++ .../lib/active_record/attribute_decorators.rb | 11 +-- .../lib/active_record/attribute_methods.rb | 4 +- .../lib/active_record/attribute_methods/dirty.rb | 4 +- activerecord/lib/active_record/attribute_set.rb | 6 +- activerecord/lib/active_record/attributes.rb | 84 +++++++--------------- activerecord/lib/active_record/core.rb | 2 +- activerecord/lib/active_record/inheritance.rb | 2 +- .../lib/active_record/locking/optimistic.rb | 2 +- activerecord/lib/active_record/model_schema.rb | 79 ++++++++++++++------ .../lib/active_record/relation/calculations.rb | 2 +- 11 files changed, 112 insertions(+), 92 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 48b50d9017..91886f1324 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -66,6 +66,10 @@ module ActiveRecord self.class.with_cast_value(name, value, type) end + def with_type(type) + self.class.new(name, value_before_type_cast, type) + end + def type_cast(*) raise NotImplementedError end @@ -137,6 +141,10 @@ module ActiveRecord nil end + def with_type(type) + self.class.with_cast_value(name, nil, type) + end + def with_value_from_database(value) raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`" end diff --git a/activerecord/lib/active_record/attribute_decorators.rb b/activerecord/lib/active_record/attribute_decorators.rb index 5b96623b6e..7d0ae32411 100644 --- a/activerecord/lib/active_record/attribute_decorators.rb +++ b/activerecord/lib/active_record/attribute_decorators.rb @@ -15,7 +15,7 @@ module ActiveRecord end def decorate_matching_attribute_types(matcher, decorator_name, &block) - clear_caches_calculated_from_columns + reload_schema_from_cache decorator_name = decorator_name.to_s # Create new hashes so we don't modify parent classes @@ -24,10 +24,11 @@ module ActiveRecord private - def add_user_provided_columns(*) - super.map do |column| - decorated_type = attribute_type_decorations.apply(column.name, column.cast_type) - column.with_type(decorated_type) + def load_schema! + super + attribute_types.each do |name, type| + decorated_type = attribute_type_decorations.apply(name, type) + define_attribute(name, decorated_type) end end end diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 6de71896ee..9d58a19304 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -83,7 +83,7 @@ module ActiveRecord generated_attribute_methods.synchronize do return false if @attribute_methods_generated superclass.define_attribute_methods unless self == base_class - super(column_names) + super(attribute_names) @attribute_methods_generated = true end true @@ -185,7 +185,7 @@ module ActiveRecord # # => ["id", "created_at", "updated_at", "name", "age"] def attribute_names @attribute_names ||= if !abstract_class? && table_exists? - column_names + attribute_types.keys else [] end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 06d87ee01e..7ba907f786 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -131,10 +131,8 @@ module ActiveRecord partial_writes? ? super(keys_for_partial_write) : super end - # Serialized attributes should always be written in case they've been - # changed in place. def keys_for_partial_write - changed & persistable_attribute_names + changed & self.class.column_names end def _field_changed?(attr, old_value) diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 0c9793d470..9142317646 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -10,6 +10,10 @@ module ActiveRecord attributes[name] || Attribute.null(name) end + def []=(name, value) + attributes[name] = value + end + def values_before_type_cast attributes.transform_values(&:value_before_type_cast) end @@ -49,7 +53,7 @@ module ActiveRecord end def initialize_dup(_) - @attributes = attributes.dup + @attributes = attributes.deep_dup super end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index faf5d632ec..c1b69092bb 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -5,12 +5,8 @@ module ActiveRecord Type = ActiveRecord::Type included do - class_attribute :user_provided_columns, instance_accessor: false # :internal: - class_attribute :user_provided_defaults, instance_accessor: false # :internal: - self.user_provided_columns = {} - self.user_provided_defaults = {} - - delegate :persistable_attribute_names, to: :class + class_attribute :user_provided_types, instance_accessor: false # :internal: + self.user_provided_types = {} end module ClassMethods # :nodoc: @@ -77,70 +73,44 @@ module ActiveRecord # # store_listing = StoreListing.new(price_in_cents: '$10.00') # store_listing.price_in_cents # => 1000 - def attribute(name, cast_type, options = {}) + def attribute(name, cast_type, **options) name = name.to_s - clear_caches_calculated_from_columns - # Assign a new hash to ensure that subclasses do not share a hash - self.user_provided_columns = user_provided_columns.merge(name => cast_type) - - if options.key?(:default) - self.user_provided_defaults = user_provided_defaults.merge(name => options[:default]) - end - end + reload_schema_from_cache - # Returns an array of column objects for the table associated with this class. - def columns - @columns ||= add_user_provided_columns(connection.schema_cache.columns(table_name)) + self.user_provided_types = user_provided_types.merge(name => [cast_type, options]) end - # Returns a hash of column objects for the table associated with this class. - def columns_hash - @columns_hash ||= Hash[columns.map { |c| [c.name, c] }] + def define_attribute( + name, + cast_type, + default: NO_DEFAULT_PROVIDED, + user_provided_default: true + ) + attribute_types[name] = cast_type + define_default_attribute(name, default, cast_type, from_user: user_provided_default) end - def persistable_attribute_names # :nodoc: - @persistable_attribute_names ||= connection.schema_cache.columns_hash(table_name).keys - end - - def reset_column_information # :nodoc: + def load_schema! super - clear_caches_calculated_from_columns + user_provided_types.each do |name, (type, options)| + define_attribute(name, type, **options) + end end private - def add_user_provided_columns(schema_columns) - existing_columns = schema_columns.map do |column| - new_type = user_provided_columns[column.name] - if new_type - column.with_type(new_type) - else - column - end - end + NO_DEFAULT_PROVIDED = Object.new # :nodoc: + private_constant :NO_DEFAULT_PROVIDED - existing_column_names = existing_columns.map(&:name) - new_columns = user_provided_columns.except(*existing_column_names).map do |(name, type)| - connection.new_column(name, nil, type) + def define_default_attribute(name, value, type, from_user:) + if value == NO_DEFAULT_PROVIDED + default_attribute = _default_attributes[name].with_type(type) + elsif from_user + default_attribute = Attribute.from_user(name, value, type) + else + default_attribute = Attribute.from_database(name, value, type) end - - existing_columns + new_columns - end - - def clear_caches_calculated_from_columns - @arel_table = nil - @attributes_builder = nil - @column_names = nil - @column_types = nil - @columns = nil - @columns_hash = nil - @content_columns = nil - @default_attributes = nil - @persistable_attribute_names = nil - end - - def raw_default_values - super.merge(user_provided_defaults) + _default_attributes[name] = default_attribute end end end diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index e68b2c399c..4416217897 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -210,7 +210,7 @@ module ActiveRecord elsif !connected? "#{super} (call '#{super}.connection' to establish a connection)" elsif table_exists? - attr_list = column_types.map { |name, type| "#{name}: #{type.type}" } * ', ' + attr_list = attribute_types.map { |name, type| "#{name}: #{type.type}" } * ', ' "#{super}(#{attr_list})" else "#{super}(Table doesn't exist)" diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index fd1e22349b..24098f72dc 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -192,7 +192,7 @@ module ActiveRecord # If this is a StrongParameters hash, and access to inheritance_column is not permitted, # this will ignore the inheritance column and return nil def subclass_from_attributes?(attrs) - columns_hash.include?(inheritance_column) && attrs.is_a?(Hash) + attribute_names.include?(inheritance_column) && attrs.is_a?(Hash) end def subclass_from_attributes(attrs) diff --git a/activerecord/lib/active_record/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 6f2b65c137..008cda46cd 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -144,7 +144,7 @@ module ActiveRecord # Set the column to use for optimistic locking. Defaults to +lock_version+. def locking_column=(value) - clear_caches_calculated_from_columns + reload_schema_from_cache @locking_column = value.to_s end diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index af0b667262..293db1c57f 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -217,28 +217,37 @@ module ActiveRecord end def attributes_builder # :nodoc: - @attributes_builder ||= AttributeSet::Builder.new(column_types, primary_key) + @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) end - def column_types # :nodoc: - @column_types ||= columns_hash.transform_values(&:cast_type).tap do |h| - h.default = Type::Value.new - end + def columns_hash # :nodoc: + load_schema + @columns_hash + end + + def columns + load_schema + @columns ||= columns_hash.values + end + + def attribute_types # :nodoc: + load_schema + @attribute_types ||= Hash.new(Type::Value.new) end def type_for_attribute(attr_name) # :nodoc: - column_types[attr_name] + attribute_types[attr_name] end # Returns a hash where the keys are column names and the values are # default values when instantiating the AR object for this table. def column_defaults + load_schema _default_attributes.to_hash end def _default_attributes # :nodoc: - @default_attributes ||= attributes_builder.build_from_database( - raw_default_values) + @default_attributes ||= AttributeSet.new({}) end # Returns an array of column names as strings. @@ -281,19 +290,53 @@ module ActiveRecord def reset_column_information connection.clear_cache! undefine_attribute_methods - connection.schema_cache.clear_table_cache!(table_name) if table_exists? + connection.schema_cache.clear_table_cache!(table_name) - @arel_engine = nil - @arel_table = nil - @column_names = nil - @column_types = nil - @content_columns = nil - @default_attributes = nil - @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + reload_schema_from_cache end private + def schema_loaded? + defined?(@columns_hash) && @columns_hash + end + + def load_schema + unless schema_loaded? + load_schema! + end + end + + def load_schema! + @columns_hash = connection.schema_cache.columns_hash(table_name) + @columns_hash.each do |name, column| + define_attribute( + name, + column.cast_type, + default: column.default, + user_provided_default: false + ) + end + end + + def reload_schema_from_cache + @arel_engine = nil + @arel_table = nil + @column_names = nil + @attribute_types = nil + @content_columns = nil + @default_attributes = nil + @inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column + @attributes_builder = nil + @column_names = nil + @attribute_types = nil + @columns = nil + @columns_hash = nil + @content_columns = nil + @default_attributes = nil + @attribute_names = nil + end + # Guesses the table name, but does not decorate it with prefix and suffix information. def undecorated_table_name(class_name = base_class.name) table_name = class_name.to_s.demodulize.underscore @@ -317,10 +360,6 @@ module ActiveRecord base.table_name end end - - def raw_default_values - columns_hash.transform_values(&:default) - end end end end diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index c3c4d7f1ce..63e0d2fc21 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -167,7 +167,7 @@ module ActiveRecord columns_hash.key?(cn) ? arel_table[cn] : cn } result = klass.connection.select_all(relation.arel, nil, bound_attributes) - result.cast_values(klass.column_types) + result.cast_values(klass.attribute_types) end end -- cgit v1.2.3