aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb25
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb37
-rw-r--r--activerecord/lib/active_record/timestamp.rb20
6 files changed, 65 insertions, 23 deletions
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 1ea0aeac3a..5c2ac5b374 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -98,26 +98,30 @@ module ActiveRecord
private
# Loads all the given data into +records+ for the +association+.
- def preloaders_on(association, records, scope)
+ def preloaders_on(association, records, scope, polymorphic_parent = false)
case association
when Hash
- preloaders_for_hash(association, records, scope)
+ preloaders_for_hash(association, records, scope, polymorphic_parent)
when Symbol
- preloaders_for_one(association, records, scope)
+ preloaders_for_one(association, records, scope, polymorphic_parent)
when String
- preloaders_for_one(association.to_sym, records, scope)
+ preloaders_for_one(association.to_sym, records, scope, polymorphic_parent)
else
raise ArgumentError, "#{association.inspect} was not recognized for preload"
end
end
- def preloaders_for_hash(association, records, scope)
+ def preloaders_for_hash(association, records, scope, polymorphic_parent)
association.flat_map { |parent, child|
- loaders = preloaders_for_one parent, records, scope
+ loaders = preloaders_for_one parent, records, scope, polymorphic_parent
recs = loaders.flat_map(&:preloaded_records).uniq
+
+ reflection = records.first.class._reflect_on_association(parent)
+ polymorphic_parent = reflection && reflection.options[:polymorphic]
+
loaders.concat Array.wrap(child).flat_map { |assoc|
- preloaders_on assoc, recs, scope
+ preloaders_on assoc, recs, scope, polymorphic_parent
}
loaders
}
@@ -135,8 +139,8 @@ module ActiveRecord
# Additionally, polymorphic belongs_to associations can have multiple associated
# classes, depending on the polymorphic_type field. So we group by the classes as
# well.
- def preloaders_for_one(association, records, scope)
- grouped_records(association, records).flat_map do |reflection, klasses|
+ def preloaders_for_one(association, records, scope, polymorphic_parent)
+ grouped_records(association, records, polymorphic_parent).flat_map do |reflection, klasses|
klasses.map do |rhs_klass, rs|
loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
loader.run self
@@ -145,10 +149,11 @@ module ActiveRecord
end
end
- def grouped_records(association, records)
+ def grouped_records(association, records, polymorphic_parent)
h = {}
records.each do |record|
next unless record
+ next if polymorphic_parent && !record.class._reflect_on_association(association)
assoc = record.association(association)
next unless assoc.klass
klasses = h[assoc.reflection] ||= {}
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 08f3e15a4b..41553cfa83 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -385,7 +385,7 @@ module ActiveRecord
end
end
- def empty_insert_statement_value
+ def empty_insert_statement_value(primary_key = nil)
"DEFAULT VALUES"
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 fbedddd7f9..477b09944f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -223,7 +223,7 @@ module ActiveRecord
end
end
- def empty_insert_statement_value
+ def empty_insert_statement_value(primary_key = nil)
"VALUES ()"
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index 7721e6b691..c2393c1fc8 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -178,7 +178,7 @@ module ActiveRecord
end
if values.empty?
- im = arel_table.compile_insert(connection.empty_insert_statement_value)
+ im = arel_table.compile_insert(connection.empty_insert_statement_value(primary_key))
im.into arel_table
else
im = arel_table.compile_insert(_substitute_values(values))
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 24b9bab0cd..c29f7e7115 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -369,6 +369,43 @@ module ActiveRecord
@klass.connection.update stmt, "#{@klass} Update All"
end
+ # Touches all records in the current relation without instantiating records first with the updated_at/on attributes
+ # set to the current time or the time specified.
+ # This method can be passed attribute names and an optional time argument.
+ # If attribute names are passed, they are updated along with updated_at/on attributes.
+ # If no time argument is passed, the current time is used as default.
+ #
+ # === Examples
+ #
+ # # Touch all records
+ # Person.all.touch
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670'"
+ #
+ # # Touch multiple records with a custom attribute
+ # Person.all.touch(:created_at)
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670', \"created_at\" = '2018-01-04 22:55:23.132670'"
+ #
+ # # Touch multiple records with a specified time
+ # Person.all.touch(time: Time.new(2020, 5, 16, 0, 0, 0))
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2020-05-16 00:00:00'"
+ #
+ # # Touch records with scope
+ # Person.where(name: 'David').touch
+ # # => "UPDATE \"people\" SET \"updated_at\" = '2018-01-04 22:55:23.132670' WHERE \"people\".\"name\" = 'David'"
+ def touch_all(*names, time: nil)
+ attributes = Array(names) + klass.timestamp_attributes_for_update_in_model
+ time ||= klass.current_time_from_proper_timezone
+ updates = {}
+ attributes.each { |column| updates[column] = time }
+
+ if klass.locking_enabled?
+ quoted_locking_column = connection.quote_column_name(klass.locking_column)
+ updates = sanitize_sql_for_assignment(updates) + ", #{quoted_locking_column} = COALESCE(#{quoted_locking_column}, 0) + 1"
+ end
+
+ update_all(updates)
+ end
+
# Destroys the records by instantiating each
# record and calling its {#destroy}[rdoc-ref:Persistence#destroy] method.
# Each object's callbacks are executed (including <tt>:dependent</tt> association options).
diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb
index 54aa7aca2c..e47f06bf3a 100644
--- a/activerecord/lib/active_record/timestamp.rb
+++ b/activerecord/lib/active_record/timestamp.rb
@@ -53,15 +53,19 @@ module ActiveRecord
end
module ClassMethods # :nodoc:
+ def timestamp_attributes_for_update_in_model
+ timestamp_attributes_for_update.select { |c| column_names.include?(c) }
+ end
+
+ def current_time_from_proper_timezone
+ default_timezone == :utc ? Time.now.utc : Time.now
+ end
+
private
def timestamp_attributes_for_create_in_model
timestamp_attributes_for_create.select { |c| column_names.include?(c) }
end
- def timestamp_attributes_for_update_in_model
- timestamp_attributes_for_update.select { |c| column_names.include?(c) }
- end
-
def all_timestamp_attributes_in_model
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
end
@@ -73,10 +77,6 @@ module ActiveRecord
def timestamp_attributes_for_update
["updated_at", "updated_on"]
end
-
- def current_time_from_proper_timezone
- default_timezone == :utc ? Time.now.utc : Time.now
- end
end
private
@@ -116,7 +116,7 @@ module ActiveRecord
end
def timestamp_attributes_for_update_in_model
- self.class.send(:timestamp_attributes_for_update_in_model)
+ self.class.timestamp_attributes_for_update_in_model
end
def all_timestamp_attributes_in_model
@@ -124,7 +124,7 @@ module ActiveRecord
end
def current_time_from_proper_timezone
- self.class.send(:current_time_from_proper_timezone)
+ self.class.current_time_from_proper_timezone
end
def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model)