diff options
Diffstat (limited to 'activerecord/lib/active_record')
14 files changed, 57 insertions, 61 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 462b3066ab..f6d8e8a342 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -1164,6 +1164,7 @@ module ActiveRecord # Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. # Note that this operation instantly fires update SQL without waiting for the save or update call on the # parent object, unless the parent object is a new record. + # This will also run validations and callbacks of associated object(s). # [collection.delete(object, ...)] # Removes one or more objects from the collection by setting their foreign keys to +NULL+. # Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>, 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 a6ad09a38a..be65cf318c 100644 --- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb +++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb @@ -74,9 +74,8 @@ module ActiveRecord value = foreign_klass.base_class.name column = klass.columns_hash[reflection.type.to_s] - substitute = klass.connection.substitute_at(column) binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name)) - constraint = constraint.and table[reflection.type].eq substitute + constraint = constraint.and table[reflection.type].eq(Arel::Nodes::BindParam.new) end joins << table.create_join(table, table.create_on(constraint), join_type) 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 0ac5e80119..c4f525336b 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb @@ -297,14 +297,15 @@ module ActiveRecord # Inserts the given fixture into the table. Overridden in adapters that require # something beyond a simple insert (eg. Oracle). def insert_fixture(fixture, table_name) - columns = schema_cache.columns_hash(table_name) + fixture = fixture.stringify_keys + columns = schema_cache.columns_hash(table_name) binds = fixture.map do |name, value| if column = columns[name] type = lookup_cast_type_from_column(column) Relation::QueryAttribute.new(name, value, type) else - raise Fixture::FixtureError, %(table "#{table_name}" has no column named "#{name}".) + raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.) end end key_list = fixture.keys.map { |name| quote_column_name(name) } diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index dce34a208f..d9b42d4283 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -311,12 +311,6 @@ module ActiveRecord {} end - # Returns a bind substitution value given a bind +column+ - # NOTE: The column param is currently being used by the sqlserver-adapter - def substitute_at(column, _unused = 0) - Arel::Nodes::BindParam.new - end - # REFERENTIAL INTEGRITY ==================================== # Override to turn off referential integrity while executing <tt>&block</tt>. diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb index 1250f8a3c3..475a298467 100644 --- a/activerecord/lib/active_record/core.rb +++ b/activerecord/lib/active_record/core.rb @@ -275,7 +275,7 @@ module ActiveRecord def relation # :nodoc: relation = Relation.create(self, arel_table, predicate_builder) - if finder_needs_type_condition? + if finder_needs_type_condition? && !ignore_default_scope? relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name) else relation diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 04519f4dc3..8655f68308 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -187,7 +187,7 @@ module ActiveRecord # scope :active, -> { where status: 0 } klass.send(:detect_enum_conflict!, name, value_method_name, true) - klass.scope value_method_name, -> { klass.where name => value } + klass.scope value_method_name, -> { where(name => value) } end end defined_enums[name.to_s] = enum_values diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index f699e83ab3..4a6e9c12fe 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -145,7 +145,7 @@ module ActiveRecord class NoEnvironmentInSchemaError < MigrationError #:nodoc: def initialize - msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rake db:migrate" + msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rake db:environment:set" if defined?(Rails.env) super("#{msg} RAILS_ENV=#{::Rails.env}") else @@ -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 << "if you are sure you want to continue, run the same command with the environment variable\n" + 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) end @@ -165,10 +165,15 @@ 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 running in #{ current } environment." - msg << "if you are sure you want to continue, run the same command with the environment variable\n" - msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1" + msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n" + msg << "You are running in `#{ current }` environment." + msg << "If you are sure you want to continue, first set the environment using:\n\n" + msg << "\tbin/rake db:environment:set" + if defined?(Rails.env) + super("#{msg} RAILS_ENV=#{::Rails.env}") + else + super(msg) + end end end diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake index ead9407391..d81d6b54b3 100644 --- a/activerecord/lib/active_record/railties/databases.rake +++ b/activerecord/lib/active_record/railties/databases.rake @@ -1,6 +1,12 @@ require 'active_record' db_namespace = namespace :db do + desc "Set the environment value for the database" + task "environment:set" => [:environment, :load_config] do + ActiveRecord::InternalMetadata.create_table + ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment + end + task :check_protected_environments => [:environment, :load_config] do ActiveRecord::Tasks::DatabaseTasks.check_protected_environments! end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 6aa490908b..032b8d4c5d 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -99,7 +99,7 @@ module ActiveRecord end substitutes = values.map do |(arel_attr, _)| - [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])] + [arel_attr, Arel::Nodes::BindParam.new] end [substitutes, binds] diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb index cb971eb255..396638d74d 100644 --- a/activerecord/lib/active_record/relation/merger.rb +++ b/activerecord/lib/active_record/relation/merger.rb @@ -141,6 +141,9 @@ module ActiveRecord end def merge_single_values + if relation.from_clause.empty? + relation.from_clause = other.from_clause + end relation.lock_value ||= other.lock_value unless other.create_with_value.blank? diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb index 5635b4215b..716b1e8505 100644 --- a/activerecord/lib/active_record/relation/query_methods.rb +++ b/activerecord/lib/active_record/relation/query_methods.rb @@ -659,8 +659,10 @@ module ActiveRecord end def or!(other) # :nodoc: - unless structurally_compatible_for_or?(other) - raise ArgumentError, 'Relation passed to #or must be structurally compatible' + incompatible_values = structurally_incompatible_values_for_or(other) + + unless incompatible_values.empty? + raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}" end self.where_clause = self.where_clause.or(other.where_clause) @@ -1189,10 +1191,10 @@ module ActiveRecord end end - def structurally_compatible_for_or?(other) - Relation::SINGLE_VALUE_METHODS.all? { |m| send("#{m}_value") == other.send("#{m}_value") } && - (Relation::MULTI_VALUE_METHODS - [:extending]).all? { |m| send("#{m}_values") == other.send("#{m}_values") } && - (Relation::CLAUSE_METHODS - [:having, :where]).all? { |m| send("#{m}_clause") == other.send("#{m}_clause") } + def structurally_incompatible_values_for_or(other) + Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } + + (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } + + (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") } end def new_where_clause diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb index e395970dc6..7794af8ca4 100644 --- a/activerecord/lib/active_record/scoping.rb +++ b/activerecord/lib/active_record/scoping.rb @@ -11,11 +11,11 @@ module ActiveRecord module ClassMethods def current_scope #:nodoc: - ScopeRegistry.value_for(:current_scope, self.to_s) + ScopeRegistry.value_for(:current_scope, self) end def current_scope=(scope) #:nodoc: - ScopeRegistry.set_value_for(:current_scope, self.to_s, scope) + ScopeRegistry.set_value_for(:current_scope, self, scope) end # Collects attributes from scopes that should be applied when creating @@ -53,18 +53,18 @@ module ActiveRecord # following code: # # registry = ActiveRecord::Scoping::ScopeRegistry - # registry.set_value_for(:current_scope, "Board", some_new_scope) + # registry.set_value_for(:current_scope, Board, some_new_scope) # # Now when you run: # - # registry.value_for(:current_scope, "Board") + # registry.value_for(:current_scope, Board) # # You will obtain whatever was defined in +some_new_scope+. The #value_for # and #set_value_for methods are delegated to the current ScopeRegistry # object, so the above example code can also be called as: # # ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope, - # "Board", some_new_scope) + # Board, some_new_scope) class ScopeRegistry # :nodoc: extend ActiveSupport::PerThreadRegistry @@ -74,16 +74,22 @@ module ActiveRecord @registry = Hash.new { |hash, key| hash[key] = {} } end - # Obtains the value for a given +scope_name+ and +variable_name+. - def value_for(scope_type, variable_name) + # Obtains the value for a given +scope_type+ and +model+. + def value_for(scope_type, model) raise_invalid_scope_type!(scope_type) - @registry[scope_type][variable_name] + klass = model + base = model.base_class + while klass <= base + value = @registry[scope_type][klass.name] + return value if value + klass = klass.superclass + end end - # Sets the +value+ for a given +scope_type+ and +variable_name+. - def set_value_for(scope_type, variable_name, value) + # Sets the +value+ for a given +scope_type+ and +model+. + def set_value_for(scope_type, model, value) raise_invalid_scope_type!(scope_type) - @registry[scope_type][variable_name] = value + @registry[scope_type][model.name] = value end private diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb index cdcb73382f..8baf3b8044 100644 --- a/activerecord/lib/active_record/scoping/default.rb +++ b/activerecord/lib/active_record/scoping/default.rb @@ -122,11 +122,11 @@ module ActiveRecord end def ignore_default_scope? # :nodoc: - ScopeRegistry.value_for(:ignore_default_scope, self) + ScopeRegistry.value_for(:ignore_default_scope, base_class) end def ignore_default_scope=(ignore) # :nodoc: - ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore) + ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore) end # The ignore_default_scope flag is used to prevent an infinite recursion diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb index f10f7a1515..8abda2ac49 100644 --- a/activerecord/lib/active_record/secure_token.rb +++ b/activerecord/lib/active_record/secure_token.rb @@ -20,35 +20,14 @@ module ActiveRecord # # <tt>SecureRandom::base58</tt> is used to generate the 24-character unique token, so collisions are highly unlikely. # - # A secure token can also be only created given a condition, for example if a user should only have an - # auto-generated invitation token if the user was invited: - # - # # Schema: User(token:string, invited:boolean) - # class User < ActiveRecord::Base - # has_secure_token if: :invited? - # end - # - # user = User.new(invited: true) - # user.save - # user.token # => "pX27zsMN2ViQKta1bGfLmVJE" - # - # user = User.new(invited: false) - # user.save - # user.token # => nil - # - # The secure token creation supports all the options a `before_create` does - like +:if+ and +:unless+. - # # Note that it's still possible to generate a race condition in the database in the same way that # {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can. # You're encouraged to add a unique index in the database to deal with this even more unlikely scenario. - def has_secure_token(attribute = :token, **before_create_options) + def has_secure_token(attribute = :token) # Load securerandom only when has_secure_token is used. require 'active_support/core_ext/securerandom' - define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token } - before_create(before_create_options) do - self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?") - end + before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?")} end def generate_unique_secure_token |