aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--actioncable/README.md15
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb9
-rw-r--r--actionpack/lib/action_dispatch/testing/test_response.rb2
-rw-r--r--actionpack/test/controller/integration_test.rb10
-rw-r--r--actionview/lib/action_view/digestor.rb13
-rw-r--r--activerecord/CHANGELOG.md4
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb18
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb4
-rw-r--r--activerecord/lib/active_record/querying.rb4
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb8
-rw-r--r--activerecord/lib/active_record/statement_cache.rb2
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb2
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb12
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb11
-rw-r--r--activerecord/test/models/comment.rb1
16 files changed, 85 insertions, 34 deletions
diff --git a/actioncable/README.md b/actioncable/README.md
index 3ed8a23a29..c85d59a1c8 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -298,22 +298,24 @@ See the [rails/actioncable-examples](http://github.com/rails/actioncable-example
## Configuration
-Action Cable has three required configurations: the Redis connection, allowed request origins, and the cable server url (which can optionally be set on the client side).
+Action Cable has three required configurations: a subscription adapter, allowed request origins, and the cable server URL (which can optionally be set on the client side).
### Redis
By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/cable.yml')`.
-This file must specify a Redis url for each Rails environment. It may use the following format:
+This file must specify an adapter and a URL for each Rails environment. It may use the following format:
```yaml
production: &production
+ adapter: redis
url: redis://10.10.3.153:6381
development: &development
+ adapter: redis
url: redis://localhost:6379
test: *development
```
-You can also change the location of the Redis config file in a Rails initializer with something like:
+You can also change the location of the Action Cable config file in a Rails initializer with something like:
```ruby
Rails.application.paths.add "config/cable", with: "somewhere/else/cable.yml"
@@ -389,7 +391,7 @@ Also note that your server must provide at least the same number of database con
## Running the cable server
### Standalone
-The cable server(s) is separated from your normal application server. It's still a rack application, but it is its own rack
+The cable server(s) is separated from your normal application server. It's still a Rack application, but it is its own Rack
application. The recommended basic setup is as follows:
```ruby
@@ -431,10 +433,7 @@ The WebSocket server doesn't have access to the session, but it has access to th
## Dependencies
-Action Cable is currently tied to Redis through its use of the pubsub feature to route
-messages back and forth over the WebSocket cable connection. This dependency may well
-be alleviated in the future, but for the moment that's what it is. So be sure to have
-Redis installed and running.
+Action Cable provides a subscription adapter interface to process its pubsub internals. By default, asynchronous, inline, PostgreSQL, evented Redis, and non-evented Redis adapters are included. The default adapter in new Rails applications is the asynchronous (`async`) adapter. To create your own adapter, you can look at `ActionCable::SubscriptionAdapter::Base` for all methods that must be implemented, and any of the adapters included within ActionCable as example implementations.
The Ruby side of things is built on top of [websocket-driver](https://github.com/faye/websocket-driver-ruby), [nio4r](https://github.com/celluloid/nio4r), and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 742bce1ca6..8a8e22053a 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -381,7 +381,7 @@ module ActionDispatch
response = _mock_session.last_response
@response = ActionDispatch::TestResponse.from_response(response)
@response.request = @request
- @response.response_parser = request_encoder
+ @response.response_parser = RequestEncoder.parser(@response.content_type)
@html_document = nil
@url_options = nil
@@ -397,6 +397,8 @@ module ActionDispatch
class RequestEncoder # :nodoc:
@encoders = {}
+ attr_reader :response_parser
+
def initialize(mime_name, param_encoder, response_parser, url_encoded_form = false)
@mime = Mime[mime_name]
@@ -424,8 +426,9 @@ module ActionDispatch
@param_encoder.call(params)
end
- def parse_body(body)
- @response_parser.call(body)
+ def self.parser(content_type)
+ mime = Mime::Type.lookup(content_type)
+ encoder(mime ? mime.ref : nil).response_parser
end
def self.encoder(name)
diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb
index 58d3e6eb0f..4f289ad4b5 100644
--- a/actionpack/lib/action_dispatch/testing/test_response.rb
+++ b/actionpack/lib/action_dispatch/testing/test_response.rb
@@ -22,7 +22,7 @@ module ActionDispatch
attr_writer :response_parser # :nodoc:
def parsed_body
- @response_parser.parse_body(body)
+ @response_parser.call(body)
end
end
end
diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb
index cb524bacb2..ea50f05f4d 100644
--- a/actionpack/test/controller/integration_test.rb
+++ b/actionpack/test/controller/integration_test.rb
@@ -1171,6 +1171,16 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
Mime::Type.unregister :wibble
end
+ def test_parsed_body_without_as_option
+ with_routing do |routes|
+ routes.draw { get ':action' => FooController }
+
+ get '/foos_json.json', params: { foo: 'heyo' }
+
+ assert_equal({ 'foo' => 'heyo' }, response.parsed_body)
+ end
+ end
+
private
def post_to_foos(as:)
with_routing do |routes|
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 6f2f9ca53c..3a6cf63803 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -64,17 +64,17 @@ module ActionView
def digest
Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.try :debug, " Cache digest for #{template.inspect}: #{digest}"
+ logger.debug " Cache digest for #{template.inspect}: #{digest}"
end
rescue ActionView::MissingTemplate
- logger.try :error, " Couldn't find template for digesting: #{name}"
+ logger.error " Couldn't find template for digesting: #{name}"
''
end
def dependencies
DependencyTracker.find_dependencies(name, template, finder.view_paths)
rescue ActionView::MissingTemplate
- logger.try :error, " '#{name}' file doesn't exist, so no dependencies"
+ logger.error " '#{name}' file doesn't exist, so no dependencies"
[]
end
@@ -86,8 +86,13 @@ module ActionView
end
private
+ class NullLogger
+ def self.debug(_); end
+ def self.error(_); end
+ end
+
def logger
- ActionView::Base.logger
+ ActionView::Base.logger || NullLogger
end
def logical_name
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index be96f9fa2a..ab17a2b438 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Allow `joins` to be unscoped.
+
+ Closes #13775.
+
* Add ActiveRecord `#second_to_last` and `#third_to_last` methods.
*Brian Christian*
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
index 708b3af5bd..c5fbe0d1d1 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -54,12 +54,18 @@ module ActiveRecord
end
scope_chain_index += 1
- relation = ActiveRecord::Relation.create(
- klass,
- table,
- predicate_builder,
- )
- scope_chain_items.concat [klass.send(:build_default_scope, relation)].compact
+ klass_scope =
+ if klass.current_scope
+ klass.current_scope.clone
+ else
+ relation = ActiveRecord::Relation.create(
+ klass,
+ table,
+ predicate_builder,
+ )
+ klass.send(:build_default_scope, relation)
+ end
+ scope_chain_items.concat [klass_scope].compact
rel = scope_chain_items.inject(scope_chain_items.shift) do |left, right|
left.merge right
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 7e0c9f7837..bb5119d64e 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -27,10 +27,10 @@ module ActiveRecord
end
# Returns an ActiveRecord::Result instance.
- def select_all(arel, name = nil, binds = [])
+ def select_all(arel, name = nil, binds = [], preparable: nil)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- if arel.is_a?(String)
+ if arel.is_a?(String) && preparable.nil?
preparable = false
else
preparable = visitor.preparable
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
index 5e27cfe507..33dbab41cb 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb
@@ -61,11 +61,11 @@ module ActiveRecord
@query_cache.clear
end
- def select_all(arel, name = nil, binds = [])
+ def select_all(arel, name = nil, binds = [], preparable: nil)
if @query_cache_enabled && !locked?(arel)
arel, binds = binds_from_relation arel, binds
sql = to_sql(arel, binds)
- cache_sql(sql, binds) { super(sql, name, binds) }
+ cache_sql(sql, binds) { super(sql, name, binds, preparable: visitor.preparable) }
else
super
end
diff --git a/activerecord/lib/active_record/querying.rb b/activerecord/lib/active_record/querying.rb
index 5259797223..de5b42e987 100644
--- a/activerecord/lib/active_record/querying.rb
+++ b/activerecord/lib/active_record/querying.rb
@@ -35,8 +35,8 @@ module ActiveRecord
#
# Post.find_by_sql ["SELECT title FROM posts WHERE author = ? AND created > ?", author_id, start_date]
# Post.find_by_sql ["SELECT body FROM comments WHERE author = :user_id OR approved_by = :user_id", { :user_id => user_id }]
- def find_by_sql(sql, binds = [])
- result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds)
+ def find_by_sql(sql, binds = [], preparable: nil)
+ result_set = connection.select_all(sanitize_sql(sql), "#{name} Load", binds, preparable: preparable)
column_types = result_set.column_types.dup
columns_hash.each_key { |k| column_types.delete k }
message_bus = ActiveSupport::Notifications.instrumenter
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 90a6a466fd..a4280c5f33 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -249,13 +249,13 @@ module ActiveRecord
# Person.offset(3).third_to_last # returns the third-to-last object from OFFSET 3
# Person.where(["user_name = :u", { u: user_name }]).third_to_last
def third_to_last
- find_nth -3
+ find_nth(-3)
end
# Same as #third_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def third_to_last!
- find_nth! -3
+ find_nth!(-3)
end
# Find the second-to-last record.
@@ -265,13 +265,13 @@ module ActiveRecord
# Person.offset(3).second_to_last # returns the second-to-last object from OFFSET 3
# Person.where(["user_name = :u", { u: user_name }]).second_to_last
def second_to_last
- find_nth -2
+ find_nth(-2)
end
# Same as #second_to_last but raises ActiveRecord::RecordNotFound if no record
# is found.
def second_to_last!
- find_nth! -2
+ find_nth!(-2)
end
# Returns true if a record exists in the table that matches the +id+ or
diff --git a/activerecord/lib/active_record/statement_cache.rb b/activerecord/lib/active_record/statement_cache.rb
index f6b0efb88a..6c896ccea6 100644
--- a/activerecord/lib/active_record/statement_cache.rb
+++ b/activerecord/lib/active_record/statement_cache.rb
@@ -106,7 +106,7 @@ module ActiveRecord
sql = query_builder.sql_for bind_values, connection
- klass.find_by_sql sql, bind_values
+ klass.find_by_sql(sql, bind_values, preparable: true)
end
alias :call :execute
end
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index a376e2a17f..f0aa4521b5 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -19,7 +19,7 @@ module ActiveRecord
relation = build_relation(finder_class, table, attribute, value)
if record.persisted? && finder_class.primary_key.to_s != attribute.to_s
if finder_class.primary_key
- relation = relation.where.not(finder_class.primary_key => record.id)
+ relation = relation.where.not(finder_class.primary_key => record.id_was)
else
raise UnknownPrimaryKey.new(finder_class, "Can not validate uniqueness for persisted record without primary key.")
end
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index ad5ca70f36..c918cbdef5 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -374,6 +374,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 10, DeveloperCalledJamis.unscoped { DeveloperCalledJamis.poor }.length
end
+ def test_default_scope_with_joins
+ assert_equal Comment.where(post_id: SpecialPostWithDefaultScope.pluck(:id)).count,
+ Comment.joins(:special_post_with_default_scope).count
+ assert_equal Comment.where(post_id: Post.pluck(:id)).count,
+ Comment.joins(:post).count
+ end
+
+ def test_unscoped_with_joins_should_not_have_default_scope
+ assert_equal SpecialPostWithDefaultScope.unscoped { Comment.joins(:special_post_with_default_scope).to_a },
+ Comment.joins(:post).to_a
+ end
+
def test_default_scope_select_ignored_by_aggregations
assert_equal DeveloperWithSelect.all.to_a.count, DeveloperWithSelect.count
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 7502a55391..e601c53dbf 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -469,4 +469,15 @@ class UniquenessValidationTest < ActiveRecord::TestCase
assert_match(/\AUnknown primary key for table dashboards in model/, e.message)
assert_match(/Can not validate uniqueness for persisted record without primary key.\z/, e.message)
end
+
+ def test_validate_uniqueness_ignores_itself_when_primary_key_changed
+ Topic.validates_uniqueness_of(:title)
+
+ t = Topic.new("title" => "This is a unique title")
+ assert t.save, "Should save t as unique"
+
+ t.id += 1
+ assert t.valid?, "Should be valid"
+ assert t.save, "Should still save t as unique"
+ end
end
diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb
index b38b17e90e..dcc5c5a310 100644
--- a/activerecord/test/models/comment.rb
+++ b/activerecord/test/models/comment.rb
@@ -14,6 +14,7 @@ class Comment < ActiveRecord::Base
has_many :ratings
belongs_to :first_post, :foreign_key => :post_id
+ belongs_to :special_post_with_default_scope, foreign_key: :post_id
has_many :children, :class_name => 'Comment', :foreign_key => :parent_id
belongs_to :parent, :class_name => 'Comment', :counter_cache => :children_count