diff options
Diffstat (limited to 'activerecord/lib/active_record')
87 files changed, 691 insertions, 659 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 7c37132d3a..e782196ce6 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1,7 +1,7 @@ require "active_support/core_ext/enumerable" require "active_support/core_ext/string/conversions" require "active_support/core_ext/module/remove_method" -require "active_record/errors" +require_relative "errors" module ActiveRecord class AssociationNotFoundError < ConfigurationError #:nodoc: @@ -342,7 +342,7 @@ module ActiveRecord # | | belongs_to | # generated methods | belongs_to | :polymorphic | has_one # ----------------------------------+------------+--------------+--------- - # other(force_reload=false) | X | X | X + # other | X | X | X # other=(other) | X | X | X # build_other(attributes={}) | X | | X # create_other(attributes={}) | X | | X @@ -352,7 +352,7 @@ module ActiveRecord # | | | has_many # generated methods | habtm | has_many | :through # ----------------------------------+-------+----------+---------- - # others(force_reload=false) | X | X | X + # others | X | X | X # others=(other,other,...) | X | X | X # other_ids | X | X | X # other_ids=(id,id,...) | X | X | X @@ -1187,7 +1187,7 @@ module ActiveRecord # +collection+ is a placeholder for the symbol passed as the +name+ argument, so # <tt>has_many :clients</tt> would add among others <tt>clients.empty?</tt>. # - # [collection(force_reload = false)] + # [collection] # Returns an array of all the associated objects. # An empty array is returned if none are found. # [collection<<(object, ...)] @@ -1407,7 +1407,7 @@ module ActiveRecord # +association+ is a placeholder for the symbol passed as the +name+ argument, so # <tt>has_one :manager</tt> would add among others <tt>manager.nil?</tt>. # - # [association(force_reload = false)] + # [association] # Returns the associated object. +nil+ is returned if none is found. # [association=(associate)] # Assigns the associate object, extracts the primary key, sets it as the foreign key, @@ -1539,7 +1539,7 @@ module ActiveRecord # +association+ is a placeholder for the symbol passed as the +name+ argument, so # <tt>belongs_to :author</tt> would add among others <tt>author.nil?</tt>. # - # [association(force_reload = false)] + # [association] # Returns the associated object. +nil+ is returned if none is found. # [association=(associate)] # Assigns the associate object, extracts the primary key, and sets it as the foreign key. @@ -1701,7 +1701,7 @@ module ActiveRecord # +collection+ is a placeholder for the symbol passed as the +name+ argument, so # <tt>has_and_belongs_to_many :categories</tt> would add among others <tt>categories.empty?</tt>. # - # [collection(force_reload = false)] + # [collection] # Returns an array of all the associated objects. # An empty array is returned if none are found. # [collection<<(object, ...)] diff --git a/activerecord/lib/active_record/associations/alias_tracker.rb b/activerecord/lib/active_record/associations/alias_tracker.rb index 4a5c821607..104de4f69d 100644 --- a/activerecord/lib/active_record/associations/alias_tracker.rb +++ b/activerecord/lib/active_record/associations/alias_tracker.rb @@ -4,21 +4,21 @@ module ActiveRecord module Associations # Keeps track of table aliases for ActiveRecord::Associations::JoinDependency class AliasTracker # :nodoc: - def self.create(connection, initial_table, type_caster) + def self.create(connection, initial_table) aliases = Hash.new(0) aliases[initial_table] = 1 - new connection, aliases, type_caster + new(connection, aliases) end - def self.create_with_joins(connection, initial_table, joins, type_caster) + def self.create_with_joins(connection, initial_table, joins) if joins.empty? - create(connection, initial_table, type_caster) + create(connection, initial_table) else aliases = Hash.new { |h, k| h[k] = initial_count_for(connection, k, joins) } aliases[initial_table] = 1 - new connection, aliases, type_caster + new(connection, aliases) end end @@ -51,17 +51,16 @@ module ActiveRecord end # table_joins is an array of arel joins which might conflict with the aliases we assign here - def initialize(connection, aliases, type_caster) + def initialize(connection, aliases) @aliases = aliases @connection = connection - @type_caster = type_caster end - def aliased_table_for(table_name, aliased_name) + def aliased_table_for(table_name, aliased_name, type_caster) if aliases[table_name].zero? # If it's zero, we can have our table_name aliases[table_name] = 1 - Arel::Table.new(table_name, type_caster: @type_caster) + Arel::Table.new(table_name, type_caster: type_caster) else # Otherwise, we need to use an alias aliased_name = @connection.table_alias_for(aliased_name) @@ -74,7 +73,7 @@ module ActiveRecord else aliased_name end - Arel::Table.new(table_name, type_caster: @type_caster).alias(table_alias) + Arel::Table.new(table_name, type_caster: type_caster).alias(table_alias) end end diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index ee2e79889f..1138ae3462 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -30,14 +30,6 @@ module ActiveRecord reset_scope end - # Returns the name of the table of the associated class: - # - # post.comments.aliased_table_name # => "comments" - # - def aliased_table_name - klass.table_name - end - # Resets the \loaded flag to +false+ and sets the \target to +nil+. def reset @loaded = false @@ -94,7 +86,7 @@ module ActiveRecord # actually gets built. def association_scope if klass - @association_scope ||= AssociationScope.scope(self, klass.connection) + @association_scope ||= AssociationScope.scope(self) end end @@ -276,7 +268,7 @@ module ActiveRecord end # Returns true if statement cache should be skipped on the association reader. - def skip_statement_cache? + def skip_statement_cache?(scope) reflection.has_scope? || scope.eager_loading? || klass.scope_attributes? || diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 1593b94f0c..6ef225b725 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -1,8 +1,8 @@ module ActiveRecord module Associations class AssociationScope #:nodoc: - def self.scope(association, connection) - INSTANCE.scope(association, connection) + def self.scope(association) + INSTANCE.scope(association) end def self.create(&block) @@ -16,12 +16,12 @@ module ActiveRecord INSTANCE = create - def scope(association, connection) + def scope(association) klass = association.klass reflection = association.reflection scope = klass.unscoped owner = association.owner - alias_tracker = AliasTracker.create connection, association.klass.table_name, klass.type_caster + alias_tracker = AliasTracker.create(klass.connection, klass.table_name) chain_head, chain_tail = get_chain(reflection, association, alias_tracker) scope.extending! reflection.extensions @@ -112,7 +112,11 @@ module ActiveRecord runtime_reflection = Reflection::RuntimeReflection.new(reflection, association) previous_reflection = runtime_reflection reflection.chain.drop(1).each do |refl| - alias_name = tracker.aliased_table_for(refl.table_name, refl.alias_candidate(name)) + alias_name = tracker.aliased_table_for( + refl.table_name, + refl.alias_candidate(name), + refl.klass.type_caster + ) proxy = ReflectionProxy.new(refl, alias_name) previous_reflection.next = proxy previous_reflection = proxy @@ -138,7 +142,7 @@ module ActiveRecord # Exclude the scope of the association itself, because that # was already merged in the #scope method. reflection.constraints.each do |scope_chain_item| - item = eval_scope(reflection.klass, table, scope_chain_item, owner) + item = eval_scope(reflection, table, scope_chain_item, owner) if scope_chain_item == refl.scope scope.merge! item.except(:where, :includes) @@ -159,9 +163,8 @@ module ActiveRecord scope end - def eval_scope(klass, table, scope, owner) - predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table)) - ActiveRecord::Relation.create(klass, table, predicate_builder).instance_exec(owner, &scope) + def eval_scope(reflection, table, scope, owner) + reflection.build_scope(table).instance_exec(owner, &scope) end end end diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb index edeb6491bd..c58b7d8160 100644 --- a/activerecord/lib/active_record/associations/builder/collection_association.rb +++ b/activerecord/lib/active_record/associations/builder/collection_association.rb @@ -1,6 +1,4 @@ -# This class is inherited by the has_many and has_many_and_belongs_to_many association classes - -require "active_record/associations" +require_relative "../../associations" module ActiveRecord::Associations::Builder # :nodoc: class CollectionAssociation < Association #:nodoc: diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index edc53e2517..a49fb155ee 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -44,10 +44,7 @@ module ActiveRecord if loaded? target.pluck(reflection.association_primary_key) else - @association_ids ||= ( - column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}" - scope.pluck(column) - ) + @association_ids ||= scope.pluck(reflection.association_primary_key) end end @@ -69,6 +66,7 @@ module ActiveRecord def reset super @target = [] + @association_ids = nil end def find(*args) @@ -300,13 +298,14 @@ module ActiveRecord private def find_target - return scope.to_a if skip_statement_cache? + scope = self.scope + return scope.to_a if skip_statement_cache?(scope) conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do StatementCache.create(conn) { |params| as = AssociationScope.create { params.bind } - target_scope.merge as.scope(self, conn) + target_scope.merge!(as.scope(self)) } end @@ -357,7 +356,10 @@ module ActiveRecord transaction do add_to_target(build_record(attributes)) do |record| yield(record) if block_given? - insert_record(record, true, raise) { @_was_loaded = loaded? } + insert_record(record, true, raise) { + @_was_loaded = loaded? + @association_ids = nil + } end end end @@ -430,7 +432,12 @@ module ActiveRecord records.each do |record| raise_on_type_mismatch!(record) add_to_target(record) do - result &&= insert_record(record, true, raise) { @_was_loaded = loaded? } unless owner.new_record? + unless owner.new_record? + result &&= insert_record(record, true, raise) { + @_was_loaded = loaded? + @association_ids = nil + } + end end end diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 8cdb508c43..d77fcaf668 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -1114,6 +1114,7 @@ module ActiveRecord end def reset_scope # :nodoc: + @offsets = {} @scope = nil self 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 53ffb3b68d..2fd20b4368 100644 --- a/activerecord/lib/active_record/associations/has_many_through_association.rb +++ b/activerecord/lib/active_record/associations/has_many_through_association.rb @@ -109,6 +109,11 @@ module ActiveRecord record end + def remove_records(existing_records, records, method) + super + delete_through_records(records) + end + def target_reflection_has_associated_record? !(through_reflection.belongs_to? && owner[through_reflection.foreign_key].blank?) end diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb index 643226267c..04cdcb6a7f 100644 --- a/activerecord/lib/active_record/associations/join_dependency.rb +++ b/activerecord/lib/active_record/associations/join_dependency.rb @@ -93,7 +93,7 @@ module ActiveRecord # joins # => [] # def initialize(base, associations, joins, eager_loading: true) - @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins, base.type_caster) + @alias_tracker = AliasTracker.create_with_joins(base.connection, base.table_name, joins) @eager_loading = eager_loading tree = self.class.make_tree associations @join_root = JoinBase.new base, build(tree, base) @@ -104,17 +104,17 @@ module ActiveRecord join_root.drop(1).map!(&:reflection) end - def join_constraints(outer_joins, join_type) + def join_constraints(joins_to_add, join_type) joins = join_root.children.flat_map { |child| make_join_constraints(join_root, child, join_type) } - joins.concat outer_joins.flat_map { |oj| + joins.concat joins_to_add.flat_map { |oj| if join_root.match? oj.join_root walk join_root, oj.join_root else oj.join_root.children.flat_map { |child| - make_outer_joins oj.join_root, child + make_join_constraints(oj.join_root, child, join_type) } end } @@ -185,7 +185,8 @@ module ActiveRecord node.reflection.chain.map { |reflection| alias_tracker.aliased_table_for( reflection.table_name, - table_alias_for(reflection, parent, reflection != node.reflection) + table_alias_for(reflection, parent, reflection != node.reflection), + reflection.klass.type_caster ) } end @@ -197,8 +198,7 @@ module ActiveRecord def table_alias_for(reflection, parent, join) name = "#{reflection.plural_name}_#{parent.table_name}" - name << "_join" if join - name + join ? "#{name}_join" : name end def walk(left, right) 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 97cfec0302..b14ddfeeeb 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -1,4 +1,4 @@ -require "active_record/associations/join_dependency/join_part" +require_relative "join_part" module ActiveRecord module Associations @@ -34,37 +34,19 @@ module ActiveRecord table = tables.shift klass = reflection.klass - join_keys = reflection.join_keys - key = join_keys.key - foreign_key = join_keys.foreign_key + constraint = reflection.build_join_constraint(table, foreign_table) - constraint = build_constraint(klass, table, key, foreign_table, foreign_key) - - predicate_builder = PredicateBuilder.new(TableMetadata.new(klass, table)) - scope_chain_items = reflection.join_scopes(table, predicate_builder) - klass_scope = reflection.klass_join_scope(table, predicate_builder) - - scope_chain_items.concat [klass_scope].compact - - rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right| - left.merge right - end - - if rel && !rel.arel.constraints.empty? - binds += rel.bound_attributes - constraint = constraint.and rel.arel.constraints - end + joins << table.create_join(table, table.create_on(constraint), join_type) - if reflection.type - value = foreign_klass.base_class.name - column = klass.columns_hash[reflection.type.to_s] + join_scope = reflection.join_scope(table, foreign_klass) - binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name)) - constraint = constraint.and klass.arel_attribute(reflection.type, table).eq(Arel::Nodes::BindParam.new) + if join_scope.arel.constraints.any? + binds.concat join_scope.bound_attributes + joins.concat join_scope.arel.join_sources + right = joins.last.right + right.expr = right.expr.and(join_scope.arel.constraints) end - joins << table.create_join(table, table.create_on(constraint), join_type) - # The current table in this iteration becomes the foreign table in the next foreign_table, foreign_klass = table, klass end @@ -72,34 +54,6 @@ module ActiveRecord JoinInformation.new joins, binds end - # Builds equality condition. - # - # Example: - # - # class Physician < ActiveRecord::Base - # has_many :appointments - # end - # - # If I execute `Physician.joins(:appointments).to_a` then - # klass # => Physician - # table # => #<Arel::Table @name="appointments" ...> - # key # => physician_id - # foreign_table # => #<Arel::Table @name="physicians" ...> - # foreign_key # => id - # - def build_constraint(klass, table, key, foreign_table, foreign_key) - constraint = table[key].eq(foreign_table[foreign_key]) - - if klass.finder_needs_type_condition? - constraint = table.create_and([ - constraint, - klass.send(:type_condition, table) - ]) - end - - constraint - end - def table tables.first end 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 fca20514d1..6e0963425d 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_base.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_base.rb @@ -1,4 +1,4 @@ -require "active_record/associations/join_dependency/join_part" +require_relative "join_part" module ActiveRecord module Associations diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb index 9f77f38b35..a18994cec4 100644 --- a/activerecord/lib/active_record/associations/preloader.rb +++ b/activerecord/lib/active_record/associations/preloader.rb @@ -54,8 +54,6 @@ module ActiveRecord autoload :BelongsTo, "active_record/associations/preloader/belongs_to" end - NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, []) - # Eager loads the named associations for the given Active Record record(s). # # In this description, 'association name' shall refer to the name passed @@ -93,7 +91,6 @@ module ActiveRecord def preload(records, associations, preload_scope = nil) records = Array.wrap(records).compact.uniq associations = Array.wrap(associations) - preload_scope = preload_scope || NULL_RELATION if records.empty? [] @@ -147,7 +144,7 @@ module ActiveRecord def preloaders_for_one(association, records, scope) grouped_records(association, records).flat_map do |reflection, klasses| klasses.map do |rhs_klass, rs| - loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope) + loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope) loader.run self loader end @@ -159,6 +156,7 @@ module ActiveRecord records.each do |record| next unless record assoc = record.association(association) + next unless assoc.klass klasses = h[assoc.reflection] ||= {} (klasses[assoc.klass] ||= []) << record end @@ -180,20 +178,11 @@ module ActiveRecord end end - class NullPreloader # :nodoc: - def self.new(klass, owners, reflection, preload_scope); self; end - def self.run(preloader); end - def self.preloaded_records; []; end - def self.owners; []; end - 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 # that accepts a preloader. - def preloader_for(reflection, owners, rhs_klass) - return NullPreloader unless rhs_klass - + def preloader_for(reflection, owners) if owners.first.association(reflection.name).loaded? return AlreadyLoaded end diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb index 63ef3f2d8c..85343040db 100644 --- a/activerecord/lib/active_record/associations/preloader/association.rb +++ b/activerecord/lib/active_record/associations/preloader/association.rb @@ -11,7 +11,6 @@ module ActiveRecord @reflection = reflection @preload_scope = preload_scope @model = owners.first && owners.first.class - @scope = nil @preloaded_records = [] end @@ -23,29 +22,11 @@ module ActiveRecord raise NotImplementedError end - def scope - @scope ||= build_scope - end - - def records_for(ids) - scope.where(association_key_name => ids) - end - - def table - klass.arel_table - end - # The name of the key on the associated records def association_key_name raise NotImplementedError end - # This is overridden by HABTM as the condition should be on the foreign_key column in - # the join table - def association_key - klass.arel_attribute(association_key_name, table) - end - # The name of the key on the model which declares the association def owner_key_name raise NotImplementedError @@ -114,54 +95,35 @@ module ActiveRecord # Make several smaller queries if necessary or make one query if the adapter supports it slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size) @preloaded_records = slices.flat_map do |slice| - records_for(slice).load(&block) + records_for(slice, &block) end @preloaded_records.group_by do |record| convert_key(record[association_key_name]) end end + def records_for(ids, &block) + scope.where(association_key_name => ids).load(&block) + end + + def scope + @scope ||= build_scope + end + def reflection_scope @reflection_scope ||= reflection.scope_for(klass) end def build_scope - scope = klass.unscoped - - values = reflection_scope.values - preload_values = preload_scope.values - - scope.where_clause = reflection_scope.where_clause + preload_scope.where_clause - scope.references_values = Array(values[:references]) + Array(preload_values[:references]) - - if preload_values[:select] || values[:select] - scope._select!(preload_values[:select] || values[:select]) - end - scope.includes! preload_values[:includes] || values[:includes] - if preload_scope.joins_values.any? - scope.joins!(preload_scope.joins_values) - else - scope.joins!(reflection_scope.joins_values) - end - - if order_values = preload_values[:order] || values[:order] - scope.order!(order_values) - end - - if preload_values[:reordering] || values[:reordering] - scope.reordering_value = true - end - - if preload_values[:readonly] || values[:readonly] - scope.readonly! - end + scope = klass.default_scoped - if options[:as] - scope.where!(klass.table_name => { reflection.type => model.base_class.sti_name }) + if reflection.type + scope.where!(reflection.type => model.base_class.sti_name) end - scope.unscope_values = Array(values[:unscope]) + Array(preload_values[:unscope]) - klass.default_scoped.merge(scope) + scope.merge!(reflection_scope) + scope.merge!(preload_scope) if preload_scope + scope 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 8b954138cd..0999746cd5 100644 --- a/activerecord/lib/active_record/associations/preloader/through_association.rb +++ b/activerecord/lib/active_record/associations/preloader/through_association.rb @@ -11,6 +11,8 @@ module ActiveRecord end def associated_records_by_owner(preloader) + through_scope = through_scope() + preloader.preload(owners, through_reflection.name, through_scope) @@ -20,7 +22,7 @@ module ActiveRecord [owner, Array(center)] end - reset_association owners, through_reflection.name + reset_association(owners, through_reflection.name, through_scope) middle_records = through_records.flat_map(&:last) @@ -63,7 +65,7 @@ module ActiveRecord id_map end - def reset_association(owners, association_name) + def reset_association(owners, association_name, through_scope) should_reset = (through_scope != through_reflection.klass.unscoped) || (options[:source_type] && through_reflection.collection?) @@ -77,17 +79,24 @@ module ActiveRecord def through_scope scope = through_reflection.klass.unscoped + values = reflection_scope.values if options[:source_type] scope.where! reflection.foreign_type => options[:source_type] else unless reflection_scope.where_clause.empty? - scope.includes_values = Array(reflection_scope.values[:includes] || options[:source]) + scope.includes_values = Array(values[:includes] || options[:source]) scope.where_clause = reflection_scope.where_clause + if joins = values[:joins] + scope.joins!(source_reflection.name => joins) + end + if left_outer_joins = values[:left_outer_joins] + scope.left_outer_joins!(source_reflection.name => left_outer_joins) + end end - scope.references! reflection_scope.values[:references] - if scope.eager_loading? && order_values = reflection_scope.values[:order] + scope.references! values[:references] + if scope.eager_loading? && order_values = values[:order] scope = scope.order(order_values) end end diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb index 91580a28d0..f8bbe4c2ed 100644 --- a/activerecord/lib/active_record/associations/singular_association.rb +++ b/activerecord/lib/active_record/associations/singular_association.rb @@ -36,13 +36,14 @@ module ActiveRecord end def find_target - return scope.take if skip_statement_cache? + scope = self.scope + return scope.take if skip_statement_cache?(scope) conn = klass.connection sc = reflection.association_scope_cache(conn, owner) do StatementCache.create(conn) { |params| as = AssociationScope.create { params.bind } - target_scope.merge(as.scope(self, conn)).limit(1) + target_scope.merge!(as.scope(self)).limit(1) } end @@ -63,6 +64,10 @@ module ActiveRecord end def _create_record(attributes, raise_error = false) + unless owner.persisted? + raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved" + end + record = build_record(attributes) yield(record) if block_given? saved = record.save diff --git a/activerecord/lib/active_record/attribute.rb b/activerecord/lib/active_record/attribute.rb index 38281158d8..78662433eb 100644 --- a/activerecord/lib/active_record/attribute.rb +++ b/activerecord/lib/active_record/attribute.rb @@ -122,7 +122,7 @@ module ActiveRecord def encode_with(coder) coder["name"] = name - coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast + 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) diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb index 57f8bbed76..c4e731fb28 100644 --- a/activerecord/lib/active_record/attribute/user_provided_default.rb +++ b/activerecord/lib/active_record/attribute/user_provided_default.rb @@ -1,4 +1,4 @@ -require "active_record/attribute" +require_relative "../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 76987fb8f4..8c58e63931 100644 --- a/activerecord/lib/active_record/attribute_methods/dirty.rb +++ b/activerecord/lib/active_record/attribute_methods/dirty.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/module/attribute_accessors" -require "active_record/attribute_mutation_tracker" +require_relative "../attribute_mutation_tracker" module ActiveRecord module AttributeMethods @@ -80,7 +80,7 @@ module ActiveRecord clear_mutation_trackers end - def raw_write_attribute(attr_name, *) + def write_attribute_without_type_cast(attr_name, *) result = super clear_attribute_change(attr_name) result diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index 2f32caa257..081aad434d 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -8,23 +8,20 @@ module ActiveRecord # Returns this record's primary key value wrapped in an array if one is # available. def to_key - sync_with_transaction_state key = id [key] if key end # Returns the primary key value. def id - if pk = self.class.primary_key - sync_with_transaction_state - _read_attribute(pk) - end + sync_with_transaction_state + _read_attribute(self.class.primary_key) if self.class.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 + _write_attribute(self.class.primary_key, value) if self.class.primary_key end # Queries the primary key value. @@ -57,16 +54,12 @@ module ActiveRecord end module ClassMethods - def define_method_attribute(attr_name) - super + ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set - if attr_name == primary_key && attr_name != "id" - generated_attribute_methods.send(:alias_method, :id, primary_key) - end + def instance_method_already_implemented?(method_name) + super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name) end - ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set - def dangerous_attribute_method?(method_name) super && !ID_ATTRIBUTE_METHODS.include?(method_name) end diff --git a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb index 4a8d231503..1f1efe8812 100644 --- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb +++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb @@ -54,8 +54,7 @@ module ActiveRecord extend ActiveSupport::Concern included do - mattr_accessor :time_zone_aware_attributes, instance_writer: false - self.time_zone_aware_attributes = false + mattr_accessor :time_zone_aware_attributes, instance_writer: false, default: false class_attribute :skip_time_zone_conversion_for_attributes, instance_writer: false, default: [] class_attribute :time_zone_aware_types, instance_writer: false, default: [ :datetime, :time ] diff --git a/activerecord/lib/active_record/attribute_methods/write.rb b/activerecord/lib/active_record/attribute_methods/write.rb index fe0e01db28..54b673c72e 100644 --- a/activerecord/lib/active_record/attribute_methods/write.rb +++ b/activerecord/lib/active_record/attribute_methods/write.rb @@ -17,7 +17,7 @@ module ActiveRecord generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1 def __temp__#{safe_name}=(value) name = ::ActiveRecord::AttributeMethods::AttrNames::ATTR_#{safe_name} - write_attribute(name, value) + _write_attribute(name, value) end alias_method #{(name + '=').inspect}, :__temp__#{safe_name}= undef_method :__temp__#{safe_name}= @@ -35,30 +35,27 @@ module ActiveRecord attr_name.to_s end - write_attribute_with_type_cast(name, value, true) + name = self.class.primary_key if name == "id".freeze && self.class.primary_key + _write_attribute(name, value) end - def raw_write_attribute(attr_name, value) # :nodoc: - write_attribute_with_type_cast(attr_name, value, false) + # This method exists to avoid the expensive primary_key check internally, without + # breaking compatibility with the write_attribute API + def _write_attribute(attr_name, value) # :nodoc: + @attributes.write_from_user(attr_name.to_s, value) + value end private - # Handle *= for method_missing. - def attribute=(attribute_name, value) - write_attribute(attribute_name, value) + def write_attribute_without_type_cast(attr_name, value) + name = attr_name.to_s + @attributes.write_cast_value(name, value) + value end - def write_attribute_with_type_cast(attr_name, value, should_type_cast) - attr_name = attr_name.to_s - attr_name = self.class.primary_key if attr_name == "id" && self.class.primary_key - - if should_type_cast - @attributes.write_from_user(attr_name, value) - else - @attributes.write_cast_value(attr_name, value) - end - - value + # Handle *= for method_missing. + def attribute=(attribute_name, value) + _write_attribute(attribute_name, value) end end end diff --git a/activerecord/lib/active_record/attribute_mutation_tracker.rb b/activerecord/lib/active_record/attribute_mutation_tracker.rb index 4de993e169..a01a58f8a5 100644 --- a/activerecord/lib/active_record/attribute_mutation_tracker.rb +++ b/activerecord/lib/active_record/attribute_mutation_tracker.rb @@ -26,6 +26,7 @@ module ActiveRecord 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 @@ -44,7 +45,7 @@ module ActiveRecord end def changed_in_place?(attr_name) - attributes[attr_name].changed_in_place? + attributes[attr_name.to_s].changed_in_place? end def forget_change(attr_name) @@ -54,7 +55,7 @@ module ActiveRecord end def original_value(attr_name) - attributes[attr_name].original_value + attributes[attr_name.to_s].original_value end def force_change(attr_name) diff --git a/activerecord/lib/active_record/attribute_set.rb b/activerecord/lib/active_record/attribute_set.rb index 01f9d815d5..6399e3de70 100644 --- a/activerecord/lib/active_record/attribute_set.rb +++ b/activerecord/lib/active_record/attribute_set.rb @@ -1,5 +1,5 @@ -require "active_record/attribute_set/builder" -require "active_record/attribute_set/yaml_encoder" +require_relative "attribute_set/builder" +require_relative "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 2f624d32af..abe22b9ae4 100644 --- a/activerecord/lib/active_record/attribute_set/builder.rb +++ b/activerecord/lib/active_record/attribute_set/builder.rb @@ -1,4 +1,4 @@ -require "active_record/attribute" +require_relative "../attribute" module ActiveRecord class AttributeSet # :nodoc: diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb index 475b9beec4..dde22bcdaa 100644 --- a/activerecord/lib/active_record/attributes.rb +++ b/activerecord/lib/active_record/attributes.rb @@ -1,4 +1,4 @@ -require "active_record/attribute/user_provided_default" +require_relative "attribute/user_provided_default" module ActiveRecord # See ActiveRecord::Attributes::ClassMethods for documentation diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb index 829a4f6e86..70f0e2af8e 100644 --- a/activerecord/lib/active_record/autosave_association.rb +++ b/activerecord/lib/active_record/autosave_association.rb @@ -140,8 +140,7 @@ module ActiveRecord included do Associations::Builder::Association.extensions << AssociationBuilderExtension - mattr_accessor :index_nested_attribute_errors, instance_writer: false - self.index_nested_attribute_errors = false + mattr_accessor :index_nested_attribute_errors, instance_writer: false, default: false end module ClassMethods # :nodoc: diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index ac1aa2df45..f0e455478a 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -13,14 +13,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 "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" +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" module ActiveRecord #:nodoc: # = 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 61bf5477aa..627b753f01 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -679,7 +679,7 @@ module ActiveRecord # this block can't be easily moved into attempt_to_checkout_all_existing_connections's # rescue block, because doing so would put it outside of synchronize section, without # being in a critical section thread_report might become inaccurate - msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds" + msg = "could not obtain ownership of all database connections in #{checkout_timeout} seconds".dup thread_report = [] @connections.each do |conn| 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 c6811a4802..879626b72a 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -51,9 +51,7 @@ module ActiveRecord # Returns a single value from a record def select_value(arel, name = nil, binds = []) - if result = select_rows(arel, name, binds).first - result.first - end + single_value_from_rows(select_rows(arel, name, binds)) end # Returns an array of the values of the first column in a select: @@ -68,6 +66,18 @@ module ActiveRecord select_all(arel, name, binds).rows end + def query_value(sql, name = nil) # :nodoc: + single_value_from_rows(query(sql, name)) + end + + def query_values(sql, name = nil) # :nodoc: + query(sql, name).map(&:first) + end + + def query(sql, name = nil) # :nodoc: + exec_query(sql, name).rows + end + # Executes the SQL statement in the context of this connection and returns # the raw result from the connection adapter. # Note: depending on your database connector, the result returned by this @@ -296,6 +306,9 @@ module ActiveRecord # Inserts the given fixture into the table. Overridden in adapters that require # something beyond a simple insert (eg. Oracle). + # Most of adapters should implement `insert_fixtures` that leverages bulk SQL insert. + # We keep this method to provide fallback + # for databases like sqlite that do not support bulk inserts. def insert_fixture(fixture, table_name) fixture = fixture.stringify_keys @@ -308,16 +321,52 @@ module ActiveRecord raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.) end end - key_list = fixture.keys.map { |name| quote_column_name(name) } - value_list = binds.map(&:value_for_database).map do |value| - begin - quote(value) - rescue TypeError - quote(YAML.dump(value)) + + table = Arel::Table.new(table_name) + + values = binds.map do |bind| + value = with_yaml_fallback(bind.value_for_database) + [table[bind.name], value] + end + + manager = Arel::InsertManager.new + manager.into(table) + manager.insert(values) + execute manager.to_sql, "Fixture Insert" + end + + # Inserts a set of fixtures into the table. Overridden in adapters that require + # something beyond a simple insert (eg. Oracle). + def insert_fixtures(fixtures, table_name) + return if fixtures.empty? + + columns = schema_cache.columns_hash(table_name) + + values = fixtures.map do |fixture| + fixture = fixture.stringify_keys + + unknown_columns = fixture.keys - columns.keys + if unknown_columns.any? + raise Fixture::FixtureError, %(table "#{table_name}" has no columns named #{unknown_columns.map(&:inspect).join(', ')}.) + end + + columns.map do |name, column| + if fixture.key?(name) + type = lookup_cast_type_from_column(column) + bind = Relation::QueryAttribute.new(name, fixture[name], type) + with_yaml_fallback(bind.value_for_database) + else + Arel.sql("DEFAULT") + end end end - execute "INSERT INTO #{quote_table_name(table_name)} (#{key_list.join(', ')}) VALUES (#{value_list.join(', ')})", "Fixture Insert" + table = Arel::Table.new(table_name) + manager = Arel::InsertManager.new + manager.into(table) + columns.each_key { |column| manager.columns << table[column] } + manager.values = manager.create_values_list(values) + execute manager.to_sql, "Fixtures Insert" end def empty_insert_statement_value @@ -371,7 +420,11 @@ module ActiveRecord end def last_inserted_id(result) - row = result.rows.first + single_value_from_rows(result.rows) + end + + def single_value_from_rows(rows) + row = rows.first row && row.first end @@ -381,6 +434,17 @@ module ActiveRecord end [relation, binds] end + + # Fixture value is quoted by Arel, however scalar values + # are not quotable. In this case we want to convert + # the column value to YAML. + def with_yaml_fallback(value) + if value.is_a?(Hash) || value.is_a?(Array) + YAML.dump(value) + else + value + end + end end end end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb index e53ba4e666..c352ddfc11 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb @@ -108,6 +108,7 @@ module ActiveRecord "sql.active_record", sql: sql, binds: binds, + type_casted_binds: -> { type_casted_binds(binds) }, name: name, connection_id: object_id, cached: true, @@ -123,6 +124,7 @@ module ActiveRecord # If arel is locked this is a SELECT ... FOR UPDATE or somesuch. Such # queries should not be cached. def locked?(arel) + arel = arel.arel if arel.is_a?(Relation) arel.respond_to?(:locked) && arel.locked end diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index a4fecc4a8e..8865e7c703 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -22,7 +22,7 @@ module ActiveRecord private def visit_AlterTable(o) - sql = "ALTER TABLE #{quote_table_name(o.name)} " + sql = "ALTER TABLE #{quote_table_name(o.name)} ".dup sql << o.adds.map { |col| accept col }.join(" ") sql << o.foreign_key_adds.map { |fk| visit_AddForeignKey fk }.join(" ") sql << o.foreign_key_drops.map { |fk| visit_DropForeignKey fk }.join(" ") @@ -30,17 +30,17 @@ module ActiveRecord def visit_ColumnDefinition(o) o.sql_type = type_to_sql(o.type, o.options) - column_sql = "#{quote_column_name(o.name)} #{o.sql_type}" + column_sql = "#{quote_column_name(o.name)} #{o.sql_type}".dup add_column_options!(column_sql, column_options(o)) unless o.type == :primary_key column_sql end def visit_AddColumnDefinition(o) - "ADD #{accept(o.column)}" + "ADD #{accept(o.column)}".dup end def visit_TableDefinition(o) - create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} " + create_sql = "CREATE#{' TEMPORARY' if o.temporary} TABLE #{quote_table_name(o.name)} ".dup statements = o.columns.map { |c| accept c } statements << accept(o.primary_keys) if o.primary_keys @@ -55,7 +55,7 @@ module ActiveRecord create_sql << "(#{statements.join(', ')})" if statements.present? add_table_options!(create_sql, table_options(o)) - create_sql << " AS #{@conn.to_sql(o.as)}" if o.as + create_sql << " AS #{to_sql(o.as)}" if o.as create_sql end @@ -114,6 +114,11 @@ module ActiveRecord sql end + def to_sql(sql) + sql = sql.to_sql if sql.respond_to?(:to_sql) + sql + end + def foreign_key_in_create(from_table, to_table, options) options = foreign_key_options(from_table, to_table, options) accept ForeignKeyDefinition.new(from_table, to_table, options) 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 16a398f631..475463c4fd 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1,4 +1,4 @@ -require "active_record/migration/join_table" +require_relative "../../migration/join_table" require "active_support/core_ext/string/access" require "digest" @@ -31,7 +31,7 @@ module ActiveRecord # Returns the relation names useable to back Active Record models. # For most adapters this means all #tables and #views. def data_sources - select_values(data_source_sql, "SCHEMA") + query_values(data_source_sql, "SCHEMA") rescue NotImplementedError tables | views end @@ -41,14 +41,14 @@ module ActiveRecord # data_source_exists?(:ebooks) # def data_source_exists?(name) - select_values(data_source_sql(name), "SCHEMA").any? if name.present? + query_values(data_source_sql(name), "SCHEMA").any? if name.present? rescue NotImplementedError data_sources.include?(name.to_s) end # Returns an array of table names defined in the database. def tables - select_values(data_source_sql(type: "BASE TABLE"), "SCHEMA") + query_values(data_source_sql(type: "BASE TABLE"), "SCHEMA") end # Checks to see if the table +table_name+ exists on the database. @@ -56,14 +56,14 @@ module ActiveRecord # table_exists?(:developers) # def table_exists?(table_name) - select_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present? + query_values(data_source_sql(table_name, type: "BASE TABLE"), "SCHEMA").any? if table_name.present? rescue NotImplementedError tables.include?(table_name.to_s) end # Returns an array of view names defined in the database. def views - select_values(data_source_sql(type: "VIEW"), "SCHEMA") + query_values(data_source_sql(type: "VIEW"), "SCHEMA") end # Checks to see if the view +view_name+ exists on the database. @@ -71,7 +71,7 @@ module ActiveRecord # view_exists?(:ebooks) # def view_exists?(view_name) - select_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present? + query_values(data_source_sql(view_name, type: "VIEW"), "SCHEMA").any? if view_name.present? rescue NotImplementedError views.include?(view_name.to_s) end @@ -188,6 +188,8 @@ module ActiveRecord # The name of the primary key, if one is to be added automatically. # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored. # + # If an array is passed, a composite primary key will be created. + # # Note that Active Record models will automatically detect their # primary key. This can be avoided by using # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model @@ -241,6 +243,23 @@ module ActiveRecord # label varchar # ) # + # ====== Create a composite primary key + # + # create_table(:orders, primary_key: [:product_id, :client_id]) do |t| + # t.belongs_to :product + # t.belongs_to :client + # end + # + # generates: + # + # CREATE TABLE order ( + # product_id integer NOT NULL, + # client_id integer NOT NULL + # ); + # + # ALTER TABLE ONLY "orders" + # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id); + # # ====== Do not add a primary key column # # create_table(:categories_suppliers, id: false) do |t| @@ -493,8 +512,7 @@ module ActiveRecord # * <tt>:default</tt> - # The column's default value. Use +nil+ for +NULL+. # * <tt>:null</tt> - - # Allows or disallows +NULL+ values in the column. This option could - # have been named <tt>:null_allowed</tt>. + # Allows or disallows +NULL+ values in the column. # * <tt>:precision</tt> - # Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns. # * <tt>:scale</tt> - @@ -992,7 +1010,7 @@ module ActiveRecord def dump_schema_information #:nodoc: versions = ActiveRecord::SchemaMigration.all_versions - insert_versions_sql(versions) + insert_versions_sql(versions) if versions.any? end def initialize_schema_migrations_table # :nodoc: @@ -1330,7 +1348,7 @@ module ActiveRecord sm_table = quote_table_name(ActiveRecord::SchemaMigration.table_name) if versions.is_a?(Array) - sql = "INSERT INTO #{sm_table} (version) VALUES\n" + sql = "INSERT INTO #{sm_table} (version) VALUES\n".dup sql << versions.map { |v| "(#{quote(v)})" }.join(",\n") sql << ";\n\n" sql diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb index 19b7821494..f63d09039f 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb @@ -1,10 +1,13 @@ module ActiveRecord module ConnectionAdapters class TransactionState - VALID_STATES = Set.new([:committed, :rolledback, nil]) - def initialize(state = nil) @state = state + @children = [] + end + + def add_child(state) + @children << state end def finalized? @@ -19,15 +22,43 @@ module ActiveRecord @state == :rolledback end + def fully_completed? + completed? + end + def completed? committed? || rolledback? end def set_state(state) - unless VALID_STATES.include?(state) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + The set_state method is deprecated and will be removed in + Rails 6.0. Please use rollback! or commit! to set transaction + state directly. + MSG + case state + when :rolledback + rollback! + when :committed + commit! + when nil + nullify! + else raise ArgumentError, "Invalid transaction state: #{state}" end - @state = state + end + + def rollback! + @children.each { |c| c.rollback! } + @state = :rolledback + end + + def commit! + @state = :committed + end + + def nullify! + @state = nil end end @@ -57,7 +88,7 @@ module ActiveRecord end def rollback - @state.set_state(:rolledback) + @state.rollback! end def rollback_records @@ -72,7 +103,7 @@ module ActiveRecord end def commit - @state.set_state(:committed) + @state.commit! end def before_commit_records @@ -100,8 +131,11 @@ module ActiveRecord end class SavepointTransaction < Transaction - def initialize(connection, savepoint_name, options, *args) + def initialize(connection, savepoint_name, parent_transaction, options, *args) super(connection, options, *args) + + parent_transaction.state.add_child(@state) + if options[:isolation] raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction" end @@ -155,7 +189,7 @@ module ActiveRecord if @stack.empty? RealTransaction.new(@connection, options, run_commit_callbacks: run_commit_callbacks) else - SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options, + SavepointTransaction.new(@connection, "active_record_#{@stack.size}", @stack.last, options, run_commit_callbacks: run_commit_callbacks) end diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 85d6fbe8b3..30b29e7007 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -1,9 +1,9 @@ -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_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 "arel/collectors/bind" require "arel/collectors/sql_string" @@ -147,7 +147,7 @@ module ActiveRecord # this method must only be called while holding connection pool's mutex def lease if in_use? - msg = "Cannot lease connection, " + msg = "Cannot lease connection, ".dup if @owner == Thread.current msg << "it is already leased by the current thread." else @@ -576,12 +576,14 @@ module ActiveRecord type_casted_binds: type_casted_binds, statement_name: statement_name, connection_id: object_id) do + begin @lock.synchronize do yield end + rescue => e + raise translate_exception_class(e, sql) end - rescue => e - raise translate_exception_class(e, sql) + end end def translate_exception(exception, message) 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 648c869915..06976aa769 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -1,13 +1,13 @@ -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_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_support/core_ext/string/strip" @@ -59,12 +59,12 @@ module ActiveRecord @statements = StatementPool.new(self.class.type_cast_config_to_integer(config[:statement_limit])) if version < "5.1.10" - raise "Your version of MySQL (#{full_version.match(/^\d+\.\d+\.\d+/)[0]}) is too old. Active Record supports MySQL >= 5.1.10." + raise "Your version of MySQL (#{version_string}) is too old. Active Record supports MySQL >= 5.1.10." end end def version #:nodoc: - @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0]) + @version ||= Version.new(version_string) end def mariadb? # :nodoc: @@ -120,11 +120,11 @@ module ActiveRecord end def get_advisory_lock(lock_name, timeout = 0) # :nodoc: - select_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1 + query_value("SELECT GET_LOCK(#{quote(lock_name)}, #{timeout})") == 1 end def release_advisory_lock(lock_name) # :nodoc: - select_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1 + query_value("SELECT RELEASE_LOCK(#{quote(lock_name)})") == 1 end def native_database_types @@ -132,7 +132,7 @@ module ActiveRecord end def index_algorithms - { default: "ALGORITHM = DEFAULT", copy: "ALGORITHM = COPY", inplace: "ALGORITHM = INPLACE" } + { default: "ALGORITHM = DEFAULT".dup, copy: "ALGORITHM = COPY".dup, inplace: "ALGORITHM = INPLACE".dup } end # HELPER METHODS =========================================== @@ -152,7 +152,7 @@ module ActiveRecord # REFERENTIAL INTEGRITY ==================================== def disable_referential_integrity #:nodoc: - old = select_value("SELECT @@FOREIGN_KEY_CHECKS") + old = query_value("SELECT @@FOREIGN_KEY_CHECKS") begin update("SET FOREIGN_KEY_CHECKS = 0") @@ -267,7 +267,7 @@ module ActiveRecord end def current_database - select_value "SELECT DATABASE() as db" + query_value("SELECT database()", "SCHEMA") end # Returns the database character set. @@ -287,7 +287,7 @@ module ActiveRecord def table_comment(table_name) # :nodoc: scope = quoted_scope(table_name) - select_value(<<-SQL.strip_heredoc, "SCHEMA") + query_value(<<-SQL.strip_heredoc, "SCHEMA") SELECT table_comment FROM information_schema.tables WHERE table_schema = #{scope[:schema]} @@ -379,7 +379,7 @@ module ActiveRecord def add_index(table_name, column_name, options = {}) #:nodoc: index_name, index_type, index_columns, _, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options) - sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}" + sql = "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}".dup execute add_sql_comment!(sql, comment) end @@ -393,7 +393,7 @@ module ActiveRecord scope = quoted_scope(table_name) - fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA") + fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA") SELECT fk.referenced_table_name AS 'to_table', fk.referenced_column_name AS 'primary_key', fk.column_name AS 'column', @@ -464,13 +464,13 @@ module ActiveRecord super end - sql << " unsigned" if unsigned && type != :primary_key + sql = "#{sql} unsigned" if unsigned && type != :primary_key sql end # SHOW VARIABLES LIKE 'name' def show_variable(name) - select_value("SELECT @@#{name}", "SCHEMA") + query_value("SELECT @@#{name}", "SCHEMA") rescue ActiveRecord::StatementInvalid nil end @@ -480,7 +480,7 @@ module ActiveRecord scope = quoted_scope(table_name) - select_values(<<-SQL.strip_heredoc, "SCHEMA") + query_values(<<-SQL.strip_heredoc, "SCHEMA") SELECT column_name FROM information_schema.key_column_usage WHERE constraint_name = 'PRIMARY' @@ -526,8 +526,25 @@ module ActiveRecord index.using == :btree || super end + def insert_fixtures(*) + without_sql_mode("NO_AUTO_VALUE_ON_ZERO") { super } + end + private + def without_sql_mode(mode) + result = execute("SELECT @@SESSION.sql_mode") + current_mode = result.first[0] + return yield unless current_mode.include?(mode) + + sql_mode = "REPLACE(@@sql_mode, '#{mode}', '')" + execute("SET @@SESSION.sql_mode = #{sql_mode}") + yield + ensure + sql_mode = "CONCAT(@@sql_mode, ',#{mode}')" + execute("SET @@SESSION.sql_mode = #{sql_mode}") + end + def initialize_type_map(m) super @@ -677,7 +694,7 @@ module ActiveRecord auto_increment: column.auto_increment? } - current_type = select_one("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE '#{column_name}'", "SCHEMA")["Type"] + current_type = exec_query("SHOW COLUMNS FROM #{quote_table_name(table_name)} LIKE #{quote(column_name)}", "SCHEMA").first["Type"] td = create_table_definition(table_name) cd = td.new_column_definition(new_column_name, current_type, options) schema_creation.accept(ChangeColumnDefinition.new(cd, column.name)) @@ -761,7 +778,7 @@ module ActiveRecord # http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430 # (trailing comma because variable_assignments will always have content) if @config[:encoding] - encoding = "NAMES #{@config[:encoding]}" + encoding = "NAMES #{@config[:encoding]}".dup encoding << " COLLATE #{@config[:collation]}" if @config[:collation] encoding << ", " end @@ -787,7 +804,7 @@ module ActiveRecord end def create_table_info(table_name) # :nodoc: - select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"] + exec_query("SHOW CREATE TABLE #{quote_table_name(table_name)}", "SCHEMA").first["Create Table"] end def arel_visitor @@ -837,7 +854,8 @@ module ActiveRecord end end - class MysqlJson < Type::Json # :nodoc: + def version_string + full_version.match(/^(?:5\.5\.5-)?(\d+\.\d+\.\d+)/)[1] end class MysqlString < Type::String # :nodoc: diff --git a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb index 8c67a7a80b..bda482a00f 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/database_statements.rb @@ -13,13 +13,8 @@ module ActiveRecord result end - # Returns an array of arrays containing the field values. - # Order is the same as that returned by +columns+. - def select_rows(arel, name = nil, binds = []) # :nodoc: - select_result(arel, name, binds) do |result| - @connection.next_result while @connection.more_results? - result.to_a - end + def query(sql, name = nil) # :nodoc: + execute(sql, name).to_a end # Executes the SQL statement in the context of this connection. @@ -58,16 +53,6 @@ module ActiveRecord @connection.last_id end - def select_result(arel, name, binds) - arel, binds = binds_from_relation(arel, binds) - sql = to_sql(arel, binds) - if without_prepared_statement?(binds) - execute_and_free(sql, name) { |result| yield result } - else - exec_stmt_and_free(sql, name, binds, cache_stmt: true) { |_, result| yield result } - end - end - def exec_stmt_and_free(sql, name, binds, cache_stmt: false) # make sure we carry over any changes to ActiveRecord::Base.default_timezone that have been # made since we established the connection diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb index 083cd6340f..eea4984680 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb @@ -16,7 +16,7 @@ module ActiveRecord end def visit_ChangeColumnDefinition(o) - change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}" + change_column_sql = "CHANGE #{quote_column_name(o.name)} #{accept(o.column)}".dup add_column_position!(change_column_sql, column_options(o.column)) end @@ -63,7 +63,7 @@ module ActiveRecord def index_in_create(table_name, column_name, options) index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options) - add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})", comment) + add_sql_comment!("#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})".dup, comment) end end end 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 e2ba0ba1a0..a46d9f8cbb 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb @@ -47,13 +47,13 @@ module ActiveRecord def schema_collation(column) if column.collation && table_name = column.table_name @table_collation_cache ||= {} - @table_collation_cache[table_name] ||= select_one("SHOW TABLE STATUS LIKE '#{table_name}'")["Collation"] + @table_collation_cache[table_name] ||= exec_query("SHOW TABLE STATUS LIKE #{quote(table_name)}", "SCHEMA").first["Collation"] column.collation.inspect if column.collation != @table_collation_cache[table_name] end end def extract_expression_for_virtual_column(column) - if mariadb? + if mariadb? && version < "10.2.5" create_table_info = create_table_info(column.table_name) if %r/#{quote_column_name(column.name)} #{Regexp.quote(column.sql_type)}(?: COLLATE \w+)? AS \((?<expression>.+?)\) #{column.extra}/ =~ create_table_info $~[:expression].inspect @@ -64,7 +64,7 @@ module ActiveRecord " WHERE table_schema = #{scope[:schema]}" \ " AND table_name = #{scope[:name]}" \ " AND column_name = #{quote(column.name)}" - select_value(sql, "SCHEMA").inspect + query_value(sql, "SCHEMA").inspect end 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 f9e1e046ea..24f8ff6367 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -45,6 +45,13 @@ module ActiveRecord indexes end + def remove_column(table_name, column_name, type = nil, options = {}) + if foreign_key_exists?(table_name, column: column_name) + remove_foreign_key(table_name, column: column_name) + end + super + end + def internal_string_options_for_primary_key super.tap do |options| if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0") @@ -66,8 +73,8 @@ module ActiveRecord def new_column_from_field(table_name, field) type_metadata = fetch_type_metadata(field[:Type], field[:Extra]) - if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP" - default, default_function = nil, field[:Default] + if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\(\))?\z/i.match?(field[:Default]) + default, default_function = nil, "CURRENT_TIMESTAMP" else default, default_function = field[:Default], nil end @@ -95,7 +102,7 @@ module ActiveRecord def data_source_sql(name = nil, type: nil) scope = quoted_scope(name, type: type) - sql = "SELECT table_name FROM information_schema.tables" + sql = "SELECT table_name FROM information_schema.tables".dup sql << " WHERE table_schema = #{scope[:schema]}" sql << " AND table_name = #{scope[:name]}" if scope[:name] sql << " AND table_type = #{scope[:type]}" if scope[:type] diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb index af55cfe2f6..c5c0a071e7 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb @@ -1,5 +1,5 @@ -require "active_record/connection_adapters/abstract_mysql_adapter" -require "active_record/connection_adapters/mysql/database_statements" +require_relative "abstract_mysql_adapter" +require_relative "mysql/database_statements" gem "mysql2", ">= 0.3.18", "< 0.5" require "mysql2" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb index 705e6063dc..ebf1715ed0 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb @@ -7,30 +7,6 @@ module ActiveRecord PostgreSQL::ExplainPrettyPrinter.new.pp(exec_query(sql, "EXPLAIN", binds)) end - def select_value(arel, name = nil, binds = []) # :nodoc: - select_result(arel, name, binds) do |result| - result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0 - end - end - - def select_values(arel, name = nil, binds = []) # :nodoc: - select_result(arel, name, binds) do |result| - if result.nfields > 0 - result.column_values(0) - else - [] - end - end - end - - # Executes a SELECT query and returns an array of rows. Each row is an - # array of field values. - def select_rows(arel, name = nil, binds = []) # :nodoc: - select_result(arel, name, binds) do |result| - result.values - end - end - # The internal PostgreSQL identifier of the money data type. MONEY_COLUMN_TYPE_OID = 790 #:nodoc: # The internal PostgreSQL identifier of the BYTEA data type. @@ -171,18 +147,14 @@ module ActiveRecord end private + # Returns the current ID of a table's sequence. + def last_insert_id_result(sequence_name) + exec_query("SELECT currval(#{quote(sequence_name)})", "SQL") + end def suppress_composite_primary_key(pk) pk unless pk.is_a?(Array) end - - def select_result(arel, name, binds) - arel, binds = binds_from_relation(arel, binds) - sql = to_sql(arel, binds) - execute_and_clear(sql, name, binds) do |result| - yield result - end - end end end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb index 4098250f3e..6666622c08 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb @@ -1,26 +1,25 @@ -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/json" -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/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/type_map_initializer" +require_relative "oid/type_map_initializer" module ActiveRecord module ConnectionAdapters diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb deleted file mode 100644 index 3c706c27c4..0000000000 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/json.rb +++ /dev/null @@ -1,10 +0,0 @@ -module ActiveRecord - module ConnectionAdapters - module PostgreSQL - module OID # :nodoc: - class Json < Type::Json # :nodoc: - end - end - end - end -end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb index 7a91272d1c..cd7442e751 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/money.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true module ActiveRecord module ConnectionAdapters module PostgreSQL @@ -22,7 +23,7 @@ module ActiveRecord # (3) -$2.55 # (4) ($2.55) - value.sub!(/^\((.+)\)$/, '-\1') # (4) + value = value.sub(/^\((.+)\)$/, '-\1') # (4) case value when /^-?\D+[\d,]+\.\d{2}$/ # (1) value.gsub!(/[^-\d.]/, "") diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb index 5e839228e9..db92333ef7 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/uuid.rb @@ -3,7 +3,7 @@ module ActiveRecord module PostgreSQL module OID # :nodoc: class Uuid < Type::Value # :nodoc: - ACCEPTABLE_UUID = %r{\A\{?([a-fA-F0-9]{4}-?){8}\}?\z}x + ACCEPTABLE_UUID = %r{\A(\{)?([a-fA-F0-9]{4}-?){8}(?(1)\}|)\z} alias_method :serialize, :deserialize diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb index 44eb666965..ee4230c6f2 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb @@ -78,7 +78,7 @@ module ActiveRecord private def lookup_cast_type(sql_type) - super(select_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i) + super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i) end def _quote(value) 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 cb45d7ba6b..a710ea6cc9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -60,7 +60,7 @@ module ActiveRecord # Returns true if schema exists. def schema_exists?(name) - select_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0 + query_value("SELECT COUNT(*) FROM pg_namespace WHERE nspname = #{quote(name)}", "SCHEMA").to_i > 0 end # Verifies existence of an index with a given name. @@ -73,7 +73,7 @@ module ActiveRecord table = quoted_scope(table_name) index = quoted_scope(index_name) - select_value(<<-SQL, "SCHEMA").to_i > 0 + query_value(<<-SQL, "SCHEMA").to_i > 0 SELECT COUNT(*) FROM pg_class t INNER JOIN pg_index d ON t.oid = d.indrelid @@ -163,7 +163,7 @@ module ActiveRecord def table_comment(table_name) # :nodoc: scope = quoted_scope(table_name, type: "BASE TABLE") if scope[:name] - select_value(<<-SQL.strip_heredoc, "SCHEMA") + query_value(<<-SQL.strip_heredoc, "SCHEMA") SELECT pg_catalog.obj_description(c.oid, 'pg_class') FROM pg_catalog.pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -176,32 +176,32 @@ module ActiveRecord # Returns the current database name. def current_database - select_value("SELECT current_database()", "SCHEMA") + query_value("SELECT current_database()", "SCHEMA") end # Returns the current schema name. def current_schema - select_value("SELECT current_schema", "SCHEMA") + query_value("SELECT current_schema", "SCHEMA") end # Returns the current database encoding format. def encoding - select_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA") + query_value("SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = current_database()", "SCHEMA") end # Returns the current database collation. def collation - select_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA") + query_value("SELECT datcollate FROM pg_database WHERE datname = current_database()", "SCHEMA") end # Returns the current database ctype. def ctype - select_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA") + query_value("SELECT datctype FROM pg_database WHERE datname = current_database()", "SCHEMA") end # Returns an array of schema names. def schema_names - select_values(<<-SQL, "SCHEMA") + query_values(<<-SQL, "SCHEMA") SELECT nspname FROM pg_namespace WHERE nspname !~ '^pg_.*' @@ -234,12 +234,12 @@ module ActiveRecord # Returns the active schema search path. def schema_search_path - @schema_search_path ||= select_value("SHOW search_path", "SCHEMA") + @schema_search_path ||= query_value("SHOW search_path", "SCHEMA") end # Returns the current client message level. def client_min_messages - select_value("SHOW client_min_messages", "SCHEMA") + query_value("SHOW client_min_messages", "SCHEMA") end # Set the client message level. @@ -257,7 +257,7 @@ module ActiveRecord end def serial_sequence(table, column) - select_value("SELECT pg_get_serial_sequence('#{table}', '#{column}')", "SCHEMA") + query_value("SELECT pg_get_serial_sequence(#{quote(table)}, #{quote(column)})", "SCHEMA") end # Sets the sequence of a table's primary key to the specified value. @@ -268,7 +268,7 @@ module ActiveRecord if sequence quoted_sequence = quote_table_name(sequence) - select_value("SELECT setval('#{quoted_sequence}', #{value})", "SCHEMA") + query_value("SELECT setval(#{quote(quoted_sequence)}, #{value})", "SCHEMA") else @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger end @@ -290,18 +290,16 @@ module ActiveRecord if pk && sequence quoted_sequence = quote_table_name(sequence) - max_pk = select_value("select MAX(#{quote_column_name pk}) from #{quote_table_name(table)}") + max_pk = query_value("SELECT MAX(#{quote_column_name pk}) FROM #{quote_table_name(table)}", "SCHEMA") if max_pk.nil? if postgresql_version >= 100000 - minvalue = select_value("SELECT seqmin from pg_sequence where seqrelid = '#{quoted_sequence}'::regclass") + minvalue = query_value("SELECT seqmin FROM pg_sequence WHERE seqrelid = #{quote(quoted_sequence)}::regclass", "SCHEMA") else - minvalue = select_value("SELECT min_value FROM #{quoted_sequence}") + minvalue = query_value("SELECT min_value FROM #{quoted_sequence}", "SCHEMA") end end - select_value(<<-end_sql, "SCHEMA") - SELECT setval('#{quoted_sequence}', #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false}) - end_sql + query_value("SELECT setval(#{quote(quoted_sequence)}, #{max_pk ? max_pk : minvalue}, #{max_pk ? true : false})", "SCHEMA") end end @@ -325,7 +323,7 @@ module ActiveRecord AND seq.relnamespace = nsp.oid AND cons.contype = 'p' AND dep.classid = 'pg_class'::regclass - AND dep.refobjid = '#{quote_table_name(table)}'::regclass + AND dep.refobjid = #{quote(quote_table_name(table))}::regclass end_sql if result.nil? || result.empty? @@ -343,7 +341,7 @@ module ActiveRecord JOIN pg_attrdef def ON (adrelid = attrelid AND adnum = attnum) JOIN pg_constraint cons ON (conrelid = adrelid AND adnum = conkey[1]) JOIN pg_namespace nsp ON (t.relnamespace = nsp.oid) - WHERE t.oid = '#{quote_table_name(table)}'::regclass + WHERE t.oid = #{quote(quote_table_name(table))}::regclass AND cons.contype = 'p' AND pg_get_expr(def.adbin, def.adrelid) ~* 'nextval|uuid_generate' end_sql @@ -360,7 +358,7 @@ module ActiveRecord end def primary_keys(table_name) # :nodoc: - select_values(<<-SQL.strip_heredoc, "SCHEMA") + query_values(<<-SQL.strip_heredoc, "SCHEMA") SELECT a.attname FROM ( SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx @@ -408,7 +406,7 @@ module ActiveRecord 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}" + sql = "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quoted_column_name} TYPE #{sql_type}".dup if options[:collation] sql << " COLLATE \"#{options[:collation]}\"" end @@ -511,7 +509,7 @@ module ActiveRecord def foreign_keys(table_name) scope = quoted_scope(table_name) - fk_info = select_all(<<-SQL.strip_heredoc, "SCHEMA") + 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 FROM pg_constraint c JOIN pg_class t1 ON c.conrelid = t1.oid @@ -568,7 +566,7 @@ module ActiveRecord super end - sql << "[]" if array && type != :primary_key + sql = "#{sql}[]" if array && type != :primary_key sql end @@ -637,7 +635,7 @@ module ActiveRecord scope = quoted_scope(name, type: type) scope[:type] ||= "'r','v','m'" # (r)elation/table, (v)iew, (m)aterialized view - sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace" + sql = "SELECT c.relname FROM pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace".dup sql << " WHERE n.nspname = #{scope[:schema]}" sql << " AND c.relname = #{scope[:name]}" if scope[:name] sql << " AND c.relkind IN (#{scope[:type]})" diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 5287dd6a51..8baef19030 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -2,20 +2,20 @@ gem "pg", "~> 0.18" require "pg" -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" +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" module ActiveRecord module ConnectionHandling # :nodoc: @@ -312,14 +312,14 @@ module ActiveRecord 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") end - select_value("SELECT pg_try_advisory_lock(#{lock_id});") + 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") end - select_value("SELECT pg_advisory_unlock(#{lock_id})") + query_value("SELECT pg_advisory_unlock(#{lock_id})") end def enable_extension(name) @@ -336,15 +336,14 @@ module ActiveRecord 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 = 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 end def extensions if supports_extensions? - exec_query("SELECT extname from pg_extension", "SCHEMA").cast_values + exec_query("SELECT extname FROM pg_extension", "SCHEMA").cast_values else super end @@ -352,14 +351,14 @@ module ActiveRecord # Returns the configured supported identifier length supported by PostgreSQL def table_alias_length - @max_identifier_length ||= select_value("SHOW max_identifier_length", "SCHEMA").to_i + @max_identifier_length ||= query_value("SHOW max_identifier_length", "SCHEMA").to_i end alias index_name_length table_alias_length # Set the authorized user for this session def session_auth=(user) clear_cache! - exec_query "SET SESSION AUTHORIZATION #{user}" + execute("SET SESSION AUTHORIZATION #{user}") end def use_insert_returning? @@ -723,11 +722,6 @@ module ActiveRecord end end - # Returns the current ID of a table's sequence. - def last_insert_id_result(sequence_name) - exec_query("SELECT currval('#{sequence_name}')", "SQL") - end - # Returns the list of a table's column names, data types, and default values. # # The underlying query is roughly: 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 e02491edb6..31e83f9260 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_statements.rb @@ -11,7 +11,7 @@ module ActiveRecord end exec_query("PRAGMA index_list(#{quote_table_name(table_name)})", "SCHEMA").map do |row| - index_sql = select_value(<<-SQL, "SCHEMA") + index_sql = query_value(<<-SQL, "SCHEMA") SELECT sql FROM sqlite_master WHERE name = #{quote(row['name'])} AND type = 'index' @@ -67,7 +67,7 @@ module ActiveRecord scope = quoted_scope(name, type: type) scope[:type] ||= "'table','view'" - sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'" + sql = "SELECT name FROM sqlite_master WHERE name <> 'sqlite_sequence'".dup sql << " AND name = #{scope[:name]}" if scope[:name] sql << " AND type IN (#{scope[:type]})" sql diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb index 7233325d5a..04129841e4 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb @@ -1,11 +1,11 @@ -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" +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" gem "sqlite3", "~> 1.3.6" require "sqlite3" @@ -169,7 +169,7 @@ module ActiveRecord # REFERENTIAL INTEGRITY ==================================== def disable_referential_integrity # :nodoc: - old = select_value("PRAGMA foreign_keys") + old = query_value("PRAGMA foreign_keys") begin execute("PRAGMA foreign_keys = OFF") @@ -337,7 +337,7 @@ module ActiveRecord alias :add_belongs_to :add_reference def foreign_keys(table_name) - fk_info = select_all("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA") + fk_info = exec_query("PRAGMA foreign_key_list(#{quote(table_name)})", "SCHEMA") fk_info.map do |row| options = { column: row["from"], @@ -349,6 +349,12 @@ module ActiveRecord end end + def insert_fixtures(rows, table_name) + rows.each do |row| + insert_fixture(row, table_name) + end + end + private def table_structure(table_name) @@ -437,7 +443,7 @@ module ActiveRecord end def sqlite_version - @sqlite_version ||= SQLite3Adapter::Version.new(select_value("SELECT sqlite_version(*)")) + @sqlite_version ||= SQLite3Adapter::Version.new(query_value("SELECT sqlite_version(*)")) end def translate_exception(exception, message) diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 8f78330d4a..198c712abc 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -56,8 +56,7 @@ module ActiveRecord # :singleton-method: # Determines whether to use Time.utc (using :utc) or Time.local (using :local) when pulling # dates and times from the database. This is set to :utc by default. - mattr_accessor :default_timezone, instance_writer: false - self.default_timezone = :utc + mattr_accessor :default_timezone, instance_writer: false, default: :utc ## # :singleton-method: @@ -67,16 +66,14 @@ module ActiveRecord # ActiveRecord::Schema file which can be loaded into any database that # supports migrations. Use :ruby if you want to have different database # adapters for, e.g., your development and test environments. - mattr_accessor :schema_format, instance_writer: false - self.schema_format = :ruby + mattr_accessor :schema_format, instance_writer: false, default: :ruby ## # :singleton-method: # Specifies if an error should be raised if the query has an order being # ignored when doing batch queries. Useful in applications where the # scope being ignored is error-worthy, rather than a warning. - mattr_accessor :error_on_ignored_order, instance_writer: false - self.error_on_ignored_order = false + mattr_accessor :error_on_ignored_order, instance_writer: false, default: false def self.error_on_ignored_order_or_limit ActiveSupport::Deprecation.warn(<<-MSG.squish) @@ -101,8 +98,7 @@ module ActiveRecord ## # :singleton-method: # Specify whether or not to use timestamps for migration versions - mattr_accessor :timestamped_migrations, instance_writer: false - self.timestamped_migrations = true + mattr_accessor :timestamped_migrations, instance_writer: false, default: true ## # :singleton-method: @@ -110,8 +106,7 @@ module ActiveRecord # db:migrate rake task. This is true by default, which is useful for the # development environment. This should ideally be false in the production # environment where dumping schema is rarely needed. - mattr_accessor :dump_schema_after_migration, instance_writer: false - self.dump_schema_after_migration = true + mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true ## # :singleton-method: @@ -120,8 +115,7 @@ module ActiveRecord # schema_search_path are dumped. Use :all to dump all schemas regardless # of schema_search_path, or a string of comma separated schemas for a # custom list. - mattr_accessor :dump_schemas, instance_writer: false - self.dump_schemas = :schema_search_path + mattr_accessor :dump_schemas, instance_writer: false, default: :schema_search_path ## # :singleton-method: @@ -130,7 +124,6 @@ module ActiveRecord # be used to identify queries which load thousands of records and # potentially cause memory bloat. mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false - self.warn_on_records_fetched_greater_than = nil mattr_accessor :maintain_test_schema, instance_accessor: false diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index 18fac5af1b..60d4fb70e0 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -105,7 +105,7 @@ module ActiveRecord class WrappedDatabaseException < StatementInvalid end - # Raised when a record cannot be inserted because it would violate a uniqueness constraint. + # Raised when a record cannot be inserted or updated because it would violate a uniqueness constraint. class RecordNotUnique < WrappedDatabaseException end diff --git a/activerecord/lib/active_record/explain.rb b/activerecord/lib/active_record/explain.rb index 8f7ae2c33c..eff5990f3a 100644 --- a/activerecord/lib/active_record/explain.rb +++ b/activerecord/lib/active_record/explain.rb @@ -1,4 +1,4 @@ -require "active_record/explain_registry" +require_relative "explain_registry" module ActiveRecord module Explain @@ -16,7 +16,7 @@ module ActiveRecord # Returns a formatted string ready to be logged. def exec_explain(queries) # :nodoc: str = queries.map do |sql, binds| - msg = "EXPLAIN for: #{sql}" + msg = "EXPLAIN for: #{sql}".dup unless binds.empty? msg << " " msg << binds.map { |attr| render_bind(attr) }.inspect diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb index abd8cfc8f2..928720d011 100644 --- a/activerecord/lib/active_record/explain_subscriber.rb +++ b/activerecord/lib/active_record/explain_subscriber.rb @@ -1,5 +1,5 @@ require "active_support/notifications" -require "active_record/explain_registry" +require_relative "explain_registry" module ActiveRecord class ExplainSubscriber # :nodoc: diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index bad6542be2..c9e97d9d2b 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -4,8 +4,8 @@ require "zlib" require "set" require "active_support/dependencies" require "active_support/core_ext/digest/uuid" -require "active_record/fixture_set/file" -require "active_record/errors" +require_relative "fixture_set/file" +require_relative "errors" module ActiveRecord class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc: @@ -492,8 +492,7 @@ module ActiveRecord end end - cattr_accessor :all_loaded_fixtures - self.all_loaded_fixtures = {} + cattr_accessor :all_loaded_fixtures, default: {} class ClassCache def initialize(class_names, config) @@ -568,9 +567,7 @@ module ActiveRecord end table_rows.each do |fixture_set_name, rows| - rows.each do |row| - conn.insert_fixture(row, fixture_set_name) - end + conn.insert_fixtures(rows, fixture_set_name) end # Cap primary key sequences to max(pk). diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb index 5776807507..1753322274 100644 --- a/activerecord/lib/active_record/inheritance.rb +++ b/activerecord/lib/active_record/inheritance.rb @@ -245,7 +245,7 @@ module ActiveRecord def ensure_proper_type klass = self.class if klass.finder_needs_type_condition? - write_attribute(klass.inheritance_column, klass.sti_name) + _write_attribute(klass.inheritance_column, klass.sti_name) end end end diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 25ee9d6bfe..89e5d153b8 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -1,5 +1,5 @@ -require "active_record/scoping/default" -require "active_record/scoping/named" +require_relative "scoping/default" +require_relative "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/locking/optimistic.rb b/activerecord/lib/active_record/locking/optimistic.rb index 3c7110369b..522da6a571 100644 --- a/activerecord/lib/active_record/locking/optimistic.rb +++ b/activerecord/lib/active_record/locking/optimistic.rb @@ -62,8 +62,8 @@ module ActiveRecord def increment_lock lock_col = self.class.locking_column - previous_lock_value = send(lock_col).to_i - send(lock_col + "=", previous_lock_value + 1) + previous_lock_value = send(lock_col) + send("#{lock_col}=", previous_lock_value + 1) end def _create_record(attribute_names = self.attribute_names, *) @@ -107,7 +107,8 @@ module ActiveRecord # If something went wrong, revert the locking_column value. rescue Exception - send(lock_col + "=", previous_lock_value.to_i) + send("#{lock_col}=", previous_lock_value.to_i) + raise end end @@ -127,7 +128,7 @@ module ActiveRecord if locking_enabled? locking_column = self.class.locking_column - relation = relation.where(locking_column => _read_attribute(locking_column)) + relation = relation.where(locking_column => read_attribute_before_type_cast(locking_column)) end relation diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb index 2297c77835..e39ca5f6dc 100644 --- a/activerecord/lib/active_record/log_subscriber.rb +++ b/activerecord/lib/active_record/log_subscriber.rb @@ -29,7 +29,7 @@ module ActiveRecord binds = nil unless (payload[:binds] || []).empty? - casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds]) + casted_params = type_casted_binds(payload[:type_casted_binds]) binds = " " + payload[:binds].zip(casted_params).map { |attr, value| render_bind(attr, value) }.inspect @@ -42,9 +42,8 @@ module ActiveRecord end private - - def type_casted_binds(binds, casted_binds) - casted_binds || ActiveRecord::Base.connection.type_casted_binds(binds) + def type_casted_binds(casted_binds) + casted_binds.respond_to?(:call) ? casted_binds.call : casted_binds end def render_bind(attr, value) diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 51c82f4ced..42220b9a5e 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -157,7 +157,7 @@ module ActiveRecord class ProtectedEnvironmentError < ActiveRecordError #:nodoc: def initialize(env = "production") - msg = "You are attempting to run a destructive action against your '#{env}' database.\n" + msg = "You are attempting to run a destructive action against your '#{env}' database.\n".dup msg << "If you are sure you want to continue, run the same command with the environment variable:\n" msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1" super(msg) @@ -166,7 +166,7 @@ module ActiveRecord class EnvironmentMismatchError < ActiveRecordError def initialize(current: nil, stored: nil) - msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n" + msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n".dup msg << "You are running in `#{ current }` environment. " msg << "If you are sure you want to continue, first set the environment using:\n\n" msg << " bin/rails db:environment:set" @@ -863,15 +863,17 @@ module ActiveRecord source_migrations.each do |migration| source = File.binread(migration.filename) inserted_comment = "# This migration comes from #{scope} (originally #{migration.version})\n" - if /\A#.*\b(?:en)?coding:\s*\S+/ =~ source + magic_comments = "".dup + loop do # If we have a magic comment in the original migration, # insert our comment after the first newline(end of the magic comment line) # so the magic keep working. # Note that magic comments must be at the first line(except sh-bang). - source[/\n/] = "\n#{inserted_comment}" - else - source = "#{inserted_comment}#{source}" + source.sub!(/\A(?:#.*\b(?:en)?coding:\s*\S+|#\s*frozen_string_literal:\s*(?:true|false)).*\n/) do |magic_comment| + magic_comments << magic_comment; "" + end || break end + source = "#{magic_comments}#{inserted_comment}#{source}" if duplicate = destination_migrations.detect { |m| m.name == migration.name } if options[:on_skip] && duplicate.scope != scope.to_s @@ -1239,7 +1241,7 @@ module ActiveRecord record_version_state_after_migrating(migration.version) end rescue => e - msg = "An error has occurred, " + msg = "An error has occurred, ".dup msg << "this and " if use_transaction?(migration) msg << "all later migrations canceled:\n\n#{e}" raise StandardError, msg, e.backtrace diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb index 1179a60e9b..14e0f5bff7 100644 --- a/activerecord/lib/active_record/model_schema.rb +++ b/activerecord/lib/active_record/model_schema.rb @@ -446,7 +446,11 @@ module ActiveRecord def load_schema return if schema_loaded? @load_schema_monitor.synchronize do - load_schema! unless defined?(@columns_hash) && @columns_hash + return if defined?(@columns_hash) && @columns_hash + + load_schema! + + @schema_loaded = true end end @@ -460,8 +464,6 @@ module ActiveRecord user_provided_default: false ) end - - @schema_loaded = true end def reload_schema_from_cache diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index f652c7c3a1..a9509e562a 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -333,7 +333,7 @@ module ActiveRecord updated_count = self.class.unscoped.where(self.class.primary_key => id).update_all(attributes) attributes.each do |k, v| - raw_write_attribute(k, v) + write_attribute_without_type_cast(k, v) end updated_count == 1 @@ -526,7 +526,7 @@ module ActiveRecord if locking_enabled? locking_column = self.class.locking_column - scope = scope.where(locking_column => _read_attribute(locking_column)) + scope = scope.where(locking_column => read_attribute_before_type_cast(locking_column)) changes[locking_column] = increment_lock end diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index ec246e97bc..e4c2e1f86f 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -5,20 +5,20 @@ module ActiveRecord # Enable the query cache within the block if Active Record is configured. # If it's not, it will execute the given block. def cache(&block) - if connected? - connection.cache(&block) - else + if configurations.empty? yield + else + connection.cache(&block) end end # Disable the query cache within the block if Active Record is configured. # If it's not, it will execute the given block. def uncached(&block) - if connected? - connection.uncached(&block) - else + if configurations.empty? yield + else + connection.uncached(&block) end end end diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb index 73518ca144..9cca103a18 100644 --- a/activerecord/lib/active_record/railtie.rb +++ b/activerecord/lib/active_record/railtie.rb @@ -48,8 +48,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 "active_record/railties/console_sandbox" if app.sandbox? - require "active_record/base" + require_relative "railties/console_sandbox" if app.sandbox? + require_relative "base" unless ActiveSupport::Logger.logger_outputs_to?(Rails.logger, STDERR, STDOUT) console = ActiveSupport::Logger.new(STDERR) Rails.logger.extend ActiveSupport::Logger.broadcast console @@ -57,7 +57,7 @@ module ActiveRecord end runner do - require "active_record/base" + require_relative "base" end initializer "active_record.initialize_timezone" do @@ -101,7 +101,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 "active_record/relation/record_fetch_warning" + require_relative "relation/record_fetch_warning" end end end @@ -139,7 +139,7 @@ end_warning # Expose database runtime to controller for logging. initializer "active_record.log_runtime" do - require "active_record/railties/controller_runtime" + require_relative "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 8658188623..4030cdc158 100644 --- a/activerecord/lib/active_record/railties/controller_runtime.rb +++ b/activerecord/lib/active_record/railties/controller_runtime.rb @@ -1,5 +1,5 @@ require "active_support/core_ext/module/attr_internal" -require "active_record/log_subscriber" +require_relative "../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 711099e9e1..abc7323341 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -187,7 +187,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 "active_record/fixtures" + require_relative "../fixtures" base_dir = ActiveRecord::Tasks::DatabaseTasks.fixtures_path @@ -209,7 +209,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 "active_record/fixtures" + require_relative "../fixtures" label, id = ENV["LABEL"], ENV["ID"] raise "LABEL or ID required" if label.blank? && id.blank? @@ -235,7 +235,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 "active_record/schema_dumper" + require_relative "../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/reflection.rb b/activerecord/lib/active_record/reflection.rb index e8ee8279fd..a453ca55c7 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -171,7 +171,7 @@ module ActiveRecord JoinKeys = Struct.new(:key, :foreign_key) # :nodoc: def join_keys - get_join_keys klass + @join_keys ||= get_join_keys(klass) end # Returns a list of scopes that should be applied for this Reflection @@ -185,10 +185,34 @@ module ActiveRecord end deprecate :scope_chain + def build_join_constraint(table, foreign_table) + key = join_keys.key + foreign_key = join_keys.foreign_key + + constraint = table[key].eq(foreign_table[foreign_key]) + + if klass.finder_needs_type_condition? + table.create_and([constraint, klass.send(:type_condition, table)]) + else + constraint + end + end + + def join_scope(table, foreign_klass) + predicate_builder = predicate_builder(table) + scope_chain_items = join_scopes(table, predicate_builder) + klass_scope = klass_join_scope(table, predicate_builder) + + if type + klass_scope.where!(type => foreign_klass.base_class.sti_name) + end + + scope_chain_items.inject(klass_scope, &:merge!) + end + def join_scopes(table, predicate_builder) # :nodoc: if scope - [ActiveRecord::Relation.create(klass, table, predicate_builder) - .instance_exec(&scope)] + [build_scope(table, predicate_builder).instance_exec(&scope)] else [] end @@ -200,12 +224,7 @@ module ActiveRecord scope.joins_values = scope.left_outer_joins_values = [].freeze } else - relation = ActiveRecord::Relation.create( - klass, - table, - predicate_builder, - ) - klass.send(:build_default_scope, relation) + klass.default_scoped(build_scope(table, predicate_builder)) end end @@ -287,12 +306,19 @@ module ActiveRecord JoinKeys.new(join_pk(association_klass), join_fk) end + def build_scope(table, predicate_builder = predicate_builder(table)) + Relation.create(klass, table, predicate_builder) + end + protected def actual_source_reflection # FIXME: this is a horrible name self end private + def predicate_builder(table) + PredicateBuilder.new(TableMetadata.new(klass, table)) + end def join_pk(_) foreign_key @@ -570,7 +596,7 @@ module ActiveRecord end VALID_AUTOMATIC_INVERSE_MACROS = [:has_many, :has_one, :belongs_to] - INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :polymorphic, :foreign_key] + INVALID_AUTOMATIC_INVERSE_OPTIONS = [:conditions, :through, :foreign_key] def add_as_source(seed) seed @@ -641,9 +667,8 @@ module ActiveRecord # us from being able to guess the inverse automatically. First, the # <tt>inverse_of</tt> option cannot be set to false. Second, we must # have <tt>has_many</tt>, <tt>has_one</tt>, <tt>belongs_to</tt> associations. - # Third, we must not have options such as <tt>:polymorphic</tt> or - # <tt>:foreign_key</tt> which prevent us from correctly guessing the - # inverse association. + # Third, we must not have options such as <tt>:foreign_key</tt> + # which prevent us from correctly guessing the inverse association. # # Anything with a scope can additionally ruin our attempt at finding an # inverse, so we exclude reflections with scopes. @@ -746,10 +771,6 @@ module ActiveRecord end class HasAndBelongsToManyReflection < AssociationReflection # :nodoc: - def initialize(name, scope, options, active_record) - super - end - def macro; :has_and_belongs_to_many; end def collection? diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 7a8f9abb36..76cf47a3ed 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -6,7 +6,7 @@ module ActiveRecord :extending, :unscope] SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :reordering, - :reverse_order, :distinct, :create_with] + :reverse_order, :distinct, :create_with, :skip_query_cache] CLAUSE_METHODS = [:where, :having, :from] INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having] @@ -18,7 +18,7 @@ module ActiveRecord attr_reader :table, :klass, :loaded, :predicate_builder alias :model :klass alias :loaded? :loaded - alias :locked? :locked + alias :locked? :lock_value def initialize(klass, table, predicate_builder, values = {}) @klass = klass @@ -333,7 +333,7 @@ module ActiveRecord # Please check unscoped if you want to remove all previous scopes (including # the default_scope) during the execution of a block. def scoping - previous, klass.current_scope = klass.current_scope, self + previous, klass.current_scope = klass.current_scope(true), self yield ensure klass.current_scope = previous @@ -657,20 +657,32 @@ module ActiveRecord end def exec_queries(&block) - @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze - - preload = preload_values - preload += includes_values unless eager_loading? - preloader = nil - preload.each do |associations| - preloader ||= build_preloader - preloader.preload @records, associations - end + skip_query_cache_if_necessary do + @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes, &block).freeze + + preload = preload_values + preload += includes_values unless eager_loading? + preloader = nil + preload.each do |associations| + preloader ||= build_preloader + preloader.preload @records, associations + end - @records.each(&:readonly!) if readonly_value + @records.each(&:readonly!) if readonly_value - @loaded = true - @records + @loaded = true + @records + end + end + + def skip_query_cache_if_necessary + if skip_query_cache_value + uncached do + yield + end + else + yield + end end def build_preloader diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb index 13a2c3f511..c7e4f8a88a 100644 --- a/activerecord/lib/active_record/relation/batches.rb +++ b/activerecord/lib/active_record/relation/batches.rb @@ -1,4 +1,4 @@ -require "active_record/relation/batches/batch_enumerator" +require_relative "batches/batch_enumerator" module ActiveRecord module Batches @@ -209,6 +209,7 @@ module ActiveRecord relation = relation.reorder(batch_order).limit(batch_limit) relation = apply_limits(relation, start, finish) + relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching batch_relation = relation loop do diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb index c562f214c9..aaba6c71f2 100644 --- a/activerecord/lib/active_record/relation/calculations.rb +++ b/activerecord/lib/active_record/relation/calculations.rb @@ -41,7 +41,7 @@ module ActiveRecord unless column_name.nil? ActiveSupport::Deprecation.warn \ "When `count' is called with a block, it ignores other arguments. " \ - "This behavior is now deprecated and will result in an ArgumentError in Rails 5.3." + "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0." end return super() @@ -86,7 +86,7 @@ module ActiveRecord unless column_name.nil? ActiveSupport::Deprecation.warn \ "When `sum' is called with a block, it ignores other arguments. " \ - "This behavior is now deprecated and will result in an ArgumentError in Rails 5.3." + "This behavior is now deprecated and will result in an ArgumentError in Rails 6.0." end return super() @@ -184,7 +184,7 @@ module ActiveRecord relation.select_values = column_names.map { |cn| @klass.has_attribute?(cn) || @klass.attribute_alias?(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, bound_attributes) } result.cast_values(klass.attribute_types) end end @@ -260,7 +260,7 @@ module ActiveRecord query_builder = relation.arel end - result = @klass.connection.select_all(query_builder, nil, bound_attributes) + result = skip_query_cache_if_necessary { @klass.connection.select_all(query_builder, nil, bound_attributes) } row = result.first value = row && row.values.first type = result.column_types.fetch(column_alias) do @@ -311,7 +311,7 @@ module ActiveRecord relation.group_values = group_fields relation.select_values = select_values - calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes) + calculated_data = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, nil, relation.bound_attributes) } if association key_ids = calculated_data.collect { |row| row[group_aliases.first] } diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index dada0fddf8..48c4dcdef4 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -44,8 +44,6 @@ module ActiveRecord delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :connection, :columns_hash, to: :klass - delegate :ast, :locked, to: :arel - module ClassSpecificRelation # :nodoc: extend ActiveSupport::Concern @@ -91,6 +89,8 @@ module ActiveRecord self.class.delegate_to_scoped_klass(method) scoping { @klass.public_send(method, *args, &block) } elsif arel.respond_to?(method) + ActiveSupport::Deprecation.warn \ + "Delegating #{method} to arel is deprecated and will be removed in Rails 6.0." self.class.delegate method, to: :arel arel.public_send(method, *args, &block) else @@ -115,15 +115,5 @@ module ActiveRecord def respond_to_missing?(method, _) super || @klass.respond_to?(method) || arel.respond_to?(method) end - - def method_missing(method, *args, &block) - if @klass.respond_to?(method) - scoping { @klass.public_send(method, *args, &block) } - elsif arel.respond_to?(method) - arel.public_send(method, *args, &block) - else - super - end - end end end diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb index 1d661fa8ed..ac0b4f597e 100644 --- a/activerecord/lib/active_record/relation/finder_methods.rb +++ b/activerecord/lib/active_record/relation/finder_methods.rb @@ -147,8 +147,7 @@ module ActiveRecord def last(limit = nil) return find_last(limit) if loaded? || limit_value - result = limit(limit) - result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key + result = ordered_relation.limit(limit) result = result.reverse_order! limit ? result.reverse : result.first @@ -316,7 +315,7 @@ module ActiveRecord relation = construct_relation_for_exists(relation, conditions) - connection.select_value(relation, "#{name} Exists", relation.bound_attributes) ? true : false + skip_query_cache_if_necessary { connection.select_value(relation.arel, "#{name} Exists", relation.bound_attributes) } ? true : false rescue ::RangeError false end @@ -335,14 +334,14 @@ module ActiveRecord name = @klass.name if ids.nil? - error = "Couldn't find #{name}" + error = "Couldn't find #{name}".dup error << " with#{conditions}" if conditions raise RecordNotFound.new(error, name) elsif Array(ids).size == 1 error = "Couldn't find #{name} with '#{key}'=#{ids}#{conditions}" raise RecordNotFound.new(error, name, key, ids) else - error = "Couldn't find all #{name.pluralize} with '#{key}': " + error = "Couldn't find all #{name.pluralize} with '#{key}': ".dup error << "(#{ids.join(", ")})#{conditions} (found #{result_size} results, but was looking for #{expected_size})" raise RecordNotFound.new(error, name, primary_key, ids) @@ -377,8 +376,7 @@ module ActiveRecord if ActiveRecord::NullRelation === relation [] else - arel = relation.arel - rows = connection.select_all(arel, "SQL", relation.bound_attributes) + rows = skip_query_cache_if_necessary { connection.select_all(relation.arel, "SQL", relation.bound_attributes) } join_dependency.instantiate(rows, aliases) end end @@ -425,9 +423,8 @@ module ActiveRecord "#{quoted_table_name}.#{quoted_primary_key}", relation.order_values) relation = relation.except(:select).select(values).distinct! - arel = relation.arel - id_rows = @klass.connection.select_all(arel, "SQL", relation.bound_attributes) + id_rows = skip_query_cache_if_necessary { @klass.connection.select_all(relation.arel, "SQL", relation.bound_attributes) } id_rows.map { |row| row[primary_key] } end @@ -535,11 +532,7 @@ module ActiveRecord if loaded? records[index, limit] || [] else - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end + relation = ordered_relation if limit_value.nil? || index < limit_value relation = relation.offset(offset_index + index) unless index.zero? @@ -554,11 +547,7 @@ module ActiveRecord if loaded? records[-index] else - relation = if order_values.empty? && primary_key - order(arel_attribute(primary_key).asc) - else - self - end + relation = ordered_relation relation.to_a[-index] # TODO: can be made more performant on large result sets by @@ -572,5 +561,13 @@ module ActiveRecord def find_last(limit) limit ? records.last(limit) : records.last end + + def ordered_relation + if order_values.empty? && primary_key + order(arel_attribute(primary_key).asc) + else + self + end + end end end diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb index a6309e0b5c..eb80c9a00d 100644 --- a/activerecord/lib/active_record/relation/predicate_builder.rb +++ b/activerecord/lib/active_record/relation/predicate_builder.rb @@ -107,21 +107,26 @@ module ActiveRecord first = value.begin last = value.end unless first.respond_to?(:infinite?) && first.infinite? - binds << build_bind_param(column_name, first) + binds << build_bind_attribute(column_name, first) first = Arel::Nodes::BindParam.new end unless last.respond_to?(:infinite?) && last.infinite? - binds << build_bind_param(column_name, last) + binds << build_bind_attribute(column_name, last) last = Arel::Nodes::BindParam.new end result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?) + when value.is_a?(Relation) + binds.concat(value.bound_attributes) else if can_be_bound?(column_name, value) - result[column_name] = Arel::Nodes::BindParam.new - binds << build_bind_param(column_name, value) - elsif value.is_a?(Relation) - binds.concat(value.bound_attributes) + bind_attribute = build_bind_attribute(column_name, value) + if value.is_a?(StatementCache::Substitute) || !bind_attribute.value_for_database.nil? + result[column_name] = Arel::Nodes::BindParam.new + binds << bind_attribute + else + result[column_name] = nil + end end end end @@ -164,17 +169,17 @@ module ActiveRecord end end - def build_bind_param(column_name, value) + def build_bind_attribute(column_name, value) Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name)) end end end -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_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 "active_record/relation/predicate_builder/association_query_value" -require "active_record/relation/predicate_builder/polymorphic_array_value" +require_relative "predicate_builder/association_query_value" +require_relative "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 a68e508fcc..0e1f64775d 100644 --- a/activerecord/lib/active_record/relation/query_attribute.rb +++ b/activerecord/lib/active_record/relation/query_attribute.rb @@ -1,4 +1,4 @@ -require "active_record/attribute" +require_relative "../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 79e65baae5..79495ead91 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -1,7 +1,7 @@ -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_relative "from_clause" +require_relative "query_attribute" +require_relative "where_clause" +require_relative "where_clause_factory" require "active_model/forbidden_attributes_protection" module ActiveRecord @@ -913,6 +913,11 @@ module ActiveRecord self end + def skip_query_cache! # :nodoc: + self.skip_query_cache_value = true + self + end + # Returns the Arel object associated with the relation. def arel # :nodoc: @arel ||= build_arel @@ -1122,7 +1127,7 @@ module ActiveRecord validate_order_args(order_args) references = order_args.grep(String) - references.map! { |arg| arg =~ /^([a-zA-Z]\w*)\.(\w+)/ && $1 }.compact! + references.map! { |arg| arg =~ /^\W?(\w+)\W?\./ && $1 }.compact! references!(references) if references.any? # if a symbol is given we prepend the quoted table name @@ -1167,7 +1172,7 @@ module ActiveRecord end end - STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having] + STRUCTURAL_OR_METHODS = Relation::VALUE_METHODS - [:extending, :where, :having, :unscope] def structurally_incompatible_values_for_or(other) STRUCTURAL_OR_METHODS.reject do |method| get_value(method) == other.get_value(method) diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb index ada89b5ec3..ddf7f825c1 100644 --- a/activerecord/lib/active_record/relation/spawn_methods.rb +++ b/activerecord/lib/active_record/relation/spawn_methods.rb @@ -1,6 +1,6 @@ require "active_support/core_ext/hash/except" require "active_support/core_ext/hash/slice" -require "active_record/relation/merger" +require_relative "merger" module ActiveRecord module SpawnMethods diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb index 04bee73e8f..b862dd56a5 100644 --- a/activerecord/lib/active_record/relation/where_clause_factory.rb +++ b/activerecord/lib/active_record/relation/where_clause_factory.rb @@ -57,7 +57,7 @@ module ActiveRecord else column = klass.column_for_attribute(attribute) - binds << predicate_builder.send(:build_bind_param, attribute, value) + binds << predicate_builder.send(:build_bind_attribute, attribute, value) value = Arel::Nodes::BindParam.new predicate = if options[:case_sensitive] diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb index 24b81aabc8..66a2846f3a 100644 --- a/activerecord/lib/active_record/schema_dumper.rb +++ b/activerecord/lib/active_record/schema_dumper.rb @@ -13,8 +13,7 @@ module ActiveRecord # A list of tables which should not be dumped to the schema. # Acceptable values are strings as well as regexp if ActiveRecord::Base.schema_format == :ruby. # Only strings are accepted if ActiveRecord::Base.schema_format == :sql. - cattr_accessor :ignore_tables - @@ignore_tables = [] + cattr_accessor :ignore_tables, default: [] class << self def dump(connection = ActiveRecord::Base.connection, stream = STDOUT, config = ActiveRecord::Base) diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb index f59737afb0..6dbabd69a1 100644 --- a/activerecord/lib/active_record/schema_migration.rb +++ b/activerecord/lib/active_record/schema_migration.rb @@ -1,5 +1,5 @@ -require "active_record/scoping/default" -require "active_record/scoping/named" +require_relative "scoping/default" +require_relative "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/scoping.rb b/activerecord/lib/active_record/scoping.rb index 7c00e7e4ed..94e0ef6724 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -10,8 +10,8 @@ module ActiveRecord end module ClassMethods - def current_scope #:nodoc: - ScopeRegistry.value_for(:current_scope, self) + def current_scope(skip_inherited_scope = false) # :nodoc: + ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope) end def current_scope=(scope) #:nodoc: @@ -75,8 +75,9 @@ module ActiveRecord end # Obtains the value for a given +scope_type+ and +model+. - def value_for(scope_type, model) + def value_for(scope_type, model, skip_inherited_scope = false) raise_invalid_scope_type!(scope_type) + return @registry[scope_type][model.name] if skip_inherited_scope klass = model base = model.base_class while klass <= base diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index a61fdd6454..b4026fabb2 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -29,8 +29,7 @@ module ActiveRecord end end - def default_scoped # :nodoc: - scope = relation + def default_scoped(scope = relation) # :nodoc: build_default_scope(scope) || scope end @@ -160,7 +159,7 @@ module ActiveRecord if body.respond_to?(:to_proc) singleton_class.send(:define_method, name) do |*args| scope = all - scope = scope.scoping { instance_exec(*args, &body) || scope } + scope = scope.instance_exec(*args, &body) || scope scope = scope.extending(extension) if extension scope end diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb index 541165b3d1..ff6745f7b5 100644 --- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb +++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb @@ -59,7 +59,6 @@ module ActiveRecord args.concat(["--no-data"]) args.concat(["--routines"]) args.concat(["--skip-comments"]) - args.concat(Array(extra_flags)) if extra_flags ignore_tables = ActiveRecord::SchemaDumper.ignore_tables if ignore_tables.any? @@ -67,6 +66,7 @@ module ActiveRecord end args.concat(["#{configuration['database']}"]) + args.unshift(*extra_flags) if extra_flags run_cmd("mysqldump", args, "dumping") end @@ -75,7 +75,7 @@ module ActiveRecord args = prepare_command_options args.concat(["--execute", %{SET FOREIGN_KEY_CHECKS = 0; SOURCE #{filename}; SET FOREIGN_KEY_CHECKS = 1}]) args.concat(["--database", "#{configuration['database']}"]) - args.concat(Array(extra_flags)) if extra_flags + args.unshift(*extra_flags) if extra_flags run_cmd("mysql", args, "loading") end @@ -99,7 +99,7 @@ module ActiveRecord def error_class if configuration["adapter"].include?("jdbc") - require "active_record/railties/jdbcmysql_error" + require_relative "../railties/jdbcmysql_error" ArJdbcMySQL::Error elsif defined?(Mysql2) Mysql2::Error @@ -151,7 +151,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION; end def run_cmd_error(cmd, args, action) - msg = "failed to execute: `#{cmd}`\n" + msg = "failed to execute: `#{cmd}`\n".dup msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n" msg end diff --git a/activerecord/lib/active_record/timestamp.rb b/activerecord/lib/active_record/timestamp.rb index 55f3a194a9..26eea0bc24 100644 --- a/activerecord/lib/active_record/timestamp.rb +++ b/activerecord/lib/active_record/timestamp.rb @@ -86,7 +86,7 @@ module ActiveRecord all_timestamp_attributes_in_model.each do |column| if !attribute_present?(column) - write_attribute(column, current_time) + _write_attribute(column, current_time) end end end @@ -100,7 +100,7 @@ module ActiveRecord timestamp_attributes_for_update_in_model.each do |column| next if will_save_change_to_attribute?(column) - write_attribute(column, current_time) + _write_attribute(column, current_time) end end super(*args) @@ -126,7 +126,7 @@ module ActiveRecord self.class.send(:current_time_from_proper_timezone) end - def max_updated_column_timestamp(timestamp_names = self.class.send(:timestamp_attributes_for_update)) + def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model) timestamp_names .map { |attr| self[attr] } .compact diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb index 45795fa287..463bb1f314 100644 --- a/activerecord/lib/active_record/transactions.rb +++ b/activerecord/lib/active_record/transactions.rb @@ -490,7 +490,7 @@ module ActiveRecord def update_attributes_from_transaction_state(transaction_state) if transaction_state && transaction_state.finalized? restore_transaction_record_state if transaction_state.rolledback? - clear_transaction_record_state + clear_transaction_record_state if transaction_state.fully_completed? end end end diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb index 9ed6c95bf9..6f4e35b159 100644 --- a/activerecord/lib/active_record/type.rb +++ b/activerecord/lib/active_record/type.rb @@ -1,20 +1,20 @@ require "active_model/type" -require "active_record/type/internal/timezone" +require_relative "type/internal/timezone" -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/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/serialized" -require "active_record/type/adapter_specific_registry" +require_relative "type/serialized" +require_relative "type/adapter_specific_registry" -require "active_record/type/type_map" -require "active_record/type/hash_lookup_type_map" +require_relative "type/type_map" +require_relative "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 f1686e4913..8b12a30c6a 100644 --- a/activerecord/lib/active_record/type_caster.rb +++ b/activerecord/lib/active_record/type_caster.rb @@ -1,5 +1,5 @@ -require "active_record/type_caster/map" -require "active_record/type_caster/connection" +require_relative "type_caster/map" +require_relative "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 9633f226f0..046b69bee2 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -84,8 +84,8 @@ module ActiveRecord end end -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" +require_relative "validations/associated" +require_relative "validations/uniqueness" +require_relative "validations/presence" +require_relative "validations/absence" +require_relative "validations/length" |