diff options
Diffstat (limited to 'activerecord/lib/active_record')
9 files changed, 54 insertions, 33 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/attribute_methods/query.rb b/activerecord/lib/active_record/attribute_methods/query.rb index 6757e9b66a..6811f54b10 100644 --- a/activerecord/lib/active_record/attribute_methods/query.rb +++ b/activerecord/lib/active_record/attribute_methods/query.rb @@ -16,8 +16,7 @@ module ActiveRecord when true then true when false, nil then false else - column = self.class.columns_hash[attr_name] - if column.nil? + if !type_for_attribute(attr_name) { false } if Numeric === value || value !~ /[^0-9]/ !value.to_i.zero? else 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/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index e6197752bc..750766714d 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -165,10 +165,11 @@ module ActiveRecord def quote_bound_value(value, c = connection) if value.respond_to?(:map) && !value.acts_like?(:string) - if value.respond_to?(:empty?) && value.empty? + quoted = value.map { |v| c.quote(v) } + if quoted.empty? c.quote(nil) else - value.map { |v| c.quote(v) }.join(",") + quoted.join(",") end else c.quote(value) 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) |