diff options
author | Aaron Patterson <aaron.patterson@gmail.com> | 2014-04-20 12:38:33 -0700 |
---|---|---|
committer | Aaron Patterson <aaron.patterson@gmail.com> | 2014-04-20 12:38:33 -0700 |
commit | 94cd2f7f2e5cb8766c221dd8293a2740c51ad31a (patch) | |
tree | c31e43ce6ac005d776c215105df6f4b4eefe23ed /activerecord | |
parent | 5cbf73c2ab5d45ff7536850d0bf3ed39d873c2a6 (diff) | |
parent | 86b02826a6d301fe19c30a6f5c01a9e084279c03 (diff) | |
download | rails-94cd2f7f2e5cb8766c221dd8293a2740c51ad31a.tar.gz rails-94cd2f7f2e5cb8766c221dd8293a2740c51ad31a.tar.bz2 rails-94cd2f7f2e5cb8766c221dd8293a2740c51ad31a.zip |
Merge branch 'master' into adequaterecord
* master: (74 commits)
[ci skip] builtin -> built-in
Fix code indentation and improve formatting
Grammar fix in Getting Started Guide
Make URL escaping more consistent
Optimize URI escaping
Always escape string passed to url helper.
Remove statement assuming coffee shop/public space wifi is inherently insecure
Don't rely on Arel master in bug report template [ci skip]
wrap methods in backticks [ci skip]
"subhash" --> "sub-hash"
multibyte_conformance.rb --> multibyte_conformance_test.rb
Fix inconsistent behavior from String#first/#last
`@destroyed` should always be set to `false` when an object is duped.
remove warning `warning: ambiguous first argument; put parentheses or even spaces`
:uglify -> :uglifier
Regression test for irregular inflection on has_many
Singularize association names before camelization
Fix spelling and proper nouns
Optimize select_value, select_values, select_rows and dry up checking whether to exec with cache for Postgresql adapter
Include default rails protect_from_forgery with: :exception
...
Conflicts:
activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
Diffstat (limited to 'activerecord')
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' |