aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md16
-rw-r--r--activerecord/Rakefile13
-rw-r--r--activerecord/lib/active_record/associations/collection_association.rb5
-rw-r--r--activerecord/lib/active_record/associations/collection_proxy.rb34
-rw-r--r--activerecord/lib/active_record/middleware/database_selector.rb22
-rw-r--r--activerecord/lib/active_record/middleware/database_selector/resolver.rb14
-rw-r--r--activerecord/lib/active_record/middleware/database_selector/resolver/session.rb2
-rw-r--r--activerecord/lib/active_record/railtie.rb2
-rw-r--r--activerecord/lib/active_record/railties/collection_cache_association_loading.rb2
-rw-r--r--activerecord/lib/active_record/relation.rb24
-rw-r--r--activerecord/lib/active_record/relation/delegation.rb2
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb2
-rw-r--r--activerecord/lib/active_record/scoping.rb19
-rw-r--r--activerecord/lib/active_record/scoping/default.rb9
-rw-r--r--activerecord/test/cases/associations/belongs_to_associations_test.rb3
-rw-r--r--activerecord/test/cases/associations/has_many_associations_test.rb3
-rw-r--r--activerecord/test/cases/associations/has_many_through_associations_test.rb3
-rw-r--r--activerecord/test/cases/associations/has_one_associations_test.rb4
-rw-r--r--activerecord/test/cases/base_test.rb2
-rw-r--r--activerecord/test/cases/relation/delegation_test.rb10
-rw-r--r--activerecord/test/cases/relations_test.rb36
-rw-r--r--activerecord/test/cases/scoping/named_scoping_test.rb10
-rw-r--r--activerecord/test/config.example.yml6
-rw-r--r--activerecord/test/models/bird.rb5
-rw-r--r--activerecord/test/models/topic.rb7
25 files changed, 148 insertions, 107 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 654caafc92..f16a746b91 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,15 @@
+* Fix `relation.create` to avoid leaking scope to initialization block and callbacks.
+
+ Fixes #9894, #17577.
+
+ *Ryuta Kamizono*
+
+* Chaining named scope is no longer leaking to class level querying methods.
+
+ Fixes #14003.
+
+ *Ryuta Kamizono*
+
* Allow applications to automatically switch connections.
Adds a middleware and configuration options that can be used in your
@@ -17,7 +29,7 @@
```
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
- config.active_record.database_operations = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
+ config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
```
To change the database selection strategy, pass a custom class to the
@@ -26,7 +38,7 @@
```
config.active_record.database_selector = { delay: 10.seconds }
config.active_record.database_resolver = MyResolver
- config.active_record.database_operations = MyResolver::MyCookies
+ config.active_record.database_resolver_context = MyResolver::MyCookies
```
*Eileen M. Uchitelle*
diff --git a/activerecord/Rakefile b/activerecord/Rakefile
index 013e81c959..9824787658 100644
--- a/activerecord/Rakefile
+++ b/activerecord/Rakefile
@@ -89,18 +89,23 @@ end
namespace :db do
namespace :mysql do
+ connection_arguments = lambda do |connection_name|
+ config = ARTest.config["connections"]["mysql2"][connection_name]
+ ["--user=#{config["username"]}", "--password=#{config["password"]}", ("--host=#{config["host"]}" if config["host"])].join(" ")
+ end
+
desc "Build the MySQL test databases"
task :build do
config = ARTest.config["connections"]["mysql2"]
- %x( mysql --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8mb4" )
- %x( mysql --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8mb4" )
+ %x( mysql #{connection_arguments["arunit"]} -e "create DATABASE #{config["arunit"]["database"]} DEFAULT CHARACTER SET utf8mb4" )
+ %x( mysql #{connection_arguments["arunit2"]} -e "create DATABASE #{config["arunit2"]["database"]} DEFAULT CHARACTER SET utf8mb4" )
end
desc "Drop the MySQL test databases"
task :drop do
config = ARTest.config["connections"]["mysql2"]
- %x( mysqladmin --user=#{config["arunit"]["username"]} --password=#{config["arunit"]["password"]} -f drop #{config["arunit"]["database"]} )
- %x( mysqladmin --user=#{config["arunit2"]["username"]} --password=#{config["arunit2"]["password"]} -f drop #{config["arunit2"]["database"]} )
+ %x( mysqladmin #{connection_arguments["arunit"]} -f drop #{config["arunit"]["database"]} )
+ %x( mysqladmin #{connection_arguments["arunit2"]} -f drop #{config["arunit2"]["database"]} )
end
desc "Rebuild the MySQL test databases"
diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb
index c6941a2e76..b0c0beac0e 100644
--- a/activerecord/lib/active_record/associations/collection_association.rb
+++ b/activerecord/lib/active_record/associations/collection_association.rb
@@ -109,9 +109,8 @@ module ActiveRecord
end
end
- # Add +records+ to this association. Returns +self+ so method calls may
- # be chained. Since << flattens its argument list and inserts each record,
- # +push+ and +concat+ behave identically.
+ # Add +records+ to this association. Since +<<+ flattens its argument list
+ # and inserts each record, +push+ and +concat+ behave identically.
def concat(*records)
records = records.flatten
if owner.new_record?
diff --git a/activerecord/lib/active_record/associations/collection_proxy.rb b/activerecord/lib/active_record/associations/collection_proxy.rb
index 4fbbc713e4..78993cee8a 100644
--- a/activerecord/lib/active_record/associations/collection_proxy.rb
+++ b/activerecord/lib/active_record/associations/collection_proxy.rb
@@ -366,34 +366,6 @@ module ActiveRecord
@association.create!(attributes, &block)
end
- # Add one or more records to the collection by setting their foreign keys
- # to the association's primary key. Since #<< flattens its argument list and
- # inserts each record, +push+ and #concat behave identically. Returns +self+
- # so method calls may be chained.
- #
- # class Person < ActiveRecord::Base
- # has_many :pets
- # end
- #
- # person.pets.size # => 0
- # person.pets.concat(Pet.new(name: 'Fancy-Fancy'))
- # person.pets.concat(Pet.new(name: 'Spook'), Pet.new(name: 'Choo-Choo'))
- # person.pets.size # => 3
- #
- # person.id # => 1
- # person.pets
- # # => [
- # # #<Pet id: 1, name: "Fancy-Fancy", person_id: 1>,
- # # #<Pet id: 2, name: "Spook", person_id: 1>,
- # # #<Pet id: 3, name: "Choo-Choo", person_id: 1>
- # # ]
- #
- # person.pets.concat([Pet.new(name: 'Brain'), Pet.new(name: 'Benny')])
- # person.pets.size # => 5
- def concat(*records)
- @association.concat(*records)
- end
-
# Replaces this collection with +other_array+. This will perform a diff
# and delete/add only records that have changed.
#
@@ -1033,8 +1005,9 @@ module ActiveRecord
end
# Adds one or more +records+ to the collection by setting their foreign keys
- # to the association's primary key. Returns +self+, so several appends may be
- # chained together.
+ # to the association's primary key. Since +<<+ flattens its argument list and
+ # inserts each record, +push+ and +concat+ behave identically. Returns +self+
+ # so several appends may be chained together.
#
# class Person < ActiveRecord::Base
# has_many :pets
@@ -1057,6 +1030,7 @@ module ActiveRecord
end
alias_method :push, :<<
alias_method :append, :<<
+ alias_method :concat, :<<
def prepend(*args)
raise NoMethodError, "prepend on association is not defined. Please use <<, push or append"
diff --git a/activerecord/lib/active_record/middleware/database_selector.rb b/activerecord/lib/active_record/middleware/database_selector.rb
index 3ab50f5f6b..aad263695b 100644
--- a/activerecord/lib/active_record/middleware/database_selector.rb
+++ b/activerecord/lib/active_record/middleware/database_selector.rb
@@ -12,8 +12,8 @@ module ActiveRecord
#
# The resolver class defines when the application should switch (i.e. read
# from the primary if a write occurred less than 2 seconds ago) and an
- # operations class that sets a value that helps the resolver class decide
- # when to switch.
+ # resolver context class that sets a value that helps the resolver class
+ # decide when to switch.
#
# Rails default middleware uses the request's session to set a timestamp
# that informs the application when to read from a primary or read from a
@@ -24,7 +24,7 @@ module ActiveRecord
#
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
- # config.active_record.database_operations = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
+ # config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
#
# New applications will include these lines commented out in the production.rb.
#
@@ -33,16 +33,16 @@ module ActiveRecord
#
# config.active_record.database_selector = { delay: 2.seconds }
# config.active_record.database_resolver = MyResolver
- # config.active_record.database_operations = MyResolver::MySession
+ # config.active_record.database_resolver_context = MyResolver::MySession
class DatabaseSelector
- def initialize(app, resolver_klass = Resolver, operations_klass = Resolver::Session, options = {})
+ def initialize(app, resolver_klass = Resolver, context_klass = Resolver::Session, options = {})
@app = app
@resolver_klass = resolver_klass
- @operations_klass = operations_klass
+ @context_klass = context_klass
@options = options
end
- attr_reader :resolver_klass, :operations_klass, :options
+ attr_reader :resolver_klass, :context_klass, :options
# Middleware that determines which database connection to use in a multiple
# database application.
@@ -57,13 +57,13 @@ module ActiveRecord
private
def select_database(request, &blk)
- operations = operations_klass.build(request)
- database_resolver = resolver_klass.call(operations, options)
+ context = context_klass.call(request)
+ resolver = resolver_klass.call(context, options)
if reading_request?(request)
- database_resolver.read(&blk)
+ resolver.read(&blk)
else
- database_resolver.write(&blk)
+ resolver.write(&blk)
end
end
diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver.rb b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
index a84c292714..80b8cd7cae 100644
--- a/activerecord/lib/active_record/middleware/database_selector/resolver.rb
+++ b/activerecord/lib/active_record/middleware/database_selector/resolver.rb
@@ -18,18 +18,18 @@ module ActiveRecord
class Resolver # :nodoc:
SEND_TO_REPLICA_DELAY = 2.seconds
- def self.call(resolver, options = {})
- new(resolver, options)
+ def self.call(context, options = {})
+ new(context, options)
end
- def initialize(resolver, options = {})
- @resolver = resolver
+ def initialize(context, options = {})
+ @context = context
@options = options
@delay = @options && @options[:delay] ? @options[:delay] : SEND_TO_REPLICA_DELAY
@instrumenter = ActiveSupport::Notifications.instrumenter
end
- attr_reader :resolver, :delay, :instrumenter
+ attr_reader :context, :delay, :instrumenter
def read(&blk)
if read_from_primary?
@@ -68,7 +68,7 @@ module ActiveRecord
instrumenter.instrument("database_selector.active_record.wrote_to_primary") do
yield
ensure
- resolver.update_last_write_timestamp
+ context.update_last_write_timestamp
end
end
end
@@ -82,7 +82,7 @@ module ActiveRecord
end
def time_since_last_write_ok?
- Time.now - resolver.last_write_timestamp >= send_to_replica_delay
+ Time.now - context.last_write_timestamp >= send_to_replica_delay
end
end
end
diff --git a/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb b/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb
index 33e0af5ee4..df7af054b7 100644
--- a/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb
+++ b/activerecord/lib/active_record/middleware/database_selector/resolver/session.rb
@@ -10,7 +10,7 @@ module ActiveRecord
# The last_write is used to determine whether it's safe to read
# from the replica or the request needs to be sent to the primary.
class Session # :nodoc:
- def self.build(request)
+ def self.call(request)
new(request.session)
end
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index aac49a92b4..a1d7c893bf 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -91,7 +91,7 @@ module ActiveRecord
initializer "active_record.database_selector" do
if options = config.active_record.delete(:database_selector)
resolver = config.active_record.delete(:database_resolver)
- operations = config.active_record.delete(:database_operations)
+ operations = config.active_record.delete(:database_resolver_context)
config.app_middleware.use ActiveRecord::Middleware::DatabaseSelector, resolver, operations, options
end
end
diff --git a/activerecord/lib/active_record/railties/collection_cache_association_loading.rb b/activerecord/lib/active_record/railties/collection_cache_association_loading.rb
index dfaac4eefb..d57680aaaa 100644
--- a/activerecord/lib/active_record/railties/collection_cache_association_loading.rb
+++ b/activerecord/lib/active_record/railties/collection_cache_association_loading.rb
@@ -3,7 +3,7 @@
module ActiveRecord
module Railties # :nodoc:
module CollectionCacheAssociationLoading #:nodoc:
- def setup(context, options, block)
+ def setup(context, options, as, block)
@relation = relation_from_options(options)
super
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index a863227276..347d745d19 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -67,6 +67,7 @@ module ActiveRecord
# user = users.new { |user| user.name = 'Oscar' }
# user.name # => Oscar
def new(attributes = nil, &block)
+ block = klass.current_scope_restoring_block(&block)
scoping { klass.new(attributes, &block) }
end
@@ -92,7 +93,11 @@ module ActiveRecord
# users.create(name: nil) # validation on name
# # => #<User id: nil, name: nil, ...>
def create(attributes = nil, &block)
- scoping { klass.create(attributes, &block) }
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create(attr, &block) }
+ else
+ new(attributes, &block).tap(&:save)
+ end
end
# Similar to #create, but calls
@@ -102,7 +107,11 @@ module ActiveRecord
# Expects arguments in the same format as
# {ActiveRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!].
def create!(attributes = nil, &block)
- scoping { klass.create!(attributes, &block) }
+ if attributes.is_a?(Array)
+ attributes.collect { |attr| create!(attr, &block) }
+ else
+ new(attributes, &block).tap(&:save!)
+ end
end
def first_or_create(attributes = nil, &block) # :nodoc:
@@ -312,12 +321,12 @@ module ActiveRecord
# Please check unscoped if you want to remove all previous scopes (including
# the default_scope) during the execution of a block.
def scoping
- @delegate_to_klass ? yield : klass._scoping(self) { yield }
+ @delegate_to_klass && klass.current_scope(true) ? yield : _scoping(self) { yield }
end
def _exec_scope(*args, &block) # :nodoc:
@delegate_to_klass = true
- instance_exec(*args, &block) || self
+ _scoping(nil) { instance_exec(*args, &block) || self }
ensure
@delegate_to_klass = false
end
@@ -632,6 +641,13 @@ module ActiveRecord
end
private
+ def _scoping(scope)
+ previous, klass.current_scope = klass.current_scope(true), scope
+ yield
+ ensure
+ klass.current_scope = previous
+ end
+
def _substitute_values(values)
values.map do |name, value|
attr = arel_attribute(name)
diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb
index 6e8a1fcad4..f7c3b3783f 100644
--- a/activerecord/lib/active_record/relation/delegation.rb
+++ b/activerecord/lib/active_record/relation/delegation.rb
@@ -132,7 +132,7 @@ module ActiveRecord
private
def respond_to_missing?(method, _)
- super || @klass.respond_to?(method) || arel.respond_to?(method)
+ super || @klass.respond_to?(method)
end
end
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 7874c4c35a..8cf4fc469f 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -8,7 +8,7 @@ module ActiveRecord
module SpawnMethods
# This is overridden by Associations::CollectionProxy
def spawn #:nodoc:
- @delegate_to_klass ? klass.all : clone
+ @delegate_to_klass && klass.current_scope(true) ? klass.all : clone
end
# Merges in the conditions from <tt>other</tt>, if <tt>other</tt> is an ActiveRecord::Relation.
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index 9eba1254a4..1142a87d25 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -23,14 +23,21 @@ module ActiveRecord
current_scope
end
- private
- def current_scope(skip_inherited_scope = false)
- ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
- end
+ def current_scope(skip_inherited_scope = false)
+ ScopeRegistry.value_for(:current_scope, self, skip_inherited_scope)
+ end
- def current_scope=(scope)
- ScopeRegistry.set_value_for(:current_scope, self, scope)
+ def current_scope=(scope)
+ ScopeRegistry.set_value_for(:current_scope, self, scope)
+ end
+
+ def current_scope_restoring_block(&block)
+ current_scope = self.current_scope(true)
+ -> *args do
+ self.current_scope = current_scope
+ yield(*args) if block_given?
end
+ end
end
def populate_with_current_scope_attributes # :nodoc:
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index 6caf9b3251..8c612df27a 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -31,14 +31,7 @@ module ActiveRecord
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
# }
def unscoped
- block_given? ? _scoping(relation) { yield } : relation
- end
-
- def _scoping(relation) # :nodoc:
- previous, self.current_scope = current_scope(true), relation
- yield
- ensure
- self.current_scope = previous
+ block_given? ? relation.scoping { yield } : relation
end
# Are there attributes associated with this scope?
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index a61569420e..c01138c059 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -63,7 +63,8 @@ class BelongsToAssociationsTest < ActiveRecord::TestCase
ActiveRecord::SQLCounter.clear_log
Client.find(3).firm
ensure
- assert ActiveRecord::SQLCounter.log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query"
+ sql_log = ActiveRecord::SQLCounter.log
+ assert sql_log.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{sql_log}"
end
def test_belongs_to_with_primary_key
diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb
index 130cb08b0d..bf44be1811 100644
--- a/activerecord/test/cases/associations/has_many_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_associations_test.rb
@@ -996,9 +996,10 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
assert_predicate companies(:first_firm).clients_of_firm, :loaded?
- companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
+ result = companies(:first_firm).clients_of_firm.concat([Client.new("name" => "Natural Company"), Client.new("name" => "Apple")])
assert_equal 4, companies(:first_firm).clients_of_firm.size
assert_equal 4, companies(:first_firm).clients_of_firm.reload.size
+ assert_equal companies(:first_firm).clients_of_firm, result
end
def test_transactions_when_adding_to_persisted
diff --git a/activerecord/test/cases/associations/has_many_through_associations_test.rb b/activerecord/test/cases/associations/has_many_through_associations_test.rb
index 0133beccec..0ac56c6168 100644
--- a/activerecord/test/cases/associations/has_many_through_associations_test.rb
+++ b/activerecord/test/cases/associations/has_many_through_associations_test.rb
@@ -242,9 +242,10 @@ class HasManyThroughAssociationsTest < ActiveRecord::TestCase
def test_concat
person = people(:david)
post = posts(:thinking)
- post.people.concat [person]
+ result = post.people.concat [person]
assert_equal 1, post.people.size
assert_equal 1, post.people.reload.size
+ assert_equal post.people, result
end
def test_associate_existing_record_twice_should_add_to_target_twice
diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb
index 3e5b5c1275..7bb629466d 100644
--- a/activerecord/test/cases/associations/has_one_associations_test.rb
+++ b/activerecord/test/cases/associations/has_one_associations_test.rb
@@ -37,8 +37,8 @@ class HasOneAssociationsTest < ActiveRecord::TestCase
ActiveRecord::SQLCounter.clear_log
companies(:first_firm).account
ensure
- log_all = ActiveRecord::SQLCounter.log_all
- assert log_all.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{log_all}"
+ sql_log = ActiveRecord::SQLCounter.log
+ assert sql_log.all? { |sql| /order by/i !~ sql }, "ORDER BY was used in the query: #{sql_log}"
end
def test_has_one_cache_nils
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index 4938b6865f..363593ca19 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1535,7 +1535,7 @@ class BasicsTest < ActiveRecord::TestCase
Bird.create!(name: "Bluejay")
ActiveRecord::Base.connection.while_preventing_writes do
- assert_queries(2) { Bird.where(name: "Bluejay").explain }
+ assert_nothing_raised { Bird.where(name: "Bluejay").explain }
end
end
diff --git a/activerecord/test/cases/relation/delegation_test.rb b/activerecord/test/cases/relation/delegation_test.rb
index b600c999a6..63ae438de3 100644
--- a/activerecord/test/cases/relation/delegation_test.rb
+++ b/activerecord/test/cases/relation/delegation_test.rb
@@ -5,7 +5,7 @@ require "models/post"
require "models/comment"
module ActiveRecord
- module ArrayDelegationTests
+ module DelegationTests
ARRAY_DELEGATES = [
:+, :-, :|, :&, :[], :shuffle,
:all?, :collect, :compact, :detect, :each, :each_cons, :each_with_index,
@@ -21,10 +21,14 @@ module ActiveRecord
assert_respond_to target, method
end
end
+
+ def test_not_respond_to_arel_method
+ assert_not_respond_to target, :exists
+ end
end
class DelegationAssociationTest < ActiveRecord::TestCase
- include ArrayDelegationTests
+ include DelegationTests
def target
Post.new.comments
@@ -32,7 +36,7 @@ module ActiveRecord
end
class DelegationRelationTest < ActiveRecord::TestCase
- include ArrayDelegationTests
+ include DelegationTests
def target
Comment.all
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 756eeca35f..0ab0459c38 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -480,21 +480,6 @@ class RelationTest < ActiveRecord::TestCase
assert_nothing_raised { Topic.reorder([]) }
end
- def test_respond_to_delegates_to_arel
- relation = Topic.all
- fake_arel = Struct.new(:responds) {
- def respond_to?(method, access = false)
- responds << [method, access]
- end
- }.new []
-
- relation.extend(Module.new { attr_accessor :arel })
- relation.arel = fake_arel
-
- relation.respond_to?(:matching_attributes)
- assert_equal [:matching_attributes, false], fake_arel.responds.first
- end
-
def test_respond_to_dynamic_finders
relation = Topic.all
@@ -1182,7 +1167,12 @@ class RelationTest < ActiveRecord::TestCase
end
def test_first_or_create_with_block
- parrot = Bird.where(color: "green").first_or_create { |bird| bird.name = "parrot" }
+ canary = Bird.create!(color: "yellow", name: "canary")
+ parrot = Bird.where(color: "green").first_or_create do |bird|
+ bird.name = "parrot"
+ assert_equal canary, Bird.find_by!(name: "canary")
+ end
+ assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
@@ -1224,7 +1214,12 @@ class RelationTest < ActiveRecord::TestCase
end
def test_first_or_create_bang_with_valid_block
- parrot = Bird.where(color: "green").first_or_create! { |bird| bird.name = "parrot" }
+ canary = Bird.create!(color: "yellow", name: "canary")
+ parrot = Bird.where(color: "green").first_or_create! do |bird|
+ bird.name = "parrot"
+ assert_equal canary, Bird.find_by!(name: "canary")
+ end
+ assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_predicate parrot, :persisted?
assert_equal "green", parrot.color
@@ -1274,7 +1269,12 @@ class RelationTest < ActiveRecord::TestCase
end
def test_first_or_initialize_with_block
- parrot = Bird.where(color: "green").first_or_initialize { |bird| bird.name = "parrot" }
+ canary = Bird.create!(color: "yellow", name: "canary")
+ parrot = Bird.where(color: "green").first_or_initialize do |bird|
+ bird.name = "parrot"
+ assert_equal canary, Bird.find_by!(name: "canary")
+ end
+ assert_equal 1, parrot.total_count
assert_kind_of Bird, parrot
assert_not_predicate parrot, :persisted?
assert_predicate parrot, :valid?
diff --git a/activerecord/test/cases/scoping/named_scoping_test.rb b/activerecord/test/cases/scoping/named_scoping_test.rb
index 418a2ae04e..27f9df295f 100644
--- a/activerecord/test/cases/scoping/named_scoping_test.rb
+++ b/activerecord/test/cases/scoping/named_scoping_test.rb
@@ -447,6 +447,16 @@ class NamedScopingTest < ActiveRecord::TestCase
assert_equal [posts(:sti_comments)], Post.with_special_comments.with_post(4).to_a.uniq
end
+ def test_chaining_doesnt_leak_conditions_to_another_scopes
+ expected = Topic.where(approved: false).where(id: Topic.children.select(:parent_id))
+ assert_equal expected.to_a, Topic.rejected.has_children.to_a
+ end
+
+ def test_nested_scoping
+ expected = Reply.approved
+ assert_equal expected.to_a, Topic.rejected.nested_scoping(expected)
+ end
+
def test_scopes_batch_finders
assert_equal 4, Topic.approved.count
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index 33962f9e5e..f5e3ac3c19 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -54,10 +54,16 @@ connections:
username: rails
encoding: utf8mb4
collation: utf8mb4_unicode_ci
+<% if ENV['MYSQL_HOST'] %>
+ host: <%= ENV['MYSQL_HOST'] %>
+<% end %>
arunit2:
username: rails
encoding: utf8mb4
collation: utf8mb4_general_ci
+<% if ENV['MYSQL_HOST'] %>
+ host: <%= ENV['MYSQL_HOST'] %>
+<% end %>
oracle:
arunit:
diff --git a/activerecord/test/models/bird.rb b/activerecord/test/models/bird.rb
index cfefa555b3..c9f6759c7d 100644
--- a/activerecord/test/models/bird.rb
+++ b/activerecord/test/models/bird.rb
@@ -16,4 +16,9 @@ class Bird < ActiveRecord::Base
def cancel_save_callback_method
throw(:abort)
end
+
+ attr_accessor :total_count
+ after_initialize do
+ self.total_count = Bird.count
+ end
end
diff --git a/activerecord/test/models/topic.rb b/activerecord/test/models/topic.rb
index a6a47687a2..fdb461ed7f 100644
--- a/activerecord/test/models/topic.rb
+++ b/activerecord/test/models/topic.rb
@@ -10,6 +10,9 @@ class Topic < ActiveRecord::Base
scope :approved, -> { where(approved: true) }
scope :rejected, -> { where(approved: false) }
+ scope :children, -> { where.not(parent_id: nil) }
+ scope :has_children, -> { where(id: Topic.children.select(:parent_id)) }
+
scope :scope_with_lambda, lambda { all }
scope :by_lifo, -> { where(author_name: "lifo") }
@@ -88,6 +91,10 @@ class Topic < ActiveRecord::Base
write_attribute(:approved, val)
end
+ def self.nested_scoping(scope)
+ scope.base
+ end
+
private
def default_written_on