aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record.rb1
-rw-r--r--activerecord/lib/active_record/association_relation.rb2
-rw-r--r--activerecord/lib/active_record/associations.rb14
-rw-r--r--activerecord/lib/active_record/associations/association.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb6
-rw-r--r--activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb3
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb2
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb1
-rw-r--r--activerecord/lib/active_record/associations/preloader/through_association.rb23
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/attribute/user_provided_default.rb15
-rw-r--r--activerecord/lib/active_record/attribute_assignment.rb8
-rw-r--r--activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb2
-rw-r--r--activerecord/lib/active_record/attributes.rb6
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb3
-rw-r--r--activerecord/lib/active_record/callbacks.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb30
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb54
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb173
-rw-r--r--activerecord/lib/active_record/connection_adapters/column.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb51
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb33
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/column.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb17
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb59
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb64
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb48
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb47
-rw-r--r--activerecord/lib/active_record/core.rb17
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/errors.rb9
-rw-r--r--activerecord/lib/active_record/fixture_set/file.rb8
-rw-r--r--activerecord/lib/active_record/gem_version.rb2
-rw-r--r--activerecord/lib/active_record/log_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/migration.rb8
-rw-r--r--activerecord/lib/active_record/migration/compatibility.rb4
-rw-r--r--activerecord/lib/active_record/model_schema.rb12
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb22
-rw-r--r--activerecord/lib/active_record/null_relation.rb2
-rw-r--r--activerecord/lib/active_record/persistence.rb2
-rw-r--r--activerecord/lib/active_record/query_cache.rb28
-rw-r--r--activerecord/lib/active_record/querying.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb19
-rw-r--r--activerecord/lib/active_record/railties/databases.rake10
-rw-r--r--activerecord/lib/active_record/reflection.rb25
-rw-r--r--activerecord/lib/active_record/relation.rb52
-rw-r--r--activerecord/lib/active_record/relation/batches.rb40
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb2
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb3
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb74
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb13
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb10
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb57
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/where_clause.rb3
-rw-r--r--activerecord/lib/active_record/sanitization.rb2
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb12
-rw-r--r--activerecord/lib/active_record/scoping/default.rb3
-rw-r--r--activerecord/lib/active_record/suppressor.rb6
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb14
-rw-r--r--activerecord/lib/active_record/tasks/mysql_database_tasks.rb2
-rw-r--r--activerecord/lib/active_record/type.rb2
-rw-r--r--activerecord/lib/active_record/type/internal/abstract_json.rb6
-rw-r--r--activerecord/lib/active_record/type/time.rb12
-rw-r--r--activerecord/lib/active_record/validations.rb2
-rw-r--r--activerecord/lib/active_record/validations/absence.rb1
-rw-r--r--activerecord/lib/active_record/validations/length.rb12
-rw-r--r--activerecord/lib/active_record/validations/presence.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb16
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb31
-rw-r--r--activerecord/lib/rails/generators/active_record/model/templates/application_record.rb5
87 files changed, 914 insertions, 491 deletions
diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb
index ab3846ae65..baa497dc98 100644
--- a/activerecord/lib/active_record.rb
+++ b/activerecord/lib/active_record.rb
@@ -137,7 +137,6 @@ module ActiveRecord
eager_autoload do
autoload :AbstractAdapter
- autoload :ConnectionManagement, "active_record/connection_adapters/abstract/connection_pool"
end
end
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index ee0bb8fafe..c18e88e4cf 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -10,7 +10,7 @@ module ActiveRecord
end
def ==(other)
- other == to_a
+ other == records
end
def build(*args, &block)
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index e13fe33b85..2fbecb7d04 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -504,7 +504,7 @@ module ActiveRecord
#
# == Customizing the query
#
- # \Associations are built from <tt>Relation</tt>s, and you can use the Relation syntax
+ # \Associations are built from <tt>Relation</tt> objects, and you can use the Relation syntax
# to customize them. For example, to add a condition:
#
# class Blog < ActiveRecord::Base
@@ -1326,7 +1326,8 @@ module ActiveRecord
# Specifies type of the source association used by #has_many <tt>:through</tt> queries where the source
# association is a polymorphic #belongs_to.
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. true by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated objects or destroy them if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated objects.
@@ -1456,7 +1457,8 @@ module ActiveRecord
# Specifies type of the source association used by #has_one <tt>:through</tt> queries where the source
# association is a polymorphic #belongs_to.
# [:validate]
- # If +false+, don't validate the associated object when saving the parent object. +false+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction,
# when saving the parent object. If false, never save or destroy the associated object.
@@ -1580,7 +1582,8 @@ module ActiveRecord
# Note: If you've enabled the counter cache, then you may want to add the counter cache attribute
# to the +attr_readonly+ list in the associated classes (e.g. <tt>class Post; attr_readonly :comments_count; end</tt>).
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. +false+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +false+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated object or destroy it if marked for destruction, when
# saving the parent object.
@@ -1766,7 +1769,8 @@ module ActiveRecord
# So if a Person class makes a #has_and_belongs_to_many association to Project,
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
# [:validate]
- # If +false+, don't validate the associated objects when saving the parent object. +true+ by default.
+ # When set to +true+, validates new objects added to association when saving the parent object. +true+ by default.
+ # If you want to ensure associated objects are revalidated on every update, use +validates_associated+.
# [:autosave]
# If true, always save the associated objects or destroy them if marked for destruction, when
# saving the parent object.
diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb
index d64ab64c99..f7edfbfb5f 100644
--- a/activerecord/lib/active_record/associations/association.rb
+++ b/activerecord/lib/active_record/associations/association.rb
@@ -257,7 +257,7 @@ module ActiveRecord
# Returns true if statement cache should be skipped on the association reader.
def skip_statement_cache?
- reflection.scope_chain.any?(&:any?) ||
+ reflection.has_scope? ||
scope.eager_loading? ||
klass.scope_attributes? ||
reflection.source_reflection.active_record.default_scopes.any?
diff --git a/activerecord/lib/active_record/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index 56a8dc4e18..f25bd7ca9f 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -70,7 +70,11 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.wrap_scope(scope, mod)
if scope
- proc { |owner| instance_exec(owner, &scope).extending(mod) }
+ if scope.arity > 0
+ proc { |owner| instance_exec(owner, &scope).extending(mod) }
+ else
+ proc { instance_exec(&scope).extending(mod) }
+ end
else
proc { extending(mod) }
end
diff --git a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
index b888148841..5fbd79d118 100644
--- a/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_and_belongs_to_many.rb
@@ -76,6 +76,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
left_model.retrieve_connection
end
+ def self.primary_key
+ false
+ end
}
join_model.name = "HABTM_#{association_name.to_s.camelize}"
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 2a9627a474..b9aed05135 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -979,6 +979,10 @@ module ActiveRecord
end
alias_method :to_a, :to_ary
+ def records # :nodoc:
+ load_target
+ end
+
# Adds one or more +records+ to the collection by setting their foreign keys
# to the association's primary key. Returns +self+, so several appends may be
# chained together.
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 0e4e951269..b94feeff12 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -228,7 +228,7 @@ module ActiveRecord
def find_reflection(klass, name)
klass._reflect_on_association(name) or
- raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
+ raise ConfigurationError, "Can't join '#{ klass.name }' to association named '#{ name }'; perhaps you misspelled it?"
end
def build(associations, base_klass)
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index ecf6fb8643..e64af84e1a 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -184,6 +184,7 @@ module ActiveRecord
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
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
index 6c83058202..b0203909ce 100644
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
@@ -38,12 +38,7 @@ module ActiveRecord
}
end
- record_offset = {}
- @preloaded_records.each_with_index do |record,i|
- record_offset[record] = i
- end
-
- through_records.each_with_object({}) { |(lhs,center),records_by_owner|
+ through_records.each_with_object({}) do |(lhs,center), records_by_owner|
pl_to_middle = center.group_by { |record| middle_to_pl[record] }
records_by_owner[lhs] = pl_to_middle.flat_map do |pl, middles|
@@ -53,13 +48,25 @@ module ActiveRecord
target_records_from_association(association)
}.compact
- rhs_records.sort_by { |rhs| record_offset[rhs] }
+ # Respect the order on `reflection_scope` if it exists, else use the natural order.
+ if reflection_scope.values[:order].present?
+ @id_map ||= id_to_index_map @preloaded_records
+ rhs_records.sort_by { |rhs| @id_map[rhs] }
+ else
+ rhs_records
+ end
end
- }
+ end
end
private
+ def id_to_index_map(ids)
+ id_map = {}
+ ids.each_with_index { |id, index| id_map[id] = index }
+ id_map
+ end
+
def reset_association(owners, association_name)
should_reset = (through_scope != through_reflection.klass.unscoped) ||
(reflection.options[:source_type] && through_reflection.collection?)
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index c7cc48ba16..f913f0852a 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -45,7 +45,7 @@ module ActiveRecord
end
def get_records
- return scope.limit(1).to_a if skip_statement_cache?
+ return scope.limit(1).records if skip_statement_cache?
conn = klass.connection
sc = reflection.association_scope_cache(conn, owner) do
diff --git a/activerecord/lib/active_record/attribute/user_provided_default.rb b/activerecord/lib/active_record/attribute/user_provided_default.rb
index 6dbd92ce28..4580813364 100644
--- a/activerecord/lib/active_record/attribute/user_provided_default.rb
+++ b/activerecord/lib/active_record/attribute/user_provided_default.rb
@@ -4,20 +4,25 @@ module ActiveRecord
class Attribute # :nodoc:
class UserProvidedDefault < FromUser # :nodoc:
def initialize(name, value, type, database_default)
+ @user_provided_value = value
super(name, value, type, database_default)
end
- def type_cast(value)
- if value.is_a?(Proc)
- super(value.call)
+ def value_before_type_cast
+ if user_provided_value.is_a?(Proc)
+ @memoized_value_before_type_cast ||= user_provided_value.call
else
- super
+ @user_provided_value
end
end
def with_type(type)
- self.class.new(name, value_before_type_cast, type, original_attribute)
+ self.class.new(name, user_provided_value, type, original_attribute)
end
+
+ protected
+
+ attr_reader :user_provided_value
end
end
end
diff --git a/activerecord/lib/active_record/attribute_assignment.rb b/activerecord/lib/active_record/attribute_assignment.rb
index a6d81c82b4..4c22be8235 100644
--- a/activerecord/lib/active_record/attribute_assignment.rb
+++ b/activerecord/lib/active_record/attribute_assignment.rb
@@ -29,14 +29,6 @@ module ActiveRecord
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
- # Tries to assign given value to given attribute.
- # In case of an error, re-raises with the ActiveRecord constant.
- def _assign_attribute(k, v) # :nodoc:
- super
- rescue ActiveModel::UnknownAttributeError
- raise UnknownAttributeError.new(self, k)
- end
-
# Assign any deferred nested attributes after the base attributes have been set.
def assign_nested_parameter_attributes(pairs)
pairs.each { |k, v| _assign_attribute(k, v) }
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 ebaaa54b2b..e160460286 100644
--- a/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
+++ b/activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -104,7 +104,7 @@ module ActiveRecord
To silence this deprecation warning, add the following:
- config.active_record.time_zone_aware_types << :time
+ config.active_record.time_zone_aware_types = [:datetime, :time]
MESSAGE
end
diff --git a/activerecord/lib/active_record/attributes.rb b/activerecord/lib/active_record/attributes.rb
index 5d0405c3be..e0ceafc617 100644
--- a/activerecord/lib/active_record/attributes.rb
+++ b/activerecord/lib/active_record/attributes.rb
@@ -119,7 +119,7 @@ module ActiveRecord
#
# class MoneyType < ActiveRecord::Type::Integer
# def cast(value)
- # if !value.kind_of(Numeric) && value.include?('$')
+ # if !value.kind_of?(Numeric) && value.include?('$')
# price_in_dollars = value.gsub(/\$/, '').to_f
# super(price_in_dollars * 100)
# else
@@ -154,7 +154,7 @@ module ActiveRecord
# end
#
# class MoneyType < Type::Value
- # def initialize(currency_converter)
+ # def initialize(currency_converter:)
# @currency_converter = currency_converter
# end
#
@@ -171,7 +171,7 @@ module ActiveRecord
#
# class Product < ActiveRecord::Base
# currency_converter = ConversionRatesFromTheInternet.new
- # attribute :price_in_bitcoins, :money, currency_converter
+ # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
# end
#
# Product.where(price_in_bitcoins: Money.new(5, "USD"))
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index bac5a38a5d..06c7482bf9 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -22,7 +22,7 @@ module ActiveRecord
#
# == Validation
#
- # Children records are validated unless <tt>:validate</tt> is +false+.
+ # Child records are validated unless <tt>:validate</tt> is +false+.
#
# == Callbacks
#
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 7ed2fe48be..6a1a27ce41 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -169,7 +169,8 @@ module ActiveRecord #:nodoc:
# ActiveRecord::RecordNotFound error if they do not return any records,
# like <tt>Person.find_by_last_name!</tt>.
#
- # It's also possible to use multiple attributes in the same find by separating them with "_and_".
+ # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
+ # "_and_".
#
# Person.find_by(user_name: user_name, password: password)
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index 854f9776a3..1f1b11eb68 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -179,7 +179,7 @@ module ActiveRecord
#
# If the +before_validation+ callback throws +:abort+, the process will be
# aborted and {ActiveRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
- # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise a ActiveRecord::RecordInvalid exception.
+ # If {ActiveRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an ActiveRecord::RecordInvalid exception.
# Nothing will be appended to the errors object.
#
# == Canceling callbacks
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 ccd2899489..e389d818fd 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -951,24 +951,5 @@ module ActiveRecord
owner_to_pool && owner_to_pool[owner.name]
end
end
-
- class ConnectionManagement
- def initialize(app)
- @app = app
- end
-
- def call(env)
- testing = env['rack.test']
-
- status, headers, body = @app.call(env)
- proxy = ::Rack::BodyProxy.new(body) do
- ActiveRecord::Base.clear_active_connections! unless testing
- end
- [status, headers, proxy]
- rescue Exception
- ActiveRecord::Base.clear_active_connections! unless testing
- raise
- end
- end
end
end
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 bb5119d64e..824040775d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -30,7 +30,7 @@ module ActiveRecord
def select_all(arel, name = nil, binds = [], preparable: nil)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- if arel.is_a?(String) && preparable.nil?
+ if !prepared_statements || (arel.is_a?(String) && preparable.nil?)
preparable = false
else
preparable = visitor.preparable
@@ -125,18 +125,21 @@ module ActiveRecord
end
alias create insert
alias insert_sql insert
+ deprecate insert_sql: :insert
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
exec_update(to_sql(arel, binds), name, binds)
end
alias update_sql update
+ deprecate update_sql: :update
# Executes the delete statement and returns the number of rows affected.
def delete(arel, name = nil, binds = [])
exec_delete(to_sql(arel, binds), name, binds)
end
alias delete_sql delete
+ deprecate delete_sql: :delete
# Returns +true+ when the connection adapter supports prepared statement
# caching, otherwise returns +false+
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 33dbab41cb..0bdfd4f900 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -65,7 +65,7 @@ module ActiveRecord
if @query_cache_enabled && !locked?(arel)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- cache_sql(sql, binds) { super(sql, name, binds, preparable: visitor.preparable) }
+ cache_sql(sql, binds) { super(sql, name, binds, preparable: preparable) }
else
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 7e3760d34b..860ef17dca 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -82,7 +82,7 @@ module ActiveRecord
# Quotes the column name. Defaults to no quoting.
def quote_column_name(column_name)
- column_name
+ column_name.to_s
end
# Quotes the table name. Defaults to column name quoting.
@@ -146,6 +146,10 @@ module ActiveRecord
end
end
+ def quoted_time(value) # :nodoc:
+ quoted_date(value).sub(/\A2000-01-01 /, '')
+ end
+
def prepare_binds_for_database(binds) # :nodoc:
binds.map(&:value_for_database)
end
@@ -166,6 +170,7 @@ module ActiveRecord
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
when Numeric, ActiveSupport::Duration then value.to_s
+ when Type::Time::Value then "'#{quoted_time(value)}'"
when Date, Time then "'#{quoted_date(value)}'"
when Symbol then "'#{quote_string(value.to_s)}'"
when Class then "'#{value}'"
@@ -181,6 +186,7 @@ module ActiveRecord
when false then unquoted_false
# BigDecimals need to be put in a non-normalized form and quoted.
when BigDecimal then value.to_s('F')
+ when Type::Time::Value then quoted_time(value)
when Date, Time then quoted_date(value)
when *types_which_need_no_typecasting
value
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 0ba4d94e3c..6add697eeb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb
@@ -53,8 +53,8 @@ module ActiveRecord
statements.concat(o.foreign_keys.map { |to_table, options| foreign_key_in_create(o.name, to_table, options) })
end
- create_sql << "(#{statements.join(', ')}) " if statements.present?
- create_sql << "#{o.options}"
+ 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
end
@@ -82,6 +82,19 @@ module ActiveRecord
"DROP CONSTRAINT #{quote_column_name(name)}"
end
+ def table_options(o)
+ table_options = {}
+ table_options[:comment] = o.comment
+ table_options[:options] = o.options
+ table_options
+ end
+
+ def add_table_options!(create_sql, options)
+ if options_sql = options[:options]
+ create_sql << " #{options_sql}"
+ end
+ end
+
def column_options(o)
column_options = {}
column_options[:null] = o.null unless o.null.nil?
@@ -92,6 +105,7 @@ module ActiveRecord
column_options[:auto_increment] = o.auto_increment
column_options[:primary_key] = o.primary_key
column_options[:collation] = o.collation
+ column_options[:comment] = o.comment
column_options
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
index cb10ca9929..bbb0e9249d 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -3,14 +3,14 @@ module ActiveRecord
# Abstract representation of an index definition on a table. Instances of
# this type are typically created and returned by methods in database
# adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using) #:nodoc:
+ class IndexDefinition < Struct.new(:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment) #:nodoc:
end
# Abstract representation of a column definition. Instances of this type
# are typically created by methods in TableDefinition, and added to the
# +columns+ attribute of said TableDefinition object, in order to be used
# for generating a number of table creation or table changing SQL statements.
- class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type) #:nodoc:
+ class ColumnDefinition < Struct.new(:name, :type, :limit, :precision, :scale, :default, :null, :first, :after, :auto_increment, :primary_key, :collation, :sql_type, :comment) #:nodoc:
def primary_key?
primary_key || type.to_sym == :primary_key
@@ -207,17 +207,18 @@ module ActiveRecord
include ColumnMethods
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as, :foreign_keys
+ attr_reader :name, :temporary, :options, :as, :foreign_keys, :comment
- def initialize(name, temporary, options, as = nil)
+ def initialize(name, temporary = false, options = nil, as = nil, comment: nil)
@columns_hash = {}
@indexes = {}
- @foreign_keys = {}
+ @foreign_keys = []
@primary_keys = nil
@temporary = temporary
@options = options
@as = as
@name = name
+ @comment = comment
end
def primary_keys(name = nil) # :nodoc:
@@ -330,7 +331,10 @@ module ActiveRecord
end
def foreign_key(table_name, options = {}) # :nodoc:
- foreign_keys[table_name] = options
+ table_name_prefix = ActiveRecord::Base.table_name_prefix
+ table_name_suffix = ActiveRecord::Base.table_name_suffix
+ table_name = "#{table_name_prefix}#{table_name}#{table_name_suffix}"
+ foreign_keys.push([table_name, options])
end
# Appends <tt>:datetime</tt> columns <tt>:created_at</tt> and
@@ -373,6 +377,7 @@ module ActiveRecord
column.auto_increment = options[:auto_increment]
column.primary_key = type == :primary_key || options[:primary_key]
column.collation = options[:collation]
+ column.comment = options[:comment]
column
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index b1b6044e72..6b5fad00ab 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -7,15 +7,16 @@ module ActiveRecord
# Adapter level by over-writing this code inside the database specific adapters
module ColumnDumper
def column_spec(column)
- spec = prepare_column_options(column)
- (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k}: ")}
+ spec = Hash[prepare_column_options(column).map { |k, v| [k, "#{k}: #{v}"] }]
+ spec[:name] = column.name.inspect
+ spec[:type] = schema_type(column).to_s
spec
end
def column_spec_for_primary_key(column)
- return if column.type == :integer
+ return {} if default_primary_key?(column)
spec = { id: schema_type(column).inspect }
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type].include?(key) })
+ spec.merge!(prepare_column_options(column).except!(:null))
end
# This can be overridden on an Adapter level basis to support other
@@ -23,9 +24,6 @@ module ActiveRecord
# PostgreSQL::ColumnDumper)
def prepare_column_options(column)
spec = {}
- spec[:name] = column.name.inspect
- spec[:type] = schema_type(column).to_s
- spec[:null] = 'false' unless column.null
if limit = schema_limit(column)
spec[:limit] = limit
@@ -42,26 +40,38 @@ module ActiveRecord
default = schema_default(column) if column.has_default?
spec[:default] = default unless default.nil?
+ spec[:null] = 'false' unless column.null
+
if collation = schema_collation(column)
spec[:collation] = collation
end
+ spec[:comment] = column.comment.inspect if column.comment
+
spec
end
# Lists the valid migration options
def migration_keys
- [:name, :limit, :precision, :scale, :default, :null, :collation]
+ [:name, :limit, :precision, :scale, :default, :null, :collation, :comment]
end
private
+ def default_primary_key?(column)
+ schema_type(column) == :integer
+ end
+
def schema_type(column)
- column.type
+ if column.bigint?
+ :bigint
+ else
+ column.type
+ end
end
def schema_limit(column)
- limit = column.limit
+ limit = column.limit unless column.bigint?
limit.inspect if limit && limit != native_database_types[column.type][:limit]
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index f0f855963a..4f361fed6b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -18,6 +18,11 @@ module ActiveRecord
nil
end
+ # Returns the table comment that's stored in database metadata.
+ def table_comment(table_name)
+ nil
+ end
+
# Truncates a table alias according to the limits of the current adapter.
def table_alias_for(table_name)
table_name[0...table_alias_length].tr('.', '_')
@@ -254,8 +259,8 @@ module ActiveRecord
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
#
# See also TableDefinition#column for details on how to create columns.
- def create_table(table_name, options = {})
- td = create_table_definition table_name, options[:temporary], options[:options], options[:as]
+ def create_table(table_name, comment: nil, **options)
+ td = create_table_definition table_name, options[:temporary], options[:options], options[:as], comment: comment
if options[:id] != false && !options[:as]
pk = options.fetch(:primary_key) do
@@ -283,6 +288,14 @@ module ActiveRecord
end
end
+ if supports_comments? && !supports_comments_in_create?
+ change_table_comment(table_name, comment) if comment
+
+ td.columns.each do |column|
+ change_column_comment(table_name, column.name, column.comment) if column.comment
+ end
+ end
+
result
end
@@ -776,7 +789,8 @@ module ActiveRecord
# [<tt>:type</tt>]
# The reference column type. Defaults to +:integer+.
# [<tt>:index</tt>]
- # Add an appropriate index. Defaults to false.
+ # Add an appropriate index. Defaults to false.
+ # See #add_index for usage of this option.
# [<tt>:foreign_key</tt>]
# Add an appropriate foreign key constraint. Defaults to false.
# [<tt>:polymorphic</tt>]
@@ -796,6 +810,14 @@ module ActiveRecord
#
# add_reference(:products, :supplier, polymorphic: true, index: true)
#
+ # ====== Create a supplier_id column with a unique index
+ #
+ # add_reference(:products, :supplier, index: { unique: true })
+ #
+ # ====== Create a supplier_id column with a named index
+ #
+ # add_reference(:products, :supplier, index: { name: "my_supplier_index" })
+ #
# ====== Create a supplier_id column and appropriate foreign key
#
# add_reference(:products, :supplier, foreign_key: true)
@@ -854,7 +876,7 @@ module ActiveRecord
#
# generates:
#
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
#
# ====== Creating a foreign key on a specific column
#
@@ -870,7 +892,7 @@ module ActiveRecord
#
# generates:
#
- # ALTER TABLE "articles" ADD CONSTRAINT articles_author_id_fk FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
#
# The +options+ hash can include the following keys:
# [<tt>:column</tt>]
@@ -1051,9 +1073,9 @@ module ActiveRecord
end
# Adds timestamps (+created_at+ and +updated_at+) columns to +table_name+.
- # Additional options (like <tt>null: false</tt>) are forwarded to #add_column.
+ # Additional options (like +:null+) are forwarded to #add_column.
#
- # add_timestamps(:suppliers, null: false)
+ # add_timestamps(:suppliers, null: true)
#
def add_timestamps(table_name, options = {})
options[:null] = false if options[:null].nil?
@@ -1075,7 +1097,7 @@ module ActiveRecord
Table.new(table_name, base)
end
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
+ def add_index_options(table_name, column_name, comment: nil, **options) #:nodoc:
column_names = Array(column_name)
options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type)
@@ -1106,13 +1128,23 @@ module ActiveRecord
end
index_columns = quoted_columns_for_index(column_names, options).join(", ")
- [index_name, index_type, index_columns, index_options, algorithm, using]
+ [index_name, index_type, index_columns, index_options, algorithm, using, comment]
end
def options_include_default?(options)
options.include?(:default) && !(options[:null] == false && options[:default].nil?)
end
+ # Changes the comment for a table or removes it if +nil+.
+ def change_table_comment(table_name, comment)
+ raise NotImplementedError, "#{self.class} does not support changing table comments"
+ end
+
+ # Changes the comment for a column or removes it if +nil+.
+ def change_column_comment(table_name, column_name, comment) #:nodoc:
+ raise NotImplementedError, "#{self.class} does not support changing column comments"
+ end
+
protected
def add_index_sort_order(option_strings, column_names, options = {})
if options.is_a?(Hash) && order = options[:order]
@@ -1194,8 +1226,8 @@ module ActiveRecord
end
private
- def create_table_definition(name, temporary = false, options = nil, as = nil)
- TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args)
+ TableDefinition.new(*args)
end
def create_alter_table(name)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index 6ecdab6eb0..ca795cb1ad 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -188,7 +188,10 @@ module ActiveRecord
transaction = begin_transaction options
yield
rescue Exception => error
- rollback_transaction if transaction
+ if transaction
+ rollback_transaction
+ after_failure_actions(transaction, error)
+ end
raise
ensure
unless error
@@ -214,7 +217,16 @@ module ActiveRecord
end
private
+
NULL_TRANSACTION = NullTransaction.new
+
+ # Deallocate invalidated prepared statements outside of the transaction
+ def after_failure_actions(transaction, error)
+ return unless transaction.is_a?(RealTransaction)
+ return unless error.is_a?(ActiveRecord::PreparedStatementCacheExpired)
+ @connection.clear_cache!
+ end
+
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index d9b42d4283..c9716c9ca9 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,5 +1,4 @@
require 'active_record/type'
-require 'active_support/core_ext/benchmark'
require 'active_record/connection_adapters/determine_if_preparable_visitor'
require 'active_record/connection_adapters/schema_cache'
require 'active_record/connection_adapters/sql_type_metadata'
@@ -28,7 +27,6 @@ module ActiveRecord
autoload_at 'active_record/connection_adapters/abstract/connection_pool' do
autoload :ConnectionHandler
- autoload :ConnectionManagement
end
autoload_under 'abstract' do
@@ -108,6 +106,7 @@ module ActiveRecord
@schema_cache = SchemaCache.new self
@visitor = nil
@prepared_statements = false
+ @quoted_column_names, @quoted_table_names = {}, {}
end
class Version
@@ -279,6 +278,16 @@ module ActiveRecord
false
end
+ # Does this adapter support metadata comments on database objects (tables, columns, indexes)?
+ def supports_comments?
+ false
+ end
+
+ # Can comments for tables, columns, and indexes be specified in create/alter table statements?
+ def supports_comments_in_create?
+ false
+ end
+
# This is meant to be implemented by the adapters that support extensions
def disable_extension(name)
end
@@ -398,7 +407,7 @@ module ActiveRecord
if can_perform_case_insensitive_comparison_for?(column)
table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
else
- case_sensitive_comparison(table, attribute, column, value)
+ table[attribute].eq(Arel::Nodes::BindParam.new)
end
end
@@ -422,8 +431,8 @@ module ActiveRecord
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
- Column.new(name, default, sql_type_metadata, null, default_function, collation)
+ def new_column(name, default, sql_type_metadata, null, table_name, default_function = nil, collation = nil) # :nodoc:
+ Column.new(name, default, sql_type_metadata, null, table_name, default_function, collation)
end
def lookup_cast_type(sql_type) # :nodoc:
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 8751b6da4b..86ad422b06 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -1,6 +1,7 @@
require 'active_record/connection_adapters/abstract_adapter'
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'
@@ -11,6 +12,7 @@ require 'active_support/core_ext/string/strip'
module ActiveRecord
module ConnectionAdapters
class AbstractMysqlAdapter < AbstractAdapter
+ include MySQL::Quoting
include MySQL::ColumnDumper
include Savepoints
@@ -32,8 +34,6 @@ module ActiveRecord
class_attribute :emulate_booleans
self.emulate_booleans = true
- QUOTED_TRUE, QUOTED_FALSE = '1', '0'
-
NATIVE_DATABASE_TYPES = {
primary_key: "int auto_increment PRIMARY KEY",
string: { name: "varchar", limit: 255 },
@@ -54,7 +54,6 @@ module ActiveRecord
def initialize(connection, logger, connection_options, config)
super(connection, logger, config)
- @quoted_column_names, @quoted_table_names = {}, {}
@visitor = Arel::Visitors::MySQL.new self
@@ -78,10 +77,14 @@ module ActiveRecord
}
end
- def version
+ def version #:nodoc:
@version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
end
+ def mariadb? # :nodoc:
+ full_version =~ /mariadb/i
+ end
+
# Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
@@ -122,7 +125,11 @@ module ActiveRecord
end
def supports_datetime_with_precision?
- version >= '5.6.4'
+ if mariadb?
+ version >= '5.3.0'
+ else
+ version >= '5.6.4'
+ end
end
def supports_advisory_locks?
@@ -153,8 +160,8 @@ module ActiveRecord
raise NotImplementedError
end
- def new_column(field, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- MySQL::Column.new(field, default, sql_type_metadata, null, default_function, collation)
+ def new_column(*args) #:nodoc:
+ MySQL::Column.new(*args)
end
# Must return the MySQL error number from the exception, if the exception has an
@@ -163,48 +170,6 @@ module ActiveRecord
raise NotImplementedError
end
- # QUOTING ==================================================
-
- def _quote(value) # :nodoc:
- if value.is_a?(Type::Binary::Data)
- "x'#{value.hex}'"
- else
- super
- end
- end
-
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= "`#{name.to_s.gsub('`', '``')}`"
- end
-
- def quote_table_name(name) #:nodoc:
- @quoted_table_names[name] ||= quote_column_name(name).gsub('.', '`.`')
- end
-
- def quoted_true
- QUOTED_TRUE
- end
-
- def unquoted_true
- 1
- end
-
- def quoted_false
- QUOTED_FALSE
- end
-
- def unquoted_false
- 0
- end
-
- def quoted_date(value)
- if supports_datetime_with_precision?
- super
- else
- super.sub(/\.\d{6}\z/, '')
- end
- end
-
# REFERENTIAL INTEGRITY ====================================
def disable_referential_integrity #:nodoc:
@@ -301,9 +266,9 @@ module ActiveRecord
# create_database 'matt_development', charset: :big5
def create_database(name, options = {})
if options[:collation]
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}` COLLATE `#{options[:collation]}`"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')} COLLATE #{quote_table_name(options[:collation])}"
else
- execute "CREATE DATABASE `#{name}` DEFAULT CHARACTER SET `#{options[:charset] || 'utf8'}`"
+ execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset] || 'utf8')}"
end
end
@@ -312,7 +277,7 @@ module ActiveRecord
# Example:
# drop_database('sebastian_development')
def drop_database(name) #:nodoc:
- execute "DROP DATABASE IF EXISTS `#{name}`"
+ execute "DROP DATABASE IF EXISTS #{quote_table_name(name)}"
end
def current_database
@@ -408,7 +373,7 @@ module ActiveRecord
mysql_index_type = row[:Index_type].downcase.to_sym
index_type = INDEX_TYPES.include?(mysql_index_type) ? mysql_index_type : nil
index_using = INDEX_USINGS.include?(mysql_index_type) ? mysql_index_type : nil
- indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using)
+ indexes << IndexDefinition.new(row[:Table], row[:Key_name], row[:Non_unique].to_i == 0, [], [], nil, nil, index_type, index_using, row[:Index_comment].presence)
end
indexes.last.columns << row[:Column_name]
@@ -421,21 +386,28 @@ module ActiveRecord
# Returns an array of +Column+ objects for the table specified by +table_name+.
def columns(table_name) # :nodoc:
- sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
- execute_and_free(sql, 'SCHEMA') do |result|
- each_hash(result).map do |field|
- type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
- new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
- else
- new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
- end
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |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]
+ else
+ default, default_function = field[:Default], nil
end
+ new_column(field[:Field], default, type_metadata, field[:Null] == "YES", table_name, default_function, field[:Collation], comment: field[:Comment].presence)
end
end
- def create_table(table_name, options = {}) #:nodoc:
- super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
+ def table_comment(table_name) # :nodoc:
+ select_value(<<-SQL.strip_heredoc, 'SCHEMA')
+ SELECT table_comment
+ FROM information_schema.tables
+ WHERE table_name=#{quote(table_name)}
+ SQL
+ end
+
+ def create_table(table_name, **options) #:nodoc:
+ super(table_name, options: 'ENGINE=InnoDB', **options)
end
def bulk_change_table(table_name, operations) #:nodoc:
@@ -518,8 +490,10 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, _, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} #{index_using} ON #{quote_table_name(table_name)} (#{index_columns}) #{index_algorithm}"
+ 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 << " COMMENT #{quote(comment)}" if comment
+ execute sql
end
def foreign_keys(table_name)
@@ -557,7 +531,12 @@ module ActiveRecord
raw_table_options = create_table_info.sub(/\A.*\n\) /m, '').sub(/\n\/\*!.*\*\/\n\z/m, '').strip
# strip AUTO_INCREMENT
- raw_table_options.sub(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+ raw_table_options.sub!(/(ENGINE=\w+)(?: AUTO_INCREMENT=\d+)/, '\1')
+
+ # strip COMMENT
+ raw_table_options.sub!(/ COMMENT='.+'/, '')
+
+ raw_table_options
end
# Maps logical Rails types to MySQL-specific data types.
@@ -585,8 +564,7 @@ module ActiveRecord
# SHOW VARIABLES LIKE 'name'
def show_variable(name)
- variables = select_all("select @@#{name} as 'Value'", 'SCHEMA')
- variables.first['Value'] unless variables.empty?
+ select_value("SELECT @@#{name}", 'SCHEMA')
rescue ActiveRecord::StatementInvalid
nil
end
@@ -608,20 +586,17 @@ module ActiveRecord
end
def case_sensitive_comparison(table, attribute, column, value)
- if value.nil? || column.case_sensitive?
- super
- else
+ if !value.nil? && column.collation && !column.case_sensitive?
table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
+ else
+ super
end
end
- def case_insensitive_comparison(table, attribute, column, value)
- if column.case_sensitive?
- super
- else
- table[attribute].eq(Arel::Nodes::BindParam.new)
- end
+ def can_perform_case_insensitive_comparison_for?(column)
+ column.case_sensitive?
end
+ private :can_perform_case_insensitive_comparison_for?
# In MySQL 5.7.5 and up, ONLY_FULL_GROUP_BY affects handling of queries that use
# DISTINCT and ORDER BY. It requires the ORDER BY columns in the select list for
@@ -671,7 +646,7 @@ module ActiveRecord
register_integer_type m, %r(^smallint)i, limit: 2
register_integer_type m, %r(^tinyint)i, limit: 1
- m.alias_type %r(tinyint\(1\))i, 'boolean' if emulate_booleans
+ m.register_type %r(^tinyint\(1\))i, Type::Boolean.new if emulate_booleans
m.alias_type %r(year)i, 'integer'
m.alias_type %r(bit)i, 'binary'
@@ -741,6 +716,8 @@ module ActiveRecord
RecordNotUnique.new(message)
when 1452
InvalidForeignKey.new(message)
+ when 1406
+ ValueTooLong.new(message)
else
super
end
@@ -826,10 +803,6 @@ module ActiveRecord
subselect.from subsubselect.as('__active_record_temp')
end
- def mariadb?
- full_version =~ /mariadb/i
- end
-
def supports_rename_index?
mariadb? ? false : version >= '5.7.6'
end
@@ -850,9 +823,19 @@ module ActiveRecord
# Make MySQL reject illegal values rather than truncating or blanking them, see
# http://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sqlmode_strict_all_tables
# If the user has provided another value for sql_mode, don't replace it.
- unless variables.has_key?('sql_mode') || defaults.include?(@config[:strict])
- variables['sql_mode'] = strict_mode? ? 'STRICT_ALL_TABLES' : ''
+ if sql_mode = variables.delete('sql_mode')
+ sql_mode = quote(sql_mode)
+ elsif !defaults.include?(strict_mode?)
+ if strict_mode?
+ sql_mode = "CONCAT(@@sql_mode, ',STRICT_ALL_TABLES')"
+ else
+ sql_mode = "REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', '')"
+ sql_mode = "REPLACE(#{sql_mode}, 'STRICT_ALL_TABLES', '')"
+ sql_mode = "REPLACE(#{sql_mode}, 'TRADITIONAL', '')"
+ end
+ sql_mode = "CONCAT(#{sql_mode}, ',NO_AUTO_VALUE_ON_ZERO')"
end
+ sql_mode_assignment = "@@SESSION.sql_mode = #{sql_mode}, " if sql_mode
# NAMES does not have an equals sign, see
# http://dev.mysql.com/doc/refman/5.7/en/set-statement.html#id944430
@@ -874,7 +857,13 @@ module ActiveRecord
end.compact.join(', ')
# ...and send them all in one query
- @connection.query "SET #{encoding} #{variable_assignments}"
+ @connection.query "SET #{encoding} #{sql_mode_assignment} #{variable_assignments}"
+ end
+
+ def column_definitions(table_name) # :nodoc:
+ execute_and_free("SHOW FULL FIELDS FROM #{quote_table_name(table_name)}", 'SCHEMA') do |result|
+ each_hash(result)
+ end
end
def extract_foreign_key_action(structure, name, action) # :nodoc:
@@ -894,8 +883,8 @@ module ActiveRecord
create_table_info_cache[table_name] ||= select_one("SHOW CREATE TABLE #{quote_table_name(table_name)}")["Create Table"]
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- MySQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args) # :nodoc:
+ MySQL::TableDefinition.new(*args)
end
def integer_to_sql(limit) # :nodoc:
@@ -940,8 +929,8 @@ module ActiveRecord
class MysqlString < Type::String # :nodoc:
def serialize(value)
case value
- when true then "1"
- when false then "0"
+ when true then MySQL::Quoting::QUOTED_TRUE
+ when false then MySQL::Quoting::QUOTED_FALSE
else super
end
end
@@ -950,8 +939,8 @@ module ActiveRecord
def cast_value(value)
case value
- when true then "1"
- when false then "0"
+ when true then MySQL::Quoting::QUOTED_TRUE
+ when false then MySQL::Quoting::QUOTED_FALSE
else super
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/column.rb b/activerecord/lib/active_record/connection_adapters/column.rb
index 10f908538f..ce2f9f1925 100644
--- a/activerecord/lib/active_record/connection_adapters/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/column.rb
@@ -5,7 +5,7 @@ module ActiveRecord
module ConnectionAdapters
# An abstract definition of a column in a table.
class Column
- attr_reader :name, :null, :sql_type_metadata, :default, :default_function, :collation
+ attr_reader :name, :default, :sql_type_metadata, :null, :table_name, :default_function, :collation, :comment
delegate :precision, :scale, :limit, :type, :sql_type, to: :sql_type_metadata, allow_nil: true
@@ -15,14 +15,15 @@ module ActiveRecord
# +default+ is the type-casted default value, such as +new+ in <tt>sales_stage varchar(20) default 'new'</tt>.
# +sql_type_metadata+ is various information about the type of the column
# +null+ determines if this column allows +NULL+ values.
- def initialize(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil)
+ def initialize(name, default, sql_type_metadata = nil, null = true, table_name = nil, default_function = nil, collation = nil, comment: nil)
@name = name.freeze
+ @table_name = table_name
@sql_type_metadata = sql_type_metadata
@null = null
@default = default
@default_function = default_function
@collation = collation
- @table_name = nil
+ @comment = comment
end
def has_default?
@@ -54,7 +55,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, name, default, sql_type_metadata, null, default_function, collation]
+ [self.class, name, default, sql_type_metadata, null, table_name, default_function, collation]
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
new file mode 100644
index 0000000000..fbab654112
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -0,0 +1,51 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module MySQL
+ module Quoting # :nodoc:
+ QUOTED_TRUE, QUOTED_FALSE = '1', '0'
+
+ def quote_column_name(name)
+ @quoted_column_names[name] ||= "`#{super.gsub('`', '``')}`"
+ end
+
+ def quote_table_name(name)
+ @quoted_table_names[name] ||= super.gsub('.', '`.`')
+ end
+
+ def quoted_true
+ QUOTED_TRUE
+ end
+
+ def unquoted_true
+ 1
+ end
+
+ def quoted_false
+ QUOTED_FALSE
+ end
+
+ def unquoted_false
+ 0
+ end
+
+ def quoted_date(value)
+ if supports_datetime_with_precision?
+ super
+ else
+ super.sub(/\.\d{6}\z/, '')
+ end
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+end
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 1e2c859af9..0384079da2 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb
@@ -2,6 +2,9 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class SchemaCreation < AbstractAdapter::SchemaCreation
+ delegate :quote, to: :@conn
+ private :quote
+
private
def visit_DropForeignKey(name)
@@ -22,6 +25,14 @@ module ActiveRecord
add_column_position!(change_column_sql, column_options(o.column))
end
+ def add_table_options!(create_sql, options)
+ super
+
+ if comment = options[:comment]
+ create_sql << " COMMENT #{quote(comment)}"
+ end
+ end
+
def column_options(o)
column_options = super
column_options[:charset] = o.charset
@@ -29,13 +40,21 @@ module ActiveRecord
end
def add_column_options!(sql, options)
- if options[:charset]
- sql << " CHARACTER SET #{options[:charset]}"
+ if charset = options[:charset]
+ sql << " CHARACTER SET #{charset}"
end
- if options[:collation]
- sql << " COLLATE #{options[:collation]}"
+
+ if collation = options[:collation]
+ sql << " COLLATE #{collation}"
end
+
super
+
+ if comment = options[:comment]
+ sql << " COMMENT #{quote(comment)}"
+ end
+
+ sql
end
def add_column_position!(sql, options)
@@ -44,12 +63,14 @@ module ActiveRecord
elsif options[:after]
sql << " AFTER #{quote_column_name(options[:after])}"
end
+
sql
end
def index_in_create(table_name, column_name, options)
- index_name, index_type, index_columns, _, _, index_using = @conn.add_index_options(table_name, column_name, options)
- "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns}) "
+ index_name, index_type, index_columns, _, _, index_using, comment = @conn.add_index_options(table_name, column_name, options)
+ index_option = " COMMENT #{quote(comment)}" if comment
+ "#{index_type} INDEX #{quote_column_name(index_name)} #{index_using} (#{index_columns})#{index_option} "
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 ccf5b6cadc..2ba9657f24 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -3,18 +3,13 @@ module ActiveRecord
module MySQL
module ColumnDumper
def column_spec_for_primary_key(column)
- spec = {}
if column.bigint?
- spec[:id] = ':bigint'
+ spec = { id: :bigint.inspect }
spec[:default] = schema_default(column) || 'nil' unless column.auto_increment?
- spec[:unsigned] = 'true' if column.unsigned?
- elsif column.auto_increment?
- spec[:unsigned] = 'true' if column.unsigned?
- return if spec.empty?
else
- spec[:id] = schema_type(column).inspect
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ spec = super
end
+ spec[:unsigned] = 'true' if column.unsigned?
spec
end
@@ -30,6 +25,10 @@ module ActiveRecord
private
+ def default_primary_key?(column)
+ super && column.auto_increment?
+ end
+
def schema_type(column)
if column.sql_type == 'tinyblob'
:blob
@@ -38,16 +37,12 @@ module ActiveRecord
end
end
- def schema_limit(column)
- super unless column.type == :boolean
- end
-
def schema_precision(column)
super unless /time/ === column.sql_type && column.precision == 0
end
def schema_collation(column)
- if column.collation && table_name = column.instance_variable_get(:@table_name)
+ 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"]
column.collation.inspect if column.collation != @table_collation_cache[table_name]
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 23b33a3555..ec343a5a57 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -16,7 +16,7 @@ module ActiveRecord
if config[:flags].kind_of? Array
config[:flags].push "FOUND_ROWS".freeze
else
- config[:flags] |= Mysql2::Client::FOUND_ROWS
+ config[:flags] |= Mysql2::Client::FOUND_ROWS
end
end
@@ -45,6 +45,14 @@ module ActiveRecord
!mariadb? && version >= '5.7.8'
end
+ def supports_comments?
+ true
+ end
+
+ def supports_comments_in_create?
+ true
+ end
+
# HELPER METHODS ===========================================
def each_hash(result) # :nodoc:
@@ -131,13 +139,7 @@ module ActiveRecord
def exec_query(sql, name = 'SQL', binds = [], prepare: false)
result = execute(sql, name)
@connection.next_result while @connection.more_results?
- ActiveRecord::Result.new(result.fields, result.to_a)
- end
-
- alias exec_without_stmt exec_query
-
- def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
- execute to_sql(sql, binds), name
+ ActiveRecord::Result.new(result.fields, result.to_a) if result
end
def exec_delete(sql, name, binds)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
index bfa03fa136..3ad1911a28 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/column.rb
@@ -8,7 +8,6 @@ module ActiveRecord
def serial?
return unless default_function
- table_name = @table_name || '(?<table_name>.+)'
%r{\Anextval\('"?#{table_name}_#{name}_seq"?'::regclass\)\z} === default_function
end
end
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 6aa264d766..6f2e03b370 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -118,7 +118,7 @@ module ActiveRecord
alias :exec_update :exec_delete
def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
- unless pk
+ if pk.nil?
# Extract the table from the insert sql. Yuck.
table_ref = extract_table_ref_from_insert_sql(sql)
pk = primary_key(table_ref) if table_ref
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index c1c77a967e..6414459cd1 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -27,8 +27,8 @@ module ActiveRecord
# - schema_name."table.name"
# - "schema.name".table_name
# - "schema.name"."table.name"
- def quote_table_name(name)
- Utils.extract_schema_qualified_name(name.to_s).quoted
+ def quote_table_name(name) # :nodoc:
+ @quoted_table_names[name] ||= Utils.extract_schema_qualified_name(name.to_s).quoted
end
# Quotes schema names for use in SQL queries.
@@ -41,8 +41,8 @@ module ActiveRecord
end
# Quotes column names for use in SQL queries.
- def quote_column_name(name) #:nodoc:
- PGconn.quote_ident(name.to_s)
+ def quote_column_name(name) # :nodoc:
+ @quoted_column_names[name] ||= PGconn.quote_ident(super)
end
# Quote date/time values for use in SQL input.
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index b82bdb8b0c..a1e10fd364 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -3,16 +3,9 @@ module ActiveRecord
module PostgreSQL
module ColumnDumper
def column_spec_for_primary_key(column)
- spec = {}
- if column.serial?
- return unless column.bigint?
- spec[:id] = ':bigserial'
- elsif column.type == :uuid
- spec[:id] = ':uuid'
- spec[:default] = schema_default(column) || 'nil'
- else
- spec[:id] = schema_type(column).inspect
- spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
+ spec = super
+ if schema_type(column) == :uuid
+ spec[:default] ||= 'nil'
end
spec
end
@@ -31,6 +24,10 @@ module ActiveRecord
private
+ def default_primary_key?(column)
+ schema_type(column) == :serial
+ end
+
def schema_type(column)
return super unless column.serial?
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 67e727d8ed..4a66b82cbb 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/string/strip'
+
module ActiveRecord
module ConnectionAdapters
module PostgreSQL
@@ -172,7 +174,8 @@ module ActiveRecord
table = Utils.extract_schema_qualified_name(table_name.to_s)
result = query(<<-SQL, 'SCHEMA')
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
+ SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid,
+ pg_catalog.obj_description(i.oid, 'pg_class') AS comment
FROM pg_class t
INNER JOIN pg_index d ON t.oid = d.indrelid
INNER JOIN pg_class i ON d.indexrelid = i.oid
@@ -190,6 +193,7 @@ module ActiveRecord
indkey = row[2].split(" ").map(&:to_i)
inddef = row[3]
oid = row[4]
+ comment = row[5]
columns = Hash[query(<<-SQL, "SCHEMA")]
SELECT a.attnum, a.attname
@@ -207,26 +211,41 @@ module ActiveRecord
where = inddef.scan(/WHERE (.+)$/).flatten[0]
using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
- IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using)
+ IndexDefinition.new(table_name, index_name, unique, column_names, [], orders, where, nil, using, comment)
end
end.compact
end
# Returns the list of all column definitions for a table.
- def columns(table_name)
- # Limit, precision, and scale are all handled by the superclass.
- column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation|
+ def columns(table_name) # :nodoc:
+ table_name = table_name.to_s
+ column_definitions(table_name).map do |column_name, type, default, notnull, oid, fmod, collation, comment|
oid = oid.to_i
fmod = fmod.to_i
type_metadata = fetch_type_metadata(column_name, type, oid, fmod)
default_value = extract_value_from_default(default)
default_function = extract_default_function(default_value, default)
- new_column(column_name, default_value, type_metadata, !notnull, default_function, collation)
+ new_column(column_name, default_value, type_metadata, !notnull, table_name, default_function, collation, comment: comment)
end
end
- def new_column(name, default, sql_type_metadata = nil, null = true, default_function = nil, collation = nil) # :nodoc:
- PostgreSQLColumn.new(name, default, sql_type_metadata, null, default_function, collation)
+ def new_column(*args) # :nodoc:
+ PostgreSQLColumn.new(*args)
+ end
+
+ # Returns a comment stored in database for given table
+ def table_comment(table_name) # :nodoc:
+ name = Utils.extract_schema_qualified_name(table_name.to_s)
+ if name.identifier
+ select_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
+ WHERE c.relname = #{quote(name.identifier)}
+ AND c.relkind IN ('r') -- (r)elation/table
+ AND n.nspname = #{name.schema ? quote(name.schema) : 'ANY (current_schemas(false))'}
+ SQL
+ end
end
# Returns the current database name.
@@ -325,7 +344,7 @@ module ActiveRecord
select_value("SELECT setval('#{quoted_sequence}', #{value})", 'SCHEMA')
else
- @logger.warn "#{table} has primary key #{pk} with no default sequence" if @logger
+ @logger.warn "#{table} has primary key #{pk} with no default sequence." if @logger
end
end
end
@@ -340,7 +359,7 @@ module ActiveRecord
end
if @logger && pk && !sequence
- @logger.warn "#{table} has primary key #{pk} with no default sequence"
+ @logger.warn "#{table} has primary key #{pk} with no default sequence."
end
if pk && sequence
@@ -445,6 +464,7 @@ module ActiveRecord
def add_column(table_name, column_name, type, options = {}) #:nodoc:
clear_cache!
super
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
def change_column(table_name, column_name, type, options = {}) #:nodoc:
@@ -466,6 +486,7 @@ module ActiveRecord
change_column_default(table_name, column_name, options[:default]) if options_include_default?(options)
change_column_null(table_name, column_name, options[:null], options[:default]) if options.key?(:null)
+ change_column_comment(table_name, column_name, options[:comment]) if options.key?(:comment)
end
# Changes the default value of a table column.
@@ -494,6 +515,18 @@ module ActiveRecord
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
+ # Adds comment for given table column or drops it if +comment+ is a +nil+
+ def change_column_comment(table_name, column_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON COLUMN #{quote_table_name(table_name)}.#{quote_column_name(column_name)} IS #{quote(comment)}"
+ end
+
+ # Adds comment for given table or drops it if +comment+ is a +nil+
+ def change_table_comment(table_name, comment) # :nodoc:
+ clear_cache!
+ execute "COMMENT ON TABLE #{quote_table_name(table_name)} IS #{quote(comment)}"
+ end
+
# Renames a column in a table.
def rename_column(table_name, column_name, new_column_name) #:nodoc:
clear_cache!
@@ -502,8 +535,10 @@ module ActiveRecord
end
def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}"
+ index_name, index_type, index_columns, index_options, index_algorithm, index_using, comment = add_index_options(table_name, column_name, options)
+ execute("CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns})#{index_options}").tap do
+ execute "COMMENT ON INDEX #{quote_column_name(index_name)} IS #{quote(comment)}" if comment
+ end
end
def remove_index(table_name, options = {}) #:nodoc:
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index beaeef3c78..d7f9378a4e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -159,6 +159,14 @@ module ActiveRecord
postgresql_version >= 90200
end
+ def supports_comments?
+ true
+ end
+
+ def supports_comments_in_create?
+ false
+ end
+
def index_algorithms
{ concurrently: 'CONCURRENTLY' }
end
@@ -398,6 +406,7 @@ module ActiveRecord
protected
# See http://www.postgresql.org/docs/current/static/errcodes-appendix.html
+ VALUE_LIMIT_VIOLATION = "22001"
FOREIGN_KEY_VIOLATION = "23503"
UNIQUE_VIOLATION = "23505"
@@ -409,6 +418,8 @@ module ActiveRecord
RecordNotUnique.new(message)
when FOREIGN_KEY_VIOLATION
InvalidForeignKey.new(message)
+ when VALUE_LIMIT_VIOLATION
+ ValueTooLong.new(message)
else
super
end
@@ -598,25 +609,41 @@ module ActiveRecord
@connection.exec_prepared(stmt_key, type_casted_binds)
end
rescue ActiveRecord::StatementInvalid => e
- pgerror = e.cause
+ raise unless is_cached_plan_failure?(e)
- # Get the PG code for the failure. Annoyingly, the code for
- # prepared statements whose return value may have changed is
- # FEATURE_NOT_SUPPORTED. Check here for more details:
- # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
- begin
- code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
- rescue
- raise e
- end
- if FEATURE_NOT_SUPPORTED == code
+ # Nothing we can do if we are in a transaction because all commands
+ # will raise InFailedSQLTransaction
+ if in_transaction?
+ raise ActiveRecord::PreparedStatementCacheExpired.new(e.cause.message)
+ else
+ # outside of transactions we can simply flush this query and retry
@statements.delete sql_key(sql)
retry
- else
- raise e
end
end
+ # Annoyingly, the code for prepared statements whose return value may
+ # have changed is FEATURE_NOT_SUPPORTED.
+ #
+ # This covers various different error types so we need to do additional
+ # work to classify the exception definitively as a
+ # ActiveRecord::PreparedStatementCacheExpired
+ #
+ # Check here for more details:
+ # http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob;f=src/backend/utils/cache/plancache.c#l573
+ CACHED_PLAN_HEURISTIC = 'cached plan must not change result type'.freeze
+ def is_cached_plan_failure?(e)
+ pgerror = e.cause
+ code = pgerror.result.result_error_field(PGresult::PG_DIAG_SQLSTATE)
+ code == FEATURE_NOT_SUPPORTED && pgerror.message.include?(CACHED_PLAN_HEURISTIC)
+ rescue
+ false
+ end
+
+ def in_transaction?
+ open_transactions > 0
+ end
+
# Returns the statement identifier for the client side cache
# of statements
def sql_key(sql)
@@ -696,7 +723,7 @@ module ActiveRecord
# Returns the list of a table's column names, data types, and default values.
#
# The underlying query is roughly:
- # SELECT column.name, column.type, default.value
+ # SELECT column.name, column.type, default.value, column.comment
# FROM column LEFT JOIN default
# ON column.table_id = default.table_id
# AND column.num = default.column_num
@@ -716,7 +743,8 @@ module ActiveRecord
SELECT a.attname, format_type(a.atttypid, a.atttypmod),
pg_get_expr(d.adbin, d.adrelid), a.attnotnull, a.atttypid, a.atttypmod,
(SELECT c.collname FROM pg_collation c, pg_type t
- WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation)
+ WHERE c.oid = a.attcollation AND t.oid = a.atttypid AND a.attcollation <> t.typcollation),
+ col_description(a.attrelid, a.attnum) AS comment
FROM pg_attribute a LEFT JOIN pg_attrdef d
ON a.attrelid = d.adrelid AND a.attnum = d.adnum
WHERE a.attrelid = '#{quote_table_name(table_name)}'::regclass
@@ -730,8 +758,8 @@ module ActiveRecord
$1.strip if $1
end
- def create_table_definition(name, temporary = false, options = nil, as = nil) # :nodoc:
- PostgreSQL::TableDefinition.new(name, temporary, options, as)
+ def create_table_definition(*args) # :nodoc:
+ PostgreSQL::TableDefinition.new(*args)
end
def can_perform_case_insensitive_comparison_for?(column)
@@ -799,7 +827,7 @@ module ActiveRecord
ActiveRecord::Type.register(:bit_varying, OID::BitVarying, adapter: :postgresql)
ActiveRecord::Type.register(:binary, OID::Bytea, adapter: :postgresql)
ActiveRecord::Type.register(:cidr, OID::Cidr, adapter: :postgresql)
- ActiveRecord::Type.register(:date_time, OID::DateTime, adapter: :postgresql)
+ ActiveRecord::Type.register(:datetime, OID::DateTime, adapter: :postgresql)
ActiveRecord::Type.register(:decimal, OID::Decimal, adapter: :postgresql)
ActiveRecord::Type.register(:enum, OID::Enum, adapter: :postgresql)
ActiveRecord::Type.register(:hstore, OID::Hstore, adapter: :postgresql)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
new file mode 100644
index 0000000000..d5a181d3e2
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -0,0 +1,48 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module SQLite3
+ module Quoting # :nodoc:
+ def quote_string(s)
+ @connection.class.quote(s)
+ end
+
+ def quote_table_name_for_assignment(table, attr)
+ quote_column_name(attr)
+ end
+
+ def quote_column_name(name)
+ @quoted_column_names[name] ||= %Q("#{super.gsub('"', '""')}")
+ end
+
+ def quoted_time(value)
+ quoted_date(value)
+ end
+
+ private
+
+ def _quote(value)
+ if value.is_a?(Type::Binary::Data)
+ "x'#{value.hex}'"
+ else
+ super
+ end
+ end
+
+ def _type_cast(value)
+ case value
+ when BigDecimal
+ value.to_f
+ when String
+ if value.encoding == Encoding::ASCII_8BIT
+ super(value.encode(Encoding::UTF_8))
+ else
+ super
+ end
+ else
+ super
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
index fe1dcbd710..70c0d28830 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/schema_creation.rb
@@ -3,6 +3,13 @@ module ActiveRecord
module SQLite3
class SchemaCreation < AbstractAdapter::SchemaCreation
private
+
+ def column_options(o)
+ options = super
+ options[:null] = false if o.primary_key
+ options
+ end
+
def add_column_options!(sql, options)
if options[:collation]
sql << " COLLATE \"#{options[:collation]}\""
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index c65d33ccb3..786b0ab2ed 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -1,6 +1,7 @@
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'
gem 'sqlite3', '~> 1.3.6'
@@ -49,6 +50,8 @@ module ActiveRecord
# * <tt>:database</tt> - Path to the database file.
class SQLite3Adapter < AbstractAdapter
ADAPTER_NAME = 'SQLite'.freeze
+
+ include SQLite3::Quoting
include Savepoints
NATIVE_DATABASE_TYPES = {
@@ -84,7 +87,6 @@ module ActiveRecord
@statements = StatementPool.new(self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
@visitor = Arel::Visitors::SQLite.new self
- @quoted_column_names = {}
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
@@ -174,44 +176,6 @@ module ActiveRecord
true
end
- # QUOTING ==================================================
-
- def _quote(value) # :nodoc:
- case value
- when Type::Binary::Data
- "x'#{value.hex}'"
- else
- super
- end
- end
-
- def _type_cast(value) # :nodoc:
- case value
- when BigDecimal
- value.to_f
- when String
- if value.encoding == Encoding::ASCII_8BIT
- super(value.encode(Encoding::UTF_8))
- else
- super
- end
- else
- super
- end
- end
-
- def quote_string(s) #:nodoc:
- @connection.class.quote(s)
- end
-
- def quote_table_name_for_assignment(table, attr)
- quote_column_name(attr)
- end
-
- def quote_column_name(name) #:nodoc:
- @quoted_column_names[name] ||= %Q("#{name.to_s.gsub('"', '""')}")
- end
-
#--
# DATABASE STATEMENTS ======================================
#++
@@ -337,7 +301,8 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name) #:nodoc:
+ def columns(table_name) # :nodoc:
+ table_name = table_name.to_s
table_structure(table_name).map do |field|
case field["dflt_value"]
when /^null$/i
@@ -351,7 +316,7 @@ module ActiveRecord
collation = field['collation']
sql_type = field['type']
type_metadata = fetch_type_metadata(sql_type)
- new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, nil, collation)
+ new_column(field['name'], field['dflt_value'], type_metadata, field['notnull'].to_i == 0, table_name, nil, collation)
end
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 24fd0aaecf..5d74631e32 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -72,6 +72,14 @@ module ActiveRecord
##
# :singleton-method:
+ # Specifies if an error should be raised on query limit or order being
+ # ignored when doing batch queries. Useful in applications where the
+ # limit or scope being ignored is error-worthy, rather than a warning.
+ mattr_accessor :error_on_ignored_order_or_limit, instance_writer: false
+ self.error_on_ignored_order_or_limit = false
+
+ ##
+ # :singleton-method:
# Specify whether or not to use timestamps for migration versions
mattr_accessor :timestamped_migrations, instance_writer: false
self.timestamped_migrations = true
@@ -128,7 +136,7 @@ module ActiveRecord
end
def initialize_find_by_cache # :nodoc:
- @find_by_statement_cache = {}.extend(Mutex_m)
+ @find_by_statement_cache = { true => {}.extend(Mutex_m), false => {}.extend(Mutex_m) }
end
def inherited(child_class) # :nodoc:
@@ -151,7 +159,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -272,8 +280,9 @@ module ActiveRecord
private
def cached_find_by_statement(key, &block) # :nodoc:
- @find_by_statement_cache[key] || @find_by_statement_cache.synchronize {
- @find_by_statement_cache[key] ||= StatementCache.create(connection, &block)
+ cache = @find_by_statement_cache[connection.prepared_statements]
+ cache[key] || cache.synchronize {
+ cache[key] ||= StatementCache.create(connection, &block)
}
end
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 903c63a7db..7be332fb97 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -152,7 +152,7 @@ module ActiveRecord
enum_values = ActiveSupport::HashWithIndifferentAccess.new
name = name.to_sym
- # def self.statuses statuses end
+ # def self.statuses() statuses end
detect_enum_conflict!(name, name.to_s.pluralize, true)
klass.singleton_class.send(:define_method, name.to_s.pluralize) { enum_values }
diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb
index 87f32c042c..b8b8684cff 100644
--- a/activerecord/lib/active_record/errors.rb
+++ b/activerecord/lib/active_record/errors.rb
@@ -125,6 +125,10 @@ module ActiveRecord
class InvalidForeignKey < WrappedDatabaseException
end
+ # Raised when a record cannot be inserted or updated because a value too long for a column type.
+ class ValueTooLong < StatementInvalid
+ end
+
# Raised when number of bind variables in statement given to +:condition+ key
# (for example, when using {ActiveRecord::Base.find}[rdoc-ref:FinderMethods#find] method)
# does not match number of expected values supplied.
@@ -139,6 +143,11 @@ module ActiveRecord
class NoDatabaseError < StatementInvalid
end
+ # Raised when Postgres returns 'cached plan must not change result type' and
+ # we cannot retry gracefully (e.g. inside a transaction)
+ class PreparedStatementCacheExpired < StatementInvalid
+ end
+
# Raised on attempt to save stale record. Record is stale when it's being saved in another query after
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
# the page before the other.
diff --git a/activerecord/lib/active_record/fixture_set/file.rb b/activerecord/lib/active_record/fixture_set/file.rb
index f969556c50..e4a44244e2 100644
--- a/activerecord/lib/active_record/fixture_set/file.rb
+++ b/activerecord/lib/active_record/fixture_set/file.rb
@@ -52,9 +52,15 @@ module ActiveRecord
end
end
+ def prepare_erb(content)
+ erb = ERB.new(content)
+ erb.filename = @file
+ erb
+ end
+
def render(content)
context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
- ERB.new(content).result(context.get_binding)
+ prepare_erb(content).result(context.get_binding)
end
# Validate our unmarshalled data.
diff --git a/activerecord/lib/active_record/gem_version.rb b/activerecord/lib/active_record/gem_version.rb
index aa1f5c4fb4..73be4cb271 100644
--- a/activerecord/lib/active_record/gem_version.rb
+++ b/activerecord/lib/active_record/gem_version.rb
@@ -8,7 +8,7 @@ module ActiveRecord
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "beta2"
+ PRE = "beta3"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activerecord/lib/active_record/log_subscriber.rb b/activerecord/lib/active_record/log_subscriber.rb
index b63caa4473..efa2a4df02 100644
--- a/activerecord/lib/active_record/log_subscriber.rb
+++ b/activerecord/lib/active_record/log_subscriber.rb
@@ -67,7 +67,7 @@ module ActiveRecord
case sql
when /\A\s*rollback/mi
RED
- when /\s*.*?select .*for update/mi, /\A\s*lock/mi
+ when /select .*for update/mi, /\A\s*lock/mi
WHITE
when /\A\s*select/i
BLUE
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 4419a7b1e7..99a79024ad 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -156,8 +156,8 @@ 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 = "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 << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
super(msg)
end
@@ -528,7 +528,7 @@ module ActiveRecord
name = "V#{version.tr('.', '_')}"
unless Compatibility.const_defined?(name)
versions = Compatibility.constants.grep(/\AV[0-9_]+\z/).map { |s| s.to_s.delete('V').tr('_', '.').inspect }
- raise "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
+ raise ArgumentError, "Unknown migration version #{version.inspect}; expected one of #{versions.sort.join(', ')}"
end
Compatibility.const_get(name)
end
@@ -540,7 +540,7 @@ module ActiveRecord
MigrationFilenameRegexp = /\A([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?\.rb\z/ # :nodoc:
# This class is used to verify that all migrations have been run before
- # loading a web page if config.active_record.migration_error is set to :page_load
+ # loading a web page if <tt>config.active_record.migration_error</tt> is set to :page_load
class CheckPending
def initialize(app)
@app = app
diff --git a/activerecord/lib/active_record/migration/compatibility.rb b/activerecord/lib/active_record/migration/compatibility.rb
index 45e35a4f71..a20d7e0820 100644
--- a/activerecord/lib/active_record/migration/compatibility.rb
+++ b/activerecord/lib/active_record/migration/compatibility.rb
@@ -57,7 +57,7 @@ module ActiveRecord
def index_exists?(table_name, column_name, options = {})
column_names = Array(column_name).map(&:to_s)
options[:name] =
- if options.key?(:name).present?
+ if options[:name].present?
options[:name].to_s
else
index_name(table_name, column: column_names)
@@ -102,7 +102,7 @@ module ActiveRecord
module Legacy
include FourTwoShared
- def run(*)
+ def migrate(*)
ActiveSupport::Deprecation.warn \
"Directly inheriting from ActiveRecord::Migration is deprecated. " \
"Please specify the Rails release the migration was written for:\n" \
diff --git a/activerecord/lib/active_record/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index ee52c3ae02..52eab952e1 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -231,6 +231,18 @@ module ActiveRecord
@explicit_sequence_name = true
end
+ # Determines if the primary key values should be selected from their
+ # corresponding sequence before the insert statement.
+ def prefetch_primary_key?
+ connection.prefetch_primary_key?(table_name)
+ end
+
+ # Returns the next value that will be used as the primary key on
+ # an insert statment.
+ def next_sequence_value
+ connection.next_sequence_value(sequence_name)
+ end
+
# Indicates whether the table associated with this class exists
def table_exists?
connection.schema_cache.data_source_exists?(table_name)
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 0d5a8e6f25..fe68869143 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -195,19 +195,27 @@ module ActiveRecord
# Nested attributes for an associated collection can also be passed in
# the form of a hash of hashes instead of an array of hashes:
#
- # Member.create(name: 'joe',
- # posts_attributes: { first: { title: 'Foo' },
- # second: { title: 'Bar' } })
+ # Member.create(
+ # name: 'joe',
+ # posts_attributes: {
+ # first: { title: 'Foo' },
+ # second: { title: 'Bar' }
+ # }
+ # )
#
# has the same effect as
#
- # Member.create(name: 'joe',
- # posts_attributes: [ { title: 'Foo' },
- # { title: 'Bar' } ])
+ # Member.create(
+ # name: 'joe',
+ # posts_attributes: [
+ # { title: 'Foo' },
+ # { title: 'Bar' }
+ # ]
+ # )
#
# The keys of the hash which is the value for +:posts_attributes+ are
# ignored in this case.
- # However, it is not allowed to use +'id'+ or +:id+ for one of
+ # However, it is not allowed to use <tt>'id'</tt> or <tt>:id</tt> for one of
# such keys, otherwise the hash will be wrapped in an array and
# interpreted as an attribute hash for a single post.
#
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 0b500346bc..1ab4e0404f 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -1,7 +1,7 @@
module ActiveRecord
module NullRelation # :nodoc:
def exec_queries
- @records = []
+ @records = [].freeze
end
def pluck(*column_names)
diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb
index d9a394fb71..afed5e5e85 100644
--- a/activerecord/lib/active_record/persistence.rb
+++ b/activerecord/lib/active_record/persistence.rb
@@ -61,7 +61,7 @@ module ActiveRecord
# +instantiate+ instead of +new+, finder methods ensure they get new
# instances of the appropriate class for each record.
#
- # See +ActiveRecord::Inheritance#discriminate_class_for_record+ to see
+ # See <tt>ActiveRecord::Inheritance#discriminate_class_for_record</tt> to see
# how this "single-table" inheritance mapping is implemented.
def instantiate(attributes, column_types = {})
klass = discriminate_class_for_record(attributes)
diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb
index dcb2bd3d84..bbbc824b25 100644
--- a/activerecord/lib/active_record/query_cache.rb
+++ b/activerecord/lib/active_record/query_cache.rb
@@ -23,34 +23,30 @@ module ActiveRecord
end
end
- def initialize(app)
- @app = app
- end
-
- def call(env)
+ def self.run
connection = ActiveRecord::Base.connection
enabled = connection.query_cache_enabled
connection_id = ActiveRecord::Base.connection_id
connection.enable_query_cache!
- response = @app.call(env)
- response[2] = Rack::BodyProxy.new(response[2]) do
- restore_query_cache_settings(connection_id, enabled)
- end
-
- response
- rescue Exception => e
- restore_query_cache_settings(connection_id, enabled)
- raise e
+ [enabled, connection_id]
end
- private
+ def self.complete(state)
+ enabled, connection_id = state
- def restore_query_cache_settings(connection_id, enabled)
ActiveRecord::Base.connection_id = connection_id
ActiveRecord::Base.connection.clear_query_cache
ActiveRecord::Base.connection.disable_query_cache! unless enabled
end
+ def self.install_executor_hooks(executor = ActiveSupport::Executor)
+ executor.register_hook(self)
+
+ executor.to_complete do
+ # FIXME: This should be skipped when env['rack.test']
+ ActiveRecord::Base.clear_active_connections!
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index de5b42e987..4e32d73001 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -1,6 +1,6 @@
module ActiveRecord
module Querying
- delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, to: :all
+ delegate :find, :take, :take!, :first, :first!, :last, :last!, :exists?, :any?, :many?, :empty?, :none?, :one?, to: :all
delegate :second, :second!, :third, :third!, :fourth, :fourth!, :fifth, :fifth!, :forty_two, :forty_two!, :third_to_last, :third_to_last!, :second_to_last, :second_to_last!, to: :all
delegate :first_or_create, :first_or_create!, :first_or_initialize, to: :all
delegate :find_or_create_by, :find_or_create_by!, :find_or_initialize_by, to: :all
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index f4200e96b7..98ea425d16 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -16,12 +16,6 @@ module ActiveRecord
config.app_generators.orm :active_record, :migration => true,
:timestamps => true
- config.app_middleware.insert_after ::ActionDispatch::Callbacks,
- ActiveRecord::QueryCache
-
- config.app_middleware.insert_after ::ActionDispatch::Callbacks,
- ActiveRecord::ConnectionAdapters::ConnectionManagement
-
config.action_dispatch.rescue_responses.merge!(
'ActiveRecord::RecordNotFound' => :not_found,
'ActiveRecord::StaleObjectError' => :conflict,
@@ -71,7 +65,6 @@ module ActiveRecord
ActiveSupport.on_load(:active_record) do
self.time_zone_aware_attributes = true
self.default_timezone = :utc
- self.time_zone_aware_types = ActiveRecord::Base.time_zone_aware_types
end
end
@@ -153,11 +146,9 @@ end_warning
end
end
- initializer "active_record.set_reloader_hooks" do |app|
- hook = app.config.reload_classes_only_on_change ? :to_prepare : :to_cleanup
-
+ initializer "active_record.set_reloader_hooks" do
ActiveSupport.on_load(:active_record) do
- ActionDispatch::Reloader.send(hook) do
+ ActiveSupport::Reloader.before_class_unload do
if ActiveRecord::Base.connected?
ActiveRecord::Base.clear_cache!
ActiveRecord::Base.clear_reloadable_connections!
@@ -166,6 +157,12 @@ end_warning
end
end
+ initializer "active_record.set_executor_hooks" do
+ ActiveSupport.on_load(:active_record) do
+ ActiveRecord::QueryCache.install_executor_hooks
+ end
+ end
+
initializer "active_record.add_watchable_files" do |app|
path = app.paths["db"].first
config.watchable_files.concat ["#{path}/schema.rb", "#{path}/structure.sql"]
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 69a7838001..fc1b62ee96 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -22,7 +22,7 @@ db_namespace = namespace :db do
end
end
- desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV, it defaults to creating the development and test databases.'
+ desc 'Creates the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:create:all to create all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to creating the development and test databases.'
task :create => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.create_current
end
@@ -33,7 +33,7 @@ db_namespace = namespace :db do
end
end
- desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV, it defaults to dropping the development and test databases.'
+ desc 'Drops the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:drop:all to drop all databases in the config). Without RAILS_ENV or when RAILS_ENV is development, it defaults to dropping the development and test databases.'
task :drop => [:load_config, :check_protected_environments] do
db_namespace["drop:_unsafe"].invoke
end
@@ -256,7 +256,7 @@ db_namespace = namespace :db do
end
desc 'Loads a schema.rb file into the database'
- task :load => [:environment, :load_config] do
+ task :load => [:environment, :load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:ruby, ENV['SCHEMA'])
end
@@ -278,7 +278,7 @@ db_namespace = namespace :db do
desc 'Clears a db/schema_cache.dump file.'
task :clear => [:environment, :load_config] do
filename = File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "schema_cache.dump")
- FileUtils.rm(filename) if File.exist?(filename)
+ rm_f filename, verbose: false
end
end
@@ -302,7 +302,7 @@ db_namespace = namespace :db do
end
desc "Recreates the databases from the structure.sql file"
- task :load => [:environment, :load_config] do
+ task :load => [:environment, :load_config, :check_protected_environments] do
ActiveRecord::Tasks::DatabaseTasks.load_schema_current(:sql, ENV['SCHEMA'])
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 956fe7c51e..bf398b0d40 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -138,6 +138,10 @@ module ActiveRecord
# PolymorphicReflection
# RuntimeReflection
class AbstractReflection # :nodoc:
+ def through_reflection?
+ false
+ end
+
def table_name
klass.table_name
end
@@ -445,6 +449,10 @@ module ActiveRecord
scope ? [[scope]] : [[]]
end
+ def has_scope?
+ scope
+ end
+
def has_inverse?
inverse_name
end
@@ -700,6 +708,10 @@ module ActiveRecord
@source_reflection_name = delegate_reflection.options[:source]
end
+ def through_reflection?
+ true
+ end
+
def klass
@klass ||= delegate_reflection.compute_class(class_name)
end
@@ -765,7 +777,6 @@ module ActiveRecord
# This is for clearing cache on the reflection. Useful for tests that need to compare
# SQL queries on associations.
def clear_association_scope_cache # :nodoc:
- @chain = nil
delegate_reflection.clear_association_scope_cache
source_reflection.clear_association_scope_cache
through_reflection.clear_association_scope_cache
@@ -812,13 +823,19 @@ module ActiveRecord
end
end
+ def has_scope?
+ scope || options[:source_type] ||
+ source_reflection.has_scope? ||
+ through_reflection.has_scope?
+ end
+
def join_keys(association_klass)
source_reflection.join_keys(association_klass)
end
# A through association is nested if there would be more than one join table
def nested?
- chain.length > 2
+ source_reflection.through_reflection? || through_reflection.through_reflection?
end
# We want to use the klass from this reflection, rather than just delegate straight to
@@ -857,7 +874,7 @@ module ActiveRecord
example_options = options.dup
example_options[:source] = source_reflection_names.first
ActiveSupport::Deprecation.warn \
- "Ambiguous source reflection for through association. Please " \
+ "Ambiguous source reflection for through association. Please " \
"specify a :source directive on your declaration like:\n" \
"\n" \
" class #{active_record.name} < ActiveRecord::Base\n" \
@@ -995,7 +1012,7 @@ module ActiveRecord
end
def constraints
- [source_type_info]
+ @reflection.constraints + [source_type_info]
end
def source_type_info
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 7e842668c6..d042fe5f8b 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -45,8 +45,8 @@ module ActiveRecord
k.name == primary_key
}]
- if !primary_key_value && connection.prefetch_primary_key?(klass.table_name)
- primary_key_value = connection.next_sequence_value(klass.sequence_name)
+ if !primary_key_value && klass.prefetch_primary_key?
+ primary_key_value = klass.next_sequence_value
values[arel_attribute(klass.primary_key)] = primary_key_value
end
end
@@ -94,12 +94,12 @@ module ActiveRecord
end
def substitute_values(values) # :nodoc:
- binds = values.map do |arel_attr, value|
- QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
- end
+ binds = []
+ substitutes = []
- substitutes = values.map do |(arel_attr, _)|
- [arel_attr, Arel::Nodes::BindParam.new]
+ values.each do |arel_attr, value|
+ binds.push QueryAttribute.new(arel_attr.name, value, klass.type_for_attribute(arel_attr.name))
+ substitutes.push [arel_attr, Arel::Nodes::BindParam.new]
end
[substitutes, binds]
@@ -253,17 +253,21 @@ module ActiveRecord
# Converts relation objects to Array.
def to_a
+ records.dup
+ end
+
+ def records # :nodoc:
load
@records
end
# Serializes the relation objects Array.
def encode_with(coder)
- coder.represent_seq(nil, to_a)
+ coder.represent_seq(nil, records)
end
def as_json(options = nil) #:nodoc:
- to_a.as_json(options)
+ records.as_json(options)
end
# Returns size of the records.
@@ -298,13 +302,13 @@ module ActiveRecord
# Returns true if there is exactly one record.
def one?
return super if block_given?
- limit_value ? to_a.one? : size == 1
+ limit_value ? records.one? : size == 1
end
# Returns true if there is more than one record.
def many?
return super if block_given?
- limit_value ? to_a.many? : size > 1
+ limit_value ? records.many? : size > 1
end
# Returns a cache key that can be used to identify the records fetched by
@@ -418,13 +422,13 @@ module ActiveRecord
if id.is_a?(Array)
id.map.with_index { |one_id, idx| update(one_id, attributes[idx]) }
elsif id == :all
- to_a.each { |record| record.update(attributes) }
+ records.each { |record| record.update(attributes) }
else
if ActiveRecord::Base === id
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `update`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
object = find(id)
@@ -453,11 +457,11 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to destroy_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).destroy_all
+ To achieve the same use where(conditions).destroy_all.
MESSAGE
where(conditions).destroy_all
else
- to_a.each(&:destroy).tap { reset }
+ records.each(&:destroy).tap { reset }
end
end
@@ -523,7 +527,7 @@ module ActiveRecord
if conditions
ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
Passing conditions to delete_all is deprecated and will be removed in Rails 5.1.
- To achieve the same use where(conditions).delete_all
+ To achieve the same use where(conditions).delete_all.
MESSAGE
where(conditions).delete_all
else
@@ -587,7 +591,7 @@ module ActiveRecord
def reset
@last = @to_sql = @order_clause = @scope_for_create = @arel = @loaded = nil
@should_eager_load = @join_dependency = nil
- @records = []
+ @records = [].freeze
@offsets = {}
self
end
@@ -654,21 +658,21 @@ module ActiveRecord
def ==(other)
case other
when Associations::CollectionProxy, AssociationRelation
- self == other.to_a
+ self == other.records
when Relation
other.to_sql == to_sql
when Array
- to_a == other
+ records == other
end
end
def pretty_print(q)
- q.pp(self.to_a)
+ q.pp(self.records)
end
# Returns true if relation is blank.
def blank?
- to_a.blank?
+ records.blank?
end
def values
@@ -676,7 +680,7 @@ module ActiveRecord
end
def inspect
- entries = to_a.take([limit_value, 11].compact.min).map!(&:inspect)
+ entries = records.take([limit_value, 11].compact.min).map!(&:inspect)
entries[10] = '...' if entries.size == 11
"#<#{self.class.name} [#{entries.join(', ')}]>"
@@ -685,14 +689,14 @@ module ActiveRecord
protected
def load_records(records)
- @records = records
+ @records = records.freeze
@loaded = true
end
private
def exec_queries
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bound_attributes)
+ @records = eager_loading? ? find_with_associations.freeze : @klass.find_by_sql(arel, bound_attributes).freeze
preload = preload_values
preload += includes_values unless eager_loading?
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index de005e2810..3639625722 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -2,6 +2,8 @@ require "active_record/relation/batches/batch_enumerator"
module ActiveRecord
module Batches
+ ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
+
# Looping through a collection of records from the database
# (using the Scoping::Named::ClassMethods.all method, for example)
# is very inefficient since it will try to instantiate all the objects at once.
@@ -31,6 +33,9 @@ module ActiveRecord
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
+ #
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -48,13 +53,13 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_each(start: nil, finish: nil, batch_size: 1000)
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
if block_given?
- find_in_batches(start: start, finish: finish, batch_size: batch_size) do |records|
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
records.each { |record| yield record }
end
else
- enum_for(:find_each, start: start, finish: finish, batch_size: batch_size) do
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
relation = self
apply_limits(relation, start, finish).size
end
@@ -83,6 +88,9 @@ module ActiveRecord
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
+ #
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
@@ -100,16 +108,16 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_in_batches(start: nil, finish: nil, batch_size: 1000)
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
relation = self
unless block_given?
- return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size) do
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
total = apply_limits(relation, start, finish).size
(total - 1).div(batch_size) + 1
end
end
- in_batches(of: batch_size, start: start, finish: finish, load: true) do |batch|
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
yield batch.to_a
end
end
@@ -140,6 +148,8 @@ module ActiveRecord
# * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
# * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
# * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
+ # the order and limit have to be ignored due to batching.
#
# This is especially useful if you want to work with the
# ActiveRecord::Relation object instead of the array of records, or if
@@ -171,14 +181,14 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control the batch
# sizes.
- def in_batches(of: 1000, start: nil, finish: nil, load: false)
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
relation = self
unless block_given?
return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
end
- if logger && (arel.orders.present? || arel.taken.present?)
- logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
+ if arel.orders.present? || arel.taken.present?
+ act_on_order_or_limit_ignored(error_on_ignore)
end
relation = relation.reorder(batch_order).limit(of)
@@ -187,7 +197,7 @@ module ActiveRecord
loop do
if load
- records = batch_relation.to_a
+ records = batch_relation.records
ids = records.map(&:id)
yielded_relation = self.where(primary_key => ids)
yielded_relation.load_records(records)
@@ -219,5 +229,15 @@ module ActiveRecord
def batch_order
"#{quoted_table_name}.#{quoted_primary_key} ASC"
end
+
+ def act_on_order_or_limit_ignored(error_on_ignore)
+ raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
+
+ if raise_error
+ raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ elsif logger
+ logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
+ end
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
index c6e39814dd..13393dc605 100644
--- a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -35,7 +35,7 @@ module ActiveRecord
return to_enum(:each_record) unless block_given?
@relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
- relation.to_a.each { |record| yield record }
+ relation.records.each { |record| yield record }
end
end
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index e4e5d63006..f2578f5f96 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -37,7 +37,8 @@ module ActiveRecord
# for each different klass, and the delegations are compiled into that subclass only.
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
- :[], :&, :|, :+, :-, :sample, :shuffle, :reverse, :compact, to: :to_a
+ :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
+ :shuffle, :split, to: :records
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
:connection, :columns_hash, :to => :klass
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index a4280c5f33..e7e331f88d 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -42,10 +42,10 @@ module ActiveRecord
# Person.find_by(name: 'Spartacus', rating: 4)
# # returns the first item or nil.
#
- # Person.where(name: 'Spartacus', rating: 4).first_or_initialize
+ # Person.find_or_initialize_by(name: 'Spartacus', rating: 4)
# # returns the first item or returns a new instance (requires you call .save to persist against the database).
#
- # Person.where(name: 'Spartacus', rating: 4).first_or_create
+ # Person.find_or_create_by(name: 'Spartacus', rating: 4)
# # returns the first item or creates it and returns it.
#
# ==== Alternatives for #find
@@ -145,15 +145,21 @@ module ActiveRecord
#
# [#<Person id:4>, #<Person id:3>, #<Person id:2>]
def last(limit = nil)
- if limit
- if order_values.empty? && primary_key
- order(arel_attribute(primary_key).desc).limit(limit).reverse
- else
- to_a.last(limit)
- end
- else
- find_last
- end
+ return find_last(limit) if loaded? || limit_value
+
+ result = limit(limit || 1)
+ result.order!(arel_attribute(primary_key)) if order_values.empty? && primary_key
+ result = result.reverse_order!
+
+ limit ? result.reverse : result.first
+ rescue ActiveRecord::IrreversibleOrderError
+ ActiveSupport::Deprecation.warn(<<-WARNING.squish)
+ Finding a last element by loading the relation when SQL ORDER
+ can not be reversed is deprecated.
+ Rails 5.1 will raise ActiveRecord::IrreversibleOrderError in this case.
+ Please call `to_a.last` if you still want to load the relation.
+ WARNING
+ find_last(limit)
end
# Same as #last but raises ActiveRecord::RecordNotFound if no record
@@ -249,13 +255,13 @@ module ActiveRecord
# Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
# Person.where(["user_name = :u", { u: user_name }]).third_to_last
def third_to_last
- find_nth(-3)
+ find_nth_from_last 3
end
# Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def third_to_last!
- find_nth!(-3)
+ find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Find the second-to-last record.
@@ -265,13 +271,13 @@ module ActiveRecord
# Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
# Person.where(["user_name = :u", { u: user_name }]).second_to_last
def second_to_last
- find_nth(-2)
+ find_nth_from_last 2
end
# Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def second_to_last!
- find_nth!(-2)
+ find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
end
# Returns true if a record exists in the table that matches the +id+ or
@@ -306,7 +312,7 @@ module ActiveRecord
conditions = conditions.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `exists?`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -330,7 +336,7 @@ module ActiveRecord
end
# This method is called whenever no records are found with either a single
- # id or multiple ids and raises a ActiveRecord::RecordNotFound exception.
+ # id or multiple ids and raises an ActiveRecord::RecordNotFound exception.
#
# The error message is different depending on whether a single id or
# multiple ids are provided. If multiple ids are provided, then the number
@@ -461,7 +467,7 @@ module ActiveRecord
id = id.id
ActiveSupport::Deprecation.warn(<<-MSG.squish)
You are passing an instance of ActiveRecord::Base to `find`.
- Please pass the id of the object by calling `.id`
+ Please pass the id of the object by calling `.id`.
MSG
end
@@ -500,7 +506,7 @@ module ActiveRecord
def find_some_ordered(ids)
ids = ids.slice(offset_value || 0, limit_value || ids.size) || []
- result = except(:limit, :offset).where(primary_key => ids).to_a
+ result = except(:limit, :offset).where(primary_key => ids).records
if result.size == ids.size
pk_type = @klass.type_for_attribute(primary_key)
@@ -516,7 +522,7 @@ module ActiveRecord
if loaded?
@records.first
else
- @take ||= limit(1).to_a.first
+ @take ||= limit(1).records.first
end
end
@@ -555,19 +561,25 @@ module ActiveRecord
relation.limit(limit).to_a
end
- def find_last
+ def find_nth_from_last(index)
if loaded?
- @records.last
+ @records[-index]
else
- @last ||=
- if limit_value
- to_a.last
- else
- reverse_order.limit(1).to_a.first
- end
+ relation = if order_values.empty? && primary_key
+ order(arel_attribute(primary_key).asc)
+ else
+ self
+ end
+
+ relation.to_a[-index]
+ # TODO: can be made more performant on large result sets by
+ # for instance, last(index)[-index] (which would require
+ # refactoring the last(n) finder method to make test suite pass),
+ # or by using a combination of reverse_order, limit, and offset,
+ # e.g., reverse_order.offset(index-1).first
end
end
-
+
private
def find_nth_with_limit_and_offset(index, limit, offset:) # :nodoc:
@@ -578,5 +590,9 @@ module ActiveRecord
find_nth_with_limit(index, limit)
end
end
+
+ def find_last(limit)
+ limit ? records.last(limit) : records.last
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 0f88791d92..ecce949370 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -5,6 +5,7 @@ module ActiveRecord
require 'active_record/relation/predicate_builder/base_handler'
require 'active_record/relation/predicate_builder/basic_object_handler'
require 'active_record/relation/predicate_builder/class_handler'
+ require 'active_record/relation/predicate_builder/polymorphic_array_handler'
require 'active_record/relation/predicate_builder/range_handler'
require 'active_record/relation/predicate_builder/relation_handler'
@@ -22,6 +23,7 @@ module ActiveRecord
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
+ register_handler(PolymorphicArrayValue, PolymorphicArrayHandler.new(self))
end
def build_from_hash(attributes)
@@ -40,10 +42,7 @@ module ActiveRecord
#
# For polymorphic relationships, find the foreign key and type:
# PriceEstimate.where(estimate_of: treasure)
- if table.associated_with?(column)
- value = AssociationQueryValue.new(table.associated_table(column), value)
- end
-
+ value = AssociationQueryHandler.value_for(table, column, value) if table.associated_with?(column)
build(table.arel_attribute(column), value)
end
@@ -137,9 +136,11 @@ module ActiveRecord
end
def convert_dot_notation_to_hash(attributes)
- dot_notation = attributes.keys.select { |s| s.include?(".".freeze) }
+ dot_notation = attributes.select do |k, v|
+ k.include?(".".freeze) && !v.is_a?(Hash)
+ end
- dot_notation.each do |key|
+ dot_notation.each_key do |key|
table_name, column_name = key.split(".".freeze)
value = attributes.delete(key)
attributes[table_name] ||= {}
diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
index e81be63cd3..d7fd878265 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_handler.rb
@@ -1,6 +1,16 @@
module ActiveRecord
class PredicateBuilder
class AssociationQueryHandler # :nodoc:
+ def self.value_for(table, column, value)
+ klass = if table.associated_table(column).polymorphic_association? && ::Array === value && value.first.is_a?(Base)
+ PolymorphicArrayValue
+ else
+ AssociationQueryValue
+ end
+
+ klass.new(table.associated_table(column), value)
+ end
+
def initialize(predicate_builder)
@predicate_builder = predicate_builder
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
new file mode 100644
index 0000000000..b6c6240343
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb
@@ -0,0 +1,57 @@
+module ActiveRecord
+ class PredicateBuilder
+ class PolymorphicArrayHandler # :nodoc:
+ def initialize(predicate_builder)
+ @predicate_builder = predicate_builder
+ end
+
+ def call(attribute, value)
+ table = value.associated_table
+ queries = value.type_to_ids_mapping.map do |type, ids|
+ { table.association_foreign_type.to_s => type, table.association_foreign_key.to_s => ids }
+ end
+
+ predicates = queries.map { |query| predicate_builder.build_from_hash(query) }
+
+ if predicates.size > 1
+ type_and_ids_predicates = predicates.map { |type_predicate, id_predicate| Arel::Nodes::Grouping.new(type_predicate.and(id_predicate)) }
+ type_and_ids_predicates.inject(&:or)
+ else
+ predicates.first
+ end
+ end
+
+ protected
+
+ attr_reader :predicate_builder
+ end
+
+ class PolymorphicArrayValue # :nodoc:
+ attr_reader :associated_table, :values
+
+ def initialize(associated_table, values)
+ @associated_table = associated_table
+ @values = values
+ end
+
+ def type_to_ids_mapping
+ default_hash = Hash.new { |hsh, key| hsh[key] = [] }
+ values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) }
+ end
+
+ private
+
+ def primary_key(value)
+ associated_table.association_primary_key(base_class(value))
+ end
+
+ def base_class(value)
+ value.class.base_class
+ end
+
+ def convert_to_id(value)
+ value._read_attribute(primary_key(value))
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 91bfa4d131..4533f3263f 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -655,6 +655,10 @@ module ActiveRecord
# # SELECT `posts`.* FROM `posts` WHERE (('id = 1' OR 'author_id = 3'))
#
def or(other)
+ unless other.is_a? Relation
+ raise ArgumentError, "You have passed #{other.class.name} object to #or. Pass an ActiveRecord::Relation object instead."
+ end
+
spawn.or!(other)
end
@@ -1112,6 +1116,8 @@ module ActiveRecord
order_query.flat_map do |o|
case o
+ when Arel::Attribute
+ o.desc
when Arel::Nodes::Ordering
o.reverse
when String
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 67d7f83cb4..d5c18a2a4a 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -29,7 +29,7 @@ module ActiveRecord
# This is mainly intended for sharing common conditions between multiple associations.
def merge(other)
if other.is_a?(Array)
- to_a & other
+ records & other
elsif other
spawn.merge!(other)
else
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 2c2d6cfa47..89396b518c 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -158,8 +158,9 @@ module ActiveRecord
end
end
+ ARRAY_WITH_EMPTY_STRING = ['']
def non_empty_predicates
- predicates - ['']
+ predicates - ARRAY_WITH_EMPTY_STRING
end
def wrap_sql_literal(node)
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 2bfc5ff7ae..a9e1fd0dad 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -60,7 +60,7 @@ module ActiveRecord
end
# Accepts an array, or string of SQL conditions and sanitizes
- # them into a valid SQL fragment for a ORDER clause.
+ # them into a valid SQL fragment for an ORDER clause.
#
# sanitize_sql_for_order(["field(id, ?)", [1,3,2]])
# # => "field(id, 1,3,2)"
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index f115c7542b..b4229cba04 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -104,10 +104,7 @@ HEADER
end
def table(table, stream)
- columns = @connection.columns(table).map do |column|
- column.instance_variable_set(:@table_name, table)
- column
- end
+ columns = @connection.columns(table)
begin
tbl = StringIO.new
@@ -126,7 +123,7 @@ HEADER
tbl.print ", primary_key: #{pk.inspect}" unless pk == 'id'
pkcol = columns.detect { |c| c.name == pk }
pkcolspec = @connection.column_spec_for_primary_key(pkcol)
- if pkcolspec
+ if pkcolspec.present?
pkcolspec.each do |key, value|
tbl.print ", #{key}: #{value}"
end
@@ -141,6 +138,10 @@ HEADER
table_options = @connection.table_options(table)
tbl.print ", options: #{table_options.inspect}" unless table_options.blank?
+ if comment = @connection.table_comment(table)
+ tbl.print ", comment: #{comment.inspect}"
+ end
+
tbl.puts " do |t|"
# then dump all non-primary key columns
@@ -212,6 +213,7 @@ HEADER
statement_parts << "where: #{index.where.inspect}" if index.where
statement_parts << "using: #{index.using.inspect}" if index.using
statement_parts << "type: #{index.type.inspect}" if index.type
+ statement_parts << "comment: #{index.comment.inspect}" if index.comment
" #{statement_parts.join(', ')}"
end
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index f6b6768ce3..9eab59ac78 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -115,7 +115,8 @@ module ActiveRecord
base_rel ||= relation
evaluate_default_scope do
default_scopes.inject(base_rel) do |default_scope, scope|
- default_scope.merge(base_rel.scoping { scope.call })
+ scope = scope.respond_to?(:to_proc) ? scope : scope.method(:call)
+ default_scope.merge(base_rel.instance_exec(&scope))
end
end
end
diff --git a/activerecord/lib/active_record/suppressor.rb b/activerecord/lib/active_record/suppressor.rb
index b3644bf569..8ec4b48d31 100644
--- a/activerecord/lib/active_record/suppressor.rb
+++ b/activerecord/lib/active_record/suppressor.rb
@@ -37,7 +37,11 @@ module ActiveRecord
end
end
- def create_or_update(*args) # :nodoc:
+ def save(*) # :nodoc:
+ SuppressorRegistry.suppressed[self.class.name] ? true : super
+ end
+
+ def save!(*) # :nodoc:
SuppressorRegistry.suppressed[self.class.name] ? true : super
end
end
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 8f52e9068a..9aea5b360b 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -107,8 +107,9 @@ module ActiveRecord
def create(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).create
+ $stdout.puts "Created database '#{configuration['database']}'"
rescue DatabaseAlreadyExists
- $stderr.puts "#{configuration['database']} already exists"
+ $stderr.puts "Database '#{configuration['database']}' already exists"
rescue Exception => error
$stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
@@ -116,7 +117,11 @@ module ActiveRecord
end
def create_all
+ old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base)
each_local_configuration { |configuration| create configuration }
+ if old_pool
+ ActiveRecord::Base.connection_handler.establish_connection(ActiveRecord::Base, old_pool.spec)
+ end
end
def create_current(environment = env)
@@ -129,11 +134,12 @@ module ActiveRecord
def drop(*arguments)
configuration = arguments.first
class_for_adapter(configuration['adapter']).new(*arguments).drop
+ $stdout.puts "Dropped database '#{configuration['database']}'"
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
$stderr.puts error
- $stderr.puts "Couldn't drop #{configuration['database']}"
+ $stderr.puts "Couldn't drop database '#{configuration['database']}'"
raise
end
@@ -155,6 +161,7 @@ module ActiveRecord
Migrator.migrate(migrations_paths, version) do |migration|
scope.blank? || scope == migration.scope
end
+ ActiveRecord::Base.clear_cache!
ensure
Migration.verbose = verbose_was
end
@@ -278,8 +285,7 @@ module ActiveRecord
def each_current_configuration(environment)
environments = [environment]
- # add test environment only if no RAILS_ENV was specified.
- environments << 'test' if environment == 'development' && ENV['RAILS_ENV'].nil?
+ environments << 'test' if environment == 'development'
configurations = ActiveRecord::Base.configurations.values_at(*environments)
configurations.compact.each do |configuration|
diff --git a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
index 7a49322e06..af0c935342 100644
--- a/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/mysql_database_tasks.rb
@@ -130,7 +130,7 @@ IDENTIFIED BY '#{configuration['password']}' WITH GRANT OPTION;
'sslca' => '--ssl-ca',
'sslcert' => '--ssl-cert',
'sslcapath' => '--ssl-capath',
- 'sslcipher' => '--ssh-cipher',
+ 'sslcipher' => '--ssl-cipher',
'sslkey' => '--ssl-key'
}.map { |opt, arg| "#{arg}=#{configuration[opt]}" if configuration[opt] }.compact
diff --git a/activerecord/lib/active_record/type.rb b/activerecord/lib/active_record/type.rb
index e210e94f00..4911d93dd9 100644
--- a/activerecord/lib/active_record/type.rb
+++ b/activerecord/lib/active_record/type.rb
@@ -61,7 +61,7 @@ module ActiveRecord
register(:binary, Type::Binary, override: false)
register(:boolean, Type::Boolean, override: false)
register(:date, Type::Date, override: false)
- register(:date_time, Type::DateTime, override: false)
+ register(:datetime, Type::DateTime, override: false)
register(:decimal, Type::Decimal, override: false)
register(:float, Type::Float, override: false)
register(:integer, Type::Integer, override: false)
diff --git a/activerecord/lib/active_record/type/internal/abstract_json.rb b/activerecord/lib/active_record/type/internal/abstract_json.rb
index 097d1bd363..513c938088 100644
--- a/activerecord/lib/active_record/type/internal/abstract_json.rb
+++ b/activerecord/lib/active_record/type/internal/abstract_json.rb
@@ -17,11 +17,7 @@ module ActiveRecord
end
def serialize(value)
- if value.is_a?(::Array) || value.is_a?(::Hash)
- ::ActiveSupport::JSON.encode(value)
- else
- value
- end
+ ::ActiveSupport::JSON.encode(value)
end
def accessor
diff --git a/activerecord/lib/active_record/type/time.rb b/activerecord/lib/active_record/type/time.rb
index 70988d84ff..7da49e43c7 100644
--- a/activerecord/lib/active_record/type/time.rb
+++ b/activerecord/lib/active_record/type/time.rb
@@ -2,6 +2,18 @@ module ActiveRecord
module Type
class Time < ActiveModel::Type::Time
include Internal::Timezone
+
+ class Value < DelegateClass(::Time) # :nodoc:
+ end
+
+ def serialize(value)
+ case value = super
+ when ::Time
+ Value.new(value)
+ else
+ value
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb
index 6677e6dc5f..ecaf04e39e 100644
--- a/activerecord/lib/active_record/validations.rb
+++ b/activerecord/lib/active_record/validations.rb
@@ -45,7 +45,7 @@ module ActiveRecord
end
# Attempts to save the record just like {ActiveRecord::Base#save}[rdoc-ref:Base#save] but
- # will raise a ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
+ # will raise an ActiveRecord::RecordInvalid exception instead of returning +false+ if the record is not valid.
def save!(options={})
perform_validations(options) ? super : raise_validation_error
end
diff --git a/activerecord/lib/active_record/validations/absence.rb b/activerecord/lib/active_record/validations/absence.rb
index 2e19e6dc5c..641d041f3d 100644
--- a/activerecord/lib/active_record/validations/absence.rb
+++ b/activerecord/lib/active_record/validations/absence.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Validations
class AbsenceValidator < ActiveModel::Validations::AbsenceValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record)
if record.class._reflect_on_association(attribute)
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
index 69e048eef1..0e0cebce4a 100644
--- a/activerecord/lib/active_record/validations/length.rb
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -2,23 +2,11 @@ module ActiveRecord
module Validations
class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record) || associations_are_dirty?(record)
if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
end
super
end
-
- def associations_are_dirty?(record)
- attributes.any? do |attribute|
- value = record.read_attribute_for_validation(attribute)
- if value.respond_to?(:loaded?) && value.loaded?
- value.target.any?(&:marked_for_destruction?)
- else
- false
- end
- end
- end
end
module ClassMethods
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 7e85ed43ac..ad82ea66c4 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -2,7 +2,6 @@ module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
- return unless should_validate?(record)
if record.class._reflect_on_association(attribute)
association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 6343de5914..1f59276137 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -11,15 +11,14 @@ module ActiveRecord
end
def validate_each(record, attribute, value)
- return unless should_validate?(record)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
relation = build_relation(finder_class, table, attribute, value)
- if record.persisted? && finder_class.primary_key.to_s != attribute.to_s
+ if record.persisted?
if finder_class.primary_key
- relation = relation.where.not(finder_class.primary_key => record.id_was)
+ relation = relation.where.not(finder_class.primary_key => record.id_was || record.id)
else
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
end
@@ -57,14 +56,13 @@ module ActiveRecord
value = value.attributes[reflection.klass.primary_key] unless value.nil?
end
- attribute_name = attribute.to_s
-
# the attribute may be an aliased attribute
- if klass.attribute_aliases[attribute_name]
- attribute = klass.attribute_aliases[attribute_name]
- attribute_name = attribute.to_s
+ if klass.attribute_alias?(attribute)
+ attribute = klass.attribute_alias(attribute)
end
+ attribute_name = attribute.to_s
+
column = klass.columns_hash[attribute_name]
cast_type = klass.type_for_attribute(attribute_name)
value = cast_type.serialize(value)
@@ -79,7 +77,7 @@ module ActiveRecord
if value.nil?
klass.unscoped.where(comparison)
else
- bind = Relation::QueryAttribute.new(attribute.to_s, value, Type::Value.new)
+ bind = Relation::QueryAttribute.new(attribute_name, value, Type::Value.new)
klass.unscoped.where(comparison, bind)
end
rescue RangeError
diff --git a/activerecord/lib/rails/generators/active_record/model/model_generator.rb b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
index 7395839fca..f191eff5bf 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -22,11 +22,13 @@ module ActiveRecord
def create_model_file
template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb")
+ generate_application_record
end
def create_module_file
return if regular_class_path.empty?
template 'module.rb', File.join('app/models', "#{class_path.join('/')}.rb") if behavior == :invoke
+ generate_application_record
end
hook_for :test_framework
@@ -37,23 +39,34 @@ module ActiveRecord
attributes.select { |a| !a.reference? && a.has_index? }
end
+ # FIXME: Change this file to a symlink once RubyGems 2.5.0 is required.
+ def generate_application_record
+ if self.behavior == :invoke && !application_record_exist?
+ template 'application_record.rb', application_record_file_name
+ end
+ end
+
# Used by the migration template to determine the parent name of the model
def parent_class_name
options[:parent] || determine_default_parent_class
end
- def determine_default_parent_class
- application_record = nil
+ def application_record_exist?
+ file_exist = nil
+ in_root { file_exist = File.exist?(application_record_file_name) }
+ file_exist
+ end
- in_root do
- application_record = if mountable_engine?
- File.exist?("app/models/#{namespaced_path}/application_record.rb")
- else
- File.exist?('app/models/application_record.rb')
- end
+ def application_record_file_name
+ @application_record_file_name ||= if mountable_engine?
+ "app/models/#{namespaced_path}/application_record.rb"
+ else
+ 'app/models/application_record.rb'
end
+ end
- if application_record
+ def determine_default_parent_class
+ if application_record_exist?
"ApplicationRecord"
else
"ActiveRecord::Base"
diff --git a/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
new file mode 100644
index 0000000000..60050e0bf8
--- /dev/null
+++ b/activerecord/lib/rails/generators/active_record/model/templates/application_record.rb
@@ -0,0 +1,5 @@
+<% module_namespacing do -%>
+class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+end
+<% end -%>