aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actioncable/lib/action_cable/channel/test_case.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb14
-rw-r--r--actionpack/test/dispatch/routing_test.rb15
-rw-r--r--activejob/CHANGELOG.md6
-rw-r--r--activejob/lib/active_job/test_helper.rb50
-rw-r--r--activejob/test/cases/test_helper_test.rb46
-rw-r--r--activerecord/lib/active_record/associations.rb2
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb4
-rw-r--r--activerecord/lib/active_record/relation.rb23
-rw-r--r--activerecord/lib/arel.rb5
-rw-r--r--activerecord/lib/arel/factory_methods.rb4
-rw-r--r--activerecord/lib/arel/nodes/node.rb10
-rw-r--r--activerecord/lib/arel/visitors/to_sql.rb4
13 files changed, 156 insertions, 35 deletions
diff --git a/actioncable/lib/action_cable/channel/test_case.rb b/actioncable/lib/action_cable/channel/test_case.rb
index 88d7c7092b..a26051d687 100644
--- a/actioncable/lib/action_cable/channel/test_case.rb
+++ b/actioncable/lib/action_cable/channel/test_case.rb
@@ -215,13 +215,9 @@ module ActionCable
# Subsribe to the channel under test. Optionally pass subscription parameters as a Hash.
def subscribe(params = {})
@connection ||= stub_connection
- # NOTE: Rails < 5.0.1 calls subscribe_to_channel during #initialize.
- # We have to stub before it
- @subscription = self.class.channel_class.allocate
+ @subscription = self.class.channel_class.new(connection, CHANNEL_IDENTIFIER, params.with_indifferent_access)
@subscription.singleton_class.include(ChannelStub)
- @subscription.send(:initialize, connection, CHANNEL_IDENTIFIER, params.with_indifferent_access)
- # Call subscribe_to_channel if it's public (Rails 5.0.1+)
- @subscription.subscribe_to_channel if ActionCable.gem_version >= Gem::Version.new("5.0.1")
+ @subscription.subscribe_to_channel
@subscription
end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 9b378d9031..8b5a39dcaf 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -50,7 +50,19 @@ module ActionDispatch
private
def constraint_args(constraint, request)
- constraint.arity == 1 ? [request] : [request.path_parameters, request]
+ arity = if constraint.respond_to?(:arity)
+ constraint.arity
+ else
+ constraint.method(:call).arity
+ end
+
+ if arity < 1
+ []
+ elsif arity == 1
+ [request]
+ else
+ [request.path_parameters, request]
+ end
end
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index fdfa21fccc..affc2d8497 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -115,6 +115,21 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 301, status
end
+ def test_accepts_a_constraint_object_responding_to_call
+ constraint = Class.new do
+ def call(*); true; end
+ def matches?(*); false; end
+ end
+
+ draw do
+ get "/", to: "home#show", constraints: constraint.new
+ end
+
+ assert_nothing_raised do
+ get "/"
+ end
+ end
+
def test_namespace_with_controller_segment
assert_raise(ArgumentError) do
draw do
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index 57a62e963d..af5c197bac 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Allow `assert_enqueued_with`/`assert_performed_with` methods to accept
+ a proc for the `args` argument. This is useful to check if only a subset of arguments
+ matches your expectations.
+
+ *Edouard Chin*
+
* `ActionDispatch::IntegrationTest` includes `ActiveJob::TestHelper` module by default.
*Ricardo Díaz*
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 9efc8c0c12..e7a9b16277 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -331,6 +331,22 @@ module ActiveJob
# assert_enqueued_with(job: MyJob, at: Date.tomorrow.noon)
# end
#
+ #
+ # The +args+ argument also accepts a proc which will get passed the actual
+ # job's arguments. Your proc needs to returns a boolean value determining if
+ # the job's arguments matches your expectation. This is useful to check only
+ # for a subset of arguments.
+ #
+ # def test_assert_enqueued_with
+ # expected_args = ->(job_args) do
+ # assert job_args.first.key?(:foo)
+ # end
+ #
+ # MyJob.perform_later(foo: 'bar', other_arg: 'No need to check in the test')
+ # assert_enqueued_with(job: MyJob, args: expected_args, queue: 'low')
+ # end
+ #
+ #
# If a block is passed, that block should cause the job to be
# enqueued with the given arguments.
#
@@ -359,7 +375,14 @@ module ActiveJob
matching_job = jobs.find do |enqueued_job|
deserialized_job = deserialize_args_for_assertion(enqueued_job)
- expected_args.all? { |key, value| value == deserialized_job[key] }
+
+ expected_args.all? do |key, value|
+ if value.respond_to?(:call)
+ value.call(deserialized_job[key])
+ else
+ value == deserialized_job[key]
+ end
+ end
end
assert matching_job, "No enqueued job found with #{expected}"
@@ -382,6 +405,22 @@ module ActiveJob
# assert_performed_with(job: MyJob, at: Date.tomorrow.noon)
# end
#
+ # The +args+ argument also accepts a proc which will get passed the actual
+ # job's arguments. Your proc needs to returns a boolean value determining if
+ # the job's arguments matches your expectation. This is useful to check only
+ # for a subset of arguments.
+ #
+ # def test_assert_performed_with
+ # expected_args = ->(job_args) do
+ # assert job_args.first.key?(:foo)
+ # end
+ # MyJob.perform_later(foo: 'bar', other_arg: 'No need to check in the test')
+ #
+ # perform_enqueued_jobs
+ #
+ # assert_performed_with(job: MyJob, args: expected_args, queue: 'high')
+ # end
+ #
# If a block is passed, that block performs all of the jobs that were
# enqueued throughout the duration of the block and asserts that
# the job has been performed with the given arguments in the block.
@@ -411,7 +450,14 @@ module ActiveJob
matching_job = jobs.find do |enqueued_job|
deserialized_job = deserialize_args_for_assertion(enqueued_job)
- expected_args.all? { |key, value| value == deserialized_job[key] }
+
+ expected_args.all? do |key, value|
+ if value.respond_to?(:call)
+ value.call(deserialized_job[key])
+ else
+ value == deserialized_job[key]
+ end
+ end
end
assert matching_job, "No performed job found with #{expected}"
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 018c40c28f..83c71ab1c4 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -538,6 +538,29 @@ class EnqueuedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_enqueued_with_selective_args
+ args = ->(job_args) do
+ assert_equal 1, job_args.first[:argument1]
+ assert job_args.first[:argument2].key?(:b)
+ end
+
+ assert_enqueued_with(job: MultipleKwargsJob, args: args) do
+ MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
+ end
+ end
+
+ def test_assert_enqueued_with_selective_args_fails
+ args = ->(job_args) do
+ false
+ end
+
+ assert_raise ActiveSupport::TestCase::Assertion do
+ assert_enqueued_with(job: MultipleKwargsJob, args: args) do
+ MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
+ end
+ end
+ end
+
def test_assert_enqueued_with_with_no_block_args
assert_raise ArgumentError do
NestedJob.set(wait_until: Date.tomorrow.noon).perform_later
@@ -1579,6 +1602,29 @@ class PerformedJobsTest < ActiveJob::TestCase
end
end
+ def test_assert_performed_with_selective_args
+ args = ->(job_args) do
+ assert_equal 1, job_args.first[:argument1]
+ assert job_args.first[:argument2].key?(:b)
+ end
+
+ assert_performed_with(job: MultipleKwargsJob, args: args) do
+ MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
+ end
+ end
+
+ def test_assert_performed_with_selective_args_fails
+ args = ->(job_args) do
+ false
+ end
+
+ assert_raise ActiveSupport::TestCase::Assertion do
+ assert_performed_with(job: MultipleKwargsJob, args: args) do
+ MultipleKwargsJob.perform_later(argument2: { b: 2, a: 1 }, argument1: 1)
+ end
+ end
+ end
+
def test_assert_performed_with_with_global_id_args
ricardo = Person.new(9)
assert_performed_with(job: HelloJob, args: [ricardo]) do
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index b1778732dd..ab1e7ad269 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1582,7 +1582,7 @@ module ActiveRecord
# association will use "taggable_type" as the default <tt>:foreign_type</tt>.
# [:primary_key]
# Specify the method that returns the primary key of associated object used for the association.
- # By default this is id.
+ # By default this is +id+.
# [:dependent]
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index 701f19a6ae..42eba27b2a 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -194,9 +194,7 @@ module ActiveRecord
def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
unexpected = args.reject do |arg|
- arg.kind_of?(Arel::Node) ||
- arg.is_a?(Arel::Nodes::SqlLiteral) ||
- arg.is_a?(Arel::Attributes::Attribute) ||
+ Arel.arel_node?(arg) ||
arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 771ca952e1..db06bd9e26 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -379,19 +379,22 @@ module ActiveRecord
def update_counters(counters) # :nodoc:
touch = counters.delete(:touch)
- updates = counters.map do |counter_name, value|
- operator = value < 0 ? "-" : "+"
- quoted_column = connection.quote_table_name_for_assignment(table.name, counter_name)
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
+ updates = {}
+ counters.each do |counter_name, value|
+ attr = arel_attribute(counter_name)
+ bind = predicate_builder.build_bind_attribute(attr.name, value.abs)
+ expr = table.coalesce(Arel::Nodes::UnqualifiedColumn.new(attr), 0)
+ expr = value < 0 ? expr - bind : expr + bind
+ updates[counter_name] = expr.expr
end
if touch
names = touch if touch != true
touch_updates = klass.touch_attributes_with_time(*names)
- updates << klass.sanitize_sql_for_assignment(touch_updates, table.name) unless touch_updates.empty?
+ updates.merge!(touch_updates) unless touch_updates.empty?
end
- update_all updates.join(", ")
+ update_all updates
end
# Touches all records in the current relation without instantiating records first with the updated_at/on attributes
@@ -632,9 +635,11 @@ module ActiveRecord
def _substitute_values(values)
values.map do |name, value|
attr = arel_attribute(name)
- type = klass.type_for_attribute(attr.name)
- bind = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
- [attr, bind]
+ unless Arel.arel_node?(value)
+ type = klass.type_for_attribute(attr.name)
+ value = predicate_builder.build_bind_attribute(attr.name, type.cast(value))
+ end
+ [attr, value]
end
end
diff --git a/activerecord/lib/arel.rb b/activerecord/lib/arel.rb
index 7d04e1cac6..dab785738e 100644
--- a/activerecord/lib/arel.rb
+++ b/activerecord/lib/arel.rb
@@ -35,6 +35,11 @@ module Arel # :nodoc: all
def self.star
sql "*"
end
+
+ def self.arel_node?(value)
+ value.is_a?(Arel::Node) || value.is_a?(Arel::Attribute) || value.is_a?(Arel::Nodes::SqlLiteral)
+ end
+
## Convenience Alias
Node = Arel::Nodes::Node
end
diff --git a/activerecord/lib/arel/factory_methods.rb b/activerecord/lib/arel/factory_methods.rb
index b828bc274e..83ec23e403 100644
--- a/activerecord/lib/arel/factory_methods.rb
+++ b/activerecord/lib/arel/factory_methods.rb
@@ -41,5 +41,9 @@ module Arel # :nodoc: all
def lower(column)
Nodes::NamedFunction.new "LOWER", [Nodes.build_quoted(column)]
end
+
+ def coalesce(*exprs)
+ Nodes::NamedFunction.new "COALESCE", exprs
+ end
end
end
diff --git a/activerecord/lib/arel/nodes/node.rb b/activerecord/lib/arel/nodes/node.rb
index 2b9b1e9828..8086102bde 100644
--- a/activerecord/lib/arel/nodes/node.rb
+++ b/activerecord/lib/arel/nodes/node.rb
@@ -8,16 +8,6 @@ module Arel # :nodoc: all
include Arel::FactoryMethods
include Enumerable
- if $DEBUG
- def _caller
- @caller
- end
-
- def initialize
- @caller = caller.dup
- end
- end
-
###
# Factory method to create a Nodes::Not node that has the recipient of
# the caller as a child.
diff --git a/activerecord/lib/arel/visitors/to_sql.rb b/activerecord/lib/arel/visitors/to_sql.rb
index 81ca63f261..1ed41ff577 100644
--- a/activerecord/lib/arel/visitors/to_sql.rb
+++ b/activerecord/lib/arel/visitors/to_sql.rb
@@ -637,7 +637,7 @@ module Arel # :nodoc: all
def visit_Arel_Nodes_Assignment(o, collector)
case o.right
- when Arel::Nodes::UnqualifiedColumn, Arel::Attributes::Attribute, Arel::Nodes::BindParam
+ when Arel::Nodes::Node, Arel::Attributes::Attribute
collector = visit o.left, collector
collector << " = "
visit o.right, collector
@@ -732,8 +732,6 @@ module Arel # :nodoc: all
end
alias :visit_Arel_Nodes_SqlLiteral :literal
- alias :visit_Bignum :literal
- alias :visit_Fixnum :literal
alias :visit_Integer :literal
def quoted(o, a)