aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord/lib')
-rw-r--r--activerecord/lib/active_record/associations.rb24
-rw-r--r--activerecord/lib/active_record/associations/association_scope.rb2
-rw-r--r--activerecord/lib/active_record/associations/builder/has_many.rb2
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb2
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb14
-rw-r--r--activerecord/lib/active_record/associations/preloader.rb15
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb72
-rw-r--r--activerecord/lib/active_record/associations/singular_association.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb12
-rw-r--r--activerecord/lib/active_record/callbacks.rb5
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb85
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb81
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb44
-rw-r--r--activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb15
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb30
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/explain_subscriber.rb2
-rw-r--r--activerecord/lib/active_record/integration.rb4
-rw-r--r--activerecord/lib/active_record/migration.rb66
-rw-r--r--activerecord/lib/active_record/railties/databases.rake2
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb19
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb4
-rw-r--r--activerecord/lib/active_record/scoping/default.rb2
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb6
-rw-r--r--activerecord/lib/active_record/tasks/sqlite_database_tasks.rb6
-rw-r--r--activerecord/lib/rails/generators/active_record/migration.rb7
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb2
-rw-r--r--activerecord/lib/rails/generators/active_record/model/model_generator.rb11
38 files changed, 370 insertions, 251 deletions
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 2abc5fa9d5..b806a2f832 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -149,7 +149,7 @@ module ActiveRecord
class HasOneThroughNestedAssociationsAreReadonly < ThroughNestedAssociationsAreReadonly #:nodoc:
end
- # This error is raised when trying to eager load a poloymorphic association using a JOIN.
+ # This error is raised when trying to eager load a polymorphic association using a JOIN.
# Eager loading polymorphic associations is only possible with
# {ActiveRecord::Relation#preload}[rdoc-ref:QueryMethods#preload].
class EagerLoadPolymorphicError < ActiveRecordError
@@ -508,7 +508,7 @@ module ActiveRecord
# to customize them. For example, to add a condition:
#
# class Blog < ActiveRecord::Base
- # has_many :published_posts, -> { where published: true }, class_name: 'Post'
+ # has_many :published_posts, -> { where(published: true) }, class_name: 'Post'
# end
#
# Inside the <tt>-> { ... }</tt> block you can use all of the usual Relation methods.
@@ -520,7 +520,7 @@ module ActiveRecord
# events that occur on the user's birthday:
#
# class User < ActiveRecord::Base
- # has_many :birthday_events, ->(user) { where starts_on: user.birthday }, class_name: 'Event'
+ # has_many :birthday_events, ->(user) { where(starts_on: user.birthday) }, class_name: 'Event'
# end
#
# Note: Joining, eager loading and preloading of these associations is not fully possible.
@@ -895,7 +895,7 @@ module ActiveRecord
# In this case it is usually more natural to include an association which has conditions defined on it:
#
# class Post < ActiveRecord::Base
- # has_many :approved_comments, -> { where approved: true }, class_name: 'Comment'
+ # has_many :approved_comments, -> { where(approved: true) }, class_name: 'Comment'
# end
#
# Post.includes(:approved_comments)
@@ -1345,10 +1345,10 @@ module ActiveRecord
# association objects.
#
# Option examples:
- # has_many :comments, -> { order "posted_on" }
- # has_many :comments, -> { includes :author }
+ # has_many :comments, -> { order("posted_on") }
+ # has_many :comments, -> { includes(:author) }
# has_many :people, -> { where(deleted: false).order("name") }, class_name: "Person"
- # has_many :tracks, -> { order "position" }, dependent: :destroy
+ # has_many :tracks, -> { order("position") }, dependent: :destroy
# has_many :comments, dependent: :nullify
# has_many :tags, as: :taggable
# has_many :reports, -> { readonly }
@@ -1476,12 +1476,12 @@ module ActiveRecord
# has_one :credit_card, dependent: :destroy # destroys the associated credit card
# has_one :credit_card, dependent: :nullify # updates the associated records foreign
# # key value to NULL rather than destroying it
- # has_one :last_comment, -> { order 'posted_on' }, class_name: "Comment"
- # has_one :project_manager, -> { where role: 'project_manager' }, class_name: "Person"
+ # has_one :last_comment, -> { order('posted_on') }, class_name: "Comment"
+ # has_one :project_manager, -> { where(role: 'project_manager') }, class_name: "Person"
# has_one :attachment, as: :attachable
# has_one :boss, -> { readonly }
# has_one :club, through: :membership
- # has_one :primary_address, -> { where primary: true }, through: :addressables, source: :addressable
+ # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
# has_one :credit_card, required: true
def has_one(name, scope = nil, options = {})
reflection = Builder::HasOne.build(self, name, scope, options)
@@ -1721,7 +1721,7 @@ module ActiveRecord
# query when you access the associated collection.
#
# Scope examples:
- # has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
+ # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
# has_and_belongs_to_many :categories, ->(category) {
# where("default_category = ?", category.name)
# }
@@ -1774,7 +1774,7 @@ module ActiveRecord
#
# Option examples:
# has_and_belongs_to_many :projects
- # has_and_belongs_to_many :projects, -> { includes :milestones, :manager }
+ # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
# has_and_belongs_to_many :nations, class_name: "Country"
# has_and_belongs_to_many :categories, join_table: "prods_cats"
# has_and_belongs_to_many :categories, -> { readonly }
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
index a140dc239c..48437a1c9e 100644
--- a/activerecord/lib/active_record/associations/association_scope.rb
+++ b/activerecord/lib/active_record/associations/association_scope.rb
@@ -147,9 +147,9 @@ module ActiveRecord
scope.includes! item.includes_values
end
+ scope.unscope!(*item.unscope_values)
scope.where_clause += item.where_clause
scope.order_values |= item.order_values
- scope.unscope!(*item.unscope_values)
end
reflection = reflection.next
diff --git a/activerecord/lib/active_record/associations/builder/has_many.rb b/activerecord/lib/active_record/associations/builder/has_many.rb
index f71b10120f..7864d4c536 100644
--- a/activerecord/lib/active_record/associations/builder/has_many.rb
+++ b/activerecord/lib/active_record/associations/builder/has_many.rb
@@ -5,7 +5,7 @@ module ActiveRecord::Associations::Builder # :nodoc:
end
def self.valid_options(options)
- super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type]
+ super + [:primary_key, :dependent, :as, :through, :source, :source_type, :inverse_of, :counter_cache, :join_table, :foreign_type, :index_errors]
end
def self.valid_dependent_options
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 256df3ca11..f32dddb8f0 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -1,5 +1,3 @@
-require "active_support/deprecation"
-
module ActiveRecord
module Associations
# = Active Record Association Collection
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 81eb5136a1..0e98a3b3a4 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -131,9 +131,9 @@ module ActiveRecord
def instantiate(result_set, aliases)
primary_key = aliases.column_alias(join_root, join_root.primary_key)
- seen = Hash.new { |h,parent_klass|
- h[parent_klass] = Hash.new { |i,parent_id|
- i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
+ seen = Hash.new { |i, object_id|
+ i[object_id] = Hash.new { |j, child_class|
+ j[child_class] = {}
}
}
@@ -150,7 +150,8 @@ module ActiveRecord
message_bus.instrument('instantiation.active_record', payload) do
result_set.each { |row_hash|
- parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
+ parent_key = primary_key ? row_hash[primary_key] : row_hash
+ parent = parents[parent_key] ||= join_root.instantiate(row_hash, column_aliases)
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
}
end
@@ -233,7 +234,6 @@ module ActiveRecord
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
return if ar_parent.nil?
- primary_id = ar_parent.id
parent.children.each do |node|
if node.reflection.collection?
@@ -253,14 +253,14 @@ module ActiveRecord
next
end
- model = seen[parent.base_klass][primary_id][node.base_klass][id]
+ model = seen[ar_parent.object_id][node.base_klass][id]
if model
construct(model, node, row, rs, seen, model_cache, aliases)
else
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
model.readonly!
- seen[parent.base_klass][primary_id][node.base_klass][id] = model
+ seen[ar_parent.object_id][node.base_klass][id] = model
construct(model, node, row, rs, seen, model_cache, aliases)
end
end
diff --git a/activerecord/lib/active_record/associations/preloader.rb b/activerecord/lib/active_record/associations/preloader.rb
index 3992a240b9..ecf6fb8643 100644
--- a/activerecord/lib/active_record/associations/preloader.rb
+++ b/activerecord/lib/active_record/associations/preloader.rb
@@ -54,6 +54,8 @@ module ActiveRecord
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
end
+ NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
+
# Eager loads the named associations for the given Active Record record(s).
#
# In this description, 'association name' shall refer to the name passed
@@ -88,9 +90,6 @@ module ActiveRecord
# [ :books, :author ]
# { author: :avatar }
# [ :books, { author: :avatar } ]
-
- NULL_RELATION = Struct.new(:values, :where_clause, :joins_values).new({}, Relation::WhereClause.empty, [])
-
def preload(records, associations, preload_scope = nil)
records = Array.wrap(records).compact.uniq
associations = Array.wrap(associations)
@@ -107,6 +106,7 @@ module ActiveRecord
private
+ # Loads all the given data into +records+ for the +association+.
def preloaders_on(association, records, scope)
case association
when Hash
@@ -132,6 +132,11 @@ module ActiveRecord
}
end
+ # Loads all the given data into +records+ for a singular +association+.
+ #
+ # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
+ # call the +run+ method for each passed in class in the +records+ argument.
+ #
# Not all records have the same class, so group then preload group on the reflection
# itself so that if various subclass share the same association then we do not split
# them unnecessarily
@@ -181,6 +186,10 @@ module ActiveRecord
def self.preloaded_records; []; end
end
+ # Returns a class containing the logic needed to load preload the data
+ # and attach it to a relation. For example +Preloader::Association+ or
+ # +Preloader::HasManyThrough+. The class returned implements a `run` method
+ # that accepts a preloader.
def preloader_for(reflection, owners, rhs_klass)
return NullPreloader unless rhs_klass
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 7a5a8f8ae6..29dd0643d6 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -12,7 +12,6 @@ module ActiveRecord
@preload_scope = preload_scope
@model = owners.first && owners.first.class
@scope = nil
- @owners_by_key = nil
@preloaded_records = []
end
@@ -56,18 +55,6 @@ module ActiveRecord
raise NotImplementedError
end
- def owners_by_key
- @owners_by_key ||= if key_conversion_required?
- owners.group_by do |owner|
- owner[owner_key_name].to_s
- end
- else
- owners.group_by do |owner|
- owner[owner_key_name]
- end
- end
- end
-
def options
reflection.options
end
@@ -75,32 +62,33 @@ module ActiveRecord
private
def associated_records_by_owner(preloader)
- owners_map = owners_by_key
- owner_keys = owners_map.keys.compact
-
- # Each record may have multiple owners, and vice-versa
- records_by_owner = owners.each_with_object({}) do |owner,h|
- h[owner] = []
+ records = load_records
+ owners.each_with_object({}) do |owner, result|
+ result[owner] = records[convert_key(owner[owner_key_name])] || []
end
+ end
- if owner_keys.any?
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
- # Make several smaller queries if necessary or make one query if the adapter supports it
- sliced = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
-
- records = load_slices sliced
- records.each do |record, owner_key|
- owners_map[owner_key].each do |owner|
- records_by_owner[owner] << record
- end
+ def owner_keys
+ unless defined?(@owner_keys)
+ @owner_keys = owners.map do |owner|
+ owner[owner_key_name]
end
+ @owner_keys.uniq!
+ @owner_keys.compact!
end
-
- records_by_owner
+ @owner_keys
end
def key_conversion_required?
- association_key_type != owner_key_type
+ @key_conversion_required ||= association_key_type != owner_key_type
+ end
+
+ def convert_key(key)
+ if key_conversion_required?
+ key.to_s
+ else
+ key
+ end
end
def association_key_type
@@ -111,17 +99,17 @@ module ActiveRecord
@model.type_for_attribute(owner_key_name.to_s).type
end
- def load_slices(slices)
- @preloaded_records = slices.flat_map { |slice|
+ def load_records
+ return {} if owner_keys.empty?
+ # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
+ # Make several smaller queries if necessary or make one query if the adapter supports it
+ slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
+ @preloaded_records = slices.flat_map do |slice|
records_for(slice)
- }
-
- @preloaded_records.map { |record|
- key = record[association_key_name]
- key = key.to_s if key_conversion_required?
-
- [record, key]
- }
+ end
+ @preloaded_records.group_by do |record|
+ convert_key(record[association_key_name])
+ end
end
def reflection_scope
diff --git a/activerecord/lib/active_record/associations/singular_association.rb b/activerecord/lib/active_record/associations/singular_association.rb
index 03cb8cb8c3..c7cc48ba16 100644
--- a/activerecord/lib/active_record/associations/singular_association.rb
+++ b/activerecord/lib/active_record/associations/singular_association.rb
@@ -1,5 +1,3 @@
-require "active_support/deprecation"
-
module ActiveRecord
module Associations
class SingularAssociation < Association #:nodoc:
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index d35bc3e794..fc12c3f45a 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -140,6 +140,8 @@ module ActiveRecord
included do
Associations::Builder::Association.extensions << AssociationBuilderExtension
+ mattr_accessor :index_nested_attribute_errors, instance_writer: false
+ self.index_nested_attribute_errors = false
end
module ClassMethods # :nodoc:
@@ -315,7 +317,7 @@ module ActiveRecord
def validate_collection_association(reflection)
if association = association_instance_get(reflection.name)
if records = associated_records_to_validate_or_save(association, new_record?, reflection.options[:autosave])
- records.each { |record| association_valid?(reflection, record) }
+ records.each_with_index { |record, index| association_valid?(reflection, record, index) }
end
end
end
@@ -323,14 +325,18 @@ module ActiveRecord
# Returns whether or not the association is valid and applies any errors to
# the parent, <tt>self</tt>, if it wasn't. Skips any <tt>:autosave</tt>
# enabled records if they're marked_for_destruction? or destroyed.
- def association_valid?(reflection, record)
+ def association_valid?(reflection, record, index=nil)
return true if record.destroyed? || (reflection.options[:autosave] && record.marked_for_destruction?)
validation_context = self.validation_context unless [:create, :update].include?(self.validation_context)
unless valid = record.valid?(validation_context)
if reflection.options[:autosave]
record.errors.each do |attribute, message|
- attribute = "#{reflection.name}.#{attribute}"
+ if index.nil? || (!reflection.options[:index_errors] && !ActiveRecord::Base.index_nested_attribute_errors)
+ attribute = "#{reflection.name}.#{attribute}"
+ else
+ attribute = "#{reflection.name}[#{index}].#{attribute}"
+ end
errors[attribute] << message
errors[attribute].uniq!
end
diff --git a/activerecord/lib/active_record/callbacks.rb b/activerecord/lib/active_record/callbacks.rb
index bfedc4af3f..cfd8cbda67 100644
--- a/activerecord/lib/active_record/callbacks.rb
+++ b/activerecord/lib/active_record/callbacks.rb
@@ -292,10 +292,15 @@ module ActiveRecord
end
def destroy #:nodoc:
+ @_destroy_callback_already_called ||= false
+ return if @_destroy_callback_already_called
+ @_destroy_callback_already_called = true
_run_destroy_callbacks { super }
rescue RecordNotDestroyed => e
@_association_destroy_exception = e
false
+ ensure
+ @_destroy_callback_already_called = false
end
def touch(*) #:nodoc:
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 dbacf56795..848aeb821c 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -29,7 +29,17 @@ module ActiveRecord
# Returns an ActiveRecord::Result instance.
def select_all(arel, name = nil, binds = [])
arel, binds = binds_from_relation arel, binds
- select(to_sql(arel, binds), name, binds)
+ sql = to_sql(arel, binds)
+ if arel.is_a?(String)
+ preparable = false
+ else
+ preparable = visitor.preparable
+ end
+ if prepared_statements && preparable
+ select_prepared(sql, name, binds)
+ else
+ select(sql, name, binds)
+ end
end
# Returns a record hash with the column names as keys and column values
@@ -67,7 +77,7 @@ module ActiveRecord
# Executes +sql+ statement in the context of this connection using
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
- def exec_query(sql, name = 'SQL', binds = [])
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
end
# Executes insert +sql+ statement in the context of this connection using
@@ -358,9 +368,12 @@ module ActiveRecord
# Returns an ActiveRecord::Result instance.
def select(sql, name = nil, binds = [])
- exec_query(sql, name, binds)
+ exec_query(sql, name, binds, prepare: false)
end
+ def select_prepared(sql, name = nil, binds = [])
+ exec_query(sql, name, binds, prepare: true)
+ end
# Returns the last auto-generated ID from the affected table.
def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
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 bceda5abd9..e2ef56798b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -210,7 +210,7 @@ module ActiveRecord
# An array of ColumnDefinition objects, representing the column changes
# that have been defined.
attr_accessor :indexes
- attr_reader :name, :temporary, :options, :as, :foreign_keys
+ attr_reader :name, :temporary, :options, :as, :foreign_keys, :native
def initialize(types, name, temporary, options, as = nil)
@columns_hash = {}
@@ -238,86 +238,19 @@ module ActiveRecord
end
# Instantiates a new column for the table.
- # The +type+ parameter is normally one of the migrations native types,
- # which is one of the following:
- # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
- # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
- # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
- # <tt>:binary</tt>, <tt>:boolean</tt>.
- #
- # You may use a type not in this list as long as it is supported by your
- # database (for example, "polygon" in MySQL), but this will not be database
- # agnostic and should usually be avoided.
- #
- # Available options are (none of these exists by default):
- # * <tt>:limit</tt> -
- # Requests a maximum column length. This is number of characters for a <tt>:string</tt> column
- # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
- # * <tt>:default</tt> -
- # The column's default value. Use nil for NULL.
- # * <tt>:null</tt> -
- # Allows or disallows +NULL+ values in the column. This option could
- # have been named <tt>:null_allowed</tt>.
- # * <tt>:precision</tt> -
- # Specifies the precision for a <tt>:decimal</tt> column.
- # * <tt>:scale</tt> -
- # Specifies the scale for a <tt>:decimal</tt> column.
+ # See {connection.add_column}[rdoc-ref:ConnectionAdapters::SchemaStatements#add_column]
+ # for available options.
+ #
+ # Additional options are:
# * <tt>:index</tt> -
# Create an index for the column. Can be either <tt>true</tt> or an options hash.
#
- # Note: The precision is the total number of significant digits
- # and the scale is the number of digits that can be stored following
- # the decimal point. For example, the number 123.45 has a precision of 5
- # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
- # range from -999.99 to 999.99.
- #
- # Please be aware of different RDBMS implementations behavior with
- # <tt>:decimal</tt> columns:
- # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
- # <tt>:precision</tt>, and makes no comments about the requirements of
- # <tt>:precision</tt>.
- # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
- # Default is (10,0).
- # * PostgreSQL: <tt>:precision</tt> [1..infinity],
- # <tt>:scale</tt> [0..infinity]. No default.
- # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
- # Internal storage as strings. No default.
- # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
- # but the maximum supported <tt>:precision</tt> is 16. No default.
- # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
- # Default is (38,0).
- # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
- # Default unknown.
- # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
- # Default (38,0).
- #
# This method returns <tt>self</tt>.
#
# == Examples
- # # Assuming +td+ is an instance of TableDefinition
- # td.column(:granted, :boolean)
- # # granted BOOLEAN
- #
- # td.column(:picture, :binary, limit: 2.megabytes)
- # # => picture BLOB(2097152)
- #
- # td.column(:sales_stage, :string, limit: 20, default: 'new', null: false)
- # # => sales_stage VARCHAR(20) DEFAULT 'new' NOT NULL
- #
- # td.column(:bill_gates_money, :decimal, precision: 15, scale: 2)
- # # => bill_gates_money DECIMAL(15,2)
#
- # td.column(:sensor_reading, :decimal, precision: 30, scale: 20)
- # # => sensor_reading DECIMAL(30,20)
- #
- # # While <tt>:scale</tt> defaults to zero on most databases, it
- # # probably wouldn't hurt to include it.
- # td.column(:huge_integer, :decimal, precision: 30)
- # # => huge_integer DECIMAL(30)
- #
- # # Defines a column with a database-specific type.
- # td.column(:foo, 'polygon')
- # # => foo polygon
+ # # Assuming +td+ is an instance of TableDefinition
+ # td.column(:granted, :boolean, index: true)
#
# == Short-hand examples
#
@@ -455,10 +388,6 @@ module ActiveRecord
ColumnDefinition.new name, type
end
- def native
- @native
- end
-
def aliased_types(name, fallback)
'timestamp' == name ? :datetime : fallback
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 d3fbc18f97..d5f8dbc8fc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -446,12 +446,81 @@ module ActiveRecord
execute "DROP TABLE#{' IF EXISTS' if options[:if_exists]} #{quote_table_name(table_name)}"
end
- # Adds a new column to the named table.
- # See TableDefinition#column for details of the options you can use.
- #
- # Note: Not all options will be available, generally this command should
- # ignore most of them. In favor of doing a low-level call to simply
- # create a column.
+ # Add a new +type+ column named +column_name+ to +table_name+.
+ #
+ # The +type+ parameter is normally one of the migrations native types,
+ # which is one of the following:
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>,
+ # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
+ # <tt>:binary</tt>, <tt>:boolean</tt>.
+ #
+ # You may use a type not in this list as long as it is supported by your
+ # database (for example, "polygon" in MySQL), but this will not be database
+ # agnostic and should usually be avoided.
+ #
+ # Available options are (none of these exists by default):
+ # * <tt>:limit</tt> -
+ # Requests a maximum column length. This is number of characters for a <tt>:string</tt> column
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
+ # * <tt>:default</tt> -
+ # The column's default value. Use nil for NULL.
+ # * <tt>:null</tt> -
+ # Allows or disallows +NULL+ values in the column. This option could
+ # have been named <tt>:null_allowed</tt>.
+ # * <tt>:precision</tt> -
+ # Specifies the precision for a <tt>:decimal</tt> column.
+ # * <tt>:scale</tt> -
+ # Specifies the scale for a <tt>:decimal</tt> column.
+ #
+ # Note: The precision is the total number of significant digits
+ # and the scale is the number of digits that can be stored following
+ # the decimal point. For example, the number 123.45 has a precision of 5
+ # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
+ # range from -999.99 to 999.99.
+ #
+ # Please be aware of different RDBMS implementations behavior with
+ # <tt>:decimal</tt> columns:
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
+ # <tt>:precision</tt>, and makes no comments about the requirements of
+ # <tt>:precision</tt>.
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
+ # Default is (10,0).
+ # * PostgreSQL: <tt>:precision</tt> [1..infinity],
+ # <tt>:scale</tt> [0..infinity]. No default.
+ # * SQLite2: Any <tt>:precision</tt> and <tt>:scale</tt> may be used.
+ # Internal storage as strings. No default.
+ # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
+ # Default is (38,0).
+ # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
+ # Default unknown.
+ # * SqlServer?: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
+ # Default (38,0).
+ #
+ # == Examples
+ #
+ # add_column(:users, :picture, :binary, limit: 2.megabytes)
+ # # ALTER TABLE "users" ADD "picture" blob(2097152)
+ #
+ # add_column(:articles, :status, :string, limit: 20, default: 'draft', null: false)
+ # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
+ #
+ # add_column(:answers, :bill_gates_money, :decimal, precision: 15, scale: 2)
+ # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
+ #
+ # add_column(:measurements, :sensor_reading, :decimal, precision: 30, scale: 20)
+ # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
+ #
+ # # While :scale defaults to zero on most databases, it
+ # # probably wouldn't hurt to include it.
+ # add_column(:measurements, :huge_integer, :decimal, precision: 30)
+ # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
+ #
+ # # Defines a column with a database-specific type.
+ # add_column(:shapes, :triangle, 'polygon')
+ # # ALTER TABLE "shapes" ADD "triangle" polygon
def add_column(table_name, column_name, type, options = {})
at = create_alter_table table_name
at.add_column(column_name, type, options)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 9b46df721a..402159ac13 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -1,5 +1,6 @@
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'
require 'active_record/connection_adapters/abstract/schema_dumper'
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 f710ec10fc..251acf1c83 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -134,6 +134,7 @@ module ActiveRecord
time: { name: "time" },
date: { name: "date" },
binary: { name: "blob" },
+ blob: { name: "blob" },
boolean: { name: "tinyint", limit: 1 },
bigint: { name: "bigint" },
json: { name: "json" },
@@ -152,6 +153,7 @@ module ActiveRecord
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
+ @visitor.extend(DetermineIfPreparableVisitor)
else
@prepared_statements = false
end
@@ -167,6 +169,10 @@ module ActiveRecord
end
end
+ def version
+ @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
+ end
+
# Returns true, since this connection adapter supports migrations.
def supports_migrations?
true
@@ -477,7 +483,10 @@ module ActiveRecord
end
def tables(name = nil) # :nodoc:
- select_values("SHOW FULL TABLES", 'SCHEMA')
+ sql = "SELECT table_name FROM information_schema.tables "
+ sql << "WHERE table_schema = #{quote(@config[:database])}"
+
+ select_values(sql, 'SCHEMA')
end
alias data_sources tables
@@ -679,12 +688,18 @@ module ActiveRecord
# Maps logical Rails types to MySQL-specific data types.
def type_to_sql(type, limit = nil, precision = nil, scale = nil, unsigned = nil)
sql = case type.to_s
- when 'binary'
- binary_to_sql(limit)
when 'integer'
integer_to_sql(limit)
when 'text'
text_to_sql(limit)
+ when 'blob'
+ binary_to_sql(limit)
+ when 'binary'
+ if (0..0xfff) === limit
+ "varbinary(#{limit})"
+ else
+ binary_to_sql(limit)
+ end
else
super(type, limit, precision, scale)
end
@@ -924,10 +939,6 @@ module ActiveRecord
subselect.from subsubselect.distinct.as('__active_record_temp')
end
- def version
- @version ||= Version.new(full_version.match(/^\d+\.\d+\.\d+/)[0])
- end
-
def mariadb?
full_version =~ /mariadb/i
end
@@ -997,15 +1008,6 @@ module ActiveRecord
MySQL::TableDefinition.new(native_database_types, name, temporary, options, as)
end
- def binary_to_sql(limit) # :nodoc:
- case limit
- when 0..0xfff; "varbinary(#{limit})"
- when nil; "blob"
- when 0x1000..0xffffffff; "blob(#{limit})"
- else raise(ActiveRecordError, "No binary type has byte length #{limit}")
- end
- end
-
def integer_to_sql(limit) # :nodoc:
case limit
when 1; 'tinyint'
@@ -1028,6 +1030,16 @@ module ActiveRecord
end
end
+ def binary_to_sql(limit) # :nodoc:
+ case limit
+ when 0..0xff; 'tinyblob'
+ when nil, 0x100..0xffff; 'blob'
+ when 0x10000..0xffffff; 'mediumblob'
+ when 0x1000000..0xffffffff; 'longblob'
+ else raise(ActiveRecordError, "No binary type has byte length #{limit}")
+ end
+ end
+
class MysqlJson < Type::Internal::AbstractJson # :nodoc:
def changed_in_place?(raw_old_value, new_value)
# Normalization is required because MySQL JSON data format includes
diff --git a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
new file mode 100644
index 0000000000..0fdc185c45
--- /dev/null
+++ b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
@@ -0,0 +1,22 @@
+module ActiveRecord
+ module ConnectionAdapters
+ module DetermineIfPreparableVisitor
+ attr_reader :preparable
+
+ def accept(*)
+ @preparable = true
+ super
+ end
+
+ def visit_Arel_Nodes_In(*)
+ @preparable = false
+ super
+ end
+
+ def visit_Arel_Nodes_SqlLiteral(*)
+ @preparable = false
+ super
+ end
+ end
+ end
+end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
index bcf397cd77..29e8c73d46 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_definitions.rb
@@ -7,6 +7,10 @@ module ActiveRecord
super
end
+ def blob(*args, **options)
+ args.each { |name| column(name, :blob, options) }
+ end
+
def json(*args, **options)
args.each { |name| column(name, :json, options) }
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 6448b8b5e2..3c48d0554e 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -27,6 +27,14 @@ module ActiveRecord
private
+ def schema_type(column)
+ if column.sql_type == 'tinyblob'
+ 'blob'
+ else
+ super
+ end
+ end
+
def schema_limit(column)
super unless column.type == :boolean
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 4461722bb4..42c4a14f00 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -126,7 +126,9 @@ module ActiveRecord
# Returns an array of arrays containing the field values.
# Order is the same as that returned by +columns+.
def select_rows(sql, name = nil, binds = [])
- execute(sql, name).to_a
+ result = execute(sql, name)
+ @connection.next_result while @connection.more_results?
+ result.to_a
end
# Executes the SQL statement in the context of this connection.
@@ -140,8 +142,9 @@ module ActiveRecord
super
end
- def exec_query(sql, name = 'SQL', binds = [])
+ 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
diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
index b3894481cc..fddb318553 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -235,11 +235,11 @@ module ActiveRecord
@client_encoding = ENCODINGS[result.rows.last.last]
end
- def exec_query(sql, name = 'SQL', binds = [])
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
if without_prepared_statement?(binds)
result_set, affected_rows = exec_without_stmt(sql, name)
else
- result_set, affected_rows = exec_stmt(sql, name, binds)
+ result_set, affected_rows = exec_stmt(sql, name, binds, cache_stmt: prepare)
end
yield affected_rows if block_given?
@@ -378,12 +378,12 @@ module ActiveRecord
private
- def exec_stmt(sql, name, binds)
+ def exec_stmt(sql, name, binds, cache_stmt: false)
cache = {}
type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
log(sql, name, binds) do
- if binds.empty?
+ if !cache_stmt
stmt = @connection.prepare(sql)
else
cache = @statements[sql] ||= {
@@ -399,7 +399,7 @@ module ActiveRecord
# place when an error occurs. To support older MySQL versions, we
# need to close the statement and delete the statement from the
# cache.
- if binds.empty?
+ if !cache_stmt
stmt.close
else
@statements.delete sql
@@ -417,7 +417,7 @@ module ActiveRecord
affected_rows = stmt.affected_rows
stmt.free_result
- stmt.close if binds.empty?
+ stmt.close if !cache_stmt
[result_set, affected_rows]
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 43e18ebb2b..0e0c0e993a 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -156,8 +156,8 @@ module ActiveRecord
end
end
- def exec_query(sql, name = 'SQL', binds = [])
- execute_and_clear(sql, name, binds) do |result|
+ def exec_query(sql, name = 'SQL', binds = [], prepare: false)
+ execute_and_clear(sql, name, binds, prepare: prepare) do |result|
types = {}
fields = result.fields
fields.each_with_index do |fname, i|
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 0686b35425..236c067fd5 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -198,6 +198,7 @@ module ActiveRecord
@visitor = Arel::Visitors::PostgreSQL.new self
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
+ @visitor.extend(DetermineIfPreparableVisitor)
else
@prepared_statements = false
end
@@ -553,16 +554,22 @@ module ActiveRecord
FEATURE_NOT_SUPPORTED = "0A000" #:nodoc:
- def execute_and_clear(sql, name, binds)
- result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
- exec_cache(sql, name, binds)
+ def execute_and_clear(sql, name, binds, prepare: false)
+ if without_prepared_statement?(binds)
+ result = exec_no_cache(sql, name, [])
+ elsif !prepare
+ result = exec_no_cache(sql, name, binds)
+ else
+ result = exec_cache(sql, name, binds)
+ end
ret = yield result
result.clear
ret
end
def exec_no_cache(sql, name, binds)
- log(sql, name, binds) { @connection.async_exec(sql, []) }
+ type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
+ log(sql, name, binds) { @connection.async_exec(sql, type_casted_binds) }
end
def exec_cache(sql, name, binds)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 505a71720f..9028c1fcb9 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -89,6 +89,7 @@ module ActiveRecord
if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
@prepared_statements = true
+ @visitor.extend(DetermineIfPreparableVisitor)
else
@prepared_statements = false
end
@@ -231,15 +232,18 @@ module ActiveRecord
end
end
- def exec_query(sql, name = nil, binds = [])
+ def exec_query(sql, name = nil, binds = [], prepare: false)
type_casted_binds = binds.map { |attr| type_cast(attr.value_for_database) }
log(sql, name, binds) do
# Don't cache statements if they are not prepared
- if without_prepared_statement?(binds)
+ unless prepare
stmt = @connection.prepare(sql)
begin
cols = stmt.columns
+ unless without_prepared_statement?(binds)
+ stmt.bind_params(type_casted_binds)
+ end
records = stmt.to_a
ensure
stmt.close
@@ -252,7 +256,7 @@ module ActiveRecord
stmt = cache[:stmt]
cols = cache[:cols] ||= stmt.columns
stmt.reset!
- stmt.bind_params type_casted_binds
+ stmt.bind_params(type_casted_binds)
end
ActiveRecord::Result.new(cols, stmt.to_a)
@@ -307,22 +311,18 @@ module ActiveRecord
# SCHEMA STATEMENTS ========================================
- def tables(name = nil, table_name = nil) #:nodoc:
- sql = <<-SQL
- SELECT name
- FROM sqlite_master
- WHERE (type = 'table' OR type = 'view') AND NOT name = 'sqlite_sequence'
- SQL
- sql << " AND name = #{quote_table_name(table_name)}" if table_name
-
- exec_query(sql, 'SCHEMA').map do |row|
- row['name']
- end
+ def tables(name = nil) # :nodoc:
+ select_values("SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'", 'SCHEMA')
end
alias data_sources tables
def table_exists?(table_name)
- table_name && tables(nil, table_name).any?
+ return false unless table_name.present?
+
+ sql = "SELECT name FROM sqlite_master WHERE type IN ('table','view') AND name <> 'sqlite_sequence'"
+ sql << " AND name = #{quote(table_name)}"
+
+ select_values(sql, 'SCHEMA').any?
end
alias data_source_exists? table_exists?
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index db3e9d1d52..142b6e8599 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -177,7 +177,7 @@ module ActiveRecord
hash = args.first
return super if hash.values.any? { |v|
- v.nil? || Array === v || Hash === v
+ v.nil? || Array === v || Hash === v || Relation === v
}
# We can't cache Post.find_by(author: david) ...yet
diff --git a/activerecord/lib/active_record/explain_subscriber.rb b/activerecord/lib/active_record/explain_subscriber.rb
index 9adabd7819..90bcf5a205 100644
--- a/activerecord/lib/active_record/explain_subscriber.rb
+++ b/activerecord/lib/active_record/explain_subscriber.rb
@@ -14,7 +14,7 @@ module ActiveRecord
end
# SCHEMA queries cannot be EXPLAINed, also we do not want to run EXPLAIN on
- # our own EXPLAINs now matter how loopingly beautiful that would be.
+ # our own EXPLAINs no matter how loopingly beautiful that would be.
#
# On the other hand, we want to monitor the performance of our real database
# queries, not the performance of the access to the query cache.
diff --git a/activerecord/lib/active_record/integration.rb b/activerecord/lib/active_record/integration.rb
index 19d2589db3..466c8509a4 100644
--- a/activerecord/lib/active_record/integration.rb
+++ b/activerecord/lib/active_record/integration.rb
@@ -10,9 +10,9 @@ module ActiveRecord
# Indicates the format used to generate the timestamp in the cache key.
# Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
#
- # This is +:nsec+, by default.
+ # This is +:usec+, by default.
class_attribute :cache_timestamp_format, :instance_writer => false
- self.cache_timestamp_format = :nsec
+ self.cache_timestamp_format = :usec
end
# Returns a String, which Action Pack uses for constructing a URL to this
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 90b8a29869..c8b96b8de0 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -194,17 +194,18 @@ module ActiveRecord
#
# == Available transformations
#
+ # === Creation
+ #
+ # * <tt>create_join_table(table_1, table_2, options)</tt>: Creates a join
+ # table having its name as the lexical order of the first two
+ # arguments. See
+ # ActiveRecord::ConnectionAdapters::SchemaStatements#create_join_table for
+ # details.
# * <tt>create_table(name, options)</tt>: Creates a table called +name+ and
# makes the table object available to a block that can then add columns to it,
# following the same format as +add_column+. See example above. The options hash
# is for fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create
# table definition.
- # * <tt>drop_table(name)</tt>: Drops the table called +name+.
- # * <tt>change_table(name, options)</tt>: Allows to make column alterations to
- # the table called +name+. It makes the table object available to a block that
- # can then add/remove columns, indexes or foreign keys to it.
- # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
- # to +new_name+.
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column
# to the table called +table_name+
# named +column_name+ specified to be one of the following types:
@@ -215,24 +216,59 @@ module ActiveRecord
# Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g.
# <tt>{ limit: 50, null: false }</tt>) -- see
# ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
- # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
- # a column but keeps the type and content.
- # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
- # the column to a different type using the same parameters as add_column.
- # * <tt>remove_column(table_name, column_name, type, options)</tt>: Removes the column
- # named +column_name+ from the table called +table_name+.
+ # * <tt>add_foreign_key(from_table, to_table, options)</tt>: Adds a new
+ # foreign key. +from_table+ is the table with the key column, +to_table+ contains
+ # the referenced primary key.
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index
# with the name of the column. Other options include
# <tt>:name</tt>, <tt>:unique</tt> (e.g.
# <tt>{ name: 'users_name_index', unique: true }</tt>) and <tt>:order</tt>
# (e.g. <tt>{ order: { name: :desc } }</tt>).
+ # * <tt>add_reference(:table_name, :reference_name)</tt>: Adds a new column
+ # +reference_name_id+ by default an integer. See
+ # ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details.
+ # * <tt>add_timestamps(table_name, options)</tt>: Adds timestamps (+created_at+
+ # and +updated_at+) columns to +table_name+.
+ #
+ # === Modification
+ #
+ # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes
+ # the column to a different type using the same parameters as add_column.
+ # * <tt>change_column_default(table_name, column_name, default)</tt>: Sets a
+ # default value for +column_name+ definded by +default+ on +table_name+.
+ # * <tt>change_column_null(table_name, column_name, null, default = nil)</tt>:
+ # Sets or removes a +NOT NULL+ constraint on +column_name+. The +null+ flag
+ # indicates whether the value can be +NULL+. See
+ # ActiveRecord::ConnectionAdapters::SchemaStatements#change_column_null for
+ # details.
+ # * <tt>change_table(name, options)</tt>: Allows to make column alterations to
+ # the table called +name+. It makes the table object available to a block that
+ # can then add/remove columns, indexes or foreign keys to it.
+ # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames
+ # a column but keeps the type and content.
+ # * <tt>rename_index(table_name, old_name, new_name)</tt>: Renames an index.
+ # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+
+ # to +new_name+.
+ #
+ # === Deletion
+ #
+ # * <tt>drop_table(name)</tt>: Drops the table called +name+.
+ # * <tt>drop_join_table(table_1, table_2, options)</tt>: Drops the join table
+ # specified by the given arguments.
+ # * <tt>remove_column(table_name, column_name, type, options)</tt>: Removes the column
+ # named +column_name+ from the table called +table_name+.
+ # * <tt>remove_columns(table_name, *column_names)</tt>: Removes the given
+ # columns from the table definition.
+ # * <tt>remove_foreign_key(from_table, options_or_to_table)</tt>: Removes the
+ # given foreign key from the table called +table_name+.
# * <tt>remove_index(table_name, column: column_names)</tt>: Removes the index
# specified by +column_names+.
# * <tt>remove_index(table_name, name: index_name)</tt>: Removes the index
# specified by +index_name+.
- # * <tt>add_reference(:table_name, :reference_name)</tt>: Adds a new column
- # +reference_name_id+ by default an integer. See
- # ActiveRecord::ConnectionAdapters::SchemaStatements#add_reference for details.
+ # * <tt>remove_reference(table_name, ref_name, options)</tt>: Removes the
+ # reference(s) on +table_name+ specified by +ref_name+.
+ # * <tt>remove_timestamps(table_name, options)</tt>: Removes the timestamp
+ # columns (+created_at+ and +updated_at+) from the table definition.
#
# == Irreversible transformations
#
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index d940ac244a..b6f3695856 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -34,7 +34,7 @@ db_namespace = namespace :db do
end
end
- # desc "Empty 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 purging the development and test databases."
+ # desc "Empty the database from DATABASE_URL or config/database.yml for the current RAILS_ENV (use db:purge:all to purge all databases in the config). Without RAILS_ENV it defaults to purging the development and test databases."
task :purge => [:load_config] do
ActiveRecord::Tasks::DatabaseTasks.purge_current
end
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 16d5774fec..f45844a9ea 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -219,6 +219,8 @@ module ActiveRecord
end
def aggregate_column(column_name)
+ return column_name if Arel::Expressions === column_name
+
if @klass.column_names.include?(column_name.to_s)
Arel::Attribute.new(@klass.unscoped.table, column_name)
else
@@ -273,15 +275,10 @@ module ActiveRecord
else
group_fields = group_attrs
end
+ group_fields = arel_columns(group_fields)
- group_aliases = group_fields.map { |field|
- column_alias_for(field)
- }
- group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
- [aliaz, field]
- }
-
- group = group_fields
+ group_aliases = group_fields.map { |field| column_alias_for(field) }
+ group_columns = group_aliases.zip(group_fields)
if operation == 'count' && column_name == :all
aggregate_alias = 'count_all'
@@ -297,7 +294,7 @@ module ActiveRecord
]
select_values += select_values unless having_clause.empty?
- select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
+ select_values.concat group_columns.map { |aliaz, field|
if field.respond_to?(:as)
field.as(aliaz)
else
@@ -306,7 +303,7 @@ module ActiveRecord
}
relation = except(:group)
- relation.group_values = group
+ relation.group_values = group_fields
relation.select_values = select_values
calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
@@ -367,9 +364,9 @@ module ActiveRecord
end
end
- # TODO: refactor to allow non-string `select_values` (eg. Arel nodes).
def select_for_count
if select_values.present?
+ return select_values.first if select_values.one?
select_values.join(", ")
else
:all
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 0dcecbd42d..f5afc1000d 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -375,7 +375,7 @@ module ActiveRecord
#
# This means it can be used in association definitions:
#
- # has_many :comments, -> { unscope where: :trashed }
+ # has_many :comments, -> { unscope(where: :trashed) }
#
def unscope(*args)
check_if_method_has_arguments!(:unscope, args)
@@ -552,8 +552,6 @@ module ActiveRecord
WhereChain.new(spawn)
elsif opts.blank?
self
- elsif !opts.is_a?(String) && !opts.respond_to?(:to_h)
- raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
else
spawn.where!(opts, *rest)
end
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index 83ac074f97..a81ff98e49 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -20,8 +20,10 @@ module ActiveRecord
attributes, binds = predicate_builder.create_binds(attributes)
parts = predicate_builder.build_from_hash(attributes)
- else
+ when Arel::Nodes::Node
parts = [opts]
+ else
+ raise ArgumentError, "Unsupported argument type: #{opts} (#{opts.class})"
end
WhereClause.new(parts, binds)
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 17bc066e4d..cdcb73382f 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -17,7 +17,7 @@ module ActiveRecord
#
# class Post < ActiveRecord::Base
# def self.default_scope
- # where published: true
+ # where(published: true)
# end
# end
#
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index ea7927a435..c0c29a618c 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -94,8 +94,9 @@ module ActiveRecord
rescue DatabaseAlreadyExists
$stderr.puts "#{configuration['database']} already exists"
rescue Exception => error
- $stderr.puts error, *(error.backtrace)
+ $stderr.puts error
$stderr.puts "Couldn't create database for #{configuration.inspect}"
+ raise
end
def create_all
@@ -115,8 +116,9 @@ module ActiveRecord
rescue ActiveRecord::NoDatabaseError
$stderr.puts "Database '#{configuration['database']}' does not exist"
rescue Exception => error
- $stderr.puts error, *(error.backtrace)
+ $stderr.puts error
$stderr.puts "Couldn't drop #{configuration['database']}"
+ raise
end
def drop_all
diff --git a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
index 9ab64d0325..9ec3c8a94a 100644
--- a/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/sqlite_database_tasks.rb
@@ -19,11 +19,15 @@ module ActiveRecord
path = Pathname.new configuration['database']
file = path.absolute? ? path.to_s : File.join(root, path)
- FileUtils.rm(file) if File.exist?(file)
+ FileUtils.rm(file)
+ rescue Errno::ENOENT => error
+ raise NoDatabaseError.new(error.message, error)
end
def purge
drop
+ rescue NoDatabaseError
+ ensure
create
end
diff --git a/activerecord/lib/rails/generators/active_record/migration.rb b/activerecord/lib/rails/generators/active_record/migration.rb
index b7418cf42f..c2b2209638 100644
--- a/activerecord/lib/rails/generators/active_record/migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration.rb
@@ -13,6 +13,13 @@ module ActiveRecord
ActiveRecord::Migration.next_migration_number(next_migration_number)
end
end
+
+ private
+
+ def primary_key_type
+ key_type = options[:primary_key_type]
+ ", id: :#{key_type}" if key_type
+ end
end
end
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index 0d57de4d65..4e5872b585 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -5,6 +5,8 @@ module ActiveRecord
class MigrationGenerator < Base # :nodoc:
argument :attributes, :type => :array, :default => [], :banner => "field[:type][:index] field[:type][:index]"
+ class_option :primary_key_type, type: :string, desc: "The type for primary key"
+
def create_migration_file
set_local_assigns!
validate_file_name!
diff --git a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
index 5b3e57dcf6..fadab2a1e6 100644
--- a/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/templates/create_table_migration.rb
@@ -1,6 +1,6 @@
class <%= migration_class_name %> < ActiveRecord::Migration
def change
- create_table :<%= table_name %> do |t|
+ create_table :<%= table_name %><%= primary_key_type %> do |t|
<% attributes.each do |attribute| -%>
<% if attribute.password_digest? -%>
t.string :password_digest<%= attribute.inject_options %>
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 7e8d68ce69..395951ac9d 100644
--- a/activerecord/lib/rails/generators/active_record/model/model_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/model/model_generator.rb
@@ -7,14 +7,13 @@ module ActiveRecord
check_class_collision
- class_option :migration, :type => :boolean
- class_option :timestamps, :type => :boolean
- class_option :parent, :type => :string, :desc => "The parent class for the generated model"
- class_option :indexes, :type => :boolean, :default => true, :desc => "Add indexes for references and belongs_to columns"
+ class_option :migration, type: :boolean
+ class_option :timestamps, type: :boolean
+ class_option :parent, type: :string, desc: "The parent class for the generated model"
+ class_option :indexes, type: :boolean, default: true, desc: "Add indexes for references and belongs_to columns"
+ class_option :primary_key_type, type: :string, desc: "The type for primary key"
-
# creates the migration file for the model.
-
def create_migration_file
return unless options[:migration] && options[:parent].nil?
attributes.each { |a| a.attr_options.delete(:index) if a.reference? && !a.has_index? } if options[:indexes] == false