aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/associations.rb9
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb13
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/has_many_through_association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb1
-rw-r--r--activerecord/lib/active_record/callbacks.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb53
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/core.rb8
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb3
-rw-r--r--activerecord/lib/active_record/model_schema.rb4
-rw-r--r--activerecord/lib/active_record/persistence.rb3
-rw-r--r--activerecord/lib/active_record/railties/controller_runtime.rb2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb8
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb5
-rw-r--r--activerecord/lib/active_record/scoping/default.rb2
-rw-r--r--activerecord/lib/active_record/touch_later.rb50
-rw-r--r--activerecord/lib/active_record/transactions.rb12
-rw-r--r--activerecord/lib/active_record/validations/presence.rb4
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
28 files changed, 178 insertions, 54 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index 58a4694880..1844b29ccb 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -49,6 +49,7 @@ module ActiveRecord
autoload :ModelSchema
autoload :NestedAttributes
autoload :NoTouching
+ autoload :TouchLater
autoload :Persistence
autoload :QueryCache
autoload :Querying
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 5e3e5f709b..c5c2178ee2 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1565,10 +1565,7 @@ module ActiveRecord
#
# class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration
# def change
- # create_table :developers_projects, id: false do |t|
- # t.integer :developer_id
- # t.integer :project_id
- # end
+ # create_join_table :developers, :projects
# end
# end
#
@@ -1718,10 +1715,8 @@ module ActiveRecord
join_model = builder.through_model
- # FIXME: we should move this to the internal constants. Also people
- # should never directly access this constant so I'm not happy about
- # setting it.
const_set join_model.name, join_model
+ private_constant join_model.name
middle_reflection = builder.middle_reflection join_model
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index ec135d49b7..97eb007f62 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -60,7 +60,7 @@ module ActiveRecord::Associations::Builder
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
end
- def self.touch_record(o, foreign_key, name, touch) # :nodoc:
+ def self.touch_record(o, foreign_key, name, touch, touch_method) # :nodoc:
old_foreign_id = o.changed_attributes[foreign_key]
if old_foreign_id
@@ -75,9 +75,9 @@ module ActiveRecord::Associations::Builder
if old_record
if touch != true
- old_record.touch touch
+ old_record.send(touch_method, touch)
else
- old_record.touch
+ old_record.send(touch_method)
end
end
end
@@ -85,9 +85,9 @@ module ActiveRecord::Associations::Builder
record = o.send name
if record && record.persisted?
if touch != true
- record.touch touch
+ record.send(touch_method, touch)
else
- record.touch
+ record.send(touch_method)
end
end
end
@@ -98,7 +98,8 @@ module ActiveRecord::Associations::Builder
touch = reflection.options[:touch]
callback = lambda { |record|
- BelongsTo.touch_record(record, foreign_key, n, touch)
+ touch_method = touching_delayed_records? ? :touch : :touch_later
+ BelongsTo.touch_record(record, foreign_key, n, touch, touch_method)
}
model.after_save callback, if: :changed?
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index 93dc4ae118..97b57a6a55 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -85,7 +85,7 @@ module ActiveRecord::Associations::Builder
def middle_reflection(join_model)
middle_name = [lhs_model.name.downcase.pluralize,
- association_name].join('_').gsub(/::/, '_').to_sym
+ association_name].join('_'.freeze).gsub('::'.freeze, '_'.freeze).to_sym
middle_options = middle_options join_model
HasMany.create_reflection(lhs_model,
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 88531205a1..6caadb4ce8 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -370,6 +370,8 @@ module ActiveRecord
replace_common_records_in_memory(other_array, original_target)
if other_array != original_target
transaction { replace_records(other_array, original_target) }
+ else
+ other_array
end
end
end
diff --git a/activerecord/lib/active_record/associations/has_many_through_association.rb b/activerecord/lib/active_record/associations/has_many_through_association.rb
index 4897ec44e9..29e8a0edc1 100644
--- a/activerecord/lib/active_record/associations/has_many_through_association.rb
+++ b/activerecord/lib/active_record/associations/has_many_through_association.rb
@@ -135,7 +135,7 @@ module ActiveRecord
if scope.klass.primary_key
count = scope.destroy_all.length
else
- scope.each(&:_run_destroy_callbacks)
+ scope.each { |record| record.run_callbacks :destroy }
arel = scope.arel
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 93550a69f1..67490ecd97 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -309,6 +309,7 @@ module ActiveRecord #:nodoc:
include Aggregations
include Transactions
include NoTouching
+ include TouchLater
include Reflection
include Serialization
include Store
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index f44e5af5de..2fcba8e309 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -289,25 +289,24 @@ module ActiveRecord
end
def destroy #:nodoc:
- _run_destroy_callbacks { super }
+ run_callbacks(:destroy) { super }
end
def touch(*) #:nodoc:
- _run_touch_callbacks { super }
+ run_callbacks(:touch) { super }
end
private
-
def create_or_update(*) #:nodoc:
- _run_save_callbacks { super }
+ run_callbacks(:save) { super }
end
def _create_record #:nodoc:
- _run_create_callbacks { super }
+ run_callbacks(:create) { super }
end
def _update_record(*) #:nodoc:
- _run_update_callbacks { super }
+ run_callbacks(:update) { super }
end
end
end
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 d99dc9a5db..8c50f3d1a3 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -358,7 +358,7 @@ module ActiveRecord
synchronize do
owner = conn.owner
- conn._run_checkin_callbacks do
+ conn.run_callbacks :checkin do
conn.expire
end
@@ -449,7 +449,7 @@ module ActiveRecord
end
def checkout_and_verify(c)
- c._run_checkout_callbacks do
+ c.run_callbacks :checkout do
c.verify!
end
c
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index d2840b9498..91c7298983 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -77,7 +77,7 @@ module ActiveRecord
# Quotes a string, escaping any ' (single quote) and \ (backslash)
# characters.
def quote_string(s)
- s.gsub(/\\/, '\&\&').gsub(/'/, "''") # ' (for ruby-mode)
+ s.gsub('\\'.freeze, '\&\&'.freeze).gsub("'".freeze, "''".freeze) # ' (for ruby-mode)
end
# Quotes the column name. Defaults to no quoting.
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 a768ee2d70..cb83d0022c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -138,7 +138,7 @@ module ActiveRecord
end
def foreign_table_name
- name.to_s.pluralize
+ Base.pluralize_table_names ? name.to_s.pluralize : name
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 72e019066e..ecb4868c13 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -377,6 +377,9 @@ module ActiveRecord
# [<tt>:force</tt>]
# Set to +:cascade+ to drop dependent objects as well.
# Defaults to false.
+ # [<tt>:if_exists</tt>]
+ # Set to +true+ to only drop the table if it exists.
+ # Defaults to false.
#
# Although this command ignores most +options+ and the block if one is given,
# it can be helpful to provide these in a migration's +change+ method so it can be reverted.
@@ -387,6 +390,10 @@ module ActiveRecord
# Adds a new column to the named table.
# See TableDefinition#column for details of the options you can use.
+ #
+ # Note: Not all options will be available, generally this command should
+ # ignore most of them. In favor of doing a low-level call to simply
+ # create a column.
def add_column(table_name, column_name, type, options = {})
at = create_alter_table table_name
at.add_column(column_name, type, options)
@@ -530,6 +537,8 @@ module ActiveRecord
#
# CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
#
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
+ #
# ====== Creating an index with a specific method
#
# add_index(:developers, :name, using: 'btree')
@@ -667,7 +676,10 @@ module ActiveRecord
# remove_reference(:products, :user, index: true, foreign_key: true)
#
def remove_reference(table_name, ref_name, options = {})
- remove_foreign_key table_name, ref_name.to_s.pluralize if options[:foreign_key]
+ if options[:foreign_key]
+ reference_name = Base.pluralize_table_names ? ref_name.to_s.pluralize : ref_name
+ remove_foreign_key(table_name, reference_name)
+ end
remove_column(table_name, "#{ref_name}_id")
remove_column(table_name, "#{ref_name}_type") if options[:polymorphic]
@@ -684,8 +696,8 @@ module ActiveRecord
# +to_table+ contains the referenced primary key.
#
# The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
- # +identifier+ is a 10 character long random string. A custom name can be specified with
- # the <tt>:name</tt> option.
+ # +identifier+ is a 10 character long string which is deterministically generated from the
+ # +from_table+ and +column+. A custom name can be specified with the <tt>:name</tt> option.
#
# ====== Creating a simple foreign key
#
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 b7c7ff1187..76aee452ca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -13,6 +13,10 @@ module ActiveRecord
end
end
+ class ColumnDefinition < ActiveRecord::ConnectionAdapters::ColumnDefinition
+ attr_accessor :charset, :collation
+ end
+
class TableDefinition < ActiveRecord::ConnectionAdapters::TableDefinition
include ColumnMethods
@@ -23,8 +27,16 @@ module ActiveRecord
column.type = :integer
column.auto_increment = true
end
+ column.charset = options[:charset]
+ column.collation = options[:collation]
column
end
+
+ private
+
+ def create_column_definition(name, type)
+ ColumnDefinition.new(name, type)
+ end
end
class Table < ActiveRecord::ConnectionAdapters::Table
@@ -60,6 +72,23 @@ module ActiveRecord
add_column_position!(change_column_sql, column_options(o.column))
end
+ def column_options(o)
+ column_options = super
+ column_options[:charset] = o.charset
+ column_options[:collation] = o.collation
+ column_options
+ end
+
+ def add_column_options!(sql, options)
+ if options[:charset]
+ sql << " CHARACTER SET #{options[:charset]}"
+ end
+ if options[:collation]
+ sql << " COLLATE #{options[:collation]}"
+ end
+ super
+ end
+
def add_column_position!(sql, options)
if options[:first]
sql << " FIRST"
@@ -99,9 +128,18 @@ module ActiveRecord
spec = super
spec.delete(:precision) if /time/ === column.sql_type && column.precision == 0
spec.delete(:limit) if :boolean === column.type
+ if column.collation && table_name = column.instance_variable_get(:@table_name)
+ @collation_cache ||= {}
+ @collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"]
+ spec[:collation] = column.collation.inspect if column.collation != @collation_cache[table_name]
+ end
spec
end
+ def migration_keys
+ super + [:collation]
+ end
+
class Column < ConnectionAdapters::Column # :nodoc:
delegate :strict, :collation, :extra, to: :sql_type_metadata, allow_nil: true
@@ -562,6 +600,21 @@ module ActiveRecord
rename_table_indexes(table_name, new_name)
end
+ # Drops a table from the database.
+ #
+ # [<tt>:force</tt>]
+ # Set to +:cascade+ to drop dependent objects as well.
+ # Defaults to false.
+ # [<tt>:if_exists</tt>]
+ # Set to +true+ to only drop the table if it exists.
+ # Defaults to false.
+ # [<tt>:temporary</tt>]
+ # Set to +true+ to drop temporary table.
+ # Defaults to false.
+ #
+ # Although this command ignores most +options+ and the block if one is given,
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
+ # In that case, +options+ and the block will be used by create_table.
def drop_table(table_name, options = {})
execute "DROP#{' TEMPORARY' if options[:temporary]} TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index a67127bd71..f4dda5154e 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -28,6 +28,7 @@ module ActiveRecord
@null = null
@default = default
@default_function = default_function
+ @table_name = nil
end
def has_default?
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 cb0b9cdbe9..168180cfd3 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -87,7 +87,7 @@ module ActiveRecord
SQL
end
- def drop_table(table_name, options = {})
+ def drop_table(table_name, options = {}) # :nodoc:
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}#{' CASCADE' if options[:force] == :cascade}"
end
@@ -422,7 +422,7 @@ module ActiveRecord
end
# Changes the default value of a table column.
- def change_column_default(table_name, column_name, default)
+ def change_column_default(table_name, column_name, default) # :nodoc:
clear_cache!
column = column_for(table_name, column_name)
return unless column
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 00fc69c878..332ac9d88c 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -568,9 +568,9 @@ module ActiveRecord
case default
# Quoted types
when /\A[\(B]?'(.*)'::/m
- $1.gsub(/''/, "'")
+ $1.gsub("''".freeze, "'".freeze)
# Boolean types
- when 'true', 'false'
+ when 'true'.freeze, 'false'.freeze
default
# Numeric types
when /\A\(?(-?\d+(\.\d*)?)\)?(::bigint)?\z/
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 9b7cba08de..1ad910c4bc 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -302,7 +302,7 @@ module ActiveRecord
assign_attributes(attributes) if attributes
yield self if block_given?
- _run_initialize_callbacks
+ run_callbacks :initialize
end
# Initialize an empty model object from +coder+. +coder+ should be
@@ -329,8 +329,8 @@ module ActiveRecord
self.class.define_attribute_methods
- _run_find_callbacks
- _run_initialize_callbacks
+ run_callbacks :find
+ run_callbacks :initialize
self
end
@@ -366,7 +366,7 @@ module ActiveRecord
@attributes = @attributes.dup
@attributes.reset(self.class.primary_key)
- _run_initialize_callbacks
+ run_callbacks(:initialize)
@new_record = true
@destroyed = false
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index 6b26d7be78..af816a278e 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -31,9 +31,10 @@ module ActiveRecord
end
def sql(event)
- self.class.runtime += event.duration
return unless logger.debug?
+ self.class.runtime += event.duration
+
payload = event.payload
return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index 75adcccce6..3674f672cb 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -328,12 +328,8 @@ module ActiveRecord
@default_attributes = nil
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
@attributes_builder = nil
- @column_names = nil
- @attribute_types = nil
@columns = nil
@columns_hash = nil
- @content_columns = nil
- @default_attributes = nil
@attribute_names = nil
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a6176dffdb..a1e1073792 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -462,9 +462,10 @@ module ActiveRecord
# ball = Ball.new
# ball.touch(:updated_at) # => raises ActiveRecordError
#
- def touch(*names, time: current_time_from_proper_timezone)
+ def touch(*names, time: nil)
raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
+ time ||= current_time_from_proper_timezone
attributes = timestamp_attributes_for_update_in_model
attributes.concat(names)
diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb
index af4840476c..8727e46cb3 100644
--- a/activerecord/lib/active_record/railties/controller_runtime.rb
+++ b/activerecord/lib/active_record/railties/controller_runtime.rb
@@ -19,7 +19,7 @@ module ActiveRecord
end
def cleanup_view_runtime
- if ActiveRecord::Base.connected?
+ if logger.info? && ActiveRecord::Base.connected?
db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime
self.db_runtime = (db_runtime || 0) + db_rt_before_render
runtime = super
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 8f16de3519..402b317d9c 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -130,9 +130,9 @@ module ActiveRecord
# the plucked column names, if they can be deduced. Plucking an SQL fragment
# returns String values by default.
#
- # Person.pluck(:id)
- # # SELECT people.id FROM people
- # # => [1, 2, 3]
+ # Person.pluck(:name)
+ # # SELECT people.name FROM people
+ # # => ['David', 'Jeremy', 'Jose']
#
# Person.pluck(:id, :name)
# # SELECT people.id, people.name FROM people
@@ -150,6 +150,8 @@ module ActiveRecord
# # SELECT DATEDIFF(updated_at, created_at) FROM people
# # => ['0', '27761', '173']
#
+ # See also +ids+.
+ #
def pluck(*column_names)
column_names.map! do |column_name|
if column_name.is_a?(Symbol) && attribute_alias?(column_name)
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index da95920571..eaeaf0321b 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -105,7 +105,10 @@ HEADER
end
def table(table, stream)
- columns = @connection.columns(table)
+ columns = @connection.columns(table).map do |column|
+ column.instance_variable_set(:@table_name, table)
+ column
+ end
begin
tbl = StringIO.new
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 5ec2c88b47..3590b8846e 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -35,7 +35,7 @@ module ActiveRecord
# Are there attributes associated with this scope?
def scope_attributes? # :nodoc:
- super || default_scopes.any?
+ super || default_scopes.any? || respond_to?(:default_scope)
end
def before_remove_const #:nodoc:
diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb
new file mode 100644
index 0000000000..4352a0ffea
--- /dev/null
+++ b/activerecord/lib/active_record/touch_later.rb
@@ -0,0 +1,50 @@
+module ActiveRecord
+ # = Active Record Touch Later
+ module TouchLater
+ extend ActiveSupport::Concern
+
+ included do
+ before_commit_without_transaction_enrollment :touch_deferred_attributes
+ end
+
+ def touch_later(*names) # :nodoc:
+ raise ActiveRecordError, "cannot touch on a new record object" unless persisted?
+
+ @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
+ @_defer_touch_attrs |= names
+ @_touch_time = current_time_from_proper_timezone
+
+ surreptitiously_touch @_defer_touch_attrs
+ self.class.connection.add_transaction_record self
+ end
+
+ def touch(*names, time: nil) # :nodoc:
+ if has_defer_touch_attrs?
+ names |= @_defer_touch_attrs
+ end
+ super(*names, time: time)
+ end
+
+ private
+ def surreptitiously_touch(attrs)
+ attrs.each { |attr| write_attribute attr, @_touch_time }
+ clear_attribute_changes attrs
+ end
+
+ def touch_deferred_attributes
+ if has_defer_touch_attrs? && persisted?
+ @_touching_delayed_records = true
+ touch(*@_defer_touch_attrs, time: @_touch_time)
+ @_touching_delayed_records, @_defer_touch_attrs, @_touch_time = nil, nil, nil
+ end
+ end
+
+ def has_defer_touch_attrs?
+ defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
+ end
+
+ def touching_delayed_records?
+ defined?(@_touching_delayed_records) && @_touching_delayed_records
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 2293d1b258..311dacb449 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -319,8 +319,8 @@ module ActiveRecord
end
def before_committed! # :nodoc:
- _run_before_commit_without_transaction_enrollment_callbacks
- _run_before_commit_callbacks
+ run_callbacks :before_commit_without_transaction_enrollment
+ run_callbacks :before_commit
end
# Call the +after_commit+ callbacks.
@@ -329,8 +329,8 @@ module ActiveRecord
# but call it after the commit of a destroyed object.
def committed!(should_run_callbacks: true) #:nodoc:
if should_run_callbacks && destroyed? || persisted?
- _run_commit_without_transaction_enrollment_callbacks
- _run_commit_callbacks
+ run_callbacks :commit_without_transaction_enrollment
+ run_callbacks :commit
end
ensure
force_clear_transaction_record_state
@@ -340,8 +340,8 @@ module ActiveRecord
# state should be rolled back to the beginning or just to the last savepoint.
def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
if should_run_callbacks
- _run_rollback_without_transaction_enrollment_callbacks
- _run_rollback_callbacks
+ run_callbacks :rollback
+ run_callbacks :rollback_without_transaction_enrollment
end
ensure
restore_transaction_record_state(force_restore_state)
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 75d5bd5a35..a9b791397b 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -43,6 +43,10 @@ module ActiveRecord
# deletes the associated object, thus putting the parent object into an invalid
# state.
#
+ # NOTE: This validation will not fail while using it with an association
+ # if the latter was assigned but not valid. If you want to ensure that
+ # it is both present and valid, you also need to use +validates_associated+.
+ #
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
# * <tt>:on</tt> - Specifies the contexts where this validation is active.
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 9be4b10a55..5106f4e127 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -76,6 +76,8 @@ module ActiveRecord
klass.connection.case_sensitive_comparison(table, attribute, column, value)
end
klass.unscoped.where(comparison)
+ rescue RangeError
+ klass.none
end
def scope_relation(record, table, relation)