aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md38
-rw-r--r--activerecord/lib/active_record/association_relation.rb8
-rw-r--r--activerecord/lib/active_record/associations/belongs_to_association.rb18
-rw-r--r--activerecord/lib/active_record/associations/builder/belongs_to.rb30
-rw-r--r--activerecord/lib/active_record/connection_adapters/connection_specification.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb52
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb4
-rw-r--r--activerecord/lib/active_record/core.rb1
-rw-r--r--activerecord/lib/active_record/counter_cache.rb30
-rw-r--r--activerecord/lib/active_record/nested_attributes.rb8
-rw-r--r--activerecord/lib/active_record/null_relation.rb4
-rw-r--r--activerecord/lib/active_record/reflection.rb4
-rw-r--r--activerecord/lib/active_record/relation/calculations.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb7
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb2
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb13
-rw-r--r--activerecord/test/cases/persistence_test.rb16
-rw-r--r--activerecord/test/cases/reflection_test.rb8
-rw-r--r--activerecord/test/cases/relations_test.rb22
-rw-r--r--activerecord/test/cases/sanitize_test.rb27
-rw-r--r--activerecord/test/models/pirate.rb1
24 files changed, 248 insertions, 78 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a45d912017..74f5666de0 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,41 @@
+* `@destroyed` should always be set to `false` when an object is duped.
+
+ *Kuldeep Aggarwal*
+
+* Fixed has_many association to make it support irregular inflections.
+
+ Fixes #8928.
+
+ *arthurnn*, *Javier Goizueta*
+
+* Fixed a problem where count used with a grouping was not returning a Hash.
+
+ Fixes #14721.
+
+ *Eric Chahin*
+
+* `sanitize_sql_like` helper method to escape a string for safe use in a SQL
+ LIKE statement.
+
+ Example:
+
+ class Article
+ def self.search(term)
+ where("title LIKE ?", sanitize_sql_like(term))
+ end
+ end
+
+ Article.search("20% _reduction_")
+ # => Query looks like "... title LIKE '20\% \_reduction\_' ..."
+
+ *Rob Gilson*, *Yves Senn*
+
+* Do not quote uuid default value on `change_column`.
+
+ Fixes #14604.
+
+ *Eric Chahin*
+
* The comparison between `Relation` and `CollectionProxy` should be consistent.
Example:
diff --git a/activerecord/lib/active_record/association_relation.rb b/activerecord/lib/active_record/association_relation.rb
index 45f1b07f69..5a84792f45 100644
--- a/activerecord/lib/active_record/association_relation.rb
+++ b/activerecord/lib/active_record/association_relation.rb
@@ -9,14 +9,6 @@ module ActiveRecord
@association
end
- def size
- @association.size
- end
-
- def empty?
- @association.empty?
- end
-
def ==(other)
other == to_a
end
diff --git a/activerecord/lib/active_record/associations/belongs_to_association.rb b/activerecord/lib/active_record/associations/belongs_to_association.rb
index 8272a5584c..1edd4fa3aa 100644
--- a/activerecord/lib/active_record/associations/belongs_to_association.rb
+++ b/activerecord/lib/active_record/associations/belongs_to_association.rb
@@ -31,6 +31,14 @@ module ActiveRecord
@updated
end
+ def decrement_counters # :nodoc:
+ with_cache_name { |name| decrement_counter name }
+ end
+
+ def increment_counters # :nodoc:
+ with_cache_name { |name| increment_counter name }
+ end
+
private
def find_target?
@@ -51,13 +59,15 @@ module ActiveRecord
end
end
- def decrement_counters
- with_cache_name { |name| decrement_counter name }
+ def decrement_counter(counter_cache_name)
+ if foreign_key_present?
+ klass.decrement_counter(counter_cache_name, target_id)
+ end
end
- def decrement_counter counter_cache_name
+ def increment_counter(counter_cache_name)
if foreign_key_present?
- klass.decrement_counter(counter_cache_name, target_id)
+ klass.increment_counter(counter_cache_name, target_id)
end
end
diff --git a/activerecord/lib/active_record/associations/builder/belongs_to.rb b/activerecord/lib/active_record/associations/builder/belongs_to.rb
index 11be92ae01..47cc1f4b34 100644
--- a/activerecord/lib/active_record/associations/builder/belongs_to.rb
+++ b/activerecord/lib/active_record/associations/builder/belongs_to.rb
@@ -26,29 +26,9 @@ module ActiveRecord::Associations::Builder
private
def self.add_counter_cache_methods(mixin)
- return if mixin.method_defined? :belongs_to_counter_cache_after_create
+ return if mixin.method_defined? :belongs_to_counter_cache_after_update
mixin.class_eval do
- def belongs_to_counter_cache_after_create(reflection)
- if record = send(reflection.name)
- cache_column = reflection.counter_cache_column
- record.class.increment_counter(cache_column, record.id)
- @_after_create_counter_called = true
- end
- end
-
- def belongs_to_counter_cache_after_destroy(reflection)
- foreign_key = reflection.foreign_key.to_sym
- unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
- record = send reflection.name
- if record && self.actually_destroyed?
- cache_column = reflection.counter_cache_column
- record.class.decrement_counter(cache_column, record.id)
- self.clear_destroy_state
- end
- end
- end
-
def belongs_to_counter_cache_after_update(reflection)
foreign_key = reflection.foreign_key
cache_column = reflection.counter_cache_column
@@ -74,14 +54,6 @@ module ActiveRecord::Associations::Builder
def self.add_counter_cache_callbacks(model, reflection)
cache_column = reflection.counter_cache_column
- model.after_create lambda { |record|
- record.belongs_to_counter_cache_after_create(reflection)
- }
-
- model.after_destroy lambda { |record|
- record.belongs_to_counter_cache_after_destroy(reflection)
- }
-
model.after_update lambda { |record|
record.belongs_to_counter_cache_after_update(reflection)
}
diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
index a8ab52be74..b79d1a4458 100644
--- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb
+++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb
@@ -62,8 +62,8 @@ module ActiveRecord
# Converts the query parameters of the URI into a hash.
#
- # "localhost?pool=5&reap_frequency=2"
- # # => { "pool" => "5", "reap_frequency" => "2" }
+ # "localhost?pool=5&reaping_frequency=2"
+ # # => { "pool" => "5", "reaping_frequency" => "2" }
#
# returns empty hash if no query present.
#
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 a5fb048b1e..168b08ba75 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -44,10 +44,32 @@ module ActiveRecord
end
end
+ def select_value(arel, name = nil, binds = [])
+ arel, binds = binds_from_relation arel, binds
+ sql = to_sql(arel, binds)
+ execute_and_clear(sql, name, binds) do |result|
+ result.getvalue(0, 0) if result.ntuples > 0 && result.nfields > 0
+ end
+ end
+
+ def select_values(arel, name = nil)
+ arel, binds = binds_from_relation arel, []
+ sql = to_sql(arel, binds)
+ execute_and_clear(sql, name, binds) do |result|
+ if result.nfields > 0
+ result.column_values(0)
+ else
+ []
+ end
+ end
+ end
+
# Executes a SELECT query and returns an array of rows. Each row is an
# array of field values.
def select_rows(sql, name = nil, binds = [])
- exec_query(sql, name, binds).rows
+ execute_and_clear(sql, name, binds) do |result|
+ result.values
+ end
end
# Executes an INSERT query and returns the new record's ID
@@ -134,28 +156,20 @@ module ActiveRecord
end
def exec_query(sql, name = 'SQL', binds = [])
- result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
- exec_cache(sql, name, binds)
-
- types = {}
- fields = result.fields
- fields.each_with_index do |fname, i|
- ftype = result.ftype i
- fmod = result.fmod i
- types[fname] = get_oid_type(ftype, fmod, fname)
+ execute_and_clear(sql, name, binds) do |result|
+ types = {}
+ fields = result.fields
+ fields.each_with_index do |fname, i|
+ ftype = result.ftype i
+ fmod = result.fmod i
+ types[fname] = get_oid_type(ftype, fmod, fname)
+ end
+ ActiveRecord::Result.new(fields, result.values, types)
end
-
- ret = ActiveRecord::Result.new(fields, result.values, types)
- result.clear
- return ret
end
def exec_delete(sql, name = 'SQL', binds = [])
- result = without_prepared_statement?(binds) ? exec_no_cache(sql, name, binds) :
- exec_cache(sql, name, binds)
- affected = result.cmd_tuples
- result.clear
- affected
+ execute_and_clear(sql, name, binds) {|result| result.cmd_tuples }
end
alias :exec_update :exec_delete
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index ac3b0f713d..403e37fde9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -182,6 +182,15 @@ module ActiveRecord
end
result
end
+
+ # Does not quote function default values for UUID columns
+ def quote_default_value(value, column) #:nodoc:
+ if column.type == :uuid && value =~ /\(\)/
+ value
+ else
+ quote(value)
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
index 1229b4851a..1dc7a6f0fd 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb
@@ -187,6 +187,10 @@ module ActiveRecord
end
end
+ def column_for(table_name, column_name) #:nodoc:
+ columns(table_name).detect { |c| c.name == column_name.to_s }
+ end
+
# Returns the current database name.
def current_database
query('select current_database()', 'SCHEMA')[0][0]
@@ -404,13 +408,15 @@ module ActiveRecord
# Changes the default value of a table column.
def change_column_default(table_name, column_name, default)
clear_cache!
- execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote(default)}"
+ column = column_for(table_name, column_name)
+ execute "ALTER TABLE #{quote_table_name(table_name)} ALTER COLUMN #{quote_column_name(column_name)} SET DEFAULT #{quote_default_value(default, column)}" if column
end
def change_column_null(table_name, column_name, null, default = nil)
clear_cache!
unless null || default.nil?
- execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote(default)} WHERE #{quote_column_name(column_name)} IS NULL")
+ column = column_for(table_name, column_name)
+ execute("UPDATE #{quote_table_name(table_name)} SET #{quote_column_name(column_name)}=#{quote_default_value(default, column)} WHERE #{quote_column_name(column_name)} IS NULL") if column
end
execute("ALTER TABLE #{quote_table_name(table_name)} ALTER #{quote_column_name(column_name)} #{null ? 'DROP' : 'SET'} NOT NULL")
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 56dd2da249..764cb576d9 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -658,6 +658,14 @@ 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)
+ ret = yield result
+ result.clear
+ ret
+ end
+
def exec_no_cache(sql, name, binds)
log(sql, name, binds) { @connection.async_exec(sql) }
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index f59c2432dd..dd4261cec7 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -30,7 +30,7 @@ module ActiveRecord
db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
- ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
rescue Errno::ENOENT => error
if error.message.include?("No such file or directory")
raise ActiveRecord::NoDatabaseError.new(error.message, error)
@@ -123,7 +123,7 @@ module ActiveRecord
end
end
- def initialize(connection, logger, config)
+ def initialize(connection, logger, connection_options, config)
super(connection, logger)
@active = nil
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 3be9c7695f..4571cc0786 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -333,6 +333,7 @@ module ActiveRecord
@attributes_cache = {}
@new_record = true
+ @destroyed = false
super
end
diff --git a/activerecord/lib/active_record/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index a5897edf03..b7b790322a 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -131,13 +131,41 @@ module ActiveRecord
private
+ def _create_record(*)
+ id = super
+
+ each_counter_cached_associations do |association|
+ if send(association.reflection.name)
+ association.increment_counters
+ @_after_create_counter_called = true
+ end
+ end
+
+ id
+ end
+
def destroy_row
affected_rows = super
- @_actually_destroyed = affected_rows > 0
+ if affected_rows > 0
+ each_counter_cached_associations do |association|
+ foreign_key = association.reflection.foreign_key.to_sym
+ unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
+ if send(association.reflection.name)
+ association.decrement_counters
+ end
+ end
+ end
+ end
affected_rows
end
+ def each_counter_cached_associations
+ reflections.each do |name, reflection|
+ yield association(name) if reflection.belongs_to? && reflection.counter_cache_column
+ end
+ end
+
end
end
diff --git a/activerecord/lib/active_record/nested_attributes.rb b/activerecord/lib/active_record/nested_attributes.rb
index 9d92e747d4..e6195e48a5 100644
--- a/activerecord/lib/active_record/nested_attributes.rb
+++ b/activerecord/lib/active_record/nested_attributes.rb
@@ -485,10 +485,10 @@ module ActiveRecord
end
# Takes in a limit and checks if the attributes_collection has too many
- # records. The method will take limits in the form of symbols, procs, and
- # number-like objects (anything that can be compared with an integer).
+ # records. It accepts limit in the form of symbol, proc, or
+ # number-like object (anything that can be compared with an integer).
#
- # Will raise an TooManyRecords error if the attributes_collection is
+ # Raises TooManyRecords error if the attributes_collection is
# larger than the limit.
def check_record_limit!(limit, attributes_collection)
if limit
@@ -519,7 +519,7 @@ module ActiveRecord
ConnectionAdapters::Column.value_to_boolean(hash['_destroy'])
end
- # Determines if a new record should be build by checking for
+ # Determines if a new record should be rejected by checking
# has_destroy_flag? or if a <tt>:reject_if</tt> proc exists for this
# association and evaluates to +true+.
def reject_new_record?(association_name, attributes)
diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb
index 5b255c3fe5..05d0c41678 100644
--- a/activerecord/lib/active_record/null_relation.rb
+++ b/activerecord/lib/active_record/null_relation.rb
@@ -43,7 +43,7 @@ module ActiveRecord
end
def count(*)
- 0
+ calculate :count, nil
end
def sum(*)
@@ -54,7 +54,7 @@ module ActiveRecord
# TODO: Remove _options argument as soon we remove support to
# activerecord-deprecated_finders.
if operation == :count
- 0
+ group_values.any? ? Hash.new : 0
else
nil
end
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 32315c4994..467d92e53c 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -460,9 +460,9 @@ module ActiveRecord
end
def derive_class_name
- class_name = name.to_s.camelize
+ class_name = name.to_s
class_name = class_name.singularize if collection?
- class_name
+ class_name.camelize
end
def derive_foreign_key
diff --git a/activerecord/lib/active_record/relation/calculations.rb b/activerecord/lib/active_record/relation/calculations.rb
index 5e525340e0..0b56430b34 100644
--- a/activerecord/lib/active_record/relation/calculations.rb
+++ b/activerecord/lib/active_record/relation/calculations.rb
@@ -188,7 +188,7 @@ module ActiveRecord
private
def has_include?(column_name)
- eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
+ eager_loading? || (includes_values.present? && ((column_name && column_name != :all) || references_eager_loaded_tables?))
end
def perform_calculation(operation, column_name, options = {})
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 936f8dba02..1aa93ffbb3 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -107,6 +107,13 @@ module ActiveRecord
end.join(', ')
end
+ # Sanitizes a +string+ so that it is safe to use within a sql
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
+ def sanitize_sql_like(string, escape_character = "\\")
+ pattern = Regexp.union(escape_character, "%", "_")
+ string.gsub(pattern) { |x| [escape_character, x].join }
+ end
+
# Accepts an array of conditions. The array has each value
# sanitized and interpolated into the SQL statement.
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index 6ce0495f6f..168b338b97 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -28,7 +28,7 @@ module ActiveRecord
# Example usage of +DatabaseTasks+ outside Rails could look as such:
#
# include ActiveRecord::Tasks
- # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml'))
+ # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
# DatabaseTasks.db_dir = 'db'
# # other settings...
#
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 9e03ea6bee..bdf8e15e3e 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -40,6 +40,19 @@ class PostgresqlUUIDTest < ActiveRecord::TestCase
drop_table "uuid_data_type"
end
+ def test_change_column_default
+ @connection.add_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v1()"
+ UUIDType.reset_column_information
+ column = UUIDType.columns.find { |c| c.name == 'thingy' }
+ assert_equal "uuid_generate_v1()", column.default_function
+
+ @connection.change_column :uuid_data_type, :thingy, :uuid, null: false, default: "uuid_generate_v4()"
+
+ UUIDType.reset_column_information
+ column = UUIDType.columns.find { |c| c.name == 'thingy' }
+ assert_equal "uuid_generate_v4()", column.default_function
+ end
+
def test_data_type_of_uuid_types
column = UUIDType.columns_hash["guid"]
assert_equal :uuid, column.type
diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb
index 046fe83e54..9209672ac5 100644
--- a/activerecord/test/cases/persistence_test.rb
+++ b/activerecord/test/cases/persistence_test.rb
@@ -233,6 +233,22 @@ class PersistenceTest < ActiveRecord::TestCase
assert_nothing_raised { Minimalistic.create!(:id => 2) }
end
+ def test_save_with_duping_of_destroyed_object
+ developer = Developer.create(name: "Kuldeep")
+ developer.destroy
+ new_developer = developer.dup
+ new_developer.save
+ assert new_developer.persisted?
+ end
+
+ def test_dup_of_destroyed_object_is_not_destroyed
+ developer = Developer.create(name: "Kuldeep")
+ developer.destroy
+ new_developer = developer.dup
+ new_developer.save
+ assert_equal new_developer.destroyed?, false
+ end
+
def test_create_many
topics = Topic.create([ { "title" => "first" }, { "title" => "second" }])
assert_equal 2, topics.size
diff --git a/activerecord/test/cases/reflection_test.rb b/activerecord/test/cases/reflection_test.rb
index fed199f6e9..c085fcf161 100644
--- a/activerecord/test/cases/reflection_test.rb
+++ b/activerecord/test/cases/reflection_test.rb
@@ -87,6 +87,14 @@ class ReflectionTest < ActiveRecord::TestCase
end
end
+ def test_irregular_reflection_class_name
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular 'plural_irregular', 'plurales_irregulares'
+ end
+ reflection = AssociationReflection.new(:has_many, 'plurales_irregulares', nil, {}, ActiveRecord::Base)
+ assert_equal 'PluralIrregular', reflection.class_name
+ end
+
def test_aggregation_reflection
reflection_for_address = AggregateReflection.new(
:composed_of, :address, nil, { :mapping => [ %w(address_street street), %w(address_city city), %w(address_country country) ] }, Customer
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index a2a2a79180..68e62934c1 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -14,6 +14,7 @@ require 'models/car'
require 'models/engine'
require 'models/tyre'
require 'models/minivan'
+require 'models/aircraft'
class RelationTest < ActiveRecord::TestCase
@@ -365,6 +366,16 @@ class RelationTest < ActiveRecord::TestCase
assert_equal({ 'salary' => 100_000 }, Developer.none.where(salary: 100_000).where_values_hash)
end
+
+ def test_null_relation_count
+ ac = Aircraft.new
+ assert_equal Hash.new, ac.engines.group(:id).count
+ assert_equal 0, ac.engines.count
+ ac.save
+ assert_equal Hash.new, ac.engines.group(:id).count
+ assert_equal 0, ac.engines.count
+ end
+
def test_joins_with_nil_argument
assert_nothing_raised { DependentFirm.joins(nil).first }
end
@@ -864,6 +875,17 @@ class RelationTest < ActiveRecord::TestCase
assert_equal 9, posts.where(:comments_count => 0).count
end
+ def test_count_on_association_relation
+ author = Author.last
+ another_author = Author.first
+ posts = Post.where(author_id: author.id)
+
+ assert_equal author.posts.where(author_id: author.id).size, posts.count
+
+ assert_equal 0, author.posts.where(author_id: another_author.id).size
+ assert author.posts.where(author_id: another_author.id).empty?
+ end
+
def test_count_with_distinct
posts = Post.all
diff --git a/activerecord/test/cases/sanitize_test.rb b/activerecord/test/cases/sanitize_test.rb
index 954eab8022..dca85fb5eb 100644
--- a/activerecord/test/cases/sanitize_test.rb
+++ b/activerecord/test/cases/sanitize_test.rb
@@ -51,4 +51,31 @@ class SanitizeTest < ActiveRecord::TestCase
select_author_sql = Post.send(:sanitize_sql_array, [''])
assert_equal('', select_author_sql)
end
+
+ def test_sanitize_sql_like
+ assert_equal '100\%', Binary.send(:sanitize_sql_like, '100%')
+ assert_equal 'snake\_cased\_string', Binary.send(:sanitize_sql_like, 'snake_cased_string')
+ assert_equal 'C:\\\\Programs\\\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint')
+ assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42')
+ end
+
+ def test_sanitize_sql_like_with_custom_escape_character
+ assert_equal '100!%', Binary.send(:sanitize_sql_like, '100%', '!')
+ assert_equal 'snake!_cased!_string', Binary.send(:sanitize_sql_like, 'snake_cased_string', '!')
+ assert_equal 'great!!', Binary.send(:sanitize_sql_like, 'great!', '!')
+ assert_equal 'C:\\Programs\\MsPaint', Binary.send(:sanitize_sql_like, 'C:\\Programs\\MsPaint', '!')
+ assert_equal 'normal string 42', Binary.send(:sanitize_sql_like, 'normal string 42', '!')
+ end
+
+ def test_sanitize_sql_like_example_use_case
+ searchable_post = Class.new(Post) do
+ def self.search(term)
+ where("title LIKE ?", sanitize_sql_like(term, '!'))
+ end
+ end
+
+ assert_sql(/LIKE '20!% !_reduction!_!!'/) do
+ searchable_post.search("20% _reduction_!").to_a
+ end
+ end
end
diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb
index 7bb0caf44b..e472cf951d 100644
--- a/activerecord/test/models/pirate.rb
+++ b/activerecord/test/models/pirate.rb
@@ -18,7 +18,6 @@ class Pirate < ActiveRecord::Base
has_many :treasures, :as => :looter
has_many :treasure_estimates, :through => :treasures, :source => :price_estimates
- # These both have :autosave enabled because accepts_nested_attributes_for is used on them.
has_one :ship
has_one :update_only_ship, :class_name => 'Ship'
has_one :non_validated_ship, :class_name => 'Ship'