From 3360742396a00c9e2ff9838373788bed432d5ea7 Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Wed, 12 Apr 2017 14:45:10 -0600 Subject: Switch to LIFO for the connection pool Using a FIFO for the connection pool can lead to issues when there are upstream components (pgbouncer, haproxy, etc.) that terminate connections that are idle after a period of time. Switching to a LIFO reduces the probability that a thread will checkout a connection that is about to be closed by an idle timeout in an upstream component. --- .../lib/active_record/connection_adapters/abstract/connection_pool.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 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 53dbbd8c21..ea76ff983e 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -80,7 +80,7 @@ module ActiveRecord # * private methods that require being called in a +synchronize+ blocks # are now explicitly documented class ConnectionPool - # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool + # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool # with which it shares a Monitor. But could be a generic Queue. # # The Queue in stdlib's 'thread' could replace this class except @@ -111,7 +111,7 @@ module ActiveRecord # Add +element+ to the queue. Never blocks. def add(element) synchronize do - @queue.push element + @queue.unshift element @cond.signal end end -- cgit v1.2.3 From 6116d7bc052839646f448b8403a7287f52b97ed7 Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Thu, 13 Apr 2017 13:56:42 -0600 Subject: Improve documentation and add test --- .../connection_adapters/abstract/connection_pool.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 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 ea76ff983e..3dc265622d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -80,11 +80,8 @@ module ActiveRecord # * private methods that require being called in a +synchronize+ blocks # are now explicitly documented class ConnectionPool - # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool - # with which it shares a Monitor. But could be a generic Queue. - # - # The Queue in stdlib's 'thread' could replace this class except - # stdlib's doesn't support waiting with a timeout. + # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool + # with which it shares a Monitor. class Queue def initialize(lock = Monitor.new) @lock = lock @@ -111,7 +108,7 @@ module ActiveRecord # Add +element+ to the queue. Never blocks. def add(element) synchronize do - @queue.unshift element + @queue.push element @cond.signal end end @@ -173,7 +170,7 @@ module ActiveRecord # Removes and returns the head of the queue if possible, or +nil+. def remove - @queue.shift + @queue.pop end # Remove and return the head the queue if the number of -- cgit v1.2.3 From 47d678d9b2ab6d3e888691e8327e657ff0b5dcb0 Mon Sep 17 00:00:00 2001 From: Devin Christensen Date: Thu, 13 Apr 2017 14:05:39 -0600 Subject: Fix typos --- .../lib/active_record/connection_adapters/abstract/connection_pool.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/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 3dc265622d..9f660a419d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -80,7 +80,7 @@ module ActiveRecord # * private methods that require being called in a +synchronize+ blocks # are now explicitly documented class ConnectionPool - # Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool + # Threadsafe, fair, LIFO queue. Meant to be used by ConnectionPool # with which it shares a Monitor. class Queue def initialize(lock = Monitor.new) -- cgit v1.2.3 From 11e32c1d841c08bd85842eb059fbf30536e804dc Mon Sep 17 00:00:00 2001 From: Eugene Kenny Date: Mon, 24 Apr 2017 23:26:13 +0100 Subject: Enable query cache on all connection pools Since the query cache no longer eagerly checks out a connection, we can enable it on all connection pools at the start of every request, and it will only take effect for requests that actually use those pools. --- activerecord/lib/active_record/query_cache.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index ec246e97bc..92dd52b068 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -24,16 +24,19 @@ module ActiveRecord end def self.run - caching_pool = ActiveRecord::Base.connection_pool - caching_was_enabled = caching_pool.query_cache_enabled + ActiveRecord::Base.connection_handler.connection_pool_list.map do |pool| + caching_was_enabled = pool.query_cache_enabled - caching_pool.enable_query_cache! + pool.enable_query_cache! - [caching_pool, caching_was_enabled] + [pool, caching_was_enabled] + end end - def self.complete((caching_pool, caching_was_enabled)) - caching_pool.disable_query_cache! unless caching_was_enabled + def self.complete(caching_pools) + caching_pools.each do |pool, caching_was_enabled| + pool.disable_query_cache! unless caching_was_enabled + end ActiveRecord::Base.connection_handler.connection_pool_list.each do |pool| pool.release_connection if pool.active_connection? && !pool.connection.transaction_open? -- cgit v1.2.3 From c342d58446defad94f26c067b19003c9941f2470 Mon Sep 17 00:00:00 2001 From: Dylan Thacker-Smith Date: Tue, 29 Aug 2017 20:12:10 -0400 Subject: activerecord: Remove a redundant mutation tracker The extra mutation tracker was needed in Rails 5.1 to preserve the old behaviour of `changes`, but now there is no difference between `changes` and `changes_to_save`, so `@mutation_tracker` can be removed. --- .../lib/active_record/attribute_methods/dirty.rb | 32 ++++++---------------- activerecord/lib/active_record/persistence.rb | 2 +- 2 files changed, 10 insertions(+), 24 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 4f957ac3ca..06598439d8 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -34,7 +34,7 @@ module ActiveRecord def reload(*) super.tap do @mutations_before_last_save = nil - clear_mutation_trackers + @mutations_from_database = nil @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end end @@ -44,22 +44,21 @@ module ActiveRecord @attributes = self.class._default_attributes.map do |attr| attr.with_value_from_user(@attributes.fetch_value(attr.name)) end - clear_mutation_trackers + @mutations_from_database = nil end def changes_applied # :nodoc: - @mutations_before_last_save = mutation_tracker - @mutations_from_database = AttributeMutationTracker.new(@attributes) + @mutations_before_last_save = mutations_from_database @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new forget_attribute_assignments - clear_mutation_trackers + @mutations_from_database = nil end def clear_changes_information # :nodoc: @mutations_before_last_save = nil @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new forget_attribute_assignments - clear_mutation_trackers + @mutations_from_database = nil end def clear_attribute_changes(attr_names) # :nodoc: @@ -75,7 +74,7 @@ module ActiveRecord if defined?(@cached_changed_attributes) @cached_changed_attributes else - super.reverse_merge(mutation_tracker.changed_values).freeze + super.reverse_merge(mutations_from_database.changed_values).freeze end end @@ -90,7 +89,7 @@ module ActiveRecord end def attribute_changed_in_place?(attr_name) # :nodoc: - mutation_tracker.changed_in_place?(attr_name) + mutations_from_database.changed_in_place?(attr_name) end # Did this attribute change when we last saved? This method can be invoked @@ -183,26 +182,18 @@ module ActiveRecord result end - def mutation_tracker - unless defined?(@mutation_tracker) - @mutation_tracker = nil - end - @mutation_tracker ||= AttributeMutationTracker.new(@attributes) - end - def mutations_from_database unless defined?(@mutations_from_database) @mutations_from_database = nil end - @mutations_from_database ||= mutation_tracker + @mutations_from_database ||= AttributeMutationTracker.new(@attributes) end def changes_include?(attr_name) - super || mutation_tracker.changed?(attr_name) + super || mutations_from_database.changed?(attr_name) end def clear_attribute_change(attr_name) - mutation_tracker.forget_change(attr_name) mutations_from_database.forget_change(attr_name) end @@ -227,11 +218,6 @@ module ActiveRecord @attributes = @attributes.map(&:forgetting_assignment) end - def clear_mutation_trackers - @mutation_tracker = nil - @mutations_from_database = nil - end - def mutations_before_last_save @mutations_before_last_save ||= NullMutationTracker.instance end diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index fbbf9082cc..b28f6e96a9 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -228,7 +228,7 @@ module ActiveRecord def becomes(klass) became = klass.new became.instance_variable_set("@attributes", @attributes) - became.instance_variable_set("@mutation_tracker", @mutation_tracker) if defined?(@mutation_tracker) + became.instance_variable_set("@mutations_from_database", @mutations_from_database) if defined?(@mutations_from_database) became.instance_variable_set("@changed_attributes", attributes_changed_by_setter) became.instance_variable_set("@new_record", new_record?) became.instance_variable_set("@destroyed", destroyed?) -- cgit v1.2.3 From 913742af0bbcace4e39c3f516d599bfc0ae5ea82 Mon Sep 17 00:00:00 2001 From: Yoshiyuki Hirano Date: Sun, 3 Sep 2017 00:23:01 +0900 Subject: Add :nodoc: to activerecord [ci skip] --- .../connection_adapters/abstract_mysql_adapter.rb | 2 +- activerecord/lib/active_record/migration/command_recorder.rb | 2 +- activerecord/lib/active_record/scoping.rb | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) (limited to 'activerecord/lib/active_record') 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 7cd086084a..fee362f7b0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -44,7 +44,7 @@ module ActiveRecord json: { name: "json" }, } - class StatementPool < ConnectionAdapters::StatementPool + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: private def dealloc(stmt) stmt[:stmt].close end diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index a3a5e0fa16..72ce9c3655 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -110,7 +110,7 @@ module ActiveRecord private - module StraightReversions + module StraightReversions # :nodoc: private { transaction: :transaction, execute_block: :execute_block, diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index da585a9562..01ac56570a 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -11,23 +11,23 @@ module ActiveRecord include Named end - module ClassMethods - def current_scope(skip_inherited_scope = false) # :nodoc: + module ClassMethods # :nodoc: + def current_scope(skip_inherited_scope = false) ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) end - def current_scope=(scope) #:nodoc: + def current_scope=(scope) ScopeRegistry.set_value_for(:current_scope, self, scope) end # Collects attributes from scopes that should be applied when creating # an AR instance for the particular class this is called on. - def scope_attributes # :nodoc: + def scope_attributes all.scope_for_create end # Are there attributes associated with this scope? - def scope_attributes? # :nodoc: + def scope_attributes? current_scope end end -- cgit v1.2.3 From 7c4dad364cd9e02cf3b8a14fcdbbb3b59428da61 Mon Sep 17 00:00:00 2001 From: dixpac Date: Sun, 17 Sep 2017 16:50:32 +0200 Subject: Fix docs describing rollback [ci skip] * `rails db:migrate STEP = 2` will not rollback the migrations, instead `rails db:rollback STEP = 2` will do the rollback. * Also, rewritten `rails db:migrate VERSION` => `rails db:rollback VERSION` for consistency. --- activerecord/lib/active_record/migration.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 52ca4671c2..8845e26ab7 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -354,9 +354,9 @@ module ActiveRecord # to match the structure of your database. # # To roll the database back to a previous migration version, use - # rails db:migrate VERSION=X where X is the version to which + # rails db:rollback VERSION=X where X is the version to which # you wish to downgrade. Alternatively, you can also use the STEP option if you - # wish to rollback last few migrations. rails db:migrate STEP=2 will rollback + # wish to rollback last few migrations. rails db:rollback STEP=2 will rollback # the latest two migrations. # # If any of the migrations throw an ActiveRecord::IrreversibleMigration exception, -- cgit v1.2.3 From 7f8f66e93136ef7ff602b6cdb1f6a7acfaafa486 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 06:17:09 +0900 Subject: Remove useless condition in `reset_association` `through_scope` is not empty scope if `options[:source_type]` is given. --- .../lib/active_record/associations/preloader/through_association.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (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 8aac00d910..7b37ec2f41 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -68,8 +68,7 @@ module ActiveRecord end def reset_association(owners, association_name, through_scope) - should_reset = (through_scope != through_reflection.klass.unscoped) || - (options[:source_type] && through_reflection.collection?) + should_reset = through_scope != through_reflection.klass.unscoped # Don't cache the association - we would only be caching a subset if should_reset -- cgit v1.2.3 From 45588fef7eef095d33beaf192b0b6a8c9c8af348 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 06:42:47 +0900 Subject: Return `through_scope` only if the scope is not empty scope Related 2b5f5cdd7c1d95716de6a206b6d09ccbb006dc17. If `through_scope` is empty scope, it is unnecessary to merge it. And also, comparing relations is a little expensive (will cause `build_arel`). It is enough to use `empty_scope?` to determine whether empty scope. --- .../lib/active_record/associations/preloader/through_association.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (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 7b37ec2f41..26bcdcbcc4 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -67,9 +67,7 @@ module ActiveRecord id_map end - def reset_association(owners, association_name, through_scope) - should_reset = through_scope != through_reflection.klass.unscoped - + def reset_association(owners, association_name, should_reset) # Don't cache the association - we would only be caching a subset if should_reset owners.each { |owner| @@ -112,7 +110,7 @@ module ActiveRecord end end - scope + scope unless scope.empty_scope? end end end -- cgit v1.2.3 From 5daf89f838abfd0c56d4a09bd352c987bc64ff9a Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 07:07:23 +0900 Subject: Don't pass `reflection_scope` to `preload_scope` if `reflection.scope` isn't given Related 2b5f5cdd7c1d95716de6a206b6d09ccbb006dc17. If `reflection.scope` isn't given, `reflection_scope` is always empty scope. It is unnecessary to merge it. --- .../lib/active_record/associations/preloader/through_association.rb | 4 +++- 1 file changed, 3 insertions(+), 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 26bcdcbcc4..53e9c14823 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -28,6 +28,8 @@ module ActiveRecord middle_records = through_records.flat_map(&:last) + reflection_scope = reflection_scope() if reflection.scope + preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope) @@ -49,7 +51,7 @@ module ActiveRecord }.compact # Respect the order on `reflection_scope` if it exists, else use the natural order. - if reflection_scope.values[:order].present? + if reflection_scope && !reflection_scope.order_values.empty? @id_map ||= id_to_index_map @preloaded_records rhs_records.sort_by { |rhs| @id_map[rhs] } else -- cgit v1.2.3 From 4bb4824ae711b7b550e8e33b428110575adaefac Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 07:45:34 +0900 Subject: Early return if `records.empty?` in `Preloader#preload` --- activerecord/lib/active_record/associations/preloader.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 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 62caf02a2c..e1754d4a19 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -91,13 +91,13 @@ module ActiveRecord # { author: :avatar } # [ :books, { author: :avatar } ] def preload(records, associations, preload_scope = nil) - records = Array.wrap(records).compact.uniq - associations = Array.wrap(associations) + records = records.compact if records.empty? [] else - associations.flat_map { |association| + records.uniq! + Array.wrap(associations).flat_map { |association| preloaders_on association, records, preload_scope } end -- cgit v1.2.3 From 9ac7dd47c5e847f7dbfb8d527ee2b917fa9fcd38 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 11 Sep 2017 09:41:08 +0900 Subject: Place class level `update`, `destroy`, and `delete` in `Persistence::ClassMethods` The docs are obviously for class level `update`, `destroy`, and `delete`. It should be placed in `Persistence::ClassMethods` rather than `Relation`. And also, these methods are not dependent on relation. So it is not needed to delegate to `all` (plus, `klass.find` is faster than `relation.find`). --- activerecord/lib/active_record/persistence.rb | 92 ++++++++++++++++++++++++++ activerecord/lib/active_record/querying.rb | 2 +- activerecord/lib/active_record/relation.rb | 94 --------------------------- 3 files changed, 93 insertions(+), 95 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index fbbf9082cc..4a5ccdc597 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -71,6 +71,98 @@ module ActiveRecord klass.allocate.init_with("attributes" => attributes, "new_record" => false, &block) end + # Updates an object (or multiple objects) and saves it to the database, if validations pass. + # The resulting object is returned whether the object was saved successfully to the database or not. + # + # ==== Parameters + # + # * +id+ - This should be the id or an array of ids to be updated. + # * +attributes+ - This should be a hash of attributes or an array of hashes. + # + # ==== Examples + # + # # Updates one record + # Person.update(15, user_name: "Samuel", group: "expert") + # + # # Updates multiple records + # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } + # Person.update(people.keys, people.values) + # + # # Updates multiple records from the result of a relation + # people = Person.where(group: "expert") + # people.update(group: "masters") + # + # Note: Updating a large number of records will run an UPDATE + # query for each record, which may cause a performance issue. + # When running callbacks is not needed for each record update, + # it is preferred to use {update_all}[rdoc-ref:Relation#update_all] + # for updating all records in a single query. + def update(id = :all, attributes) + if id.is_a?(Array) + id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } + elsif id == :all + all.each { |record| record.update(attributes) } + else + if ActiveRecord::Base === id + raise ArgumentError, + "You are passing an instance of ActiveRecord::Base to `update`. " \ + "Please pass the id of the object by calling `.id`." + end + object = find(id) + object.update(attributes) + object + end + end + + # Destroy an object (or multiple objects) that has the given id. The object is instantiated first, + # therefore all callbacks and filters are fired off before the object is deleted. This method is + # less efficient than #delete but allows cleanup methods and other actions to be run. + # + # This essentially finds the object (or multiple objects) with the given id, creates a new object + # from the attributes, and then calls destroy on it. + # + # ==== Parameters + # + # * +id+ - Can be either an Integer or an Array of Integers. + # + # ==== Examples + # + # # Destroy a single object + # Todo.destroy(1) + # + # # Destroy multiple objects + # todos = [1,2,3] + # Todo.destroy(todos) + def destroy(id) + if id.is_a?(Array) + id.map { |one_id| destroy(one_id) } + else + find(id).destroy + end + end + + # Deletes the row with a primary key matching the +id+ argument, using a + # SQL +DELETE+ statement, and returns the number of rows deleted. Active + # Record objects are not instantiated, so the object's callbacks are not + # executed, including any :dependent association options. + # + # You can delete multiple rows at once by passing an Array of ids. + # + # Note: Although it is often much faster than the alternative, #destroy, + # skipping callbacks might bypass business logic in your application + # that ensures referential integrity or performs other essential jobs. + # + # ==== Examples + # + # # Delete a single row + # Todo.delete(1) + # + # # Delete multiple rows + # Todo.delete([2,3,4]) + def delete(id_or_array) + where(primary_key => id_or_array).delete_all + end + private # Called by +instantiate+ to decide which class to use for a new # record instance. diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index f780538319..3996d5661f 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -7,7 +7,7 @@ module ActiveRecord delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all delegate :find_by, :find_by!, to: :all - delegate :destroy, :destroy_all, :delete, :delete_all, :update, :update_all, to: :all + delegate :destroy_all, :delete_all, :update_all, to: :all delegate :find_each, :find_in_batches, :in_batches, to: :all delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 42a9d8492b..f6825e67d7 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -376,50 +376,6 @@ module ActiveRecord @klass.connection.update stmt, "SQL" end - # Updates an object (or multiple objects) and saves it to the database, if validations pass. - # The resulting object is returned whether the object was saved successfully to the database or not. - # - # ==== Parameters - # - # * +id+ - This should be the id or an array of ids to be updated. - # * +attributes+ - This should be a hash of attributes or an array of hashes. - # - # ==== Examples - # - # # Updates one record - # Person.update(15, user_name: 'Samuel', group: 'expert') - # - # # Updates multiple records - # people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } } - # Person.update(people.keys, people.values) - # - # # Updates multiple records from the result of a relation - # people = Person.where(group: 'expert') - # people.update(group: 'masters') - # - # Note: Updating a large number of records will run an - # UPDATE query for each record, which may cause a performance - # issue. When running callbacks is not needed for each record update, - # it is preferred to use #update_all for updating all records - # in a single query. - def update(id = :all, attributes) - if id.is_a?(Array) - id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } - elsif id == :all - records.each { |record| record.update(attributes) } - else - if ActiveRecord::Base === id - raise ArgumentError, <<-MSG.squish - You are passing an instance of ActiveRecord::Base to `update`. - Please pass the id of the object by calling `.id`. - MSG - end - object = find(id) - object.update(attributes) - object - end - end - # Destroys the records by instantiating each # record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method. # Each object's callbacks are executed (including :dependent association options). @@ -440,33 +396,6 @@ module ActiveRecord records.each(&:destroy).tap { reset } end - # Destroy an object (or multiple objects) that has the given id. The object is instantiated first, - # therefore all callbacks and filters are fired off before the object is deleted. This method is - # less efficient than #delete but allows cleanup methods and other actions to be run. - # - # This essentially finds the object (or multiple objects) with the given id, creates a new object - # from the attributes, and then calls destroy on it. - # - # ==== Parameters - # - # * +id+ - Can be either an Integer or an Array of Integers. - # - # ==== Examples - # - # # Destroy a single object - # Todo.destroy(1) - # - # # Destroy multiple objects - # todos = [1,2,3] - # Todo.destroy(todos) - def destroy(id) - if id.is_a?(Array) - id.map { |one_id| destroy(one_id) } - else - find(id).destroy - end - end - # Deletes the records without instantiating the records # first, and hence not calling the {#destroy}[rdoc-ref:Persistence#destroy] # method nor invoking callbacks. @@ -509,29 +438,6 @@ module ActiveRecord affected end - # Deletes the row with a primary key matching the +id+ argument, using a - # SQL +DELETE+ statement, and returns the number of rows deleted. Active - # Record objects are not instantiated, so the object's callbacks are not - # executed, including any :dependent association options. - # - # You can delete multiple rows at once by passing an Array of ids. - # - # Note: Although it is often much faster than the alternative, - # #destroy, skipping callbacks might bypass business logic in - # your application that ensures referential integrity or performs other - # essential jobs. - # - # ==== Examples - # - # # Delete a single row - # Todo.delete(1) - # - # # Delete multiple rows - # Todo.delete([2,3,4]) - def delete(id_or_array) - where(primary_key => id_or_array).delete_all - end - # Causes the records to be loaded from the database if they have not # been loaded already. You can use this if for some reason you need # to explicitly load some records before actually using them. The -- cgit v1.2.3 From 358360198e623229495b7175a2b3a0fe96a543ac Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 04:29:49 +0900 Subject: Ensure returning affected objects for class level `update` and `destroy` Class level `update` and `destroy` are using `find` in the internal, so it will raise `RecordNotFound` if given ids cannot find an object even though the method already affect (update or destroy) to any objects. These methods should return affected objects even in that case. --- activerecord/lib/active_record/persistence.rb | 6 ++++-- 1 file changed, 4 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 4a5ccdc597..d1cb3783e7 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -99,7 +99,7 @@ module ActiveRecord # for updating all records in a single query. def update(id = :all, attributes) if id.is_a?(Array) - id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) } + id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }.compact elsif id == :all all.each { |record| record.update(attributes) } else @@ -112,6 +112,7 @@ module ActiveRecord object.update(attributes) object end + rescue RecordNotFound end # Destroy an object (or multiple objects) that has the given id. The object is instantiated first, @@ -135,10 +136,11 @@ module ActiveRecord # Todo.destroy(todos) def destroy(id) if id.is_a?(Array) - id.map { |one_id| destroy(one_id) } + id.map { |one_id| destroy(one_id) }.compact else find(id).destroy end + rescue RecordNotFound end # Deletes the row with a primary key matching the +id+ argument, using a -- cgit v1.2.3 From 37be1cb47755a3d0a02eed12ac8cfb377b172f2d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 08:22:04 +0900 Subject: `id` (primary key) is not only an integer [ci skip] --- 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 d1cb3783e7..b48a137a73 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -124,7 +124,7 @@ module ActiveRecord # # ==== Parameters # - # * +id+ - Can be either an Integer or an Array of Integers. + # * +id+ - This should be the id or an array of ids to be destroyed. # # ==== Examples # -- cgit v1.2.3 From 9b53f74d897d346f44caae7b0cffcd900f986259 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 04:55:51 +0900 Subject: Remove the code that swapping `scope` and `options` `options` is never assigned to `scope` as long as using splat hash. --- activerecord/lib/active_record/associations.rb | 6 +++--- activerecord/lib/active_record/associations/builder/association.rb | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index a61c0336db..ef26f4a20c 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1400,7 +1400,7 @@ module ActiveRecord # has_many :tags, as: :taggable # has_many :reports, -> { readonly } # has_many :subscribers, through: :subscriptions, source: :user - def has_many(name, scope = nil, options = {}, &extension) + def has_many(name, scope = nil, **options, &extension) reflection = Builder::HasMany.build(self, name, scope, options, &extension) Reflection.add_reflection self, name, reflection end @@ -1534,7 +1534,7 @@ module ActiveRecord # has_one :club, through: :membership # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable # has_one :credit_card, required: true - def has_one(name, scope = nil, options = {}) + def has_one(name, scope = nil, **options) reflection = Builder::HasOne.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end @@ -1678,7 +1678,7 @@ module ActiveRecord # belongs_to :company, touch: :employees_last_updated_at # belongs_to :user, optional: true # belongs_to :account, default: -> { company.account } - def belongs_to(name, scope = nil, options = {}) + def belongs_to(name, scope = nil, **options) reflection = Builder::BelongsTo.build(self, name, scope, options) Reflection.add_reflection self, name, reflection end diff --git a/activerecord/lib/active_record/associations/builder/association.rb b/activerecord/lib/active_record/associations/builder/association.rb index 496b16b58f..ca3032d967 100644 --- a/activerecord/lib/active_record/associations/builder/association.rb +++ b/activerecord/lib/active_record/associations/builder/association.rb @@ -38,11 +38,6 @@ module ActiveRecord::Associations::Builder # :nodoc: def self.create_reflection(model, name, scope, options, extension = nil) raise ArgumentError, "association names must be a Symbol" unless name.kind_of?(Symbol) - if scope.is_a?(Hash) - options = scope - scope = nil - end - validate_options(options) scope = build_scope(scope, extension) -- cgit v1.2.3 From a714117736530871528857c5adfb5520e387aca5 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 11 Sep 2017 05:24:37 +0900 Subject: Fix collided sequence name detection If collided named sequence already exists, newly created serial column will generate alternative sequence name. Fix sequence name detection to allow the alternative names. --- .../lib/active_record/connection_adapters/postgresql/column.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index 1b67cee24b..ff95fa4a0e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -10,8 +10,15 @@ module ActiveRecord def serial? return unless default_function - %r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function + if %r{\Anextval\('"?(?.+_(?seq\d*))"?'::regclass\)\z} =~ default_function + sequence_name_from_parts(table_name, name, suffix) == sequence_name + end end + + private + def sequence_name_from_parts(table_name, column_name, suffix) + "#{table_name}_#{column_name}_#{suffix}" + end end end end -- cgit v1.2.3 From 9b1115ea34e44fe772272ec58ccba7a9cbc0955a Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 11:03:59 +0900 Subject: Extract `associate_records_to_owner` to refactor `Preloader::Association` Since we have `Preloader#preload`, `Preloader::Association#preload` is a little confusing. And also, since the `preload` method is an abstract method, it is hard to read where `associated_records_by_owner` is called. This refactors `Preloader::Association` to ease to read where `associated_records_by_owner` is called. --- .../lib/active_record/associations/preloader/association.rb | 12 +++++++----- .../associations/preloader/collection_association.rb | 11 ++++------- .../associations/preloader/singular_association.rb | 11 +++-------- 3 files changed, 14 insertions(+), 20 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 9bb6a613e1..323f25e37e 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -17,11 +17,9 @@ module ActiveRecord end def run(preloader) - preload(preloader) - end - - def preload(preloader) - raise NotImplementedError + associated_records_by_owner(preloader).each do |owner, records| + associate_records_to_owner(owner, records) + end end # The name of the key on the associated records @@ -51,6 +49,10 @@ module ActiveRecord end end + def associate_records_to_owner(owner, records) + raise NotImplementedError + end + def owner_keys unless defined?(@owner_keys) @owner_keys = owners.map do |owner| diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb index fb920a642c..fc2029f54a 100644 --- a/activerecord/lib/active_record/associations/preloader/collection_association.rb +++ b/activerecord/lib/active_record/associations/preloader/collection_association.rb @@ -5,13 +5,10 @@ module ActiveRecord class Preloader class CollectionAssociation < Association #:nodoc: private - - def preload(preloader) - associated_records_by_owner(preloader).each do |owner, records| - association = owner.association(reflection.name) - association.loaded! - association.target.concat(records) - end + def associate_records_to_owner(owner, records) + association = owner.association(reflection.name) + association.loaded! + association.target.concat(records) end end end diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb index 266b5f6b1c..30a92411e3 100644 --- a/activerecord/lib/active_record/associations/preloader/singular_association.rb +++ b/activerecord/lib/active_record/associations/preloader/singular_association.rb @@ -5,14 +5,9 @@ module ActiveRecord class Preloader class SingularAssociation < Association #:nodoc: private - - def preload(preloader) - associated_records_by_owner(preloader).each do |owner, associated_records| - record = associated_records.first - - association = owner.association(reflection.name) - association.target = record - end + def associate_records_to_owner(owner, records) + association = owner.association(reflection.name) + association.target = records.first end end end -- cgit v1.2.3 From a1aa86b99e298c4a2b7a2e5da7a1bb9d8ba65a3b Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 11:42:29 +0900 Subject: The name of the key on the owner is abstracted as `reflection.join_foreign_key` --- .../lib/active_record/associations/preloader/association.rb | 10 +++++----- .../lib/active_record/associations/preloader/belongs_to.rb | 4 ---- .../lib/active_record/associations/preloader/has_many.rb | 4 ---- .../lib/active_record/associations/preloader/has_one.rb | 4 ---- 4 files changed, 5 insertions(+), 17 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 323f25e37e..1e2d6c4f1e 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -27,16 +27,16 @@ module ActiveRecord raise NotImplementedError end - # The name of the key on the model which declares the association - def owner_key_name - raise NotImplementedError - end - private def options reflection.options end + # The name of the key on the model which declares the association + def owner_key_name + reflection.join_foreign_key + end + def associated_records_by_owner(preloader) records = load_records do |record| owner = owners_by_key[convert_key(record[association_key_name])] diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb index ae9695f26a..62df4f5530 100644 --- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb +++ b/activerecord/lib/active_record/associations/preloader/belongs_to.rb @@ -7,10 +7,6 @@ module ActiveRecord def association_key_name options[:primary_key] || klass && klass.primary_key end - - def owner_key_name - reflection.foreign_key - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb index 29a1ce099d..9ff0aa24ab 100644 --- a/activerecord/lib/active_record/associations/preloader/has_many.rb +++ b/activerecord/lib/active_record/associations/preloader/has_many.rb @@ -7,10 +7,6 @@ module ActiveRecord def association_key_name reflection.foreign_key end - - def owner_key_name - reflection.active_record_primary_key - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb index d87abf630f..e58fa351b0 100644 --- a/activerecord/lib/active_record/associations/preloader/has_one.rb +++ b/activerecord/lib/active_record/associations/preloader/has_one.rb @@ -7,10 +7,6 @@ module ActiveRecord def association_key_name reflection.foreign_key end - - def owner_key_name - reflection.active_record_primary_key - end end end end -- cgit v1.2.3 From dae16f8cd5908a0accfaf280d89b47b757c4bf2d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 12:14:20 +0900 Subject: The name of the key on the associated record is abstracted as `reflection.join_primary_key` --- .../associations/preloader/association.rb | 10 +++++----- .../active_record/associations/preloader/belongs_to.rb | 3 --- .../active_record/associations/preloader/has_many.rb | 3 --- .../active_record/associations/preloader/has_one.rb | 3 --- activerecord/lib/active_record/reflection.rb | 18 +++++++++--------- 5 files changed, 14 insertions(+), 23 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 1e2d6c4f1e..8101c5604b 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -22,16 +22,16 @@ module ActiveRecord end end - # The name of the key on the associated records - def association_key_name - raise NotImplementedError - end - private def options reflection.options end + # The name of the key on the associated records + def association_key_name + reflection.join_primary_key(klass) + end + # The name of the key on the model which declares the association def owner_key_name reflection.join_foreign_key diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb index 62df4f5530..a8e3340b23 100644 --- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb +++ b/activerecord/lib/active_record/associations/preloader/belongs_to.rb @@ -4,9 +4,6 @@ module ActiveRecord module Associations class Preloader class BelongsTo < SingularAssociation #:nodoc: - def association_key_name - options[:primary_key] || klass && klass.primary_key - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb index 9ff0aa24ab..72f55bc43f 100644 --- a/activerecord/lib/active_record/associations/preloader/has_many.rb +++ b/activerecord/lib/active_record/associations/preloader/has_many.rb @@ -4,9 +4,6 @@ module ActiveRecord module Associations class Preloader class HasMany < CollectionAssociation #:nodoc: - def association_key_name - reflection.foreign_key - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb index e58fa351b0..e339b65fb5 100644 --- a/activerecord/lib/active_record/associations/preloader/has_one.rb +++ b/activerecord/lib/active_record/associations/preloader/has_one.rb @@ -4,9 +4,6 @@ module ActiveRecord module Associations class Preloader class HasOne < SingularAssociation #:nodoc: - def association_key_name - reflection.foreign_key - end end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 889e24dd1a..82ab2415e1 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -292,13 +292,17 @@ module ActiveRecord end def get_join_keys(association_klass) - JoinKeys.new(join_pk(association_klass), join_foreign_key) + JoinKeys.new(join_primary_key(association_klass), join_foreign_key) end def build_scope(table, predicate_builder = predicate_builder(table)) Relation.create(klass, table, predicate_builder) end + def join_primary_key(_) + foreign_key + end + def join_foreign_key active_record_primary_key end @@ -313,10 +317,6 @@ module ActiveRecord PredicateBuilder.new(TableMetadata.new(klass, table)) end - def join_pk(_) - foreign_key - end - def primary_key(klass) klass.primary_key || raise(UnknownPrimaryKey.new(klass)) end @@ -736,6 +736,10 @@ module ActiveRecord end end + def join_primary_key(klass) + polymorphic? ? association_primary_key(klass) : association_primary_key + end + def join_foreign_key foreign_key end @@ -745,10 +749,6 @@ module ActiveRecord def calculate_constructable(macro, options) !polymorphic? end - - def join_pk(klass) - polymorphic? ? association_primary_key(klass) : association_primary_key - end end class HasAndBelongsToManyReflection < AssociationReflection # :nodoc: -- cgit v1.2.3 From 8cde75813d54b92a894fb47d296929e8a6d26ba1 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 18 Sep 2017 12:29:44 +0900 Subject: Remove unused delegation to `reflection.options` in `Preloader::Association` --- activerecord/lib/active_record/associations/preloader/association.rb | 4 ---- .../lib/active_record/associations/preloader/through_association.rb | 1 + 2 files changed, 1 insertion(+), 4 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 8101c5604b..fe696e0d6e 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -23,10 +23,6 @@ module ActiveRecord end private - def options - reflection.options - end - # The name of the key on the associated records def association_key_name reflection.join_primary_key(klass) diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 53e9c14823..fa32cc5553 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -80,6 +80,7 @@ module ActiveRecord def through_scope scope = through_reflection.klass.unscoped + options = reflection.options if options[:source_type] scope.where! reflection.foreign_type => options[:source_type] -- cgit v1.2.3 From 7bab6b8cd950be79c040377b43b6ed6030fb98f4 Mon Sep 17 00:00:00 2001 From: Yoshiyuki Kinjo Date: Wed, 20 Sep 2017 00:09:17 +0900 Subject: Add :comment option for add_column [ci skip] --- .../lib/active_record/connection_adapters/abstract/schema_statements.rb | 2 ++ 1 file changed, 2 insertions(+) (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 f57c7a5d4d..c9607df28c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -522,6 +522,8 @@ module ActiveRecord # Specifies the precision for the :decimal and :numeric columns. # * :scale - # Specifies the scale for the :decimal and :numeric columns. + # * :comment - + # Specifies the comment for the column. This option is ignored by some backends. # # Note: The precision is the total number of significant digits, # and the scale is the number of digits that can be stored following -- cgit v1.2.3 From 0c8bed9f848446a6876c27781a38badb2c916be4 Mon Sep 17 00:00:00 2001 From: Yuki Masutomi Date: Tue, 19 Sep 2017 18:51:30 +0900 Subject: make create_join_table compatible. --- activerecord/lib/active_record/migration/compatibility.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 784292f3f9..87c1c58aff 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -71,6 +71,21 @@ module ActiveRecord end end + def create_join_table(table_1, table_2, column_options: {}, **options) + column_options.reverse_merge!(type: :integer) + + if block_given? + super(table_1, table_2, column_options: column_options, **options) do |t| + class << t + prepend TableDefinition + end + yield t + end + else + super + end + end + def add_reference(table_name, ref_name, **options) super(table_name, ref_name, type: :integer, **options) end -- cgit v1.2.3 From fda9df7b4e328ed2bfc3dad05899ef6269495688 Mon Sep 17 00:00:00 2001 From: Jeremy Green Date: Sat, 16 Sep 2017 10:56:04 -0600 Subject: Update payload names for `sql.active_record` to be more descriptive. Fixes #30586. --- activerecord/lib/active_record/relation.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 42a9d8492b..cf0d14f4e1 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -63,7 +63,7 @@ module ActiveRecord @klass.connection.insert( im, - "SQL", + "#{@klass} Create", primary_key || false, primary_key_value, nil, @@ -86,7 +86,7 @@ module ActiveRecord @klass.connection.update( um, - "SQL", + "#{@klass} Update", ) end @@ -373,7 +373,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - @klass.connection.update stmt, "SQL" + @klass.connection.update stmt, "#{@klass} Update All" end # Updates an object (or multiple objects) and saves it to the database, if validations pass. @@ -503,7 +503,7 @@ module ActiveRecord stmt.wheres = arel.constraints end - affected = @klass.connection.delete(stmt, "SQL") + affected = @klass.connection.delete(stmt, "#{@klass} Destroy") reset affected -- cgit v1.2.3 From 611f2b2911197946bd81d5d5fb017e7133d77d2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mehmet=20Emin=20=C4=B0NA=C3=87?= Date: Tue, 15 Mar 2016 03:01:09 +0200 Subject: Use algorithm while removing index with db:rollback Closes #24190 --- activerecord/lib/active_record/migration/command_recorder.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index a3a5e0fa16..ac7d506fd1 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -161,8 +161,8 @@ module ActiveRecord table, columns, options = *args options ||= {} - index_name = options[:name] - options_hash = index_name ? { name: index_name } : { column: columns } + options_hash = options.slice(:name, :algorithm) + options_hash[:column] = columns if !options_hash[:name] [:remove_index, [table, options_hash]] end -- cgit v1.2.3 From 72a22fa9299d41b6d088bb4a0c3c6807f5bed07d Mon Sep 17 00:00:00 2001 From: Alecs Popa Date: Thu, 21 Sep 2017 19:55:16 +0300 Subject: Implement change_table_comment and change_column_comment for MySql Adapter --- .../connection_adapters/abstract_mysql_adapter.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'activerecord/lib/active_record') 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 7cd086084a..3dad43ced8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -311,6 +311,11 @@ module ActiveRecord execute("ALTER TABLE #{quote_table_name(table_name)} #{sqls}") end + def change_table_comment(table_name, comment) #:nodoc: + comment = "" if comment.nil? + execute("ALTER TABLE #{quote_table_name(table_name)} COMMENT #{quote(comment)}") + end + # Renames a table. # # Example: @@ -365,6 +370,11 @@ module ActiveRecord change_column table_name, column_name, column.sql_type, null: null end + def change_column_comment(table_name, column_name, comment) #:nodoc: + column = column_for(table_name, column_name) + change_column table_name, column_name, column.sql_type, comment: comment + end + def change_column(table_name, column_name, type, options = {}) #:nodoc: execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}") end -- cgit v1.2.3 From 6fd81d2d8ee77053c10a998c77d3799dfd3c51c0 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 22 Sep 2017 23:04:54 +0900 Subject: Return nil if table comment is blank --- .../lib/active_record/connection_adapters/abstract_mysql_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_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 3dad43ced8..6e35bb0bbe 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -284,7 +284,7 @@ module ActiveRecord def table_comment(table_name) # :nodoc: scope = quoted_scope(table_name) - query_value(<<-SQL.strip_heredoc, "SCHEMA") + query_value(<<-SQL.strip_heredoc, "SCHEMA").presence SELECT table_comment FROM information_schema.tables WHERE table_schema = #{scope[:schema]} -- cgit v1.2.3 From 18346bd52ab84fca996f4dfc1dd110ab2edf1f46 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 22 Sep 2017 23:09:53 +0900 Subject: `index_name` should be quoted --- .../lib/active_record/connection_adapters/abstract_mysql_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_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 6e35bb0bbe..46ab8f3edd 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -726,7 +726,7 @@ module ActiveRecord def remove_index_sql(table_name, options = {}) index_name = index_name_for_remove(table_name, options) - "DROP INDEX #{index_name}" + "DROP INDEX #{quote_column_name(index_name)}" end def add_timestamps_sql(table_name, options = {}) -- cgit v1.2.3 From b798c5b796f58a9286e1a037db4453750cd26f95 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 23 Sep 2017 05:55:48 +0900 Subject: Prevent extra `column_for` for `change_column_{default,null,comment}` `change_column_{default,null,comment}` in mysql2 adapter are passing `column.sql_type` as `type` to `change_column` to intend keeping previous type. But `column_for` requires extra query, so use passing `nil` to `type` explicitly in the internal for the purpose. --- .../connection_adapters/abstract_mysql_adapter.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) (limited to 'activerecord/lib/active_record') 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 46ab8f3edd..ae991d3d79 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -356,23 +356,19 @@ module ActiveRecord def change_column_default(table_name, column_name, default_or_changes) #:nodoc: default = extract_new_default_value(default_or_changes) - column = column_for(table_name, column_name) - change_column table_name, column_name, column.sql_type, default: default + change_column table_name, column_name, nil, default: default end def change_column_null(table_name, column_name, null, default = nil) #:nodoc: - column = column_for(table_name, column_name) - unless null || default.nil? execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL") end - change_column table_name, column_name, column.sql_type, null: null + change_column table_name, column_name, nil, null: null end def change_column_comment(table_name, column_name, comment) #:nodoc: - column = column_for(table_name, column_name) - change_column table_name, column_name, column.sql_type, comment: comment + change_column table_name, column_name, nil, comment: comment end def change_column(table_name, column_name, type, options = {}) #:nodoc: @@ -678,6 +674,7 @@ module ActiveRecord def change_column_sql(table_name, column_name, type, options = {}) column = column_for(table_name, column_name) + type ||= column.sql_type unless options.key?(:default) options[:default] = column.default -- cgit v1.2.3 From cfc461c3f8ee3a5f35fb05c75e492176c4d8854a Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Fri, 22 Sep 2017 21:33:14 -0400 Subject: Ensure `1 AS one` for SQL Server with calculations. --- 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 42d43224fa..0889d61c92 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -391,7 +391,7 @@ module ActiveRecord def build_count_subquery(relation, column_name, distinct) relation.select_values = [ if column_name == :all - distinct ? table[Arel.star] : Arel.sql("1") + distinct ? table[Arel.star] : Arel.sql(FinderMethods::ONE_AS_ONE) else column_alias = Arel.sql("count_column") aggregate_column(column_name).as(column_alias) -- cgit v1.2.3 From dfe6939e16bdcf854d32b61933d55fe313ac49fb Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 23 Sep 2017 13:18:11 +0900 Subject: Adding legacy primary key should be compatible Currently implicit legacy primary key is compatible, but adding explicit legacy primary key is not compatible. It should also be fixed. Fixes #30664. --- .../lib/active_record/migration/compatibility.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 87c1c58aff..92993d64a7 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -20,6 +20,11 @@ module ActiveRecord class V5_0 < V5_1 module TableDefinition + def primary_key(name, type = :primary_key, **options) + type = :integer if type == :primary_key + super + end + def references(*args, **options) super(*args, type: :integer, **options) end @@ -86,6 +91,20 @@ module ActiveRecord end end + def add_column(table_name, column_name, type, options = {}) + if type == :primary_key + case adapter_name + when "PostgreSQL" + type = :serial + when "Mysql2" + type = :integer + options[:auto_increment] = true + end + options[:primary_key] = true + end + super + end + def add_reference(table_name, ref_name, **options) super(table_name, ref_name, type: :integer, **options) end -- cgit v1.2.3 From 202d6578f44ab03ee89ed57b73a97d513fc5a008 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 23 Sep 2017 15:34:56 +0900 Subject: Move integer-like primary key normalization to `new_column_definition` Currently the normalization only exists in `primary_key` shorthand. It should be moved to `new_column_definition` to also affect to `add_column` with primary key. --- .../abstract/schema_definitions.rb | 4 ++++ .../mysql/schema_definitions.rb | 9 ++++----- .../postgresql/schema_definitions.rb | 19 ++++++++++++------- .../sqlite3/schema_definitions.rb | 22 +++++++--------------- .../sqlite3/schema_statements.rb | 4 ---- .../lib/active_record/migration/compatibility.rb | 8 +------- 6 files changed, 28 insertions(+), 38 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 788a455773..1041db0b8f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -410,6 +410,10 @@ module ActiveRecord def aliased_types(name, fallback) "timestamp" == name ? :datetime : fallback end + + def integer_like_primary_key?(type, options) + options[:primary_key] && [:integer, :bigint].include?(type) && !options.key?(:default) + end end class AlterTable # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb index b22a2e4da7..eff96af87a 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -4,11 +4,6 @@ module ActiveRecord module ConnectionAdapters module MySQL module ColumnMethods - def primary_key(name, type = :primary_key, **options) - options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default) - super - end - def blob(*args, **options) args.each { |name| column(name, :blob, options) } end @@ -62,6 +57,10 @@ module ActiveRecord include ColumnMethods def new_column_definition(name, type, **options) # :nodoc: + if integer_like_primary_key?(type, options) + options[:auto_increment] = true + end + case type when :virtual type = options[:type] diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index f1489e4d69..cb13f9fec1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -44,15 +44,8 @@ module ActiveRecord # a record (as primary keys cannot be +nil+). This might be done via the # +SecureRandom.uuid+ method and a +before_save+ callback, for instance. def primary_key(name, type = :primary_key, **options) - options[:auto_increment] = true if [:integer, :bigint].include?(type) && !options.key?(:default) if type == :uuid options[:default] = options.fetch(:default, "gen_random_uuid()") - elsif options.delete(:auto_increment) == true && %i(integer bigint).include?(type) - type = if type == :bigint || options[:limit] == 8 - :bigserial - else - :serial - end end super @@ -185,6 +178,18 @@ module ActiveRecord class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods + + def new_column_definition(name, type, **options) # :nodoc: + if integer_like_primary_key?(type, options) + type = if type == :bigint || options[:limit] == 8 + :bigserial + else + :serial + end + end + + super + end end class Table < ActiveRecord::ConnectionAdapters::Table diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb index 501f17dbad..2010de1ce2 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -3,27 +3,19 @@ module ActiveRecord module ConnectionAdapters module SQLite3 - module ColumnMethods - def primary_key(name, type = :primary_key, **options) - if %i(integer bigint).include?(type) && (options.delete(:auto_increment) == true || !options.key?(:default)) - type = :primary_key - end - - super - end - end - class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition - include ColumnMethods - def references(*args, **options) super(*args, type: :integer, **options) end alias :belongs_to :references - end - class Table < ActiveRecord::ConnectionAdapters::Table - include ColumnMethods + def new_column_definition(name, type, **options) # :nodoc: + if integer_like_primary_key?(type, options) + type = :primary_key + end + + super + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb index a512702b7b..f4e55147df 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -39,10 +39,6 @@ module ActiveRecord end end - def update_table_definition(table_name, base) - SQLite3::Table.new(table_name, base) - end - def create_schema_dumper(options) SQLite3::SchemaDumper.create(self, options) end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 92993d64a7..502cef2e20 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -93,13 +93,7 @@ module ActiveRecord def add_column(table_name, column_name, type, options = {}) if type == :primary_key - case adapter_name - when "PostgreSQL" - type = :serial - when "Mysql2" - type = :integer - options[:auto_increment] = true - end + type = :integer options[:primary_key] = true end super -- cgit v1.2.3 From 8f037a5ab929f929fe0897edc9ecadf901c32e56 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 25 Sep 2017 08:27:41 +0900 Subject: Extract `integer_like_primary_key_type` to ease to handle it for adapters --- .../connection_adapters/abstract/schema_definitions.rb | 7 +++++++ .../connection_adapters/mysql/schema_definitions.rb | 10 +++++----- .../connection_adapters/postgresql/schema_definitions.rb | 9 +++------ .../connection_adapters/sqlite3/schema_definitions.rb | 9 +++------ 4 files changed, 18 insertions(+), 17 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 1041db0b8f..be2f625d74 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -396,6 +396,9 @@ module ActiveRecord alias :belongs_to :references def new_column_definition(name, type, **options) # :nodoc: + if integer_like_primary_key?(type, options) + type = integer_like_primary_key_type(type, options) + end type = aliased_types(type.to_s, type) options[:primary_key] ||= type == :primary_key options[:null] = false if options[:primary_key] @@ -414,6 +417,10 @@ module ActiveRecord def integer_like_primary_key?(type, options) options[:primary_key] && [:integer, :bigint].include?(type) && !options.key?(:default) end + + def integer_like_primary_key_type(type, options) + type + end end class AlterTable # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb index eff96af87a..da25e4863c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -57,17 +57,12 @@ module ActiveRecord include ColumnMethods def new_column_definition(name, type, **options) # :nodoc: - if integer_like_primary_key?(type, options) - options[:auto_increment] = true - end - case type when :virtual type = options[:type] when :primary_key type = :integer options[:limit] ||= 8 - options[:auto_increment] = true options[:primary_key] = true when /\Aunsigned_(?.+)\z/ type = $~[:type].to_sym @@ -81,6 +76,11 @@ module ActiveRecord def aliased_types(name, fallback) fallback end + + def integer_like_primary_key_type(type, options) + options[:auto_increment] = true + type + end end class Table < ActiveRecord::ConnectionAdapters::Table diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index cb13f9fec1..75622eb304 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -179,17 +179,14 @@ module ActiveRecord class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition include ColumnMethods - def new_column_definition(name, type, **options) # :nodoc: - if integer_like_primary_key?(type, options) - type = if type == :bigint || options[:limit] == 8 + private + def integer_like_primary_key_type(type, options) + if type == :bigint || options[:limit] == 8 :bigserial else :serial end end - - super - end end class Table < ActiveRecord::ConnectionAdapters::Table diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb index 2010de1ce2..c9855019c1 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb @@ -9,13 +9,10 @@ module ActiveRecord end alias :belongs_to :references - def new_column_definition(name, type, **options) # :nodoc: - if integer_like_primary_key?(type, options) - type = :primary_key + private + def integer_like_primary_key_type(type, options) + :primary_key end - - super - end end end end -- cgit v1.2.3 From 89711d99b08b5ace8aa5b7394e2ccedb302d118f Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Mon, 25 Sep 2017 20:29:43 +0800 Subject: PERF: Restore memoization when preloading associations. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Benchmark Script ``` require 'active_record' require 'benchmark/ips' require 'ruby-prof' require 'memory_profiler' require 'byebug' ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL')) ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table :users, force: true do |t| t.string :name, :email t.integer :topic_id t.timestamps null: false end create_table :topics, force: true do |t| t.string :title t.timestamps null: false end end attributes = { name: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', email: 'foobar@email.com' } class Topic < ActiveRecord::Base has_many :users end class User < ActiveRecord::Base belongs_to :topic end 100.times do User.create!(attributes) end users = User.first(50) Topic.create!(title: 'This is a topic', users: users) Benchmark.ips do |x| x.config(time: 10, warmup: 5) x.report("preload") do User.includes(:topic).all.to_a end end ``` Before ``` Calculating ------------------------------------- preload 26.000 i/100ms ------------------------------------------------- preload 265.347 (± 3.0%) i/s - 2.652k ``` After ``` Calculating ------------------------------------- preload 39.000 i/100ms ------------------------------------------------- preload 406.053 (± 1.7%) i/s - 4.095k ``` --- activerecord/lib/active_record/reflection.rb | 11 +++++------ 1 file changed, 5 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 82ab2415e1..032df925bf 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -425,7 +425,6 @@ module ActiveRecord def initialize(name, scope, options, active_record) super - @automatic_inverse_of = nil @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type") @foreign_type = options[:foreign_type] || "#{name}_type" @constructable = calculate_constructable(macro, options) @@ -609,12 +608,14 @@ module ActiveRecord # If it cannot find a suitable inverse association name, it returns # +nil+. def inverse_name - options.fetch(:inverse_of) do - @automatic_inverse_of ||= automatic_inverse_of + unless defined?(@inverse_name) + @inverse_name = options.fetch(:inverse_of) { automatic_inverse_of } end + + @inverse_name end - # returns either false or the inverse association name that it finds. + # returns either +nil+ or the inverse association name that it finds. def automatic_inverse_of if can_find_inverse_of_automatically?(self) inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym @@ -631,8 +632,6 @@ module ActiveRecord return inverse_name end end - - false end # Checks if the inverse reflection that is returned from the -- cgit v1.2.3 From ff56fdb08d643b8d9892763c4b9731e0391260fd Mon Sep 17 00:00:00 2001 From: Francesco Rodriguez Date: Mon, 25 Sep 2017 14:06:15 +0200 Subject: Preload digest/sha2 to avoid thread safe error. I got this error in production using Puma in multi-threaded mode: ``` RuntimeError: Digest::Base cannot be directly inherited in Ruby from active_support/security_utils.rb:23:in `variable_size_secure_compare' from active_support/security_utils.rb:23:in `hexdigest' from active_support/security_utils.rb:23:in `digest' ``` Looks like Digest uses const_missing to load Digest::SHA256 (https://github.com/ruby/ruby/blob/trunk/ext/digest/lib/digest.rb#L8) - https://bugs.ruby-lang.org/issues/9494 - https://github.com/ruby/ruby/commit/c02fa39463a0c6bf698b01bc610135604aca2ff4 --- .../lib/active_record/connection_adapters/abstract/schema_statements.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/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index c9607df28c..4f0c1890be 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -2,7 +2,7 @@ require_relative "../../migration/join_table" require "active_support/core_ext/string/access" -require "digest" +require "digest/sha2" module ActiveRecord module ConnectionAdapters # :nodoc: -- cgit v1.2.3 From e03c9066ff83cac409281d6fc5e2377493009364 Mon Sep 17 00:00:00 2001 From: Guo Xiang Tan Date: Fri, 8 Sep 2017 22:30:39 +0800 Subject: PERF: Partially recover some performance when preloading. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Benchmark Script: ``` require 'active_record' require 'benchmark/ips' ActiveRecord::Base.establish_connection(ENV.fetch('DATABASE_URL')) ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table :users, force: true do |t| t.string :name, :email t.integer :topic_id t.timestamps null: false end create_table :topics, force: true do |t| t.string :title t.timestamps null: false end end attributes = { name: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', email: 'foobar@email.com' } class Topic < ActiveRecord::Base has_many :users end class User < ActiveRecord::Base belongs_to :topic end 100.times do User.create!(attributes) end users = User.first(50) Topic.create!(title: 'This is a topic', users: users) Benchmark.ips do |x| x.config(time: 10, warmup: 5) x.report("preload") do User.includes(:topic).all.to_a end end ``` Before: ``` Calculating ------------------------------------- preload 40.000 i/100ms ------------------------------------------------- preload 407.962 (± 1.5%) i/s - 4.080k ``` After: ``` alculating ------------------------------------- preload 43.000 i/100ms ------------------------------------------------- preload 427.567 (± 1.6%) i/s - 4.300k ``` --- .../lib/active_record/associations/preloader/association.rb | 12 +++--------- .../lib/active_record/attribute_methods/primary_key.rb | 6 ++++-- activerecord/lib/active_record/attribute_methods/read.rb | 5 +++-- activerecord/lib/active_record/attribute_methods/write.rb | 5 +++-- 4 files changed, 13 insertions(+), 15 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 fe696e0d6e..607d376a08 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -50,20 +50,14 @@ module ActiveRecord end def owner_keys - unless defined?(@owner_keys) - @owner_keys = owners.map do |owner| - owner[owner_key_name] - end - @owner_keys.uniq! - @owner_keys.compact! - end - @owner_keys + @owner_keys ||= owners_by_key.keys end def owners_by_key unless defined?(@owners_by_key) @owners_by_key = owners.each_with_object({}) do |owner, h| - h[convert_key(owner[owner_key_name])] = owner + key = convert_key(owner[owner_key_name]) + h[key] = owner if key end end @owners_by_key diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 63c059e291..d8fc046e10 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -17,13 +17,15 @@ module ActiveRecord # Returns the primary key value. def id sync_with_transaction_state - _read_attribute(self.class.primary_key) if self.class.primary_key + primary_key = self.class.primary_key + _read_attribute(primary_key) if primary_key end # Sets the primary key value. def id=(value) sync_with_transaction_state - _write_attribute(self.class.primary_key, value) if self.class.primary_key + primary_key = self.class.primary_key + _write_attribute(primary_key, value) if primary_key end # Queries the primary key value. diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index b070235684..4077250583 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -58,8 +58,9 @@ module ActiveRecord attr_name.to_s end - name = self.class.primary_key if name == "id".freeze && self.class.primary_key - sync_with_transaction_state if name == self.class.primary_key + primary_key = self.class.primary_key + name = primary_key if name == "id".freeze && primary_key + sync_with_transaction_state if name == primary_key _read_attribute(name, &block) end diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index 37891ce2ef..bb0ec6a8c3 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -39,8 +39,9 @@ module ActiveRecord attr_name.to_s end - name = self.class.primary_key if name == "id".freeze && self.class.primary_key - sync_with_transaction_state if name == self.class.primary_key + primary_key = self.class.primary_key + name = primary_key if name == "id".freeze && primary_key + sync_with_transaction_state if name == primary_key _write_attribute(name, value) end -- cgit v1.2.3 From 9cf7e3494f5bd34f1382c1ff4ea3d811a4972ae2 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Tue, 26 Sep 2017 13:22:12 -0600 Subject: Treat `Set` as an `Array` in `Relation#where` I do not want to set the expectation that any enumerable object should behave this way, but this case in particular comes up frequently enough that I'm caving on this one. Fixes #30684. --- activerecord/lib/active_record/relation/predicate_builder.rb | 1 + 1 file changed, 1 insertion(+) (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 5c42414072..be4b169f67 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -13,6 +13,7 @@ module ActiveRecord register_handler(Range, RangeHandler.new(self)) register_handler(Relation, RelationHandler.new) register_handler(Array, ArrayHandler.new(self)) + register_handler(Set, ArrayHandler.new(self)) end def build_from_hash(attributes) -- cgit v1.2.3 From 8d3bf8b66c783419600ad815db8a5405fe573b0b Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 27 Sep 2017 06:18:23 +0900 Subject: Don't generate `foreign_type` if `options[:polymorphic]` is not given Because the reflection doesn't have `foreign_type` unless the association is a polymorphic association. --- activerecord/lib/active_record/reflection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 032df925bf..97adfb4352 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -426,7 +426,7 @@ module ActiveRecord def initialize(name, scope, options, active_record) super @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type") - @foreign_type = options[:foreign_type] || "#{name}_type" + @foreign_type = options[:polymorphic] && (options[:foreign_type] || "#{name}_type") @constructable = calculate_constructable(macro, options) @association_scope_cache = Concurrent::Map.new -- cgit v1.2.3 From 51b6c3429f41ebbc571b8fcbe7c21d3131b832d8 Mon Sep 17 00:00:00 2001 From: Thomas Cannon Date: Tue, 26 Sep 2017 10:37:32 -0400 Subject: `Postgres::OID::Range` serializes to a `Range`, quote in `Quoting` PostgreSQL 9.1+ introduced range types, and Rails added support for using this datatype in ActiveRecord. However, the serialization of `PostgreSQL::OID::Range` was incomplete, because it did not properly quote the bounds that make up the range. A clear example of this is a `tsrange`. Normally, ActiveRecord quotes Date/Time objects to include the milliseconds. However, the way `PostgreSQL::OID::Range` serialized its bounds, the milliseconds were dropped. This meant that the value was incomplete and not equal to the submitted value. An example of normal timestamps vs. a `tsrange`. Note how the bounds for the range do not include their milliseconds (they were present in the ruby Range): UPDATE "iterations" SET "updated_at" = $1, "range" = $2 WHERE "iterations"."id" = $3 [["updated_at", "2017-09-23 17:07:01.304864"], ["range", "[2017-09-23 00:00:00 UTC,2017-09-23 23:59:59 UTC]"], ["id", 1234]] `PostgreSQL::OID::Range` serialized the range by interpolating a string for the range, which works for most cases, but does not work for timestamps: def serialize(value) if value.is_a?(::Range) from = type_cast_single_for_database(value.begin) to = type_cast_single_for_database(value.end) "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}" else super end end (byebug) from = type_cast_single_for_database(value.begin) 2010-01-01 13:30:00 UTC (byebug) to = type_cast_single_for_database(value.end) 2011-02-02 19:30:00 UTC (byebug) "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}" "[2010-01-01 13:30:00 UTC,2011-02-02 19:30:00 UTC)" @sgrif (the original implementer for Postgres Range support) provided some feedback about where the quoting should occur: Yeah, quoting at all is definitely wrong here. I'm not sure what I was thinking in 02579b5, but what this is doing is definitely in the wrong place. It should probably just be returning a range of subtype.serialize(value.begin) and subtype.serialize(value.end), and letting the adapter handle the rest. `Postgres::OID::Range` now returns a `Range` object, and `ActiveRecord::ConnectionAdapters::PostgreSQL::Quoting` can now encode and quote a `Range`: def encode_range(range) "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}" end ... encode_range(range) #=> "['2010-01-01 13:30:00.670277','2011-02-02 19:30:00.745125')" This commit includes tests to make sure the milliseconds are preserved in `tsrange` and `tstzrange` columns --- .../lib/active_record/connection_adapters/postgresql/oid/range.rb | 2 +- .../lib/active_record/connection_adapters/postgresql/quoting.rb | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) (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 7d5d7d91e6..a89aa5ea09 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/range.rb @@ -35,7 +35,7 @@ module ActiveRecord if value.is_a?(::Range) from = type_cast_single_for_database(value.begin) to = type_cast_single_for_database(value.end) - "[#{from},#{to}#{value.exclude_end? ? ')' : ']'}" + ::Range.new(from, to, value.exclude_end?) else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index a0a22ba0f1..9fdeab06c1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -101,6 +101,8 @@ module ActiveRecord end when OID::Array::Data _quote(encode_array(value)) + when Range + _quote(encode_range(value)) else super end @@ -117,6 +119,8 @@ module ActiveRecord value.to_s when OID::Array::Data encode_array(value) + when Range + encode_range(value) else super end @@ -133,6 +137,10 @@ module ActiveRecord result end + def encode_range(range) + "[#{type_cast(range.first)},#{type_cast(range.last)}#{range.exclude_end? ? ')' : ']'}" + end + def determine_encoding_of_strings_in_array(value) case value when ::Array then determine_encoding_of_strings_in_array(value.first) -- cgit v1.2.3 From c83f28fffe8b18ee75822394a11f446c05f692fd Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 30 Sep 2017 20:07:09 +0900 Subject: Ensure `AliasTracker` respects a custom table name --- activerecord/lib/active_record/associations/association_scope.rb | 2 +- activerecord/lib/active_record/associations/join_dependency.rb | 2 +- 2 files changed, 2 insertions(+), 2 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 b2dc044312..6118cef913 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -23,7 +23,7 @@ module ActiveRecord reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.create(klass.connection, klass.table_name) + alias_tracker = AliasTracker.create(klass.connection, scope.table.name) chain = get_chain(reflection, association, alias_tracker) scope.extending! reflection.extensions diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 77b3d11b47..23741b2f6a 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -91,7 +91,7 @@ module ActiveRecord # joins # => [] # def initialize(base, table, associations, joins, eager_loading: true) - @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins) + @alias_tracker = AliasTracker.create_with_joins(base.connection, table.name, joins) @eager_loading = eager_loading tree = self.class.make_tree associations @join_root = JoinBase.new(base, table, build(tree, base)) -- cgit v1.2.3 From 87598c8c80f4cbeef114267d75c46f1c87ca057a Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Mon, 2 Oct 2017 15:46:30 +0900 Subject: Make automatically synchronize test schema work inside engine In Rails engine, migration files are in under `db/migrate` of engine. Therefore, when rake task is executed in engine, `db/migrate` is automatically added to `DatabaseTasks.migrations_paths`. https://github.com/rails/rails/blob/a18cf23a9cbcbeed61e8049442640c7153e0a8fb/activerecord/lib/active_record/railtie.rb#L39..L43 However, if execute the rake task under dummy app, migration files will not be loaded because engine's migration path setting process is not called. Therefore, in order to load migration files correctly, it is necessary to execute rake task under engine. Fixes #30765 --- activerecord/lib/active_record/migration.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 8845e26ab7..ec8119e5da 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -581,7 +581,8 @@ module ActiveRecord def load_schema_if_pending! if ActiveRecord::Migrator.needs_migration? || !ActiveRecord::Migrator.any_migrations? # Roundtrip to Rake to allow plugins to hook into database initialization. - FileUtils.cd Rails.root do + root = defined?(ENGINE_ROOT) ? ENGINE_ROOT : Rails.root + FileUtils.cd(root) do current_config = Base.connection_config Base.clear_all_connections! system("bin/rails db:test:prepare") -- cgit v1.2.3 From 71b15603fddd85602813d1c96857e2674b6075d0 Mon Sep 17 00:00:00 2001 From: Felipe Oliveira Date: Tue, 3 Oct 2017 08:41:12 -0300 Subject: Add update_only example to AR nested attributes doc [ci_skip] --- activerecord/lib/active_record/nested_attributes.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) (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 435c81c153..fa20bce3a9 100644 --- a/activerecord/lib/active_record/nested_attributes.rb +++ b/activerecord/lib/active_record/nested_attributes.rb @@ -63,6 +63,18 @@ module ActiveRecord # member.update params[:member] # member.avatar.icon # => 'sad' # + # If you want to update the current avatar without providing the id, you must add :update_only option. + # + # class Member < ActiveRecord::Base + # has_one :avatar + # accepts_nested_attributes_for :avatar, update_only: true + # end + # + # params = { member: { avatar_attributes: { icon: 'sad' } } } + # member.update params[:member] + # member.avatar.id # => 2 + # member.avatar.icon # => 'sad' + # # By default you will only be able to set and update attributes on the # associated model. If you want to destroy the associated model through the # attributes hash, you have to enable it first using the -- cgit v1.2.3 From 723ff351e105a3546f1112e70b7f6d204717127f Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 4 Oct 2017 06:53:59 +0900 Subject: Prefer official name PostgreSQL over Postgres --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (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 4d37a292d6..5b6ad5fbc4 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -312,14 +312,14 @@ module ActiveRecord def get_advisory_lock(lock_id) # :nodoc: unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 - raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer") + raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer") end query_value("SELECT pg_try_advisory_lock(#{lock_id})") end def release_advisory_lock(lock_id) # :nodoc: unless lock_id.is_a?(Integer) && lock_id.bit_length <= 63 - raise(ArgumentError, "Postgres requires advisory lock ids to be a signed 64 bit integer") + raise(ArgumentError, "PostgreSQL requires advisory lock ids to be a signed 64 bit integer") end query_value("SELECT pg_advisory_unlock(#{lock_id})") end -- cgit v1.2.3 From 85ca01bcb319d5ce66c0fa9ce6aede533492f0e9 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Wed, 4 Oct 2017 13:56:51 +0300 Subject: Express #change_column_comment as public api Implemented by #22911 Related to #30677 --- .../lib/active_record/connection_adapters/abstract/schema_statements.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/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 4f0c1890be..e6bcdbda60 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1174,7 +1174,7 @@ module ActiveRecord end # Changes the comment for a column or removes it if +nil+. - def change_column_comment(table_name, column_name, comment) #:nodoc: + def change_column_comment(table_name, column_name, comment) raise NotImplementedError, "#{self.class} does not support changing column comments" end -- cgit v1.2.3 From 8a8ac422d8c388bf5f39ae2e6ec3ae84780186e7 Mon Sep 17 00:00:00 2001 From: meganemura Date: Thu, 5 Oct 2017 13:21:03 +0900 Subject: Use __callee__ to pass alias instead of original method name Before ``` > Article.left_joins ArgumentError: The method .left_outer_joins() must contain arguments. ``` After ``` > Article.left_joins ArgumentError: The method .left_joins() must contain arguments. ``` --- 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 c88603fde2..e76ad05b71 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -441,7 +441,7 @@ module ActiveRecord # => SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON "posts"."user_id" = "users"."id" # def left_outer_joins(*args) - check_if_method_has_arguments!(:left_outer_joins, args) + check_if_method_has_arguments!(__callee__, args) args.compact! args.flatten! -- cgit v1.2.3 From 214dc7d82d8a87914d5d426b7e8460300000acc2 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 2 Oct 2017 08:29:00 +0900 Subject: Extract repeatedly appeared prepending compatible table definition --- .../lib/active_record/migration/compatibility.rb | 49 +++++++++++----------- 1 file changed, 24 insertions(+), 25 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 502cef2e20..2b247f7a7a 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -52,11 +52,8 @@ module ActiveRecord end if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -65,11 +62,8 @@ module ActiveRecord def change_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -80,11 +74,8 @@ module ActiveRecord column_options.reverse_merge!(type: :integer) if block_given? - super(table_1, table_2, column_options: column_options, **options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -103,6 +94,14 @@ module ActiveRecord super(table_name, ref_name, type: :integer, **options) end alias :add_belongs_to :add_reference + + private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + end end class V4_2 < V5_0 @@ -121,11 +120,8 @@ module ActiveRecord def create_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -134,11 +130,8 @@ module ActiveRecord def change_table(table_name, options = {}) if block_given? - super(table_name, options) do |t| - class << t - prepend TableDefinition - end - yield t + super do |t| + yield compatible_table_definition(t) end else super @@ -174,6 +167,12 @@ module ActiveRecord end private + def compatible_table_definition(t) + class << t + prepend TableDefinition + end + t + end def index_name_for_remove(table_name, options = {}) index_name = index_name(table_name, options) -- cgit v1.2.3 From 563c16c7cbccc2f3a088139bfb855865a51b027e Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 2 Jun 2017 04:43:08 +0900 Subject: Add JSON attribute test cases for SQLite3 adapter --- activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 2 ++ .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 1 - 2 files changed, 2 insertions(+), 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 8c889f98f5..6859feb2f3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -478,6 +478,8 @@ module ActiveRecord m.alias_type %r(number)i, "decimal" m.alias_type %r(double)i, "float" + m.register_type %r(^json)i, Type::Json.new + m.register_type(%r(decimal)i) do |sql_type| scale = extract_scale(sql_type) precision = extract_precision(sql_type) 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 ae991d3d79..add6f8632b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -563,7 +563,6 @@ module ActiveRecord m.register_type %r(longblob)i, Type::Binary.new(limit: 2**32 - 1) m.register_type %r(^float)i, Type::Float.new(limit: 24) m.register_type %r(^double)i, Type::Float.new(limit: 53) - m.register_type %r(^json)i, Type::Json.new register_integer_type m, %r(^bigint)i, limit: 8 register_integer_type m, %r(^int)i, limit: 4 -- cgit v1.2.3 From 9cbebd70eae7aa78a5b165dd3cae8e53c9a8ab83 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 6 Oct 2017 03:37:27 +0900 Subject: Move duplicated code to `delete_or_destroy` in `CollectionAssociation` --- .../lib/active_record/associations/collection_association.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (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 ceedf150e3..ed215fb22c 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -181,8 +181,6 @@ module ActiveRecord # are actually removed from the database, that depends precisely on # +delete_records+. They are in any case removed from the collection. def delete(*records) - return if records.empty? - records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } delete_or_destroy(records, options[:dependent]) end @@ -192,8 +190,6 @@ module ActiveRecord # Note that this method removes records from the database ignoring the # +:dependent+ option. def destroy(*records) - return if records.empty? - records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } delete_or_destroy(records, :destroy) end @@ -376,6 +372,8 @@ module ActiveRecord end def delete_or_destroy(records, method) + return if records.empty? + records = find(records) if records.any? { |record| record.kind_of?(Integer) || record.kind_of?(String) } records = records.flatten records.each { |record| raise_on_type_mismatch!(record) } existing_records = records.reject(&:new_record?) -- cgit v1.2.3 From d20f4b73bd65a4c679aa31fb95795148d7799ae7 Mon Sep 17 00:00:00 2001 From: Chris Salzberg Date: Sun, 8 Oct 2017 21:25:12 +0900 Subject: Include Mutex_m into module class instead of extending instance --- activerecord/lib/active_record/attribute_methods.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (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 e4ca6c8408..891e556dc4 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -33,7 +33,9 @@ module ActiveRecord BLACKLISTED_CLASS_METHODS = %w(private public protected allocate new name parent superclass) - class GeneratedAttributeMethods < Module; end # :nodoc: + class GeneratedAttributeMethods < Module #:nodoc: + include Mutex_m + end module ClassMethods def inherited(child_class) #:nodoc: @@ -42,7 +44,7 @@ module ActiveRecord end def initialize_generated_modules # :nodoc: - @generated_attribute_methods = GeneratedAttributeMethods.new { extend Mutex_m } + @generated_attribute_methods = GeneratedAttributeMethods.new @attribute_methods_generated = false include @generated_attribute_methods -- cgit v1.2.3 From 8ad8bbaef8c4f521f292765a02ddb91e48160366 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 7 Oct 2017 17:29:45 +0900 Subject: Decouple building `AliasTracker` from `JoinDependency` This is preparation to respect parent relation's alias tracking for fixing #30681. --- .../lib/active_record/associations/alias_tracker.rb | 14 ++++---------- .../lib/active_record/associations/association_scope.rb | 3 +-- .../lib/active_record/associations/join_dependency.rb | 4 ++-- activerecord/lib/active_record/relation.rb | 4 ++++ activerecord/lib/active_record/relation/finder_methods.rb | 4 +++- activerecord/lib/active_record/relation/merger.rb | 2 +- activerecord/lib/active_record/relation/query_methods.rb | 2 +- 7 files changed, 16 insertions(+), 17 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 096f016976..358cdabc7f 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -6,22 +6,16 @@ module ActiveRecord module Associations # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency class AliasTracker # :nodoc: - def self.create(connection, initial_table) - aliases = Hash.new(0) - aliases[initial_table] = 1 - new(connection, aliases) - end - - def self.create_with_joins(connection, initial_table, joins) + def self.create(connection, initial_table, joins) if joins.empty? - create(connection, initial_table) + aliases = Hash.new(0) else aliases = Hash.new { |h, k| h[k] = initial_count_for(connection, k, joins) } - aliases[initial_table] = 1 - new(connection, aliases) end + aliases[initial_table] = 1 + new(connection, aliases) end def self.initial_count_for(connection, name, table_joins) diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 6118cef913..11967e0571 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -23,8 +23,7 @@ module ActiveRecord reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.create(klass.connection, scope.table.name) - chain = get_chain(reflection, association, alias_tracker) + chain = get_chain(reflection, association, scope.alias_tracker) scope.extending! reflection.extensions add_constraints(scope, owner, chain) diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 23741b2f6a..b70b680885 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -90,8 +90,8 @@ module ActiveRecord # associations # => [:appointments] # joins # => [] # - def initialize(base, table, associations, joins, eager_loading: true) - @alias_tracker = AliasTracker.create_with_joins(base.connection, table.name, joins) + def initialize(base, table, associations, alias_tracker, eager_loading: true) + @alias_tracker = alias_tracker @eager_loading = eager_loading tree = self.class.make_tree associations @join_root = JoinBase.new(base, table, build(tree, base)) diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 3517091a6e..1f59a0c53a 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -551,6 +551,10 @@ module ActiveRecord limit_value || offset_value end + def alias_tracker(joins = []) # :nodoc: + ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins) + end + protected def load_records(records) diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index c92d5a52f4..5d4b549470 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -390,7 +390,9 @@ module ActiveRecord def construct_join_dependency(joins = [], eager_loading: true) including = eager_load_values + includes_values - ActiveRecord::Associations::JoinDependency.new(klass, table, including, joins, eager_loading: eager_loading) + ActiveRecord::Associations::JoinDependency.new( + klass, table, including, alias_tracker(joins), eager_loading: eager_loading + ) end def construct_relation_for_association_calculations diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index 03824ffff9..ebc72d28fd 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -122,7 +122,7 @@ module ActiveRecord end join_dependency = ActiveRecord::Associations::JoinDependency.new( - other.klass, other.table, joins_dependency, [] + other.klass, other.table, joins_dependency, other.alias_tracker ) relation.joins! rest diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index e76ad05b71..fb5beeffef 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1013,7 +1013,7 @@ module ActiveRecord join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( - klass, table, association_joins, join_list + klass, table, association_joins, alias_tracker(join_list) ) joins = join_dependency.join_constraints(stashed_association_joins, join_type) -- cgit v1.2.3 From 55a2c101b2889710e1591c9adc15e4d5ca7fb126 Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Fri, 29 Sep 2017 18:24:24 -0700 Subject: Distinguish missing adapter gems from load errors within the adapter * When the adapter is missing, raise an exception that points out config typos and missing Gemfile entries. (We can assume that a non-builtin adapter was used since these are always available.) * When loading an adapter raises a LoadError, prefix its error message to indicate that the adapter is likely missing an optional dependency. --- .../connection_adapters/connection_specification.rb | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 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 29542f917e..e9bda1885b 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -183,13 +183,25 @@ module ActiveRecord raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter) + # Require the adapter itself and give useful feedback about + # 1. Missing adapter gems and + # 2. Adapter gems' missing dependencies. path_to_adapter = "active_record/connection_adapters/#{spec[:adapter]}_adapter" begin require path_to_adapter - rescue Gem::LoadError => e - raise Gem::LoadError, "Specified '#{spec[:adapter]}' for database adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by ActiveRecord)." rescue LoadError => e - raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/database.yml is valid. If you use an adapter other than 'mysql2', 'postgresql' or 'sqlite3' add the necessary adapter gem to the Gemfile.", e.backtrace + # We couldn't require the adapter itself. Raise an exception that + # points out config typos and missing gems. + if e.path == path_to_adapter + # We can assume that a non-builtin adapter was specified, so it's + # either misspelled or missing from Gemfile. + raise e.class, "Could not load the '#{spec[:adapter]}' Active Record adapter. Ensure that the adapter is spelled correctly in config/database.yml and that you've added the necessary adapter gem to your Gemfile.", e.backtrace + + # Bubbled up from the adapter require. Prefix the exception message + # with some guidance about how to address it and reraise. + else + raise e.class, "Error loading the '#{spec[:adapter]}' Action Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace + end end adapter_method = "#{spec[:adapter]}_connection" -- cgit v1.2.3 From 00f2d39c2e3111bf695aed9335388f0e06904d6a Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 9 Oct 2017 07:05:26 +0900 Subject: Remove passing redundant `self` to internal `apply_join_dependency` etc --- .../lib/active_record/relation/finder_methods.rb | 25 +++++++++++----------- 1 file changed, 12 insertions(+), 13 deletions(-) (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 5d4b549470..5ccfe3d1b7 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -310,12 +310,12 @@ module ActiveRecord return false if !conditions || limit_value == 0 - relation = self unless eager_loading? - relation ||= apply_join_dependency(self, construct_join_dependency(eager_loading: false)) - - return false if ActiveRecord::NullRelation === relation + if eager_loading? + relation = apply_join_dependency(construct_join_dependency(eager_loading: false)) + return relation.exists?(conditions) + end - relation = construct_relation_for_exists(relation, conditions) + relation = construct_relation_for_exists(conditions) skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists") } ? true : false rescue ::RangeError @@ -368,15 +368,14 @@ module ActiveRecord # join_dependency = construct_join_dependency(joins_values) - aliases = join_dependency.aliases - relation = select aliases.columns - relation = apply_join_dependency(relation, join_dependency) + relation = apply_join_dependency(join_dependency) + relation._select!(join_dependency.aliases.columns) yield relation, join_dependency end - def construct_relation_for_exists(relation, conditions) - relation = relation.except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) + def construct_relation_for_exists(conditions) + relation = except(:select, :distinct, :order)._select!(ONE_AS_ONE).limit!(1) case conditions when Array, Hash @@ -396,11 +395,11 @@ module ActiveRecord end def construct_relation_for_association_calculations - apply_join_dependency(self, construct_join_dependency(joins_values)) + apply_join_dependency(construct_join_dependency(joins_values)) end - def apply_join_dependency(relation, join_dependency) - relation = relation.except(:includes, :eager_load, :preload).joins!(join_dependency) + def apply_join_dependency(join_dependency) + relation = except(:includes, :eager_load, :preload).joins!(join_dependency) if using_limitable_reflections?(join_dependency.reflections) relation -- cgit v1.2.3 From e50d4c9dedc75835c17211f337c0c08d2a5f58a0 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Mon, 9 Oct 2017 08:52:34 +0900 Subject: Fix typo in loading error message s/Action Record/Active Record/ --- .../lib/active_record/connection_adapters/connection_specification.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (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 e9bda1885b..508132accb 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -200,7 +200,7 @@ module ActiveRecord # Bubbled up from the adapter require. Prefix the exception message # with some guidance about how to address it and reraise. else - raise e.class, "Error loading the '#{spec[:adapter]}' Action Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace + raise e.class, "Error loading the '#{spec[:adapter]}' Active Record adapter. Missing a gem it depends on? #{e.message}", e.backtrace end end -- cgit v1.2.3 From 723b526158df576f673c1bc28439744378a03f5d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 2 Oct 2017 06:39:42 +0900 Subject: Fix `relation.exists?` with has_many through associations `relation.exists?` should reference correct aliases while joining tables of has_many through associations. --- activerecord/lib/active_record/relation/finder_methods.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (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 5ccfe3d1b7..6e07402979 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -366,7 +366,7 @@ module ActiveRecord # preexisting join in joins_values to categorizations (by way of # the `has_many :through` for categories). # - join_dependency = construct_join_dependency(joins_values) + join_dependency = construct_join_dependency relation = apply_join_dependency(join_dependency) relation._select!(join_dependency.aliases.columns) @@ -387,15 +387,15 @@ module ActiveRecord relation end - def construct_join_dependency(joins = [], eager_loading: true) + def construct_join_dependency(eager_loading: true) including = eager_load_values + includes_values ActiveRecord::Associations::JoinDependency.new( - klass, table, including, alias_tracker(joins), eager_loading: eager_loading + klass, table, including, alias_tracker(joins_values), eager_loading: eager_loading ) end def construct_relation_for_association_calculations - apply_join_dependency(construct_join_dependency(joins_values)) + apply_join_dependency(construct_join_dependency) end def apply_join_dependency(join_dependency) -- cgit v1.2.3 From bb5b672ac6b4dd694a3f706d98d82f3000ce18cd Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 9 Oct 2017 10:30:03 +0900 Subject: Remove meaningless named `construct_relation_for_association_calculations` I don't think this is a good abstraction because the internal method is used only if the relation need to be applied join dependency. --- activerecord/lib/active_record/relation/calculations.rb | 5 +++-- activerecord/lib/active_record/relation/finder_methods.rb | 4 ---- 2 files changed, 3 insertions(+), 6 deletions(-) (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 0889d61c92..4ef0502893 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -130,7 +130,7 @@ module ActiveRecord # end def calculate(operation, column_name) if has_include?(column_name) - relation = construct_relation_for_association_calculations + relation = apply_join_dependency(construct_join_dependency) relation.distinct! if operation.to_s.downcase == "count" relation.calculate(operation, column_name) @@ -180,7 +180,8 @@ module ActiveRecord end if has_include?(column_names.first) - construct_relation_for_association_calculations.pluck(*column_names) + relation = apply_join_dependency(construct_join_dependency) + relation.pluck(*column_names) else relation = spawn relation.select_values = column_names.map { |cn| diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 6e07402979..707245bab2 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -394,10 +394,6 @@ module ActiveRecord ) end - def construct_relation_for_association_calculations - apply_join_dependency(construct_join_dependency) - end - def apply_join_dependency(join_dependency) relation = except(:includes, :eager_load, :preload).joins!(join_dependency) -- cgit v1.2.3 From c5ab6e51a7b9ee05a2d262a72c7130b9c1d1b0ce Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 9 Oct 2017 15:18:59 +0900 Subject: Joined tables in association scope doesn't use the same aliases with the parent relation's aliases Building association scope in join dependency should respect the parent relation's aliases to avoid using the same alias name more than once. Fixes #30681. --- .../active_record/associations/alias_tracker.rb | 7 +++---- .../active_record/associations/join_dependency.rb | 7 ++++--- .../join_dependency/join_association.rb | 17 +++++++++++------ activerecord/lib/active_record/relation.rb | 3 ++- .../lib/active_record/relation/query_methods.rb | 22 +++++++++++----------- 5 files changed, 31 insertions(+), 25 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 358cdabc7f..a8d8ee268a 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -30,6 +30,8 @@ module ActiveRecord ).size elsif join.respond_to? :left join.left.table_name == name ? 1 : 0 + elsif join.is_a?(Hash) + join[name] else # this branch is reached by two tests: # @@ -73,10 +75,7 @@ module ActiveRecord end end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - attr_reader :aliases + attr_reader :aliases private diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index b70b680885..df4bf07999 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -43,8 +43,6 @@ module ActiveRecord Column = Struct.new(:name, :alias) end - attr_reader :alias_tracker, :base_klass, :join_root - def self.make_tree(associations) hash = {} walk_tree associations, hash @@ -158,6 +156,9 @@ module ActiveRecord parents.values end + protected + attr_reader :alias_tracker, :base_klass, :join_root + private def make_constraints(parent, child, tables, join_type) @@ -224,7 +225,7 @@ module ActiveRecord raise EagerLoadPolymorphicError.new(reflection) end - JoinAssociation.new reflection, build(right, reflection.klass) + JoinAssociation.new(reflection, build(right, reflection.klass), alias_tracker) end.compact end 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 a526468bf6..a007d0cf5f 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -11,11 +11,12 @@ module ActiveRecord attr_accessor :tables - def initialize(reflection, children) + def initialize(reflection, children, alias_tracker) super(reflection.klass, children) - @reflection = reflection - @tables = nil + @alias_tracker = alias_tracker + @reflection = reflection + @tables = nil end def match?(other) @@ -38,11 +39,12 @@ module ActiveRecord joins << table.create_join(table, table.create_on(constraint), join_type) join_scope = reflection.join_scope(table, foreign_klass) + arel = join_scope.arel(alias_tracker.aliases) - if join_scope.arel.constraints.any? - joins.concat join_scope.arel.join_sources + if arel.constraints.any? + joins.concat arel.join_sources right = joins.last.right - right.expr = right.expr.and(join_scope.arel.constraints) + right.expr = right.expr.and(arel.constraints) end # The current table in this iteration becomes the foreign table in the next @@ -55,6 +57,9 @@ module ActiveRecord def table tables.first end + + protected + attr_reader :alias_tracker end end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 1f59a0c53a..997cfe4b5e 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -551,7 +551,8 @@ module ActiveRecord limit_value || offset_value end - def alias_tracker(joins = []) # :nodoc: + def alias_tracker(joins = [], aliases = nil) # :nodoc: + joins += [aliases] if aliases ActiveRecord::Associations::AliasTracker.create(connection, table.name, joins) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index fb5beeffef..578d8a6eb7 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -898,8 +898,8 @@ module ActiveRecord end # Returns the Arel object associated with the relation. - def arel # :nodoc: - @arel ||= build_arel + def arel(aliases = nil) # :nodoc: + @arel ||= build_arel(aliases) end protected @@ -921,11 +921,11 @@ module ActiveRecord raise ImmutableRelation if defined?(@arel) && @arel end - def build_arel + def build_arel(aliases) arel = Arel::SelectManager.new(table) - build_joins(arel, joins_values.flatten) unless joins_values.empty? - build_left_outer_joins(arel, left_outer_joins_values.flatten) unless left_outer_joins_values.empty? + build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? + build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty? arel.where(where_clause.ast) unless where_clause.empty? arel.having(having_clause.ast) unless having_clause.empty? @@ -970,7 +970,7 @@ module ActiveRecord end end - def build_left_outer_joins(manager, outer_joins) + def build_left_outer_joins(manager, outer_joins, aliases) buckets = outer_joins.group_by do |join| case join when Hash, Symbol, Array @@ -980,10 +980,10 @@ module ActiveRecord end end - build_join_query(manager, buckets, Arel::Nodes::OuterJoin) + build_join_query(manager, buckets, Arel::Nodes::OuterJoin, aliases) end - def build_joins(manager, joins) + def build_joins(manager, joins, aliases) buckets = joins.group_by do |join| case join when String @@ -999,10 +999,10 @@ module ActiveRecord end end - build_join_query(manager, buckets, Arel::Nodes::InnerJoin) + build_join_query(manager, buckets, Arel::Nodes::InnerJoin, aliases) end - def build_join_query(manager, buckets, join_type) + def build_join_query(manager, buckets, join_type, aliases) buckets.default = [] association_joins = buckets[:association_join] @@ -1013,7 +1013,7 @@ module ActiveRecord join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) join_dependency = ActiveRecord::Associations::JoinDependency.new( - klass, table, association_joins, alias_tracker(join_list) + klass, table, association_joins, alias_tracker(join_list, aliases) ) joins = join_dependency.join_constraints(stashed_association_joins, join_type) -- cgit v1.2.3 From b1ee5e8e7f98b32196423a144e7a87ae284c7a5c Mon Sep 17 00:00:00 2001 From: Mike Boone Date: Tue, 10 Oct 2017 23:08:03 -0400 Subject: Fix some typos. --- activerecord/lib/active_record/callbacks.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index a2439e6ec7..ac1070e65b 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -240,7 +240,7 @@ module ActiveRecord # # private # - # def log_chidren + # def log_children # # Child processing # end # @@ -265,7 +265,7 @@ module ActiveRecord # # private # - # def log_chidren + # def log_children # # Child processing # end # -- cgit v1.2.3 From 5668dc6b1863ef43be8f8ef0fb1d5db913085fb3 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 14 Oct 2017 13:19:26 +0900 Subject: Fix `COUNT(DISTINCT ...)` for `GROUP BY` with `ORDER BY` and `LIMIT` This is the fix for the regression of #29848. In #29848, I've kept existing select list in the subquery for the count if ORDER BY is given. But it had accidentally affect to GROUP BY queries also. It should keep the previous behavior in that case. Fixes #30886. --- 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 4ef0502893..116bddce85 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -216,7 +216,7 @@ module ActiveRecord if operation == "count" column_name ||= select_for_count if column_name == :all - if distinct && !(has_limit_or_offset? && order_values.any?) + if distinct && (group_values.any? || !(has_limit_or_offset? && order_values.any?)) column_name = primary_key end elsif column_name =~ /\s*DISTINCT[\s(]+/i -- cgit v1.2.3 From 9493d4553569118b2a85da84fd3a8ba2b5b2de76 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 15 Oct 2017 20:18:12 +0900 Subject: MySQL: Don't lose `auto_increment: true` in the `db/schema.rb` Currently `AUTO_INCREMENT` is implicitly used in the default primary key definition. But `AUTO_INCREMENT` is not only used for single column primary key, but also for composite primary key. In that case, `auto_increment: true` should be dumped explicitly in the `db/schema.rb`. Fixes #30894. --- .../lib/active_record/connection_adapters/mysql/schema_dumper.rb | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb index 95eb77aea4..d23178e43c 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -8,6 +8,7 @@ module ActiveRecord def prepare_column_options(column) spec = super spec[:unsigned] = "true" if column.unsigned? + spec[:auto_increment] = "true" if column.auto_increment? if @connection.supports_virtual_columns? && column.virtual? spec[:as] = extract_expression_for_virtual_column(column) @@ -18,6 +19,12 @@ module ActiveRecord spec end + def column_spec_for_primary_key(column) + spec = super + spec.delete(:auto_increment) if column.type == :integer && column.auto_increment? + spec + end + def default_primary_key?(column) super && column.auto_increment? && !column.unsigned? end -- cgit v1.2.3 From 8877492df2853cba92d95dabdfcdee7244e7d8f0 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 15 Oct 2017 23:22:33 +0900 Subject: Fix longer sequence name detection for serial columns (#28339) We already found the longer sequence name, but we could not consider whether it was the sequence name created by serial type due to missed a max identifier length limitation. I've addressed the sequence name consideration to respect the max identifier length. Fixes #28332. --- .../lib/active_record/connection_adapters/column.rb | 2 +- .../connection_adapters/postgresql/column.rb | 20 ++++++++++++++++++++ .../postgresql/schema_statements.rb | 3 ++- .../connection_adapters/postgresql_adapter.rb | 5 +++-- 4 files changed, 26 insertions(+), 4 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 16273fb5f1..28d949b503 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -15,7 +15,7 @@ module ActiveRecord # +default+ is the type-casted default value, such as +new+ in sales_stage varchar(20) default 'new'. # +sql_type_metadata+ is various information about the type of the column # +null+ determines if this column allows +NULL+ values. - def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil) + def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil, **) @name = name.freeze @table_name = table_name @sql_type_metadata = sql_type_metadata diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb index ff95fa4a0e..469ef3f5a0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb @@ -7,6 +7,11 @@ module ActiveRecord delegate :array, :oid, :fmod, to: :sql_type_metadata alias :array? :array + def initialize(*, max_identifier_length: 63, **) + super + @max_identifier_length = max_identifier_length + end + def serial? return unless default_function @@ -15,8 +20,23 @@ module ActiveRecord end end + protected + attr_reader :max_identifier_length + private def sequence_name_from_parts(table_name, column_name, suffix) + over_length = [table_name, column_name, suffix].map(&:length).sum + 2 - max_identifier_length + + if over_length > 0 + column_name_length = [(max_identifier_length - suffix.length - 2) / 2, column_name.length].min + over_length -= column_name.length - column_name_length + column_name = column_name[0, column_name_length - [over_length, 0].min] + end + + if over_length > 0 + table_name = table_name[0, table_name.length - over_length] + end + "#{table_name}_#{column_name}_#{suffix}" end end 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 9e2f61e6ce..79cb4e276d 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -617,7 +617,8 @@ module ActiveRecord table_name, default_function, collation, - comment: comment.presence + comment: comment.presence, + max_identifier_length: max_identifier_length ) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5b6ad5fbc4..eae3d59946 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -352,10 +352,11 @@ module ActiveRecord end # Returns the configured supported identifier length supported by PostgreSQL - def table_alias_length + def max_identifier_length @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i end - alias index_name_length table_alias_length + alias table_alias_length max_identifier_length + alias index_name_length max_identifier_length # Set the authorized user for this session def session_auth=(user) -- cgit v1.2.3 From 99b2bf8db39846897f017129a41efcffa840dc68 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Tue, 12 Sep 2017 01:06:07 +0300 Subject: Fix `bin/rails db:setup` and `bin/rails db:test:prepare` create wrong ar_internal_metadata's data for a test database. Before: ``` $ RAILS_ENV=test rails dbconsole > SELECT * FROM ar_internal_metadata; key|value|created_at|updated_at environment|development|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679 ``` After: ``` $ RAILS_ENV=test rails dbconsole > SELECT * FROM ar_internal_metadata; key|value|created_at|updated_at environment|test|2017-09-11 23:14:10.815679|2017-09-11 23:14:10.815679 ``` Fixes #26731. --- activerecord/lib/active_record/railties/databases.rake | 4 ++-- activerecord/lib/active_record/tasks/database_tasks.rb | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 691b3612d8..eb32c29b7e 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -313,7 +313,7 @@ db_namespace = namespace :db do begin should_reconnect = ActiveRecord::Base.connection_pool.active_connection? ActiveRecord::Schema.verbose = false - ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"] + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :ruby, ENV["SCHEMA"], "test" ensure if should_reconnect ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env]) @@ -323,7 +323,7 @@ db_namespace = namespace :db do # desc "Recreate the test database from an existent structure.sql file" task load_structure: %w(db:test:purge) do - ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"] + ActiveRecord::Tasks::DatabaseTasks.load_schema ActiveRecord::Base.configurations["test"], :sql, ENV["SCHEMA"], "test" end # desc "Empty the test database" diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 0f3f84ca08..0b85965bab 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -225,7 +225,7 @@ module ActiveRecord class_for_adapter(configuration["adapter"]).new(*arguments).structure_load(filename, structure_load_flags) end - def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc: + def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env) # :nodoc: file ||= schema_file(format) case format @@ -240,7 +240,7 @@ module ActiveRecord raise ArgumentError, "unknown format #{format.inspect}" end ActiveRecord::InternalMetadata.create_table - ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment + ActiveRecord::InternalMetadata[:environment] = environment end def schema_file(format = ActiveRecord::Base.schema_format) @@ -253,8 +253,8 @@ module ActiveRecord end def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env) - each_current_configuration(environment) { |configuration| - load_schema configuration, format, file + each_current_configuration(environment) { |configuration, configuration_environment| + load_schema configuration, format, file, configuration_environment } ActiveRecord::Base.establish_connection(environment.to_sym) end @@ -301,9 +301,10 @@ module ActiveRecord environments = [environment] environments << "test" if environment == "development" - configurations = ActiveRecord::Base.configurations.values_at(*environments) - configurations.compact.each do |configuration| - yield configuration unless configuration["database"].blank? + ActiveRecord::Base.configurations.slice(*environments).each do |configuration_environment, configuration| + next unless configuration["database"] + + yield configuration, configuration_environment end end -- cgit v1.2.3 From 678e563da3e7cddca9501ec90cdd76ee399a62b1 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Tue, 12 Sep 2017 10:08:16 +0300 Subject: `ActiveRecord::Tasks::DatabaseTasks.load_schema` has always to establish database connection When load schema from `structure.sql`, database connection isn't established. `ActiveRecord::Tasks::DatabaseTasks.load_schema` has to establish database connection since it executes ``` ActiveRecord::InternalMetadata.create_table ActiveRecord::InternalMetadata[:environment] = environment ``` --- activerecord/lib/active_record/tasks/database_tasks.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index 0b85965bab..ff388ff1f6 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -228,13 +228,13 @@ module ActiveRecord def load_schema(configuration, format = ActiveRecord::Base.schema_format, file = nil, environment = env) # :nodoc: file ||= schema_file(format) + check_schema_file(file) + ActiveRecord::Base.establish_connection(configuration) + case format when :ruby - check_schema_file(file) - ActiveRecord::Base.establish_connection(configuration) load(file) when :sql - check_schema_file(file) structure_load(configuration, file) else raise ArgumentError, "unknown format #{format.inspect}" -- cgit v1.2.3 From 5ea4cae911604eb879f68f66fad4e83121d1b072 Mon Sep 17 00:00:00 2001 From: Eugene Kenny Date: Mon, 16 Oct 2017 00:20:20 +0100 Subject: Remove association(true) references from docs [ci skip] Passing `true` to force an association to reload its records from the database was deprecated in 5.0 and removed in 5.1. --- activerecord/lib/active_record/associations.rb | 2 +- activerecord/lib/active_record/associations/collection_proxy.rb | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index ef26f4a20c..91f915183a 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -850,7 +850,7 @@ module ActiveRecord # project.milestones # fetches milestones from the database # project.milestones.size # uses the milestone cache # project.milestones.empty? # uses the milestone cache - # project.milestones(true).size # fetches milestones from the database + # project.milestones.reload.size # fetches milestones from the database # project.milestones # uses the milestone cache # # == Eager loading of associations diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 412e89255d..07f7303f8d 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -1072,7 +1072,6 @@ module ActiveRecord end # Reloads the collection from the database. Returns +self+. - # Equivalent to collection(true). # # class Person < ActiveRecord::Base # has_many :pets @@ -1086,9 +1085,6 @@ module ActiveRecord # # person.pets.reload # fetches pets from the database # # => [#] - # - # person.pets(true) # fetches pets from the database - # # => [#] def reload proxy_association.reload reset_scope -- cgit v1.2.3 From 3218d5f8407e902177fbffcea40cf7d5eb4d89a9 Mon Sep 17 00:00:00 2001 From: pavel Date: Tue, 17 Oct 2017 23:26:11 +0200 Subject: compatibility - use int instead of bigint --- activerecord/lib/active_record/migration/compatibility.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index 2b247f7a7a..c979aaf0a0 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -171,7 +171,7 @@ module ActiveRecord class << t prepend TableDefinition end - t + super end def index_name_for_remove(table_name, options = {}) -- cgit v1.2.3 From e9f82e76e83497db094fe63104f548453fbe512e Mon Sep 17 00:00:00 2001 From: Altech Date: Thu, 19 Oct 2017 16:30:31 +0900 Subject: Convert ignored_columns to a list of string --- activerecord/lib/active_record/model_schema.rb | 32 ++++++++++++++------------ 1 file changed, 17 insertions(+), 15 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 34c0ef4e75..bed9400f51 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -115,20 +115,6 @@ module ActiveRecord # If true, the default table name for a Product class will be "products". If false, it would just be "product". # See table_name for the full rules on table/class naming. This is true, by default. - ## - # :singleton-method: ignored_columns - # :call-seq: ignored_columns - # - # The list of columns names the model should ignore. Ignored columns won't have attribute - # accessors defined, and won't be referenced in SQL queries. - - ## - # :singleton-method: ignored_columns= - # :call-seq: ignored_columns=(columns) - # - # Sets the columns names the model should ignore. Ignored columns won't have attribute - # accessors defined, and won't be referenced in SQL queries. - included do mattr_accessor :primary_key_prefix_type, instance_writer: false @@ -138,9 +124,9 @@ module ActiveRecord class_attribute :internal_metadata_table_name, instance_accessor: false, default: "ar_internal_metadata" class_attribute :protected_environments, instance_accessor: false, default: [ "production" ] class_attribute :pluralize_table_names, instance_writer: false, default: true - class_attribute :ignored_columns, instance_accessor: false, default: [].freeze self.inheritance_column = "type" + self.ignored_columns = [].freeze delegate :type_for_attribute, to: :class @@ -271,6 +257,22 @@ module ActiveRecord @explicit_inheritance_column = true end + # The list of columns names the model should ignore. Ignored columns won't have attribute + # accessors defined, and won't be referenced in SQL queries. + def ignored_columns + if defined?(@ignored_columns) + @ignored_columns + else + superclass.ignored_columns + end + end + + # Sets the columns names the model should ignore. Ignored columns won't have attribute + # accessors defined, and won't be referenced in SQL queries. + def ignored_columns=(columns) + @ignored_columns = columns.map(&:to_s) + end + def sequence_name if base_class == self @sequence_name ||= reset_sequence_name -- cgit v1.2.3 From d5ea33b9a003d32ebaeae7ad89c479c04f790642 Mon Sep 17 00:00:00 2001 From: Akira Matsuda Date: Sat, 21 Oct 2017 22:17:03 +0900 Subject: [Active Record] require => require_relative This basically reverts 9d4f79d3d394edb74fa2192e5d9ad7b09ce50c6d --- activerecord/lib/active_record/associations.rb | 2 +- .../associations/builder/collection_association.rb | 2 +- .../join_dependency/join_association.rb | 2 +- .../associations/join_dependency/join_base.rb | 2 +- .../attribute/user_provided_default.rb | 2 +- .../lib/active_record/attribute_methods/dirty.rb | 2 +- activerecord/lib/active_record/attribute_set.rb | 4 +-- .../lib/active_record/attribute_set/builder.rb | 2 +- activerecord/lib/active_record/attributes.rb | 2 +- activerecord/lib/active_record/base.rb | 16 ++++----- .../abstract/schema_statements.rb | 2 +- .../connection_adapters/abstract_adapter.rb | 12 +++---- .../connection_adapters/abstract_mysql_adapter.rb | 20 +++++------ .../connection_adapters/mysql2_adapter.rb | 4 +-- .../connection_adapters/postgresql/oid.rb | 42 +++++++++++----------- .../connection_adapters/postgresql_adapter.rb | 28 +++++++-------- .../connection_adapters/sqlite3_adapter.rb | 16 ++++----- activerecord/lib/active_record/explain.rb | 2 +- .../lib/active_record/explain_subscriber.rb | 2 +- activerecord/lib/active_record/fixtures.rb | 4 +-- .../lib/active_record/internal_metadata.rb | 4 +-- activerecord/lib/active_record/railtie.rb | 10 +++--- .../active_record/railties/controller_runtime.rb | 2 +- .../lib/active_record/railties/databases.rake | 6 ++-- activerecord/lib/active_record/relation/batches.rb | 2 +- .../active_record/relation/predicate_builder.rb | 16 ++++----- .../lib/active_record/relation/query_attribute.rb | 2 +- .../lib/active_record/relation/query_methods.rb | 8 ++--- .../lib/active_record/relation/spawn_methods.rb | 2 +- activerecord/lib/active_record/schema_migration.rb | 4 +-- .../active_record/tasks/mysql_database_tasks.rb | 2 +- activerecord/lib/active_record/type.rb | 24 ++++++------- activerecord/lib/active_record/type_caster.rb | 4 +-- activerecord/lib/active_record/validations.rb | 10 +++--- 34 files changed, 132 insertions(+), 132 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 91f915183a..2723b3dc03 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -3,7 +3,7 @@ require "active_support/core_ext/enumerable" require "active_support/core_ext/string/conversions" require "active_support/core_ext/module/remove_method" -require_relative "errors" +require "active_record/errors" module ActiveRecord class AssociationNotFoundError < ConfigurationError #:nodoc: diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index 753fde5146..35a72c3850 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../associations" +require "active_record/associations" module ActiveRecord::Associations::Builder # :nodoc: class CollectionAssociation < Association #:nodoc: 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 a007d0cf5f..221c791bf8 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "join_part" +require "active_record/associations/join_dependency/join_part" module ActiveRecord module Associations diff --git a/activerecord/lib/active_record/associations/join_dependency/join_base.rb b/activerecord/lib/active_record/associations/join_dependency/join_base.rb index 8a8fa8993b..988b4e8fa2 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "join_part" +require "active_record/associations/join_dependency/join_part" module ActiveRecord module Associations diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb index 690a931615..f746960fac 100644 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ b/activerecord/lib/active_record/attribute/user_provided_default.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../attribute" +require "active_record/attribute" module ActiveRecord class Attribute # :nodoc: diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 06598439d8..79110d04f4 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "active_support/core_ext/module/attribute_accessors" -require_relative "../attribute_mutation_tracker" +require "active_record/attribute_mutation_tracker" module ActiveRecord module AttributeMethods diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 492067e2b3..9785666e77 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "attribute_set/builder" -require_relative "attribute_set/yaml_encoder" +require "active_record/attribute_set/builder" +require "active_record/attribute_set/yaml_encoder" module ActiveRecord class AttributeSet # :nodoc: diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb index e3a9c7fdb3..349cc7e403 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../attribute" +require "active_record/attribute" module ActiveRecord class AttributeSet # :nodoc: diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index afb559db71..9224d58928 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "attribute/user_provided_default" +require "active_record/attribute/user_provided_default" module ActiveRecord # See ActiveRecord::Attributes::ClassMethods for documentation diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 541ff51fbe..b7ad944cec 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -15,14 +15,14 @@ require "active_support/core_ext/kernel/singleton_class" require "active_support/core_ext/module/introspection" require "active_support/core_ext/object/duplicable" require "active_support/core_ext/class/subclasses" -require_relative "attribute_decorators" -require_relative "define_callbacks" -require_relative "errors" -require_relative "log_subscriber" -require_relative "explain_subscriber" -require_relative "relation/delegation" -require_relative "attributes" -require_relative "type_caster" +require "active_record/attribute_decorators" +require "active_record/define_callbacks" +require "active_record/errors" +require "active_record/log_subscriber" +require "active_record/explain_subscriber" +require "active_record/relation/delegation" +require "active_record/attributes" +require "active_record/type_caster" module ActiveRecord #:nodoc: # = 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 e6bcdbda60..24b2234c67 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../migration/join_table" +require "active_record/migration/join_table" require "active_support/core_ext/string/access" require "digest/sha2" diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 6859feb2f3..f2fe4e2c30 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require_relative "../type" -require_relative "determine_if_preparable_visitor" -require_relative "schema_cache" -require_relative "sql_type_metadata" -require_relative "abstract/schema_dumper" -require_relative "abstract/schema_creation" +require "active_record/type" +require "active_record/connection_adapters/determine_if_preparable_visitor" +require "active_record/connection_adapters/schema_cache" +require "active_record/connection_adapters/sql_type_metadata" +require "active_record/connection_adapters/abstract/schema_dumper" +require "active_record/connection_adapters/abstract/schema_creation" require "arel/collectors/bind" require "arel/collectors/composite" require "arel/collectors/sql_string" 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 add6f8632b..bfec6fb784 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -require_relative "abstract_adapter" -require_relative "statement_pool" -require_relative "mysql/column" -require_relative "mysql/explain_pretty_printer" -require_relative "mysql/quoting" -require_relative "mysql/schema_creation" -require_relative "mysql/schema_definitions" -require_relative "mysql/schema_dumper" -require_relative "mysql/schema_statements" -require_relative "mysql/type_metadata" +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/mysql/column" +require "active_record/connection_adapters/mysql/explain_pretty_printer" +require "active_record/connection_adapters/mysql/quoting" +require "active_record/connection_adapters/mysql/schema_creation" +require "active_record/connection_adapters/mysql/schema_definitions" +require "active_record/connection_adapters/mysql/schema_dumper" +require "active_record/connection_adapters/mysql/schema_statements" +require "active_record/connection_adapters/mysql/type_metadata" require "active_support/core_ext/string/strip" diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 2c2321872d..8de582fee1 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "abstract_mysql_adapter" -require_relative "mysql/database_statements" +require "active_record/connection_adapters/abstract_mysql_adapter" +require "active_record/connection_adapters/mysql/database_statements" gem "mysql2", ">= 0.3.18", "< 0.5" require "mysql2" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index b28418d74f..542ca75d3e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,27 +1,27 @@ # frozen_string_literal: true -require_relative "oid/array" -require_relative "oid/bit" -require_relative "oid/bit_varying" -require_relative "oid/bytea" -require_relative "oid/cidr" -require_relative "oid/date_time" -require_relative "oid/decimal" -require_relative "oid/enum" -require_relative "oid/hstore" -require_relative "oid/inet" -require_relative "oid/jsonb" -require_relative "oid/money" -require_relative "oid/oid" -require_relative "oid/point" -require_relative "oid/legacy_point" -require_relative "oid/range" -require_relative "oid/specialized_string" -require_relative "oid/uuid" -require_relative "oid/vector" -require_relative "oid/xml" +require "active_record/connection_adapters/postgresql/oid/array" +require "active_record/connection_adapters/postgresql/oid/bit" +require "active_record/connection_adapters/postgresql/oid/bit_varying" +require "active_record/connection_adapters/postgresql/oid/bytea" +require "active_record/connection_adapters/postgresql/oid/cidr" +require "active_record/connection_adapters/postgresql/oid/date_time" +require "active_record/connection_adapters/postgresql/oid/decimal" +require "active_record/connection_adapters/postgresql/oid/enum" +require "active_record/connection_adapters/postgresql/oid/hstore" +require "active_record/connection_adapters/postgresql/oid/inet" +require "active_record/connection_adapters/postgresql/oid/jsonb" +require "active_record/connection_adapters/postgresql/oid/money" +require "active_record/connection_adapters/postgresql/oid/oid" +require "active_record/connection_adapters/postgresql/oid/point" +require "active_record/connection_adapters/postgresql/oid/legacy_point" +require "active_record/connection_adapters/postgresql/oid/range" +require "active_record/connection_adapters/postgresql/oid/specialized_string" +require "active_record/connection_adapters/postgresql/oid/uuid" +require "active_record/connection_adapters/postgresql/oid/vector" +require "active_record/connection_adapters/postgresql/oid/xml" -require_relative "oid/type_map_initializer" +require "active_record/connection_adapters/postgresql/oid/type_map_initializer" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index eae3d59946..e9ae861bfb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -4,20 +4,20 @@ gem "pg", "~> 0.18" require "pg" -require_relative "abstract_adapter" -require_relative "statement_pool" -require_relative "postgresql/column" -require_relative "postgresql/database_statements" -require_relative "postgresql/explain_pretty_printer" -require_relative "postgresql/oid" -require_relative "postgresql/quoting" -require_relative "postgresql/referential_integrity" -require_relative "postgresql/schema_creation" -require_relative "postgresql/schema_definitions" -require_relative "postgresql/schema_dumper" -require_relative "postgresql/schema_statements" -require_relative "postgresql/type_metadata" -require_relative "postgresql/utils" +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/postgresql/column" +require "active_record/connection_adapters/postgresql/database_statements" +require "active_record/connection_adapters/postgresql/explain_pretty_printer" +require "active_record/connection_adapters/postgresql/oid" +require "active_record/connection_adapters/postgresql/quoting" +require "active_record/connection_adapters/postgresql/referential_integrity" +require "active_record/connection_adapters/postgresql/schema_creation" +require "active_record/connection_adapters/postgresql/schema_definitions" +require "active_record/connection_adapters/postgresql/schema_dumper" +require "active_record/connection_adapters/postgresql/schema_statements" +require "active_record/connection_adapters/postgresql/type_metadata" +require "active_record/connection_adapters/postgresql/utils" module ActiveRecord module ConnectionHandling # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 6fdd666486..670afa3684 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require_relative "abstract_adapter" -require_relative "statement_pool" -require_relative "sqlite3/explain_pretty_printer" -require_relative "sqlite3/quoting" -require_relative "sqlite3/schema_creation" -require_relative "sqlite3/schema_definitions" -require_relative "sqlite3/schema_dumper" -require_relative "sqlite3/schema_statements" +require "active_record/connection_adapters/abstract_adapter" +require "active_record/connection_adapters/statement_pool" +require "active_record/connection_adapters/sqlite3/explain_pretty_printer" +require "active_record/connection_adapters/sqlite3/quoting" +require "active_record/connection_adapters/sqlite3/schema_creation" +require "active_record/connection_adapters/sqlite3/schema_definitions" +require "active_record/connection_adapters/sqlite3/schema_dumper" +require "active_record/connection_adapters/sqlite3/schema_statements" gem "sqlite3", "~> 1.3.6" require "sqlite3" diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index 8bb54a24b7..7ccb938888 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "explain_registry" +require "active_record/explain_registry" module ActiveRecord module Explain diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index 9252fa3fed..a86217abc0 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "active_support/notifications" -require_relative "explain_registry" +require "active_record/explain_registry" module ActiveRecord class ExplainSubscriber # :nodoc: diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index 12169fffa9..86f13d75d5 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -6,8 +6,8 @@ require "zlib" require "set" require "active_support/dependencies" require "active_support/core_ext/digest/uuid" -require_relative "fixture_set/file" -require_relative "errors" +require "active_record/fixture_set/file" +require "active_record/errors" module ActiveRecord class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 14795cc815..5a65edf27e 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "scoping/default" -require_relative "scoping/named" +require "active_record/scoping/default" +require "active_record/scoping/named" module ActiveRecord # This class is used to create a table that keeps track of values and keys such diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index ead42d64ec..812e1d7a00 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -53,8 +53,8 @@ module ActiveRecord # to avoid cross references when loading a constant for the # first time. Also, make it output to STDERR. console do |app| - require_relative "railties/console_sandbox" if app.sandbox? - require_relative "base" + require "active_record/railties/console_sandbox" if app.sandbox? + require "active_record/base" unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT) console = ActiveSupport::Logger.new(STDERR) Rails.logger.extend ActiveSupport::Logger.broadcast console @@ -62,7 +62,7 @@ module ActiveRecord end runner do - require_relative "base" + require "active_record/base" end initializer "active_record.initialize_timezone" do @@ -106,7 +106,7 @@ module ActiveRecord initializer "active_record.warn_on_records_fetched_greater_than" do if config.active_record.warn_on_records_fetched_greater_than ActiveSupport.on_load(:active_record) do - require_relative "relation/record_fetch_warning" + require "active_record/relation/record_fetch_warning" end end end @@ -146,7 +146,7 @@ end_warning # Expose database runtime to controller for logging. initializer "active_record.log_runtime" do - require_relative "railties/controller_runtime" + require "active_record/railties/controller_runtime" ActiveSupport.on_load(:action_controller) do include ActiveRecord::Railties::ControllerRuntime end diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb index 3cf66980a5..2ae733f657 100644 --- a/activerecord/lib/active_record/railties/controller_runtime.rb +++ b/activerecord/lib/active_record/railties/controller_runtime.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "active_support/core_ext/module/attr_internal" -require_relative "../log_subscriber" +require "active_record/log_subscriber" module ActiveRecord module Railties # :nodoc: diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index eb32c29b7e..723272b4b2 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -189,7 +189,7 @@ db_namespace = namespace :db do namespace :fixtures do desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." task load: [:environment, :load_config] do - require_relative "../fixtures" + require "active_record/fixtures" base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path @@ -211,7 +211,7 @@ db_namespace = namespace :db do # desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." task identify: [:environment, :load_config] do - require_relative "../fixtures" + require "active_record/fixtures" label, id = ENV["LABEL"], ENV["ID"] raise "LABEL or ID required" if label.blank? && id.blank? @@ -237,7 +237,7 @@ db_namespace = namespace :db do namespace :schema do desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record" task dump: [:environment, :load_config] do - require_relative "../schema_dumper" + require "active_record/schema_dumper" filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb") File.open(filename, "w:utf-8") do |file| ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file) diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 356ad0dcd6..561869017a 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "batches/batch_enumerator" +require "active_record/relation/batches/batch_enumerator" module ActiveRecord module Batches diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index be4b169f67..885c26d7aa 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -124,11 +124,11 @@ module ActiveRecord end end -require_relative "predicate_builder/array_handler" -require_relative "predicate_builder/base_handler" -require_relative "predicate_builder/basic_object_handler" -require_relative "predicate_builder/range_handler" -require_relative "predicate_builder/relation_handler" - -require_relative "predicate_builder/association_query_value" -require_relative "predicate_builder/polymorphic_array_value" +require "active_record/relation/predicate_builder/array_handler" +require "active_record/relation/predicate_builder/base_handler" +require "active_record/relation/predicate_builder/basic_object_handler" +require "active_record/relation/predicate_builder/range_handler" +require "active_record/relation/predicate_builder/relation_handler" + +require "active_record/relation/predicate_builder/association_query_value" +require "active_record/relation/predicate_builder/polymorphic_array_value" diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb index 5a9a7fd432..fad08e2613 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../attribute" +require "active_record/attribute" module ActiveRecord class Relation diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 578d8a6eb7..269790cdba 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative "from_clause" -require_relative "query_attribute" -require_relative "where_clause" -require_relative "where_clause_factory" +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" module ActiveRecord diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index 424894f835..617d8de8b2 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -2,7 +2,7 @@ require "active_support/core_ext/hash/except" require "active_support/core_ext/hash/slice" -require_relative "merger" +require "active_record/relation/merger" module ActiveRecord module SpawnMethods diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index 339a5334a8..f2d8b038fa 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "scoping/default" -require_relative "scoping/named" +require "active_record/scoping/default" +require "active_record/scoping/named" module ActiveRecord # This class is used to create a table that keeps track of which migrations diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 84265aa9e3..2f91884f59 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -101,7 +101,7 @@ module ActiveRecord def error_class if configuration["adapter"].include?("jdbc") - require_relative "../railties/jdbcmysql_error" + require "active_record/railties/jdbcmysql_error" ArJdbcMySQL::Error elsif defined?(Mysql2) Mysql2::Error diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index fa22df92b8..c303186ef2 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -2,21 +2,21 @@ require "active_model/type" -require_relative "type/internal/timezone" +require "active_record/type/internal/timezone" -require_relative "type/date" -require_relative "type/date_time" -require_relative "type/decimal_without_scale" -require_relative "type/json" -require_relative "type/time" -require_relative "type/text" -require_relative "type/unsigned_integer" +require "active_record/type/date" +require "active_record/type/date_time" +require "active_record/type/decimal_without_scale" +require "active_record/type/json" +require "active_record/type/time" +require "active_record/type/text" +require "active_record/type/unsigned_integer" -require_relative "type/serialized" -require_relative "type/adapter_specific_registry" +require "active_record/type/serialized" +require "active_record/type/adapter_specific_registry" -require_relative "type/type_map" -require_relative "type/hash_lookup_type_map" +require "active_record/type/type_map" +require "active_record/type/hash_lookup_type_map" module ActiveRecord module Type diff --git a/activerecord/lib/active_record/type_caster.rb b/activerecord/lib/active_record/type_caster.rb index ed2e4fb79c..2e5f45fa3d 100644 --- a/activerecord/lib/active_record/type_caster.rb +++ b/activerecord/lib/active_record/type_caster.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "type_caster/map" -require_relative "type_caster/connection" +require "active_record/type_caster/map" +require "active_record/type_caster/connection" module ActiveRecord module TypeCaster # :nodoc: diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 3f5c879f2f..ca27a3f0ab 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -86,8 +86,8 @@ module ActiveRecord end end -require_relative "validations/associated" -require_relative "validations/uniqueness" -require_relative "validations/presence" -require_relative "validations/absence" -require_relative "validations/length" +require "active_record/validations/associated" +require "active_record/validations/uniqueness" +require "active_record/validations/presence" +require "active_record/validations/absence" +require "active_record/validations/length" -- cgit v1.2.3 From 9fedd013475456dc9bdbf0be45a194cc5fc2566e Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 23 Oct 2017 21:42:11 +0900 Subject: Ensure associations doesn't table name collide with string joins Currently we have no test for alias tracking with string joins. I've add test case for that to catch a future regression. --- activerecord/lib/active_record/associations/alias_tracker.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index a8d8ee268a..6bac5fd3f5 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -19,14 +19,16 @@ module ActiveRecord end def self.initial_count_for(connection, name, table_joins) - # quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase - quoted_name = connection.quote_table_name(name).downcase + quoted_name = nil counts = table_joins.map do |join| if join.is_a?(Arel::Nodes::StringJoin) + # quoted_name should be case ignored as some database adapters (Oracle) return quoted name in uppercase + quoted_name ||= connection.quote_table_name(name) + # Table names + table aliases - join.left.downcase.scan( - /join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/ + join.left.scan( + /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i ).size elsif join.respond_to? :left join.left.table_name == name ? 1 : 0 -- cgit v1.2.3 From 40cadf52db5e21349f6ecd9c4dc9b89d42ebc986 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 23 Oct 2017 22:20:58 +0900 Subject: Fix duplicate aliases when using both INNER/LEFT JOINs It should be shared the count of alias tracking in both INNER/LEFT JOINs to avoid duplicate aliases. Fixes #30504. Closes #30410. --- activerecord/lib/active_record/relation/query_methods.rb | 7 ++++--- 1 file changed, 4 insertions(+), 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 269790cdba..897ff5c8af 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -924,7 +924,7 @@ module ActiveRecord def build_arel(aliases) arel = Arel::SelectManager.new(table) - build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? + aliases = build_joins(arel, joins_values.flatten, aliases) unless joins_values.empty? build_left_outer_joins(arel, left_outer_joins_values.flatten, aliases) unless left_outer_joins_values.empty? arel.where(where_clause.ast) unless where_clause.empty? @@ -1011,9 +1011,10 @@ module ActiveRecord string_joins = buckets[:string_join].map(&:strip).uniq join_list = join_nodes + convert_join_strings_to_ast(manager, string_joins) + alias_tracker = alias_tracker(join_list, aliases) join_dependency = ActiveRecord::Associations::JoinDependency.new( - klass, table, association_joins, alias_tracker(join_list, aliases) + klass, table, association_joins, alias_tracker ) joins = join_dependency.join_constraints(stashed_association_joins, join_type) @@ -1021,7 +1022,7 @@ module ActiveRecord manager.join_sources.concat(join_list) - manager + alias_tracker.aliases end def convert_join_strings_to_ast(table, joins) -- cgit v1.2.3 From 82472b3922bda2f337a79cef961b4760d04f9689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 17 Jul 2017 17:46:07 -0400 Subject: Remove deprecated support to `quoted_id` when typecasting an Active Record object --- .../connection_adapters/abstract/quoting.rb | 17 ----------------- activerecord/lib/active_record/sanitization.rb | 5 ----- 2 files changed, 22 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 9ad04c3216..92e46ccf9f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb @@ -11,19 +11,6 @@ module ActiveRecord def quote(value) value = id_value_for_database(value) if value.is_a?(Base) - if value.respond_to?(:quoted_id) - at = value.method(:quoted_id).source_location - at &&= " at %s:%d" % at - - owner = value.method(:quoted_id).owner.to_s - klass = value.class.to_s - klass += "(#{owner})" unless owner == klass - - ActiveSupport::Deprecation.warn \ - "Defining #quoted_id is deprecated and will be ignored in Rails 5.2. (defined on #{klass}#{at})" - return value.quoted_id - end - if value.respond_to?(:value_for_database) value = value.value_for_database end @@ -37,10 +24,6 @@ module ActiveRecord def type_cast(value, column = nil) value = id_value_for_database(value) if value.is_a?(Base) - if value.respond_to?(:quoted_id) && value.respond_to?(:id) - return value.id - end - if column value = type_cast_from_column(column, value) end diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 91a4f1fad6..3482d32059 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -207,10 +207,5 @@ module ActiveRecord end end end - - def quoted_id # :nodoc: - self.class.connection.quote(@attributes[self.class.primary_key].value_for_database) - end - deprecate :quoted_id end end -- cgit v1.2.3 From 8f5b34df81175e30f68879479243fbce966122d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 17 Jul 2017 17:58:56 -0400 Subject: Remove deprecated argument `default` from `index_name_exists?` --- .../connection_adapters/abstract/schema_statements.rb | 7 +------ .../connection_adapters/postgresql/schema_statements.rb | 7 +------ 2 files changed, 2 insertions(+), 12 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 24b2234c67..4169883443 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -806,12 +806,7 @@ module ActiveRecord end # Verifies the existence of an index with a given name. - def index_name_exists?(table_name, index_name, default = nil) - unless default.nil? - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing default to #index_name_exists? is deprecated without replacement. - MSG - end + def index_name_exists?(table_name, index_name) index_name = index_name.to_s indexes(table_name).detect { |i| i.name == index_name } end 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 79cb4e276d..d90dc8a99c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -66,12 +66,7 @@ module ActiveRecord end # Verifies existence of an index with a given name. - def index_name_exists?(table_name, index_name, default = nil) - unless default.nil? - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing default to #index_name_exists? is deprecated without replacement. - MSG - end + def index_name_exists?(table_name, index_name) table = quoted_scope(table_name) index = quoted_scope(index_name) -- cgit v1.2.3 From e65aff70696be52b46ebe57207ebd8bb2cfcdbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 23 Aug 2017 16:07:48 -0400 Subject: Remove deprecated support to passing a class to `:class_name` on associations --- activerecord/lib/active_record/associations.rb | 4 ++-- activerecord/lib/active_record/reflection.rb | 9 +-------- 2 files changed, 3 insertions(+), 10 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 2723b3dc03..d1c2286227 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1848,7 +1848,7 @@ module ActiveRecord builder = Builder::HasAndBelongsToMany.new name, self, options - join_model = ActiveSupport::Deprecation.silence { builder.through_model } + join_model = builder.through_model const_set join_model.name, join_model private_constant join_model.name @@ -1877,7 +1877,7 @@ module ActiveRecord hm_options[k] = options[k] if options.key? k end - ActiveSupport::Deprecation.silence { has_many name, scope, hm_options, &extension } + has_many name, scope, hm_options, &extension _reflections[name.to_s].parent_reflection = habtm_reflection end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 97adfb4352..a60dad91d6 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -431,14 +431,7 @@ module ActiveRecord @association_scope_cache = Concurrent::Map.new if options[:class_name] && options[:class_name].class == Class - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing a class to the `class_name` is deprecated and will raise - an ArgumentError in Rails 5.2. It eagerloads more classes than - necessary and potentially creates circular dependencies. - - Please pass the class name as a string: - `#{macro} :#{name}, class_name: '#{options[:class_name]}'` - MSG + raise ArgumentError, "A class was passed to `:class_name` but we are expecting a string." end end -- cgit v1.2.3 From 63cf15877bae859ff7b4ebaf05186f3ca79c1863 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 23 Aug 2017 16:08:34 -0400 Subject: Rase when calling `lock!` in a dirty record --- activerecord/lib/active_record/locking/pessimistic.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/locking/pessimistic.rb b/activerecord/lib/active_record/locking/pessimistic.rb index 72bccd4906..bb85c47e06 100644 --- a/activerecord/lib/active_record/locking/pessimistic.rb +++ b/activerecord/lib/active_record/locking/pessimistic.rb @@ -63,12 +63,13 @@ module ActiveRecord def lock!(lock = true) if persisted? if changed? - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Locking a record with unpersisted changes is deprecated and will raise an - exception in Rails 5.2. Use `save` to persist the changes, or `reload` to - discard them explicitly. + raise(<<-MSG.squish) + Locking a record with unpersisted changes is not supported. Use + `save` to persist the changes, or `reload` to discard them + explicitly. MSG end + reload(lock: lock) end self -- cgit v1.2.3 From c9660b5777707658c414b430753029cd9bc39934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Wed, 23 Aug 2017 17:47:04 -0400 Subject: Remove deprecated methods `initialize_schema_migrations_table` and `initialize_internal_metadata_table` --- .../connection_adapters/abstract/schema_statements.rb | 10 ---------- 1 file changed, 10 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 4169883443..1970cf5de0 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1015,16 +1015,6 @@ module ActiveRecord insert_versions_sql(versions) if versions.any? end - def initialize_schema_migrations_table # :nodoc: - ActiveRecord::SchemaMigration.create_table - end - deprecate :initialize_schema_migrations_table - - def initialize_internal_metadata_table # :nodoc: - ActiveRecord::InternalMetadata.create_table - end - deprecate :initialize_internal_metadata_table - def internal_string_options_for_primary_key # :nodoc: { primary_key: true } end -- cgit v1.2.3 From 9438c144b1893f2a59ec0924afe4d46bd8d5ffdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 15 Sep 2017 13:01:00 -0300 Subject: Remove deprecated method `supports_migrations?` --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 5 ----- 1 file changed, 5 deletions(-) (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 f2fe4e2c30..ece01500e7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -196,11 +196,6 @@ module ActiveRecord self.class::ADAPTER_NAME end - def supports_migrations? # :nodoc: - true - end - deprecate :supports_migrations? - def supports_primary_key? # :nodoc: true end -- cgit v1.2.3 From c56ff22fc6e97df4656ddc22909d9bf8b0c2cbb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 15 Sep 2017 13:04:08 -0300 Subject: Remove deprecated method `supports_primary_key?` --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 5 ----- 1 file changed, 5 deletions(-) (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 ece01500e7..a848dacff3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -196,11 +196,6 @@ module ActiveRecord self.class::ADAPTER_NAME end - def supports_primary_key? # :nodoc: - true - end - deprecate :supports_primary_key? - # Does this adapter support DDL rollbacks in transactions? That is, would # CREATE TABLE or ALTER TABLE get rolled back by a transaction? def supports_ddl_transactions? -- cgit v1.2.3 From 7df6e3f3cbdea9a0460ddbab445c81fbb1cfd012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 15 Sep 2017 13:06:24 -0300 Subject: Remove deprecated method `ActiveRecord::Migrator.schema_migrations_table_name` --- activerecord/lib/active_record/migration.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 8845e26ab7..1485687053 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1026,11 +1026,6 @@ module ActiveRecord new(:up, migrations(migrations_paths), nil) end - def schema_migrations_table_name - SchemaMigration.table_name - end - deprecate :schema_migrations_table_name - def get_all_versions(connection = Base.connection) if SchemaMigration.table_exists? SchemaMigration.all_versions.map(&:to_i) -- cgit v1.2.3 From d6b779ecebe57f6629352c34bfd6c442ac8fba0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 15 Sep 2017 13:51:54 -0300 Subject: Remove deprecated argument `name` from `#indexes` --- .../connection_adapters/abstract/schema_statements.rb | 2 +- .../connection_adapters/mysql/schema_statements.rb | 8 +------- .../connection_adapters/postgresql/schema_statements.rb | 10 +--------- .../connection_adapters/sqlite3/schema_statements.rb | 8 +------- 4 files changed, 4 insertions(+), 24 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 1970cf5de0..6698047ad6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -79,7 +79,7 @@ module ActiveRecord end # Returns an array of indexes for the given table. - def indexes(table_name, name = nil) + def indexes(table_name) raise NotImplementedError, "#indexes is not implemented" end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb index 759493e3bd..a15c7d1787 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -5,13 +5,7 @@ module ActiveRecord module MySQL module SchemaStatements # :nodoc: # Returns an array of indexes for the given table. - def indexes(table_name, name = nil) - if name - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing name to #indexes is deprecated without replacement. - MSG - end - + def indexes(table_name) indexes = [] current_index = nil execute_and_free("SHOW KEYS FROM #{quote_table_name(table_name)}", "SCHEMA") do |result| 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 d90dc8a99c..846e721983 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/string/strip" - module ActiveRecord module ConnectionAdapters module PostgreSQL @@ -84,13 +82,7 @@ module ActiveRecord end # Returns an array of indexes for the given table. - def indexes(table_name, name = nil) # :nodoc: - if name - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing name to #indexes is deprecated without replacement. - MSG - end - + def indexes(table_name) # :nodoc: scope = quoted_scope(table_name) result = query(<<-SQL, "SCHEMA") diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb index f4e55147df..f0d702136d 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -5,13 +5,7 @@ module ActiveRecord module SQLite3 module SchemaStatements # :nodoc: # Returns an array of indexes for the given table. - def indexes(table_name, name = nil) - if name - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing name to #indexes is deprecated without replacement. - MSG - end - + def indexes(table_name) exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row| index_sql = query_value(<<-SQL, "SCHEMA") SELECT sql -- cgit v1.2.3 From 9c6ee1bed0292fc32c23dc1c68951ae64fc510be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 15 Sep 2017 13:56:25 -0300 Subject: Remove deprecated arguments from `#verify!` --- .../lib/active_record/connection_adapters/abstract_adapter.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (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 a848dacff3..e3aab8dad8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -392,10 +392,7 @@ module ActiveRecord # Checks whether the connection to the database is still active (i.e. not stale). # This is done under the hood by calling #active?. If the connection # is no longer active, then this method will reconnect to the database. - def verify!(*ignored) - if ignored.size > 0 - ActiveSupport::Deprecation.warn("Passing arguments to #verify method of the connection has no effect and has been deprecated. Please remove all arguments from the #verify method call.") - end + def verify! reconnect! unless active? end -- cgit v1.2.3 From e1066f450d1a99c9a0b4d786b202e2ca82a4c3b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 15 Sep 2017 14:13:04 -0300 Subject: Remove deprecated configuration `.error_on_ignored_order_or_limit` --- activerecord/lib/active_record/core.rb | 20 -------------------- 1 file changed, 20 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 945c4eca78..0f7a503c90 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -76,26 +76,6 @@ module ActiveRecord # scope being ignored is error-worthy, rather than a warning. mattr_accessor :error_on_ignored_order, instance_writer: false, default: false - def self.error_on_ignored_order_or_limit - ActiveSupport::Deprecation.warn(<<-MSG.squish) - The flag error_on_ignored_order_or_limit is deprecated. Limits are - now supported. Please use error_on_ignored_order instead. - MSG - error_on_ignored_order - end - - def error_on_ignored_order_or_limit - self.class.error_on_ignored_order_or_limit - end - - def self.error_on_ignored_order_or_limit=(value) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - The flag error_on_ignored_order_or_limit is deprecated. Limits are - now supported. Please use error_on_ignored_order= instead. - MSG - self.error_on_ignored_order = value - end - ## # :singleton-method: # Specify whether or not to use timestamps for migration versions -- cgit v1.2.3 From ef7784752c5c5efbe23f62d2bbcc62d4fd8aacab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 15 Sep 2017 14:20:50 -0300 Subject: Remove deprecated methd `#scope_chain` --- activerecord/lib/active_record/reflection.rb | 6 ------ 1 file changed, 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 a60dad91d6..8877f762b2 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/string/filters" -require "active_support/deprecation" require "concurrent/map" module ActiveRecord @@ -174,11 +173,6 @@ module ActiveRecord scope ? [scope] : [] end - def scope_chain - chain.map(&:scopes) - end - deprecate :scope_chain - def build_join_constraint(table, foreign_table) key = join_keys.key foreign_key = join_keys.foreign_key -- cgit v1.2.3 From 8f5413b896099f80ef46a97819fe47a820417bc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 15 Sep 2017 14:40:42 -0300 Subject: Remove deprecated method `#sanitize_conditions` --- activerecord/lib/active_record/sanitization.rb | 2 -- 1 file changed, 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 3482d32059..1c3099f55c 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -30,8 +30,6 @@ module ActiveRecord end end alias :sanitize_sql :sanitize_sql_for_conditions - alias :sanitize_conditions :sanitize_sql - deprecate sanitize_conditions: :sanitize_sql # Accepts an array, hash, or string of SQL conditions and sanitizes # them into a valid SQL fragment for a SET clause. -- cgit v1.2.3 From c792354adcbf8c966f274915c605c6713b840548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Thu, 19 Oct 2017 16:17:48 -0400 Subject: Remove deprecated `:if` and `:unless` string filter for callbacks --- activerecord/lib/active_record/callbacks.rb | 4 ++-- activerecord/lib/active_record/transactions.rb | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb index ac1070e65b..9ab2780760 100644 --- a/activerecord/lib/active_record/callbacks.rb +++ b/activerecord/lib/active_record/callbacks.rb @@ -98,9 +98,9 @@ module ActiveRecord # == Types of callbacks # # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects, - # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects + # inline methods (using a proc). Method references and callback objects # are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for - # creating mix-ins), and inline eval methods are deprecated. + # creating mix-ins). # # The method reference callbacks work by specifying a protected or private method available in the object, like this: # diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 6761f2da25..97cba5d1c7 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -285,7 +285,7 @@ module ActiveRecord fire_on = Array(options[:on]) assert_valid_transaction_action(fire_on) options[:if] = Array(options[:if]) - options[:if].unshift("transaction_include_any_action?(#{fire_on})") + options[:if].unshift(-> { transaction_include_any_action?(fire_on) }) end end -- cgit v1.2.3 From fea8d3509d65396f7c04a5e2608ef8863e1abe75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Fri, 20 Oct 2017 11:49:03 -0400 Subject: Remove text about deprecation that was already removed --- activerecord/lib/active_record/associations.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index d1c2286227..573f41d7ad 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -557,9 +557,8 @@ module ActiveRecord # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event' # end # - # Note: Joining, eager loading and preloading of these associations is not fully possible. + # Note: Joining, eager loading and preloading of these associations is not possible. # These operations happen before instance creation and the scope will be called with a +nil+ argument. - # This can lead to unexpected behavior and is deprecated. # # == Association callbacks # -- cgit v1.2.3 From 1015cab6e4db5f18deadba0a6eb2adbe82ffe585 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Mon, 23 Oct 2017 20:05:27 +0000 Subject: Remove `supports_disable_referential_integrity?` `supports_disable_referential_integrity?` used to handle if PostgreSQL database supports `ALTER TABLE DISABLE/ENABLE TRIGGER` statements. Refer https://github.com/rails/rails/commit/9a947af0e79cfb8692eb7e5ae94c1b8c40756f49 These statements have been documented since 8.1. https://www.postgresql.org/docs/8.1/static/sql-altertable.html > DISABLE/ENABLE TRIGGER Now Rails supports PostgreSQL 9.1 or higher only. No need to handle `supports_disable_referential_integrity?` anymore. Also, this method does not exist in any other adapters including AbstractAdapter. --- .../postgresql/referential_integrity.rb | 42 +++++++++------------- 1 file changed, 17 insertions(+), 25 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb index 386d22a9bd..8df91c988b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/referential_integrity.rb @@ -4,26 +4,21 @@ module ActiveRecord module ConnectionAdapters module PostgreSQL module ReferentialIntegrity # :nodoc: - def supports_disable_referential_integrity? # :nodoc: - true - end - def disable_referential_integrity # :nodoc: - if supports_disable_referential_integrity? - original_exception = nil + original_exception = nil - begin - transaction(requires_new: true) do - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) - end - rescue ActiveRecord::ActiveRecordError => e - original_exception = e + begin + transaction(requires_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} DISABLE TRIGGER ALL" }.join(";")) end + rescue ActiveRecord::ActiveRecordError => e + original_exception = e + end - begin - yield - rescue ActiveRecord::InvalidForeignKey => e - warn <<-WARNING + begin + yield + rescue ActiveRecord::InvalidForeignKey => e + warn <<-WARNING WARNING: Rails was not able to disable referential integrity. This is most likely caused due to missing permissions. @@ -32,17 +27,14 @@ Rails needs superuser privileges to disable referential integrity. cause: #{original_exception.try(:message)} WARNING - raise e - end + raise e + end - begin - transaction(requires_new: true) do - execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) - end - rescue ActiveRecord::ActiveRecordError + begin + transaction(requires_new: true) do + execute(tables.collect { |name| "ALTER TABLE #{quote_table_name(name)} ENABLE TRIGGER ALL" }.join(";")) end - else - yield + rescue ActiveRecord::ActiveRecordError end end end -- cgit v1.2.3 From 019583e939f6615590a4d6c1e4370e4e973fedff Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Tue, 24 Oct 2017 21:47:11 +0900 Subject: Ensure associations doesn't table name collide with aliased joins Currently alias tracker only refer a table name, doesn't respect an alias name. Should use `join.left.name` rather than `join.left.table_name`. --- activerecord/lib/active_record/associations/alias_tracker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 6bac5fd3f5..07bd0a273b 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -31,7 +31,7 @@ module ActiveRecord /JOIN(?:\s+\w+)?\s+(?:\S+\s+)?(?:#{quoted_name}|#{name})\sON/i ).size elsif join.respond_to? :left - join.left.table_name == name ? 1 : 0 + join.left.name == name ? 1 : 0 elsif join.is_a?(Hash) join[name] else -- cgit v1.2.3 From 82ae8369925e152d507486f7520558ac09f090e8 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Tue, 24 Oct 2017 19:03:37 +0530 Subject: Save index order :desc to schema.rb (sqlite). Fixes #30902 Although the sqlite adapter supports index sort orders, they weren't being written to db/schema.rb. --- .../connection_adapters/sqlite3/schema_statements.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb index f0d702136d..3ab9dee370 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -19,8 +19,14 @@ module ActiveRecord /\sWHERE\s+(?.+)$/i =~ index_sql - columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col| - col["name"] + columns = [] + orders = {} + exec_query("PRAGMA index_xinfo(#{quote(row['name'])})", "SCHEMA").each do |col| + # xinfo also lists non-key columns, let's filter those out + next if col["key"] == 0 + + columns << col["name"] + orders[col["name"]] = :desc if col["desc"] == 1 end IndexDefinition.new( @@ -28,6 +34,7 @@ module ActiveRecord row["name"], row["unique"] != 0, columns, + orders: orders, where: where ) end -- cgit v1.2.3 From 5cd6f1792e9710979ce53943c28b2b38dae34e98 Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Tue, 24 Oct 2017 21:45:05 +0000 Subject: `supports_extensions?` return always true since PostgreSQL 9.1 since the minimum version of PostgreSQL currently Rails supports is 9.1, there is no need to handle if `supports_extensions?` Refer https://www.postgresql.org/docs/9.1/static/sql-createextension.html "CREATE EXTENSION" --- .../active_record/connection_adapters/postgresql_adapter.rb | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) (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 e9ae861bfb..2c3c1df2a9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -337,18 +337,12 @@ module ActiveRecord end def extension_enabled?(name) - if supports_extensions? - res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA") - res.cast_values.first - end + res = exec_query("SELECT EXISTS(SELECT * FROM pg_available_extensions WHERE name = '#{name}' AND installed_version IS NOT NULL) as enabled", "SCHEMA") + res.cast_values.first end def extensions - if supports_extensions? - exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values - else - super - end + exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values end # Returns the configured supported identifier length supported by PostgreSQL -- cgit v1.2.3 From ab8c7a518eb9583dbc574ff68fd56bcccf383452 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 25 Oct 2017 14:33:51 +0530 Subject: Avoid using index_xinfo, only available in sqlite >= 3.8.9 --- .../sqlite3/schema_statements.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb index 3ab9dee370..58e5138e02 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -19,14 +19,17 @@ module ActiveRecord /\sWHERE\s+(?.+)$/i =~ index_sql - columns = [] - orders = {} - exec_query("PRAGMA index_xinfo(#{quote(row['name'])})", "SCHEMA").each do |col| - # xinfo also lists non-key columns, let's filter those out - next if col["key"] == 0 + columns = exec_query("PRAGMA index_info(#{quote(row['name'])})", "SCHEMA").map do |col| + col["name"] + end - columns << col["name"] - orders[col["name"]] = :desc if col["desc"] == 1 + # Add info on sort order for columns (only desc order is explicitly specified, asc is + # the default) + orders = {} + if index_sql # index_sql can be null in case of primary key indexes + index_sql.scan(/"(\w+)" DESC/).flatten.each { |order_column| + orders[order_column] = :desc + } end IndexDefinition.new( @@ -34,8 +37,8 @@ module ActiveRecord row["name"], row["unique"] != 0, columns, - orders: orders, - where: where + where: where, + orders: orders ) end end -- cgit v1.2.3 From cf4ba2fa46933f6b450a95c14aa175eae4a4e9ba Mon Sep 17 00:00:00 2001 From: Yasuo Honda Date: Wed, 25 Oct 2017 01:21:09 +0000 Subject: Implement `PostgreSQL::SchemaDumper#extensions` and abstract `SchemaDumper#extensions` is now an empty method. Since #30337, every database adapter has its own `SchemaDumper`. `extensions` are only supported by PostgreSQL database and postgresql database adapter. --- .../connection_adapters/postgresql/schema_dumper.rb | 12 ++++++++++++ activerecord/lib/active_record/schema_dumper.rb | 10 +--------- 2 files changed, 13 insertions(+), 9 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb index c0dbb166b7..84643d20da 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb @@ -5,6 +5,18 @@ module ActiveRecord module PostgreSQL class SchemaDumper < ConnectionAdapters::SchemaDumper # :nodoc: private + + def extensions(stream) + extensions = @connection.extensions + if extensions.any? + stream.puts " # These are extensions that must be enabled in order to support this database" + extensions.sort.each do |extension| + stream.puts " enable_extension #{extension.inspect}" + end + stream.puts + end + end + def prepare_column_options(column) spec = super spec[:array] = "true" if column.array? diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 8d0311fabd..66f7d29886 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -82,16 +82,8 @@ HEADER stream.puts "end" end + # extensions are only supported by PostgreSQL def extensions(stream) - return unless @connection.supports_extensions? - extensions = @connection.extensions - if extensions.any? - stream.puts " # These are extensions that must be enabled in order to support this database" - extensions.sort.each do |extension| - stream.puts " enable_extension #{extension.inspect}" - end - stream.puts - end end def tables(stream) -- cgit v1.2.3 From e5ce7bcbae1a9eaa40d9e094ae43e3b9226324cf Mon Sep 17 00:00:00 2001 From: Joe Van Dyk Date: Wed, 25 Oct 2017 19:52:21 -0700 Subject: remove incorrect statement about serializable transactions using serializable isolation would prevent the duplicate insert as done in the example from happening --- activerecord/lib/active_record/validations/uniqueness.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index f4ad58c087..4c2c5dd852 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -205,9 +205,7 @@ module ActiveRecord # | # Boom! We now have a duplicate # | # title! # - # This could even happen if you use transactions with the 'serializable' - # isolation level. The best way to work around this problem is to add a unique - # index to the database table using + # The best way to work around this problem is to add a unique index to the database table using # {connection.add_index}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_index]. # In the rare case that a race condition occurs, the database will guarantee # the field's uniqueness. -- cgit v1.2.3 From d95fbd7dba54cfbd282f808102dfdffa3499d669 Mon Sep 17 00:00:00 2001 From: pavel Date: Thu, 26 Oct 2017 23:16:54 +0200 Subject: fix initial count --- activerecord/lib/active_record/associations/alias_tracker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 07bd0a273b..14881cfe17 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -33,7 +33,7 @@ module ActiveRecord elsif join.respond_to? :left join.left.name == name ? 1 : 0 elsif join.is_a?(Hash) - join[name] + join.fetch(name, 0) else # this branch is reached by two tests: # -- cgit v1.2.3 From a8966cc4701dcdd982e291d63b27be736c777fc8 Mon Sep 17 00:00:00 2001 From: pavel Date: Fri, 27 Oct 2017 01:22:16 +0200 Subject: delegate scope for --- activerecord/lib/active_record/reflection.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 8877f762b2..c18214a842 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1002,7 +1002,7 @@ module ActiveRecord end class PolymorphicReflection < AbstractReflection # :nodoc: - delegate :klass, :scope, :plural_name, :type, :get_join_keys, to: :@reflection + delegate :klass, :scope, :plural_name, :type, :get_join_keys, :scope_for, to: :@reflection def initialize(reflection, previous_reflection) @reflection = reflection -- cgit v1.2.3 From c40b4428e6d3885a8adc0ceba3aeac7599c14879 Mon Sep 17 00:00:00 2001 From: Shuhei Kitagawa Date: Sat, 28 Oct 2017 17:20:38 +0900 Subject: removed unnecessary returns --- activerecord/lib/active_record/attribute_methods.rb | 2 +- activerecord/lib/active_record/counter_cache.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (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 891e556dc4..23d2aef214 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -236,7 +236,7 @@ module ActiveRecord return has_attribute?(name) end - return true + true end # Returns +true+ if the given attribute is in the attributes hash, otherwise +false+. diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb index 5005d58f1c..ee4f818cbf 100644 --- a/activerecord/lib/active_record/counter_cache.rb +++ b/activerecord/lib/active_record/counter_cache.rb @@ -53,7 +53,7 @@ module ActiveRecord unscoped.where(primary_key => object.id).update_all(updates) end - return true + true end # A generic "counter updater" implementation, intended primarily to be -- cgit v1.2.3 From cd4cbfccea281b670e527401b0e8572746e39a5d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 27 Oct 2017 23:36:09 +0900 Subject: Ordinal methods should respect loaded records We should reset partially loaded `@offsets` cache when latest records has loaded because the cache has been staled and it may not be consistent with latest records. --- activerecord/lib/active_record/relation.rb | 1 + 1 file changed, 1 insertion(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 997cfe4b5e..c16d89aa00 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -594,6 +594,7 @@ module ActiveRecord end @records.each(&:readonly!) if readonly_value + @offsets = {} unless @offsets.empty? @loaded = true @records -- cgit v1.2.3 From aa0865a8f07f889006a8b2ab1849aad6ab5f8fea Mon Sep 17 00:00:00 2001 From: suginoy Date: Sat, 28 Oct 2017 23:38:15 +0900 Subject: [ci skip]Update the documentation about the primary key type Replace the primary key type `integer` in docs with `bigint`. ref #26266 --- activerecord/lib/active_record/associations.rb | 6 ++--- .../abstract/schema_statements.rb | 30 +++++++++++----------- .../active_record/connection_adapters/column.rb | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 573f41d7ad..bc7e288500 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -483,14 +483,14 @@ module ActiveRecord # The tables for these classes could look something like: # # CREATE TABLE users ( - # id int NOT NULL auto_increment, - # account_id int default NULL, + # id bigint NOT NULL auto_increment, + # account_id bigint default NULL, # name varchar default NULL, # PRIMARY KEY (id) # ) # # CREATE TABLE accounts ( - # id int NOT NULL auto_increment, + # id bigint NOT NULL auto_increment, # name varchar default NULL, # PRIMARY KEY (id) # ) 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 6698047ad6..9b7345f7c3 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -216,7 +216,7 @@ module ActiveRecord # generates: # # CREATE TABLE suppliers ( - # id int auto_increment PRIMARY KEY + # id bigint auto_increment PRIMARY KEY # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 # # ====== Rename the primary key column @@ -228,7 +228,7 @@ module ActiveRecord # generates: # # CREATE TABLE objects ( - # guid int auto_increment PRIMARY KEY, + # guid bigint auto_increment PRIMARY KEY, # name varchar(80) # ) # @@ -255,8 +255,8 @@ module ActiveRecord # generates: # # CREATE TABLE order ( - # product_id integer NOT NULL, - # client_id integer NOT NULL + # product_id bigint NOT NULL, + # client_id bigint NOT NULL # ); # # ALTER TABLE ONLY "orders" @@ -265,15 +265,15 @@ module ActiveRecord # ====== Do not add a primary key column # # create_table(:categories_suppliers, id: false) do |t| - # t.column :category_id, :integer - # t.column :supplier_id, :integer + # t.column :category_id, :bigint + # t.column :supplier_id, :bigint # end # # generates: # # CREATE TABLE categories_suppliers ( - # category_id int, - # supplier_id int + # category_id bigint, + # supplier_id bigint # ) # # ====== Create a temporary table based on a query @@ -361,8 +361,8 @@ module ActiveRecord # generates: # # CREATE TABLE assemblies_parts ( - # assembly_id int NOT NULL, - # part_id int NOT NULL, + # assembly_id bigint NOT NULL, + # part_id bigint NOT NULL, # ) ENGINE=InnoDB DEFAULT CHARSET=utf8 # def create_join_table(table_1, table_2, column_options: {}, **options) @@ -432,7 +432,7 @@ module ActiveRecord # t.references :company # end # - # Creates a company_id(integer) column. + # Creates a company_id(bigint) column. # # ====== Add a polymorphic foreign key column # @@ -440,7 +440,7 @@ module ActiveRecord # t.belongs_to :company, polymorphic: true # end # - # Creates company_type(varchar) and company_id(integer) columns. + # Creates company_type(varchar) and company_id(bigint) columns. # # ====== Remove a column # @@ -811,14 +811,14 @@ module ActiveRecord indexes(table_name).detect { |i| i.name == index_name } end - # Adds a reference. The reference column is an integer by default, + # Adds a reference. The reference column is a bigint by default, # the :type option can be used to specify a different type. # Optionally adds a +_type+ column, if :polymorphic option is provided. # #add_reference and #add_belongs_to are acceptable. # # The +options+ hash can include the following keys: # [:type] - # The reference column type. Defaults to +:integer+. + # The reference column type. Defaults to +:bigint+. # [:index] # Add an appropriate index. Defaults to true. # See #add_index for usage of this option. @@ -829,7 +829,7 @@ module ActiveRecord # [:null] # Whether the column allows nulls. Defaults to true. # - # ====== Create a user_id integer column + # ====== Create a user_id bigint column # # add_reference(:products, :user) # diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb index 28d949b503..5d81de9fe1 100644 --- a/activerecord/lib/active_record/connection_adapters/column.rb +++ b/activerecord/lib/active_record/connection_adapters/column.rb @@ -11,7 +11,7 @@ module ActiveRecord # Instantiates a new column in the table. # - # +name+ is the column's name, such as supplier_id in supplier_id int. + # +name+ is the column's name, such as supplier_id in supplier_id bigint. # +default+ is the type-casted default value, such as +new+ in sales_stage varchar(20) default 'new'. # +sql_type_metadata+ is various information about the type of the column # +null+ determines if this column allows +NULL+ values. -- cgit v1.2.3 From 46a7b2e20c24f90591495f28786774132f324dbc Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 30 Oct 2017 21:29:26 +0900 Subject: `PolymorphicReflection#scopes` is no longer used since a5651eb5 --- activerecord/lib/active_record/reflection.rb | 5 ----- 1 file changed, 5 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index c18214a842..8633809757 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -1009,11 +1009,6 @@ module ActiveRecord @previous_reflection = previous_reflection end - def scopes - scopes = @previous_reflection.scopes - scopes << @previous_reflection.source_type_scope - end - def join_scopes(table, predicate_builder) # :nodoc: scopes = @previous_reflection.join_scopes(table, predicate_builder) + super scopes << @previous_reflection.source_type_scope -- cgit v1.2.3 From 13c5aa818e9284fe30f83469b340e579195bda3f Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 30 Oct 2017 22:06:12 +0900 Subject: `source_type_scope` should respect correct table alias `join_scopes` in `PolymorphicReflection` is passed aliased `table`, so it should be respected for `source_type_scope`. Closes #13969. Fixes #13920. Fixes #15190. --- activerecord/lib/active_record/reflection.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index 8633809757..87bfd75bca 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -839,10 +839,6 @@ module ActiveRecord source_reflection.join_scopes(table, predicate_builder) + super end - def source_type_scope - through_reflection.klass.where(foreign_type => options[:source_type]) - end - def has_scope? scope || options[:source_type] || source_reflection.has_scope? || @@ -1011,15 +1007,15 @@ module ActiveRecord def join_scopes(table, predicate_builder) # :nodoc: scopes = @previous_reflection.join_scopes(table, predicate_builder) + super - scopes << @previous_reflection.source_type_scope + scopes << build_scope(table, predicate_builder).instance_exec(nil, &source_type_scope) end def constraints - @reflection.constraints + [source_type_info] + @reflection.constraints + [source_type_scope] end private - def source_type_info + def source_type_scope type = @previous_reflection.foreign_type source_type = @previous_reflection.options[:source_type] lambda { |object| where(type => source_type) } -- cgit v1.2.3 From c4867b88f6eed16230a90077b82ad02293ea45b3 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Fri, 25 Aug 2017 23:05:21 +0300 Subject: Raise error if unsupported charset for mysql `blog$ bin/rails db:create` Before: ``` Couldn't create database for {"adapter"=>"mysql2", "encoding"=>"utf42", "pool"=>5, "username"=>"root", "password"=>nil, "socket"=>"/var/run/mysqld/mysqld.sock", "database"=>"blog_development"}, {:charset=>"utf42"} (If you set the charset manually, make sure you have a matching collation) Created database 'blog_development' Couldn't create database for {"adapter"=>"mysql2", "encoding"=>"utf42", "pool"=>5, "username"=>"root", "password"=>nil, "socket"=>"/var/run/mysqld/mysqld.sock", "database"=>"blog_test"}, {:charset=>"utf42"} (If you set the charset manually, make sure you have a matching collation) Created database 'blog_test' ``` After: ``` Unsupported charset: '"utf42"' Couldn't create database for {"adapter"=>"mysql2", "encoding"=>"utf42", "pool"=>5, "username"=>"root", "password"=>nil, "socket"=>"/var/run/mysqld/mysqld.sock", "database"=>"blog_development"} rails aborted! Mysql2::Error: Unsupported charset: '"utf42"' ... (stack trace) ... bin/rails:4:in `
' Tasks: TOP => db:create (See full trace by running task with --trace) ``` Closes #29683 Related to #27398 --- activerecord/lib/active_record/tasks/mysql_database_tasks.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 2f91884f59..d6a46f7743 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -31,9 +31,7 @@ module ActiveRecord end establish_connection configuration else - $stderr.puts error.inspect - $stderr.puts "Couldn't create database for #{configuration.inspect}, #{creation_options.inspect}" - $stderr.puts "(If you set the charset manually, make sure you have a matching collation)" if configuration["encoding"] + raise end end -- cgit v1.2.3 From a37d03c4b08451765acc721b51a4b5b7eab7e8cc Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Sat, 26 Aug 2017 00:06:10 +0300 Subject: Simplify implementation of `MySQLDatabaseTasks` Don't process MySQL ERROR 1045, raise error instead Make behavior of `MySQLDatabaseTasks` more consistent with behavior of `PostgreSQLDatabaseTasks` --- .../lib/active_record/railties/jdbcmysql_error.rb | 18 --------- .../active_record/tasks/mysql_database_tasks.rb | 45 ---------------------- 2 files changed, 63 deletions(-) delete mode 100644 activerecord/lib/active_record/railties/jdbcmysql_error.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/railties/jdbcmysql_error.rb b/activerecord/lib/active_record/railties/jdbcmysql_error.rb deleted file mode 100644 index 72c75ddd52..0000000000 --- a/activerecord/lib/active_record/railties/jdbcmysql_error.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -#FIXME Remove if ArJdbcMysql will give. -module ArJdbcMySQL #:nodoc: - class Error < StandardError #:nodoc: - attr_accessor :error_number, :sql_state - - def initialize(msg) - super - @error_number = nil - @sql_state = nil - end - - # Mysql gem compatibility - alias_method :errno, :error_number - alias_method :error, :message - end -end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index d6a46f7743..e697fa6def 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -3,8 +3,6 @@ module ActiveRecord module Tasks # :nodoc: class MySQLDatabaseTasks # :nodoc: - ACCESS_DENIED_ERROR = 1045 - delegate :connection, :establish_connection, to: ActiveRecord::Base def initialize(configuration) @@ -21,18 +19,6 @@ module ActiveRecord else raise end - rescue error_class => error - if error.respond_to?(:errno) && error.errno == ACCESS_DENIED_ERROR - $stdout.print error.message - establish_connection root_configuration_without_database - connection.create_database configuration["database"], creation_options - if configuration["username"] != "root" - connection.execute grant_statement.gsub(/\s+/, " ").strip - end - establish_connection configuration - else - raise - end end def drop @@ -97,37 +83,6 @@ module ActiveRecord end end - def error_class - if configuration["adapter"].include?("jdbc") - require "active_record/railties/jdbcmysql_error" - ArJdbcMySQL::Error - elsif defined?(Mysql2) - Mysql2::Error - else - StandardError - end - end - - def grant_statement - <<-SQL -GRANT ALL PRIVILEGES ON `#{configuration['database']}`.* - TO '#{configuration['username']}'@'localhost' -IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; - SQL - end - - def root_configuration_without_database - configuration_without_database.merge( - "username" => "root", - "password" => root_password - ) - end - - def root_password - $stdout.print "Please provide the root password for your MySQL installation\n>" - $stdin.gets.strip - end - def prepare_command_options args = { "host" => "--host", -- cgit v1.2.3 From 0d7ab973e15b476ed42471395027e329f95c8951 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Fri, 3 Nov 2017 10:09:43 +0900 Subject: Remove unused classes * `HasManyThroughCantDissociateNewRecords` and `HasManyThroughCantAssociateNewRecords` are no longer used since f6b12c1. * `ReadOnlyAssociation` is no longer used since 0da426b. --- activerecord/lib/active_record/associations.rb | 30 -------------------------- 1 file changed, 30 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index bc7e288500..661605d3e5 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -140,26 +140,6 @@ module ActiveRecord class HasOneThroughCantAssociateThroughHasOneOrManyReflection < ThroughCantAssociateThroughHasOneOrManyReflection #:nodoc: end - class HasManyThroughCantAssociateNewRecords < ActiveRecordError #:nodoc: - def initialize(owner = nil, reflection = nil) - if owner && reflection - super("Cannot associate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to create the has_many :through record associating them.") - else - super("Cannot associate new records.") - end - end - end - - class HasManyThroughCantDissociateNewRecords < ActiveRecordError #:nodoc: - def initialize(owner = nil, reflection = nil) - if owner && reflection - super("Cannot dissociate new records through '#{owner.class.name}##{reflection.name}' on '#{reflection.source_reflection.class_name rescue nil}##{reflection.source_reflection.name rescue nil}'. Both records must have an id in order to delete the has_many :through record associating them.") - else - super("Cannot dissociate new records.") - end - end - end - class ThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc: def initialize(owner = nil, reflection = nil) if owner && reflection @@ -189,16 +169,6 @@ module ActiveRecord end end - class ReadOnlyAssociation < ActiveRecordError #:nodoc: - def initialize(reflection = nil) - if reflection - super("Cannot add to a has_many :through association. Try adding to #{reflection.through_reflection.name.inspect}.") - else - super("Read-only reflection error.") - end - end - end - # This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations # (has_many, has_one) when there is at least 1 child associated instance. # ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project -- cgit v1.2.3 From e3a295664681ea0480a9c95f6bfc776a113ff926 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 1 Nov 2017 10:36:47 +0900 Subject: Ensure `apply_join_dependency` for `collection_cache_key` if eager-loading is needed Fixes #30315. --- activerecord/lib/active_record/collection_cache_key.rb | 3 +++ activerecord/lib/active_record/relation/calculations.rb | 4 ++-- activerecord/lib/active_record/relation/finder_methods.rb | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/collection_cache_key.rb b/activerecord/lib/active_record/collection_cache_key.rb index f3e6516414..88b398ad45 100644 --- a/activerecord/lib/active_record/collection_cache_key.rb +++ b/activerecord/lib/active_record/collection_cache_key.rb @@ -12,6 +12,9 @@ module ActiveRecord timestamp = collection.max_by(×tamp_column)._read_attribute(timestamp_column) end else + if collection.eager_loading? + collection = collection.send(:apply_join_dependency) + end column_type = type_for_attribute(timestamp_column.to_s) column = connection.column_name_from_arel_node(collection.arel_attribute(timestamp_column)) select_values = "COUNT(*) AS #{connection.quote_column_name("size")}, MAX(%s) AS timestamp" diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 116bddce85..11256ab3d9 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -130,7 +130,7 @@ module ActiveRecord # end def calculate(operation, column_name) if has_include?(column_name) - relation = apply_join_dependency(construct_join_dependency) + relation = apply_join_dependency relation.distinct! if operation.to_s.downcase == "count" relation.calculate(operation, column_name) @@ -180,7 +180,7 @@ module ActiveRecord end if has_include?(column_names.first) - relation = apply_join_dependency(construct_join_dependency) + relation = apply_join_dependency relation.pluck(*column_names) else relation = spawn diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 707245bab2..18566b5662 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -394,7 +394,7 @@ module ActiveRecord ) end - def apply_join_dependency(join_dependency) + def apply_join_dependency(join_dependency = construct_join_dependency) relation = except(:includes, :eager_load, :preload).joins!(join_dependency) if using_limitable_reflections?(join_dependency.reflections) -- cgit v1.2.3 From 8a2ee3d8c6921ce34e597658e3f2b43b41423d1d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 16 Oct 2017 01:33:40 +0900 Subject: Ensure `apply_join_dependency` for `update_all` and `delete_all` if eager-loading is needed If a relation has eager-loading values, `count` and `exists?` works properly, but `update_all` and `delete_all` doesn't work due to missing `apply_join_dependency`. It should be applied to work consistently. Fixes #28863. --- activerecord/lib/active_record/relation.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 997cfe4b5e..7615fb6ee9 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -359,6 +359,11 @@ module ActiveRecord def update_all(updates) raise ArgumentError, "Empty list of attributes to change" if updates.blank? + if eager_loading? + relation = apply_join_dependency + return relation.update_all(updates) + end + stmt = Arel::UpdateManager.new stmt.set Arel.sql(@klass.send(:sanitize_sql_for_assignment, updates)) @@ -423,6 +428,11 @@ module ActiveRecord raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}") end + if eager_loading? + relation = apply_join_dependency + return relation.delete_all + end + stmt = Arel::DeleteManager.new stmt.from(table) -- cgit v1.2.3 From e617fb57f5a388d5f0a47fd5e576588dd10066b0 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 30 Oct 2017 23:55:48 +0900 Subject: Fix preloading polymorphic association when through association has already loaded If through association has already loaded, `source_type` is ignored to loaded through records. The loaded records should be filtered by `source_type` in that case. Fixes #30904. --- .../associations/preloader/through_association.rb | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (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 fa32cc5553..5bd49b041a 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -13,18 +13,30 @@ module ActiveRecord end def associated_records_by_owner(preloader) + already_loaded = owners.first.association(through_reflection.name).loaded? through_scope = through_scope() - preloader.preload(owners, - through_reflection.name, - through_scope) + unless already_loaded + preloader.preload(owners, through_reflection.name, through_scope) + end through_records = owners.map do |owner| center = owner.association(through_reflection.name).target [owner, Array(center)] end - reset_association(owners, through_reflection.name, through_scope) + if already_loaded + if source_type = reflection.options[:source_type] + through_records.map! do |owner, center| + center = center.select do |record| + record[reflection.foreign_type] == source_type + end + [owner, center] + end + end + else + reset_association(owners, through_reflection.name, through_scope) + end middle_records = through_records.flat_map(&:last) -- cgit v1.2.3 From e0bef22665f93e88f6b2f3ac6bd55543ed0d6343 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 6 Nov 2017 06:40:36 +0900 Subject: Fix preloading polymorphic multi-level through association This is partially fixed by e617fb57 when through association has already loaded. Otherwise, second level through association should respect `preload_scope`. Fixes #30242. Closes #30076. [Ryuta Kamizono & CicholGricenchos] --- .../active_record/associations/preloader/through_association.rb | 8 +++++++- 1 file changed, 7 insertions(+), 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 5bd49b041a..b16fca7dc9 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -40,7 +40,11 @@ module ActiveRecord middle_records = through_records.flat_map(&:last) - reflection_scope = reflection_scope() if reflection.scope + if preload_scope + reflection_scope = reflection_scope().merge(preload_scope) + elsif reflection.scope + reflection_scope = reflection_scope() + end preloaders = preloader.preload(middle_records, source_reflection.name, @@ -70,6 +74,8 @@ module ActiveRecord rhs_records end end + end.tap do + reset_association(middle_records, source_reflection.name, preload_scope) end end -- cgit v1.2.3 From ffc1ed9bd54f3cf746f7ddf798e000fc025edef6 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 6 Nov 2017 08:29:40 +0900 Subject: `scoping` should respect current class and STI constraint (#29199) A relation includes `klass`, so it can not be used as it is if current class is different from `current_scope.klass`. It should be created new relation by current class to respect the klass and STI constraint. Fixes #17603. Fixes #23576. --- activerecord/lib/active_record/scoping/named.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 6fa096c1fe..310af72c41 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -24,8 +24,14 @@ module ActiveRecord # You can define a scope that applies to all finders using # {default_scope}[rdoc-ref:Scoping::Default::ClassMethods#default_scope]. def all + current_scope = self.current_scope + if current_scope - current_scope.clone + if self == current_scope.klass + current_scope.clone + else + relation.merge!(current_scope) + end else default_scoped end -- cgit v1.2.3 From edcc0a714d911468caf7db0879fc7a3d1185ee3c Mon Sep 17 00:00:00 2001 From: Bogdan Gusiev Date: Mon, 6 Nov 2017 17:32:54 +0200 Subject: Refactor Preloader Code --- .../associations/preloader/association.rb | 4 +- .../associations/preloader/has_many_through.rb | 10 -- .../associations/preloader/through_association.rb | 107 ++++++++------------- 3 files changed, 40 insertions(+), 81 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 607d376a08..b0c41c7d5f 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -17,7 +17,7 @@ module ActiveRecord end def run(preloader) - associated_records_by_owner(preloader).each do |owner, records| + associated_records_by_owner(preloader) do |owner, records| associate_records_to_owner(owner, records) end end @@ -41,7 +41,7 @@ module ActiveRecord end owners.each_with_object({}) do |owner, result| - result[owner] = records[convert_key(owner[owner_key_name])] || [] + yield(owner, records[convert_key(owner[owner_key_name])] || []) end end diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb index 0639fdca44..3e17d07a33 100644 --- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb +++ b/activerecord/lib/active_record/associations/preloader/has_many_through.rb @@ -5,16 +5,6 @@ module ActiveRecord class Preloader class HasManyThrough < CollectionAssociation #:nodoc: include ThroughAssociation - - def associated_records_by_owner(preloader) - records_by_owner = super - - if reflection_scope.distinct_value - records_by_owner.each_value(&:uniq!) - end - - records_by_owner - end end end end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index b16fca7dc9..1bb7e98231 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -13,86 +13,45 @@ module ActiveRecord end def associated_records_by_owner(preloader) - already_loaded = owners.first.association(through_reflection.name).loaded? - through_scope = through_scope() - - unless already_loaded - preloader.preload(owners, through_reflection.name, through_scope) - end - - through_records = owners.map do |owner| - center = owner.association(through_reflection.name).target - [owner, Array(center)] - end + already_loaded = owners.first.association(through_reflection.name).loaded? + through_scope = through_scope() + reflection_scope = target_reflection_scope + through_preloaders = preloader.preload(owners, through_reflection.name, through_scope) + middle_records = through_preloaders.flat_map(&:preloaded_records) + preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope) + @preloaded_records = preloaders.flat_map(&:preloaded_records) - if already_loaded - if source_type = reflection.options[:source_type] - through_records.map! do |owner, center| - center = center.select do |record| + owners.each do |owner| + through_records = Array(owner.association(through_reflection.name).target) + if already_loaded + if source_type = reflection.options[:source_type] + through_records = through_records.select do |record| record[reflection.foreign_type] == source_type end - [owner, center] end + else + owner.association(through_reflection.name).reset if through_scope end - else - reset_association(owners, through_reflection.name, through_scope) - end - - middle_records = through_records.flat_map(&:last) - - if preload_scope - reflection_scope = reflection_scope().merge(preload_scope) - elsif reflection.scope - reflection_scope = reflection_scope() - end - - preloaders = preloader.preload(middle_records, - source_reflection.name, - reflection_scope) - - @preloaded_records = preloaders.flat_map(&:preloaded_records) - - middle_to_pl = preloaders.each_with_object({}) do |pl, h| - pl.owners.each { |middle| - h[middle] = pl - } - end - - through_records.each_with_object({}) do |(lhs, center), records_by_owner| - pl_to_middle = center.group_by { |record| middle_to_pl[record] } - - records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles| - rhs_records = middles.flat_map { |r| - r.association(source_reflection.name).target - }.compact - - # Respect the order on `reflection_scope` if it exists, else use the natural order. - if reflection_scope && !reflection_scope.order_values.empty? - @id_map ||= id_to_index_map @preloaded_records - rhs_records.sort_by { |rhs| @id_map[rhs] } - else - rhs_records - end + result = through_records.flat_map do |record| + association = record.association(source_reflection.name) + target = association.target + association.reset if preload_scope + target end - end.tap do - reset_association(middle_records, source_reflection.name, preload_scope) + result.compact! + if reflection_scope + result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any? + result.uniq! if reflection_scope.distinct_value + end + yield(owner, result) end end private - def id_to_index_map(ids) - id_map = {} - ids.each_with_index { |id, index| id_map[id] = index } - id_map - end - - def reset_association(owners, association_name, should_reset) - # Don't cache the association - we would only be caching a subset - if should_reset - owners.each { |owner| - owner.association(association_name).reset - } + def preload_index + @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index| + result[id] = index end end @@ -133,6 +92,16 @@ module ActiveRecord scope unless scope.empty_scope? end + + def target_reflection_scope + if preload_scope + reflection_scope.merge(preload_scope) + elsif reflection.scope + reflection_scope + else + nil + end + end end end end -- cgit v1.2.3 From 01c703248396528d9f3398d0f9d0143831e9d0dc Mon Sep 17 00:00:00 2001 From: Keenan Brock Date: Thu, 9 Mar 2017 11:48:44 -0500 Subject: Properly check transaction in persistence ``` [NoMethodError]: undefined method `state' for nil:NilClass Method:[rescue in block in refresh] ``` In `within_new_transaction`, there is the possibility that `begin_transaction` returns a `nil`. (i.e.: so `transaction = nil`) So this method is checking `transaction` for nil in 2 spots. Unfortunately, there is one line that is not checking `transaction` for `nil` That line, `commit_transaction`, throws an exception for us in AR 5.0.0.1 The problem with the method is finally realized in the error checking itself. it calls `transaction.state` (i.e.: nil.state) and that is the final exception raised. The actual underlying (user) issue is hidden by this line. Solution is test transaction for nil. --- .../lib/active_record/connection_adapters/abstract/transaction.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/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 147e16e9fa..d9ac8db6a8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -240,7 +240,7 @@ module ActiveRecord rollback_transaction if transaction else begin - commit_transaction + commit_transaction if transaction rescue Exception rollback_transaction(transaction) unless transaction.state.completed? raise -- cgit v1.2.3 From 90fe2a42f0b68a66e970169d38e91a0126de7d3e Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Tue, 26 Sep 2017 00:10:17 +0300 Subject: Fix `bin/rails db:migrate` with specified `VERSION` Ensure that `bin/rails db:migrate` with specified `VERSION` reverts all migrations only if `VERSION` is `0`. Raise error if target migration doesn't exist. --- activerecord/lib/active_record/migration.rb | 2 +- .../lib/active_record/railties/databases.rake | 19 +++++++++++++++---- .../lib/active_record/tasks/database_tasks.rb | 15 ++++++++++++--- 3 files changed, 28 insertions(+), 8 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 1485687053..d12a979a7f 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1224,7 +1224,7 @@ module ActiveRecord # Return true if a valid version is not provided. def invalid_target? - !target && @target_version && @target_version > 0 + @target_version && @target_version != 0 && !target end def execute_migration_in_transaction(migration, direction) diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 723272b4b2..3bca2982e0 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -97,16 +97,27 @@ db_namespace = namespace :db do task up: [:environment, :load_config] do raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - ActiveRecord::Migrator.run(:up, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version) + ActiveRecord::Tasks::DatabaseTasks.check_target_version + + ActiveRecord::Migrator.run( + :up, + ActiveRecord::Tasks::DatabaseTasks.migrations_paths, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) db_namespace["_dump"].invoke end # desc 'Runs the "down" for a given migration VERSION.' task down: [:environment, :load_config] do raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty? - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil - ActiveRecord::Migrator.run(:down, ActiveRecord::Tasks::DatabaseTasks.migrations_paths, version) + + ActiveRecord::Tasks::DatabaseTasks.check_target_version + + ActiveRecord::Migrator.run( + :down, + ActiveRecord::Tasks::DatabaseTasks.migrations_paths, + ActiveRecord::Tasks::DatabaseTasks.target_version + ) db_namespace["_dump"].invoke end diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb index ff388ff1f6..4657e51e6d 100644 --- a/activerecord/lib/active_record/tasks/database_tasks.rb +++ b/activerecord/lib/active_record/tasks/database_tasks.rb @@ -164,13 +164,12 @@ module ActiveRecord end def migrate - raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? + check_target_version verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] != "false" : true - version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil scope = ENV["SCOPE"] verbose_was, Migration.verbose = Migration.verbose, verbose - Migrator.migrate(migrations_paths, version) do |migration| + Migrator.migrate(migrations_paths, target_version) do |migration| scope.blank? || scope == migration.scope end ActiveRecord::Base.clear_cache! @@ -178,6 +177,16 @@ module ActiveRecord Migration.verbose = verbose_was end + def check_target_version + if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"])) + raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`" + end + end + + def target_version + ENV["VERSION"].to_i if ENV["VERSION"] && !ENV["VERSION"].empty? + end + def charset_current(environment = env) charset ActiveRecord::Base.configurations[environment] end -- cgit v1.2.3 From 3f1695bb9c008b7cb1840e09e640f3ec0c59a564 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Tue, 7 Nov 2017 08:44:44 +0900 Subject: Remove useless `associated_records_by_owner` `associated_records_by_owner` had returned customizing result before calling `associate_records_to_owner` for through association subclasses. Since #22115, `associate_records_to_owner` is called in the method and not returned owner and result pairs. Removing the method will reduce method call and block call nesting. --- .../associations/preloader/association.rb | 22 ++++++++-------------- .../associations/preloader/through_association.rb | 4 ++-- 2 files changed, 10 insertions(+), 16 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 b0c41c7d5f..e77761692d 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -17,8 +17,14 @@ module ActiveRecord end def run(preloader) - associated_records_by_owner(preloader) do |owner, records| - associate_records_to_owner(owner, records) + records = load_records do |record| + owner = owners_by_key[convert_key(record[association_key_name])] + association = owner.association(reflection.name) + association.set_inverse_instance(record) + end + + owners.each do |owner| + associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || []) end end @@ -33,18 +39,6 @@ module ActiveRecord reflection.join_foreign_key end - def associated_records_by_owner(preloader) - records = load_records do |record| - owner = owners_by_key[convert_key(record[association_key_name])] - association = owner.association(reflection.name) - association.set_inverse_instance(record) - end - - owners.each_with_object({}) do |owner, result| - yield(owner, records[convert_key(owner[owner_key_name])] || []) - end - end - def associate_records_to_owner(owner, records) raise NotImplementedError end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 1bb7e98231..b1813ba66b 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -12,7 +12,7 @@ module ActiveRecord reflection.source_reflection end - def associated_records_by_owner(preloader) + def run(preloader) already_loaded = owners.first.association(through_reflection.name).loaded? through_scope = through_scope() reflection_scope = target_reflection_scope @@ -43,7 +43,7 @@ module ActiveRecord result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any? result.uniq! if reflection_scope.distinct_value end - yield(owner, result) + associate_records_to_owner(owner, result) end end -- cgit v1.2.3 From 0c2cb880e34c943275758ca6a6ff84afa7a29fba Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 8 Nov 2017 20:08:19 +0900 Subject: Don't expose internal methods in `Preloader::ThroughAssociation` `through_reflection` and `source_reflection` are used only in the class. --- .../associations/preloader/through_association.rb | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (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 b1813ba66b..762275fbad 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -4,14 +4,6 @@ module ActiveRecord module Associations class Preloader module ThroughAssociation #:nodoc: - def through_reflection - reflection.through_reflection - end - - def source_reflection - reflection.source_reflection - end - def run(preloader) already_loaded = owners.first.association(through_reflection.name).loaded? through_scope = through_scope() @@ -48,6 +40,13 @@ module ActiveRecord end private + def through_reflection + reflection.through_reflection + end + + def source_reflection + reflection.source_reflection + end def preload_index @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index| -- cgit v1.2.3 From 65aa0b7e3448f849a91b6f510b78d4303ff44dbc Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 8 Nov 2017 20:28:52 +0900 Subject: Don't expose accessors which are internal used only --- activerecord/lib/active_record/associations/preloader.rb | 5 +++-- activerecord/lib/active_record/associations/preloader/association.rb | 4 +++- 2 files changed, 6 insertions(+), 3 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 e1754d4a19..5a93a89d0a 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -166,8 +166,6 @@ module ActiveRecord end class AlreadyLoaded # :nodoc: - attr_reader :owners, :reflection - def initialize(klass, owners, reflection, preload_scope) @owners = owners @reflection = reflection @@ -178,6 +176,9 @@ module ActiveRecord def preloaded_records owners.flat_map { |owner| owner.association(reflection.name).target } end + + protected + attr_reader :owners, :reflection end # Returns a class containing the logic needed to load preload the data diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index e77761692d..19c337dc39 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -4,7 +4,6 @@ module ActiveRecord module Associations class Preloader class Association #:nodoc: - attr_reader :owners, :reflection, :preload_scope, :model, :klass attr_reader :preloaded_records def initialize(klass, owners, reflection, preload_scope) @@ -28,6 +27,9 @@ module ActiveRecord end end + protected + attr_reader :owners, :reflection, :preload_scope, :model, :klass + private # The name of the key on the associated records def association_key_name -- cgit v1.2.3 From f989b341eccc6a86fd1ddfff7f1441920855c84e Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 8 Feb 2017 11:23:26 -0700 Subject: add config to check arguments to unsafe AR methods --- .../lib/active_record/attribute_methods.rb | 16 +++++++ activerecord/lib/active_record/core.rb | 9 ++++ activerecord/lib/active_record/querying.rb | 21 ++++++---- .../lib/active_record/relation/calculations.rb | 42 ++++++++++++------- .../lib/active_record/relation/query_methods.rb | 49 ++++++++++++++++++++++ 5 files changed, 116 insertions(+), 21 deletions(-) (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 23d2aef214..fa0d79ba5f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -167,6 +167,22 @@ module ActiveRecord end end + # Can the given name be treated as a column name? Returns true if name + # is attribute or attribute alias. + # + # class Person < ActiveRecord::Base + # end + # + # Person.respond_to_attribute?(:name) + # # => true + # + # Person.respond_to_attribute?("foo") + # # => false + def respond_to_attribute?(name) + name = name.to_s + attribute_names.include?(name) || attribute_aliases.include?(name) + end + # Returns true if the given attribute exists, otherwise false. # # class Person < ActiveRecord::Base diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 0f7a503c90..b1e3f71dfe 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -76,6 +76,15 @@ module ActiveRecord # scope being ignored is error-worthy, rather than a warning. mattr_accessor :error_on_ignored_order, instance_writer: false, default: false + # :singleton-method: + # Specify the behavior for unsafe raw query methods. Values are as follows + # enabled - Unsafe raw SQL can be passed to query methods. + # deprecated - Warnings are logged when unsafe raw SQL is passed to + # query methods. + # disabled - Unsafe raw SQL passed to query methods results in + # ArguementError. + mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :enabled + ## # :singleton-method: # Specify whether or not to use timestamps for migration versions diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 3996d5661f..0233835bcf 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -2,18 +2,25 @@ module ActiveRecord module Querying - delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all - delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, + :any?, :many?, :none?, :one?, to: :all + delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, + :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, + :second_to_last, :second_to_last!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all - delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all + delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, + to: :all delegate :find_by, :find_by!, to: :all delegate :destroy_all, :delete_all, :update_all, to: :all delegate :find_each, :find_in_batches, :in_batches, to: :all - delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, - :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, - :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all + delegate :select, :group, :order, :unsafe_raw_order, :except, :reorder, + :unsafe_raw_reorder, :limit, :offset, :joins, :left_joins, + :left_outer_joins, :or, :where, :rewhere, :preload, :eager_load, + :includes, :from, :lock, :readonly, :extending, :having, + :create_with, :distinct, :references, :none, :unscope, :merge, + to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all - delegate :pluck, :ids, to: :all + delegate :pluck, :unsafe_raw_pluck, :ids, to: :all # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 11256ab3d9..dea7542f03 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -175,21 +175,13 @@ module ActiveRecord # See also #ids. # def pluck(*column_names) - if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty? - return records.pluck(*column_names) - end + _pluck(column_names, @klass.allow_unsafe_raw_sql == :enabled) + end - if has_include?(column_names.first) - relation = apply_join_dependency - relation.pluck(*column_names) - else - relation = spawn - relation.select_values = column_names.map { |cn| - @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn - } - result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } - result.cast_values(klass.attribute_types) - end + # Same as #pluck but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_pluck(*column_names) + _pluck(column_names, true) end # Pluck all the ID's for the relation using the table's primary key @@ -202,6 +194,28 @@ module ActiveRecord private + def _pluck(column_names, unsafe_raw) + unrecognized = column_names.reject do |cn| + @klass.respond_to_attribute?(cn) + end + + if loaded? && unrecognized.none? + records.pluck(*column_names) + elsif has_include?(column_names.first) + relation = apply_join_dependency + relation.pluck(*column_names) + elsif unsafe_raw || unrecognized.none? + relation = spawn + relation.select_values = column_names.map { |cn| + @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn + } + result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } + result.cast_values(klass.attribute_types) + else + raise ArgumentError, "Invalid column name: #{unrecognized}" + end + end + def has_include?(column_name) eager_loading? || (includes_values.present? && column_name && column_name != :all) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 897ff5c8af..63b1d8e154 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -295,7 +295,22 @@ module ActiveRecord spawn.order!(*args) end + # Same as #order but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_order(*args) # :nodoc: + check_if_method_has_arguments!(:order, args) + spawn.unsafe_raw_order!(*args) + end + + # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: + restrict_order_args(args) unless klass.allow_unsafe_raw_sql == :enabled + unsafe_raw_order!(*args) + end + + # Same as #order! but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_order!(*args) # :nodoc: preprocess_order_args(args) self.order_values += args @@ -316,7 +331,22 @@ module ActiveRecord spawn.reorder!(*args) end + # Same as #reorder but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_reorder(*args) # :nodoc: + check_if_method_has_arguments!(:reorder, args) + spawn.unsafe_raw_reorder!(*args) + end + + # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: + restrict_order_args(args) unless klass.allow_unsafe_raw_sql == :enabled + unsafe_raw_reorder! + end + + # Same as #reorder! but allows raw SQL regardless of `allow_unsafe_raw_sql` + # config setting. + def unsafe_raw_reorder!(*args) # :nodoc: preprocess_order_args(args) self.reordering_value = true @@ -1139,6 +1169,25 @@ module ActiveRecord end.flatten! end + # Only allow column names and directions as arguments to #order and + # #reorder. Other arguments will cause an ArugmentError to be raised. + def restrict_order_args(args) + args = args.dup + orderings = args.extract_options! + columns = args | orderings.keys + + unrecognized = columns.reject { |c| klass.respond_to_attribute?(c) } + if unrecognized.any? + raise ArgumentError, "Invalid order column: #{unrecognized}" + end + + # TODO: find a better list of modifiers. + unrecognized = orderings.values.reject { |d| VALID_DIRECTIONS.include?(d.to_s) } + if unrecognized.any? + raise ArgumentError, "Invalid order direction: #{unrecognized}" + end + end + # Checks to make sure that the arguments are not blank. Note that if some # blank-like object were initially passed into the query method, then this # method will not raise an error. -- cgit v1.2.3 From 864b16063d14977096d9d24ac894fee605dfb7a7 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Tue, 21 Feb 2017 11:17:16 -0700 Subject: allow Arel.sql() for pluck --- .../lib/active_record/attribute_methods.rb | 38 ++++++++++++++- activerecord/lib/active_record/errors.rb | 25 ++++++++++ activerecord/lib/active_record/querying.rb | 21 +++------ .../lib/active_record/relation/calculations.rb | 20 +++++--- .../lib/active_record/relation/query_methods.rb | 54 +++------------------- 5 files changed, 90 insertions(+), 68 deletions(-) (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 fa0d79ba5f..b3d3c0559f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -167,6 +167,30 @@ module ActiveRecord end end + def enforce_raw_sql_whitelist(args, whitelist: attribute_names_and_aliases) # :nodoc: + return if allow_unsafe_raw_sql == :enabled + + unexpected = args.reject do |arg| + whitelist.include?(arg.to_s) || + arg.kind_of?(Arel::Node) || arg.is_a?(Arel::Nodes::SqlLiteral) + end + + return if unexpected.none? + + if allow_unsafe_raw_sql == :deprecated + ActiveSupport::Deprecation.warn( + "Dangerous query method used with non-attribute argument(s): " + + "#{unexpected.map(&:inspect).join(", ")}. Non-argument " + + "arguments will be disallowed in Rails 5.3." + ) + else + raise(ActiveRecord::UnknownAttributeReference, + "Query method called with non-attribute argument(s): " + + unexpected.map(&:inspect).join(", ") + ) + end + end + # Can the given name be treated as a column name? Returns true if name # is attribute or attribute alias. # @@ -178,7 +202,7 @@ module ActiveRecord # # Person.respond_to_attribute?("foo") # # => false - def respond_to_attribute?(name) + def respond_to_attribute?(name) # :nodoc: name = name.to_s attribute_names.include?(name) || attribute_aliases.include?(name) end @@ -214,6 +238,18 @@ module ActiveRecord ConnectionAdapters::NullColumn.new(name) end end + + # An Array of String attribute names and aliases for accessing those + # attributes. + # + # class Person < ActiveRecord::Base + # end + # + # Person.attribute_names_and_aliases + # # => ["id", "created_at", "updated_at", "name", "age"] + def attribute_names_and_aliases # :nodoc: + attribute_names + attribute_aliases.keys + end end # A Person object with a name attribute can ask person.respond_to?(:name), diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 9ef3316393..3c039eef82 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -339,4 +339,29 @@ module ActiveRecord # Wait time value is set by innodb_lock_wait_timeout. class TransactionTimeout < StatementInvalid end + + # UnknownAttributeReference is raised when an unknown and potentially unsafe + # value is passed to a query method when allow_unsafe_raw_sql is set to + # :disabled. For example, passing a non column name value to a relation's + # #order method might cause this exception. + # + # When working around this exception, caution should be taken to avoid SQL + # injection vulnerabilities when passing user-provided values to query + # methods. Known-safe values can be passed to query methods by wrapping them + # in Arel.sql. + # + # For example, with allow_unsafe_raw_sql set to :disabled, the following + # code would raise this exception: + # + # Post.order("length(title)").first + # + # The desired result can be accomplished by wrapping the known-safe string + # in Arel.sql: + # + # Post.order(Arel.sql("length(title)")).first + # + # Again, such a workaround should *not* be used when passing user-provided + # values, such as request parameters or model attributes to query methods. + class UnknownAttributeReference < ActiveRecordError + end end diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb index 0233835bcf..3996d5661f 100644 --- a/activerecord/lib/active_record/querying.rb +++ b/activerecord/lib/active_record/querying.rb @@ -2,25 +2,18 @@ module ActiveRecord module Querying - delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, - :any?, :many?, :none?, :one?, to: :all - delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, - :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, - :second_to_last, :second_to_last!, to: :all + delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :none?, :one?, to: :all + delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all - delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, - to: :all + delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all delegate :find_by, :find_by!, to: :all delegate :destroy_all, :delete_all, :update_all, to: :all delegate :find_each, :find_in_batches, :in_batches, to: :all - delegate :select, :group, :order, :unsafe_raw_order, :except, :reorder, - :unsafe_raw_reorder, :limit, :offset, :joins, :left_joins, - :left_outer_joins, :or, :where, :rewhere, :preload, :eager_load, - :includes, :from, :lock, :readonly, :extending, :having, - :create_with, :distinct, :references, :none, :unscope, :merge, - to: :all + delegate :select, :group, :order, :except, :reorder, :limit, :offset, :joins, :left_joins, :left_outer_joins, :or, + :where, :rewhere, :preload, :eager_load, :includes, :from, :lock, :readonly, :extending, + :having, :create_with, :distinct, :references, :none, :unscope, :merge, to: :all delegate :count, :average, :minimum, :maximum, :sum, :calculate, to: :all - delegate :pluck, :unsafe_raw_pluck, :ids, to: :all + delegate :pluck, :ids, to: :all # Executes a custom SQL query against your database and returns all the results. The results will # be returned as an array with columns requested encapsulated as attributes of the model you call diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index dea7542f03..236d36e15f 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -175,13 +175,21 @@ module ActiveRecord # See also #ids. # def pluck(*column_names) - _pluck(column_names, @klass.allow_unsafe_raw_sql == :enabled) - end + if loaded? && (column_names.map(&:to_s) - @klass.attribute_names_and_aliases).empty? + return records.pluck(*column_names) + end - # Same as #pluck but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_pluck(*column_names) - _pluck(column_names, true) + if has_include?(column_names.first) + construct_relation_for_association_calculations.pluck(*column_names) + else + enforce_raw_sql_whitelist(column_names) + relation = spawn + relation.select_values = column_names.map { |cn| + @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn + } + result = klass.connection.select_all(relation.arel, nil, bound_attributes) + result.cast_values(klass.attribute_types) + end end # Pluck all the ID's for the relation using the table's primary key diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 63b1d8e154..4c63d0450a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -295,22 +295,9 @@ module ActiveRecord spawn.order!(*args) end - # Same as #order but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_order(*args) # :nodoc: - check_if_method_has_arguments!(:order, args) - spawn.unsafe_raw_order!(*args) - end - # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: - restrict_order_args(args) unless klass.allow_unsafe_raw_sql == :enabled - unsafe_raw_order!(*args) - end - - # Same as #order! but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_order!(*args) # :nodoc: + enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) preprocess_order_args(args) self.order_values += args @@ -331,22 +318,9 @@ module ActiveRecord spawn.reorder!(*args) end - # Same as #reorder but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_reorder(*args) # :nodoc: - check_if_method_has_arguments!(:reorder, args) - spawn.unsafe_raw_reorder!(*args) - end - # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: - restrict_order_args(args) unless klass.allow_unsafe_raw_sql == :enabled - unsafe_raw_reorder! - end - - # Same as #reorder! but allows raw SQL regardless of `allow_unsafe_raw_sql` - # config setting. - def unsafe_raw_reorder!(*args) # :nodoc: + enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) preprocess_order_args(args) self.reordering_value = true @@ -946,6 +920,11 @@ module ActiveRecord private + # Extract column names from arguments passed to #order or #reorder. + def column_names_from_order_arguments(args) + args.flat_map { |arg| arg.is_a?(Hash) ? arg.keys : arg } + end + def assert_mutability! raise ImmutableRelation if @loaded raise ImmutableRelation if defined?(@arel) && @arel @@ -1169,25 +1148,6 @@ module ActiveRecord end.flatten! end - # Only allow column names and directions as arguments to #order and - # #reorder. Other arguments will cause an ArugmentError to be raised. - def restrict_order_args(args) - args = args.dup - orderings = args.extract_options! - columns = args | orderings.keys - - unrecognized = columns.reject { |c| klass.respond_to_attribute?(c) } - if unrecognized.any? - raise ArgumentError, "Invalid order column: #{unrecognized}" - end - - # TODO: find a better list of modifiers. - unrecognized = orderings.values.reject { |d| VALID_DIRECTIONS.include?(d.to_s) } - if unrecognized.any? - raise ArgumentError, "Invalid order direction: #{unrecognized}" - end - end - # Checks to make sure that the arguments are not blank. Note that if some # blank-like object were initially passed into the query method, then this # method will not raise an error. -- cgit v1.2.3 From 310c3a8f2d043f3d00d3f703052a1e160430a2c2 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Thu, 10 Aug 2017 11:21:22 -0600 Subject: beef up deprecation warning --- activerecord/lib/active_record/attribute_methods.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (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 b3d3c0559f..e167c4ad6f 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -179,9 +179,13 @@ module ActiveRecord if allow_unsafe_raw_sql == :deprecated ActiveSupport::Deprecation.warn( - "Dangerous query method used with non-attribute argument(s): " + - "#{unexpected.map(&:inspect).join(", ")}. Non-argument " + - "arguments will be disallowed in Rails 5.3." + "Dangerous query method (method whose arguments are used as raw " \ + "SQL) called with non-attribute argument(s): " \ + "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \ + "arguments will be disallowed in Rails 6.0. This method should " \ + "not be called with user-provided values, such as request " \ + "parameters or model attributes. Known-safe values can be passed " \ + "by wrapping them in Arel.sql()." ) else raise(ActiveRecord::UnknownAttributeReference, -- cgit v1.2.3 From 0d8626ad3f1bac3b814d554147ff7926c0380fad Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Fri, 11 Aug 2017 09:55:54 -0600 Subject: remove :enabled option --- activerecord/lib/active_record/attribute_methods.rb | 2 -- activerecord/lib/active_record/core.rb | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) (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 e167c4ad6f..5ce3ba7a63 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -168,8 +168,6 @@ module ActiveRecord end def enforce_raw_sql_whitelist(args, whitelist: attribute_names_and_aliases) # :nodoc: - return if allow_unsafe_raw_sql == :enabled - unexpected = args.reject do |arg| whitelist.include?(arg.to_s) || arg.kind_of?(Arel::Node) || arg.is_a?(Arel::Nodes::SqlLiteral) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index b1e3f71dfe..b97b14644e 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -78,12 +78,11 @@ module ActiveRecord # :singleton-method: # Specify the behavior for unsafe raw query methods. Values are as follows - # enabled - Unsafe raw SQL can be passed to query methods. # deprecated - Warnings are logged when unsafe raw SQL is passed to # query methods. # disabled - Unsafe raw SQL passed to query methods results in - # ArguementError. - mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :enabled + # UnknownAttributeReference exception. + mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated ## # :singleton-method: -- cgit v1.2.3 From 92e78593ee056bb73a7a87c10af3f2587eca1150 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Mon, 25 Sep 2017 10:13:53 -0600 Subject: work with actual string when reversing order --- activerecord/lib/active_record/relation/query_methods.rb | 3 +++ 1 file changed, 3 insertions(+) (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 4c63d0450a..7fe0129b4a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1075,6 +1075,9 @@ module ActiveRecord when Arel::Nodes::Ordering o.reverse when String + # ensure we're not dealing with string subclass (Eg. Arel::Nodes::SqlLiteral) + o = String.new(o) + if does_not_support_reverse?(o) raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" end -- cgit v1.2.3 From 65328025917f0b60d0fbbeca87f173f34d9c91c5 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Mon, 25 Sep 2017 10:39:35 -0600 Subject: call enforce_raw_sql_whitelist on @klass so it works with FakeKlass --- 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 7fe0129b4a..f3b44d19d6 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -297,7 +297,7 @@ module ActiveRecord # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: - enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) + @klass.enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) preprocess_order_args(args) self.order_values += args @@ -320,7 +320,7 @@ module ActiveRecord # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: - enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) + @klass.enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) preprocess_order_args(args) self.reordering_value = true -- cgit v1.2.3 From 40d302d880f247ef9547708b1d26a390945b6fe9 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Mon, 25 Sep 2017 10:54:53 -0600 Subject: always allow Arel::Attributes::Attribute also --- activerecord/lib/active_record/attribute_methods.rb | 4 +++- 1 file changed, 3 insertions(+), 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 5ce3ba7a63..ff381b4e0b 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -170,7 +170,9 @@ module ActiveRecord def enforce_raw_sql_whitelist(args, whitelist: attribute_names_and_aliases) # :nodoc: unexpected = args.reject do |arg| whitelist.include?(arg.to_s) || - arg.kind_of?(Arel::Node) || arg.is_a?(Arel::Nodes::SqlLiteral) + arg.kind_of?(Arel::Node) || + arg.is_a?(Arel::Nodes::SqlLiteral) || + arg.is_a?(Arel::Attributes::Attribute) end return if unexpected.none? -- cgit v1.2.3 From 5180fe2cd8233169935065efe8762bd5d7b2709c Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Tue, 26 Sep 2017 09:29:24 -0600 Subject: allow table name and direction in string order arg --- .../lib/active_record/relation/calculations.rb | 31 ++++++---------------- .../lib/active_record/relation/query_methods.rb | 26 ++++++++++++++++-- 2 files changed, 32 insertions(+), 25 deletions(-) (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 236d36e15f..75795fe493 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -180,14 +180,15 @@ module ActiveRecord end if has_include?(column_names.first) - construct_relation_for_association_calculations.pluck(*column_names) + relation = apply_join_dependency + relation.pluck(*column_names) else - enforce_raw_sql_whitelist(column_names) + enforce_raw_sql_whitelist(column_names, whitelist: allowed_pluck_columns) relation = spawn relation.select_values = column_names.map { |cn| @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn } - result = klass.connection.select_all(relation.arel, nil, bound_attributes) + result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } result.cast_values(klass.attribute_types) end end @@ -202,26 +203,10 @@ module ActiveRecord private - def _pluck(column_names, unsafe_raw) - unrecognized = column_names.reject do |cn| - @klass.respond_to_attribute?(cn) - end - - if loaded? && unrecognized.none? - records.pluck(*column_names) - elsif has_include?(column_names.first) - relation = apply_join_dependency - relation.pluck(*column_names) - elsif unsafe_raw || unrecognized.none? - relation = spawn - relation.select_values = column_names.map { |cn| - @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn - } - result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } - result.cast_values(klass.attribute_types) - else - raise ArgumentError, "Invalid column name: #{unrecognized}" - end + def allowed_pluck_columns + @klass.attribute_names_and_aliases.map do |name| + [name, "#{table_name}.#{name}"] + end.flatten end def has_include?(column_name) diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index f3b44d19d6..094e5aa733 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -297,7 +297,11 @@ module ActiveRecord # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: - @klass.enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) + @klass.enforce_raw_sql_whitelist( + column_names_from_order_arguments(args), + whitelist: allowed_order_columns + ) + preprocess_order_args(args) self.order_values += args @@ -320,7 +324,11 @@ module ActiveRecord # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: - @klass.enforce_raw_sql_whitelist(column_names_from_order_arguments(args)) + @klass.enforce_raw_sql_whitelist( + column_names_from_order_arguments(args), + whitelist: allowed_order_columns + ) + preprocess_order_args(args) self.reordering_value = true @@ -920,6 +928,20 @@ module ActiveRecord private + def allowed_order_columns + @klass.attribute_names_and_aliases.map do |name| + [name, "#{table_name}.#{name}"].map do |name| + [ + name, + "#{name} asc", + "#{name} ASC", + "#{name} desc", + "#{name} DESC" + ] + end + end.flatten + end + # Extract column names from arguments passed to #order or #reorder. def column_names_from_order_arguments(args) args.flat_map { |arg| arg.is_a?(Hash) ? arg.keys : arg } -- cgit v1.2.3 From 798557145c727b2abef2487783f02e57f04197c9 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 11 Oct 2017 13:16:57 -0600 Subject: try using regexes --- .../lib/active_record/attribute_methods.rb | 48 ++++++++-------------- .../lib/active_record/relation/calculations.rb | 12 ++---- .../lib/active_record/relation/query_methods.rb | 21 +--------- 3 files changed, 21 insertions(+), 60 deletions(-) (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 ff381b4e0b..64f81ca582 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -167,12 +167,24 @@ module ActiveRecord end end - def enforce_raw_sql_whitelist(args, whitelist: attribute_names_and_aliases) # :nodoc: + # Regexp whitelist. Matches the following: + # "#{table_name}.#{column_name}" + # "#{column_name}" + COLUMN_NAME_WHITELIST = /\A(?:\w+\.)?\w+\z/i + + # Regexp whitelist. Matches the following: + # "#{table_name}.#{column_name}" + # "#{table_name}.#{column_name} #{direction}" + # "#{column_name}" + # "#{column_name} #{direction}" + COLUMN_NAME_ORDER_WHITELIST = /\A(?:\w+\.)?\w+(?:\s+asc|\s+desc)?\z/i + + def enforce_raw_sql_whitelist(args, whitelist: COLUMN_NAME_WHITELIST) # :nodoc: unexpected = args.reject do |arg| - whitelist.include?(arg.to_s) || - arg.kind_of?(Arel::Node) || + arg.kind_of?(Arel::Node) || arg.is_a?(Arel::Nodes::SqlLiteral) || - arg.is_a?(Arel::Attributes::Attribute) + arg.is_a?(Arel::Attributes::Attribute) || + arg.to_s.split(/\s*,\s*/).all? { |part| whitelist.match?(part) } end return if unexpected.none? @@ -195,22 +207,6 @@ module ActiveRecord end end - # Can the given name be treated as a column name? Returns true if name - # is attribute or attribute alias. - # - # class Person < ActiveRecord::Base - # end - # - # Person.respond_to_attribute?(:name) - # # => true - # - # Person.respond_to_attribute?("foo") - # # => false - def respond_to_attribute?(name) # :nodoc: - name = name.to_s - attribute_names.include?(name) || attribute_aliases.include?(name) - end - # Returns true if the given attribute exists, otherwise false. # # class Person < ActiveRecord::Base @@ -242,18 +238,6 @@ module ActiveRecord ConnectionAdapters::NullColumn.new(name) end end - - # An Array of String attribute names and aliases for accessing those - # attributes. - # - # class Person < ActiveRecord::Base - # end - # - # Person.attribute_names_and_aliases - # # => ["id", "created_at", "updated_at", "name", "age"] - def attribute_names_and_aliases # :nodoc: - attribute_names + attribute_aliases.keys - end end # A Person object with a name attribute can ask person.respond_to?(:name), diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index 75795fe493..d49472fc70 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -175,7 +175,7 @@ module ActiveRecord # See also #ids. # def pluck(*column_names) - if loaded? && (column_names.map(&:to_s) - @klass.attribute_names_and_aliases).empty? + if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty? return records.pluck(*column_names) end @@ -183,10 +183,10 @@ module ActiveRecord relation = apply_join_dependency relation.pluck(*column_names) else - enforce_raw_sql_whitelist(column_names, whitelist: allowed_pluck_columns) + enforce_raw_sql_whitelist(column_names) relation = spawn relation.select_values = column_names.map { |cn| - @klass.respond_to_attribute?(cn) ? arel_attribute(cn) : cn + @klass.has_attribute?(cn) || @klass.attribute_alias?(cn) ? arel_attribute(cn) : cn } result = skip_query_cache_if_necessary { klass.connection.select_all(relation.arel, nil) } result.cast_values(klass.attribute_types) @@ -203,12 +203,6 @@ module ActiveRecord private - def allowed_pluck_columns - @klass.attribute_names_and_aliases.map do |name| - [name, "#{table_name}.#{name}"] - end.flatten - end - def has_include?(column_name) eager_loading? || (includes_values.present? && column_name && column_name != :all) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 094e5aa733..59a732168c 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -299,7 +299,7 @@ module ActiveRecord def order!(*args) # :nodoc: @klass.enforce_raw_sql_whitelist( column_names_from_order_arguments(args), - whitelist: allowed_order_columns + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST ) preprocess_order_args(args) @@ -326,7 +326,7 @@ module ActiveRecord def reorder!(*args) # :nodoc: @klass.enforce_raw_sql_whitelist( column_names_from_order_arguments(args), - whitelist: allowed_order_columns + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST ) preprocess_order_args(args) @@ -928,20 +928,6 @@ module ActiveRecord private - def allowed_order_columns - @klass.attribute_names_and_aliases.map do |name| - [name, "#{table_name}.#{name}"].map do |name| - [ - name, - "#{name} asc", - "#{name} ASC", - "#{name} desc", - "#{name} DESC" - ] - end - end.flatten - end - # Extract column names from arguments passed to #order or #reorder. def column_names_from_order_arguments(args) args.flat_map { |arg| arg.is_a?(Hash) ? arg.keys : arg } @@ -1097,9 +1083,6 @@ module ActiveRecord when Arel::Nodes::Ordering o.reverse when String - # ensure we're not dealing with string subclass (Eg. Arel::Nodes::SqlLiteral) - o = String.new(o) - if does_not_support_reverse?(o) raise IrreversibleOrderError, "Order #{o.inspect} can not be reversed automatically" end -- cgit v1.2.3 From ab03eb9f576312c75e61caaf9705a8ac5175c769 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 11 Oct 2017 13:56:42 -0600 Subject: use << instead of #concat in #reverse_sql_order because we might be working with Arel SQL literator which overrides #concat --- 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 59a732168c..34723b0b4f 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1088,7 +1088,7 @@ module ActiveRecord end o.split(",").map! do |s| s.strip! - s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || s.concat(" DESC") + s.gsub!(/\sasc\Z/i, " DESC") || s.gsub!(/\sdesc\Z/i, " ASC") || (s << " DESC") end else o -- cgit v1.2.3 From c711a27d29a3201ff47751a1d788f1e634186dd3 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 11 Oct 2017 14:15:47 -0600 Subject: convert order arg to string before checking if we can reverse it --- activerecord/lib/active_record/relation/query_methods.rb | 4 ++++ 1 file changed, 4 insertions(+) (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 34723b0b4f..db7fe8123d 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1097,6 +1097,10 @@ module ActiveRecord end def does_not_support_reverse?(order) + # Account for String subclasses like Arel::Nodes::SqlLiteral that + # override methods like #count. + order = String.new(order) unless order.instance_of?(String) + # Uses SQL function with multiple arguments. (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) || # Uses "nulls first" like construction. -- cgit v1.2.3 From b76cc29865fb69389ffdb7bd9f8085aa86354f82 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Thu, 12 Oct 2017 11:48:48 -0600 Subject: deal with Array arguments to #order --- activerecord/lib/active_record/relation/query_methods.rb | 14 +++++++++++++- activerecord/lib/active_record/sanitization.rb | 6 ++++++ 2 files changed, 19 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 db7fe8123d..1dcd786498 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -930,7 +930,19 @@ module ActiveRecord # Extract column names from arguments passed to #order or #reorder. def column_names_from_order_arguments(args) - args.flat_map { |arg| arg.is_a?(Hash) ? arg.keys : arg } + args.flat_map do |arg| + case arg + when Hash + # Tag.order(id: :desc) + arg.keys + when Array + # Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]) + arg.flatten + else + # Tag.order(:id) + arg + end + end end def assert_mutability! diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 1c3099f55c..4caf1145f0 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -63,6 +63,12 @@ module ActiveRecord # # => "id ASC" def sanitize_sql_for_order(condition) # :doc: if condition.is_a?(Array) && condition.first.to_s.include?("?") + # Ensure we aren't dealing with a subclass of String that might + # override methods we use (eg. Arel::Nodes::SqlLiteral). + if condition.first.kind_of?(String) && !condition.first.instance_of?(String) + condition = [String.new(condition.first), *condition[1..-1]] + end + sanitize_sql_array(condition) else condition -- cgit v1.2.3 From 8ef71ac4a119a4c03d78db2372b41ddcc8a95035 Mon Sep 17 00:00:00 2001 From: Ben Toews Date: Wed, 18 Oct 2017 10:21:45 -0600 Subject: push order arg checks down to allow for binds --- .../lib/active_record/relation/query_methods.rb | 33 ++++------------------ activerecord/lib/active_record/sanitization.rb | 6 +++- 2 files changed, 11 insertions(+), 28 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 1dcd786498..cef0847651 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -297,11 +297,6 @@ module ActiveRecord # Same as #order but operates on relation in-place instead of copying. def order!(*args) # :nodoc: - @klass.enforce_raw_sql_whitelist( - column_names_from_order_arguments(args), - whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST - ) - preprocess_order_args(args) self.order_values += args @@ -324,11 +319,6 @@ module ActiveRecord # Same as #reorder but operates on relation in-place instead of copying. def reorder!(*args) # :nodoc: - @klass.enforce_raw_sql_whitelist( - column_names_from_order_arguments(args), - whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST - ) - preprocess_order_args(args) self.reordering_value = true @@ -928,23 +918,6 @@ module ActiveRecord private - # Extract column names from arguments passed to #order or #reorder. - def column_names_from_order_arguments(args) - args.flat_map do |arg| - case arg - when Hash - # Tag.order(id: :desc) - arg.keys - when Array - # Tag.order([Arel.sql("field(id, ?)"), [1, 3, 2]]) - arg.flatten - else - # Tag.order(:id) - arg - end - end - end - def assert_mutability! raise ImmutableRelation if @loaded raise ImmutableRelation if defined?(@arel) && @arel @@ -1146,6 +1119,12 @@ module ActiveRecord klass.send(:sanitize_sql_for_order, arg) end order_args.flatten! + + @klass.enforce_raw_sql_whitelist( + order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a }, + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST + ) + validate_order_args(order_args) references = order_args.grep(String) diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 4caf1145f0..232743d3b6 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -63,13 +63,17 @@ module ActiveRecord # # => "id ASC" def sanitize_sql_for_order(condition) # :doc: if condition.is_a?(Array) && condition.first.to_s.include?("?") + enforce_raw_sql_whitelist([condition.first], + whitelist: AttributeMethods::ClassMethods::COLUMN_NAME_ORDER_WHITELIST + ) + # Ensure we aren't dealing with a subclass of String that might # override methods we use (eg. Arel::Nodes::SqlLiteral). if condition.first.kind_of?(String) && !condition.first.instance_of?(String) condition = [String.new(condition.first), *condition[1..-1]] end - sanitize_sql_array(condition) + Arel.sql(sanitize_sql_array(condition)) else condition end -- cgit v1.2.3 From 1f9f6f6cfc57020ccb35f77872c56f069f337075 Mon Sep 17 00:00:00 2001 From: Brent Wheeldon Date: Thu, 2 Nov 2017 14:53:43 -0400 Subject: Prevent deadlocks with load interlock and DB lock. This fixes an issue where competing threads deadlock each other. - Thread A holds the load interlock but is blocked on getting the DB lock - Thread B holds the DB lock but is blocked on getting the load interlock (for example when there is a `Model.transaction` block that needs to autoload) This solution allows for dependency loading in other threads while a thread is waiting to acquire the DB lock. Fixes #31019 --- activerecord/lib/active_record/connection_adapters/abstract_adapter.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_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index e3aab8dad8..e91ef3b779 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -6,6 +6,7 @@ require "active_record/connection_adapters/schema_cache" require "active_record/connection_adapters/sql_type_metadata" require "active_record/connection_adapters/abstract/schema_dumper" require "active_record/connection_adapters/abstract/schema_creation" +require "active_support/concurrency/load_interlock_aware_monitor" require "arel/collectors/bind" require "arel/collectors/composite" require "arel/collectors/sql_string" @@ -108,7 +109,7 @@ module ActiveRecord @schema_cache = SchemaCache.new self @quoted_column_names, @quoted_table_names = {}, {} @visitor = arel_visitor - @lock = Monitor.new + @lock = ActiveSupport::Concurrency::LoadInterlockAwareMonitor.new if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true }) @prepared_statements = true -- cgit v1.2.3 From c3675f50d2e59b7fc173d7b332860c4b1a24a726 Mon Sep 17 00:00:00 2001 From: Lisa Ugray Date: Thu, 19 Oct 2017 12:45:07 -0400 Subject: Move Attribute and AttributeSet to ActiveModel Use these to back the attributes API. Stop automatically including ActiveModel::Dirty in ActiveModel::Attributes, and make it optional. --- activerecord/lib/active_record/attribute.rb | 242 --------------------- .../attribute/user_provided_default.rb | 32 --- .../lib/active_record/attribute_methods/dirty.rb | 96 +------- .../active_record/attribute_mutation_tracker.rb | 111 ---------- activerecord/lib/active_record/attribute_set.rb | 113 ---------- .../lib/active_record/attribute_set/builder.rb | 126 ----------- .../active_record/attribute_set/yaml_encoder.rb | 43 ---- activerecord/lib/active_record/attributes.rb | 6 +- .../lib/active_record/legacy_yaml_adapter.rb | 2 +- activerecord/lib/active_record/model_schema.rb | 6 +- .../lib/active_record/relation/query_attribute.rb | 4 +- .../lib/active_record/relation/query_methods.rb | 4 +- 12 files changed, 13 insertions(+), 772 deletions(-) delete mode 100644 activerecord/lib/active_record/attribute.rb delete mode 100644 activerecord/lib/active_record/attribute/user_provided_default.rb delete mode 100644 activerecord/lib/active_record/attribute_mutation_tracker.rb delete mode 100644 activerecord/lib/active_record/attribute_set.rb delete mode 100644 activerecord/lib/active_record/attribute_set/builder.rb delete mode 100644 activerecord/lib/active_record/attribute_set/yaml_encoder.rb (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb deleted file mode 100644 index fc474edc15..0000000000 --- a/activerecord/lib/active_record/attribute.rb +++ /dev/null @@ -1,242 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - class Attribute # :nodoc: - class << self - def from_database(name, value, type) - FromDatabase.new(name, value, type) - end - - def from_user(name, value, type, original_attribute = nil) - FromUser.new(name, value, type, original_attribute) - end - - def with_cast_value(name, value, type) - WithCastValue.new(name, value, type) - end - - def null(name) - Null.new(name) - end - - def uninitialized(name, type) - Uninitialized.new(name, type) - end - end - - attr_reader :name, :value_before_type_cast, :type - - # This method should not be called directly. - # Use #from_database or #from_user - def initialize(name, value_before_type_cast, type, original_attribute = nil) - @name = name - @value_before_type_cast = value_before_type_cast - @type = type - @original_attribute = original_attribute - end - - def value - # `defined?` is cheaper than `||=` when we get back falsy values - @value = type_cast(value_before_type_cast) unless defined?(@value) - @value - end - - def original_value - if assigned? - original_attribute.original_value - else - type_cast(value_before_type_cast) - end - end - - def value_for_database - type.serialize(value) - end - - def changed? - changed_from_assignment? || changed_in_place? - end - - def changed_in_place? - has_been_read? && type.changed_in_place?(original_value_for_database, value) - end - - def forgetting_assignment - with_value_from_database(value_for_database) - end - - def with_value_from_user(value) - type.assert_valid_value(value) - self.class.from_user(name, value, type, original_attribute || self) - end - - def with_value_from_database(value) - self.class.from_database(name, value, type) - end - - def with_cast_value(value) - self.class.with_cast_value(name, value, type) - end - - def with_type(type) - if changed_in_place? - with_value_from_user(value).with_type(type) - else - self.class.new(name, value_before_type_cast, type, original_attribute) - end - end - - def type_cast(*) - raise NotImplementedError - end - - def initialized? - true - end - - def came_from_user? - false - end - - def has_been_read? - defined?(@value) - end - - def ==(other) - self.class == other.class && - name == other.name && - 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 - - def init_with(coder) - @name = coder["name"] - @value_before_type_cast = coder["value_before_type_cast"] - @type = coder["type"] - @original_attribute = coder["original_attribute"] - @value = coder["value"] if coder.map.key?("value") - end - - def encode_with(coder) - coder["name"] = name - coder["value_before_type_cast"] = value_before_type_cast unless value_before_type_cast.nil? - coder["type"] = type if type - coder["original_attribute"] = original_attribute if original_attribute - coder["value"] = value if defined?(@value) - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :original_attribute - alias_method :assigned?, :original_attribute - - def original_value_for_database - if assigned? - original_attribute.original_value_for_database - else - _original_value_for_database - end - end - - private - def initialize_dup(other) - if defined?(@value) && @value.duplicable? - @value = @value.dup - end - end - - def changed_from_assignment? - assigned? && type.changed?(original_value, value, value_before_type_cast) - end - - def _original_value_for_database - type.serialize(original_value) - end - - class FromDatabase < Attribute # :nodoc: - def type_cast(value) - type.deserialize(value) - end - - def _original_value_for_database - value_before_type_cast - end - end - - class FromUser < Attribute # :nodoc: - def type_cast(value) - type.cast(value) - end - - def came_from_user? - !type.value_constructed_by_mass_assignment?(value_before_type_cast) - end - end - - class WithCastValue < Attribute # :nodoc: - def type_cast(value) - value - end - - def changed_in_place? - false - end - end - - class Null < Attribute # :nodoc: - def initialize(name) - super(name, nil, Type.default_value) - end - - def type_cast(*) - 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 - alias_method :with_value_from_user, :with_value_from_database - end - - class Uninitialized < Attribute # :nodoc: - UNINITIALIZED_ORIGINAL_VALUE = Object.new - - def initialize(name, type) - super(name, nil, type) - end - - def value - if block_given? - yield name - end - end - - def original_value - UNINITIALIZED_ORIGINAL_VALUE - end - - def value_for_database - end - - def initialized? - false - end - - def with_type(type) - self.class.new(name, type) - end - end - private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue - end -end diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb deleted file mode 100644 index f746960fac..0000000000 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -require "active_record/attribute" - -module ActiveRecord - class Attribute # :nodoc: - class UserProvidedDefault < FromUser # :nodoc: - def initialize(name, value, type, database_default) - @user_provided_value = value - super(name, value, type, database_default) - end - - def value_before_type_cast - if user_provided_value.is_a?(Proc) - @memoized_value_before_type_cast ||= user_provided_value.call - else - @user_provided_value - end - end - - def with_type(type) - self.class.new(name, user_provided_value, type, original_attribute) - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :user_provided_value - end - end -end diff --git a/activerecord/lib/active_record/attribute_methods/dirty.rb b/activerecord/lib/active_record/attribute_methods/dirty.rb index 79110d04f4..3de6fe566d 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/module/attribute_accessors" -require "active_record/attribute_mutation_tracker" module ActiveRecord module AttributeMethods @@ -33,65 +32,13 @@ module ActiveRecord # reload the record and clears changed attributes. def reload(*) super.tap do + @previously_changed = ActiveSupport::HashWithIndifferentAccess.new @mutations_before_last_save = nil + @attributes_changed_by_setter = ActiveSupport::HashWithIndifferentAccess.new @mutations_from_database = nil - @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new end end - def initialize_dup(other) # :nodoc: - super - @attributes = self.class._default_attributes.map do |attr| - attr.with_value_from_user(@attributes.fetch_value(attr.name)) - end - @mutations_from_database = nil - end - - def changes_applied # :nodoc: - @mutations_before_last_save = mutations_from_database - @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new - forget_attribute_assignments - @mutations_from_database = nil - end - - def clear_changes_information # :nodoc: - @mutations_before_last_save = nil - @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new - forget_attribute_assignments - @mutations_from_database = nil - end - - def clear_attribute_changes(attr_names) # :nodoc: - super - attr_names.each do |attr_name| - clear_attribute_change(attr_name) - end - end - - def changed_attributes # :nodoc: - # This should only be set by methods which will call changed_attributes - # multiple times when it is known that the computed value cannot change. - if defined?(@cached_changed_attributes) - @cached_changed_attributes - else - super.reverse_merge(mutations_from_database.changed_values).freeze - end - end - - def changes # :nodoc: - cache_changed_attributes do - super - end - end - - def previous_changes # :nodoc: - mutations_before_last_save.changes - end - - def attribute_changed_in_place?(attr_name) # :nodoc: - mutations_from_database.changed_in_place?(attr_name) - end - # Did this attribute change when we last saved? This method can be invoked # as +saved_change_to_name?+ instead of saved_change_to_attribute?("name"). # Behaves similarly to +attribute_changed?+. This method is useful in @@ -182,26 +129,6 @@ module ActiveRecord result end - def mutations_from_database - unless defined?(@mutations_from_database) - @mutations_from_database = nil - end - @mutations_from_database ||= AttributeMutationTracker.new(@attributes) - end - - def changes_include?(attr_name) - super || mutations_from_database.changed?(attr_name) - end - - def clear_attribute_change(attr_name) - mutations_from_database.forget_change(attr_name) - end - - def attribute_will_change!(attr_name) - super - mutations_from_database.force_change(attr_name) - end - def _update_record(*) partial_writes? ? super(keys_for_partial_write) : super end @@ -213,25 +140,6 @@ module ActiveRecord def keys_for_partial_write changed_attribute_names_to_save & self.class.column_names end - - def forget_attribute_assignments - @attributes = @attributes.map(&:forgetting_assignment) - end - - def mutations_before_last_save - @mutations_before_last_save ||= NullMutationTracker.instance - end - - def cache_changed_attributes - @cached_changed_attributes = changed_attributes - yield - ensure - clear_changed_attributes_cache - end - - def clear_changed_attributes_cache - remove_instance_variable(:@cached_changed_attributes) if defined?(@cached_changed_attributes) - end end end end diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb deleted file mode 100644 index 94bf641a5d..0000000000 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - class AttributeMutationTracker # :nodoc: - OPTION_NOT_GIVEN = Object.new - - def initialize(attributes) - @attributes = attributes - @forced_changes = Set.new - end - - def changed_values - attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| - if changed?(attr_name) - result[attr_name] = attributes[attr_name].original_value - end - end - end - - def changes - attr_names.each_with_object({}.with_indifferent_access) do |attr_name, result| - change = change_to_attribute(attr_name) - if change - result[attr_name] = change - end - end - end - - def change_to_attribute(attr_name) - attr_name = attr_name.to_s - if changed?(attr_name) - [attributes[attr_name].original_value, attributes.fetch_value(attr_name)] - end - end - - def any_changes? - attr_names.any? { |attr| changed?(attr) } - end - - def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN) - attr_name = attr_name.to_s - forced_changes.include?(attr_name) || - attributes[attr_name].changed? && - (OPTION_NOT_GIVEN == from || attributes[attr_name].original_value == from) && - (OPTION_NOT_GIVEN == to || attributes[attr_name].value == to) - end - - def changed_in_place?(attr_name) - attributes[attr_name.to_s].changed_in_place? - end - - def forget_change(attr_name) - attr_name = attr_name.to_s - attributes[attr_name] = attributes[attr_name].forgetting_assignment - forced_changes.delete(attr_name) - end - - def original_value(attr_name) - attributes[attr_name.to_s].original_value - end - - def force_change(attr_name) - forced_changes << attr_name.to_s - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :attributes, :forced_changes - - private - - def attr_names - attributes.keys - end - end - - class NullMutationTracker # :nodoc: - include Singleton - - def changed_values(*) - {} - end - - def changes(*) - {} - end - - def change_to_attribute(attr_name) - end - - def any_changes?(*) - false - end - - def changed?(*) - false - end - - def changed_in_place?(*) - false - end - - def forget_change(*) - end - - def original_value(*) - end - end -end diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb deleted file mode 100644 index 9785666e77..0000000000 --- a/activerecord/lib/active_record/attribute_set.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -require "active_record/attribute_set/builder" -require "active_record/attribute_set/yaml_encoder" - -module ActiveRecord - class AttributeSet # :nodoc: - delegate :each_value, :fetch, to: :attributes - - def initialize(attributes) - @attributes = attributes - end - - def [](name) - 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 - - def to_hash - initialized_attributes.transform_values(&:value) - end - alias_method :to_h, :to_hash - - def key?(name) - attributes.key?(name) && self[name].initialized? - end - - def keys - attributes.each_key.select { |name| self[name].initialized? } - end - - if defined?(JRUBY_VERSION) - # This form is significantly faster on JRuby, and this is one of our biggest hotspots. - # https://github.com/jruby/jruby/pull/2562 - def fetch_value(name, &block) - self[name].value(&block) - end - else - def fetch_value(name) - self[name].value { |n| yield n if block_given? } - end - end - - def write_from_database(name, value) - attributes[name] = self[name].with_value_from_database(value) - end - - def write_from_user(name, value) - attributes[name] = self[name].with_value_from_user(value) - end - - def write_cast_value(name, value) - attributes[name] = self[name].with_cast_value(value) - end - - def freeze - @attributes.freeze - super - end - - def deep_dup - self.class.allocate.tap do |copy| - copy.instance_variable_set(:@attributes, attributes.deep_dup) - end - end - - def initialize_dup(_) - @attributes = attributes.dup - super - end - - def initialize_clone(_) - @attributes = attributes.clone - super - end - - def reset(key) - if key?(key) - write_from_database(key, nil) - end - end - - def accessed - attributes.select { |_, attr| attr.has_been_read? }.keys - end - - def map(&block) - new_attributes = attributes.transform_values(&block) - AttributeSet.new(new_attributes) - end - - def ==(other) - attributes == other.attributes - end - - protected - - attr_reader :attributes - - private - - def initialized_attributes - attributes.select { |_, attr| attr.initialized? } - end - end -end diff --git a/activerecord/lib/active_record/attribute_set/builder.rb b/activerecord/lib/active_record/attribute_set/builder.rb deleted file mode 100644 index 349cc7e403..0000000000 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ /dev/null @@ -1,126 +0,0 @@ -# frozen_string_literal: true - -require "active_record/attribute" - -module ActiveRecord - class AttributeSet # :nodoc: - class Builder # :nodoc: - attr_reader :types, :always_initialized, :default - - def initialize(types, always_initialized = nil, &default) - @types = types - @always_initialized = always_initialized - @default = default - end - - def build_from_database(values = {}, additional_types = {}) - if always_initialized && !values.key?(always_initialized) - values[always_initialized] = nil - end - - attributes = LazyAttributeHash.new(types, values, additional_types, &default) - AttributeSet.new(attributes) - end - end - end - - class LazyAttributeHash # :nodoc: - delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize - - def initialize(types, values, additional_types, &default) - @types = types - @values = values - @additional_types = additional_types - @materialized = false - @delegate_hash = {} - @default = default || proc {} - end - - def key?(key) - delegate_hash.key?(key) || values.key?(key) || types.key?(key) - end - - def [](key) - delegate_hash[key] || assign_default_value(key) - end - - def []=(key, value) - if frozen? - raise RuntimeError, "Can't modify frozen hash" - end - delegate_hash[key] = value - end - - def deep_dup - dup.tap do |copy| - copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup)) - end - end - - def initialize_dup(_) - @delegate_hash = Hash[delegate_hash] - super - end - - def select - keys = types.keys | values.keys | delegate_hash.keys - keys.each_with_object({}) do |key, hash| - attribute = self[key] - if yield(key, attribute) - hash[key] = attribute - end - end - end - - def ==(other) - if other.is_a?(LazyAttributeHash) - materialize == other.materialize - else - materialize == other - end - end - - def marshal_dump - materialize - end - - def marshal_load(delegate_hash) - @delegate_hash = delegate_hash - @types = {} - @values = {} - @additional_types = {} - @materialized = true - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :types, :values, :additional_types, :delegate_hash, :default - - def materialize - unless @materialized - values.each_key { |key| self[key] } - types.each_key { |key| self[key] } - unless frozen? - @materialized = true - end - end - delegate_hash - end - - private - - def assign_default_value(name) - type = additional_types.fetch(name, types[name]) - value_present = true - value = values.fetch(name) { value_present = false } - - if value_present - delegate_hash[name] = Attribute.from_database(name, value, type) - elsif types.key?(name) - delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type) - end - end - end -end diff --git a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb b/activerecord/lib/active_record/attribute_set/yaml_encoder.rb deleted file mode 100644 index 9254ce16ab..0000000000 --- a/activerecord/lib/active_record/attribute_set/yaml_encoder.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - class AttributeSet - # Attempts to do more intelligent YAML dumping of an - # ActiveRecord::AttributeSet to reduce the size of the resulting string - class YAMLEncoder # :nodoc: - def initialize(default_types) - @default_types = default_types - end - - def encode(attribute_set, coder) - coder["concise_attributes"] = attribute_set.each_value.map do |attr| - if attr.type.equal?(default_types[attr.name]) - attr.with_type(nil) - else - attr - end - end - end - - def decode(coder) - if coder["attributes"] - coder["attributes"] - else - attributes_hash = Hash[coder["concise_attributes"].map do |attr| - if attr.type.nil? - attr = attr.with_type(default_types[attr.name]) - end - [attr.name, attr] - end] - AttributeSet.new(attributes_hash) - end - end - - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :default_types - end - end -end diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 9224d58928..0b7c9398a8 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require "active_record/attribute/user_provided_default" +require "active_model/attribute/user_provided_default" module ActiveRecord # See ActiveRecord::Attributes::ClassMethods for documentation @@ -250,14 +250,14 @@ module ActiveRecord if value == NO_DEFAULT_PROVIDED default_attribute = _default_attributes[name].with_type(type) elsif from_user - default_attribute = Attribute::UserProvidedDefault.new( + default_attribute = ActiveModel::Attribute::UserProvidedDefault.new( name, value, type, _default_attributes.fetch(name.to_s) { nil }, ) else - default_attribute = Attribute.from_database(name, value, type) + default_attribute = ActiveModel::Attribute.from_database(name, value, type) end _default_attributes[name] = default_attribute end diff --git a/activerecord/lib/active_record/legacy_yaml_adapter.rb b/activerecord/lib/active_record/legacy_yaml_adapter.rb index 23644aab8f..ffa095dd94 100644 --- a/activerecord/lib/active_record/legacy_yaml_adapter.rb +++ b/activerecord/lib/active_record/legacy_yaml_adapter.rb @@ -8,7 +8,7 @@ module ActiveRecord case coder["active_record_yaml_version"] when 1, 2 then coder else - if coder["attributes"].is_a?(AttributeSet) + if coder["attributes"].is_a?(ActiveModel::AttributeSet) Rails420.convert(klass, coder) else Rails41.convert(klass, coder) diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index bed9400f51..12ee4a4137 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -323,7 +323,7 @@ module ActiveRecord end def attributes_builder # :nodoc: - @attributes_builder ||= AttributeSet::Builder.new(attribute_types, primary_key) do |name| + @attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, primary_key) do |name| unless columns_hash.key?(name) _default_attributes[name].dup end @@ -346,7 +346,7 @@ module ActiveRecord end def yaml_encoder # :nodoc: - @yaml_encoder ||= AttributeSet::YAMLEncoder.new(attribute_types) + @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types) end # Returns the type of the attribute with the given name, after applying @@ -376,7 +376,7 @@ module ActiveRecord end def _default_attributes # :nodoc: - @default_attributes ||= AttributeSet.new({}) + @default_attributes ||= ActiveModel::AttributeSet.new({}) end # Returns an array of column names as strings. diff --git a/activerecord/lib/active_record/relation/query_attribute.rb b/activerecord/lib/active_record/relation/query_attribute.rb index fad08e2613..3532f28858 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require "active_record/attribute" +require "active_model/attribute" module ActiveRecord class Relation - class QueryAttribute < Attribute # :nodoc: + class QueryAttribute < ActiveModel::Attribute # :nodoc: def type_cast(value) value end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 897ff5c8af..9c5be4ad9b 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -930,7 +930,7 @@ module ActiveRecord arel.where(where_clause.ast) unless where_clause.empty? arel.having(having_clause.ast) unless having_clause.empty? if limit_value - limit_attribute = Attribute.with_cast_value( + limit_attribute = ActiveModel::Attribute.with_cast_value( "LIMIT".freeze, connection.sanitize_limit(limit_value), Type.default_value, @@ -938,7 +938,7 @@ module ActiveRecord arel.take(Arel::Nodes::BindParam.new(limit_attribute)) end if offset_value - offset_attribute = Attribute.with_cast_value( + offset_attribute = ActiveModel::Attribute.with_cast_value( "OFFSET".freeze, offset_value.to_i, Type.default_value, -- cgit v1.2.3 From de40c45f2b7d4a2ba47d28e9a4967134e45df91f Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Wed, 8 Nov 2017 22:21:12 +0900 Subject: Remove useless preloader classes They are only different by one line of code which doesn't deserve a hierarchy of 7 classes. Closes #31079. [Ryuta Kamizono & Bogdan Gusiev] --- .../lib/active_record/associations/preloader.rb | 26 ++++++---------------- .../associations/preloader/association.rb | 8 ++++++- .../associations/preloader/belongs_to.rb | 10 --------- .../preloader/collection_association.rb | 16 ------------- .../associations/preloader/has_many.rb | 10 --------- .../associations/preloader/has_many_through.rb | 11 --------- .../associations/preloader/has_one.rb | 10 --------- .../associations/preloader/has_one_through.rb | 11 --------- .../associations/preloader/singular_association.rb | 15 ------------- .../associations/preloader/through_association.rb | 2 +- 10 files changed, 15 insertions(+), 104 deletions(-) delete mode 100644 activerecord/lib/active_record/associations/preloader/belongs_to.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/collection_association.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/has_many.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/has_many_through.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/has_one.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/has_one_through.rb delete mode 100644 activerecord/lib/active_record/associations/preloader/singular_association.rb (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 5a93a89d0a..e1087be9b3 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -44,16 +44,8 @@ module ActiveRecord extend ActiveSupport::Autoload eager_autoload do - autoload :Association, "active_record/associations/preloader/association" - autoload :SingularAssociation, "active_record/associations/preloader/singular_association" - autoload :CollectionAssociation, "active_record/associations/preloader/collection_association" - autoload :ThroughAssociation, "active_record/associations/preloader/through_association" - - autoload :HasMany, "active_record/associations/preloader/has_many" - autoload :HasManyThrough, "active_record/associations/preloader/has_many_through" - autoload :HasOne, "active_record/associations/preloader/has_one" - autoload :HasOneThrough, "active_record/associations/preloader/has_one_through" - autoload :BelongsTo, "active_record/associations/preloader/belongs_to" + autoload :Association, "active_record/associations/preloader/association" + autoload :ThroughAssociation, "active_record/associations/preloader/through_association" end # Eager loads the named associations for the given Active Record record(s). @@ -182,8 +174,7 @@ module ActiveRecord end # Returns a class containing the logic needed to load preload the data - # and attach it to a relation. For example +Preloader::Association+ or - # +Preloader::HasManyThrough+. The class returned implements a `run` method + # and attach it to a relation. The class returned implements a `run` method # that accepts a preloader. def preloader_for(reflection, owners) if owners.first.association(reflection.name).loaded? @@ -191,13 +182,10 @@ module ActiveRecord end reflection.check_preloadable! - case reflection.macro - when :has_many - reflection.options[:through] ? HasManyThrough : HasMany - when :has_one - reflection.options[:through] ? HasOneThrough : HasOne - when :belongs_to - BelongsTo + if reflection.options[:through] + ThroughAssociation + else + Association end end end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 19c337dc39..735da152b7 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -42,7 +42,13 @@ module ActiveRecord end def associate_records_to_owner(owner, records) - raise NotImplementedError + association = owner.association(reflection.name) + if reflection.collection? + association.loaded! + association.target.concat(records) + else + association.target = records.first + end end def owner_keys diff --git a/activerecord/lib/active_record/associations/preloader/belongs_to.rb b/activerecord/lib/active_record/associations/preloader/belongs_to.rb deleted file mode 100644 index a8e3340b23..0000000000 --- a/activerecord/lib/active_record/associations/preloader/belongs_to.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class BelongsTo < SingularAssociation #:nodoc: - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/collection_association.rb b/activerecord/lib/active_record/associations/preloader/collection_association.rb deleted file mode 100644 index fc2029f54a..0000000000 --- a/activerecord/lib/active_record/associations/preloader/collection_association.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class CollectionAssociation < Association #:nodoc: - private - def associate_records_to_owner(owner, records) - association = owner.association(reflection.name) - association.loaded! - association.target.concat(records) - end - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/has_many.rb b/activerecord/lib/active_record/associations/preloader/has_many.rb deleted file mode 100644 index 72f55bc43f..0000000000 --- a/activerecord/lib/active_record/associations/preloader/has_many.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class HasMany < CollectionAssociation #:nodoc: - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/has_many_through.rb b/activerecord/lib/active_record/associations/preloader/has_many_through.rb deleted file mode 100644 index 3e17d07a33..0000000000 --- a/activerecord/lib/active_record/associations/preloader/has_many_through.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class HasManyThrough < CollectionAssociation #:nodoc: - include ThroughAssociation - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/has_one.rb b/activerecord/lib/active_record/associations/preloader/has_one.rb deleted file mode 100644 index e339b65fb5..0000000000 --- a/activerecord/lib/active_record/associations/preloader/has_one.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class HasOne < SingularAssociation #:nodoc: - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/has_one_through.rb b/activerecord/lib/active_record/associations/preloader/has_one_through.rb deleted file mode 100644 index 17734d0257..0000000000 --- a/activerecord/lib/active_record/associations/preloader/has_one_through.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class HasOneThrough < SingularAssociation #:nodoc: - include ThroughAssociation - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/singular_association.rb b/activerecord/lib/active_record/associations/preloader/singular_association.rb deleted file mode 100644 index 30a92411e3..0000000000 --- a/activerecord/lib/active_record/associations/preloader/singular_association.rb +++ /dev/null @@ -1,15 +0,0 @@ -# frozen_string_literal: true - -module ActiveRecord - module Associations - class Preloader - class SingularAssociation < Association #:nodoc: - private - def associate_records_to_owner(owner, records) - association = owner.association(reflection.name) - association.target = records.first - end - end - end - end -end diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb index 762275fbad..a6b7ab80a2 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -3,7 +3,7 @@ module ActiveRecord module Associations class Preloader - module ThroughAssociation #:nodoc: + class ThroughAssociation < Association # :nodoc: def run(preloader) already_loaded = owners.first.association(through_reflection.name).loaded? through_scope = through_scope() -- cgit v1.2.3 From df49896a1e67feea56062639c3cf51e8e0b12a51 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 10 Nov 2017 22:22:55 +0900 Subject: Ensure `apply_join_dependency` for subqueries in `from` and `where` Fixes #21577. --- .../lib/active_record/relation/predicate_builder/relation_handler.rb | 4 ++++ activerecord/lib/active_record/relation/query_methods.rb | 3 +++ 2 files changed, 7 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb index f51ea4fde0..c8bbfa5051 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/relation_handler.rb @@ -4,6 +4,10 @@ module ActiveRecord class PredicateBuilder class RelationHandler # :nodoc: def call(attribute, value) + if value.eager_loading? + value = value.send(:apply_join_dependency) + end + if value.select_values.empty? value = value.select(value.arel_attribute(value.klass.primary_key)) end diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 9c5be4ad9b..34554450dd 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -963,6 +963,9 @@ module ActiveRecord name = from_clause.name case opts when Relation + if opts.eager_loading? + opts = opts.send(:apply_join_dependency) + end name ||= "subquery" opts.arel.as(name.to_s) else -- cgit v1.2.3 From b1e068bd30a88fbcc93a835edd6dbacf1d2d251c Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 10 Nov 2017 23:33:39 +0900 Subject: Consolidate duplicated `to_ary`/`to_a` definitions in `Relation` and `CollectionProxy` --- .../lib/active_record/associations/collection_proxy.rb | 10 ++++++---- activerecord/lib/active_record/relation.rb | 3 ++- activerecord/lib/active_record/relation/delegation.rb | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 07f7303f8d..8b4a48a38c 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -988,6 +988,12 @@ module ActiveRecord load_target == other end + ## + # :method: to_ary + # + # :call-seq: + # to_ary() + # # Returns a new array of objects from the collection. If the collection # hasn't been loaded, it fetches the records from the database. # @@ -1021,10 +1027,6 @@ module ActiveRecord # # #, # # # # # ] - def to_ary - load_target.dup - end - alias_method :to_a, :to_ary def records # :nodoc: load_target diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7615fb6ee9..e2d2f45503 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -243,9 +243,10 @@ module ActiveRecord end # Converts relation objects to Array. - def to_a + def to_ary records.dup end + alias to_a to_ary def records # :nodoc: load diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 48af777b69..4863befec8 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -38,7 +38,7 @@ module ActiveRecord # may vary depending on the klass of a relation, so we create a subclass of Relation # for each different klass, and the delegations are compiled into that subclass only. - delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join, + delegate :to_xml, :encode_with, :length, :each, :uniq, :join, :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of, :to_sentence, :to_formatted_s, :as_json, :shuffle, :split, :slice, :index, :rindex, to: :records -- cgit v1.2.3 From 4528dd6327f35d3139a48cbac9c9192f2253cbad Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 11 Nov 2017 03:54:10 +0900 Subject: Relation merging should keep joining order `joins_values.partition` will break joins values order. It should be kept as user intended order. Fixes #15488. --- activerecord/lib/active_record/relation/merger.rb | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 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 ebc72d28fd..b736b21525 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -112,22 +112,20 @@ module ActiveRecord if other.klass == relation.klass relation.joins!(*other.joins_values) else - joins_dependency, rest = other.joins_values.partition do |join| + alias_tracker = nil + joins_dependency = other.joins_values.map do |join| case join when Hash, Symbol, Array - true + alias_tracker ||= other.alias_tracker + ActiveRecord::Associations::JoinDependency.new( + other.klass, other.table, join, alias_tracker + ) else - false + join end end - join_dependency = ActiveRecord::Associations::JoinDependency.new( - other.klass, other.table, joins_dependency, other.alias_tracker - ) - - relation.joins! rest - - @relation = relation.joins join_dependency + relation.joins!(*joins_dependency) end end -- cgit v1.2.3 From 24b59434e6aca9679b9f86a41cfbb1a33e3d5619 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 11 Nov 2017 06:43:54 +0900 Subject: Add missing autoload `Type` (#31123) Attribute modules (`Attribute`, `Attributes`, `AttributeSet`) uses `Type`, but referencing `Type` before the modules still fail. ``` % ./bin/test -w test/cases/attribute_test.rb -n test_with_value_from_user_validates_the_value Run options: -n test_with_value_from_user_validates_the_value --seed 31876 E Error: ActiveModel::AttributeTest#test_with_value_from_user_validates_the_value: NameError: uninitialized constant ActiveModel::AttributeTest::Type /Users/kamipo/src/github.com/rails/rails/activemodel/test/cases/attribute_test.rb:233:in `block in ' bin/test test/cases/attribute_test.rb:232 Finished in 0.002985s, 335.0479 runs/s, 335.0479 assertions/s. 1 runs, 1 assertions, 0 failures, 1 errors, 0 skips ``` Probably we need more autoloading at least `Type`. --- activerecord/lib/active_record/connection_adapters/abstract_adapter.rb | 1 - 1 file changed, 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 e3aab8dad8..345983a655 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_record/type" require "active_record/connection_adapters/determine_if_preparable_visitor" require "active_record/connection_adapters/schema_cache" require "active_record/connection_adapters/sql_type_metadata" -- cgit v1.2.3 From 4a65dfcb9adae8fb12a86521c1a34b392e6084c2 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sat, 11 Nov 2017 19:53:40 +0900 Subject: Raise `TransactionTimeout` when lock wait timeout exceeded for PG adapter Follow up of #30360. --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 +++ 1 file changed, 3 insertions(+) (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 2c3c1df2a9..46863c41ab 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -391,6 +391,7 @@ module ActiveRecord UNIQUE_VIOLATION = "23505" SERIALIZATION_FAILURE = "40001" DEADLOCK_DETECTED = "40P01" + LOCK_NOT_AVAILABLE = "55P03" def translate_exception(exception, message) return exception unless exception.respond_to?(:result) @@ -410,6 +411,8 @@ module ActiveRecord SerializationFailure.new(message) when DEADLOCK_DETECTED Deadlocked.new(message) + when LOCK_NOT_AVAILABLE + TransactionTimeout.new(message) else super end -- cgit v1.2.3 From a968a7609db56f56298c462aa26809588f9375de Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 13 Nov 2017 20:15:16 +0900 Subject: Add new error class `StatementTimeout` which will be raised when statement timeout exceeded (#31129) We are sometimes using The MAX_EXECUTION_TIME hint for MySQL depending on the situation. It will prevent catastrophic performance down by wrong performing queries. The new error class `StatementTimeout` will make to be easier to handle that case. https://dev.mysql.com/doc/refman/5.7/en/optimizer-hints.html#optimizer-hints-execution-time --- .../active_record/connection_adapters/abstract_mysql_adapter.rb | 3 +++ .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 +++ activerecord/lib/active_record/errors.rb | 7 +++++-- 3 files changed, 11 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') 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 bfec6fb784..ca651ef390 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -635,6 +635,7 @@ module ActiveRecord ER_CANNOT_ADD_FOREIGN = 1215 ER_CANNOT_CREATE_TABLE = 1005 ER_LOCK_WAIT_TIMEOUT = 1205 + ER_QUERY_TIMEOUT = 3024 def translate_exception(exception, message) case error_number(exception) @@ -660,6 +661,8 @@ module ActiveRecord Deadlocked.new(message) when ER_LOCK_WAIT_TIMEOUT TransactionTimeout.new(message) + when ER_QUERY_TIMEOUT + StatementTimeout.new(message) else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 46863c41ab..5ce6765dd8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -392,6 +392,7 @@ module ActiveRecord SERIALIZATION_FAILURE = "40001" DEADLOCK_DETECTED = "40P01" LOCK_NOT_AVAILABLE = "55P03" + QUERY_CANCELED = "57014" def translate_exception(exception, message) return exception unless exception.respond_to?(:result) @@ -413,6 +414,8 @@ module ActiveRecord Deadlocked.new(message) when LOCK_NOT_AVAILABLE TransactionTimeout.new(message) + when QUERY_CANCELED + StatementTimeout.new(message) else super end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 9ef3316393..f77cd23e22 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -335,8 +335,11 @@ module ActiveRecord class IrreversibleOrderError < ActiveRecordError end - # TransactionTimeout will be raised when lock wait timeout expires. - # Wait time value is set by innodb_lock_wait_timeout. + # TransactionTimeout will be raised when lock wait timeout exceeded. class TransactionTimeout < StatementInvalid end + + # StatementTimeout will be raised when statement timeout exceeded. + class StatementTimeout < StatementInvalid + end end -- cgit v1.2.3 From 6acde9578fa55ceab8ef6520bbd5aab2a860d051 Mon Sep 17 00:00:00 2001 From: Jon Moss Date: Sun, 5 Feb 2017 16:40:03 -0500 Subject: Do not use `Arel.star` when `ignored_columns` If there are any ignored columns, we will now list out all columns we want to be returned from the database. Includes a regression test. --- activerecord/lib/active_record/relation/query_methods.rb | 2 ++ 1 file changed, 2 insertions(+) (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 897ff5c8af..a8800e432a 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1035,6 +1035,8 @@ module ActiveRecord def build_select(arel) if select_values.any? arel.project(*arel_columns(select_values.uniq)) + elsif @klass.ignored_columns.any? + arel.project(*arel_columns(@klass.column_names)) else arel.project(table[Arel.star]) end -- cgit v1.2.3 From 68fe6b08ee72cc47263e0d2c9ff07f75c4b42761 Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 13 Nov 2017 13:24:28 -0700 Subject: Properly cast input in `update_all` The documentation claims that given values go through "normal AR type casting and serialization", which to me implies `serialize(cast(value))`, not just serialization. The docs were changed to use this wording in #22492. The tests I cited in that PR (which is the same test modified in this commit), is worded in a way that implies it should be using `cast` as well. It's possible that I originally meant "normal type casting" to imply just the call to `serialize`, but given that `update_all(archived: params['archived'])` seems to be pretty common, I'm inclined to make this change as long as no tests are broken from it. --- activerecord/lib/active_record/sanitization.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index 1c3099f55c..90cc3373fb 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -110,7 +110,8 @@ module ActiveRecord def sanitize_sql_hash_for_assignment(attrs, table) # :doc: c = connection attrs.map do |attr, value| - value = type_for_attribute(attr.to_s).serialize(value) + type = type_for_attribute(attr.to_s) + value = type.serialize(type.cast(value)) "#{c.quote_table_name_for_assignment(table, attr)} = #{c.quote(value)}" end.join(", ") end -- cgit v1.2.3 From b6d5e46311d7ea59539c1f45c6ffb269eeb23912 Mon Sep 17 00:00:00 2001 From: Yuji Yaginuma Date: Tue, 14 Nov 2017 13:54:58 +0900 Subject: Add `environment` as dependency of `load_config` (#31135) Currently the environment is not loaded in some db tasks. Therefore, if use encrypted secrets values in `database.yml`, `read_encrypted_secrets` will not be true, so the value can not be used correctly. To fix this, added `environment` as dependency of `load_config`. It also removes explicit `environment` dependencies that are no longer needed. Fixes #30717 --- .../lib/active_record/railties/databases.rake | 48 +++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index 3bca2982e0..fce3e1c5cf 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -4,16 +4,16 @@ require "active_record" db_namespace = namespace :db do desc "Set the environment value for the database" - task "environment:set" => [:environment, :load_config] do + task "environment:set" => :load_config do ActiveRecord::InternalMetadata.create_table ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment end - task check_protected_environments: [:environment, :load_config] do + task check_protected_environments: :load_config do ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! end - task :load_config do + task load_config: :environment do ActiveRecord::Base.configurations = ActiveRecord::Tasks::DatabaseTasks.database_configuration || {} ActiveRecord::Migrator.migrations_paths = ActiveRecord::Tasks::DatabaseTasks.migrations_paths end @@ -56,7 +56,7 @@ db_namespace = namespace :db do end desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." - task migrate: [:environment, :load_config] do + task migrate: :load_config do ActiveRecord::Tasks::DatabaseTasks.migrate db_namespace["_dump"].invoke end @@ -78,7 +78,7 @@ db_namespace = namespace :db do namespace :migrate do # desc 'Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x).' - task redo: [:environment, :load_config] do + task redo: :load_config do raise "Empty VERSION provided" if ENV["VERSION"] && ENV["VERSION"].empty? if ENV["VERSION"] @@ -94,7 +94,7 @@ db_namespace = namespace :db do task reset: ["db:drop", "db:create", "db:migrate"] # desc 'Runs the "up" for a given migration VERSION.' - task up: [:environment, :load_config] do + task up: :load_config do raise "VERSION is required" if !ENV["VERSION"] || ENV["VERSION"].empty? ActiveRecord::Tasks::DatabaseTasks.check_target_version @@ -108,7 +108,7 @@ db_namespace = namespace :db do end # desc 'Runs the "down" for a given migration VERSION.' - task down: [:environment, :load_config] do + task down: :load_config do raise "VERSION is required - To go down one migration, use db:rollback" if !ENV["VERSION"] || ENV["VERSION"].empty? ActiveRecord::Tasks::DatabaseTasks.check_target_version @@ -122,7 +122,7 @@ db_namespace = namespace :db do end desc "Display status of migrations" - task status: [:environment, :load_config] do + task status: :load_config do unless ActiveRecord::SchemaMigration.table_exists? abort "Schema migrations table does not exist yet." end @@ -140,14 +140,14 @@ db_namespace = namespace :db do end desc "Rolls the schema back to the previous version (specify steps w/ STEP=n)." - task rollback: [:environment, :load_config] do + task rollback: :load_config do step = ENV["STEP"] ? ENV["STEP"].to_i : 1 ActiveRecord::Migrator.rollback(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step) db_namespace["_dump"].invoke end # desc 'Pushes the schema to the next version (specify steps w/ STEP=n).' - task forward: [:environment, :load_config] do + task forward: :load_config do step = ENV["STEP"] ? ENV["STEP"].to_i : 1 ActiveRecord::Migrator.forward(ActiveRecord::Tasks::DatabaseTasks.migrations_paths, step) db_namespace["_dump"].invoke @@ -157,12 +157,12 @@ db_namespace = namespace :db do task reset: [ "db:drop", "db:setup" ] # desc "Retrieves the charset for the current environment's database" - task charset: [:environment, :load_config] do + task charset: :load_config do puts ActiveRecord::Tasks::DatabaseTasks.charset_current end # desc "Retrieves the collation for the current environment's database" - task collation: [:environment, :load_config] do + task collation: :load_config do begin puts ActiveRecord::Tasks::DatabaseTasks.collation_current rescue NoMethodError @@ -171,12 +171,12 @@ db_namespace = namespace :db do end desc "Retrieves the current schema version number" - task version: [:environment, :load_config] do + task version: :load_config do puts "Current version: #{ActiveRecord::Migrator.current_version}" end # desc "Raises an error if there are pending migrations" - task abort_if_pending_migrations: [:environment, :load_config] do + task abort_if_pending_migrations: :load_config do pending_migrations = ActiveRecord::Migrator.open(ActiveRecord::Tasks::DatabaseTasks.migrations_paths).pending_migrations if pending_migrations.any? @@ -199,7 +199,7 @@ db_namespace = namespace :db do namespace :fixtures do desc "Loads fixtures into the current environment's database. Load specific fixtures using FIXTURES=x,y. Load from subdirectory in test/fixtures using FIXTURES_DIR=z. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." - task load: [:environment, :load_config] do + task load: :load_config do require "active_record/fixtures" base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path @@ -221,7 +221,7 @@ db_namespace = namespace :db do end # desc "Search for a fixture given a LABEL or ID. Specify an alternative path (eg. spec/fixtures) using FIXTURES_PATH=spec/fixtures." - task identify: [:environment, :load_config] do + task identify: :load_config do require "active_record/fixtures" label, id = ENV["LABEL"], ENV["ID"] @@ -247,7 +247,7 @@ db_namespace = namespace :db do namespace :schema do desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record" - task dump: [:environment, :load_config] do + task dump: :load_config do require "active_record/schema_dumper" filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema.rb") File.open(filename, "w:utf-8") do |file| @@ -257,7 +257,7 @@ db_namespace = namespace :db do end desc "Loads a schema.rb file into the database" - task load: [:environment, :load_config, :check_protected_environments] do + task load: [:load_config, :check_protected_environments] do ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV["SCHEMA"]) end @@ -267,14 +267,14 @@ db_namespace = namespace :db do namespace :cache do desc "Creates a db/schema_cache.yml file." - task dump: [:environment, :load_config] do + task dump: :load_config do conn = ActiveRecord::Base.connection filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(conn, filename) end desc "Clears a db/schema_cache.yml file." - task clear: [:environment, :load_config] do + task clear: :load_config do filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.yml") rm_f filename, verbose: false end @@ -284,7 +284,7 @@ db_namespace = namespace :db do namespace :structure do desc "Dumps the database structure to db/structure.sql. Specify another file with SCHEMA=db/my_structure.sql" - task dump: [:environment, :load_config] do + task dump: :load_config do filename = ENV["SCHEMA"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql") current_config = ActiveRecord::Tasks::DatabaseTasks.current_config ActiveRecord::Tasks::DatabaseTasks.structure_dump(current_config, filename) @@ -299,7 +299,7 @@ db_namespace = namespace :db do end desc "Recreates the databases from the structure.sql file" - task load: [:environment, :load_config, :check_protected_environments] do + task load: [:load_config, :check_protected_environments] do ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV["SCHEMA"]) end @@ -338,12 +338,12 @@ db_namespace = namespace :db do end # desc "Empty the test database" - task purge: %w(environment load_config check_protected_environments) do + task purge: %w(load_config check_protected_environments) do ActiveRecord::Tasks::DatabaseTasks.purge ActiveRecord::Base.configurations["test"] end # desc 'Load the test schema' - task prepare: %w(environment load_config) do + task prepare: :load_config do unless ActiveRecord::Base.configurations.blank? db_namespace["test:load"].invoke end -- cgit v1.2.3 From 67dbfc69f690f231d5b8257e03b8338af19c1d05 Mon Sep 17 00:00:00 2001 From: Nikolai B Date: Tue, 14 Nov 2017 14:12:40 +0000 Subject: Update `exists?` documentation Make it clear that `exists?` can be chained onto a relation --- activerecord/lib/active_record/relation/finder_methods.rb | 3 ++- 1 file changed, 2 insertions(+), 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 18566b5662..706fd57704 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -284,7 +284,7 @@ module ActiveRecord # * Hash - Finds the record that matches these +find+-style conditions # (such as {name: 'David'}). # * +false+ - Returns always +false+. - # * No args - Returns +false+ if the table is empty, +true+ otherwise. + # * No args - Returns +false+ if the relation is empty, +true+ otherwise. # # For more information about specifying conditions as a hash or array, # see the Conditions section in the introduction to ActiveRecord::Base. @@ -300,6 +300,7 @@ module ActiveRecord # Person.exists?(name: 'David') # Person.exists?(false) # Person.exists? + # Person.where(name: 'Spartacus', rating: 4).exists? def exists?(conditions = :none) if Base === conditions raise ArgumentError, <<-MSG.squish -- cgit v1.2.3 From df82237a45e930a7ab53b95bee78a3f34c5b92fb Mon Sep 17 00:00:00 2001 From: Rich Date: Tue, 14 Nov 2017 19:24:00 +0000 Subject: Add a #populate method to migrations (#31082) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add a #populate method to migrations * Address rubocop issues * Rename to #up_only and use #execute in the examples intead of the model * Update CHANGELOG [Rich Daley & Rafael Mendonça França] --- activerecord/lib/active_record/migration.rb | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index c13efa9d70..67c8c9fc08 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -734,6 +734,24 @@ module ActiveRecord execute_block { yield helper } end + # Used to specify an operation that is only run when migrating up + # (for example, populating a new column with its initial values). + # + # In the following example, the new column `published` will be given + # the value `true` for all existing records. + # + # class AddPublishedToPosts < ActiveRecord::Migration[5.3] + # def change + # add_column :posts, :published, :boolean, default: false + # up_only do + # execute "update posts set published = 'true'" + # end + # end + # end + def up_only + execute_block { yield } unless reverting? + end + # Runs the given migration classes. # Last argument can specify options: # - :direction (default is :up) -- cgit v1.2.3 From 46a2f93614ccf0d1628e6fc3c4666cee476d17c8 Mon Sep 17 00:00:00 2001 From: bogdanvlviv Date: Tue, 14 Nov 2017 20:02:52 +0000 Subject: Fix migration version in doc of #up_only --- activerecord/lib/active_record/migration.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 67c8c9fc08..360bf25a8c 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -740,7 +740,7 @@ module ActiveRecord # In the following example, the new column `published` will be given # the value `true` for all existing records. # - # class AddPublishedToPosts < ActiveRecord::Migration[5.3] + # class AddPublishedToPosts < ActiveRecord::Migration[5.2] # def change # add_column :posts, :published, :boolean, default: false # up_only do -- cgit v1.2.3 From bbae710a405ce92074c1666dcf859196c29e2ed2 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 17 Nov 2017 03:12:57 +0900 Subject: Avoid creating extra `relation` and `build_arel` in `_create_record` and `_update_record` (#29999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently `_create_record` and `_update_record` in `Persistence` are creating extra `unscoped` and calling `build_arel` in the relation. But `compile_insert` and `compile_update` can be done without those expensive operation for `SelectManager` creation. So I moved the implementation to `Persistence` to avoid creating extra relation and refactored to avoid calling `build_arel`. https://gist.github.com/kamipo/8ed73d760112cfa5f6263c9413633419 Before: ``` Warming up -------------------------------------- _update_record 150.000 i/100ms Calculating ------------------------------------- _update_record 1.548k (±12.3%) i/s - 7.650k in 5.042603s ``` After: ``` Warming up -------------------------------------- _update_record 201.000 i/100ms Calculating ------------------------------------- _update_record 2.002k (±12.8%) i/s - 9.849k in 5.027681s ``` 30% faster for STI classes. --- activerecord/lib/active_record/persistence.rb | 43 ++++++++++++++++++- activerecord/lib/active_record/relation.rb | 61 --------------------------- 2 files changed, 41 insertions(+), 63 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a57c60ffac..4e1b05dbf6 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -165,6 +165,38 @@ module ActiveRecord where(primary_key => id_or_array).delete_all end + def _insert_record(values) # :nodoc: + primary_key_value = nil + + if primary_key && Hash === values + arel_primary_key = arel_attribute(primary_key) + primary_key_value = values[arel_primary_key] + + if !primary_key_value && prefetch_primary_key? + primary_key_value = next_sequence_value + values[arel_primary_key] = primary_key_value + end + end + + if values.empty? + im = arel_table.compile_insert(connection.empty_insert_statement_value) + im.into arel_table + else + im = arel_table.compile_insert(_substitute_values(values)) + end + + connection.insert(im, "#{self} Create", primary_key || false, primary_key_value) + end + + def _update_record(values, id, id_was) # :nodoc: + bind = predicate_builder.build_bind_attribute(primary_key, id_was || id) + um = arel_table.where( + arel_attribute(primary_key).eq(bind) + ).compile_update(_substitute_values(values), primary_key) + + connection.update(um, "#{self} Update") + end + private # Called by +instantiate+ to decide which class to use for a new # record instance. @@ -174,6 +206,13 @@ module ActiveRecord def discriminate_class_for_record(record) self end + + def _substitute_values(values) + values.map do |attr, value| + bind = predicate_builder.build_bind_attribute(attr.name, value) + [attr, bind] + end + end end # Returns true if this object hasn't been saved yet -- that is, a record @@ -671,7 +710,7 @@ module ActiveRecord rows_affected = 0 @_trigger_update_callback = true else - rows_affected = self.class.unscoped._update_record attributes_values, id, id_in_database + rows_affected = self.class._update_record(attributes_values, id, id_in_database) @_trigger_update_callback = rows_affected > 0 end @@ -685,7 +724,7 @@ module ActiveRecord def _create_record(attribute_names = self.attribute_names) attributes_values = arel_attributes_with_values_for_create(attribute_names) - new_id = self.class.unscoped.insert attributes_values + new_id = self.class._insert_record(attributes_values) self.id ||= new_id if self.class.primary_key @new_record = false diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index e2d2f45503..081ef5771f 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -36,67 +36,6 @@ module ActiveRecord reset end - def insert(values) # :nodoc: - primary_key_value = nil - - if primary_key && Hash === values - primary_key_value = values[values.keys.find { |k| - k.name == primary_key - }] - - if !primary_key_value && klass.prefetch_primary_key? - primary_key_value = klass.next_sequence_value - values[arel_attribute(klass.primary_key)] = primary_key_value - end - end - - im = arel.create_insert - im.into @table - - substitutes = substitute_values values - - if values.empty? # empty insert - im.values = Arel.sql(connection.empty_insert_statement_value) - else - im.insert substitutes - end - - @klass.connection.insert( - im, - "#{@klass} Create", - primary_key || false, - primary_key_value, - nil, - ) - end - - def _update_record(values, id, id_was) # :nodoc: - substitutes = substitute_values values - - scope = @klass.unscoped - - if @klass.finder_needs_type_condition? - scope.unscope!(where: @klass.inheritance_column) - end - - relation = scope.where(@klass.primary_key => (id_was || id)) - um = relation - .arel - .compile_update(substitutes, @klass.primary_key) - - @klass.connection.update( - um, - "#{@klass} Update", - ) - end - - def substitute_values(values) # :nodoc: - values.map do |arel_attr, value| - bind = predicate_builder.build_bind_attribute(arel_attr.name, value) - [arel_attr, bind] - end - end - def arel_attribute(name) # :nodoc: klass.arel_attribute(name, table) end -- cgit v1.2.3 From f32cff5563f2188e657aa2fd9f8513f0da4a49ca Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Fri, 17 Nov 2017 21:55:39 +1030 Subject: Improve AR connection fork safety Use whatever adapter-provided means we have available to ensure forked children don't send quit/shutdown/goodbye messages to the server on connections that belonged to their parent. --- .../abstract/connection_pool.rb | 35 ++++++++++++++++++++++ .../connection_adapters/abstract_adapter.rb | 13 ++++++++ .../connection_adapters/mysql2_adapter.rb | 5 ++++ .../connection_adapters/postgresql_adapter.rb | 5 ++++ 4 files changed, 58 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 0759f4d2b3..0f39c077c9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -450,6 +450,21 @@ module ActiveRecord disconnect(false) end + # Discards all connections in the pool (even if they're currently + # leased!), along with the pool itself. Any further interaction with the + # pool (except #spec and #schema_cache) is undefined. + # + # See AbstractAdapter#discard! + def discard! # :nodoc: + synchronize do + return if @connections.nil? # already discarded + @connections.each do |conn| + conn.discard! + end + @connections = @available = @thread_cached_conns = nil + end + end + # Clears the cache which maps classes and re-connects connections that # require reloading. # @@ -866,11 +881,31 @@ module ActiveRecord # about the model. The model needs to pass a specification name to the handler, # in order to look up the correct connection pool. class ConnectionHandler + def self.unowned_pool_finalizer(pid_map) # :nodoc: + lambda do |_| + discard_unowned_pools(pid_map) + end + end + + def self.discard_unowned_pools(pid_map) # :nodoc: + pid_map.each do |pid, pools| + pools.values.compact.each(&:discard!) unless pid == Process.pid + end + end + def initialize # These caches are keyed by spec.name (ConnectionSpecification#name). @owner_to_pool = Concurrent::Map.new(initial_capacity: 2) do |h, k| + # Discard the parent's connection pools immediately; we have no need + # of them + ConnectionHandler.discard_unowned_pools(h) + h[k] = Concurrent::Map.new(initial_capacity: 2) end + + # Backup finalizer: if the forked child never needed a pool, the above + # early discard has not occurred + ObjectSpace.define_finalizer self, ConnectionHandler.unowned_pool_finalizer(@owner_to_pool) end def connection_pool_list diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 345983a655..df11391fa7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -366,6 +366,19 @@ module ActiveRecord reset_transaction end + # Immediately forget this connection ever existed. Unlike disconnect!, + # this will not communicate with the server. + # + # After calling this method, the behavior of all other methods becomes + # undefined. This is called internally just before a forked process gets + # rid of a connection that belonged to its parent. + def discard! + # This should be overridden by concrete adapters. + # + # Prevent @connection's finalizer from touching the socket, or + # otherwise communicating with its server, when it is collected. + end + # Reset the state of this connection, directing the DBMS to clear # transactions and other connection-related server-side state. Usually a # database-dependent operation. diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 8de582fee1..1d614dc8bf 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -105,6 +105,11 @@ module ActiveRecord @connection.close end + def discard! # :nodoc: + @connection.automatic_close = false + @connection = nil + end + private def connect diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5ce6765dd8..1739c288b6 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -273,6 +273,11 @@ module ActiveRecord end end + def discard! # :nodoc: + @connection.socket_io.reopen(IO::NULL) + @connection = nil + end + def native_database_types #:nodoc: NATIVE_DATABASE_TYPES end -- cgit v1.2.3 From eeaf9cf61c3cd14929583878785c31dab79e2196 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 20 Nov 2017 06:24:50 +0900 Subject: Prevent extra `spawn` to make `klass.all` faster (#29009) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These extra `spawn` are called via `klass.all` and `klass.all` is called everywhere in the internal. Avoiding the extra `spawn` makes` klass.all` 30% faster for STI classes. https://gist.github.com/kamipo/684d03817a8115848cec8e8b079560b7 ``` Warming up -------------------------------------- fast relation 4.410k i/100ms slow relation 3.334k i/100ms Calculating ------------------------------------- fast relation 47.373k (± 5.2%) i/s - 238.140k in 5.041836s slow relation 35.757k (±15.9%) i/s - 176.702k in 5.104625s Comparison: fast relation: 47373.2 i/s slow relation: 35756.7 i/s - 1.32x slower ``` --- activerecord/lib/active_record/core.rb | 3 ++- activerecord/lib/active_record/scoping/default.rb | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index b97b14644e..481159e501 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -277,7 +277,8 @@ module ActiveRecord relation = Relation.create(self, arel_table, predicate_builder) if finder_needs_type_condition? && !ignore_default_scope? - relation.where(type_condition).create_with(inheritance_column.to_s => sti_name) + relation.where!(type_condition) + relation.create_with!(inheritance_column.to_s => sti_name) else relation end diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index 86ae374318..8c612df27a 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -111,7 +111,7 @@ module ActiveRecord # The user has defined their own default scope method, so call that evaluate_default_scope do if scope = default_scope - (base_rel ||= relation).merge(scope) + (base_rel ||= relation).merge!(scope) end end elsif default_scopes.any? @@ -119,7 +119,7 @@ module ActiveRecord evaluate_default_scope do default_scopes.inject(base_rel) do |default_scope, scope| scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call) - default_scope.merge(base_rel.instance_exec(&scope)) + default_scope.merge!(base_rel.instance_exec(&scope)) end end end -- cgit v1.2.3 From 3063ace1070e4ddb8d0cc09fbd23049e7b21617a Mon Sep 17 00:00:00 2001 From: "T.J. Schuck" Date: Wed, 22 Nov 2017 14:45:51 -0500 Subject: Update incorrect backtick usage in RDoc to teletype [ci skip] --- activerecord/lib/active_record/migration.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 360bf25a8c..15e9c09ffb 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -737,8 +737,8 @@ module ActiveRecord # Used to specify an operation that is only run when migrating up # (for example, populating a new column with its initial values). # - # In the following example, the new column `published` will be given - # the value `true` for all existing records. + # In the following example, the new column +published+ will be given + # the value +true+ for all existing records. # # class AddPublishedToPosts < ActiveRecord::Migration[5.2] # def change -- cgit v1.2.3 From ae032ec38463e923c0556fbdd28b9d9fced18b11 Mon Sep 17 00:00:00 2001 From: Nikita Misharin Date: Mon, 20 Nov 2017 16:31:46 +0300 Subject: Provide arguments to RecordNotFound --- .../active_record/associations/collection_association.rb | 8 +++++++- activerecord/lib/active_record/relation/finder_methods.rb | 14 +++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) (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 ed215fb22c..921237a735 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -79,7 +79,13 @@ module ActiveRecord def find(*args) if options[:inverse_of] && loaded? args_flatten = args.flatten - raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank? + model = scope.klass + + if args_flatten.blank? + error_message = "Couldn't find #{model.name} without an ID" + raise RecordNotFound.new(error_message, model.name, model.primary_key, args) + end + result = find_by_scan(*args) result_size = Array(result).size diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 706fd57704..77c1367556 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -88,7 +88,7 @@ module ActiveRecord where(arg, *args).take! rescue ::RangeError raise RecordNotFound.new("Couldn't find #{@klass.name} with an out of range value", - @klass.name) + @klass.name, @klass.primary_key) end # Gives a record (or N records if a parameter is supplied) without any implied @@ -339,7 +339,7 @@ module ActiveRecord if ids.nil? error = "Couldn't find #{name}".dup error << " with#{conditions}" if conditions - raise RecordNotFound.new(error, name) + raise RecordNotFound.new(error, name, key) elsif Array(ids).size == 1 error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}" raise RecordNotFound.new(error, name, key, ids) @@ -347,7 +347,7 @@ module ActiveRecord error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})." error << " Couldn't find #{name.pluralize(not_found_ids.size)} with #{key.to_s.pluralize(not_found_ids.size)} #{not_found_ids.join(', ')}." if not_found_ids - raise RecordNotFound.new(error, name, primary_key, ids) + raise RecordNotFound.new(error, name, key, ids) end end @@ -433,9 +433,12 @@ module ActiveRecord ids = ids.flatten.compact.uniq + model_name = @klass.name + case ids.size when 0 - raise RecordNotFound, "Couldn't find #{@klass.name} without an ID" + error_message = "Couldn't find #{model_name} without an ID" + raise RecordNotFound.new(error_message, model_name, primary_key) when 1 result = find_one(ids.first) expects_array ? [ result ] : result @@ -443,7 +446,8 @@ module ActiveRecord find_some(ids) end rescue ::RangeError - raise RecordNotFound, "Couldn't find #{@klass.name} with an out of range ID" + error_message = "Couldn't find #{model_name} with an out of range ID" + raise RecordNotFound.new(error_message, model_name, primary_key, ids) end def find_one(id) -- cgit v1.2.3 From 9027fafff6da932e6e64ddb828665f4b01fc8902 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sat, 25 Nov 2017 16:05:13 +1030 Subject: Flush idle database connections --- .../abstract/connection_pool.rb | 74 +++++++++++++++++----- .../connection_adapters/abstract_adapter.rb | 8 +++ .../lib/active_record/connection_handling.rb | 2 +- activerecord/lib/active_record/railtie.rb | 9 +++ 4 files changed, 76 insertions(+), 17 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 6c06f67239..94687b9bb7 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -63,15 +63,13 @@ module ActiveRecord # There are several connection-pooling-related options that you can add to # your database connection configuration: # - # * +pool+: number indicating size of connection pool (default 5) - # * +checkout_timeout+: number of seconds to block and wait for a connection - # before giving up and raising a timeout error (default 5 seconds). - # * +reaping_frequency+: frequency in seconds to periodically run the - # Reaper, which attempts to find and recover connections from dead - # threads, which can occur if a programmer forgets to close a - # connection at the end of a thread or a thread dies unexpectedly. - # Regardless of this setting, the Reaper will be invoked before every - # blocking wait. (Default +nil+, which means don't schedule the Reaper). + # * +pool+: maximum number of connections the pool may manage (default 5). + # * +idle_timeout+: number of seconds that a connection will be kept + # unused in the pool before it is automatically disconnected (default + # 300 seconds). Set this to zero to keep connections forever. + # * +checkout_timeout+: number of seconds to wait for a connection to + # become available before giving up and raising a timeout error (default + # 5 seconds). # #-- # Synchronization policy: @@ -280,12 +278,12 @@ module ActiveRecord end end - # Every +frequency+ seconds, the reaper will call +reap+ on +pool+. - # A reaper instantiated with a +nil+ frequency will never reap the - # connection pool. + # Every +frequency+ seconds, the reaper will call +reap+ and +flush+ on + # +pool+. A reaper instantiated with a zero frequency will never reap + # the connection pool. # - # Configure the frequency by setting "reaping_frequency" in your - # database yaml file. + # Configure the frequency by setting +reaping_frequency+ in your database + # yaml file (default 60 seconds). class Reaper attr_reader :pool, :frequency @@ -295,11 +293,12 @@ module ActiveRecord end def run - return unless frequency + return unless frequency && frequency > 0 Thread.new(frequency, pool) { |t, p| loop do sleep t p.reap + p.flush end } end @@ -323,6 +322,10 @@ module ActiveRecord @spec = spec @checkout_timeout = (spec.config[:checkout_timeout] && spec.config[:checkout_timeout].to_f) || 5 + if @idle_timeout = spec.config.fetch(:idle_timeout, 300) + @idle_timeout = @idle_timeout.to_f + @idle_timeout = nil if @idle_timeout <= 0 + end # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 @@ -353,7 +356,10 @@ module ActiveRecord @lock_thread = false - @reaper = Reaper.new(self, spec.config[:reaping_frequency] && spec.config[:reaping_frequency].to_f) + # +reaping_frequency+ is configurable mostly for historical reasons, but it could + # also be useful if someone wants a very low +idle_timeout+. + reaping_frequency = spec.config.fetch(:reaping_frequency, 60) + @reaper = Reaper.new(self, reaping_frequency && reaping_frequency.to_f) @reaper.run end @@ -572,6 +578,35 @@ module ActiveRecord end end + # Disconnect all connections that have been idle for at least + # +minimum_idle+ seconds. Connections currently checked out, or that were + # checked in less than +minimum_idle+ seconds ago, are unaffected. + def flush(minimum_idle = @idle_timeout) + return if minimum_idle.nil? + + idle_connections = synchronize do + @connections.select do |conn| + !conn.in_use? && conn.seconds_idle >= minimum_idle + end.each do |conn| + conn.lease + + @available.delete conn + @connections.delete conn + end + end + + idle_connections.each do |conn| + conn.disconnect! + end + end + + # Disconnect all currently idle connections. Connections currently checked + # out are unaffected. + def flush! + reap + flush(-1) + end + def num_waiting_in_queue # :nodoc: @available.num_waiting end @@ -921,6 +956,13 @@ module ActiveRecord connection_pool_list.each(&:disconnect!) end + # Disconnects all currently idle connections. + # + # See ConnectionPool#flush! for details. + def flush_idle_connections! + connection_pool_list.each(&:flush!) + end + # Locate the connection of the nearest super class. This can be an # active or defined connection: if it is the latter, it will be # opened and set as the active connection for the class it was defined diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 7e6db860dd..95e808648c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -105,6 +105,7 @@ module ActiveRecord @logger = logger @config = config @pool = nil + @idle_since = Concurrent.monotonic_time @schema_cache = SchemaCache.new self @quoted_column_names, @quoted_table_names = {}, {} @visitor = arel_visitor @@ -164,6 +165,7 @@ module ActiveRecord "Current thread: #{Thread.current}." end + @idle_since = Concurrent.monotonic_time @owner = nil else raise ActiveRecordError, "Cannot expire connection, it is not currently leased." @@ -183,6 +185,12 @@ module ActiveRecord end end + # Seconds since this connection was returned to the pool + def seconds_idle # :nodoc: + return 0 if in_use? + Concurrent.monotonic_time - @idle_since + end + def unprepared_statement old_prepared_statements, @prepared_statements = @prepared_statements, false yield diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb index 9a47edfba4..88d28dc52a 100644 --- a/activerecord/lib/active_record/connection_handling.rb +++ b/activerecord/lib/active_record/connection_handling.rb @@ -140,6 +140,6 @@ module ActiveRecord end delegate :clear_active_connections!, :clear_reloadable_connections!, - :clear_all_connections!, to: :connection_handler + :clear_all_connections!, :flush_idle_connections!, to: :connection_handler end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 812e1d7a00..9ee8425e1b 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -177,7 +177,16 @@ end_warning initializer "active_record.clear_active_connections" do config.after_initialize do ActiveSupport.on_load(:active_record) do + # Ideally the application doesn't connect to the database during boot, + # but sometimes it does. In case it did, we want to empty out the + # connection pools so that a non-database-using process (e.g. a master + # process in a forking server model) doesn't retain a needless + # connection. If it was needed, the incremental cost of reestablishing + # this connection is trivial: the rest of the pool would need to be + # populated anyway. + clear_active_connections! + flush_idle_connections! end end end -- cgit v1.2.3 From c43bcc812549ed939952a08419588eb44e0794d0 Mon Sep 17 00:00:00 2001 From: Matthew Draper Date: Sun, 26 Nov 2017 15:33:49 +1030 Subject: Let rubygems handle our objection to mysql2 0.4.3 --- activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 1d614dc8bf..957e8acc90 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -3,9 +3,8 @@ require "active_record/connection_adapters/abstract_mysql_adapter" require "active_record/connection_adapters/mysql/database_statements" -gem "mysql2", ">= 0.3.18", "< 0.5" +gem "mysql2", ">= 0.3.18", "< 0.5", "!= 0.4.3" require "mysql2" -raise "mysql2 0.4.3 is not supported. Please upgrade to 0.4.4+" if Mysql2::VERSION == "0.4.3" module ActiveRecord module ConnectionHandling # :nodoc: -- cgit v1.2.3 From d7ab5710916f8ee58a970312e9bc9276022fe3a6 Mon Sep 17 00:00:00 2001 From: eileencodes Date: Sun, 26 Nov 2017 10:22:54 -0500 Subject: Revert "Merge pull request #31006 from rails/kamipo/ordinal_methods_should_respect_loaded_records" This reverts commit 0f79ab91150b4cdb6c018530978a3395962c7a02, reversing changes made to d575f7f2e737739302a0e8210d01c10f5d4e2c35. This PR philosophically conflicts with #30800 and Matthew thinks we should hold off merging this until we find concensus. Reverting since we're about to cut a release for 5.2. --- activerecord/lib/active_record/relation.rb | 1 - 1 file changed, 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index d3b8091665..081ef5771f 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -544,7 +544,6 @@ module ActiveRecord end @records.each(&:readonly!) if readonly_value - @offsets = {} unless @offsets.empty? @loaded = true @records -- cgit v1.2.3 From ad0630f0ae2d65bc0adda4097af8009a985b6f15 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 27 Nov 2017 10:30:25 +0900 Subject: Rename `TransactionTimeout` to more descriptive `LockWaitTimeout` (#31223) Since #31129, new error class `StatementTimeout` has been added. `TransactionTimeout` is caused by the timeout shorter than `StatementTimeout`, but its name is too generic. I think that it should be a name that understands the difference with `StatementTimeout`. --- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 2 +- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- activerecord/lib/active_record/errors.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') 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 0e552dba95..77ccac54bf 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -660,7 +660,7 @@ module ActiveRecord when ER_LOCK_DEADLOCK Deadlocked.new(message) when ER_LOCK_WAIT_TIMEOUT - TransactionTimeout.new(message) + LockWaitTimeout.new(message) when ER_QUERY_TIMEOUT StatementTimeout.new(message) else diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 1739c288b6..489feadb00 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -418,7 +418,7 @@ module ActiveRecord when DEADLOCK_DETECTED Deadlocked.new(message) when LOCK_NOT_AVAILABLE - TransactionTimeout.new(message) + LockWaitTimeout.new(message) when QUERY_CANCELED StatementTimeout.new(message) else diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 7382879fce..89e777ffc3 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -335,8 +335,8 @@ module ActiveRecord class IrreversibleOrderError < ActiveRecordError end - # TransactionTimeout will be raised when lock wait timeout exceeded. - class TransactionTimeout < StatementInvalid + # LockWaitTimeout will be raised when lock wait timeout exceeded. + class LockWaitTimeout < StatementInvalid end # StatementTimeout will be raised when statement timeout exceeded. -- cgit v1.2.3 From 0e2cd3d749dadcdb5027d48399aea02ef74815f3 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 27 Nov 2017 11:54:59 +0900 Subject: Add new error class `QueryCanceled` which will be raised when canceling statement due to user request (#31235) This changes `StatementTimeout` to `QueryCanceled` for PostgreSQL. In MySQL, errno 1317 (`ER_QUERY_INTERRUPTED`) is only used when the query is manually cancelled. But in PostgreSQL, `QUERY_CANCELED` error code (57014) which is used `StatementTimeout` is also used when the both case. And, we can not tell which reason happened. So I decided to introduce new error class `QueryCanceled` closer to the error code name. --- .../lib/active_record/connection_adapters/abstract_mysql_adapter.rb | 3 +++ .../lib/active_record/connection_adapters/postgresql_adapter.rb | 2 +- activerecord/lib/active_record/errors.rb | 4 ++++ 3 files changed, 8 insertions(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') 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 77ccac54bf..ede8a9c1e2 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -635,6 +635,7 @@ module ActiveRecord ER_CANNOT_ADD_FOREIGN = 1215 ER_CANNOT_CREATE_TABLE = 1005 ER_LOCK_WAIT_TIMEOUT = 1205 + ER_QUERY_INTERRUPTED = 1317 ER_QUERY_TIMEOUT = 3024 def translate_exception(exception, message) @@ -663,6 +664,8 @@ module ActiveRecord LockWaitTimeout.new(message) when ER_QUERY_TIMEOUT StatementTimeout.new(message) + when ER_QUERY_INTERRUPTED + QueryCanceled.new(message) else super end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 489feadb00..7b27f6b7a0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -420,7 +420,7 @@ module ActiveRecord when LOCK_NOT_AVAILABLE LockWaitTimeout.new(message) when QUERY_CANCELED - StatementTimeout.new(message) + QueryCanceled.new(message) else super end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 89e777ffc3..efcbd44776 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -343,6 +343,10 @@ module ActiveRecord class StatementTimeout < StatementInvalid end + # QueryCanceled will be raised when canceling statement due to user request. + class QueryCanceled < StatementInvalid + end + # UnknownAttributeReference is raised when an unknown and potentially unsafe # value is passed to a query method when allow_unsafe_raw_sql is set to # :disabled. For example, passing a non column name value to a relation's -- cgit v1.2.3 From c10152daf948b5bdcf48cda06b9bbddee9e8c398 Mon Sep 17 00:00:00 2001 From: Anmol Chopra Date: Fri, 24 Nov 2017 17:52:35 +0530 Subject: Inverse instance should not be reloaded during autosave if called in validation Record saved in save_has_one_association already make call to association.loaded! via record's before_save callback of save_belongs_to_association, but this will reload object if accessed in record's validation. --- activerecord/lib/active_record/autosave_association.rb | 3 +++ 1 file changed, 3 insertions(+) (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 6974cf74f6..a1250c3835 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -436,6 +436,9 @@ module ActiveRecord if (autosave && record.changed_for_autosave?) || new_record? || record_changed?(reflection, record, key) unless reflection.through_reflection record[reflection.foreign_key] = key + if inverse_reflection = reflection.inverse_of + record.association(inverse_reflection.name).loaded! + end end saved = record.save(validate: !autosave) -- cgit v1.2.3 From cceeeb6e57f1cf8b24d507e2da9ed85d374c8bc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Mon, 27 Nov 2017 13:01:15 -0500 Subject: Preparing for 5.2.0.beta1 release --- activerecord/lib/active_record/gem_version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index 7ccb57b305..c262fb3229 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -10,7 +10,7 @@ module ActiveRecord MAJOR = 5 MINOR = 2 TINY = 0 - PRE = "alpha" + PRE = "beta1" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end -- cgit v1.2.3 From 924a368f5c654f5304e575c767eb0fc64adc8659 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Tue, 28 Nov 2017 05:09:00 +0900 Subject: Drop mysql2 version less than 0.4.3 to guarantee fork safety (#31244) Since #31173, mysql2 adapter depends on `automatic_close` which is introduced since mysql2 0.4.3. So the adapter with the mysql2 version before doesn't work with fork now. ``` % ARCONN=mysql2 be ruby -w -Itest test/cases/connection_adapters/connection_handler_test.rb -n test_forked_child_doesnt_mangle_parent_connection Using mysql2 Run options: -n test_forked_child_doesnt_mangle_parent_connection --seed 19988 /Users/kamipo/src/github.com/rails/rails/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb:108:in `discard!': undefined method `automatic_close=' for # (NoMethodError) ``` This drops mysql2 version less than 0.4.3 to guarantee fork safety. --- activerecord/lib/active_record/connection_adapters/mysql2_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/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index 957e8acc90..bfdc7995f0 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -3,7 +3,7 @@ require "active_record/connection_adapters/abstract_mysql_adapter" require "active_record/connection_adapters/mysql/database_statements" -gem "mysql2", ">= 0.3.18", "< 0.5", "!= 0.4.3" +gem "mysql2", "~> 0.4.4" require "mysql2" module ActiveRecord -- cgit v1.2.3 From 95b86e57a6c07bc636f8feab4afd4c7fe0ca512c Mon Sep 17 00:00:00 2001 From: Sean Griffin Date: Mon, 27 Nov 2017 14:06:51 -0700 Subject: Change how `AttributeSet::Builder` receives its defaults There are two concerns which are both being combined into one here, but both have the same goal. There are certain attributes which we want to always consider initialized. Previously, they were handled separately. The primary key (which is assumed to be backed by a database column) needs to be initialized, because there is a ton of code in Active Record that assumes `foo.id` will never raise. Additionally, we want attributes which aren't backed by a database column to always be initialized, since we would never receive a database value for them. Ultimately these two concerns can be combined into one. The old implementation hid a lot of inherent complexity, and is hard to optimize from the outside. We can simplify things significantly by just passing in a hash. This has slightly different semantics from the old behavior, in that `Foo.select(:bar).first.id` will return the default value for the primary key, rather than `nil` unconditionally -- however, the default value is always `nil` in practice. --- activerecord/lib/active_record/model_schema.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 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 12ee4a4137..1941d3d5ea 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -323,11 +323,11 @@ module ActiveRecord end def attributes_builder # :nodoc: - @attributes_builder ||= ActiveModel::AttributeSet::Builder.new(attribute_types, primary_key) do |name| - unless columns_hash.key?(name) - _default_attributes[name].dup - end + unless defined?(@attributes_builder) && @attributes_builder + defaults = _default_attributes.except(*(column_names - [primary_key])) + @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults) end + @attributes_builder end def columns_hash # :nodoc: -- cgit v1.2.3 From 2c5acb20dd944cc061a03e8d1ca6d3e469c0d46e Mon Sep 17 00:00:00 2001 From: suginoy Date: Tue, 28 Nov 2017 19:36:01 +0900 Subject: Update docs `ActiveRecord::FinderMethods#find` ref https://github.com/rails/rails/pull/22653 --- activerecord/lib/active_record/relation/finder_methods.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (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 77c1367556..ff06ecbee1 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -18,9 +18,10 @@ module ActiveRecord # Person.find([1]) # returns an array for the object with ID = 1 # Person.where("administrator = 1").order("created_on DESC").find(1) # - # NOTE: The returned records may not be in the same order as the ids you - # provide since database rows are unordered. You will need to provide an explicit QueryMethods#order - # option if you want the results to be sorted. + # NOTE: The returned records are in the same order as the ids you provide. + # If you want the results to be sorted by database, you can use ActiveRecord::QueryMethods#where + # method and provide an explicit ActiveRecord::QueryMethods#order option. + # But ActiveRecord::QueryMethods#where method doesn't raise ActiveRecord::RecordNotFound. # # ==== Find with lock # -- cgit v1.2.3 From 6552ce0361f30a46a58a8bcb659866f06c8b7430 Mon Sep 17 00:00:00 2001 From: Chen Kinnrot Date: Sat, 18 Nov 2017 21:22:00 +0200 Subject: Prevent scope named same as a ActiveRecord::Relation instance method. Due to inconsistent behavior when chaining scopes and one scope named after a Relation method Validation code added in 2 places: - scope, to prevent problematic scope names. - enum, cause it tries to auto define scope. --- activerecord/lib/active_record/enum.rb | 2 ++ activerecord/lib/active_record/scoping/named.rb | 6 ++++++ 2 files changed, 8 insertions(+) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index f373b98035..1a3e6e4d09 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -221,6 +221,8 @@ module ActiveRecord def detect_enum_conflict!(enum_name, method_name, klass_method = false) if klass_method && dangerous_class_method?(method_name) raise_conflict_error(enum_name, method_name, type: "class") + elsif klass_method && method_defined_within?(method_name, Relation) + raise_conflict_error(enum_name, method_name, type: "class", source: Relation.name) elsif !klass_method && dangerous_attribute_method?(method_name) raise_conflict_error(enum_name, method_name) elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module) diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index 310af72c41..752655aa05 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -171,6 +171,12 @@ module ActiveRecord "a class method with the same name." end + if method_defined_within?(name, Relation) + raise ArgumentError, "You tried to define a scope named \"#{name}\" " \ + "on the model \"#{self.name}\", but ActiveRecord::Relation already defined " \ + "an instance method with the same name." + end + valid_scope_name?(name) extension = Module.new(&block) if block -- cgit v1.2.3 From 2837d0f3347e747a8c12bd3c097bc7282072d42b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Mendon=C3=A7a=20Fran=C3=A7a?= Date: Tue, 28 Nov 2017 00:01:45 -0500 Subject: Preparing for 5.2.0.beta2 release --- activerecord/lib/active_record/gem_version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb index c262fb3229..7e47dac016 100644 --- a/activerecord/lib/active_record/gem_version.rb +++ b/activerecord/lib/active_record/gem_version.rb @@ -10,7 +10,7 @@ module ActiveRecord MAJOR = 5 MINOR = 2 TINY = 0 - PRE = "beta1" + PRE = "beta2" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end -- cgit v1.2.3 From d2aca50bbd69c7a12cdcbeaba3dbef2679927b90 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Thu, 30 Nov 2017 08:58:32 +0900 Subject: Add :nodoc: to `StatementPool` which is internal used [ci skip] In #30510, `StatementPool` in `AbstractMysqlAdapter` was hidden in the doc. But that class is also had in sqlite3 and postgresql adapters and the base class is :nodoc: class. --- .../lib/active_record/connection_adapters/postgresql_adapter.rb | 3 +-- activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) (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 7b27f6b7a0..27011bfe92 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -166,7 +166,7 @@ module ActiveRecord { concurrently: "CONCURRENTLY" } end - class StatementPool < ConnectionAdapters::StatementPool + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: def initialize(connection, max) super(max) @connection = connection @@ -182,7 +182,6 @@ module ActiveRecord end private - def dealloc(key) @connection.query "DEALLOCATE #{key}" if connection_active? rescue PG::Error diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 670afa3684..daece2bffd 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -90,9 +90,8 @@ module ActiveRecord # Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true class_attribute :represent_boolean_as_integer, default: false - class StatementPool < ConnectionAdapters::StatementPool + class StatementPool < ConnectionAdapters::StatementPool # :nodoc: private - def dealloc(stmt) stmt[:stmt].close unless stmt[:stmt].closed? end -- cgit v1.2.3 From 1dca75c2c8f930b58d86cd2216af5e14307b3e53 Mon Sep 17 00:00:00 2001 From: Greg Navis Date: Sat, 13 May 2017 03:09:58 +0200 Subject: Add support for PostgreSQL operator classes to add_index Add support for specifying non-default operator classes in PostgreSQL indexes. An example CREATE INDEX query that becomes possible is: CREATE INDEX users_name ON users USING gist (name gist_trgm_ops); Previously it was possible to specify the `gist` index but not the custom operator class. The `add_index` call for the above query is: add_index :users, :name, using: :gist, opclasses: {name: :gist_trgm_ops} --- .../abstract/schema_definitions.rb | 4 ++- .../abstract/schema_statements.rb | 28 +++++++++++++++++-- .../postgresql/schema_statements.rb | 32 ++++++++++++++-------- .../connection_adapters/postgresql_adapter.rb | 17 ++++++++++++ activerecord/lib/active_record/schema_dumper.rb | 1 + 5 files changed, 67 insertions(+), 15 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 be2f625d74..b66009bdc6 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -6,7 +6,7 @@ module ActiveRecord # this type are typically created and returned by methods in database # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes class IndexDefinition # :nodoc: - attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment + attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclass, :comment def initialize( table, name, @@ -17,6 +17,7 @@ module ActiveRecord where: nil, type: nil, using: nil, + opclass: {}, comment: nil ) @table = table @@ -28,6 +29,7 @@ module ActiveRecord @where = where @type = type @using = using + @opclass = opclass @comment = comment end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index 9b7345f7c3..2a335e8f2b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -738,6 +738,29 @@ module ActiveRecord # # Note: only supported by PostgreSQL and MySQL # + # ====== Creating an index with a specific operator class + # + # add_index(:developers, :name, using: 'gist', opclass: :gist_trgm_ops) + # + # generates: + # + # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL + # + # add_index(:developers, [:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops }) + # + # generates: + # + # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL + # + # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops }) + # + # generates: + # + # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL + # + # + # Note: only supported by PostgreSQL + # # ====== Creating an index with a specific type # # add_index(:developers, :name, type: :fulltext) @@ -1120,7 +1143,7 @@ module ActiveRecord def add_index_options(table_name, column_name, comment: nil, **options) # :nodoc: column_names = index_column_names(column_name) - options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type) + options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclass) index_type = options[:type].to_s if options.key?(:type) index_type ||= options[:unique] ? "UNIQUE" : "" @@ -1186,7 +1209,8 @@ module ActiveRecord quoted_columns end - # Overridden by the MySQL adapter for supporting index lengths + # Overridden by the MySQL adapter for supporting index lengths and by + # the PostgreSQL adapter for supporting operator classes. def add_options_for_index_columns(quoted_columns, **options) if supports_index_sort_order? quoted_columns = add_index_sort_order(quoted_columns, options) 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 846e721983..15a1dfd102 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -87,10 +87,7 @@ module ActiveRecord result = query(<<-SQL, "SCHEMA") SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid, - pg_catalog.obj_description(i.oid, 'pg_class') AS comment, - (SELECT COUNT(*) FROM pg_opclass o - JOIN (SELECT unnest(string_to_array(d.indclass::text, ' '))::int oid) c - ON o.oid = c.oid WHERE o.opcdefault = 'f') + pg_catalog.obj_description(i.oid, 'pg_class') AS comment FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid INNER JOIN pg_class i ON d.indexrelid = i.oid @@ -109,11 +106,10 @@ module ActiveRecord inddef = row[3] oid = row[4] comment = row[5] - opclass = row[6] using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten - if indkey.include?(0) || opclass > 0 + if indkey.include?(0) columns = expressions else columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact @@ -123,10 +119,21 @@ module ActiveRecord AND a.attnum IN (#{indkey.join(",")}) SQL - # add info on sort order for columns (only desc order is explicitly specified, asc is the default) - orders = Hash[ - expressions.scan(/(\w+) DESC/).flatten.map { |order_column| [order_column, :desc] } - ] + orders = {} + opclasses = {} + + # add info on sort order (only desc order is explicitly specified, asc is the default) + # and non-default opclasses + expressions.scan(/(\w+)(?: (?!DESC)(\w+))?(?: (DESC))?/).each do |column, opclass, desc| + opclasses[column] = opclass if opclass + orders[column] = :desc if desc + end + + # Use a string for the opclass description (instead of a hash) when all columns + # have the same opclass specified. + if columns.count == opclasses.count && opclasses.values.uniq.count == 1 + opclasses = opclasses.values.first + end end IndexDefinition.new( @@ -137,6 +144,7 @@ module ActiveRecord orders: orders, where: where, using: using.to_sym, + opclass: opclasses, comment: comment.presence ) end @@ -458,8 +466,8 @@ module ActiveRecord end def add_index(table_name, column_name, options = {}) #:nodoc: - index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options) - execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do + index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options) + execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}").tap do execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 7b27f6b7a0..eee61780b7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -388,6 +388,23 @@ module ActiveRecord private + def add_index_opclass(column_names, options = {}) + opclass = + if options[:opclass].is_a?(Hash) + options[:opclass].symbolize_keys + else + Hash.new { |hash, column| hash[column] = options[:opclass].to_s } + end + column_names.each do |name, column| + column << " #{opclass[name]}" if opclass[name].present? + end + end + + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_opclass(quoted_columns, options) + super + end + # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html VALUE_LIMIT_VIOLATION = "22001" NUMERIC_VALUE_OUT_OF_RANGE = "22003" diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 66f7d29886..8cb2851557 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -189,6 +189,7 @@ HEADER index_parts << "where: #{index.where.inspect}" if index.where index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index) index_parts << "type: #{index.type.inspect}" if index.type + index_parts << "opclass: #{index.opclass.inspect}" if index.opclass.present? index_parts << "comment: #{index.comment.inspect}" if index.comment index_parts end -- cgit v1.2.3 From 0dc3b6535dfb8127f568bb4abc2fb09118ce4b96 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 1 Dec 2017 11:49:36 +0900 Subject: Maintain raising `RecordNotFound` for class level `update` and` destroy` In 35836019, class level `update` and `destroy` suppressed `RecordNotFound` to ensure returning affected objects. But `RecordNotFound` is a common exception caught by a `rescue_from` handler. So changing the behavior when a typical `params[:id]` is passed has a compatibility problem. The previous behavior should not be changed. Fixes #31301. --- activerecord/lib/active_record/persistence.rb | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index 4e1b05dbf6..17459f2505 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -99,7 +99,11 @@ module ActiveRecord # for updating all records in a single query. def update(id = :all, attributes) if id.is_a?(Array) - id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }.compact + id.map.with_index { |one_id, idx| + object = find_by(primary_key => one_id) + object.update(attributes[idx]) if object + object + }.compact elsif id == :all all.each { |record| record.update(attributes) } else @@ -112,7 +116,6 @@ module ActiveRecord object.update(attributes) object end - rescue RecordNotFound end # Destroy an object (or multiple objects) that has the given id. The object is instantiated first, @@ -136,11 +139,13 @@ module ActiveRecord # Todo.destroy(todos) def destroy(id) if id.is_a?(Array) - id.map { |one_id| destroy(one_id) }.compact + id.map { |one_id| + object = find_by(primary_key => one_id) + object.destroy if object + }.compact else find(id).destroy end - rescue RecordNotFound end # Deletes the row with a primary key matching the +id+ argument, using a -- cgit v1.2.3 From 695ec1fef0b7fdb3125a3e2e4364a7f449265c1b Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 1 Dec 2017 18:21:21 +0900 Subject: Class level `update` and `destroy` checks all the records exist before making changes (#31306) It makes more sense than ignoring invalid IDs. --- activerecord/lib/active_record/persistence.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 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 17459f2505..a13b0d0181 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -99,11 +99,9 @@ module ActiveRecord # for updating all records in a single query. def update(id = :all, attributes) if id.is_a?(Array) - id.map.with_index { |one_id, idx| - object = find_by(primary_key => one_id) - object.update(attributes[idx]) if object - object - }.compact + id.map { |one_id| find(one_id) }.each_with_index { |object, idx| + object.update(attributes[idx]) + } elsif id == :all all.each { |record| record.update(attributes) } else @@ -139,10 +137,7 @@ module ActiveRecord # Todo.destroy(todos) def destroy(id) if id.is_a?(Array) - id.map { |one_id| - object = find_by(primary_key => one_id) - object.destroy if object - }.compact + find(id).each(&:destroy) else find(id).destroy end -- cgit v1.2.3 From 32516e8862946da963e4ba9256553156ca321596 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 1 Dec 2017 20:48:29 +0900 Subject: Remove unpaired `}` [ci skip] --- .../active_record/connection_adapters/abstract/schema_statements.rb | 3 +-- 1 file changed, 1 insertion(+), 2 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 2a335e8f2b..7ae32d0ced 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -752,13 +752,12 @@ module ActiveRecord # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL # - # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops }) + # add_index(:developers, [:name, :city], using: 'gist', opclass: :gist_trgm_ops) # # generates: # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL # - # # Note: only supported by PostgreSQL # # ====== Creating an index with a specific type -- cgit v1.2.3 From 8203482a9ef9bd60d5014745fd7af868d9954b1d Mon Sep 17 00:00:00 2001 From: Travis Hunter Date: Fri, 20 Jan 2017 16:18:46 -0500 Subject: Add support for invalid foreign keys in Postgres Add validate_constraint and update naming --- .../abstract/schema_definitions.rb | 5 +++ .../abstract/schema_statements.rb | 2 + .../connection_adapters/abstract_adapter.rb | 5 +++ .../postgresql/schema_creation.rb | 12 ++++++ .../postgresql/schema_definitions.rb | 13 +++++++ .../postgresql/schema_statements.rb | 44 +++++++++++++++++++++- .../connection_adapters/postgresql_adapter.rb | 4 ++ 7 files changed, 84 insertions(+), 1 deletion(-) (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 b66009bdc6..88ef8e0819 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -87,6 +87,11 @@ module ActiveRecord options[:primary_key] != default_primary_key end + def validate? + options.fetch(:validate, true) + end + alias validated? validate? + def defined_for?(to_table_ord = nil, to_table: nil, **options) if to_table_ord self.to_table == to_table_ord.to_s 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 7ae32d0ced..09ccf0c193 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -964,6 +964,8 @@ module ActiveRecord # Action that happens ON DELETE. Valid values are +:nullify+, +:cascade+ and +:restrict+ # [:on_update] # Action that happens ON UPDATE. Valid values are +:nullify+, +:cascade+ and +:restrict+ + # [:validate] + # (Postgres only) Specify whether or not the constraint should be validated. Defaults to +true+. def add_foreign_key(from_table, to_table, options = {}) return unless supports_foreign_keys? diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 8993c517a6..fc80d332f9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -272,6 +272,11 @@ module ActiveRecord false end + # Does this adapter support creating invalid constraints? + def supports_validate_constraints? + false + end + # Does this adapter support creating foreign key constraints # in the same statement as creating the table? def supports_foreign_keys_in_create? diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb index 59f661da25..8e381a92cf 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_creation.rb @@ -5,6 +5,18 @@ module ActiveRecord module PostgreSQL class SchemaCreation < AbstractAdapter::SchemaCreation # :nodoc: private + def visit_AlterTable(o) + super << o.constraint_validations.map { |fk| visit_ValidateConstraint fk }.join(" ") + end + + def visit_AddForeignKey(o) + super.dup.tap { |sql| sql << " NOT VALID" unless o.validate? } + end + + def visit_ValidateConstraint(name) + "VALIDATE CONSTRAINT #{quote_column_name(name)}" + end + def add_column_options!(sql, options) if options[:collation] sql << " COLLATE \"#{options[:collation]}\"" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index 75622eb304..a3bb66bf3e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -192,6 +192,19 @@ module ActiveRecord class Table < ActiveRecord::ConnectionAdapters::Table include ColumnMethods end + + class AlterTable < ActiveRecord::ConnectionAdapters::AlterTable + attr_reader :constraint_validations + + def initialize(td) + super + @constraint_validations = [] + end + + def validate_constraint(name) + @constraint_validations << name + end + end end end end 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 15a1dfd102..38102b2e7a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -507,7 +507,7 @@ module ActiveRecord def foreign_keys(table_name) scope = quoted_scope(table_name) fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA") - SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete + SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid FROM pg_constraint c JOIN pg_class t1 ON c.conrelid = t1.oid JOIN pg_class t2 ON c.confrelid = t2.oid @@ -529,6 +529,7 @@ module ActiveRecord options[:on_delete] = extract_foreign_key_action(row["on_delete"]) options[:on_update] = extract_foreign_key_action(row["on_update"]) + options[:validate] = row["valid"] ForeignKeyDefinition.new(table_name, row["to_table"], options) end @@ -589,6 +590,43 @@ module ActiveRecord PostgreSQL::SchemaDumper.create(self, options) end + # Validates the given constraint. + # + # Validates the constraint named +constraint_name+ on +accounts+. + # + # validate_foreign_key :accounts, :constraint_name + def validate_constraint(table_name, constraint_name) + return unless supports_validate_constraints? + + at = create_alter_table table_name + at.validate_constraint constraint_name + + execute schema_creation.accept(at) + end + + # Validates the given foreign key. + # + # Validates the foreign key on +accounts.branch_id+. + # + # validate_foreign_key :accounts, :branches + # + # Validates the foreign key on +accounts.owner_id+. + # + # validate_foreign_key :accounts, column: :owner_id + # + # Validates the foreign key named +special_fk_name+ on the +accounts+ table. + # + # validate_foreign_key :accounts, name: :special_fk_name + # + # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key. + def validate_foreign_key(from_table, options_or_to_table = {}) + return unless supports_validate_constraints? + + fk_name_to_validate = foreign_key_for!(from_table, options_or_to_table).name + + validate_constraint from_table, fk_name_to_validate + end + private def schema_creation PostgreSQL::SchemaCreation.new(self) @@ -598,6 +636,10 @@ module ActiveRecord PostgreSQL::TableDefinition.new(*args) end + def create_alter_table(name) + PostgreSQL::AlterTable.new create_table_definition(name) + end + def new_column_from_field(table_name, field) column_name, type, default, notnull, oid, fmod, collation, comment = field type_metadata = fetch_type_metadata(column_name, type, oid.to_i, fmod.to_i) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 9397481c4c..68b79f777e 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -142,6 +142,10 @@ module ActiveRecord true end + def supports_validate_constraints? + true + end + def supports_views? true end -- cgit v1.2.3 From 70c96b4423abacecb1116184c8ea4d4662b809f8 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Sat, 2 Dec 2017 11:18:38 +0900 Subject: Fix method name in `validate_constraint` doc [ci skip] --- .../active_record/connection_adapters/postgresql/schema_statements.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/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 38102b2e7a..af13a9e16f 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -594,7 +594,7 @@ module ActiveRecord # # Validates the constraint named +constraint_name+ on +accounts+. # - # validate_foreign_key :accounts, :constraint_name + # validate_constraint :accounts, :constraint_name def validate_constraint(table_name, constraint_name) return unless supports_validate_constraints? -- cgit v1.2.3 From dd6338a0699f2301d4b2fc8653688b4c4183cee5 Mon Sep 17 00:00:00 2001 From: Dinah Shi Date: Sat, 2 Dec 2017 14:30:10 +0800 Subject: Extract sql fragment generators for alter table from PostgreSQL adapter --- .../abstract/schema_statements.rb | 16 +++- .../connection_adapters/abstract_mysql_adapter.rb | 36 +++------ .../postgresql/schema_statements.rb | 86 +++++++++++++--------- .../lib/active_record/migration/compatibility.rb | 9 +++ 4 files changed, 88 insertions(+), 59 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 9b7345f7c3..f0c6ad63f9 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -600,7 +600,7 @@ module ActiveRecord # to provide these in a migration's +change+ method so it can be reverted. # In that case, +type+ and +options+ will be used by #add_column. def remove_column(table_name, column_name, type = nil, options = {}) - execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}" + execute "ALTER TABLE #{quote_table_name(table_name)} #{remove_column_for_alter(table_name, column_name, type, options)}" end # Changes the column's definition according to the new options. @@ -1340,6 +1340,20 @@ module ActiveRecord options.is_a?(Hash) && options.key?(:name) && options.except(:name, :algorithm).empty? end + def add_column_for_alter(table_name, column_name, type, options = {}) + td = create_table_definition(table_name) + cd = td.new_column_definition(column_name, type, options) + schema_creation.accept(AddColumnDefinition.new(cd)) + end + + def remove_column_for_alter(table_name, column_name, type = nil, options = {}) + "DROP COLUMN #{quote_column_name(column_name)}" + end + + def remove_columns_for_alter(table_name, *column_names) + column_names.map { |column_name| remove_column_for_alter(table_name, column_name) } + end + def insert_versions_sql(versions) sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) 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 0e552dba95..2926366e10 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -299,7 +299,7 @@ module ActiveRecord def bulk_change_table(table_name, operations) #:nodoc: sqls = operations.flat_map do |command, args| table, arguments = args.shift, args - method = :"#{command}_sql" + method = :"#{command}_for_alter" if respond_to?(method, true) send(method, table, *arguments) @@ -372,11 +372,11 @@ module ActiveRecord end def change_column(table_name, column_name, type, options = {}) #:nodoc: - execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_sql(table_name, column_name, type, options)}") + execute("ALTER TABLE #{quote_table_name(table_name)} #{change_column_for_alter(table_name, column_name, type, options)}") end def rename_column(table_name, column_name, new_column_name) #:nodoc: - execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_sql(table_name, column_name, new_column_name)}") + execute("ALTER TABLE #{quote_table_name(table_name)} #{rename_column_for_alter(table_name, column_name, new_column_name)}") rename_column_indexes(table_name, column_name, new_column_name) end @@ -668,13 +668,7 @@ module ActiveRecord end end - def add_column_sql(table_name, column_name, type, options = {}) - td = create_table_definition(table_name) - cd = td.new_column_definition(column_name, type, options) - schema_creation.accept(AddColumnDefinition.new(cd)) - end - - def change_column_sql(table_name, column_name, type, options = {}) + def change_column_for_alter(table_name, column_name, type, options = {}) column = column_for(table_name, column_name) type ||= column.sql_type @@ -695,7 +689,7 @@ module ActiveRecord schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end - def rename_column_sql(table_name, column_name, new_column_name) + def rename_column_for_alter(table_name, column_name, new_column_name) column = column_for(table_name, column_name) options = { default: column.default, @@ -709,31 +703,23 @@ module ActiveRecord schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) end - def remove_column_sql(table_name, column_name, type = nil, options = {}) - "DROP #{quote_column_name(column_name)}" - end - - def remove_columns_sql(table_name, *column_names) - column_names.map { |column_name| remove_column_sql(table_name, column_name) } - end - - def add_index_sql(table_name, column_name, options = {}) + def add_index_for_alter(table_name, column_name, options = {}) index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options) index_algorithm[0, 0] = ", " if index_algorithm.present? "ADD #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_algorithm}" end - def remove_index_sql(table_name, options = {}) + def remove_index_for_alter(table_name, options = {}) index_name = index_name_for_remove(table_name, options) "DROP INDEX #{quote_column_name(index_name)}" end - def add_timestamps_sql(table_name, options = {}) - [add_column_sql(table_name, :created_at, :datetime, options), add_column_sql(table_name, :updated_at, :datetime, options)] + def add_timestamps_for_alter(table_name, options = {}) + [add_column_for_alter(table_name, :created_at, :datetime, options), add_column_for_alter(table_name, :updated_at, :datetime, options)] end - def remove_timestamps_sql(table_name, options = {}) - [remove_column_sql(table_name, :updated_at), remove_column_sql(table_name, :created_at)] + def remove_timestamps_for_alter(table_name, options = {}) + [remove_column_for_alter(table_name, :updated_at), remove_column_for_alter(table_name, :created_at)] end # MySQL is too stupid to create a temporary table for use subquery, so we have 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 846e721983..371597031c 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -391,51 +391,24 @@ module ActiveRecord end def change_column(table_name, column_name, type, options = {}) #:nodoc: - clear_cache! quoted_table_name = quote_table_name(table_name) - quoted_column_name = quote_column_name(column_name) - sql_type = type_to_sql(type, options) - sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup - if options[:collation] - sql << " COLLATE \"#{options[:collation]}\"" - end - if options[:using] - sql << " USING #{options[:using]}" - elsif options[:cast_as] - cast_as_type = type_to_sql(options[:cast_as], options) - sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})" - end - execute sql - - change_column_default(table_name, column_name, options[:default]) if options.key?(:default) - change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) - change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) + sqls, procs = change_column_for_alter(table_name, column_name, type, options) + execute "ALTER TABLE #{quoted_table_name} #{sqls.join(", ")}" + procs.each(&:call) end # Changes the default value of a table column. def change_column_default(table_name, column_name, default_or_changes) # :nodoc: - clear_cache! - column = column_for(table_name, column_name) - return unless column - - default = extract_new_default_value(default_or_changes) - alter_column_query = "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} %s" - if default.nil? - # DEFAULT NULL results in the same behavior as DROP DEFAULT. However, PostgreSQL will - # cast the default to the columns type, which leaves us with a default like "default NULL::character varying". - execute alter_column_query % "DROP DEFAULT" - else - execute alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}" - end + execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_default_for_alter(table_name, column_name, default_or_changes)}" end def change_column_null(table_name, column_name, null, default = nil) #:nodoc: clear_cache! unless null || default.nil? column = column_for(table_name, column_name) - execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column + execute "UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_expression(default, column)} WHERE #{quote_column_name(column_name)} IS NULL" if column end - execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL") + execute "ALTER TABLE #{quote_table_name(table_name)} #{change_column_null_for_alter(table_name, column_name, null, default)}" end # Adds comment for given table column or drops it if +comment+ is a +nil+ @@ -629,6 +602,53 @@ module ActiveRecord end end + def change_column_for_alter(table_name, column_name, type, options = {}) + clear_cache! + quoted_column_name = quote_column_name(column_name) + sql_type = type_to_sql(type, options) + sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup + if options[:collation] + sql << " COLLATE \"#{options[:collation]}\"" + end + if options[:using] + sql << " USING #{options[:using]}" + elsif options[:cast_as] + cast_as_type = type_to_sql(options[:cast_as], options) + sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})" + end + + sqls = [sql] + procs = [] + sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default) + sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null) + procs << Proc.new { change_column_comment(table_name, column_name, options[:comment]) } if options.key?(:comment) + + [sqls, procs] + end + + + # Changes the default value of a table column. + def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc: + clear_cache! + column = column_for(table_name, column_name) + return unless column + + default = extract_new_default_value(default_or_changes) + alter_column_query = "ALTER COLUMN #{quote_column_name(column_name)} %s" + if default.nil? + # DEFAULT NULL results in the same behavior as DROP DEFAULT. However, PostgreSQL will + # cast the default to the columns type, which leaves us with a default like "default NULL::character varying". + alter_column_query % "DROP DEFAULT" + else + alter_column_query % "SET DEFAULT #{quote_default_expression(default, column)}" + end + end + + def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc: + clear_cache! + "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" + end + def data_source_sql(name = nil, type: nil) scope = quoted_scope(name, type: type) scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index c979aaf0a0..da307c1723 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -16,6 +16,15 @@ module ActiveRecord V5_2 = Current class V5_1 < V5_2 + if adapter_name == "PostgreSQL" + def change_column(table_name, column_name, type, options = {}) + unless options[:null] || options[:default].nil? + column = connection.send(:column_for, table_name, column_name) + connection.execute("UPDATE #{connection.quote_table_name(table_name)} SET #{connection.quote_column_name(column_name)}=#{connection.quote_default_expression(options[:default], column)} WHERE #{connection.quote_column_name(column_name)} IS NULL") if column + end + connection.change_column(table_name, column_name, type, options) + end + end end class V5_0 < V5_1 -- cgit v1.2.3 From 01fb0907b2dafcecf63eedb80408d4cbff5f4d23 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 3 Dec 2017 04:57:04 +0900 Subject: Refactor `length`, `order`, and `opclass` index options dumping --- .../abstract/schema_definitions.rb | 19 ++++++++++++++----- .../connection_adapters/abstract/schema_statements.rb | 2 +- .../connection_adapters/abstract_mysql_adapter.rb | 2 +- .../connection_adapters/mysql/schema_statements.rb | 15 +++++++++------ .../postgresql/schema_statements.rb | 16 +++++----------- activerecord/lib/active_record/schema_dumper.rb | 14 +++++++++++--- 6 files changed, 41 insertions(+), 27 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 88ef8e0819..86406d95ad 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -6,7 +6,7 @@ module ActiveRecord # this type are typically created and returned by methods in database # adapters. e.g. ActiveRecord::ConnectionAdapters::MySQL::SchemaStatements#indexes class IndexDefinition # :nodoc: - attr_reader :table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :opclass, :comment + attr_reader :table, :name, :unique, :columns, :lengths, :orders, :opclasses, :where, :type, :using, :comment def initialize( table, name, @@ -14,24 +14,33 @@ module ActiveRecord columns = [], lengths: {}, orders: {}, + opclasses: {}, where: nil, type: nil, using: nil, - opclass: {}, comment: nil ) @table = table @name = name @unique = unique @columns = columns - @lengths = lengths - @orders = orders + @lengths = concise_options(lengths) + @orders = concise_options(orders) + @opclasses = concise_options(opclasses) @where = where @type = type @using = using - @opclass = opclass @comment = comment end + + private + def concise_options(options) + if columns.size == options.size && options.values.uniq.size == 1 + options.values.first + else + options + end + end end # Abstract representation of a column definition. Instances of this type 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 fac9e1cca2..efc934c34c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1202,7 +1202,7 @@ module ActiveRecord when Hash order = order.symbolize_keys quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? } - when String + else quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? } end end 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 143dc6d08d..41c628302d 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -611,7 +611,7 @@ module ActiveRecord when Hash length = length.symbolize_keys quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? } - when Integer + else quoted_columns.each { |name, column| column << "(#{length})" } end end diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb index a15c7d1787..d5b2c18ee6 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -22,23 +22,26 @@ module ActiveRecord index_using = mysql_index_type end - indexes << IndexDefinition.new( + indexes << [ row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, + [], + lengths: {}, + orders: {}, type: index_type, using: index_using, comment: row[:Index_comment].presence - ) + ] end - indexes.last.columns << row[:Column_name] - indexes.last.lengths.merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part] - indexes.last.orders.merge!(row[:Column_name] => :desc) if row[:Collation] == "D" + indexes.last[-2] << row[:Column_name] + indexes.last[-1][:lengths].merge!(row[:Column_name] => row[:Sub_part].to_i) if row[:Sub_part] + indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D" end end - indexes + indexes.map { |index| IndexDefinition.new(*index) } end def remove_column(table_name, column_name, type = nil, options = {}) 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 64f08e0ee1..7b4d5711cb 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -109,6 +109,9 @@ module ActiveRecord using, expressions, where = inddef.scan(/ USING (\w+?) \((.+?)\)(?: WHERE (.+))?\z/).flatten + orders = {} + opclasses = {} + if indkey.include?(0) columns = expressions else @@ -119,21 +122,12 @@ module ActiveRecord AND a.attnum IN (#{indkey.join(",")}) SQL - orders = {} - opclasses = {} - # add info on sort order (only desc order is explicitly specified, asc is the default) # and non-default opclasses expressions.scan(/(\w+)(?: (?!DESC)(\w+))?(?: (DESC))?/).each do |column, opclass, desc| - opclasses[column] = opclass if opclass + opclasses[column] = opclass.to_sym if opclass orders[column] = :desc if desc end - - # Use a string for the opclass description (instead of a hash) when all columns - # have the same opclass specified. - if columns.count == opclasses.count && opclasses.values.uniq.count == 1 - opclasses = opclasses.values.first - end end IndexDefinition.new( @@ -142,9 +136,9 @@ module ActiveRecord unique, columns, orders: orders, + opclasses: opclasses, where: where, using: using.to_sym, - opclass: opclasses, comment: comment.presence ) end diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 8cb2851557..16ccba6b6c 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -184,12 +184,12 @@ HEADER "name: #{index.name.inspect}", ] index_parts << "unique: true" if index.unique - index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present? - index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present? + index_parts << "length: #{format_index_parts(index.lengths)}" if index.lengths.present? + index_parts << "order: #{format_index_parts(index.orders)}" if index.orders.present? + index_parts << "opclass: #{format_index_parts(index.opclasses)}" if index.opclasses.present? index_parts << "where: #{index.where.inspect}" if index.where index_parts << "using: #{index.using.inspect}" if !@connection.default_index_type?(index) index_parts << "type: #{index.type.inspect}" if index.type - index_parts << "opclass: #{index.opclass.inspect}" if index.opclass.present? index_parts << "comment: #{index.comment.inspect}" if index.comment index_parts end @@ -232,6 +232,14 @@ HEADER options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ") end + def format_index_parts(options) + if options.is_a?(Hash) + "{ #{format_options(options)} }" + else + options.inspect + end + end + def remove_prefix_and_suffix(table) prefix = Regexp.escape(@options[:table_name_prefix].to_s) suffix = Regexp.escape(@options[:table_name_suffix].to_s) -- cgit v1.2.3 From 33a3f7123bc4cc49b99b01a40bbfd463b2e73f76 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 3 Dec 2017 07:08:30 +0900 Subject: Extract duplicated index column options normalization as `options_for_index_columns` And placed `add_options_for_index_columns` in `schema_statements.rb` consistently to ease to find related code. --- .../connection_adapters/abstract/schema_statements.rb | 19 ++++++++++--------- .../connection_adapters/abstract_mysql_adapter.rb | 19 ------------------- .../connection_adapters/mysql/schema_statements.rb | 12 ++++++++++++ .../postgresql/schema_statements.rb | 12 ++++++++++++ .../connection_adapters/postgresql_adapter.rb | 18 ------------------ 5 files changed, 34 insertions(+), 46 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 efc934c34c..4f58b0242c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1197,17 +1197,18 @@ module ActiveRecord end def add_index_sort_order(quoted_columns, **options) - if order = options[:order] - case order - when Hash - order = order.symbolize_keys - quoted_columns.each { |name, column| column << " #{order[name].upcase}" if order[name].present? } - else - quoted_columns.each { |name, column| column << " #{order.upcase}" if order.present? } - end + orders = options_for_index_columns(options[:order]) + quoted_columns.each do |name, column| + column << " #{orders[name].upcase}" if orders[name].present? end + end - quoted_columns + def options_for_index_columns(options) + if options.is_a?(Hash) + options.symbolize_keys + else + Hash.new { |hash, column| hash[column] = options } + end end # Overridden by the MySQL adapter for supporting index lengths and by 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 41c628302d..479131caad 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -605,25 +605,6 @@ module ActiveRecord end end - def add_index_length(quoted_columns, **options) - if length = options[:length] - case length - when Hash - length = length.symbolize_keys - quoted_columns.each { |name, column| column << "(#{length[name]})" if length[name].present? } - else - quoted_columns.each { |name, column| column << "(#{length})" } - end - end - - quoted_columns - end - - def add_options_for_index_columns(quoted_columns, **options) - quoted_columns = add_index_length(quoted_columns, options) - super - end - # See https://dev.mysql.com/doc/refman/5.7/en/error-messages-server.html ER_DUP_ENTRY = 1062 ER_NOT_NULL_VIOLATION = 1048 diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb index d5b2c18ee6..ce50590651 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -106,6 +106,18 @@ module ActiveRecord super unless specifier == "RESTRICT" end + def add_index_length(quoted_columns, **options) + lengths = options_for_index_columns(options[:length]) + quoted_columns.each do |name, column| + column << "(#{lengths[name]})" if lengths[name].present? + end + end + + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_length(quoted_columns, options) + super + end + def data_source_sql(name = nil, type: nil) scope = quoted_scope(name, type: type) 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 7b4d5711cb..2e9e74403b 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -693,6 +693,18 @@ module ActiveRecord "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" end + def add_index_opclass(quoted_columns, **options) + opclasses = options_for_index_columns(options[:opclass]) + quoted_columns.each do |name, column| + column << " #{opclasses[name]}" if opclasses[name].present? + end + end + + def add_options_for_index_columns(quoted_columns, **options) + quoted_columns = add_index_opclass(quoted_columns, options) + super + end + def data_source_sql(name = nil, type: nil) scope = quoted_scope(name, type: type) scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 68b79f777e..23fc69d649 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -390,24 +390,6 @@ module ActiveRecord end private - - def add_index_opclass(column_names, options = {}) - opclass = - if options[:opclass].is_a?(Hash) - options[:opclass].symbolize_keys - else - Hash.new { |hash, column| hash[column] = options[:opclass].to_s } - end - column_names.each do |name, column| - column << " #{opclass[name]}" if opclass[name].present? - end - end - - def add_options_for_index_columns(quoted_columns, **options) - quoted_columns = add_index_opclass(quoted_columns, options) - super - end - # See https://www.postgresql.org/docs/current/static/errcodes-appendix.html VALUE_LIMIT_VIOLATION = "22001" NUMERIC_VALUE_OUT_OF_RANGE = "22003" -- cgit v1.2.3 From dbee80bca0ef504120219e6c7686437456511060 Mon Sep 17 00:00:00 2001 From: "yuuji.yaginuma" Date: Fri, 1 Dec 2017 18:16:06 +0900 Subject: Make `Migrator.current_version` work without a current database This is necessary in order to make the processing dependent on `Migrator.current_version` work even without database. Context: https://github.com/rails/rails/pull/31135#issuecomment-348404326 --- activerecord/lib/active_record/migration.rb | 10 +++++++++- activerecord/lib/active_record/railtie.rb | 7 +++++-- 2 files changed, 14 insertions(+), 3 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 15e9c09ffb..4d4b0dc67a 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1053,7 +1053,15 @@ module ActiveRecord end end - def current_version(connection = Base.connection) + def current_version(connection = nil) + if connection.nil? + begin + connection = Base.connection + rescue ActiveRecord::NoDatabaseError + return nil + end + end + get_all_versions(connection).max || 0 end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 9ee8425e1b..4538ed6a5f 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -90,12 +90,15 @@ module ActiveRecord filename = File.join(app.config.paths["db"].first, "schema_cache.yml") if File.file?(filename) + current_version = ActiveRecord::Migrator.current_version + next if current_version.nil? + cache = YAML.load(File.read(filename)) - if cache.version == ActiveRecord::Migrator.current_version + if cache.version == current_version connection.schema_cache = cache connection_pool.schema_cache = cache.dup else - warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{ActiveRecord::Migrator.current_version}, but the one in the cache is #{cache.version}." + warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}." end end end -- cgit v1.2.3 From d0f5dce492696019ddf409892829f89bee5f45ef Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 3 Dec 2017 15:00:05 +0900 Subject: `change_column_default` should be executed after type changing If do not execute a type changing first, filling in default value may be failed. ``` % ARCONN=postgresql be ruby -w -Itest test/cases/migration/compatibility_test.rb -n test_legacy_change_column_with_null_executes_update Using postgresql Run options: -n test_legacy_change_column_with_null_executes_update --seed 20459 E Error: ActiveRecord::Migration::CompatibilityTest#test_legacy_change_column_with_null_executes_update: StandardError: An error has occurred, this and all later migrations canceled: PG::StringDataRightTruncation: ERROR: value too long for type character varying(5) : UPDATE "testings" SET "foo"='foobar' WHERE "foo" IS NULL ``` --- .../connection_adapters/postgresql/schema_statements.rb | 15 ++++++++------- .../lib/active_record/migration/compatibility.rb | 17 ++++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) (limited to 'activerecord/lib/active_record') 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 2e9e74403b..bf5fbb30e1 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -393,9 +393,9 @@ module ActiveRecord end def change_column(table_name, column_name, type, options = {}) #:nodoc: - quoted_table_name = quote_table_name(table_name) + clear_cache! sqls, procs = change_column_for_alter(table_name, column_name, type, options) - execute "ALTER TABLE #{quoted_table_name} #{sqls.join(", ")}" + execute "ALTER TABLE #{quote_table_name(table_name)} #{sqls.join(", ")}" procs.each(&:call) end @@ -646,8 +646,7 @@ module ActiveRecord end end - def change_column_for_alter(table_name, column_name, type, options = {}) - clear_cache! + def change_column_sql(table_name, column_name, type, options = {}) quoted_column_name = quote_column_name(column_name) sql_type = type_to_sql(type, options) sql = "ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup @@ -661,7 +660,11 @@ module ActiveRecord sql << " USING CAST(#{quoted_column_name} AS #{cast_as_type})" end - sqls = [sql] + sql + end + + def change_column_for_alter(table_name, column_name, type, options = {}) + sqls = [change_column_sql(table_name, column_name, type, options)] procs = [] sqls << change_column_default_for_alter(table_name, column_name, options[:default]) if options.key?(:default) sqls << change_column_null_for_alter(table_name, column_name, options[:null], options[:default]) if options.key?(:null) @@ -673,7 +676,6 @@ module ActiveRecord # Changes the default value of a table column. def change_column_default_for_alter(table_name, column_name, default_or_changes) # :nodoc: - clear_cache! column = column_for(table_name, column_name) return unless column @@ -689,7 +691,6 @@ module ActiveRecord end def change_column_null_for_alter(table_name, column_name, null, default = nil) #:nodoc: - clear_cache! "ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL" end diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb index da307c1723..bd8c054c28 100644 --- a/activerecord/lib/active_record/migration/compatibility.rb +++ b/activerecord/lib/active_record/migration/compatibility.rb @@ -16,13 +16,16 @@ module ActiveRecord V5_2 = Current class V5_1 < V5_2 - if adapter_name == "PostgreSQL" - def change_column(table_name, column_name, type, options = {}) - unless options[:null] || options[:default].nil? - column = connection.send(:column_for, table_name, column_name) - connection.execute("UPDATE #{connection.quote_table_name(table_name)} SET #{connection.quote_column_name(column_name)}=#{connection.quote_default_expression(options[:default], column)} WHERE #{connection.quote_column_name(column_name)} IS NULL") if column - end - connection.change_column(table_name, column_name, type, options) + def change_column(table_name, column_name, type, options = {}) + if adapter_name == "PostgreSQL" + clear_cache! + sql = connection.send(:change_column_sql, table_name, column_name, type, options) + execute "ALTER TABLE #{quote_table_name(table_name)} #{sql}" + change_column_default(table_name, column_name, options[:default]) if options.key?(:default) + change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null) + change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment) + else + super end end end -- cgit v1.2.3 From 70fa9e9ab7fd89589664ecd7ee367448ef45f9d8 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 3 Dec 2017 15:28:10 +0900 Subject: Emulate JSON types for SQLite3 adapter (#29664) Actually SQLite3 doesn't have JSON storage class (so it is stored as a TEXT like Date and Time). But emulating JSON types is convinient for making database agnostic migrations. --- .../connection_adapters/abstract/schema_definitions.rb | 1 + .../active_record/connection_adapters/mysql/schema_definitions.rb | 4 ---- .../connection_adapters/postgresql/schema_definitions.rb | 4 ---- .../lib/active_record/connection_adapters/sqlite3_adapter.rb | 7 ++++++- 4 files changed, 7 insertions(+), 9 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 86406d95ad..0594b4b485 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -220,6 +220,7 @@ module ActiveRecord :decimal, :float, :integer, + :json, :string, :text, :time, diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb index da25e4863c..2ed4ad16ae 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb @@ -32,10 +32,6 @@ module ActiveRecord args.each { |name| column(name, :longtext, options) } end - def json(*args, **options) - args.each { |name| column(name, :json, options) } - end - def unsigned_integer(*args, **options) args.each { |name| column(name, :unsigned_integer, options) } end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb index a3bb66bf3e..6047217fcd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_definitions.rb @@ -95,10 +95,6 @@ module ActiveRecord args.each { |name| column(name, :int8range, options) } end - def json(*args, **options) - args.each { |name| column(name, :json, options) } - end - def jsonb(*args, **options) args.each { |name| column(name, :jsonb, options) } end diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index daece2bffd..ff63f63bf7 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -70,7 +70,8 @@ module ActiveRecord time: { name: "time" }, date: { name: "date" }, binary: { name: "blob" }, - boolean: { name: "boolean" } + boolean: { name: "boolean" }, + json: { name: "json" }, } ## @@ -134,6 +135,10 @@ module ActiveRecord true end + def supports_json? + true + end + def supports_multi_insert? sqlite_version >= "3.7.11" end -- cgit v1.2.3 From 9b823c02b62a91216ef1ad0c9d4fca095377afb6 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Sun, 3 Dec 2017 15:45:40 +0900 Subject: SQLite3 valid integer value should be 8 bytes (64-bit signed integer) (#28379) This is a regression since Rails 4.2. SQLite3 integer is stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value. Assuming default valid value as 4 bytes caused that actual valid value in INTEGER storage class cannot be stored and existing value cannot be found. https://www.sqlite.org/datatype3.html We should allow valid value in INTEGER storage class in SQLite3 to fix the regression. Fixes #22594. --- .../active_record/connection_adapters/sqlite3_adapter.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (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 ff63f63bf7..574a7b4572 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -374,6 +374,10 @@ module ActiveRecord end private + def initialize_type_map(m = type_map) + super + register_class_with_limit m, %r(int)i, SQLite3Integer + end def table_structure(table_name) structure = exec_query("PRAGMA table_info(#{quote_table_name(table_name)})", "SCHEMA") @@ -529,6 +533,17 @@ module ActiveRecord def configure_connection execute("PRAGMA foreign_keys = ON", "SCHEMA") end + + class SQLite3Integer < Type::Integer # :nodoc: + private + def _limit + # INTEGER storage class can be stored 8 bytes value. + # See https://www.sqlite.org/datatype3.html#storage_classes_and_datatypes + limit || 8 + end + end + + ActiveRecord::Type.register(:integer, SQLite3Integer, adapter: :sqlite3) end ActiveSupport.run_load_hooks(:active_record_sqlite3adapter, SQLite3Adapter) end -- cgit v1.2.3 From 07788c7ad8bad797ec97cba038e37e007f343afa Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Mon, 4 Dec 2017 08:18:31 +0900 Subject: `current_version` should catch `NoDatabaseError` from `get_all_versions` `get_all_versions` doesn't use passed `connection`. So it should be caught `NoDatabaseError` from `SchemaMigration.table_exists?`. --- activerecord/lib/active_record/migration.rb | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) (limited to 'activerecord/lib/active_record') diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 4d4b0dc67a..5c10d4ff24 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -1045,7 +1045,7 @@ module ActiveRecord new(:up, migrations(migrations_paths), nil) end - def get_all_versions(connection = Base.connection) + def get_all_versions if SchemaMigration.table_exists? SchemaMigration.all_versions.map(&:to_i) else @@ -1054,19 +1054,12 @@ module ActiveRecord end def current_version(connection = nil) - if connection.nil? - begin - connection = Base.connection - rescue ActiveRecord::NoDatabaseError - return nil - end - end - - get_all_versions(connection).max || 0 + get_all_versions.max || 0 + rescue ActiveRecord::NoDatabaseError end - def needs_migration?(connection = Base.connection) - (migrations(migrations_paths).collect(&:version) - get_all_versions(connection)).size > 0 + def needs_migration?(connection = nil) + (migrations(migrations_paths).collect(&:version) - get_all_versions).size > 0 end def any_migrations? -- cgit v1.2.3 From 5ac4f4d2563e7f9c5ffaecce4be4b9e2c5b0c081 Mon Sep 17 00:00:00 2001 From: Ashley Ellis Pierce Date: Mon, 4 Dec 2017 16:44:33 -0500 Subject: Fix sqlite migrations with custom primary keys Previously, if a record was created with a custom primary key, that table could not be migrated using sqlite. While attempting to copy the table, the type of the primary key was ignored. Once that was corrected, copying the indexes would fail because custom primary keys are autoindexed by sqlite by default. To correct that, this skips copying the index if the index name begins with "sqlite_". This is a reserved word that indicates that the index is an internal schema object. SQLite prohibits applications from creating objects whose names begin with "sqlite_", so this string should be safe to use as a check. ref https://www.sqlite.org/fileformat2.html#intschema --- .../lib/active_record/connection_adapters/sqlite3_adapter.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (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 daece2bffd..3197b522b3 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -395,10 +395,11 @@ module ActiveRecord def copy_table(from, to, options = {}) from_primary_key = primary_key(from) + from_primary_key_column = columns(from).select { |column| column.name == from_primary_key }.first options[:id] = false create_table(to, options) do |definition| @definition = definition - @definition.primary_key(from_primary_key) if from_primary_key.present? + @definition.primary_key(from_primary_key, from_primary_key_column.type) if from_primary_key.present? columns(from).each do |column| column_name = options[:rename] ? (options[:rename][column.name] || @@ -422,6 +423,9 @@ module ActiveRecord def copy_table_indexes(from, to, rename = {}) indexes(from).each do |index| name = index.name + # indexes sqlite creates for internal use start with `sqlite_` and + # don't need to be copied + next if name.starts_with?("sqlite_") if to == "a#{from}" name = "t#{name}" elsif from == "a#{to}" -- cgit v1.2.3 From 6a8ce7416d6615a13ee5c4b9f6bcd91cc5adef4d Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 8 Dec 2017 02:37:02 +0900 Subject: Fix `scope_for_create` to do not lose polymorphic associations This regression was caused at 213796fb due to polymorphic predicates are combined by `Arel::Nodes::And`. But I'd like to keep that combined because it would help inverting polymorphic predicates correctly (e9ba12f7), and we can collect equality nodes regardless of combined by `Arel::Nodes::And` (`a AND (b AND c) AND d` == `a AND b AND c AND d`). This change fixes the regression to collect equality nodes in `Arel::Nodes::And` as well. Fixes #31338. --- .../lib/active_record/relation/where_clause.rb | 18 ++++++++++++++++-- 1 file changed, 16 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 752bb38481..a502713e56 100644 --- a/activerecord/lib/active_record/relation/where_clause.rb +++ b/activerecord/lib/active_record/relation/where_clause.rb @@ -47,7 +47,7 @@ module ActiveRecord end def to_h(table_name = nil) - equalities = predicates.grep(Arel::Nodes::Equality) + equalities = equalities(predicates) if table_name equalities = equalities.select do |node| node.left.relation.name == table_name @@ -90,6 +90,20 @@ module ActiveRecord end private + def equalities(predicates) + equalities = [] + + predicates.each do |node| + case node + when Arel::Nodes::Equality + equalities << node + when Arel::Nodes::And + equalities.concat equalities(node.children) + end + end + + equalities + end def predicates_unreferenced_by(other) predicates.reject do |n| @@ -121,7 +135,7 @@ module ActiveRecord end def except_predicates(columns) - self.predicates.reject do |node| + predicates.reject do |node| case node when Arel::Nodes::Between, Arel::Nodes::In, Arel::Nodes::NotIn, Arel::Nodes::Equality, Arel::Nodes::NotEqual, Arel::Nodes::LessThan, Arel::Nodes::LessThanOrEqual, Arel::Nodes::GreaterThan, Arel::Nodes::GreaterThanOrEqual subrelation = (node.left.kind_of?(Arel::Attributes::Attribute) ? node.left : node.right) -- cgit v1.2.3 From 131cc6eab64eeae6c7f508dca4176183144cf3a6 Mon Sep 17 00:00:00 2001 From: Ryuta Kamizono Date: Fri, 8 Dec 2017 10:49:25 +0900 Subject: SQLite: Fix `copy_table` with composite primary keys `connection.primary_key` also return composite primary keys, so `from_primary_key_column` may not be found even if `from_primary_key` is presented. ``` % ARCONN=sqlite3 be ruby -w -Itest test/cases/adapters/sqlite3/sqlite3_adapter_test.rb -n test_copy_table_with_composite_primary_keys Using sqlite3 Run options: -n test_copy_table_with_composite_primary_keys --seed 19041 # Running: E Error: ActiveRecord::ConnectionAdapters::SQLite3AdapterTest#test_copy_table_with_composite_primary_keys: NoMethodError: undefined method `type' for nil:NilClass /path/to/rails/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb:411:in `block in copy_table' ``` This change fixes `copy_table` to do not lose composite primary keys. --- .../lib/active_record/connection_adapters/sqlite3_adapter.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 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 65aba20052..c72db15ce3 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -404,22 +404,24 @@ module ActiveRecord def copy_table(from, to, options = {}) from_primary_key = primary_key(from) - from_primary_key_column = columns(from).select { |column| column.name == from_primary_key }.first options[:id] = false create_table(to, options) do |definition| @definition = definition - @definition.primary_key(from_primary_key, from_primary_key_column.type) if from_primary_key.present? + if from_primary_key.is_a?(Array) + @definition.primary_keys from_primary_key + end columns(from).each do |column| column_name = options[:rename] ? (options[:rename][column.name] || options[:rename][column.name.to_sym] || column.name) : column.name - next if column_name == from_primary_key @definition.column(column_name, column.type, limit: column.limit, default: column.default, precision: column.precision, scale: column.scale, - null: column.null, collation: column.collation) + null: column.null, collation: column.collation, + primary_key: column_name == from_primary_key + ) end yield @definition if block_given? end -- cgit v1.2.3