diff options
22 files changed, 86 insertions, 95 deletions
diff --git a/.travis.yml b/.travis.yml index d465378c72..af207f4f5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,8 @@ before_script: script: 'ci/travis.rb' env: + global: + - "JRUBY_OPTS='--dev -J-Xmx1024M'" matrix: - "GEM=railties" - "GEM=ap" @@ -47,14 +49,14 @@ env: - "GEM=guides" rvm: - - 2.2.5 - - 2.3.1 + - 2.2.6 + - 2.3.2 - ruby-head matrix: include: # Latest compiled version in http://rubies.travis-ci.org - - rvm: 2.3.1 + - rvm: 2.3.2 env: - "GEM=ar:mysql2" addons: @@ -62,8 +64,11 @@ matrix: - rvm: jruby-9.1.5.0 jdk: oraclejdk8 env: - - "JRUBY_OPTS='--dev -J-Xmx1024M'" - - "GEM='ap'" + - "GEM=ap" + - rvm: jruby-9.1.5.0 + jdk: oraclejdk8 + env: + - "GEM=am,aj" allow_failures: - rvm: ruby-head - rvm: jruby-9.1.5.0 @@ -35,6 +35,9 @@ gem "sass", github: "sass/sass", branch: "stable", require: false # FIXME: Remove this fork after https://github.com/nex3/rb-inotify/pull/49 is fixed. gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", require: false +# Explicitly avoid 1.x that doesn't support Ruby 2.4+ +gem "json", ">= 2.0.0" + group :doc do gem "sdoc", "1.0.0.beta2" gem "redcarpet", "~> 3.2.3", platforms: :ruby diff --git a/Gemfile.lock b/Gemfile.lock index 1a39484c2b..06e822964b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -379,6 +379,7 @@ DEPENDENCIES em-hiredis hiredis jquery-rails + json (>= 2.0.0) kindlerb (>= 1.0.1) listen (>= 3.0.5, < 3.2) minitest (< 5.3.4) diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index 1a8b3375ae..9e45c0da24 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -232,16 +232,16 @@ module ActiveJob # MyJob.set(wait_until: Date.tomorrow.noon).perform_later # end # end - def assert_enqueued_with(args = {}) + def assert_enqueued_with(job: nil, args: nil, at: nil, queue: nil) original_enqueued_jobs_count = enqueued_jobs.count - args.assert_valid_keys(:job, :args, :at, :queue) - serialized_args = serialize_args_for_assertion(args) + expected = { job: job, args: args, at: at, queue: queue }.compact + serialized_args = serialize_args_for_assertion(expected) yield in_block_jobs = enqueued_jobs.drop(original_enqueued_jobs_count) matching_job = in_block_jobs.find do |job| serialized_args.all? { |key, value| value == job[key] } end - assert matching_job, "No enqueued job found with #{args}" + assert matching_job, "No enqueued job found with #{expected}" instantiate_job(matching_job) end @@ -256,16 +256,16 @@ module ActiveJob # MyJob.set(wait_until: Date.tomorrow.noon).perform_later # end # end - def assert_performed_with(args = {}) + def assert_performed_with(job: nil, args: nil, at: nil, queue: nil) original_performed_jobs_count = performed_jobs.count - args.assert_valid_keys(:job, :args, :at, :queue) - serialized_args = serialize_args_for_assertion(args) + expected = { job: job, args: args, at: at, queue: queue }.compact + serialized_args = serialize_args_for_assertion(expected) perform_enqueued_jobs { yield } in_block_jobs = performed_jobs.drop(original_performed_jobs_count) matching_job = in_block_jobs.find do |job| serialized_args.all? { |key, value| value == job[key] } end - assert matching_job, "No performed job found with #{args}" + assert matching_job, "No performed job found with #{expected}" instantiate_job(matching_job) end diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 8efefe9b2e..7f4be54dbb 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,10 @@ +* Allow `ActionController::Parameters`-like objects to be passed as + values for Postgres HStore columns. + + Fixes #26904. + + *Jon Moss* + * Added `stat` method to `ActiveRecord::ConnectionAdapters::ConnectionPool` Example: diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 5a323c62e6..b2cf4713bb 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -246,13 +246,6 @@ module ActiveRecord end end - def distinct - seen = {} - load_target.find_all do |record| - seen[record.id] = true unless seen.key?(record.id) - end - end - # Replace this collection with +other_array+. This will perform a diff # and delete/add only records that have changed. def replace(other_array) diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb index 0800639c24..35a98d7090 100644 --- a/activerecord/lib/active_record/associations/collection_proxy.rb +++ b/activerecord/lib/active_record/associations/collection_proxy.rb @@ -718,6 +718,12 @@ module ActiveRecord @association.destroy(*records) end + ## + # :method: distinct + # + # :call-seq: + # distinct(value = true) + # # Specifies whether the records should be unique or not. # # class Person < ActiveRecord::Base @@ -732,10 +738,17 @@ module ActiveRecord # # person.pets.select(:name).distinct # # => [#<Pet name: "Fancy-Fancy">] - def distinct - @association.distinct + # + # person.pets.select(:name).distinct.distinct(false) + # # => [ + # # #<Pet name: "Fancy-Fancy">, + # # #<Pet name: "Fancy-Fancy"> + # # ] + + #-- + def uniq + load_target.uniq end - alias uniq distinct def calculate(operation, column_name) null_scope? ? scope.calculate(operation, column_name) : super diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb index a74a044a3a..d629ebca91 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -24,6 +24,8 @@ module ActiveRecord def serialize(value) if value.is_a?(::Hash) value.map { |k, v| "#{escape_hstore(k)}=>#{escape_hstore(v)}" }.join(", ") + elsif value.respond_to?(:to_unsafe_h) + serialize(value.to_unsafe_h) else value end diff --git a/activerecord/lib/active_record/null_relation.rb b/activerecord/lib/active_record/null_relation.rb index 254550c378..2bb7ed6d5e 100644 --- a/activerecord/lib/active_record/null_relation.rb +++ b/activerecord/lib/active_record/null_relation.rb @@ -41,12 +41,11 @@ module ActiveRecord end def calculate(operation, _column_name) - if [:count, :sum].include? operation + case operation + when :count, :sum group_values.any? ? Hash.new : 0 - elsif [:average, :minimum, :maximum].include?(operation) && group_values.any? - Hash.new - else - nil + when :average, :minimum, :maximum + group_values.any? ? Hash.new : nil end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 8b30a48c1c..6f602e4a23 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -362,6 +362,9 @@ module ActiveRecord # # # Update all books that match conditions, but limit it to 5 ordered by date # Book.where('title LIKE ?', '%Rails%').order(:created_at).limit(5).update_all(author: 'David') + # + # # Update all invoices and set the number column to its id value. + # Invoice.update_all('number = id') def update_all(updates) raise ArgumentError, "Empty list of attributes to change" if updates.blank? diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index c9982d3705..1f35300739 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -10,6 +10,12 @@ if ActiveRecord::Base.connection.supports_extensions? store_accessor :settings, :language, :timezone end + class FakeParameters + def to_unsafe_h + { "hi" => "hi" } + end + end + def setup @connection = ActiveRecord::Base.connection @@ -321,6 +327,10 @@ if ActiveRecord::Base.connection.supports_extensions? assert_match %r[t\.hstore "tags",\s+default: {}], output end + def test_supports_to_unsafe_h_values + assert_equal("\"hi\"=>\"hi\"", @type.serialize(FakeParameters.new)) + end + private def assert_array_cycle(array) # test creation diff --git a/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb index b898929f8a..181c1a097c 100644 --- a/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb +++ b/activerecord/test/cases/adapters/postgresql/prepared_statements_test.rb @@ -1,19 +1,20 @@ require "cases/helper" +require "models/computer" require "models/developer" class PreparedStatementsTest < ActiveRecord::PostgreSQLTestCase fixtures :developers def setup - @default_prepared_statements = Developer.connection_config[:prepared_statements] - Developer.connection_config[:prepared_statements] = false + @default_prepared_statements = ActiveRecord::Base.connection.instance_variable_get("@prepared_statements") + ActiveRecord::Base.connection.instance_variable_set("@prepared_statements", false) end def teardown - Developer.connection_config[:prepared_statements] = @default_prepared_statements + ActiveRecord::Base.connection.instance_variable_set("@prepared_statements", @default_prepared_statements) end - def nothing_raised_with_falsy_prepared_statements + def test_nothing_raised_with_falsy_prepared_statements assert_nothing_raised do Developer.where(id: 1) end diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index 4902792eee..4b7ac594cf 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -383,7 +383,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase dev.projects << projects(:active_record) assert_equal 3, dev.projects.size - assert_equal 1, dev.projects.distinct.size + assert_equal 1, dev.projects.uniq.size end def test_distinct_before_the_fact diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 3afd062950..d657be71cc 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -919,6 +919,7 @@ class HasManyAssociationsTest < ActiveRecord::TestCase company.clients_of_firm.build("name" => "Another Client") company.clients_of_firm.build("name" => "Yet Another Client") assert_equal 4, company.clients_of_firm.size + assert_equal 4, company.clients_of_firm.uniq.size end def test_collection_not_empty_after_building diff --git a/activerecord/test/cases/associations/join_model_test.rb b/activerecord/test/cases/associations/join_model_test.rb index 15446c6dc7..a4345f3857 100644 --- a/activerecord/test/cases/associations/join_model_test.rb +++ b/activerecord/test/cases/associations/join_model_test.rb @@ -413,7 +413,7 @@ class AssociationsJoinModelTest < ActiveRecord::TestCase author = Author.includes(:taggings).find authors(:david).id expected_taggings = taggings(:welcome_general, :thinking_general) assert_no_queries do - assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id) + assert_equal expected_taggings, author.taggings.uniq.sort_by(&:id) end end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index 0501514ba9..96833ad428 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -785,7 +785,6 @@ class RelationTest < ActiveRecord::TestCase expected_taggings = taggings(:welcome_general, :thinking_general) assert_no_queries do - assert_equal expected_taggings, author.taggings.distinct.sort_by(&:id) assert_equal expected_taggings, author.taggings.uniq.sort_by(&:id) end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 8b022af235..10095ee1bd 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -31,10 +31,6 @@ *Andrew White* -* Remove deprecated method `Module.local_constants` - - *Andrew White* - * Remove deprecated file `active_support/core_ext/time/marshal.rb` *Andrew White* diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index 135690cc42..7f29c955f9 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -85,14 +85,16 @@ module ActiveSupport # # Returns the exception if it was handled and +nil+ if it was not. def rescue_with_handler(exception, object: self) - if handler = handler_for_rescue(exception, object: object) + handler, exception = handler_for_rescue(exception, object: object) + if handler handler.call exception exception end end def handler_for_rescue(exception, object: self) #:nodoc: - case rescuer = find_rescue_handler(exception) + rescuer, exception = find_rescue_handler(exception) + result = case rescuer when Symbol method = object.method(rescuer) if method.arity == 0 @@ -107,6 +109,7 @@ module ActiveSupport -> e { object.instance_exec(e, &rescuer) } end end + [result, exception] end private @@ -121,7 +124,11 @@ module ActiveSupport end end - handler || find_rescue_handler(exception.cause) + if handler + [handler, exception] + else + find_rescue_handler(exception.cause) + end end end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index d30b34ecd6..404efe50bf 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -2,6 +2,7 @@ module ActiveSupport module Testing module Isolation require "thread" + require "shellwords" def self.included(klass) #:nodoc: klass.class_eval do @@ -80,7 +81,7 @@ module ActiveSupport load_paths = $-I.map { |p| "-I\"#{File.expand_path(p)}\"" }.join(" ") orig_args = ORIG_ARGV.join(" ") - test_opts = "-n#{self.class.name}##{self.name}" + test_opts = "-n#{self.class.name}##{Shellwords.escape(self.name)}" command = "#{Gem.ruby} #{load_paths} #{$0} '#{orig_args}' #{test_opts}" # IO.popen lets us pass env in a cross-platform way diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb index 7e5c3d1a8f..f7eb047d44 100644 --- a/activesupport/test/rescuable_test.rb +++ b/activesupport/test/rescuable_test.rb @@ -137,6 +137,6 @@ class RescuableTest < ActiveSupport::TestCase def test_rescue_falls_back_to_exception_cause @stargate.dispatch :fall_back_to_cause - assert_equal "unhandled RuntimeError with a handleable cause", @stargate.result + assert_equal "dex", @stargate.result end end diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 70b04a9695..6bbc79a326 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -511,56 +511,6 @@ NOTE: Defined in `active_support/core_ext/object/inclusion.rb`. Extensions to `Module` ---------------------- -### `alias_method_chain` - -**This method is deprecated in favour of using Module#prepend.** - -Using plain Ruby you can wrap methods with other methods, that's called _alias chaining_. - -For example, let's say you'd like params to be strings in functional tests, as they are in real requests, but still want the convenience of assigning integers and other kind of values. To accomplish that you could wrap `ActionDispatch::IntegrationTest#process` this way in `test/test_helper.rb`: - -```ruby -ActionDispatch::IntegrationTest.class_eval do - # save a reference to the original process method - alias_method :original_process, :process - - # now redefine process and delegate to original_process - def process('GET', path, params: nil, headers: nil, env: nil, xhr: false) - params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - original_process('GET', path, params: params) - end -end -``` - -That's the method `get`, `post`, etc., delegate the work to. - -That technique has a risk, it could be the case that `:original_process` was taken. To try to avoid collisions people choose some label that characterizes what the chaining is about: - -```ruby -ActionDispatch::IntegrationTest.class_eval do - def process_with_stringified_params(...) - params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - process_without_stringified_params(method, path, params: params) - end - alias_method :process_without_stringified_params, :process - alias_method :process, :process_with_stringified_params -end -``` - -The method `alias_method_chain` provides a shortcut for that pattern: - -```ruby -ActionDispatch::IntegrationTest.class_eval do - def process_with_stringified_params(...) - params = Hash[*params.map {|k, v| [k, v.to_s]}.flatten] - process_without_stringified_params(method, path, params: params) - end - alias_method_chain :process, :stringified_params -end -``` - -NOTE: Defined in `active_support/core_ext/module/aliasing.rb`. - ### Attributes #### `alias_attribute` diff --git a/guides/source/security.md b/guides/source/security.md index aea9728c10..bb67eb75d9 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -762,7 +762,7 @@ s = sanitize(user_input, tags: tags, attributes: %w(href title)) This allows only the given tags and does a good job, even against all kinds of tricks and malformed tags. -As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &, ", <, and > by their uninterpreted representations in HTML (`&`, `"`, `<`, and `>`). However, it can easily happen that the programmer forgets to use it, so _it is recommended to use the SafeErb gem. SafeErb reminds you to escape strings from external sources. +As a second step, _it is good practice to escape all output of the application_, especially when re-displaying user input, which hasn't been input-filtered (as in the search form example earlier on). _Use `escapeHTML()` (or its alias `h()`) method_ to replace the HTML input characters &, ", <, and > by their uninterpreted representations in HTML (`&`, `"`, `<`, and `>`). ##### Obfuscation and Encoding Injection |