aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md66
-rw-r--r--activerecord/lib/active_record/associations.rb6
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb112
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb145
-rw-r--r--activerecord/lib/active_record/associations/join_dependency.rb6
-rw-r--r--activerecord/lib/active_record/associations/preloader/association.rb2
-rw-r--r--activerecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb12
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/column.rb24
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb7
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb4
-rw-r--r--activerecord/lib/active_record/no_touching.rb4
-rw-r--r--activerecord/lib/active_record/querying.rb3
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb12
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb44
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb32
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/case_sensitive_handler.rb21
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb6
-rw-r--r--activerecord/lib/active_record/relation/where_clause_factory.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb14
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb24
-rw-r--r--activerecord/lib/active_record/scoping/named.rb2
-rw-r--r--activerecord/lib/active_record/table_metadata.rb4
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb32
-rw-r--r--activerecord/test/cases/adapters/mysql2/enum_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/bit_string_test.rb5
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb4
-rw-r--r--activerecord/test/cases/associations/eager_test.rb22
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb49
-rw-r--r--activerecord/test/cases/associations/left_outer_join_association_test.rb25
-rw-r--r--activerecord/test/cases/associations_test.rb7
-rw-r--r--activerecord/test/cases/column_definition_test.rb7
-rw-r--r--activerecord/test/cases/database_statements_test.rb7
-rw-r--r--activerecord/test/cases/defaults_test.rb86
-rw-r--r--activerecord/test/cases/finder_test.rb9
-rw-r--r--activerecord/test/cases/migration_test.rb17
-rw-r--r--activerecord/test/cases/relation/where_test.rb2
-rw-r--r--activerecord/test/cases/relation_test.rb4
-rw-r--r--activerecord/test/cases/relations_test.rb9
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb27
-rw-r--r--activerecord/test/cases/serialized_attribute_test.rb7
-rw-r--r--activerecord/test/cases/touch_later_test.rb9
-rw-r--r--activerecord/test/models/post.rb1
50 files changed, 509 insertions, 432 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index bf81c23036..4aa1853bde 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,63 @@
+* Remove standardized column types/arguments spaces in schema dump.
+
+ *Tim Petricola*
+
+* Avoid loading records from database when they are already loaded using
+ the `pluck` method on a collection.
+
+ Fixes #25921.
+
+ *Ryuta Kamizono*
+
+* Remove text default treated as an empty string in non-strict mode for
+ consistency with other types.
+
+ Strict mode controls how MySQL handles invalid or missing values in
+ data-change statements such as INSERT or UPDATE. If strict mode is not
+ in effect, MySQL inserts adjusted values for invalid or missing values
+ and produces warnings.
+
+ def test_mysql_not_null_defaults_non_strict
+ using_strict(false) do
+ with_mysql_not_null_table do |klass|
+ record = klass.new
+ assert_nil record.non_null_integer
+ assert_nil record.non_null_string
+ assert_nil record.non_null_text
+ assert_nil record.non_null_blob
+
+ record.save!
+ record.reload
+
+ assert_equal 0, record.non_null_integer
+ assert_equal "", record.non_null_string
+ assert_equal "", record.non_null_text
+ assert_equal "", record.non_null_blob
+ end
+ end
+ end
+
+ https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict
+
+ *Ryuta Kamizono*
+
+* Sqlite3 migrations to add a column to an existing table can now be
+ successfully rolled back when the column was given and invalid column
+ type.
+
+ Fixes #26087
+
+ *Travis O'Neill*
+
+* Deprecate `sanitize_conditions`. Use `sanitize_sql` instead.
+
+ *Ryuta Kamizono*
+
+* Doing count on relations that contain LEFT OUTER JOIN Arel node no longer
+ force a DISTINCT. This solves issues when using count after a left_joins.
+
+ *Maxime Handfield Lapointe*
+
* RecordNotFound raised by association.find exposes `id`, `primary_key` and
`model` methods to be consistent with RecordNotFound raised by Record.find.
@@ -52,8 +112,8 @@
*Xavier Noria*
* Using `group` with an attribute that has a custom type will properly cast
- the hash keys after calling a calculation method like `count`.
-
+ the hash keys after calling a calculation method like `count`.
+
Fixes #25595.
*Sean Griffin*
@@ -88,7 +148,7 @@
*Sean Griffin*
* Ensure hashes can be assigned to attributes created using `composed_of`.
-
+
Fixes #25210.
*Sean Griffin*
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 659e512ce2..dc68b01386 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1296,6 +1296,12 @@ module ActiveRecord
# If using with the <tt>:through</tt> option, the association on the join model must be
# a #belongs_to, and the records which get deleted are the join records, rather than
# the associated records.
+ #
+ # If using <tt>dependent: :destroy</tt> on a scoped association, only the scoped objects are destroyed.
+ # For example, if a Post model defines
+ # <tt>has_many :comments, -> { where published: true }, dependent: :destroy</tt> and <tt>destroy</tt> is
+ # called on a post, only published comments are destroyed. This means that any unpublished comments in the
+ # database would still contain a foreign key pointing to the now deleted post.
# [:counter_cache]
# This option can be used to configure a custom named <tt>:counter_cache.</tt> You only need this option,
# when you customized the name of your <tt>:counter_cache</tt> on the #belongs_to association.
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index 44404cc176..0f51b35164 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -82,14 +82,6 @@ module ActiveRecord
@target = []
end
- def select(*fields)
- if block_given?
- load_target.select.each { |e| yield e }
- else
- scope.select(*fields)
- end
- end
-
def find(*args)
if block_given?
load_target.find(*args) { |*block_args| yield(*block_args) }
@@ -111,50 +103,6 @@ module ActiveRecord
end
end
- def first(*args)
- first_nth_or_last(:first, *args)
- end
-
- def second(*args)
- first_nth_or_last(:second, *args)
- end
-
- def third(*args)
- first_nth_or_last(:third, *args)
- end
-
- def fourth(*args)
- first_nth_or_last(:fourth, *args)
- end
-
- def fifth(*args)
- first_nth_or_last(:fifth, *args)
- end
-
- def forty_two(*args)
- first_nth_or_last(:forty_two, *args)
- end
-
- def third_to_last(*args)
- first_nth_or_last(:third_to_last, *args)
- end
-
- def second_to_last(*args)
- first_nth_or_last(:second_to_last, *args)
- end
-
- def last(*args)
- first_nth_or_last(:last, *args)
- end
-
- def take(limit = nil)
- if find_from_target?
- limit ? load_target.take(limit) : load_target.first
- else
- scope.take(limit)
- end
- end
-
def build(attributes = {}, &block)
if attributes.is_a?(Array)
attributes.collect { |attr| build(attr, &block) }
@@ -322,15 +270,6 @@ module ActiveRecord
end
end
- # Returns the size of the collection calling +size+ on the target.
- #
- # If the collection has been already loaded +length+ and +size+ are
- # equivalent. If not and you are going to need the records anyway this
- # method will take one less query. Otherwise +size+ is more efficient.
- def length
- load_target.size
- end
-
# Returns true if the collection is empty.
#
# If the collection has been loaded
@@ -347,28 +286,6 @@ module ActiveRecord
end
end
- # Returns true if the collections is not empty.
- # If block given, loads all records and checks for one or more matches.
- # Otherwise, equivalent to +!collection.empty?+.
- def any?
- if block_given?
- load_target.any? { |*block_args| yield(*block_args) }
- else
- !empty?
- end
- end
-
- # Returns true if the collection has more than 1 record.
- # If block given, loads all records and checks for two or more matches.
- # Otherwise, equivalent to +collection.size > 1+.
- def many?
- if block_given?
- load_target.many? { |*block_args| yield(*block_args) }
- else
- size > 1
- end
- end
-
def distinct
seen = {}
load_target.find_all do |record|
@@ -453,6 +370,12 @@ module ActiveRecord
owner.new_record? && !foreign_key_present?
end
+ def find_from_target?
+ loaded? ||
+ owner.new_record? ||
+ target.any? { |record| record.new_record? || record.changed? }
+ end
+
private
def find_target
@@ -599,21 +522,6 @@ module ActiveRecord
owner.class.send(full_callback_name)
end
- # Should we deal with assoc.first or assoc.last by issuing an independent query to
- # the database, or by getting the target, and then taking the first/last item from that?
- #
- # If the args is just a non-empty options hash, go to the database.
- #
- # Otherwise, go to the database only if none of the following are true:
- # * target already loaded
- # * owner is new record
- # * target contains new or changed record(s)
- def find_from_target?
- loaded? ||
- owner.new_record? ||
- target.any? { |record| record.new_record? || record.changed? }
- end
-
def include_in_memory?(record)
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
assoc = owner.association(reflection.through_reflection.name)
@@ -640,14 +548,6 @@ module ActiveRecord
load_target.select { |r| ids.include?(r.id.to_s) }
end
end
-
- # Fetches the first/last using SQL if possible, otherwise from the target array.
- def first_nth_or_last(type, *args)
- args.shift if args.first.is_a?(Hash) && args.first.empty?
-
- collection = find_from_target? ? load_target : scope
- collection.send(type, *args)
- end
end
end
end
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index fb1b31429e..48436155ce 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -28,7 +28,6 @@ module ActiveRecord
# is computed directly through SQL and does not trigger by itself the
# instantiation of the actual post records.
class CollectionProxy < Relation
- delegate(*(ActiveRecord::Calculations.public_instance_methods - [:count]), to: :scope)
delegate :exists?, :update_all, :arel, to: :scope
def initialize(klass, association) #:nodoc:
@@ -54,6 +53,12 @@ module ActiveRecord
@association.loaded?
end
+ ##
+ # :method: select
+ #
+ # :call-seq:
+ # select(*fields, &block)
+ #
# Works in two ways.
#
# *First:* Specify a subset of fields to be selected from the result set.
@@ -107,9 +112,6 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook">,
# # #<Pet id: 3, name: "Choo-Choo">
# # ]
- def select(*fields, &block)
- @association.select(*fields, &block)
- end
# Finds an object in the collection responding to the +id+. Uses the same
# rules as ActiveRecord::Base.find. Returns ActiveRecord::RecordNotFound
@@ -141,6 +143,12 @@ module ActiveRecord
@association.find(*args, &block)
end
+ ##
+ # :method: first
+ #
+ # :call-seq:
+ # first(limit = nil)
+ #
# Returns the first record, or the first +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
# form returns an empty array.
@@ -167,45 +175,63 @@ module ActiveRecord
# another_person_without.pets # => []
# another_person_without.pets.first # => nil
# another_person_without.pets.first(3) # => []
- def first(*args)
- @association.first(*args)
- end
+ ##
+ # :method: second
+ #
+ # :call-seq:
+ # second()
+ #
# Same as #first except returns only the second record.
- def second(*args)
- @association.second(*args)
- end
+ ##
+ # :method: third
+ #
+ # :call-seq:
+ # third()
+ #
# Same as #first except returns only the third record.
- def third(*args)
- @association.third(*args)
- end
+ ##
+ # :method: fourth
+ #
+ # :call-seq:
+ # fourth()
+ #
# Same as #first except returns only the fourth record.
- def fourth(*args)
- @association.fourth(*args)
- end
+ ##
+ # :method: fifth
+ #
+ # :call-seq:
+ # fifth()
+ #
# Same as #first except returns only the fifth record.
- def fifth(*args)
- @association.fifth(*args)
- end
+ ##
+ # :method: forty_two
+ #
+ # :call-seq:
+ # forty_two()
+ #
# Same as #first except returns only the forty second record.
# Also known as accessing "the reddit".
- def forty_two(*args)
- @association.forty_two(*args)
- end
+ ##
+ # :method: third_to_last
+ #
+ # :call-seq:
+ # third_to_last()
+ #
# Same as #first except returns only the third-to-last record.
- def third_to_last(*args)
- @association.third_to_last(*args)
- end
+ ##
+ # :method: second_to_last
+ #
+ # :call-seq:
+ # second_to_last()
+ #
# Same as #first except returns only the second-to-last record.
- def second_to_last(*args)
- @association.second_to_last(*args)
- end
# Returns the last record, or the last +n+ records, from the collection.
# If the collection is empty, the first form returns +nil+, and the second
@@ -233,8 +259,9 @@ module ActiveRecord
# another_person_without.pets # => []
# another_person_without.pets.last # => nil
# another_person_without.pets.last(3) # => []
- def last(*args)
- @association.last(*args)
+ def last(limit = nil)
+ load_target if find_from_target?
+ super
end
# Gives a record (or N records if a parameter is supplied) from the collection
@@ -263,7 +290,8 @@ module ActiveRecord
# another_person_without.pets.take # => nil
# another_person_without.pets.take(2) # => []
def take(limit = nil)
- @association.take(limit)
+ load_target if find_from_target?
+ super
end
# Returns a new object of the collection type that has been instantiated
@@ -738,6 +766,14 @@ module ActiveRecord
@association.count(column_name, &block)
end
+ def calculate(operation, column_name)
+ null_scope? ? scope.calculate(operation, column_name) : super
+ end
+
+ def pluck(*column_names)
+ null_scope? ? scope.pluck(*column_names) : super
+ end
+
# Returns the size of the collection. If the collection hasn't been loaded,
# it executes a <tt>SELECT COUNT(*)</tt> query. Else it calls <tt>collection.size</tt>.
#
@@ -766,6 +802,12 @@ module ActiveRecord
@association.size
end
+ ##
+ # :method: length
+ #
+ # :call-seq:
+ # length()
+ #
# Returns the size of the collection calling +size+ on the target.
# If the collection has been already loaded, +length+ and +size+ are
# equivalent. If not and you are going to need the records anyway this
@@ -786,9 +828,6 @@ module ActiveRecord
# # #<Pet id: 2, name: "Spook", person_id: 1>,
# # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
# # ]
- def length
- @association.length
- end
# Returns +true+ if the collection is empty. If the collection has been
# loaded it is equivalent
@@ -812,6 +851,12 @@ module ActiveRecord
@association.empty?
end
+ ##
+ # :method: any?
+ #
+ # :call-seq:
+ # any?()
+ #
# Returns +true+ if the collection is not empty.
#
# class Person < ActiveRecord::Base
@@ -841,10 +886,13 @@ module ActiveRecord
# pet.group == 'dogs'
# end
# # => true
- def any?(&block)
- @association.any?(&block)
- end
+ ##
+ # :method: many?
+ #
+ # :call-seq:
+ # many?()
+ #
# Returns true if the collection has more than one record.
# Equivalent to <tt>collection.size > 1</tt>.
#
@@ -879,9 +927,6 @@ module ActiveRecord
# pet.group == 'cats'
# end
# # => true
- def many?(&block)
- @association.many?(&block)
- end
# Returns +true+ if the given +record+ is present in the collection.
#
@@ -1071,8 +1116,28 @@ module ActiveRecord
self
end
+ protected
+
+ def find_nth_with_limit(index, limit)
+ load_target if find_from_target?
+ super
+ end
+
+ def find_nth_from_last(index)
+ load_target if find_from_target?
+ super
+ end
+
private
+ def null_scope?
+ @association.null_scope?
+ end
+
+ def find_from_target?
+ @association.find_from_target?
+ end
+
def exec_queries
load_target
end
diff --git a/activerecord/lib/active_record/associations/join_dependency.rb b/activerecord/lib/active_record/associations/join_dependency.rb
index 3946d5baa4..62acad0eda 100644
--- a/activerecord/lib/active_record/associations/join_dependency.rb
+++ b/activerecord/lib/active_record/associations/join_dependency.rb
@@ -274,7 +274,11 @@ module ActiveRecord
construct(model, node, row, rs, seen, model_cache, aliases)
else
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
- model.readonly!
+
+ if node.reflection.scope_for(node.base_klass).readonly_value
+ model.readonly!
+ end
+
seen[ar_parent.object_id][node.base_klass][id] = model
construct(model, node, row, rs, seen, model_cache, aliases)
end
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index a8afa48865..4bb627f399 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -113,7 +113,7 @@ module ActiveRecord
end
def reflection_scope
- @reflection_scope ||= reflection.scope ? klass.unscoped.instance_exec(nil, &reflection.scope) : klass.unscoped
+ @reflection_scope ||= reflection.scope_for(klass)
end
def build_scope
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index 01d7886406..1e7e939097 100644
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -312,8 +312,8 @@ module ActiveRecord #:nodoc:
include NestedAttributes
include Aggregations
include Transactions
- include NoTouching
include TouchLater
+ include NoTouching
include Reflection
include Serialization
include Store
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 4a1e1f0ffc..def1dadb6a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -90,6 +90,7 @@ module ActiveRecord
# +binds+ as the bind substitutes. +name+ is logged along with
# the executed +sql+ statement.
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
+ sql, binds = sql_for_insert(sql, pk, nil, sequence_name, binds)
exec_query(sql, name, binds)
end
@@ -121,8 +122,7 @@ module ActiveRecord
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
- value = exec_insert(sql, name, binds, pk, sequence_name)
+ value = exec_insert(to_sql(arel, binds), name, binds, pk, sequence_name)
id_value || last_inserted_id(value)
end
alias create insert
@@ -379,7 +379,7 @@ module ActiveRecord
end
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
- [sql, binds, pk, sequence_name]
+ [sql, binds]
end
def last_inserted_id(result)
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 d9a799676f..ffde4f2c93 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
@@ -303,7 +303,7 @@ module ActiveRecord
# end
def column(name, type, options = {})
name = name.to_s
- type = type.to_sym
+ type = type.to_sym if type
options = options.dup
if @columns_hash[name] && @columns_hash[name].primary_key?
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 afa0860707..45d782e45e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -1047,7 +1047,8 @@ module ActiveRecord
end
def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc:
- if native = native_database_types[type.to_sym]
+ type = type.to_sym if type
+ if native = native_database_types[type]
column_type_sql = (native.is_a?(Hash) ? native[:name] : native).dup
if type == :decimal # ignore limit, use precision and scale
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index 0c7197a002..4dde525ebc 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -433,16 +433,16 @@ module ActiveRecord
@connection
end
- def case_sensitive_comparison(table, attribute, column, value)
- table[attribute].eq(Arel::Nodes::BindParam.new)
+ def case_sensitive_comparison(attribute, column, value) # :nodoc:
+ attribute.eq(value)
end
- def case_insensitive_comparison(table, attribute, column, value)
+ def case_insensitive_comparison(attribute, column, value) # :nodoc:
if can_perform_case_insensitive_comparison_for?(column)
- table[attribute].lower.eq(table.lower(Arel::Nodes::BindParam.new))
- else
- table[attribute].eq(Arel::Nodes::BindParam.new)
+ value = attribute.relation.lower(value)
+ attribute = attribute.lower
end
+ attribute.eq(value)
end
def can_perform_case_insensitive_comparison_for?(column)
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 5900919e88..3a28879c15 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -613,12 +613,11 @@ module ActiveRecord
SQL
end
- def case_sensitive_comparison(table, attribute, column, value)
+ def case_sensitive_comparison(attribute, column, value) # :nodoc:
if column.collation && !column.case_sensitive?
- table[attribute].eq(Arel::Nodes::Bin.new(Arel::Nodes::BindParam.new))
- else
- super
+ value = Arel::Nodes::Bin.new(value)
end
+ attribute.eq(value)
end
def can_perform_case_insensitive_comparison_for?(column)
@@ -710,7 +709,7 @@ module ActiveRecord
end
def fetch_type_metadata(sql_type, extra = "")
- MySQL::TypeMetadata.new(super(sql_type), extra: extra, strict: strict_mode?)
+ MySQL::TypeMetadata.new(super(sql_type), extra: extra)
end
def add_index_length(quoted_columns, **options)
@@ -923,7 +922,7 @@ module ActiveRecord
when 3; "mediumint"
when nil, 4; "int"
when 5..8; "bigint"
- else raise(ActiveRecordError, "No integer type has byte size #{limit}")
+ else raise(ActiveRecordError, "No integer type has byte size #{limit}. Use a decimal with scale 0 instead.")
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/column.rb b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
index 5452e44c84..296d9a15f8 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/column.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/column.rb
@@ -2,21 +2,7 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class Column < ConnectionAdapters::Column # :nodoc:
- delegate :strict, :extra, to: :sql_type_metadata, allow_nil: true
-
- def initialize(*)
- super
- extract_default
- end
-
- def has_default?
- return false if blob_or_text_column? # MySQL forbids defaults on blob and text columns
- super
- end
-
- def blob_or_text_column?
- /\A(?:tiny|medium|long)?blob\b/ === sql_type || type == :text
- end
+ delegate :extra, to: :sql_type_metadata, allow_nil: true
def unsigned?
/\bunsigned\z/ === sql_type
@@ -29,14 +15,6 @@ module ActiveRecord
def auto_increment?
extra == "auto_increment"
end
-
- private
-
- def extract_default
- if blob_or_text_column?
- @default = null || strict ? nil : ""
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
index 1be5cb4740..24dcf852e1 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/type_metadata.rb
@@ -2,13 +2,12 @@ module ActiveRecord
module ConnectionAdapters
module MySQL
class TypeMetadata < DelegateClass(SqlTypeMetadata) # :nodoc:
- attr_reader :extra, :strict
+ attr_reader :extra
- def initialize(type_metadata, extra: "", strict: false)
+ def initialize(type_metadata, extra: "")
super(type_metadata)
@type_metadata = type_metadata
@extra = extra
- @strict = strict
end
def ==(other)
@@ -24,7 +23,7 @@ module ActiveRecord
protected
def attributes_for_hash
- [self.class, @type_metadata, extra, strict]
+ [self.class, @type_metadata, extra]
end
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 090ef989e6..7414eba6c5 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -124,26 +124,29 @@ module ActiveRecord
pk = primary_key(table_ref) if table_ref
end
- pk = suppress_composite_primary_key(pk)
-
- if pk && use_insert_returning?
+ if pk = suppress_composite_primary_key(pk)
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
end
super
end
+ protected :sql_for_insert
def exec_insert(sql, name = nil, binds = [], pk = nil, sequence_name = nil)
- val = exec_query(sql, name, binds)
- if !use_insert_returning? && pk
+ if use_insert_returning? || pk == false
+ super
+ else
+ result = exec_query(sql, name, binds)
unless sequence_name
table_ref = extract_table_ref_from_insert_sql(sql)
- sequence_name = default_sequence_name(table_ref, pk)
- return val unless sequence_name
+ if table_ref
+ pk = primary_key(table_ref) if pk.nil?
+ pk = suppress_composite_primary_key(pk)
+ sequence_name = default_sequence_name(table_ref, pk)
+ end
+ return result unless sequence_name
end
last_insert_id_result(sequence_name)
- else
- val
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
index a6b5a89ec0..74bff229ea 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb
@@ -7,7 +7,7 @@ module ActiveRecord
:bit
end
- def cast(value)
+ def cast_value(value)
if ::String === value
case value
when /^0x/i
@@ -16,7 +16,7 @@ module ActiveRecord
value # Bit-string notation
end
else
- value
+ value.to_s
end
end
diff --git a/activerecord/lib/active_record/no_touching.rb b/activerecord/lib/active_record/no_touching.rb
index edb5066fa0..4059020e25 100644
--- a/activerecord/lib/active_record/no_touching.rb
+++ b/activerecord/lib/active_record/no_touching.rb
@@ -45,6 +45,10 @@ module ActiveRecord
NoTouching.applied_to?(self.class)
end
+ def touch_later(*) # :nodoc:
+ super unless no_touching?
+ end
+
def touch(*) # :nodoc:
super unless no_touching?
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 7b4b8c60f8..dd7d650207 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -62,8 +62,7 @@ module ActiveRecord
#
# * +sql+ - An SQL statement which should return a count query from the database, see the example above.
def count_by_sql(sql)
- sql = sanitize_conditions(sql)
- connection.select_value(sql, "#{name} Count").to_i
+ connection.select_value(sanitize_sql(sql), "#{name} Count").to_i
end
end
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 3553ff4da3..8c5e4d042e 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -311,6 +311,10 @@ module ActiveRecord
active_record == other_aggregation.active_record
end
+ def scope_for(klass)
+ scope ? klass.unscoped.instance_exec(nil, &scope) : klass.unscoped
+ end
+
private
def derive_class_name
name.to_s.camelize
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index ecf3700aab..b569abc7a8 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -117,7 +117,10 @@ module ActiveRecord
end
if has_include?(column_name)
- construct_relation_for_association_calculations.calculate(operation, column_name)
+ relation = construct_relation_for_association_calculations
+ relation = relation.distinct if operation.to_s.downcase == "count"
+
+ relation.calculate(operation, column_name)
else
perform_calculation(operation, column_name)
end
@@ -160,7 +163,7 @@ module ActiveRecord
#
def pluck(*column_names)
if loaded? && (column_names.map(&:to_s) - @klass.attribute_names - @klass.attribute_aliases.keys).empty?
- return @records.pluck(*column_names)
+ return records.pluck(*column_names)
end
if has_include?(column_names.first)
@@ -198,11 +201,6 @@ module ActiveRecord
if operation == "count"
column_name ||= select_for_count
-
- unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
- distinct = true
- end
-
column_name = primary_key if column_name == :all && distinct
distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index d46b4e0683..376867675b 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -97,13 +97,13 @@ module ActiveRecord
# Person.take(5) # returns 5 objects fetched by SELECT * FROM people LIMIT 5
# Person.where(["name LIKE '%?'", name]).take
def take(limit = nil)
- limit ? limit(limit).to_a : find_take
+ limit ? find_take_with_limit(limit) : find_take
end
# Same as #take but raises ActiveRecord::RecordNotFound if no record
# is found. Note that #take! accepts no arguments.
def take!
- take or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ take || raise_record_not_found_exception!
end
# Find the first record (or first N records if a parameter is supplied).
@@ -126,7 +126,7 @@ module ActiveRecord
# Same as #first but raises ActiveRecord::RecordNotFound if no record
# is found. Note that #first! accepts no arguments.
def first!
- find_nth! 0
+ first || raise_record_not_found_exception!
end
# Find the last record (or last N records if a parameter is supplied).
@@ -165,7 +165,7 @@ module ActiveRecord
# Same as #last but raises ActiveRecord::RecordNotFound if no record
# is found. Note that #last! accepts no arguments.
def last!
- last or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ last || raise_record_not_found_exception!
end
# Find the second record.
@@ -181,7 +181,7 @@ module ActiveRecord
# Same as #second but raises ActiveRecord::RecordNotFound if no record
# is found.
def second!
- find_nth! 1
+ second || raise_record_not_found_exception!
end
# Find the third record.
@@ -197,7 +197,7 @@ module ActiveRecord
# Same as #third but raises ActiveRecord::RecordNotFound if no record
# is found.
def third!
- find_nth! 2
+ third || raise_record_not_found_exception!
end
# Find the fourth record.
@@ -213,7 +213,7 @@ module ActiveRecord
# Same as #fourth but raises ActiveRecord::RecordNotFound if no record
# is found.
def fourth!
- find_nth! 3
+ fourth || raise_record_not_found_exception!
end
# Find the fifth record.
@@ -229,7 +229,7 @@ module ActiveRecord
# Same as #fifth but raises ActiveRecord::RecordNotFound if no record
# is found.
def fifth!
- find_nth! 4
+ fifth || raise_record_not_found_exception!
end
# Find the forty-second record. Also known as accessing "the reddit".
@@ -245,7 +245,7 @@ module ActiveRecord
# Same as #forty_two but raises ActiveRecord::RecordNotFound if no record
# is found.
def forty_two!
- find_nth! 41
+ forty_two || raise_record_not_found_exception!
end
# Find the third-to-last record.
@@ -261,7 +261,7 @@ module ActiveRecord
# Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def third_to_last!
- find_nth_from_last 3 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ third_to_last || raise_record_not_found_exception!
end
# Find the second-to-last record.
@@ -277,7 +277,7 @@ module ActiveRecord
# Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def second_to_last!
- find_nth_from_last 2 or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ second_to_last || raise_record_not_found_exception!
end
# Returns true if a record exists in the table that matches the +id+ or
@@ -345,12 +345,16 @@ module ActiveRecord
# of results obtained should be provided in the +result_size+ argument and
# the expected number of results should be provided in the +expected_size+
# argument.
- def raise_record_not_found_exception!(ids, result_size, expected_size) #:nodoc:
+ def raise_record_not_found_exception!(ids = nil, result_size = nil, expected_size = nil) # :nodoc:
conditions = arel.where_sql(@klass.arel_engine)
conditions = " [#{conditions}]" if conditions
name = @klass.name
- if Array(ids).size == 1
+ if ids.nil?
+ error = "Couldn't find #{name}"
+ error << " with#{conditions}" if conditions
+ raise RecordNotFound, error
+ elsif Array(ids).size == 1
error = "Couldn't find #{name} with '#{primary_key}'=#{ids}#{conditions}"
raise RecordNotFound.new(error, name, primary_key, ids)
else
@@ -522,17 +526,21 @@ module ActiveRecord
end
end
- def find_nth(index)
- @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
+ def find_take_with_limit(limit)
+ if loaded?
+ records.take(limit)
+ else
+ limit(limit).to_a
+ end
end
- def find_nth!(index)
- find_nth(index) or raise RecordNotFound.new("Couldn't find #{@klass.name} with [#{arel.where_sql(@klass.arel_engine)}]")
+ def find_nth(index)
+ @offsets[offset_index + index] ||= find_nth_with_limit(index, 1).first
end
def find_nth_with_limit(index, limit)
if loaded?
- records[index, limit]
+ records[index, limit] || []
else
relation = if order_values.empty? && primary_key
order(arel_attribute(primary_key).asc)
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 0c8282de8e..29422bf131 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -4,6 +4,7 @@ module ActiveRecord
require "active_record/relation/predicate_builder/association_query_handler"
require "active_record/relation/predicate_builder/base_handler"
require "active_record/relation/predicate_builder/basic_object_handler"
+ require "active_record/relation/predicate_builder/case_sensitive_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"
@@ -16,6 +17,7 @@ module ActiveRecord
@handlers = []
register_handler(BasicObject, BasicObjectHandler.new)
+ register_handler(CaseSensitiveHandler::Value, CaseSensitiveHandler.new)
register_handler(Class, ClassHandler.new(self))
register_handler(Base, BaseHandler.new(self))
register_handler(Range, RangeHandler.new)
@@ -31,9 +33,9 @@ module ActiveRecord
expand_from_hash(attributes)
end
- def create_binds(attributes)
+ def create_binds(attributes, options)
attributes = convert_dot_notation_to_hash(attributes)
- create_binds_for_hash(attributes)
+ create_binds_for_hash(attributes, options)
end
def self.references(attributes)
@@ -74,7 +76,7 @@ module ActiveRecord
return ["1=0"] if attributes.empty?
attributes.flat_map do |key, value|
- if value.is_a?(Hash)
+ if value.is_a?(Hash) && !table.has_column?(key)
associated_predicate_builder(key).expand_from_hash(value)
else
build(table.arel_attribute(key), value)
@@ -82,14 +84,14 @@ module ActiveRecord
end
end
- def create_binds_for_hash(attributes)
+ def create_binds_for_hash(attributes, options)
result = attributes.dup
binds = []
attributes.each do |column_name, value|
case
- when value.is_a?(Hash)
- attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value)
+ when value.is_a?(Hash) && !table.has_column?(column_name)
+ attrs, bvs = associated_predicate_builder(column_name).create_binds_for_hash(value, options)
result[column_name] = attrs
binds += bvs
next
@@ -108,11 +110,15 @@ module ActiveRecord
end
result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?)
- else
- if can_be_bound?(column_name, value)
- result[column_name] = Arel::Nodes::BindParam.new
- binds << build_bind_param(column_name, value)
- end
+ when can_be_bound?(column_name, value)
+ result[column_name] =
+ if perform_case_sensitive?(options)
+ CaseSensitiveHandler::Value.new(
+ Arel::Nodes::BindParam.new, table, options[:case_sensitive])
+ else
+ Arel::Nodes::BindParam.new
+ end
+ binds << build_bind_param(column_name, value)
end
# Find the foreign key when using queries such as:
@@ -164,6 +170,10 @@ module ActiveRecord
end
end
+ def perform_case_sensitive?(options)
+ options.key?(:case_sensitive)
+ end
+
def build_bind_param(column_name, value)
Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/case_sensitive_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/case_sensitive_handler.rb
new file mode 100644
index 0000000000..acf0bbd829
--- /dev/null
+++ b/activerecord/lib/active_record/relation/predicate_builder/case_sensitive_handler.rb
@@ -0,0 +1,21 @@
+module ActiveRecord
+ class PredicateBuilder
+ class CaseSensitiveHandler # :nodoc:
+ def call(attribute, value)
+ value.call(attribute)
+ end
+
+ class Value < Struct.new(:value, :table, :case_sensitive?) # :nodoc:
+ def call(attribute)
+ klass = table.send(:klass)
+ column = klass.column_for_attribute(attribute.name)
+ if case_sensitive?
+ klass.connection.case_sensitive_comparison(attribute, column, value)
+ else
+ klass.connection.case_insensitive_comparison(attribute, column, value)
+ end
+ end
+ 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 4ee490274b..bd41653df0 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1099,9 +1099,9 @@ module ActiveRecord
def does_not_support_reverse?(order)
# Uses SQL function with multiple arguments.
- /\([^()]*,[^()]*\)/.match?(order) ||
- # Uses "nulls first" like construction.
- /nulls (first|last)\Z/i.match?(order)
+ (order.include?(",") && order.split(",").find { |section| section.count("(") != section.count(")") }) ||
+ # Uses "nulls first" like construction.
+ /nulls (first|last)\Z/i.match?(order)
end
def build_order(arel)
diff --git a/activerecord/lib/active_record/relation/where_clause_factory.rb b/activerecord/lib/active_record/relation/where_clause_factory.rb
index dc00149130..122ab04c00 100644
--- a/activerecord/lib/active_record/relation/where_clause_factory.rb
+++ b/activerecord/lib/active_record/relation/where_clause_factory.rb
@@ -17,7 +17,7 @@ module ActiveRecord
attributes = klass.send(:expand_hash_conditions_for_aggregates, attributes)
attributes.stringify_keys!
- attributes, binds = predicate_builder.create_binds(attributes)
+ attributes, binds = predicate_builder.create_binds(attributes, other.last || {})
parts = predicate_builder.build_from_hash(attributes)
when Arel::Nodes::Node
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 6617008344..7f596120eb 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -5,13 +5,6 @@ module ActiveRecord
extend ActiveSupport::Concern
module ClassMethods
- # Used to sanitize objects before they're used in an SQL SELECT statement.
- # Delegates to {connection.quote}[rdoc-ref:ConnectionAdapters::Quoting#quote].
- def sanitize(object) # :nodoc:
- connection.quote(object)
- end
- alias_method :quote_value, :sanitize
-
protected
# Accepts an array or string of SQL conditions and sanitizes
@@ -36,8 +29,9 @@ module ActiveRecord
else condition
end
end
- alias_method :sanitize_sql, :sanitize_sql_for_conditions
- alias_method :sanitize_conditions, :sanitize_sql
+ alias :sanitize_sql :sanitize_sql_for_conditions
+ alias :sanitize_conditions :sanitize_sql
+ deprecate sanitize_conditions: :sanitize_sql
# Accepts an array, hash, or string of SQL conditions and sanitizes
# them into a valid SQL fragment for a SET clause.
@@ -216,7 +210,7 @@ module ActiveRecord
# TODO: Deprecate this
def quoted_id # :nodoc:
- self.class.quote_value(@attributes[self.class.primary_key].value_for_database)
+ self.class.connection.quote(@attributes[self.class.primary_key].value_for_database)
end
end
end
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 01f788a424..ab2d64e903 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -145,29 +145,9 @@ HEADER
# find all migration keys used in this table
keys = @connection.migration_keys
- # figure out the lengths for each column based on above keys
- lengths = keys.map { |key|
- column_specs.map { |spec|
- spec[key] ? spec[key].length + 2 : 0
- }.max
- }
-
- # the string we're going to sprintf our values against, with standardized column widths
- format_string = lengths.map { |len| "%-#{len}s" }
-
- # find the max length for the 'type' column, which is special
- type_length = column_specs.map { |column| column[:type].length }.max
-
- # add column type definition to our format string
- format_string.unshift " t.%-#{type_length}s "
-
- format_string *= ""
-
column_specs.each do |colspec|
- values = keys.zip(lengths).map { |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
- values.unshift colspec[:type]
- tbl.print((format_string % values).gsub(/,\s*$/, ""))
- tbl.puts
+ values = keys.map { |key| colspec[key] }.compact
+ tbl.puts " t.#{colspec[:type]} #{values.join(", ")}"
end
indexes_in_create(table, tbl)
diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb
index 46ee3540bd..094c0e9c6f 100644
--- a/activerecord/lib/active_record/scoping/named.rb
+++ b/activerecord/lib/active_record/scoping/named.rb
@@ -174,7 +174,7 @@ module ActiveRecord
protected
def valid_scope_name?(name)
- if respond_to?(name, true)
+ if respond_to?(name, true) && logger
logger.warn "Creating scope :#{name}. " \
"Overwriting existing method #{self.name}.#{name}."
end
diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb
index e8d6a144f9..0ca880e635 100644
--- a/activerecord/lib/active_record/table_metadata.rb
+++ b/activerecord/lib/active_record/table_metadata.rb
@@ -37,6 +37,10 @@ module ActiveRecord
end
end
+ def has_column?(column_name)
+ klass && klass.columns_hash.key?(column_name.to_s)
+ end
+
def associated_with?(association_name)
klass && klass._reflect_on_association(association_name)
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 8c4930a81d..08c4b01439 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -50,37 +50,7 @@ module ActiveRecord
end
def build_relation(klass, attribute, value) # :nodoc:
- if reflection = klass._reflect_on_association(attribute)
- attribute = reflection.foreign_key
- value = value.attributes[reflection.klass.primary_key] unless value.nil?
- end
-
- if value.nil?
- return klass.unscoped.where!(attribute => value)
- end
-
- # the attribute may be an aliased attribute
- if klass.attribute_alias?(attribute)
- attribute = klass.attribute_alias(attribute)
- end
-
- attribute_name = attribute.to_s
-
- table = klass.arel_table
- column = klass.columns_hash[attribute_name]
- cast_type = klass.type_for_attribute(attribute_name)
-
- comparison = if !options[:case_sensitive]
- # will use SQL LOWER function before comparison, unless it detects a case insensitive collation
- klass.connection.case_insensitive_comparison(table, attribute, column, value)
- else
- klass.connection.case_sensitive_comparison(table, attribute, column, value)
- end
- klass.unscoped.tap do |scope|
- parts = [comparison]
- binds = [Relation::QueryAttribute.new(attribute_name, value, cast_type)]
- scope.where_clause += Relation::WhereClause.new(parts, binds)
- end
+ klass.unscoped.where!({ attribute => value }, options)
end
def scope_relation(record, relation)
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 9f51223d58..7ad3e3ca2d 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -9,11 +9,6 @@ class Mysql2EnumTest < ActiveRecord::Mysql2TestCase
assert_equal 8, column.limit
end
- def test_should_not_be_blob_or_text_column
- column = EnumTest.columns_hash["enum_column"]
- assert_not column.blob_or_text_column?
- end
-
def test_should_not_be_unsigned
column = EnumTest.columns_hash["enum_column"]
assert_not column.unsigned?
diff --git a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
index f646e59848..7712e809a2 100644
--- a/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/bit_string_test.rb
@@ -65,10 +65,11 @@ class PostgresqlBitStringTest < ActiveRecord::PostgreSQLTestCase
end
def test_roundtrip
- PostgresqlBitString.create! a_bit: "00001010", a_bit_varying: "0101"
- record = PostgresqlBitString.first
+ record = PostgresqlBitString.create!(a_bit: "00001010", a_bit_varying: "0101")
assert_equal "00001010", record.a_bit
assert_equal "0101", record.a_bit_varying
+ assert_nil record.another_bit
+ assert_nil record.another_bit_varying
record.a_bit = "11111111"
record.a_bit_varying = "0xF"
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index 4b8d06be4b..e6af93a53e 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -88,12 +88,6 @@ module ActiveRecord
assert_equal expect.to_i, result.rows.first.first
end
- def test_sql_for_insert_with_returning_disabled
- connection = connection_without_insert_returning
- sql, binds = connection.sql_for_insert("sql", nil, nil, nil, "binds")
- assert_equal ["sql", "binds"], [sql, binds]
- end
-
def test_serial_sequence
assert_equal "public.accounts_id_seq",
@connection.serial_sequence("accounts", "id")
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 321b74c1f5..9a59691737 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -198,13 +198,13 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
def test_schema_dumper_for_uuid_primary_key
schema = dump_table_schema "pg_uuids"
assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
- assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
end
def test_schema_dumper_for_uuid_primary_key_with_custom_default
schema = dump_table_schema "pg_uuids_2"
assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
- assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
end
end
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 2c826acf96..6011c552b2 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -1424,6 +1424,24 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert david.readonly_comments.first.readonly?
end
+ test "eager-loading non-readonly association" do
+ # has_one
+ firm = Firm.where(id: "1").eager_load(:account).first!
+ assert_not firm.account.readonly?
+
+ # has_and_belongs_to_many
+ project = Project.where(id: "2").eager_load(:developers).first!
+ assert_not project.developers.first.readonly?
+
+ # has_many :through
+ david = Author.where(id: "1").eager_load(:comments).first!
+ assert_not david.comments.first.readonly?
+
+ # belongs_to
+ post = Post.where(id: "1").eager_load(:author).first!
+ assert_not post.author.readonly?
+ end
+
test "eager-loading readonly association" do
# has-one
firm = Firm.where(id: "1").eager_load(:readonly_account).first!
@@ -1438,8 +1456,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
assert david.readonly_comments.first.readonly?
# belongs_to
- post = Post.where(id: "1").eager_load(:author).first!
- assert post.author.readonly?
+ post = Post.where(id: "1").eager_load(:readonly_author).first!
+ assert post.readonly_author.readonly?
end
test "preloading a polymorphic association with references to the associated table" do
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index d53c2b0c51..1bfb1ea0c8 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -380,47 +380,78 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_no_queries do
bulbs.first()
- bulbs.first({})
end
assert_no_queries do
bulbs.second()
- bulbs.second({})
end
assert_no_queries do
bulbs.third()
- bulbs.third({})
end
assert_no_queries do
bulbs.fourth()
- bulbs.fourth({})
end
assert_no_queries do
bulbs.fifth()
- bulbs.fifth({})
end
assert_no_queries do
bulbs.forty_two()
- bulbs.forty_two({})
end
assert_no_queries do
bulbs.third_to_last()
- bulbs.third_to_last({})
end
assert_no_queries do
bulbs.second_to_last()
- bulbs.second_to_last({})
end
assert_no_queries do
bulbs.last()
- bulbs.last({})
+ end
+ end
+
+ def test_finder_method_with_dirty_target
+ company = companies(:first_firm)
+ new_clients = []
+ assert_no_queries(ignore_none: false) do
+ new_clients << company.clients_of_firm.build(name: "Another Client")
+ new_clients << company.clients_of_firm.build(name: "Another Client II")
+ new_clients << company.clients_of_firm.build(name: "Another Client III")
+ end
+
+ assert_not company.clients_of_firm.loaded?
+ assert_queries(1) do
+ assert_same new_clients[0], company.clients_of_firm.third
+ assert_same new_clients[1], company.clients_of_firm.fourth
+ assert_same new_clients[2], company.clients_of_firm.fifth
+ assert_same new_clients[0], company.clients_of_firm.third_to_last
+ assert_same new_clients[1], company.clients_of_firm.second_to_last
+ assert_same new_clients[2], company.clients_of_firm.last
+ end
+ end
+
+ def test_finder_bang_method_with_dirty_target
+ company = companies(:first_firm)
+ new_clients = []
+ assert_no_queries(ignore_none: false) do
+ new_clients << company.clients_of_firm.build(name: "Another Client")
+ new_clients << company.clients_of_firm.build(name: "Another Client II")
+ new_clients << company.clients_of_firm.build(name: "Another Client III")
+ end
+
+ assert_not company.clients_of_firm.loaded?
+ assert_queries(1) do
+ assert_same new_clients[0], company.clients_of_firm.third!
+ assert_same new_clients[1], company.clients_of_firm.fourth!
+ assert_same new_clients[2], company.clients_of_firm.fifth!
+ assert_same new_clients[0], company.clients_of_firm.third_to_last!
+ assert_same new_clients[1], company.clients_of_firm.second_to_last!
+ assert_same new_clients[2], company.clients_of_firm.last!
end
end
diff --git a/activerecord/test/cases/associations/left_outer_join_association_test.rb b/activerecord/test/cases/associations/left_outer_join_association_test.rb
index d814229f64..2cc6468827 100644
--- a/activerecord/test/cases/associations/left_outer_join_association_test.rb
+++ b/activerecord/test/cases/associations/left_outer_join_association_test.rb
@@ -25,23 +25,32 @@ class LeftOuterJoinAssociationTest < ActiveRecord::TestCase
end
end
- def test_construct_finder_sql_executes_a_left_outer_join
- assert_not_equal Author.count, Author.joins(:posts).count
- assert_equal Author.count, Author.left_outer_joins(:posts).count
+ def test_left_outer_joins_count_is_same_as_size_of_loaded_results
+ assert_equal 17, Post.left_outer_joins(:comments).to_a.size
+ assert_equal 17, Post.left_outer_joins(:comments).count
end
- def test_left_outer_join_by_left_joins
- assert_not_equal Author.count, Author.joins(:posts).count
- assert_equal Author.count, Author.left_joins(:posts).count
+ def test_left_joins_aliases_left_outer_joins
+ assert_equal Post.left_outer_joins(:comments).to_sql, Post.left_joins(:comments).to_sql
+ end
+
+ def test_left_outer_joins_return_has_value_for_every_comment
+ all_post_ids = Post.pluck(:id)
+ assert_equal all_post_ids, all_post_ids & Post.left_outer_joins(:comments).pluck(:id)
+ end
+
+ def test_left_outer_joins_actually_does_a_left_outer_join
+ queries = capture_sql { Author.left_outer_joins(:posts).to_a }
+ assert queries.any? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
def test_construct_finder_sql_ignores_empty_left_outer_joins_hash
- queries = capture_sql { Author.left_outer_joins({}) }
+ queries = capture_sql { Author.left_outer_joins({}).to_a }
assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
def test_construct_finder_sql_ignores_empty_left_outer_joins_array
- queries = capture_sql { Author.left_outer_joins([]) }
+ queries = capture_sql { Author.left_outer_joins([]).to_a }
assert queries.none? { |sql| /LEFT OUTER JOIN/i.match?(sql) }
end
diff --git a/activerecord/test/cases/associations_test.rb b/activerecord/test/cases/associations_test.rb
index f29a968c07..5222703570 100644
--- a/activerecord/test/cases/associations_test.rb
+++ b/activerecord/test/cases/associations_test.rb
@@ -256,6 +256,13 @@ class AssociationProxyTest < ActiveRecord::TestCase
assert_no_queries { david.posts.first! }
end
+ def test_pluck_uses_loaded_target
+ david = authors(:david)
+ assert_equal david.posts.pluck(:title), david.posts.load.pluck(:title)
+ assert david.posts.loaded?
+ assert_no_queries { david.posts.pluck(:title) }
+ end
+
def test_reset_unloads_target
david = authors(:david)
david.posts.reload
diff --git a/activerecord/test/cases/column_definition_test.rb b/activerecord/test/cases/column_definition_test.rb
index 989beaa5c8..a65bb89052 100644
--- a/activerecord/test/cases/column_definition_test.rb
+++ b/activerecord/test/cases/column_definition_test.rb
@@ -60,14 +60,13 @@ module ActiveRecord
end
def test_should_not_set_default_for_blob_and_text_data_types
- text_type = MySQL::TypeMetadata.new(
- SqlTypeMetadata.new(type: :text))
+ text_type = MySQL::TypeMetadata.new(SqlTypeMetadata.new(type: :text))
text_column = MySQL::Column.new("title", nil, text_type)
- assert_equal nil, text_column.default
+ assert_nil text_column.default
not_null_text_column = MySQL::Column.new("title", nil, text_type, false)
- assert_equal "", not_null_text_column.default
+ assert_nil not_null_text_column.default
end
def test_has_default_should_return_false_for_blob_and_text_data_types
diff --git a/activerecord/test/cases/database_statements_test.rb b/activerecord/test/cases/database_statements_test.rb
index ac9912d4d1..bb16076fd2 100644
--- a/activerecord/test/cases/database_statements_test.rb
+++ b/activerecord/test/cases/database_statements_test.rb
@@ -5,6 +5,13 @@ class DatabaseStatementsTest < ActiveRecord::TestCase
@connection = ActiveRecord::Base.connection
end
+ unless current_adapter?(:OracleAdapter)
+ def test_exec_insert
+ result = @connection.exec_insert("INSERT INTO accounts (firm_id,credit_limit) VALUES (42,5000)", nil, [])
+ assert_not_nil @connection.send(:last_inserted_id, result)
+ end
+ end
+
def test_insert_should_return_the_inserted_id
assert_not_nil return_the_inserted_id(method: :insert)
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index f94815d34c..fcaff38f82 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -127,92 +127,66 @@ if current_adapter?(:Mysql2Adapter)
ActiveRecord::Base.establish_connection connection
end
- # MySQL cannot have defaults on text/blob columns. It reports the
- # default value as null.
+ # Strict mode controls how MySQL handles invalid or missing values
+ # in data-change statements such as INSERT or UPDATE. A value can be
+ # invalid for several reasons. For example, it might have the wrong
+ # data type for the column, or it might be out of range. A value is
+ # missing when a new row to be inserted does not contain a value for
+ # a non-NULL column that has no explicit DEFAULT clause in its definition.
+ # (For a NULL column, NULL is inserted if the value is missing.)
#
- # Despite this, in non-strict mode, MySQL will use an empty string
- # as the default value of the field, if no other value is
- # specified.
+ # If strict mode is not in effect, MySQL inserts adjusted values for
+ # invalid or missing values and produces warnings. In strict mode,
+ # you can produce this behavior by using INSERT IGNORE or UPDATE IGNORE.
#
- # Therefore, in non-strict mode, we want column.default to report
- # an empty string as its default, to be consistent with that.
- #
- # In strict mode, column.default should be nil.
- def test_mysql_text_not_null_defaults_non_strict
+ # https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html#sql-mode-strict
+ def test_mysql_not_null_defaults_non_strict
using_strict(false) do
- with_text_blob_not_null_table do |klass|
+ with_mysql_not_null_table do |klass|
record = klass.new
- assert_equal "", record.non_null_blob
- assert_equal "", record.non_null_text
-
- assert_nil record.null_blob
- assert_nil record.null_text
+ assert_nil record.non_null_integer
+ assert_nil record.non_null_string
+ assert_nil record.non_null_text
+ assert_nil record.non_null_blob
record.save!
record.reload
+ assert_equal 0, record.non_null_integer
+ assert_equal "", record.non_null_string
assert_equal "", record.non_null_text
assert_equal "", record.non_null_blob
-
- assert_nil record.null_text
- assert_nil record.null_blob
end
end
end
- def test_mysql_text_not_null_defaults_strict
+ def test_mysql_not_null_defaults_strict
using_strict(true) do
- with_text_blob_not_null_table do |klass|
+ with_mysql_not_null_table do |klass|
record = klass.new
- assert_nil record.non_null_blob
+ assert_nil record.non_null_integer
+ assert_nil record.non_null_string
assert_nil record.non_null_text
- assert_nil record.null_blob
- assert_nil record.null_text
+ assert_nil record.non_null_blob
assert_raises(ActiveRecord::StatementInvalid) { klass.create }
end
end
end
- def with_text_blob_not_null_table
+ def with_mysql_not_null_table
klass = Class.new(ActiveRecord::Base)
- klass.table_name = "test_mysql_text_not_null_defaults"
+ klass.table_name = "test_mysql_not_null_defaults"
klass.connection.create_table klass.table_name do |t|
- t.column :non_null_text, :text, null: false
- t.column :non_null_blob, :blob, null: false
- t.column :null_text, :text, null: true
- t.column :null_blob, :blob, null: true
+ t.integer :non_null_integer, null: false
+ t.string :non_null_string, null: false
+ t.text :non_null_text, null: false
+ t.blob :non_null_blob, null: false
end
yield klass
ensure
klass.connection.drop_table(klass.table_name) rescue nil
end
-
- # MySQL uses an implicit default 0 rather than NULL unless in strict mode.
- # We use an implicit NULL so schema.rb is compatible with other databases.
- def test_mysql_integer_not_null_defaults
- klass = Class.new(ActiveRecord::Base)
- klass.table_name = "test_integer_not_null_default_zero"
- klass.connection.create_table klass.table_name do |t|
- t.column :zero, :integer, null: false, default: 0
- t.column :omit, :integer, null: false
- end
-
- assert_equal "0", klass.columns_hash["zero"].default
- assert !klass.columns_hash["zero"].null
- assert_equal nil, klass.columns_hash["omit"].default
- assert !klass.columns_hash["omit"].null
-
- assert_raise(ActiveRecord::StatementInvalid) { klass.create! }
-
- assert_nothing_raised do
- instance = klass.create!(omit: 1)
- assert_equal 0, instance.zero
- assert_equal 1, instance.omit
- end
- ensure
- klass.connection.drop_table(klass.table_name) rescue nil
- end
end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index f8f9f2d383..31bc4fa1f2 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -22,13 +22,13 @@ class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
def test_find_by_id_with_hash
- assert_raises(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Post.find_by_id(limit: 1)
end
end
def test_find_by_title_and_id_with_hash
- assert_raises(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Post.find_by_title_and_id("foo", limit: 1)
end
end
@@ -874,11 +874,6 @@ class FinderTest < ActiveRecord::TestCase
assert_kind_of Time, Topic.where(["id = :id", { id: 1 }]).first.written_on
end
- def test_string_sanitation
- assert_not_equal "'something ' 1=1'", ActiveRecord::Base.sanitize("something ' 1=1")
- assert_equal "'something; select table'", ActiveRecord::Base.sanitize("something; select table")
- end
-
def test_count_by_sql
assert_equal(0, Entrant.count_by_sql("SELECT COUNT(*) FROM entrants WHERE id > 3"))
assert_equal(1, Entrant.count_by_sql(["SELECT COUNT(*) FROM entrants WHERE id > ?", 2]))
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index 76a4592ac5..151f3c8efd 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -551,6 +551,23 @@ class MigrationTest < ActiveRecord::TestCase
end
end
+ if current_adapter?(:SQLite3Adapter)
+ def test_allows_sqlite3_rollback_on_invalid_column_type
+ Person.connection.create_table :something, force: true do |t|
+ t.column :number, :integer
+ t.column :name, :string
+ t.column :foo, :bar
+ end
+ assert Person.connection.column_exists?(:something, :foo)
+ assert_nothing_raised { Person.connection.remove_column :something, :foo, :bar }
+ assert !Person.connection.column_exists?(:something, :foo)
+ assert Person.connection.column_exists?(:something, :name)
+ assert Person.connection.column_exists?(:something, :number)
+ ensure
+ Person.connection.drop_table :something, if_exists: true
+ end
+ end
+
if current_adapter? :OracleAdapter
def test_create_table_with_custom_sequence_name
# table name is 29 chars, the standard sequence name will
diff --git a/activerecord/test/cases/relation/where_test.rb b/activerecord/test/cases/relation/where_test.rb
index ad9008ea4d..ce4e041793 100644
--- a/activerecord/test/cases/relation/where_test.rb
+++ b/activerecord/test/cases/relation/where_test.rb
@@ -196,7 +196,7 @@ module ActiveRecord
end
def test_where_error
- assert_raises(ActiveRecord::StatementInvalid) do
+ assert_nothing_raised do
Post.where(id: { "posts.author_id" => 10 }).first
end
end
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 0bd4ee3e36..32b8781a6b 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -224,7 +224,7 @@ module ActiveRecord
def test_relation_merging_with_merged_joins_as_symbols
special_comments_with_ratings = SpecialComment.joins(:ratings)
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
- assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
+ assert_equal({ 2=>1, 4=>3, 5=>1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
end
def test_relation_merging_with_joins_as_join_dependency_pick_proper_parent
@@ -274,7 +274,7 @@ module ActiveRecord
join_string = "LEFT OUTER JOIN #{Rating.quoted_table_name} ON #{SpecialComment.quoted_table_name}.id = #{Rating.quoted_table_name}.comment_id"
special_comments_with_ratings = SpecialComment.joins join_string
posts_with_special_comments_with_ratings = Post.group("posts.id").joins(:special_comments).merge(special_comments_with_ratings)
- assert_equal 3, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count.length
+ assert_equal({ 2=>1, 4=>3, 5=>1 }, authors(:david).posts.merge(posts_with_special_comments_with_ratings).count)
end
class EnsureRoundTripTypeCasting < ActiveRecord::Type::Value
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index a5bb6e1c51..3efaace69d 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -240,6 +240,15 @@ class RelationTest < ActiveRecord::TestCase
assert_raises(ActiveRecord::IrreversibleOrderError) do
Topic.order("concat(author_name, title)").reverse_order
end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("concat(lower(author_name), title)").reverse_order
+ end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("concat(author_name, lower(title))").reverse_order
+ end
+ assert_raises(ActiveRecord::IrreversibleOrderError) do
+ Topic.order("concat(lower(author_name), title, length(title)").reverse_order
+ end
end
def test_reverse_order_with_nulls_first_or_last
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 33baf84ef2..57b1bc889a 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -70,38 +70,35 @@ class SchemaDumperTest < ActiveRecord::TestCase
assert_match %r{create_table "CamelCase"}, output
end
- def assert_line_up(lines, pattern, required = false)
+ def assert_no_line_up(lines, pattern)
return assert(true) if lines.empty?
matches = lines.map { |line| line.match(pattern) }
- assert matches.all? if required
matches.compact!
return assert(true) if matches.empty?
- assert_equal 1, matches.map { |match| match.offset(0).first }.uniq.length
+ line_matches = lines.map { |line| [line, line.match(pattern)] }.select { |line, match| match }
+ assert line_matches.all? { |line, match|
+ start = match.offset(0).first
+ line[start - 2..start - 1] == ", "
+ }
end
def column_definition_lines(output = standard_dump)
output.scan(/^( *)create_table.*?\n(.*?)^\1end/m).map { |m| m.last.split(/\n/) }
end
- def test_types_line_up
+ def test_types_no_line_up
column_definition_lines.each do |column_set|
next if column_set.empty?
- lengths = column_set.map do |column|
- if match = column.match(/\bt\.\w+\s+(?="\w+?")/)
- match[0].length
- end
- end.compact
-
- assert_equal 1, lengths.uniq.length
+ assert column_set.all? { |column| !column.match(/\bt\.\w+\s{2,}/) }
end
end
- def test_arguments_line_up
+ def test_arguments_no_line_up
column_definition_lines.each do |column_set|
- assert_line_up(column_set, /default: /)
- assert_line_up(column_set, /limit: /)
- assert_line_up(column_set, /null: /)
+ assert_no_line_up(column_set, /default: /)
+ assert_no_line_up(column_set, /limit: /)
+ assert_no_line_up(column_set, /null: /)
end
end
diff --git a/activerecord/test/cases/serialized_attribute_test.rb b/activerecord/test/cases/serialized_attribute_test.rb
index ceb0d5441b..bebd856faf 100644
--- a/activerecord/test/cases/serialized_attribute_test.rb
+++ b/activerecord/test/cases/serialized_attribute_test.rb
@@ -157,6 +157,13 @@ class SerializedAttributeTest < ActiveRecord::TestCase
assert_equal(settings, Topic.find(topic.id).content)
end
+ def test_where_by_serialized_attribute_with_hash
+ settings = { "color" => "green" }
+ Topic.serialize(:content, Hash)
+ topic = Topic.create!(content: settings)
+ assert_equal topic, Topic.where(content: settings).take
+ end
+
def test_serialized_default_class
Topic.serialize(:content, Hash)
topic = Topic.new
diff --git a/activerecord/test/cases/touch_later_test.rb b/activerecord/test/cases/touch_later_test.rb
index 697447a4f3..d1e8c649d9 100644
--- a/activerecord/test/cases/touch_later_test.rb
+++ b/activerecord/test/cases/touch_later_test.rb
@@ -24,6 +24,15 @@ class TouchLaterTest < ActiveRecord::TestCase
assert_not invoice.changed?
end
+ def test_touch_later_respects_no_touching_policy
+ time = Time.now.utc - 25.days
+ topic = Topic.create!(updated_at: time, created_at: time)
+ Topic.no_touching do
+ topic.touch_later
+ end
+ assert_equal time.to_i, topic.updated_at.to_i
+ end
+
def test_touch_later_update_the_attributes
time = Time.now.utc - 25.days
topic = Topic.create!(updated_at: time, created_at: time)
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 42eff9cff9..66a99cbcda 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -24,6 +24,7 @@ class Post < ActiveRecord::Base
scope :limit_by, lambda { |l| limit(l) }
belongs_to :author
+ belongs_to :readonly_author, -> { readonly }, class_name: "Author", foreign_key: :author_id
belongs_to :author_with_posts, -> { includes(:posts) }, class_name: "Author", foreign_key: :author_id
belongs_to :author_with_address, -> { includes(:author_address) }, class_name: "Author", foreign_key: :author_id