aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib/active_record
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib/active_record')
-rw-r--r--activerecord/lib/active_record/associations.rb5
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb22
-rw-r--r--activerecord/lib/active_record/persistence.rb36
-rw-r--r--activerecord/lib/active_record/scoping/default.rb4
-rw-r--r--activerecord/lib/active_record/statement_cache.rb4
7 files changed, 50 insertions, 29 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 7bdbd8ce69..64c20adc87 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -703,8 +703,9 @@ module ActiveRecord
# #belongs_to associations.
#
# Extra options on the associations, as defined in the
- # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
- # also prevent the association's inverse from being found automatically.
+ # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt>
+ # constant, or a custom scope, will also prevent the association's inverse
+ # from being found automatically.
#
# The automatic guessing of the inverse association uses a heuristic based
# on the name of the class, so it may not work for all associations,
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index ff57c40121..5848cd9112 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -20,10 +20,10 @@ module ActiveRecord::Associations::Builder # :nodoc:
}
end
- def self.define_extensions(model, name)
+ def self.define_extensions(model, name, &block)
if block_given?
extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
- extension = Module.new(&Proc.new)
+ extension = Module.new(&block)
model.module_parent.const_set(extension_module_name, extension)
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 05d8d5ac00..683d7190d5 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -281,6 +281,8 @@ module ActiveRecord
super
@connection.reset
configure_connection
+ rescue PG::ConnectionBad
+ connect
end
end
@@ -738,6 +740,8 @@ module ActiveRecord
def connect
@connection = PG.connect(@connection_parameters)
configure_connection
+ add_pg_encoders
+ add_pg_decoders
end
# Configures the encoding, verbosity, schema search path, and time zone of the connection.
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index cb3d34a740..3004caf82d 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -36,8 +36,6 @@ module ActiveRecord
config.merge(results_as_hash: true)
)
- db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
-
ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
rescue Errno::ENOENT => error
if error.message.include?("No such file or directory")
@@ -95,8 +93,6 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
-
- @active = true
configure_connection
end
@@ -144,14 +140,18 @@ module ActiveRecord
alias supports_insert_conflict_target? supports_insert_on_conflict?
def active?
- @active
+ !@connection.closed?
+ end
+
+ def reconnect!
+ super
+ connect if @connection.closed?
end
# Disconnects from the database if already connected. Otherwise, this
# method does nothing.
def disconnect!
super
- @active = false
@connection.close rescue nil
end
@@ -611,7 +611,17 @@ module ActiveRecord
StatementPool.new(self.class.type_cast_config_to_integer(@config[:statement_limit]))
end
+ def connect
+ @connection = ::SQLite3::Database.new(
+ @config[:database].to_s,
+ @config.merge(results_as_hash: true)
+ )
+ configure_connection
+ end
+
def configure_connection
+ @connection.busy_timeout(self.class.type_cast_config_to_integer(@config[:timeout])) if @config[:timeout]
+
execute("PRAGMA foreign_keys = ON", "SCHEMA")
end
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index a09b5f0e96..0c31f0f57e 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -57,7 +57,7 @@ module ActiveRecord
end
end
- # Inserts a single record into the databases. This method constructs a single SQL INSERT
+ # Inserts a single record into the database. This method constructs a single SQL INSERT
# statement and sends it straight to the database. It does not instantiate the involved
# models and it does not trigger Active Record callbacks or validations. However, values
# passed to #insert will still go through Active Record's normal type casting and
@@ -94,8 +94,8 @@ module ActiveRecord
#
# [:unique_by]
# (Postgres and SQLite only) In a table with more than one unique constraint or index,
- # new records may considered duplicates according to different criteria. By default,
- # new rows will be skipped if they violate _any_ unique constraint/index. By defining
+ # new records may be considered duplicates according to different criteria. By default,
+ # new rows will be skipped if they violate _any_ unique constraint or index. By defining
# <tt>:unique_by</tt>, you can skip rows that would create duplicates according to the given
# constraint but raise <tt>ActiveRecord::RecordNotUnique</tt> if rows violate other constraints.
#
@@ -125,7 +125,7 @@ module ActiveRecord
InsertAll.new(self, attributes, on_duplicate: :skip, returning: returning, unique_by: unique_by).execute
end
- # Inserts a single record into the databases. This method constructs a single SQL INSERT
+ # Inserts a single record into the database. This method constructs a single SQL INSERT
# statement and sends it straight to the database. It does not instantiate the involved
# models and it does not trigger Active Record callbacks or validations. However, values
# passed to #insert! will still go through Active Record's normal type casting and
@@ -173,7 +173,7 @@ module ActiveRecord
# { title: 'Eloquent Ruby', author: 'Russ' }
# ])
#
- # # raises ActiveRecord::RecordNotUnique because 'Eloquent Ruby'
+ # # Raises ActiveRecord::RecordNotUnique because 'Eloquent Ruby'
# # does not have a unique ID
# Book.insert_all!([
# { id: 1, title: 'Rework', author: 'David' },
@@ -184,7 +184,7 @@ module ActiveRecord
InsertAll.new(self, attributes, on_duplicate: :raise, returning: returning).execute
end
- # Upserts (inserts-or-creates) a single record into the databases. This method constructs
+ # Upserts (updates or inserts) a single record into the database. This method constructs
# a single SQL INSERT statement and sends it straight to the database. It does not
# instantiate the involved models and it does not trigger Active Record callbacks or
# validations. However, values passed to #upsert will still go through Active Record's
@@ -195,7 +195,7 @@ module ActiveRecord
upsert_all([ attributes ], returning: returning, unique_by: unique_by)
end
- # Upserts (creates-or-updates) multiple records into the database. This method constructs
+ # Upserts (updates or inserts) multiple records into the database. This method constructs
# a single SQL INSERT statement and sends it straight to the database. It does not
# instantiate the involved models and it does not trigger Active Record callbacks or
# validations. However, values passed to #upsert_all will still go through Active Record's
@@ -219,11 +219,14 @@ module ActiveRecord
#
# [:unique_by]
# (Postgres and SQLite only) In a table with more than one unique constraint or index,
- # new records may considered duplicates according to different criteria. For MySQL,
+ # new records may be considered duplicates according to different criteria. For MySQL,
# an upsert will take place if a new record violates _any_ unique constraint. For
# Postgres and SQLite, new rows will replace existing rows when the new row has the
- # same primary key as the existing row. By defining <tt>:unique_by</tt>, you can supply
- # a different key for matching new records to existing ones than the primary key.
+ # same primary key as the existing row. In case of SQLite, an upsert operation causes
+ # an insert to behave as an update or a no-op if the insert would violate
+ # a uniqueness constraint. By defining <tt>:unique_by</tt>, you can supply
+ # a different unique constraint for matching new records to existing ones than the
+ # primary key.
#
# (For example, if you have a unique index on the ISBN column and use that as
# the <tt>:unique_by</tt>, a new record with the same ISBN as an existing record
@@ -240,13 +243,16 @@ module ActiveRecord
#
# ==== Examples
#
- # # Insert multiple records, performing an upsert when records have duplicate ISBNs
+ # # Given a unique index on <tt>books.isbn</tt> and the following record:
+ # Book.create!(title: 'Rework', author: 'David', isbn: '1')
+ #
+ # # Insert multiple records, allowing new records with the same ISBN
+ # # as an existing record to overwrite the existing record.
# # ('Eloquent Ruby' will overwrite 'Rework' because its ISBN is duplicate)
# Book.upsert_all([
- # { title: 'Rework', author: 'David', isbn: '1' },
- # { title: 'Eloquent Ruby', author: 'Russ', isbn: '1' }
- # ],
- # unique_by: { columns: %w[ isbn ] })
+ # { title: 'Eloquent Ruby', author: 'Russ', isbn: '1' },
+ # { title: 'Clean Code', author: 'Robert', isbn: '2' }
+ # ], unique_by: { columns: %w[ isbn ] })
#
def upsert_all(attributes, returning: nil, unique_by: nil)
InsertAll.new(self, attributes, on_duplicate: :update, returning: returning, unique_by: unique_by).execute
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index de75fbe127..87bcfd5181 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -86,8 +86,8 @@ module ActiveRecord
# # Should return a scope, you can call 'super' here etc.
# end
# end
- def default_scope(scope = nil) # :doc:
- scope = Proc.new if block_given?
+ def default_scope(scope = nil, &block) # :doc:
+ scope = block if block_given?
if scope.is_a?(Relation) || !scope.respond_to?(:call)
raise ArgumentError,
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index 95984e7ada..93bce15230 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -113,8 +113,8 @@ module ActiveRecord
end
end
- def self.create(connection, block = Proc.new)
- relation = block.call Params.new
+ def self.create(connection, callable = nil, &block)
+ relation = (callable || block).call Params.new
query_builder, binds = connection.cacheable_query(self, relation.arel)
bind_map = BindMap.new(binds)
new(query_builder, bind_map, relation.klass)