aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock3
-rw-r--r--actioncable/README.md53
-rw-r--r--actioncable/actioncable.gemspec1
-rw-r--r--actioncable/lib/action_cable/channel/periodic_timers.rb2
-rw-r--r--actioncable/lib/action_cable/connection/base.rb2
-rw-r--r--actioncable/lib/action_cable/connection/identification.rb2
-rw-r--r--actioncable/lib/action_cable/process/logging.rb3
-rw-r--r--actioncable/lib/action_cable/server/base.rb5
-rw-r--r--actioncable/lib/action_cable/server/worker.rb55
-rw-r--r--actioncable/test/connection/authorization_test.rb1
-rw-r--r--actioncable/test/connection/base_test.rb1
-rw-r--r--actioncable/test/connection/cross_site_forgery_test.rb1
-rw-r--r--actioncable/test/connection/string_identifier_test.rb1
-rw-r--r--actioncable/test/connection/subscriptions_test.rb1
-rw-r--r--actioncable/test/test_helper.rb5
-rw-r--r--actioncable/test/worker_test.rb2
-rw-r--r--actionpack/CHANGELOG.md16
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb7
-rw-r--r--actionpack/lib/action_controller/test_case.rb16
-rw-r--r--actionpack/lib/action_dispatch.rb1
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb9
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb6
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_dispatch/testing/assertion_response.rb49
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb28
-rw-r--r--actionpack/test/abstract_unit.rb6
-rw-r--r--actionpack/test/assertions/response_assertions_test.rb19
-rw-r--r--actionpack/test/controller/parameters/accessors_test.rb6
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb2
-rw-r--r--actionpack/test/controller/routing_test.rb12
-rw-r--r--actionpack/test/dispatch/response_test.rb21
-rw-r--r--actionpack/test/dispatch/static_test.rb4
-rw-r--r--actionview/CHANGELOG.md8
-rw-r--r--actionview/lib/action_view/flows.rb2
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb2
-rw-r--r--actionview/lib/action_view/lookup_context.rb2
-rw-r--r--actionview/lib/action_view/rendering.rb2
-rw-r--r--actionview/lib/action_view/template/types.rb29
-rw-r--r--actionview/test/template/capture_helper_test.rb13
-rw-r--r--activerecord/CHANGELOG.md26
-rw-r--r--activerecord/lib/active_record/associations.rb1
-rw-r--r--activerecord/lib/active_record/associations/join_dependency/join_association.rb3
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb21
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb8
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb9
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb10
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb21
-rw-r--r--activerecord/lib/active_record/core.rb2
-rw-r--r--activerecord/lib/active_record/enum.rb2
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb5
-rw-r--r--activerecord/lib/active_record/migration.rb65
-rw-r--r--activerecord/lib/active_record/railties/databases.rake6
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/merger.rb3
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb14
-rw-r--r--activerecord/lib/active_record/schema_migration.rb2
-rw-r--r--activerecord/lib/active_record/scoping.rb28
-rw-r--r--activerecord/lib/active_record/scoping/default.rb4
-rw-r--r--activerecord/lib/active_record/secure_token.rb25
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb9
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb8
-rw-r--r--activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb5
-rw-r--r--activerecord/test/cases/base_test.rb5
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/defaults_test.rb26
-rw-r--r--activerecord/test/cases/fixtures_test.rb4
-rw-r--r--activerecord/test/cases/migration_test.rb30
-rw-r--r--activerecord/test/cases/relation/merging_test.rb7
-rw-r--r--activerecord/test/cases/relation/or_test.rb4
-rw-r--r--activerecord/test/cases/scoping/default_scoping_test.rb14
-rw-r--r--activerecord/test/cases/scoping/relation_scoping_test.rb20
-rw-r--r--activerecord/test/cases/secure_token_test.rb14
-rw-r--r--activerecord/test/fixtures/naked/yml/trees.yml3
-rw-r--r--activerecord/test/models/post.rb7
-rw-r--r--activerecord/test/models/user.rb3
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb7
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb37
-rw-r--r--activerecord/test/schema/schema.rb1
-rw-r--r--activesupport/CHANGELOG.md8
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb12
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb6
-rw-r--r--activesupport/test/caching_test.rb8
-rw-r--r--guides/assets/images/belongs_to.pngbin26076 -> 25803 bytes
-rw-r--r--guides/assets/images/has_many.pngbin28988 -> 28919 bytes
-rw-r--r--guides/source/action_controller_overview.md8
-rw-r--r--guides/source/active_record_postgresql.md4
-rw-r--r--guides/source/active_record_querying.md32
-rw-r--r--guides/source/association_basics.md476
-rw-r--r--railties/CHANGELOG.md6
-rw-r--r--railties/lib/rails/commands/server.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb5
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb5
-rw-r--r--railties/lib/rails/test_unit/minitest_plugin.rb3
-rw-r--r--railties/lib/rails/test_unit/testing.rake7
-rw-r--r--railties/test/application/rake_test.rb4
-rw-r--r--railties/test/application/test_runner_test.rb11
-rw-r--r--railties/test/commands/server_test.rb9
104 files changed, 943 insertions, 582 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index e56ad217d6..bcedeff385 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -31,7 +31,6 @@ PATH
specs:
actioncable (5.0.0.beta1)
actionpack (= 5.0.0.beta1)
- celluloid (~> 0.17.2)
coffee-rails (~> 4.1.0)
em-hiredis (~> 0.3.0)
faye-websocket (~> 0.10.0)
@@ -145,7 +144,7 @@ GEM
eventmachine (~> 1.0)
hiredis (~> 0.5.0)
erubis (2.7.0)
- eventmachine (1.0.8)
+ eventmachine (1.0.9.1)
execjs (2.6.0)
faye-websocket (0.10.2)
eventmachine (>= 0.12.0)
diff --git a/actioncable/README.md b/actioncable/README.md
index c7420d48bc..636f9935cf 100644
--- a/actioncable/README.md
+++ b/actioncable/README.md
@@ -298,11 +298,12 @@ See the [rails/actioncable-examples](http://github.com/rails/actioncable-example
## Configuration
-Action Cable has two required configurations: the Redis connection and specifying allowed request origins.
+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).
### Redis
-By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/redis/cable.yml')`. The file must follow the following format:
+By default, `ActionCable::Server::Base` will look for a configuration file in `Rails.root.join('config/redis/cable.yml')`.
+This file must specify a Redis url for each Rails environment. It may use the following format:
```yaml
production: &production
@@ -312,8 +313,7 @@ development: &development
test: *development
```
-This format allows you to specify one configuration per Rails environment. 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 Redis config file in a Rails initializer with something like:
```ruby
Rails.application.paths.add "config/redis/cable", with: "somewhere/else/cable.yml"
@@ -327,29 +327,31 @@ Action Cable will only accept requests from specified origins, which are passed
ActionCable.server.config.allowed_request_origins = ['http://rubyonrails.com', /http:\/\/ruby.*/]
```
+When running in the development environment, this defaults to "http://localhost:3000".
+
To disable and allow requests from any origin:
```ruby
ActionCable.server.config.disable_request_forgery_protection = true
```
-By default, Action Cable allows all requests from localhost:3000 when running in the development environment.
+### Consumer Configuration
-### Other Configurations
+Once you have decided how to run your cable server (see below), you must provide the server url (or path) to your client-side setup.
+There are two ways you can do this.
-The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
+The first is to simply pass it in when creating your consumer. For a standalone server,
+this would be something like: `App.cable = ActionCable.createConsumer("ws://example.com:28080")`, and for an in-app server,
+something like: `App.cable = ActionCable.createConsumer("/cable")`.
-```ruby
-ActionCable.server.config.log_tags = [
- -> request { request.env['bc.account_id'] || "no-account" },
- :action_cable,
- -> request { request.uuid }
-]
-```
+The second option is to pass the server url through the `action_cable_meta_tag` in your layout.
+This uses a url or path typically set via `config.action_cable.url` in the environment configuration files, or defaults to "/cable".
-Your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
+This method is especially useful if your websocket url might change between environments. If you host your production server via https, you will need to use the wss scheme
for your ActionCable server, but development might remain http and use the ws scheme. You might use localhost in development and your
-domain in production. In any case, to vary the websocket url between environments, add the following configuration to each environment:
+domain in production.
+
+In any case, to vary the websocket url between environments, add the following configuration to each environment:
```ruby
config.action_cable.url = "ws://example.com:28080"
@@ -367,6 +369,18 @@ And finally, create your consumer like so:
App.cable = ActionCable.createConsumer()
```
+### Other Configurations
+
+The other common option to configure is the log tags applied to the per-connection logger. Here's close to what we're using in Basecamp:
+
+```ruby
+ActionCable.server.config.log_tags = [
+ -> request { request.env['bc.account_id'] || "no-account" },
+ :action_cable,
+ -> request { request.uuid }
+]
+```
+
For a full list of all configuration options, see the `ActionCable::Server::Configuration` class.
Also note that your server must provide at least the same number of database connections as you have workers. The default worker pool is set to 100, so that means you have to make at least that available. You can change that in `config/database.yml` through the `pool` attribute.
@@ -394,8 +408,7 @@ Then you start the server using a binstub in bin/cable ala:
bundle exec puma -p 28080 cable/config.ru
```
-The above will start a cable server on port 28080. Remember to point your client-side setup against that using something like:
-`App.cable = ActionCable.createConsumer("ws://basecamp.dev:28080")`.
+The above will start a cable server on port 28080.
### In app
@@ -408,8 +421,6 @@ Example::Application.routes.draw do
end
```
-You can use `App.cable = ActionCable.createConsumer()` to connect to the cable server if `action_cable_meta_tag` is included in the layout. A custom path is specified as first argument to `createConsumer` (e.g. `App.cable = ActionCable.createConsumer("/websocket")`).
-
For every instance of your server you create and for every worker your server spawns, you will also have a new instance of ActionCable, but the use of Redis keeps messages synced across connections.
### Notes
@@ -427,7 +438,7 @@ messages back and forth over the WebSocket cable connection. This dependency may
be alleviated in the future, but for the moment that's what it is. So be sure to have
Redis installed and running.
-The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [celluloid](https://github.com/celluloid/celluloid).
+The Ruby side of things is built on top of [faye-websocket](https://github.com/faye/faye-websocket-ruby) and [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby).
## Deployment
diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec
index 74c21bd24d..a04fc932aa 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -23,7 +23,6 @@ Gem::Specification.new do |s|
s.add_dependency 'coffee-rails', '~> 4.1.0'
s.add_dependency 'faye-websocket', '~> 0.10.0'
s.add_dependency 'websocket-driver', '~> 0.6.1'
- s.add_dependency 'celluloid', '~> 0.17.2'
s.add_dependency 'em-hiredis', '~> 0.3.0'
s.add_dependency 'redis', '~> 3.0'
diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb
index 25fe8e5e54..7f0fb37afc 100644
--- a/actioncable/lib/action_cable/channel/periodic_timers.rb
+++ b/actioncable/lib/action_cable/channel/periodic_timers.rb
@@ -28,7 +28,7 @@ module ActionCable
def start_periodic_timers
self.class.periodic_timers.each do |callback, options|
active_periodic_timers << EventMachine::PeriodicTimer.new(options[:every]) do
- connection.worker_pool.async.run_periodic_timer(self, callback)
+ connection.worker_pool.async_run_periodic_timer(self, callback)
end
end
end
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index 977856d656..a8cfdf90f3 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -103,7 +103,7 @@ module ActionCable
# Invoke a method on the connection asynchronously through the pool of thread workers.
def send_async(method, *arguments)
- worker_pool.async.invoke(self, method, *arguments)
+ worker_pool.async_invoke(self, method, *arguments)
end
# Return a basic hash of statistics for the connection keyed with `identifier`, `started_at`, and `subscriptions`.
diff --git a/actioncable/lib/action_cable/connection/identification.rb b/actioncable/lib/action_cable/connection/identification.rb
index 2d75ff8d6d..885ff3f102 100644
--- a/actioncable/lib/action_cable/connection/identification.rb
+++ b/actioncable/lib/action_cable/connection/identification.rb
@@ -11,7 +11,7 @@ module ActionCable
end
class_methods do
- # Mark a key as being a connection identifier index that can then used to find the specific connection again later.
+ # Mark a key as being a connection identifier index that can then be used to find the specific connection again later.
# Common identifiers are current_user and current_account, but could be anything really.
#
# Note that anything marked as an identifier will automatically create a delegate by the same name on any
diff --git a/actioncable/lib/action_cable/process/logging.rb b/actioncable/lib/action_cable/process/logging.rb
index 72b1a080d1..dce637b3ca 100644
--- a/actioncable/lib/action_cable/process/logging.rb
+++ b/actioncable/lib/action_cable/process/logging.rb
@@ -1,10 +1,7 @@
require 'action_cable/server'
require 'eventmachine'
-require 'celluloid'
EM.error_handler do |e|
puts "Error raised inside the event loop: #{e.message}"
puts e.backtrace.join("\n")
end
-
-Celluloid.logger = ActionCable.server.logger
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
index 740e4b301e..3785bbd154 100644
--- a/actioncable/lib/action_cable/server/base.rb
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -1,6 +1,3 @@
-# FIXME: Cargo culted fix from https://github.com/celluloid/celluloid-pool/issues/10
-require 'celluloid/current'
-
require 'em-hiredis'
module ActionCable
@@ -39,7 +36,7 @@ module ActionCable
# The thread worker pool for handling all the connection work on this server. Default size is set by config.worker_pool_size.
def worker_pool
- @worker_pool ||= ActionCable::Server::Worker.pool(size: config.worker_pool_size)
+ @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size)
end
# Requires and returns a hash of all the channel class constants keyed by name.
diff --git a/actioncable/lib/action_cable/server/worker.rb b/actioncable/lib/action_cable/server/worker.rb
index e063b2a2e1..3b6c6d44a1 100644
--- a/actioncable/lib/action_cable/server/worker.rb
+++ b/actioncable/lib/action_cable/server/worker.rb
@@ -1,39 +1,68 @@
-require 'celluloid'
require 'active_support/callbacks'
+require 'active_support/core_ext/module/attribute_accessors_per_thread'
+require 'concurrent'
module ActionCable
module Server
# Worker used by Server.send_async to do connection work in threads. Only for internal use.
class Worker
include ActiveSupport::Callbacks
- include Celluloid
- attr_reader :connection
+ thread_mattr_accessor :connection
define_callbacks :work
include ActiveRecordConnectionManagement
+ def initialize(max_size: 5)
+ @pool = Concurrent::ThreadPoolExecutor.new(
+ min_threads: 1,
+ max_threads: max_size,
+ max_queue: 0,
+ )
+ end
+
+ def async_invoke(receiver, method, *args)
+ @pool.post do
+ invoke(receiver, method, *args)
+ end
+ end
+
def invoke(receiver, method, *args)
- @connection = receiver
+ begin
+ self.connection = receiver
- run_callbacks :work do
- receiver.send method, *args
+ run_callbacks :work do
+ receiver.send method, *args
+ end
+ rescue Exception => e
+ logger.error "There was an exception - #{e.class}(#{e.message})"
+ logger.error e.backtrace.join("\n")
+
+ receiver.handle_exception if receiver.respond_to?(:handle_exception)
+ ensure
+ self.connection = nil
end
- rescue Exception => e
- logger.error "There was an exception - #{e.class}(#{e.message})"
- logger.error e.backtrace.join("\n")
+ end
- receiver.handle_exception if receiver.respond_to?(:handle_exception)
+ def async_run_periodic_timer(channel, callback)
+ @pool.post do
+ run_periodic_timer(channel, callback)
+ end
end
def run_periodic_timer(channel, callback)
- @connection = channel.connection
+ begin
+ self.connection = channel.connection
- run_callbacks :work do
- callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
+ run_callbacks :work do
+ callback.respond_to?(:call) ? channel.instance_exec(&callback) : channel.send(callback)
+ end
+ ensure
+ self.connection = nil
end
end
private
+
def logger
ActionCable.server.logger
end
diff --git a/actioncable/test/connection/authorization_test.rb b/actioncable/test/connection/authorization_test.rb
index 68668b2835..87d0e79ef3 100644
--- a/actioncable/test/connection/authorization_test.rb
+++ b/actioncable/test/connection/authorization_test.rb
@@ -10,7 +10,6 @@ class ActionCable::Connection::AuthorizationTest < ActionCable::TestCase
end
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/connection/base_test.rb b/actioncable/test/connection/base_test.rb
index da6041db4a..182562db82 100644
--- a/actioncable/test/connection/base_test.rb
+++ b/actioncable/test/connection/base_test.rb
@@ -14,7 +14,6 @@ class ActionCable::Connection::BaseTest < ActionCable::TestCase
end
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/connection/cross_site_forgery_test.rb b/actioncable/test/connection/cross_site_forgery_test.rb
index d445e08f2a..a29f65fb97 100644
--- a/actioncable/test/connection/cross_site_forgery_test.rb
+++ b/actioncable/test/connection/cross_site_forgery_test.rb
@@ -6,7 +6,6 @@ class ActionCable::Connection::CrossSiteForgeryTest < ActionCable::TestCase
class Connection < ActionCable::Connection::Base
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/connection/string_identifier_test.rb b/actioncable/test/connection/string_identifier_test.rb
index ab69df57b3..9d0bda83ef 100644
--- a/actioncable/test/connection/string_identifier_test.rb
+++ b/actioncable/test/connection/string_identifier_test.rb
@@ -10,7 +10,6 @@ class ActionCable::Connection::StringIdentifierTest < ActionCable::TestCase
end
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/connection/subscriptions_test.rb b/actioncable/test/connection/subscriptions_test.rb
index 4f6760827e..62e41484fe 100644
--- a/actioncable/test/connection/subscriptions_test.rb
+++ b/actioncable/test/connection/subscriptions_test.rb
@@ -5,7 +5,6 @@ class ActionCable::Connection::SubscriptionsTest < ActionCable::TestCase
attr_reader :websocket
def send_async(method, *args)
- # Bypass Celluloid
send method, *args
end
end
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
index 12dcd98402..325305939f 100644
--- a/actioncable/test/test_helper.rb
+++ b/actioncable/test/test_helper.rb
@@ -14,11 +14,6 @@ require 'rack/mock'
# Require all the stubs and models
Dir[File.dirname(__FILE__) + '/stubs/*.rb'].each {|file| require file }
-$CELLULOID_DEBUG = false
-$CELLULOID_TEST = false
-require 'celluloid'
-Celluloid.logger = Logger.new(StringIO.new)
-
require 'faye/websocket'
class << Faye::WebSocket
remove_method :ensure_reactor_running
diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb
index 69c4b6529d..9911a3b98b 100644
--- a/actioncable/test/worker_test.rb
+++ b/actioncable/test/worker_test.rb
@@ -17,8 +17,6 @@ class WorkerTest < ActiveSupport::TestCase
end
setup do
- Celluloid.boot
-
@worker = ActionCable::Server::Worker.new
@receiver = Receiver.new
end
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index b93ae8f8ff..e31838db2f 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,8 +1,12 @@
+* Provide the name of HTTP Status code in assertions.
+
+ *Sean Collins*
+
* More explicit error message when running `rake routes`. `CONTROLLER` argument
can now be supplied in different ways:
- `Rails::WelcomeController`, `Rails::Welcome`, `rails/welcome`
+ `Rails::WelcomeController`, `Rails::Welcome`, `rails/welcome`.
- Fixes #22918
+ Fixes #22918.
*Edouard Chin*
@@ -10,7 +14,7 @@
helper methods. An `ArgumentError` will be raised if the passed parameters
are not secure.
- Fixes #22832
+ Fixes #22832.
*Prathamesh Sonpatki*
@@ -26,7 +30,7 @@
or unfiltered values based on from where it is called, `to_h` or `to_unsafe_h`
respectively.
- Fixes #22841
+ Fixes #22841.
*Prathamesh Sonpatki*
@@ -76,7 +80,7 @@
https://github.com/rails/rails/pull/18334#issuecomment-69234050 we want
`protect_from_forgery` to default to `prepend: false`.
- `protect_from_forgery` will now be insterted into the callback chain at the
+ `protect_from_forgery` will now be inserted into the callback chain at the
point it is called in your application. This is useful for cases where you
want to `protect_from_forgery` after you perform required authentication
callbacks or other callbacks that are required to run after forgery protection.
@@ -350,7 +354,7 @@
* Allow `Bearer` as token-keyword in `Authorization-Header`.
- Aditionally to `Token`, the keyword `Bearer` is acceptable as a keyword
+ Additionally to `Token`, the keyword `Bearer` is acceptable as a keyword
for the auth-token. The `Bearer` keyword is described in the original
OAuth RFC and used in libraries like Angular-JWT.
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 4cd67a85cc..5cbf4157a4 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -109,7 +109,8 @@ module ActionController
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
- delegate :keys, :key?, :has_key?, :empty?, :include?, :inspect, to: :@parameters
+ delegate :keys, :key?, :has_key?, :empty?, :include?, :inspect,
+ :as_json, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action'
@@ -419,7 +420,7 @@ module ActionController
# params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
- def fetch(key, *args, &block)
+ def fetch(key, *args)
convert_value_to_parameters(
@parameters.fetch(key) {
if block_given?
@@ -514,7 +515,7 @@ module ActionController
# to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key
# and return the result of block.
- def delete(key, &block)
+ def delete(key)
convert_value_to_parameters(@parameters.delete(key))
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index c55720859e..b43bb9dc17 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -8,6 +8,10 @@ require 'rails-dom-testing'
module ActionController
# :stopdoc:
+ class Metal
+ include Testing::Functional
+ end
+
# ActionController::TestCase will be deprecated and moved to a gem in Rails 5.1.
# Please use ActionDispatch::IntegrationTest going forward.
class TestRequest < ActionDispatch::TestRequest #:nodoc:
@@ -455,16 +459,16 @@ module ActionController
parameters = nil
end
- if parameters.present? || session.present? || flash.present?
+ if parameters || session || flash
non_kwarg_request_warning
end
end
- if body.present?
+ if body
@request.set_header 'RAW_POST_DATA', body
end
- if http_method.present?
+ if http_method
http_method = http_method.to_s.upcase
else
http_method = "GET"
@@ -472,16 +476,12 @@ module ActionController
parameters ||= {}
- if format.present?
+ if format
parameters[:format] = format
end
@html_document = nil
- unless @controller.respond_to?(:recycle!)
- @controller.extend(Testing::Functional)
- end
-
self.cookies.update @request.cookies
self.cookies.update_cookies_from_jar
@request.set_header 'HTTP_COOKIE', cookies.to_header
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 7bc6575ccd..1e4df07d6e 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -95,6 +95,7 @@ module ActionDispatch
autoload :TestProcess
autoload :TestRequest
autoload :TestResponse
+ autoload :AssertionResponse
end
end
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 30ade14c26..842dfa5827 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -91,7 +91,7 @@ module ActionDispatch
DATE = 'Date'.freeze
LAST_MODIFIED = "Last-Modified".freeze
- SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate])
+ SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public private must-revalidate])
def cache_control_segments
if cache_control = _cache_control
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 29cf821090..5427425ef7 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -259,7 +259,7 @@ module ActionDispatch
end
# Returns the IP address of client as a +String+,
- # usually set by the RemoteIp middleware.
+ # usually set by the RemoteIp middleware.
def remote_ip
@remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s
end
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 9b11111a67..14f86c7c07 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -232,7 +232,7 @@ module ActionDispatch # :nodoc:
end
# Sets the HTTP character set. In case of nil parameter
- # it sets the charset to utf-8.
+ # it sets the charset to utf-8.
#
# response.charset = 'utf-16' # => 'utf-16'
# response.charset = nil # => 'utf-8'
@@ -412,6 +412,13 @@ module ActionDispatch # :nodoc:
end
def before_sending
+ # Normally we've already committed by now, but it's possible
+ # (e.g., if the controller action tries to read back its own
+ # response) to get here before that. In that case, we must force
+ # an "early" commit: we're about to freeze the headers, so this is
+ # our last chance.
+ commit! unless committed?
+
headers.freeze
request.commit_cookie_jar! unless committed?
end
diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb
index ea9ab3821d..41c220236a 100644
--- a/actionpack/lib/action_dispatch/middleware/static.rb
+++ b/actionpack/lib/action_dispatch/middleware/static.rb
@@ -27,7 +27,7 @@ module ActionDispatch
# in the server's `public/` directory (see Static#call).
def match?(path)
path = ::Rack::Utils.unescape_path path
- return false unless path.valid_encoding?
+ return false unless valid_path?(path)
path = Rack::Utils.clean_path_info path
paths = [path, "#{path}#{ext}", "#{path}/#{@index}#{ext}"]
@@ -94,6 +94,10 @@ module ActionDispatch
false
end
end
+
+ def valid_path?(path)
+ path.valid_encoding? && !path.include?("\0")
+ end
end
# This middleware will attempt to return the contents of a file's body from
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 9e7fcbd849..42890225fa 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -109,7 +109,7 @@ module ActionDispatch
@delegate.values
end
- # Writes given value to given key of the session.
+ # Writes given value to given key of the session.
def []=(key, value)
load_for_write!
@delegate[key.to_s] = value
@@ -148,8 +148,8 @@ module ActionDispatch
@delegate.delete key.to_s
end
- # Returns value of given key from the session, or raises +KeyError+
- # if can't find given key in case of not setted dafault value.
+ # Returns value of the given key from the session, or raises +KeyError+
+ # if can't find the given key and no default value is set.
# Returns default value if specified.
#
# session.fetch(:foo)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 522012063d..f57d88d1a5 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -187,7 +187,7 @@ module ActionDispatch
# Get all the symbol nodes followed by literals that are not the
# dummy node.
symbols = ast.find_all { |n|
- n.cat? && n.left.symbol? && n.right.cat? && n.right.left.literal?
+ n.cat? && n.left.symbol? && n.right.cat?
}.map(&:left)
# Get all the symbol nodes preceded by literals.
diff --git a/actionpack/lib/action_dispatch/testing/assertion_response.rb b/actionpack/lib/action_dispatch/testing/assertion_response.rb
new file mode 100644
index 0000000000..3fb81ff083
--- /dev/null
+++ b/actionpack/lib/action_dispatch/testing/assertion_response.rb
@@ -0,0 +1,49 @@
+module ActionDispatch
+ # This is a class that abstracts away an asserted response.
+ # It purposely does not inherit from Response, because it doesn't need it.
+ # That means it does not have headers or a body.
+ #
+ # As an input to the initializer, we take a Fixnum, a String, or a Symbol.
+ # If it's a Fixnum or String, we figure out what its symbolized name.
+ # If it's a Symbol, we figure out what its corresponding code is.
+ # The resulting code will be a Fixnum, for real HTTP codes, and it will
+ # be a String for the pseudo-HTTP codes, such as:
+ # :success, :missing, :redirect and :error
+ class AssertionResponse
+ attr_reader :code, :name
+
+ GENERIC_RESPONSE_CODES = { # :nodoc:
+ success: "2XX",
+ missing: "404",
+ redirect: "3XX",
+ error: "5XX"
+ }
+
+ def initialize(code_or_name)
+ if code_or_name.is_a?(Symbol)
+ @name = code_or_name
+ @code = code_from_name(code_or_name)
+ else
+ @name = name_from_code(code_or_name)
+ @code = code_or_name
+ end
+
+ raise ArgumentError, "Invalid response name: #{name}" if @code.nil?
+ raise ArgumentError, "Invalid response code: #{code}" if @name.nil?
+ end
+
+ def code_and_name
+ "#{code}: #{name}"
+ end
+
+ private
+
+ def code_from_name(name)
+ GENERIC_RESPONSE_CODES[name] || Rack::Utils::SYMBOL_TO_STATUS_CODE[name]
+ end
+
+ def name_from_code(code)
+ GENERIC_RESPONSE_CODES.invert[code] || Rack::Utils::HTTP_STATUS_CODES[code]
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb
index c138660a21..cd55b7d975 100644
--- a/actionpack/lib/action_dispatch/testing/assertions/response.rb
+++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb
@@ -1,4 +1,3 @@
-
module ActionDispatch
module Assertions
# A small suite of assertions that test responses from \Rails applications.
@@ -29,18 +28,10 @@ module ActionDispatch
def assert_response(type, message = nil)
message ||= generate_response_message(type)
- if Symbol === type
- if [:success, :missing, :redirect, :error].include?(type)
- assert @response.send(RESPONSE_PREDICATES[type]), message
- else
- code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
- if code.nil?
- raise ArgumentError, "Invalid response type :#{type}"
- end
- assert_equal code, @response.response_code, message
- end
+ if RESPONSE_PREDICATES.keys.include?(type)
+ assert @response.send(RESPONSE_PREDICATES[type]), message
else
- assert_equal type, @response.response_code, message
+ assert_equal AssertionResponse.new(type).code, @response.response_code, message
end
end
@@ -85,8 +76,9 @@ module ActionDispatch
end
end
- def generate_response_message(type, code = @response.response_code)
- "Expected response to be a <#{type}>, but was a <#{code}>"
+ def generate_response_message(expected, actual = @response.response_code)
+ "Expected response to be a <#{code_with_name(expected)}>,"\
+ " but was a <#{code_with_name(actual)}>"
.concat location_if_redirected
end
@@ -95,6 +87,14 @@ module ActionDispatch
location = normalize_argument_to_redirection(@response.location)
" redirect to <#{location}>"
end
+
+ def code_with_name(code_or_name)
+ if RESPONSE_PREDICATES.values.include?("#{code_or_name}?".to_sym)
+ code_or_name = RESPONSE_PREDICATES.invert["#{code_or_name}?".to_sym]
+ end
+
+ AssertionResponse.new(code_or_name).code_and_name
+ end
end
end
end
diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb
index 604ba267d6..1ef10ff956 100644
--- a/actionpack/test/abstract_unit.rb
+++ b/actionpack/test/abstract_unit.rb
@@ -20,7 +20,11 @@ rescue LoadError
puts "'drb/unix' is not available"
end
-PROCESS_COUNT = (ENV['N'] || 4).to_i
+if ENV['TRAVIS']
+ PROCESS_COUNT = 0
+else
+ PROCESS_COUNT = (ENV['N'] || 4).to_i
+end
require 'active_support/testing/autorun'
require 'abstract_controller'
diff --git a/actionpack/test/assertions/response_assertions_test.rb b/actionpack/test/assertions/response_assertions_test.rb
index 841fa6aaad..579ce0ed29 100644
--- a/actionpack/test/assertions/response_assertions_test.rb
+++ b/actionpack/test/assertions/response_assertions_test.rb
@@ -74,7 +74,18 @@ module ActionDispatch
@response.status = 404
error = assert_raises(Minitest::Assertion) { assert_response :success }
- expected = "Expected response to be a <success>, but was a <404>"
+ expected = "Expected response to be a <2XX: success>,"\
+ " but was a <404: Not Found>"
+ assert_match expected, error.message
+ end
+
+ def test_error_message_shows_404_when_asserted_for_200
+ @response = ActionDispatch::Response.new
+ @response.status = 404
+
+ error = assert_raises(Minitest::Assertion) { assert_response 200 }
+ expected = "Expected response to be a <200: OK>,"\
+ " but was a <404: Not Found>"
assert_match expected, error.message
end
@@ -84,7 +95,8 @@ module ActionDispatch
@response.location = 'http://test.host/posts/redirect/1'
error = assert_raises(Minitest::Assertion) { assert_response :success }
- expected = "Expected response to be a <success>, but was a <302>" \
+ expected = "Expected response to be a <2XX: success>,"\
+ " but was a <302: Found>" \
" redirect to <http://test.host/posts/redirect/1>"
assert_match expected, error.message
end
@@ -95,7 +107,8 @@ module ActionDispatch
@response.location = 'http://test.host/posts/redirect/2'
error = assert_raises(Minitest::Assertion) { assert_response 301 }
- expected = "Expected response to be a <301>, but was a <302>" \
+ expected = "Expected response to be a <301: Moved Permanently>,"\
+ " but was a <302: Found>" \
" redirect to <http://test.host/posts/redirect/2>"
assert_match expected, error.message
end
diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb
index 97875c3cbb..a8f4d877a6 100644
--- a/actionpack/test/controller/parameters/accessors_test.rb
+++ b/actionpack/test/controller/parameters/accessors_test.rb
@@ -27,6 +27,12 @@ class ParametersAccessorsTest < ActiveSupport::TestCase
assert_not @params[:person][:name].permitted?
end
+ test "as_json returns the JSON representation of the parameters hash" do
+ assert_not @params.as_json.key? "parameters"
+ assert_not @params.as_json.key? "permitted"
+ assert @params.as_json.key? "person"
+ end
+
test "each carries permitted status" do
@params.permit!
@params.each { |key, value| assert(value.permitted?) if key == "person" }
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
index 896bda2597..3299f2d9d0 100644
--- a/actionpack/test/controller/parameters/parameters_permit_test.rb
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -334,7 +334,7 @@ class ParametersPermitTest < ActiveSupport::TestCase
assert_not company.dupped
end
- test "included? returns true when the key is present" do
+ test "include? returns true when the key is present" do
assert @params.include? :person
assert @params.include? 'person'
assert_not @params.include? :gorilla
diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb
index a39fede5b9..178cd41922 100644
--- a/actionpack/test/controller/routing_test.rb
+++ b/actionpack/test/controller/routing_test.rb
@@ -94,6 +94,18 @@ class LegacyRouteSetTests < ActiveSupport::TestCase
assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
end
+ def test_symbols_with_dashes_reversed
+ rs.draw do
+ get ':artist(/omg-:song)', :to => lambda { |env|
+ resp = ActiveSupport::JSON.encode ActionDispatch::Request.new(env).path_parameters
+ [200, {}, [resp]]
+ }
+ end
+
+ hash = ActiveSupport::JSON.decode get(URI('http://example.org/journey/omg-faithfully'))
+ assert_equal({"artist"=>"journey", "song"=>"faithfully"}, hash)
+ end
+
def test_id_with_dash
rs.draw do
get '/journey/:id', :to => lambda { |env|
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index c37679bc5f..43ee2efd9f 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -37,6 +37,27 @@ class ResponseTest < ActiveSupport::TestCase
assert_equal "closed stream", e.message
end
+ def test_read_body_during_action
+ @response.body = "Hello, World!"
+
+ # even though there's no explicitly set content-type,
+ assert_equal nil, @response.content_type
+
+ # after the action reads back @response.body,
+ assert_equal "Hello, World!", @response.body
+
+ # the response can be built.
+ status, headers, body = @response.to_a
+ assert_equal 200, status
+ assert_equal({
+ "Content-Type" => "text/html; charset=utf-8"
+ }, headers)
+
+ parts = []
+ body.each { |part| parts << part }
+ assert_equal ["Hello, World!"], parts
+ end
+
def test_response_body_encoding
body = ["hello".encode(Encoding::UTF_8)]
response = ActionDispatch::Response.new 200, {}, body
diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb
index 1da57ab50b..ea8b5e904e 100644
--- a/actionpack/test/dispatch/static_test.rb
+++ b/actionpack/test/dispatch/static_test.rb
@@ -40,6 +40,10 @@ module StaticTests
assert_equal "Hello, World!", get("/doorkeeper%E3E4".force_encoding('ASCII-8BIT')).body
end
+ def test_handles_urls_with_null_byte
+ assert_equal "Hello, World!", get("/doorkeeper%00").body
+ end
+
def test_sets_cache_control
app = assert_deprecated do
ActionDispatch::Static.new(DummyApp, @root, "public, max-age=60")
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index dbdd4378d0..98ac2c1c22 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,8 +1,14 @@
+* Create a new `ActiveSupport::SafeBuffer` instance when `content_for` is flushed.
+
+ Fixes #19890
+
+ *Yoong Kang Lim*
+
* Fix `collection_radio_buttons` hidden_field name and make it appear
before the actual input radio tags to make the real value override
the hidden when passed.
- Fixes #22773
+ Fixes #22773.
*Santiago Pastorino*
diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb
index ba24510e56..bc61920848 100644
--- a/actionview/lib/action_view/flows.rb
+++ b/actionview/lib/action_view/flows.rb
@@ -15,7 +15,7 @@ module ActionView
# Called by each renderer object to set the layout contents.
def set(key, value)
- @content[key] = value
+ @content[key] = ActiveSupport::SafeBuffer.new(value)
end
# Called by content_for
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 91e934cd64..cc54faa778 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -136,7 +136,7 @@ module ActionView
tag(
"link",
"rel" => tag_options[:rel] || "alternate",
- "type" => tag_options[:type] || Mime[type].to_s,
+ "type" => tag_options[:type] || Template::Types[type].to_s,
"title" => tag_options[:title] || type.to_s.upcase,
"href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
)
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index d3935788ef..63a3c4ea5e 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -67,7 +67,7 @@ module ActionView
def self.get(details)
if details[:formats]
details = details.dup
- details[:formats] &= Mime::SET.symbols
+ details[:formats] &= Template::Types.symbols
end
@details_keys[details] ||= new
end
diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb
index 8604637da2..3ca7f9d220 100644
--- a/actionview/lib/action_view/rendering.rb
+++ b/actionview/lib/action_view/rendering.rb
@@ -84,7 +84,7 @@ module ActionView
end
def rendered_format
- Mime[lookup_context.rendered_format]
+ Template::Types[lookup_context.rendered_format]
end
private
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index be45fcf742..c233d06ccb 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -5,19 +5,12 @@ module ActionView
class Template
class Types
class Type
- cattr_accessor :types
- self.types = Set.new
-
- def self.register(*t)
- types.merge(t.map(&:to_s))
- end
-
- register :html, :text, :js, :css, :xml, :json
+ SET = Struct.new(:symbols).new([ :html, :text, :js, :css, :xml, :json ])
def self.[](type)
- return type if type.is_a?(self)
-
- if type.is_a?(Symbol) || types.member?(type.to_s)
+ if type.is_a?(self)
+ type
+ else
new(type)
end
end
@@ -28,16 +21,18 @@ module ActionView
@symbol = symbol.to_sym
end
- delegate :to_s, :to_sym, :to => :symbol
+ def to_s
+ @symbol.to_s
+ end
alias to_str to_s
def ref
- to_sym || to_s
+ @symbol
end
+ alias to_sym ref
def ==(type)
- return false if type.blank?
- symbol.to_sym == type.to_sym
+ @symbol == type.to_sym unless type.blank?
end
end
@@ -52,6 +47,10 @@ module ActionView
def self.[](type)
type_klass[type]
end
+
+ def self.symbols
+ type_klass::SET.symbols
+ end
end
end
end
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index 1e099d482c..7e6761e580 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -148,6 +148,19 @@ class CaptureHelperTest < ActionView::TestCase
assert ! content_for?(:something_else)
end
+ def test_content_for_should_be_html_safe_after_flush_empty
+ assert ! content_for?(:title)
+ content_for :title do
+ content_tag(:p, 'title')
+ end
+ assert content_for(:title).html_safe?
+ content_for :title, "", flush: true
+ content_for(:title) do
+ content_tag(:p, 'title')
+ end
+ assert content_for(:title).html_safe?
+ end
+
def test_provide
assert !content_for?(:title)
provide :title, "hi"
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 56ac3b95b3..e14a9a972c 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,9 +1,21 @@
-* Support `:if` and `:unless` options in `has_secure_token`
+* Add expression support on the schema default.
- *Griffin Smith*
+ Example:
+
+ create_table :posts do |t|
+ t.datetime :published_at, default: -> { 'NOW()' }
+ end
+
+ *Ryuta Kamizono*
+
+* Fix regression when loading fixture files with symbol keys.
+
+ Fixes #22584.
+
+ *Yves Senn*
* Use `version` column as primary key for schema_migrations table because
- schema_migrations versions are guaranteed to be unique.
+ `schema_migrations` versions are guaranteed to be unique.
This makes it possible to use `update_attributes` on models that do
not have a primary key.
@@ -89,6 +101,14 @@
defaults without breaking existing migrations, or forcing them to be
rewritten through a deprecation cycle.
+ New migrations specify the Rails version they were written for:
+
+ class AddStatusToOrders < ActiveRecord::Migration[5.0]
+ def change
+ # ...
+ end
+ end
+
*Matthew Draper*, *Ravil Bayramgalin*
* Use bind params for `limit` and `offset`. This will generate significantly
diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb
index 462b3066ab..f6d8e8a342 100644
--- a/activerecord/lib/active_record/associations.rb
+++ b/activerecord/lib/active_record/associations.rb
@@ -1164,6 +1164,7 @@ module ActiveRecord
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
# Note that this operation instantly fires update SQL without waiting for the save or update call on the
# parent object, unless the parent object is a new record.
+ # This will also run validations and callbacks of associated object(s).
# [collection.delete(object, ...)]
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
# Objects will be in addition destroyed if they're associated with <tt>dependent: :destroy</tt>,
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 a6ad09a38a..be65cf318c 100644
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
@@ -74,9 +74,8 @@ module ActiveRecord
value = foreign_klass.base_class.name
column = klass.columns_hash[reflection.type.to_s]
- substitute = klass.connection.substitute_at(column)
binds << Relation::QueryAttribute.new(column.name, value, klass.type_for_attribute(column.name))
- constraint = constraint.and table[reflection.type].eq substitute
+ constraint = constraint.and table[reflection.type].eq(Arel::Nodes::BindParam.new)
end
joins << table.create_join(table, table.create_on(constraint), join_type)
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 0ac5e80119..7a2a1a0e33 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -106,7 +106,7 @@ module ActiveRecord
exec_query(sql, name, binds)
end
- # Returns the last auto-generated ID from the affected table.
+ # Executes an INSERT query and returns the new record's ID
#
# +id_value+ will be returned unless the value is nil, in
# which case the database will attempt to calculate the last inserted
@@ -115,9 +115,12 @@ module ActiveRecord
# If the next id was calculated in advance (as in Oracle), it should be
# passed in as +id_value+.
def insert(arel, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- insert_sql(to_sql(arel, binds), name, pk, id_value, sequence_name, binds)
+ sql, binds, pk, sequence_name = sql_for_insert(to_sql(arel, binds), pk, id_value, sequence_name, binds)
+ value = exec_insert(sql, name, binds, pk, sequence_name)
+ id_value || last_inserted_id(value)
end
alias create insert
+ alias insert_sql insert
# Executes the update statement and returns the number of rows affected.
def update(arel, name = nil, binds = [])
@@ -297,14 +300,15 @@ module ActiveRecord
# Inserts the given fixture into the table. Overridden in adapters that require
# something beyond a simple insert (eg. Oracle).
def insert_fixture(fixture, table_name)
- columns = schema_cache.columns_hash(table_name)
+ fixture = fixture.stringify_keys
+ columns = schema_cache.columns_hash(table_name)
binds = fixture.map do |name, value|
if column = columns[name]
type = lookup_cast_type_from_column(column)
Relation::QueryAttribute.new(name, value, type)
else
- raise Fixture::FixtureError, %(table "#{table_name}" has no column named "#{name}".)
+ raise Fixture::FixtureError, %(table "#{table_name}" has no column named #{name.inspect}.)
end
end
key_list = fixture.keys.map { |name| quote_column_name(name) }
@@ -352,13 +356,6 @@ module ActiveRecord
end
alias join_to_delete join_to_update
- # Executes an INSERT query and returns the new record's ID
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = [])
- sql, binds = sql_for_insert(sql, pk, id_value, sequence_name, binds)
- value = exec_insert(sql, name, binds, pk, sequence_name)
- id_value || last_inserted_id(value)
- end
-
protected
# Returns a subquery for the given key using the join information.
@@ -378,7 +375,7 @@ module ActiveRecord
end
def sql_for_insert(sql, pk, id_value, sequence_name, binds)
- [sql, binds]
+ [sql, binds, pk, sequence_name]
end
def last_inserted_id(result)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index bcc41acaa1..7e3760d34b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -102,9 +102,13 @@ module ActiveRecord
quote_table_name("#{table}.#{attr}")
end
- def quote_default_expression(value, column) #:nodoc:
- value = lookup_cast_type(column.sql_type).serialize(value)
- quote(value)
+ def quote_default_expression(value, column) # :nodoc:
+ if value.is_a?(Proc)
+ value.call
+ else
+ value = lookup_cast_type(column.sql_type).serialize(value)
+ quote(value)
+ end
end
def quoted_true
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
index 797662d07c..a95109fdae 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_dumper.rb
@@ -76,11 +76,17 @@ module ActiveRecord
def schema_default(column)
type = lookup_cast_type_from_column(column)
default = type.deserialize(column.default)
- unless default.nil?
+ if default.nil?
+ schema_expression(column)
+ else
type.type_cast_for_schema(default)
end
end
+ def schema_expression(column)
+ "-> { #{column.default_function.inspect} }" if column.default_function
+ end
+
def schema_collation(column)
column.collation.inspect if column.collation
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index dce34a208f..d9b42d4283 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -311,12 +311,6 @@ module ActiveRecord
{}
end
- # Returns a bind substitution value given a bind +column+
- # NOTE: The column param is currently being used by the sqlserver-adapter
- def substitute_at(column, _unused = 0)
- Arel::Nodes::BindParam.new
- end
-
# REFERENTIAL INTEGRITY ====================================
# Override to turn off referential integrity while executing <tt>&block</tt>.
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index d435d91102..3e84786be0 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -495,12 +495,16 @@ module ActiveRecord
end
# Returns an array of +Column+ objects for the table specified by +table_name+.
- def columns(table_name)#:nodoc:
+ def columns(table_name) # :nodoc:
sql = "SHOW FULL FIELDS FROM #{quote_table_name(table_name)}"
execute_and_free(sql, 'SCHEMA') do |result|
each_hash(result).map do |field|
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
+ if type_metadata.type == :datetime && field[:Default] == "CURRENT_TIMESTAMP"
+ new_column(field[:Field], nil, type_metadata, field[:Null] == "YES", field[:Default], field[:Collation])
+ else
+ new_column(field[:Field], field[:Default], type_metadata, field[:Null] == "YES", nil, field[:Collation])
+ end
end
end
end
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 11a151edd5..6c15facf3b 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -72,16 +72,6 @@ module ActiveRecord
end
end
- # Executes an INSERT query and returns the new record's ID
- def insert_sql(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil, binds = []) # :nodoc:
- unless pk
- # Extract the table from the insert sql. Yuck.
- table_ref = extract_table_ref_from_insert_sql(sql)
- pk = primary_key(table_ref) if table_ref
- end
- super
- end
-
# The internal PostgreSQL identifier of the money data type.
MONEY_COLUMN_TYPE_OID = 790 #:nodoc:
# The internal PostgreSQL identifier of the BYTEA data type.
@@ -162,12 +152,18 @@ module ActiveRecord
end
alias :exec_update :exec_delete
- def sql_for_insert(sql, pk, id_value, sequence_name, binds)
+ def sql_for_insert(sql, pk, id_value, sequence_name, binds) # :nodoc:
+ unless pk
+ # Extract the table from the insert sql. Yuck.
+ table_ref = extract_table_ref_from_insert_sql(sql)
+ pk = primary_key(table_ref) if table_ref
+ end
+
if pk && use_insert_returning?
sql = "#{sql} RETURNING #{quote_column_name(pk)}"
end
- [sql, binds]
+ super
end
def exec_insert(sql, name, binds, pk = nil, sequence_name = nil)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index d5879ea7df..c1c77a967e 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -55,10 +55,11 @@ module ActiveRecord
end
end
- # Does not quote function default values for UUID columns
- def quote_default_expression(value, column) #:nodoc:
- if column.type == :uuid && value =~ /\(\)/
- value
+ def quote_default_expression(value, column) # :nodoc:
+ if value.is_a?(Proc)
+ value.call
+ elsif column.type == :uuid && value =~ /\(\)/
+ value # Does not quote function default values for UUID columns
elsif column.respond_to?(:array?)
value = type_cast_from_column(column, value)
quote(value)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
index a4f0742516..cc7721ddd8 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_dumper.rb
@@ -9,7 +9,7 @@ module ActiveRecord
spec[:id] = ':bigserial'
elsif column.type == :uuid
spec[:id] = ':uuid'
- spec[:default] = column.default_function.inspect
+ spec[:default] = schema_default(column) || 'nil'
else
spec[:id] = column.type.inspect
spec.merge!(prepare_column_options(column).delete_if { |key, _| [:name, :type, :null].include?(key) })
@@ -41,12 +41,8 @@ module ActiveRecord
end
end
- def schema_default(column)
- if column.default_function
- column.default_function.inspect unless column.serial?
- else
- super
- end
+ def schema_expression(column)
+ super unless column.serial?
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index e313a34c3a..2de6fbfaf0 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -512,8 +512,13 @@ module ActiveRecord
def extract_value_from_default(default) # :nodoc:
case default
# Quoted types
- when /\A[\(B]?'(.*)'::/m
- $1.gsub("''".freeze, "'".freeze)
+ when /\A[\(B]?'(.*)'.*::"?([\w. ]+)"?(?:\[\])?\z/m
+ # The default 'now'::date is CURRENT_DATE
+ if $1 == "now".freeze && $2 == "date".freeze
+ nil
+ else
+ $1.gsub("''".freeze, "'".freeze)
+ end
# Boolean types
when 'true'.freeze, 'false'.freeze
default
@@ -535,7 +540,7 @@ module ActiveRecord
end
def has_default_function?(default_value, default) # :nodoc:
- !default_value && (%r{\w+\(.*\)} === default)
+ !default_value && (%r{\w+\(.*\)|\(.*\)::\w+} === default)
end
def load_additional_types(type_map, oids = nil) # :nodoc:
@@ -690,15 +695,7 @@ module ActiveRecord
end
# Returns the current ID of a table's sequence.
- def last_insert_id(sequence_name) #:nodoc:
- Integer(last_insert_id_value(sequence_name))
- end
-
- def last_insert_id_value(sequence_name)
- last_insert_id_result(sequence_name).rows.first.first
- end
-
- def last_insert_id_result(sequence_name) #:nodoc:
+ def last_insert_id_result(sequence_name) # :nodoc:
exec_query("SELECT currval('#{sequence_name}')", 'SQL')
end
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 1250f8a3c3..475a298467 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -275,7 +275,7 @@ module ActiveRecord
def relation # :nodoc:
relation = Relation.create(self, arel_table, predicate_builder)
- if finder_needs_type_condition?
+ if finder_needs_type_condition? && !ignore_default_scope?
relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
else
relation
diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb
index 04519f4dc3..8655f68308 100644
--- a/activerecord/lib/active_record/enum.rb
+++ b/activerecord/lib/active_record/enum.rb
@@ -187,7 +187,7 @@ module ActiveRecord
# scope :active, -> { where status: 0 }
klass.send(:detect_enum_conflict!, name, value_method_name, true)
- klass.scope value_method_name, -> { klass.where name => value }
+ klass.scope value_method_name, -> { where(name => value) }
end
end
defined_enums[name.to_s] = enum_values
diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb
index 2e962f4218..e5c6e5c885 100644
--- a/activerecord/lib/active_record/internal_metadata.rb
+++ b/activerecord/lib/active_record/internal_metadata.rb
@@ -33,13 +33,12 @@ module ActiveRecord
# Creates an internal metadata table with columns +key+ and +value+
def create_table
unless table_exists?
- connection.create_table(table_name, primary_key: :key, id: false ) do |t|
+ connection.create_table(table_name, id: false) do |t|
t.column :key, :string
t.column :value, :string
t.timestamps
+ t.index :key, unique: true, name: index_name
end
-
- connection.add_index table_name, :key, unique: true, name: index_name
end
end
end
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index f699e83ab3..f5b29c7f2e 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -145,7 +145,7 @@ module ActiveRecord
class NoEnvironmentInSchemaError < MigrationError #:nodoc:
def initialize
- msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rake db:migrate"
+ msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rake db:environment:set"
if defined?(Rails.env)
super("#{msg} RAILS_ENV=#{::Rails.env}")
else
@@ -157,7 +157,7 @@ module ActiveRecord
class ProtectedEnvironmentError < ActiveRecordError #:nodoc:
def initialize(env = "production")
msg = "You are attempting to run a destructive action against your '#{env}' database\n"
- msg << "if you are sure you want to continue, run the same command with the environment variable\n"
+ msg << "If you are sure you want to continue, run the same command with the environment variable\n"
msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
super(msg)
end
@@ -165,10 +165,15 @@ module ActiveRecord
class EnvironmentMismatchError < ActiveRecordError
def initialize(current: nil, stored: nil)
- msg = "You are attempting to modify a database that was last run in #{ stored } environment.\n"
- msg << "You are running in #{ current } environment."
- msg << "if you are sure you want to continue, run the same command with the environment variable\n"
- msg << "DISABLE_DATABASE_ENVIRONMENT_CHECK=1"
+ msg = "You are attempting to modify a database that was last run in `#{ stored }` environment.\n"
+ msg << "You are running in `#{ current }` environment."
+ msg << "If you are sure you want to continue, first set the environment using:\n\n"
+ msg << "\tbin/rake db:environment:set"
+ if defined?(Rails.env)
+ super("#{msg} RAILS_ENV=#{::Rails.env}")
+ else
+ super(msg)
+ end
end
end
@@ -1165,45 +1170,58 @@ module ActiveRecord
private
+ # Used for running a specific migration.
def run_without_lock
migration = migrations.detect { |m| m.version == @target_version }
raise UnknownMigrationVersionError.new(@target_version) if migration.nil?
- unless (up? && migrated.include?(migration.version.to_i)) || (down? && !migrated.include?(migration.version.to_i))
- begin
- execute_migration_in_transaction(migration, @direction)
- rescue => e
- canceled_msg = use_transaction?(migration) ? ", this migration was canceled" : ""
- raise StandardError, "An error has occurred#{canceled_msg}:\n\n#{e}", e.backtrace
- end
- end
+ execute_migration_in_transaction(migration, @direction)
+
+ record_environment
end
+ # Used for running multiple migrations up to or down to a certain value.
def migrate_without_lock
- if !target && @target_version && @target_version > 0
+ if invalid_target?
raise UnknownMigrationVersionError.new(@target_version)
end
runnable.each do |migration|
- Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
-
- begin
- execute_migration_in_transaction(migration, @direction)
- rescue => e
- canceled_msg = use_transaction?(migration) ? "this and " : ""
- raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
- end
+ execute_migration_in_transaction(migration, @direction)
end
+
+ record_environment
+ end
+
+ # Stores the current environment in the database.
+ def record_environment
+ return if down?
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
def ran?(migration)
migrated.include?(migration.version.to_i)
end
+ # Return true if a valid version is not provided.
+ def invalid_target?
+ !target && @target_version && @target_version > 0
+ end
+
def execute_migration_in_transaction(migration, direction)
+ return if down? && !migrated.include?(migration.version.to_i)
+ return if up? && migrated.include?(migration.version.to_i)
+
+ Base.logger.info "Migrating to #{migration.name} (#{migration.version})" if Base.logger
+
ddl_transaction(migration) do
migration.migrate(direction)
record_version_state_after_migrating(migration.version)
end
+ rescue => e
+ msg = "An error has occurred, "
+ msg << "this and " if use_transaction?(migration)
+ msg << "all later migrations canceled:\n\n#{e}"
+ raise StandardError, msg, e.backtrace
end
def target
@@ -1233,7 +1251,6 @@ module ActiveRecord
else
migrated << version
ActiveRecord::SchemaMigration.create!(version: version.to_s)
- ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
end
end
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index ead9407391..d81d6b54b3 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -1,6 +1,12 @@
require 'active_record'
db_namespace = namespace :db do
+ desc "Set the environment value for the database"
+ task "environment:set" => [:environment, :load_config] do
+ ActiveRecord::InternalMetadata.create_table
+ ActiveRecord::InternalMetadata[:environment] = ActiveRecord::Migrator.current_environment
+ end
+
task :check_protected_environments => [:environment, :load_config] do
ActiveRecord::Tasks::DatabaseTasks.check_protected_environments!
end
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 6aa490908b..032b8d4c5d 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -99,7 +99,7 @@ module ActiveRecord
end
substitutes = values.map do |(arel_attr, _)|
- [arel_attr, connection.substitute_at(klass.columns_hash[arel_attr.name])]
+ [arel_attr, Arel::Nodes::BindParam.new]
end
[substitutes, binds]
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index cb971eb255..396638d74d 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -141,6 +141,9 @@ module ActiveRecord
end
def merge_single_values
+ if relation.from_clause.empty?
+ relation.from_clause = other.from_clause
+ end
relation.lock_value ||= other.lock_value
unless other.create_with_value.blank?
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 5635b4215b..716b1e8505 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -659,8 +659,10 @@ module ActiveRecord
end
def or!(other) # :nodoc:
- unless structurally_compatible_for_or?(other)
- raise ArgumentError, 'Relation passed to #or must be structurally compatible'
+ incompatible_values = structurally_incompatible_values_for_or(other)
+
+ unless incompatible_values.empty?
+ raise ArgumentError, "Relation passed to #or must be structurally compatible. Incompatible values: #{incompatible_values}"
end
self.where_clause = self.where_clause.or(other.where_clause)
@@ -1189,10 +1191,10 @@ module ActiveRecord
end
end
- def structurally_compatible_for_or?(other)
- Relation::SINGLE_VALUE_METHODS.all? { |m| send("#{m}_value") == other.send("#{m}_value") } &&
- (Relation::MULTI_VALUE_METHODS - [:extending]).all? { |m| send("#{m}_values") == other.send("#{m}_values") } &&
- (Relation::CLAUSE_METHODS - [:having, :where]).all? { |m| send("#{m}_clause") == other.send("#{m}_clause") }
+ def structurally_incompatible_values_for_or(other)
+ Relation::SINGLE_VALUE_METHODS.reject { |m| send("#{m}_value") == other.send("#{m}_value") } +
+ (Relation::MULTI_VALUE_METHODS - [:extending]).reject { |m| send("#{m}_values") == other.send("#{m}_values") } +
+ (Relation::CLAUSE_METHODS - [:having, :where]).reject { |m| send("#{m}_clause") == other.send("#{m}_clause") }
end
def new_where_clause
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index 3017ee0bbb..ee4c71f304 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -31,8 +31,8 @@ module ActiveRecord
connection.create_table(table_name, id: false) do |t|
t.column :version, :string, version_options
+ t.index :version, unique: true, name: index_name
end
- connection.add_index table_name, :version, unique: true, name: index_name
end
end
diff --git a/activerecord/lib/active_record/scoping.rb b/activerecord/lib/active_record/scoping.rb
index e395970dc6..7794af8ca4 100644
--- a/activerecord/lib/active_record/scoping.rb
+++ b/activerecord/lib/active_record/scoping.rb
@@ -11,11 +11,11 @@ module ActiveRecord
module ClassMethods
def current_scope #:nodoc:
- ScopeRegistry.value_for(:current_scope, self.to_s)
+ ScopeRegistry.value_for(:current_scope, self)
end
def current_scope=(scope) #:nodoc:
- ScopeRegistry.set_value_for(:current_scope, self.to_s, scope)
+ ScopeRegistry.set_value_for(:current_scope, self, scope)
end
# Collects attributes from scopes that should be applied when creating
@@ -53,18 +53,18 @@ module ActiveRecord
# following code:
#
# registry = ActiveRecord::Scoping::ScopeRegistry
- # registry.set_value_for(:current_scope, "Board", some_new_scope)
+ # registry.set_value_for(:current_scope, Board, some_new_scope)
#
# Now when you run:
#
- # registry.value_for(:current_scope, "Board")
+ # registry.value_for(:current_scope, Board)
#
# You will obtain whatever was defined in +some_new_scope+. The #value_for
# and #set_value_for methods are delegated to the current ScopeRegistry
# object, so the above example code can also be called as:
#
# ActiveRecord::Scoping::ScopeRegistry.set_value_for(:current_scope,
- # "Board", some_new_scope)
+ # Board, some_new_scope)
class ScopeRegistry # :nodoc:
extend ActiveSupport::PerThreadRegistry
@@ -74,16 +74,22 @@ module ActiveRecord
@registry = Hash.new { |hash, key| hash[key] = {} }
end
- # Obtains the value for a given +scope_name+ and +variable_name+.
- def value_for(scope_type, variable_name)
+ # Obtains the value for a given +scope_type+ and +model+.
+ def value_for(scope_type, model)
raise_invalid_scope_type!(scope_type)
- @registry[scope_type][variable_name]
+ klass = model
+ base = model.base_class
+ while klass <= base
+ value = @registry[scope_type][klass.name]
+ return value if value
+ klass = klass.superclass
+ end
end
- # Sets the +value+ for a given +scope_type+ and +variable_name+.
- def set_value_for(scope_type, variable_name, value)
+ # Sets the +value+ for a given +scope_type+ and +model+.
+ def set_value_for(scope_type, model, value)
raise_invalid_scope_type!(scope_type)
- @registry[scope_type][variable_name] = value
+ @registry[scope_type][model.name] = value
end
private
diff --git a/activerecord/lib/active_record/scoping/default.rb b/activerecord/lib/active_record/scoping/default.rb
index cdcb73382f..8baf3b8044 100644
--- a/activerecord/lib/active_record/scoping/default.rb
+++ b/activerecord/lib/active_record/scoping/default.rb
@@ -122,11 +122,11 @@ module ActiveRecord
end
def ignore_default_scope? # :nodoc:
- ScopeRegistry.value_for(:ignore_default_scope, self)
+ ScopeRegistry.value_for(:ignore_default_scope, base_class)
end
def ignore_default_scope=(ignore) # :nodoc:
- ScopeRegistry.set_value_for(:ignore_default_scope, self, ignore)
+ ScopeRegistry.set_value_for(:ignore_default_scope, base_class, ignore)
end
# The ignore_default_scope flag is used to prevent an infinite recursion
diff --git a/activerecord/lib/active_record/secure_token.rb b/activerecord/lib/active_record/secure_token.rb
index f10f7a1515..8abda2ac49 100644
--- a/activerecord/lib/active_record/secure_token.rb
+++ b/activerecord/lib/active_record/secure_token.rb
@@ -20,35 +20,14 @@ module ActiveRecord
#
# <tt>SecureRandom::base58</tt> is used to generate the 24-character unique token, so collisions are highly unlikely.
#
- # A secure token can also be only created given a condition, for example if a user should only have an
- # auto-generated invitation token if the user was invited:
- #
- # # Schema: User(token:string, invited:boolean)
- # class User < ActiveRecord::Base
- # has_secure_token if: :invited?
- # end
- #
- # user = User.new(invited: true)
- # user.save
- # user.token # => "pX27zsMN2ViQKta1bGfLmVJE"
- #
- # user = User.new(invited: false)
- # user.save
- # user.token # => nil
- #
- # The secure token creation supports all the options a `before_create` does - like +:if+ and +:unless+.
- #
# Note that it's still possible to generate a race condition in the database in the same way that
# {validates_uniqueness_of}[rdoc-ref:Validations::ClassMethods#validates_uniqueness_of] can.
# You're encouraged to add a unique index in the database to deal with this even more unlikely scenario.
- def has_secure_token(attribute = :token, **before_create_options)
+ def has_secure_token(attribute = :token)
# Load securerandom only when has_secure_token is used.
require 'active_support/core_ext/securerandom'
-
define_method("regenerate_#{attribute}") { update! attribute => self.class.generate_unique_secure_token }
- before_create(before_create_options) do
- self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?")
- end
+ before_create { self.send("#{attribute}=", self.class.generate_unique_secure_token) unless self.send("#{attribute}?")}
end
def generate_unique_secure_token
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index e361521155..f1995b243a 100644
--- a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
@@ -139,8 +139,8 @@ module ActiveRecord
def test_sql_for_insert_with_returning_disabled
connection = connection_without_insert_returning
- result = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
- assert_equal ['sql', 'binds'], result
+ sql, binds = connection.sql_for_insert('sql', nil, nil, nil, 'binds')
+ assert_equal ['sql', 'binds'], [sql, binds]
end
def test_serial_sequence
@@ -322,11 +322,6 @@ module ActiveRecord
end
end
- def test_substitute_at
- bind = @connection.substitute_at(nil)
- assert_equal Arel.sql('$1'), bind.to_sql
- end
-
def test_partial_index
with_example_table do
@connection.add_index 'ex', %w{ id number }, :name => 'partial', :where => "number > 100"
diff --git a/activerecord/test/cases/adapters/postgresql/uuid_test.rb b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
index 3d90790367..7628075ad2 100644
--- a/activerecord/test/cases/adapters/postgresql/uuid_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/uuid_test.rb
@@ -197,14 +197,14 @@ class PostgresqlUUIDGenerationTest < ActiveRecord::PostgreSQLTestCase
def test_schema_dumper_for_uuid_primary_key
schema = dump_table_schema "pg_uuids"
- assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: "uuid_generate_v1\(\)"/, schema)
- assert_match(/t\.uuid "other_uuid", default: "uuid_generate_v4\(\)"/, schema)
+ assert_match(/\bcreate_table "pg_uuids", id: :uuid, default: -> { "uuid_generate_v1\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid", default: -> { "uuid_generate_v4\(\)" }/, schema)
end
def test_schema_dumper_for_uuid_primary_key_with_custom_default
schema = dump_table_schema "pg_uuids_2"
- assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: "my_uuid_generator\(\)"/, schema)
- assert_match(/t\.uuid "other_uuid_2", default: "my_uuid_generator\(\)"/, schema)
+ assert_match(/\bcreate_table "pg_uuids_2", id: :uuid, default: -> { "my_uuid_generator\(\)" }/, schema)
+ assert_match(/t\.uuid "other_uuid_2", default: -> { "my_uuid_generator\(\)" }/, schema)
end
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
index a2fd1177a6..038d9e2d0f 100644
--- a/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/sqlite3_adapter_test.rb
@@ -130,11 +130,6 @@ module ActiveRecord
assert_equal 'UTF-8', @conn.encoding
end
- def test_bind_value_substitute
- bind_param = @conn.substitute_at('foo')
- assert_equal Arel.sql('?'), bind_param.to_sql
- end
-
def test_exec_no_binds
with_example_table 'id int, data string' do
result = @conn.exec_query('SELECT id, data FROM ex')
diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb
index ba3e16bdb2..ecdf508e3e 100644
--- a/activerecord/test/cases/base_test.rb
+++ b/activerecord/test/cases/base_test.rb
@@ -1275,9 +1275,10 @@ class BasicsTest < ActiveRecord::TestCase
UnloadablePost.send(:current_scope=, UnloadablePost.all)
UnloadablePost.unloadable
- assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
+ klass = UnloadablePost
+ assert_not_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
ActiveSupport::Dependencies.remove_unloadable_constants!
- assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, "UnloadablePost")
+ assert_nil ActiveRecord::Scoping::ScopeRegistry.value_for(:current_scope, klass)
ensure
Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost)
end
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 1e38b97c4a..cd9c76f1f0 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -39,7 +39,7 @@ module ActiveRecord
end
def test_binds_are_logged
- sub = @connection.substitute_at(@pk)
+ sub = Arel::Nodes::BindParam.new
binds = [Relation::QueryAttribute.new("id", 1, Type::Value.new)]
sql = "select * from topics where id = #{sub.to_sql}"
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index fb2d3bd497..69b0487dd8 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -1,4 +1,5 @@
require "cases/helper"
+require 'support/schema_dumping_helper'
require 'models/default'
require 'models/entrant'
@@ -80,7 +81,32 @@ class DefaultStringsTest < ActiveRecord::TestCase
end
end
+if current_adapter?(:PostgreSQLAdapter)
+ class PostgresqlDefaultExpressionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ test "schema dump includes default expression" do
+ output = dump_table_schema("defaults")
+ assert_match %r/t\.date\s+"modified_date",\s+default: -> { "\('now'::text\)::date" }/, output
+ assert_match %r/t\.date\s+"modified_date_function",\s+default: -> { "now\(\)" }/, output
+ assert_match %r/t\.datetime\s+"modified_time",\s+default: -> { "now\(\)" }/, output
+ assert_match %r/t\.datetime\s+"modified_time_function",\s+default: -> { "now\(\)" }/, output
+ end
+ end
+end
+
if current_adapter?(:Mysql2Adapter)
+ class MysqlDefaultExpressionTest < ActiveRecord::TestCase
+ include SchemaDumpingHelper
+
+ if ActiveRecord::Base.connection.version >= '5.6.0'
+ test "schema dump includes default expression" do
+ output = dump_table_schema("datetime_defaults")
+ assert_match %r/t\.datetime\s+"modified_datetime",\s+default: -> { "CURRENT_TIMESTAMP" }/, output
+ end
+ end
+ end
+
class DefaultsTestWithoutTransactionalFixtures < ActiveRecord::TestCase
# ActiveRecord::Base#create! (and #save and other related methods) will
# open a new transaction. When in transactional tests mode, this will
diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb
index c73958900b..da934ab8fe 100644
--- a/activerecord/test/cases/fixtures_test.rb
+++ b/activerecord/test/cases/fixtures_test.rb
@@ -223,6 +223,10 @@ class FixturesTest < ActiveRecord::TestCase
assert_equal(%(table "parrots" has no column named "arrr".), e.message)
end
+ def test_yaml_file_with_symbol_columns
+ ActiveRecord::FixtureSet.create_fixtures(FIXTURES_ROOT + "/naked/yml", "trees")
+ end
+
def test_omap_fixtures
assert_nothing_raised do
fixtures = ActiveRecord::FixtureSet.new(Account.connection, 'categories', Category, FIXTURES_ROOT + "/categories_ordered")
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index cfa223f93e..f51e366b1d 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -301,7 +301,7 @@ class MigrationTest < ActiveRecord::TestCase
e = assert_raise(StandardError) { migrator.run }
- assert_equal "An error has occurred, this migration was canceled:\n\nSomething broke", e.message
+ assert_equal "An error has occurred, this and all later migrations canceled:\n\nSomething broke", e.message
assert_no_column Person, :last_name,
"On error, the Migrator should revert schema changes but it did not."
@@ -380,6 +380,33 @@ class MigrationTest < ActiveRecord::TestCase
old_path = ActiveRecord::Migrator.migrations_paths
ActiveRecord::Migrator.migrations_paths = migrations_path
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
+
+ original_rails_env = ENV["RAILS_ENV"]
+ original_rack_env = ENV["RACK_ENV"]
+ ENV["RAILS_ENV"] = ENV["RACK_ENV"] = "foofoo"
+ new_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+
+ refute_equal current_env, new_env
+
+ sleep 1 # mysql by default does not store fractional seconds in the database
+ ActiveRecord::Migrator.up(migrations_path)
+ assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
+ ensure
+ ActiveRecord::Migrator.migrations_paths = old_path
+ ENV["RAILS_ENV"] = original_rails_env
+ ENV["RACK_ENV"] = original_rack_env
+ end
+
+
+ def test_migration_sets_internal_metadata_even_when_fully_migrated
+ current_env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
+ migrations_path = MIGRATIONS_ROOT + "/valid"
+ old_path = ActiveRecord::Migrator.migrations_paths
+ ActiveRecord::Migrator.migrations_paths = migrations_path
+
+ ActiveRecord::Migrator.up(migrations_path)
assert_equal current_env, ActiveRecord::InternalMetadata[:environment]
original_rails_env = ENV["RAILS_ENV"]
@@ -390,6 +417,7 @@ class MigrationTest < ActiveRecord::TestCase
refute_equal current_env, new_env
sleep 1 # mysql by default does not store fractional seconds in the database
+
ActiveRecord::Migrator.up(migrations_path)
assert_equal new_env, ActiveRecord::InternalMetadata[:environment]
ensure
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 0a2e874e4f..60a806c05a 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -104,6 +104,13 @@ class RelationMergingTest < ActiveRecord::TestCase
post = PostThatLoadsCommentsInAnAfterSaveHook.create!(title: "First Post", body: "Blah blah blah.")
assert_equal "First comment!", post.comments.where(body: "First comment!").first_or_create.body
end
+
+ def test_merging_with_from_clause
+ relation = Post.all
+ assert relation.from_clause.empty?
+ relation = relation.merge(Post.from("posts"))
+ refute relation.from_clause.empty?
+ end
end
class MergingDifferentRelationsTest < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/relation/or_test.rb b/activerecord/test/cases/relation/or_test.rb
index 2006fc9611..28a0862f91 100644
--- a/activerecord/test/cases/relation/or_test.rb
+++ b/activerecord/test/cases/relation/or_test.rb
@@ -52,9 +52,11 @@ module ActiveRecord
end
def test_or_with_incompatible_relations
- assert_raises ArgumentError do
+ error = assert_raises ArgumentError do
Post.order('body asc').where('id = 1').or(Post.order('id desc').where(:id => [2, 3])).to_a
end
+
+ assert_equal "Relation passed to #or must be structurally compatible. Incompatible values: [:order]", error.message
end
def test_or_when_grouping
diff --git a/activerecord/test/cases/scoping/default_scoping_test.rb b/activerecord/test/cases/scoping/default_scoping_test.rb
index 86316ab476..ad5ca70f36 100644
--- a/activerecord/test/cases/scoping/default_scoping_test.rb
+++ b/activerecord/test/cases/scoping/default_scoping_test.rb
@@ -459,4 +459,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
scope = Bus.all
assert_equal scope.where_clause.ast.children.length, 1
end
+
+ def test_sti_conditions_are_not_carried_in_default_scope
+ ConditionalStiPost.create! body: ''
+ SubConditionalStiPost.create! body: ''
+ SubConditionalStiPost.create! title: 'Hello world', body: ''
+
+ assert_equal 2, ConditionalStiPost.count
+ assert_equal 2, ConditionalStiPost.all.to_a.size
+ assert_equal 3, ConditionalStiPost.unscope(where: :title).to_a.size
+
+ assert_equal 1, SubConditionalStiPost.count
+ assert_equal 1, SubConditionalStiPost.all.to_a.size
+ assert_equal 2, SubConditionalStiPost.unscope(where: :title).to_a.size
+ end
end
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 4bfffbe9c6..c15d57460b 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -209,9 +209,23 @@ class RelationScopingTest < ActiveRecord::TestCase
assert_not_equal [], Developer.all
end
- def test_current_scope_does_not_pollute_other_subclasses
- Post.none.scoping do
- assert StiPost.all.any?
+ def test_current_scope_does_not_pollute_sibling_subclasses
+ Comment.none.scoping do
+ assert_not SpecialComment.all.any?
+ assert_not VerySpecialComment.all.any?
+ assert_not SubSpecialComment.all.any?
+ end
+
+ SpecialComment.none.scoping do
+ assert Comment.all.any?
+ assert VerySpecialComment.all.any?
+ assert_not SubSpecialComment.all.any?
+ end
+
+ SubSpecialComment.none.scoping do
+ assert Comment.all.any?
+ assert VerySpecialComment.all.any?
+ assert SpecialComment.all.any?
end
end
end
diff --git a/activerecord/test/cases/secure_token_test.rb b/activerecord/test/cases/secure_token_test.rb
index 239b975d82..e731443fc2 100644
--- a/activerecord/test/cases/secure_token_test.rb
+++ b/activerecord/test/cases/secure_token_test.rb
@@ -29,18 +29,4 @@ class SecureTokenTest < ActiveRecord::TestCase
assert_equal @user.token, "custom-secure-token"
end
-
- def test_failing_if_condition_does_not_set_token
- @user.token_condition = false
- @user.save
-
- assert_nil @user.conditional_token
- end
-
- def test_passing_if_condition_sets_token
- @user.token_condition = true
- @user.save
-
- assert_not_nil @user.conditional_token
- end
end
diff --git a/activerecord/test/fixtures/naked/yml/trees.yml b/activerecord/test/fixtures/naked/yml/trees.yml
new file mode 100644
index 0000000000..d163b98f21
--- /dev/null
+++ b/activerecord/test/fixtures/naked/yml/trees.yml
@@ -0,0 +1,3 @@
+root:
+ :id: 1
+ :name: The Root
diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb
index 23cebe2602..bf3079a1df 100644
--- a/activerecord/test/models/post.rb
+++ b/activerecord/test/models/post.rb
@@ -263,3 +263,10 @@ end
class SerializedPost < ActiveRecord::Base
serialize :title
end
+
+class ConditionalStiPost < Post
+ default_scope { where(title: 'Untitled') }
+end
+
+class SubConditionalStiPost < ConditionalStiPost
+end
diff --git a/activerecord/test/models/user.rb b/activerecord/test/models/user.rb
index a40385e047..f5dc93e994 100644
--- a/activerecord/test/models/user.rb
+++ b/activerecord/test/models/user.rb
@@ -1,9 +1,6 @@
class User < ActiveRecord::Base
has_secure_token
has_secure_token :auth_token
- has_secure_token :conditional_token, if: :token_condition
-
- attr_accessor :token_condition
end
class UserWithNotification < User
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 752572a79c..701e6f45b3 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -1,4 +1,11 @@
ActiveRecord::Schema.define do
+
+ if ActiveRecord::Base.connection.version >= '5.6.0'
+ create_table :datetime_defaults, force: true do |t|
+ t.datetime :modified_datetime, default: -> { 'CURRENT_TIMESTAMP' }
+ end
+ end
+
create_table :binary_fields, force: true do |t|
t.binary :var_binary, limit: 255
t.binary :var_binary_large, limit: 4095
diff --git a/activerecord/test/schema/postgresql_specific_schema.rb b/activerecord/test/schema/postgresql_specific_schema.rb
index df0362573b..3a5d73a0ed 100644
--- a/activerecord/test/schema/postgresql_specific_schema.rb
+++ b/activerecord/test/schema/postgresql_specific_schema.rb
@@ -11,7 +11,23 @@ ActiveRecord::Schema.define do
t.uuid :uuid_parent_id
end
- %w(postgresql_times postgresql_oids defaults postgresql_timestamp_with_zones
+ create_table :defaults, force: true do |t|
+ t.date :modified_date, default: -> { 'CURRENT_DATE' }
+ t.date :modified_date_function, default: -> { 'now()' }
+ t.date :fixed_date, default: '2004-01-01'
+ t.datetime :modified_time, default: -> { 'CURRENT_TIMESTAMP' }
+ t.datetime :modified_time_function, default: -> { 'now()' }
+ t.datetime :fixed_time, default: '2004-01-01 00:00:00.000000-00'
+ t.column :char1, 'char(1)', default: 'Y'
+ t.string :char2, limit: 50, default: 'a varchar field'
+ t.text :char3, default: 'a text field'
+ t.bigint :bigint_default, default: -> { '0::bigint' }
+ t.text :multiline_default, default: '--- []
+
+'
+ end
+
+ %w(postgresql_times postgresql_oids postgresql_timestamp_with_zones
postgresql_partitioned_table postgresql_partitioned_table_parent).each do |table_name|
drop_table table_name, if_exists: true
end
@@ -28,25 +44,6 @@ ActiveRecord::Schema.define do
end
execute <<_SQL
- CREATE TABLE defaults (
- id serial primary key,
- modified_date date default CURRENT_DATE,
- modified_date_function date default now(),
- fixed_date date default '2004-01-01',
- modified_time timestamp default CURRENT_TIMESTAMP,
- modified_time_function timestamp default now(),
- fixed_time timestamp default '2004-01-01 00:00:00.000000-00',
- char1 char(1) default 'Y',
- char2 character varying(50) default 'a varchar field',
- char3 text default 'a text field',
- bigint_default bigint default 0::bigint,
- multiline_default text DEFAULT '--- []
-
-'::text
-);
-_SQL
-
- execute <<_SQL
CREATE TABLE postgresql_times (
id SERIAL PRIMARY KEY,
time_interval INTERVAL,
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 4a74137c00..025184f63a 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -1007,7 +1007,6 @@ ActiveRecord::Schema.define do
create_table :users, force: true do |t|
t.string :token
t.string :auth_token
- t.string :conditional_token
end
create_table :test_with_keyword_column_name, force: true do |t|
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index cebe19be89..21c79949ca 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,10 +1,12 @@
-* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`
+* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`.
*David Cornu*
-* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter` payload when an exception is raised.
+* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter`
+ payload when an exception is raised.
- Adds new key/value pair to payload when an exception is raised: e.g. `:exception_object => #<RuntimeError: FAIL>`.
+ Adds new key/value pair to payload when an exception is raised:
+ e.g. `:exception_object => #<RuntimeError: FAIL>`.
*Ryan T. Hosford*
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index dff2443bc8..99c55b1aa4 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -17,6 +17,7 @@ module ActiveSupport
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
EXCLUDED_DIRS = ['.', '..'].freeze
+ GITKEEP_FILES = ['.gitkeep', '.keep'].freeze
def initialize(cache_path, options = nil)
super(options)
@@ -24,10 +25,10 @@ module ActiveSupport
end
# Deletes all items from the cache. In this case it deletes all the entries in the specified
- # file store directory except for .gitkeep. Be careful which directory is specified in your
+ # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
+ root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
rescue Errno::ENOENT
end
@@ -154,7 +155,7 @@ module ActiveSupport
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if File.realpath(dir) == File.realpath(cache_path)
- if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty?
+ if exclude_from(dir, EXCLUDED_DIRS).empty?
Dir.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
@@ -193,6 +194,11 @@ module ActiveSupport
end
end
end
+
+ # Exclude entries from source directory
+ def exclude_from(source, excludes)
+ Dir.entries(source).reject { |f| excludes.include?(f) }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 32fe8025fe..f5ea6669ce 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -21,15 +21,15 @@ module ActiveSupport
# # => [:aaa, :bbb, :ccc]
#
# Fred.aaa
- # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.0. (called from irb_binding at (irb):10)
+ # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
# # => nil
#
# Fred.bbb
- # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.0 (use zzz instead). (called from irb_binding at (irb):11)
+ # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
# # => nil
#
# Fred.ccc
- # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.0 (use Bar#ccc instead). (called from irb_binding at (irb):12)
+ # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
# # => nil
#
# Passing in a custom deprecator:
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 2701dc2fe9..4a299429f3 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -780,10 +780,12 @@ class FileStoreTest < ActiveSupport::TestCase
include AutoloadingCacheBehavior
def test_clear
- filepath = File.join(cache_dir, ".gitkeep")
- FileUtils.touch(filepath)
+ gitkeep = File.join(cache_dir, ".gitkeep")
+ keep = File.join(cache_dir, ".keep")
+ FileUtils.touch([gitkeep, keep])
@cache.clear
- assert File.exist?(filepath)
+ assert File.exist?(gitkeep)
+ assert File.exist?(keep)
end
def test_clear_without_cache_dir
diff --git a/guides/assets/images/belongs_to.png b/guides/assets/images/belongs_to.png
index 43c963ffa8..077d237e4e 100644
--- a/guides/assets/images/belongs_to.png
+++ b/guides/assets/images/belongs_to.png
Binary files differ
diff --git a/guides/assets/images/has_many.png b/guides/assets/images/has_many.png
index e7589e3b75..79da2613d7 100644
--- a/guides/assets/images/has_many.png
+++ b/guides/assets/images/has_many.png
Binary files differ
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index d2e2d27737..f68f2d1faf 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -1118,7 +1118,7 @@ Rails default exception handling displays a "500 Server Error" message for all e
### The Default 500 and 404 Templates
-By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them.
+By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them.
### `rescue_from`
@@ -1174,7 +1174,11 @@ end
WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development).
-NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed.
+NOTE: When running in the production environment, all
+`ActiveRecord::RecordNotFound` errors render the 404 error page. Unless you need
+a custom behavior you don't need to handle this.
+
+NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed.
Force HTTPS protocol
--------------------
diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md
index b592209d4b..68c6a77882 100644
--- a/guides/source/active_record_postgresql.md
+++ b/guides/source/active_record_postgresql.md
@@ -84,6 +84,7 @@ Book.where("array_length(ratings, 1) >= 3")
### Hstore
* [type definition](http://www.postgresql.org/docs/current/static/hstore.html)
+* [functions and operators](http://www.postgresql.org/docs/current/static/hstore.html#AEN167712)
NOTE: You need to enable the `hstore` extension to use hstore.
@@ -108,6 +109,9 @@ profile.settings # => {"color"=>"blue", "resolution"=>"800x600"}
profile.settings = {"color" => "yellow", "resolution" => "1280x1024"}
profile.save!
+
+Profile.where("settings->'color' = ?", "yellow")
+#=> #<ActiveRecord::Relation [#<Profile id: 1, settings: {"color"=>"yellow", "resolution"=>"1280x1024"}>]>
```
### JSON
diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md
index 4606ac4683..674f498ae4 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -59,7 +59,7 @@ To retrieve objects from the database, Active Record provides several finder met
The methods are:
-* `bind`
+* `find`
* `create_with`
* `distinct`
* `eager_load`
@@ -170,7 +170,7 @@ TIP: The retrieved record may vary depending on the database engine.
#### `first`
-The `first` method finds the first record ordered by the primary key. For example:
+The `first` method finds the first record ordered by primary key (default). For example:
```ruby
client = Client.first
@@ -204,11 +204,24 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id ASC LIMIT 3
```
+On a collection that is ordered using `order`, `first` will return the first record ordered by the specified attribute for `order`.
+
+```ruby
+client = Client.order(:first_name).first
+# => #<Client id: 2, first_name: "Fifo">
+```
+
+The SQL equivalent of the above is:
+
+```sql
+SELECT * FROM clients ORDER BY clients.first_name ASC LIMIT 1
+```
+
The `first!` method behaves exactly like `first`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
#### `last`
-The `last` method finds the last record ordered by the primary key. For example:
+The `last` method finds the last record ordered by primary key (default). For example:
```ruby
client = Client.last
@@ -242,6 +255,19 @@ The SQL equivalent of the above is:
SELECT * FROM clients ORDER BY clients.id DESC LIMIT 3
```
+On a collection that is ordered using `order`, `last` will return the last record ordered by the specified attribute for `order`.
+
+```ruby
+client = Client.order(:first_name).last
+# => #<Client id: 220, first_name: "Sara">
+```
+
+The SQL equivalent of the above is:
+
+```sql
+SELECT * FROM clients ORDER BY clients.first_name DESC LIMIT 1
+```
+
The `last!` method behaves exactly like `last`, except that it will raise `ActiveRecord::RecordNotFound` if no matching record is found.
#### `find_by`
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index d83dda7228..94cee94e0f 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -19,51 +19,51 @@ Why Associations?
In Rails, an _association_ is a connection between two Active Record models. Why do we need associations between models? Because they make common operations simpler and easier in your code. For example, consider a simple Rails application that includes a model for customers and a model for orders. Each customer can have many orders. Without associations, the model declarations would look like this:
```ruby
-class Customer < ApplicationRecord
+class Author < ActiveRecord::Base
end
-class Order < ApplicationRecord
+class Book < ActiveRecord::Base
end
```
-Now, suppose we wanted to add a new order for an existing customer. We'd need to do something like this:
+Now, suppose we wanted to add a new book for an existing author. We'd need to do something like this:
```ruby
-@order = Order.create(order_date: Time.now, customer_id: @customer.id)
+@book = Book.create(published_at: Time.now, author_id: @author.id)
```
-Or consider deleting a customer, and ensuring that all of its orders get deleted as well:
+Or consider deleting a author, and ensuring that all of its books get deleted as well:
```ruby
-@orders = Order.where(customer_id: @customer.id)
-@orders.each do |order|
- order.destroy
+@books = Book.where(author_id: @author.id)
+@books.each do |book|
+ book.destroy
end
-@customer.destroy
+@author.destroy
```
-With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up customers and orders:
+With Active Record associations, we can streamline these - and other - operations by declaratively telling Rails that there is a connection between the two models. Here's the revised code for setting up authors and books:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, dependent: :destroy
+class Author < ApplicationRecord
+ has_many :books, dependent: :destroy
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
-With this change, creating a new order for a particular customer is easier:
+With this change, creating a new book for a particular author is easier:
```ruby
-@order = @customer.orders.create(order_date: Time.now)
+@book = @author.books.create(published_at: Time.now)
```
-Deleting a customer and all of its orders is *much* easier:
+Deleting a author and all of its books is *much* easier:
```ruby
-@customer.destroy
+@author.destroy
```
To learn more about the different types of associations, read the next section of this guide. That's followed by some tips and tricks for working with associations, and then by a complete reference to the methods and options for associations in Rails.
@@ -86,31 +86,31 @@ In the remainder of this guide, you'll learn how to declare and use the various
### The `belongs_to` Association
-A `belongs_to` association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes customers and orders, and each order can be assigned to exactly one customer, you'd declare the order model this way:
+A `belongs_to` association sets up a one-to-one connection with another model, such that each instance of the declaring model "belongs to" one instance of the other model. For example, if your application includes authors and books, and each book can be assigned to exactly one author, you'd declare the book model this way:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
![belongs_to Association Diagram](images/belongs_to.png)
-NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `customer` association in the `Order` model, you would be told that there was an "uninitialized constant Order::Customers". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too.
+NOTE: `belongs_to` associations _must_ use the singular term. If you used the pluralized form in the above example for the `author` association in the `Book` model, you would be told that there was an "uninitialized constant Book::Authors". This is because Rails automatically infers the class name from the association name. If the association name is wrongly pluralized, then the inferred class will be wrongly pluralized too.
The corresponding migration might look like this:
```ruby
-class CreateOrders < ActiveRecord::Migration[5.0]
+class CreateBooks < ActiveRecord::Migration[5.0]
def change
- create_table :customers do |t|
+ create_table :authors do |t|
t.string :name
t.timestamps null: false
end
- create_table :orders do |t|
- t.belongs_to :customer, index: true
- t.datetime :order_date
+ create_table :books do |t|
+ t.belongs_to :author, index: true
+ t.datetime :published_at
t.timestamps null: false
end
end
@@ -161,11 +161,11 @@ end
### The `has_many` Association
-A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing customers and orders, the customer model could be declared like this:
+A `has_many` association indicates a one-to-many connection with another model. You'll often find this association on the "other side" of a `belongs_to` association. This association indicates that each instance of the model has zero or more instances of another model. For example, in an application containing authors and books, the author model could be declared like this:
```ruby
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
@@ -176,16 +176,16 @@ NOTE: The name of the other model is pluralized when declaring a `has_many` asso
The corresponding migration might look like this:
```ruby
-class CreateCustomers < ActiveRecord::Migration[5.0]
+class CreateAuthors < ActiveRecord::Migration[5.0]
def change
- create_table :customers do |t|
+ create_table :authors do |t|
t.string :name
t.timestamps null: false
end
- create_table :orders do |t|
- t.belongs_to :customer, index: true
- t.datetime :order_date
+ create_table :books do |t|
+ t.belongs_to :author, index: true
+ t.datetime :published_at
t.timestamps null: false
end
end
@@ -540,17 +540,17 @@ Here are a few things you should know to make efficient use of Active Record ass
All of the association methods are built around caching, which keeps the result of the most recent query available for further operations. The cache is even shared across methods. For example:
```ruby
-customer.orders # retrieves orders from the database
-customer.orders.size # uses the cached copy of orders
-customer.orders.empty? # uses the cached copy of orders
+author.books # retrieves books from the database
+author.books.size # uses the cached copy of books
+author.books.empty? # uses the cached copy of books
```
But what if you want to reload the cache, because data might have been changed by some other part of the application? Just pass `true` to the association call:
```ruby
-customer.orders # retrieves orders from the database
-customer.orders.size # uses the cached copy of orders
-customer.orders(true).empty? # discards the cached copy of orders
+author.books # retrieves books from the database
+author.books.size # uses the cached copy of books
+author.books(true).empty? # discards the cached copy of books
# and goes back to the database
```
@@ -567,23 +567,23 @@ Associations are extremely useful, but they are not magic. You are responsible f
When you declare a `belongs_to` association, you need to create foreign keys as appropriate. For example, consider this model:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
-This declaration needs to be backed up by the proper foreign key declaration on the orders table:
+This declaration needs to be backed up by the proper foreign key declaration on the books table:
```ruby
-class CreateOrders < ActiveRecord::Migration[5.0]
+class CreateBooks < ActiveRecord::Migration[5.0]
def change
- create_table :orders do |t|
- t.datetime :order_date
- t.string :order_number
- t.integer :customer_id
+ create_table :books do |t|
+ t.datetime :published_at
+ t.string :book_number
+ t.integer :author_id
end
- add_index :orders, :customer_id
+ add_index :books, :author_id
end
end
```
@@ -592,7 +592,7 @@ If you create an association some time after you build the underlying model, you
#### Creating Join Tables for `has_and_belongs_to_many` Associations
-If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical order of the class names. So a join between customer and order models will give the default join table name of "customers_orders" because "c" outranks "o" in lexical ordering.
+If you create a `has_and_belongs_to_many` association, you need to explicitly create the joining table. Unless the name of the join table is explicitly specified by using the `:join_table` option, Active Record creates the name by using the lexical book of the class names. So a join between author and book models will give the default join table name of "authors_books" because "a" outranks "b" in lexical ordering.
WARNING: The precedence between model names is calculated using the `<=>` operator for `String`. This means that if the strings are of different lengths, and the strings are equal when compared up to the shortest length, then the longer string is considered of higher lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers" to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes", but it in fact generates a join table name of "paper_boxes_papers" (because the underscore '\_' is lexicographically _less_ than 's' in common encodings).
@@ -700,45 +700,45 @@ end
It's normal for associations to work in two directions, requiring declaration on two different models:
```ruby
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
By default, Active Record doesn't know about the connection between these associations. This can lead to two copies of an object getting out of sync:
```ruby
-c = Customer.first
-o = c.orders.first
-c.first_name == o.customer.first_name # => true
-c.first_name = 'Manny'
-c.first_name == o.customer.first_name # => false
+a = Author.first
+b = c.books.first
+a.first_name == b.author.first_name # => true
+a.first_name = 'Manny'
+a.first_name == b.author.first_name # => false
```
-This happens because `c` and `o.customer` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
+This happens because `a` and `b.author` are two different in-memory representations of the same data, and neither one is automatically refreshed from changes to the other. Active Record provides the `:inverse_of` option so that you can inform it of these relations:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, inverse_of: :customer
+class Author < ApplicationRecord
+ has_many :books, inverse_of: :author
end
-class Order < ApplicationRecord
- belongs_to :customer, inverse_of: :orders
+class book < ApplicationRecord
+ belongs_to :author, inverse_of: :books
end
```
-With these changes, Active Record will only load one copy of the customer object, preventing inconsistencies and making your application more efficient:
+With these changes, Active Record will only load one copy of the author object, preventing inconsistencies and making your application more efficient:
```ruby
-c = Customer.first
-o = c.orders.first
-c.first_name == o.customer.first_name # => true
-c.first_name = 'Manny'
-c.first_name == o.customer.first_name # => true
+a = author.first
+b = c.books.first
+a.first_name == b.author.first_name # => true
+a.first_name = 'Manny'
+a.first_name == b.author.first_name # => true
```
There are a few limitations to `inverse_of` support:
@@ -781,19 +781,19 @@ When you declare a `belongs_to` association, the declaring class automatically g
In all of these methods, `association` is replaced with the symbol passed as the first argument to `belongs_to`. For example, given the declaration:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
```
-Each instance of the `Order` model will have these methods:
+Each instance of the `Book` model will have these methods:
```ruby
-customer
-customer=
-build_customer
-create_customer
-create_customer!
+author
+author=
+build_author
+create_author
+create_author!
```
NOTE: When initializing a new `has_one` or `belongs_to` association you must use the `build_` prefix to build the association, rather than the `association.build` method that would be used for `has_many` or `has_and_belongs_to_many` associations. To create one, use the `create_` prefix.
@@ -803,7 +803,7 @@ NOTE: When initializing a new `has_one` or `belongs_to` association you must use
The `association` method returns the associated object, if any. If no associated object is found, it returns `nil`.
```ruby
-@customer = @order.customer
+@author = @book.author
```
If the associated object has already been retrieved from the database for this object, the cached version will be returned. To override this behavior (and force a database read), call `#reload` on the parent object.
@@ -817,7 +817,7 @@ If the associated object has already been retrieved from the database for this o
The `association=` method assigns an associated object to this object. Behind the scenes, this means extracting the primary key from the associated object and setting this object's foreign key to the same value.
```ruby
-@order.customer = @customer
+@book.author = @author
```
##### `build_association(attributes = {})`
@@ -825,8 +825,8 @@ The `association=` method assigns an associated object to this object. Behind th
The `build_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, and the link through this object's foreign key will be set, but the associated object will _not_ yet be saved.
```ruby
-@customer = @order.build_customer(customer_number: 123,
- customer_name: "John Doe")
+@author = @book.build_author(author_number: 123,
+ author_name: "John Doe")
```
##### `create_association(attributes = {})`
@@ -834,8 +834,8 @@ The `build_association` method returns a new object of the associated type. This
The `create_association` method returns a new object of the associated type. This object will be instantiated from the passed attributes, the link through this object's foreign key will be set, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved.
```ruby
-@customer = @order.create_customer(customer_number: 123,
- customer_name: "John Doe")
+@author = @book.create_author(author_number: 123,
+ author_name: "John Doe")
```
##### `create_association!(attributes = {})`
@@ -848,8 +848,8 @@ Does the same as `create_association` above, but raises `ActiveRecord::RecordInv
While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `belongs_to` association reference. Such customizations can easily be accomplished by passing options and scope blocks when you create the association. For example, this association uses two such options:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, dependent: :destroy,
+class Book < ApplicationRecord
+ belongs_to :author, dependent: :destroy,
counter_cache: true
end
```
@@ -874,11 +874,11 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members
##### `:class_name`
-If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if an order belongs to a customer, but the actual name of the model containing customers is `Patron`, you'd set things up this way:
+If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a book belongs to a author, but the actual name of the model containing authors is `Patron`, you'd set things up this way:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, class_name: "Patron"
+class Book < ApplicationRecord
+ belongs_to :author, class_name: "Patron"
end
```
@@ -887,22 +887,22 @@ end
The `:counter_cache` option can be used to make finding the number of belonging objects more efficient. Consider these models:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-With these declarations, asking for the value of `@customer.orders.size` requires making a call to the database to perform a `COUNT(*)` query. To avoid this call, you can add a counter cache to the _belonging_ model:
+With these declarations, asking for the value of `@author.books.size` requires making a call to the database to perform a `COUNT(*)` query. To avoid this call, you can add a counter cache to the _belonging_ model:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, counter_cache: true
+class Book < ApplicationRecord
+ belongs_to :author, counter_cache: true
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
@@ -911,18 +911,18 @@ With this declaration, Rails will keep the cache value up to date, and then retu
Although the `:counter_cache` option is specified on the model that includes
the `belongs_to` declaration, the actual column must be added to the
_associated_ (`has_many`) model. In the case above, you would need to add a
-column named `orders_count` to the `Customer` model.
+column named `books_count` to the `Author` model.
You can override the default column name by specifying a custom column name in
the `counter_cache` declaration instead of `true`. For example, to use
-`count_of_orders` instead of `orders_count`:
+`count_of_books` instead of `books_count`:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, counter_cache: :count_of_orders
+class Book < ApplicationRecord
+ belongs_to :author, counter_cache: :count_of_books
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
@@ -949,8 +949,8 @@ WARNING: You should not specify this option on a `belongs_to` association that i
By convention, Rails assumes that the column used to hold the foreign key on this model is the name of the association with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, class_name: "Patron",
+class Book < ApplicationRecord
+ belongs_to :author, class_name: "Patron",
foreign_key: "patron_id"
end
```
@@ -982,12 +982,12 @@ When we execute `@user.todos.create` then the `@todo` record will have its
The `:inverse_of` option specifies the name of the `has_many` or `has_one` association that is the inverse of this association. Does not work in combination with the `:polymorphic` options.
```ruby
-class Customer < ApplicationRecord
- has_many :orders, inverse_of: :customer
+class Author < ApplicationRecord
+ has_many :books, inverse_of: :author
end
-class Order < ApplicationRecord
- belongs_to :customer, inverse_of: :orders
+class Book < ApplicationRecord
+ belongs_to :author, inverse_of: :books
end
```
@@ -1000,20 +1000,20 @@ Passing `true` to the `:polymorphic` option indicates that this is a polymorphic
If you set the `:touch` option to `true`, then the `updated_at` or `updated_on` timestamp on the associated object will be set to the current time whenever this object is saved or destroyed:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, touch: true
+class Book < ApplicationRecord
+ belongs_to :author, touch: true
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-In this case, saving or destroying an order will update the timestamp on the associated customer. You can also specify a particular timestamp attribute to update:
+In this case, saving or destroying an book will update the timestamp on the associated author. You can also specify a particular timestamp attribute to update:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, touch: :orders_updated_at
+class Book < ApplicationRecord
+ belongs_to :author, touch: :books_updated_at
end
```
@@ -1031,8 +1031,8 @@ object won't be validated. By default, this option is set to `false`.
There may be times when you wish to customize the query used by `belongs_to`. Such customizations can be achieved via a scope block. For example:
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, -> { where active: true },
+class Book < ApplicationRecord
+ belongs_to :author, -> { where active: true },
dependent: :destroy
end
```
@@ -1049,8 +1049,8 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class Order < ApplicationRecord
- belongs_to :customer, -> { where active: true }
+class book < ApplicationRecord
+ belongs_to :author, -> { where active: true }
end
```
@@ -1060,37 +1060,37 @@ You can use the `includes` method to specify second-order associations that shou
```ruby
class LineItem < ApplicationRecord
- belongs_to :order
+ belongs_to :book
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
has_many :line_items
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-If you frequently retrieve customers directly from line items (`@line_item.order.customer`), then you can make your code somewhat more efficient by including customers in the association from line items to orders:
+If you frequently retrieve authors directly from line items (`@line_item.book.author`), then you can make your code somewhat more efficient by including authors in the association from line items to books:
```ruby
class LineItem < ApplicationRecord
- belongs_to :order, -> { includes :customer }
+ belongs_to :book, -> { includes :author }
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
has_many :line_items
end
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-NOTE: There's no need to use `includes` for immediate associations - that is, if you have `Order belongs_to :customer`, then the customer is eager-loaded automatically when it's needed.
+NOTE: There's no need to use `includes` for immediate associations - that is, if you have `Book belongs_to :author`, then the author is eager-loaded automatically when it's needed.
##### `readonly`
@@ -1107,8 +1107,8 @@ TIP: If you use the `select` method on a `belongs_to` association, you should al
You can see if any associated objects exist by using the `association.nil?` method:
```ruby
-if @order.customer.nil?
- @msg = "No customer found for this order"
+if @book.author.nil?
+ @msg = "No author found for this book"
end
```
@@ -1415,30 +1415,30 @@ When you declare a `has_many` association, the declaring class automatically gai
In all of these methods, `collection` is replaced with the symbol passed as the first argument to `has_many`, and `collection_singular` is replaced with the singularized version of that symbol. For example, given the declaration:
```ruby
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
```
-Each instance of the `Customer` model will have these methods:
+Each instance of the `Author` model will have these methods:
```ruby
-orders
-orders<<(object, ...)
-orders.delete(object, ...)
-orders.destroy(object, ...)
-orders=(objects)
-order_ids
-order_ids=(ids)
-orders.clear
-orders.empty?
-orders.size
-orders.find(...)
-orders.where(...)
-orders.exists?(...)
-orders.build(attributes = {}, ...)
-orders.create(attributes = {})
-orders.create!(attributes = {})
+books
+books<<(object, ...)
+books.delete(object, ...)
+books.destroy(object, ...)
+books=(objects)
+book_ids
+book_ids=(ids)
+books.clear
+books.empty?
+books.size
+books.find(...)
+books.where(...)
+books.exists?(...)
+books.build(attributes = {}, ...)
+books.create(attributes = {})
+books.create!(attributes = {})
```
##### `collection`
@@ -1446,7 +1446,7 @@ orders.create!(attributes = {})
The `collection` method returns an array of all of the associated objects. If there are no associated objects, it returns an empty array.
```ruby
-@orders = @customer.orders
+@books = @author.books
```
##### `collection<<(object, ...)`
@@ -1454,7 +1454,7 @@ The `collection` method returns an array of all of the associated objects. If th
The `collection<<` method adds one or more objects to the collection by setting their foreign keys to the primary key of the calling model.
```ruby
-@customer.orders << @order1
+@author.books << @book1
```
##### `collection.delete(object, ...)`
@@ -1462,7 +1462,7 @@ The `collection<<` method adds one or more objects to the collection by setting
The `collection.delete` method removes one or more objects from the collection by setting their foreign keys to `NULL`.
```ruby
-@customer.orders.delete(@order1)
+@author.books.delete(@book1)
```
WARNING: Additionally, objects will be destroyed if they're associated with `dependent: :destroy`, and deleted if they're associated with `dependent: :delete_all`.
@@ -1472,7 +1472,7 @@ WARNING: Additionally, objects will be destroyed if they're associated with `dep
The `collection.destroy` method removes one or more objects from the collection by running `destroy` on each object.
```ruby
-@customer.orders.destroy(@order1)
+@author.books.destroy(@book1)
```
WARNING: Objects will _always_ be removed from the database, ignoring the `:dependent` option.
@@ -1486,7 +1486,7 @@ The `collection=` method makes the collection contain only the supplied objects,
The `collection_singular_ids` method returns an array of the ids of the objects in the collection.
```ruby
-@order_ids = @customer.order_ids
+@book_ids = @author.book_ids
```
##### `collection_singular_ids=(ids)`
@@ -1498,7 +1498,7 @@ The `collection_singular_ids=` method makes the collection contain only the obje
The `collection.clear` method removes all objects from the collection according to the strategy specified by the `dependent` option. If no option is given, it follows the default strategy. The default strategy for `has_many :through` associations is `delete_all`, and for `has_many` associations is to set the foreign keys to `NULL`.
```ruby
-@customer.orders.clear
+@author.books.clear
```
WARNING: Objects will be deleted if they're associated with `dependent: :destroy`,
@@ -1509,8 +1509,8 @@ just like `dependent: :delete_all`.
The `collection.empty?` method returns `true` if the collection does not contain any associated objects.
```erb
-<% if @customer.orders.empty? %>
- No Orders Found
+<% if @author.books.empty? %>
+ No Books Found
<% end %>
```
@@ -1519,7 +1519,7 @@ The `collection.empty?` method returns `true` if the collection does not contain
The `collection.size` method returns the number of objects in the collection.
```ruby
-@order_count = @customer.orders.size
+@book_count = @author.books.size
```
##### `collection.find(...)`
@@ -1527,7 +1527,7 @@ The `collection.size` method returns the number of objects in the collection.
The `collection.find` method finds objects within the collection. It uses the same syntax and options as `ActiveRecord::Base.find`.
```ruby
-@open_orders = @customer.orders.find(1)
+@available_books = @author.books.find(1)
```
##### `collection.where(...)`
@@ -1535,8 +1535,8 @@ The `collection.find` method finds objects within the collection. It uses the sa
The `collection.where` method finds objects within the collection based on the conditions supplied but the objects are loaded lazily meaning that the database is queried only when the object(s) are accessed.
```ruby
-@open_orders = @customer.orders.where(open: true) # No query yet
-@open_order = @open_orders.first # Now the database will be queried
+@available_books = @author.books.where(available: true) # No query yet
+@available_book = @available_books.first # Now the database will be queried
```
##### `collection.exists?(...)`
@@ -1550,12 +1550,12 @@ conditions exists in the collection. It uses the same syntax and options as
The `collection.build` method returns a single or array of new objects of the associated type. The object(s) will be instantiated from the passed attributes, and the link through their foreign key will be created, but the associated objects will _not_ yet be saved.
```ruby
-@order = @customer.orders.build(order_date: Time.now,
- order_number: "A12345")
+@book = @author.books.build(published_at: Time.now,
+ book_number: "A12345")
-@orders = @customer.orders.build([
- { order_date: Time.now, order_number: "A12346" },
- { order_date: Time.now, order_number: "A12347" }
+@books = @author.books.build([
+ { published_at: Time.now, book_number: "A12346" },
+ { published_at: Time.now, book_number: "A12347" }
])
```
@@ -1564,12 +1564,12 @@ The `collection.build` method returns a single or array of new objects of the as
The `collection.create` method returns a single or array of new objects of the associated type. The object(s) will be instantiated from the passed attributes, the link through its foreign key will be created, and, once it passes all of the validations specified on the associated model, the associated object _will_ be saved.
```ruby
-@order = @customer.orders.create(order_date: Time.now,
- order_number: "A12345")
+@book = @author.books.create(published_at: Time.now,
+ book_number: "A12345")
-@orders = @customer.orders.create([
- { order_date: Time.now, order_number: "A12346" },
- { order_date: Time.now, order_number: "A12347" }
+@books = @author.books.create([
+ { published_at: Time.now, book_number: "A12346" },
+ { published_at: Time.now, book_number: "A12347" }
])
```
@@ -1582,8 +1582,8 @@ Does the same as `collection.create` above, but raises `ActiveRecord::RecordInva
While Rails uses intelligent defaults that will work well in most situations, there may be times when you want to customize the behavior of the `has_many` association reference. Such customizations can easily be accomplished by passing options when you create the association. For example, this association uses two such options:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, dependent: :delete_all, validate: false
+class Author < ApplicationRecord
+ has_many :books, dependent: :delete_all, validate: false
end
```
@@ -1612,11 +1612,11 @@ If you set the `:autosave` option to `true`, Rails will save any loaded members
##### `:class_name`
-If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a customer has many orders, but the actual name of the model containing orders is `Transaction`, you'd set things up this way:
+If the name of the other model cannot be derived from the association name, you can use the `:class_name` option to supply the model name. For example, if a author has many books, but the actual name of the model containing books is `Transaction`, you'd set things up this way:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, class_name: "Transaction"
+class Author < ApplicationRecord
+ has_many :books, class_name: "Transaction"
end
```
@@ -1639,8 +1639,8 @@ Controls what happens to the associated objects when their owner is destroyed:
By convention, Rails assumes that the column used to hold the foreign key on the other model is the name of this model with the suffix `_id` added. The `:foreign_key` option lets you set the name of the foreign key directly:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, foreign_key: "cust_id"
+class Author < ApplicationRecord
+ has_many :books, foreign_key: "cust_id"
end
```
@@ -1651,12 +1651,12 @@ TIP: In any case, Rails will not create foreign key columns for you. You need to
The `:inverse_of` option specifies the name of the `belongs_to` association that is the inverse of this association. Does not work in combination with the `:through` or `:as` options.
```ruby
-class Customer < ApplicationRecord
- has_many :orders, inverse_of: :customer
+class Author < ApplicationRecord
+ has_many :books, inverse_of: :author
end
-class Order < ApplicationRecord
- belongs_to :customer, inverse_of: :orders
+class Book < ApplicationRecord
+ belongs_to :author, inverse_of: :books
end
```
@@ -1700,8 +1700,8 @@ If you set the `:validate` option to `false`, then associated objects will not b
There may be times when you wish to customize the query used by `has_many`. Such customizations can be achieved via a scope block. For example:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, -> { where processed: true }
+class Author < ApplicationRecord
+ has_many :books, -> { where processed: true }
end
```
@@ -1723,22 +1723,22 @@ You can use any of the standard [querying methods](active_record_querying.html)
The `where` method lets you specify the conditions that the associated object must meet.
```ruby
-class Customer < ApplicationRecord
- has_many :confirmed_orders, -> { where "confirmed = 1" },
- class_name: "Order"
+class Author < ApplicationRecord
+ has_many :confirmed_books, -> { where "confirmed = 1" },
+ class_name: "Book"
end
```
You can also set conditions via a hash:
```ruby
-class Customer < ApplicationRecord
- has_many :confirmed_orders, -> { where confirmed: true },
- class_name: "Order"
+class Author < ApplicationRecord
+ has_many :confirmed_books, -> { where confirmed: true },
+ class_name: "Book"
end
```
-If you use a hash-style `where` option, then record creation via this association will be automatically scoped using the hash. In this case, using `@customer.confirmed_orders.create` or `@customer.confirmed_orders.build` will create orders where the confirmed column has the value `true`.
+If you use a hash-style `where` option, then record creation via this association will be automatically scoped using the hash. In this case, using `@author.confirmed_books.create` or `@author.confirmed_books.build` will create books where the confirmed column has the value `true`.
##### `extending`
@@ -1749,9 +1749,9 @@ The `extending` method specifies a named module to extend the association proxy.
The `group` method supplies an attribute name to group the result set by, using a `GROUP BY` clause in the finder SQL.
```ruby
-class Customer < ApplicationRecord
- has_many :line_items, -> { group 'orders.id' },
- through: :orders
+class Author < ApplicationRecord
+ has_many :line_items, -> { group 'books.id' },
+ through: :books
end
```
@@ -1760,34 +1760,34 @@ end
You can use the `includes` method to specify second-order associations that should be eager-loaded when this association is used. For example, consider these models:
```ruby
-class Customer < ApplicationRecord
- has_many :orders
+class Author < ApplicationRecord
+ has_many :books
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
has_many :line_items
end
class LineItem < ApplicationRecord
- belongs_to :order
+ belongs_to :book
end
```
-If you frequently retrieve line items directly from customers (`@customer.orders.line_items`), then you can make your code somewhat more efficient by including line items in the association from customers to orders:
+If you frequently retrieve line items directly from authors (`@author.books.line_items`), then you can make your code somewhat more efficient by including line items in the association from authors to books:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, -> { includes :line_items }
+class Author < ApplicationRecord
+ has_many :books, -> { includes :line_items }
end
-class Order < ApplicationRecord
- belongs_to :customer
+class Book < ApplicationRecord
+ belongs_to :author
has_many :line_items
end
class LineItem < ApplicationRecord
- belongs_to :order
+ belongs_to :book
end
```
@@ -1796,10 +1796,10 @@ end
The `limit` method lets you restrict the total number of objects that will be fetched through an association.
```ruby
-class Customer < ApplicationRecord
- has_many :recent_orders,
- -> { order('order_date desc').limit(100) },
- class_name: "Order",
+class Author < ApplicationRecord
+ has_many :recent_books,
+ -> { order('published_at desc').limit(100) },
+ class_name: "Book",
end
```
@@ -1812,8 +1812,8 @@ The `offset` method lets you specify the starting offset for fetching objects vi
The `order` method dictates the order in which associated objects will be received (in the syntax used by an SQL `ORDER BY` clause).
```ruby
-class Customer < ApplicationRecord
- has_many :orders, -> { order "date_confirmed DESC" }
+class Author < ApplicationRecord
+ has_many :books, -> { order "date_confirmed DESC" }
end
```
@@ -1872,11 +1872,21 @@ If you want to make sure that, upon insertion, all of the records in the
persisted association are distinct (so that you can be sure that when you
inspect the association that you will never find duplicate records), you should
add a unique index on the table itself. For example, if you have a table named
-`person_articles` and you want to make sure all the articles are unique, you could
-add the following in a migration:
+`readings` and you want to make sure the articles can only be added to a person once,
+you could add the following in a migration:
```ruby
-add_index :person_articles, :article, unique: true
+add_index :readings, [:person_id, :article_id], unique: true
+```
+
+Once you have this unique index, attempting to add the article to a person twice
+will raise an `ActiveRecord::RecordNotUnique` error:
+
+```ruby
+person = Person.create(name: 'Honda')
+article = Article.create(name: 'a1')
+person.articles << article
+person.articles << article # => ActiveRecord::RecordNotUnique
```
Note that checking for uniqueness using something like `include?` is subject
@@ -2271,10 +2281,10 @@ Association callbacks are similar to normal callbacks, but they are triggered by
You define association callbacks by adding options to the association declaration. For example:
```ruby
-class Customer < ApplicationRecord
- has_many :orders, before_add: :check_credit_limit
+class Author < ApplicationRecord
+ has_many :books, before_add: :check_credit_limit
- def check_credit_limit(order)
+ def check_credit_limit(book)
...
end
end
@@ -2285,15 +2295,15 @@ Rails passes the object being added or removed to the callback.
You can stack callbacks on a single event by passing them as an array:
```ruby
-class Customer < ApplicationRecord
- has_many :orders,
+class Author < ApplicationRecord
+ has_many :books,
before_add: [:check_credit_limit, :calculate_shipping_charges]
- def check_credit_limit(order)
+ def check_credit_limit(book)
...
end
- def calculate_shipping_charges(order)
+ def calculate_shipping_charges(book)
...
end
end
@@ -2306,10 +2316,10 @@ If a `before_add` callback throws an exception, the object does not get added to
You're not limited to the functionality that Rails automatically builds into association proxy objects. You can also extend these objects through anonymous modules, adding new finders, creators, or other methods. For example:
```ruby
-class Customer < ApplicationRecord
- has_many :orders do
- def find_by_order_prefix(order_number)
- find_by(region_id: order_number[0..2])
+class Author < ApplicationRecord
+ has_many :books do
+ def find_by_book_prefix(book_number)
+ find_by(category_id: book_number[0..2])
end
end
end
@@ -2324,8 +2334,8 @@ module FindRecentExtension
end
end
-class Customer < ApplicationRecord
- has_many :orders, -> { extending FindRecentExtension }
+class Author < ApplicationRecord
+ has_many :books, -> { extending FindRecentExtension }
end
class Supplier < ApplicationRecord
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 2ce39d7ed3..2c363c55da 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -1,8 +1,12 @@
+* Bring back `TEST=` env for `rake test` task.
+
+ *Yves Senn*
+
* Specify log file names or all logs to clear `rake log:clear`
Specify which logs to clear when using the `rake log:clear` task, e.g. `rake log:clear LOGS=test,staging`
- Clear all logs from log/*.log e.g. `rake log:clear ENV['LOGS']=all`
+ Clear all logs from log/*.log e.g. `rake log:clear LOGS=all`
By default `rake log:clear` clears standard environment log files i.e. 'development,test,production'
diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb
index 45d649ec31..27cbaf360a 100644
--- a/railties/lib/rails/commands/server.rb
+++ b/railties/lib/rails/commands/server.rb
@@ -6,6 +6,8 @@ require 'rails'
module Rails
class Server < ::Rack::Server
class Options
+ DEFAULT_PID_PATH = File.expand_path("tmp/pids/server.pid").freeze
+
def parse!(args)
args, options = args.dup, {}
@@ -91,7 +93,7 @@ module Rails
environment: (ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development").dup,
daemonize: false,
caching: false,
- pid: File.expand_path("tmp/pids/server.pid")
+ pid: Options::DEFAULT_PID_PATH
})
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
index 78f4530514..f613b40f80 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/active_record_belongs_to_required_by_default.rb
@@ -1,5 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Require `belongs_to` associations by default. This is a new Rails 5.0 default,
-# so introduced as a config to ensure apps made with earlier versions of Rails aren't affected when upgrading.
+# Require `belongs_to` associations by default. This is a new Rails 5.0
+# default, so it is introduced as a configuration option to ensure that apps
+# made on earlier versions of Rails are not affected when upgrading.
Rails.application.config.active_record.belongs_to_required_by_default = true
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
index 0b718aa1c6..649e82280e 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/callback_terminator.rb
@@ -1,5 +1,6 @@
# Be sure to restart your server when you modify this file.
-# Do not halt callback chains when a callback returns false. This is a new Rails 5.0 default,
-# so introduced as a config to ensure apps made with earlier versions of Rails aren't affected when upgrading.
+# Do not halt callback chains when a callback returns false. This is a new
+# Rails 5.0 default, so it is introduced as a configuration option to ensure
+# that apps made with earlier versions of Rails are not affected when upgrading.
ActiveSupport.halt_callback_chains_on_return_false = false
diff --git a/railties/lib/rails/test_unit/minitest_plugin.rb b/railties/lib/rails/test_unit/minitest_plugin.rb
index d4ab2ada66..29a3d991b8 100644
--- a/railties/lib/rails/test_unit/minitest_plugin.rb
+++ b/railties/lib/rails/test_unit/minitest_plugin.rb
@@ -80,7 +80,8 @@ module Minitest
Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner)
end
- self.reporter.reporters.clear # Replace progress reporter for colors.
+ # Replace progress reporter for colors.
+ self.reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) }
self.reporter << SuppressedSummaryReporter.new(options[:io], options)
self.reporter << ::Rails::TestUnitReporter.new(options[:io], options)
end
diff --git a/railties/lib/rails/test_unit/testing.rake b/railties/lib/rails/test_unit/testing.rake
index 6676c6a079..41921e43f3 100644
--- a/railties/lib/rails/test_unit/testing.rake
+++ b/railties/lib/rails/test_unit/testing.rake
@@ -7,7 +7,12 @@ task default: :test
desc "Runs all tests in test folder"
task :test do
$: << "test"
- Minitest.rake_run(["test"])
+ pattern = if ENV.key?('TEST')
+ ENV['TEST']
+ else
+ "test"
+ end
+ Minitest.rake_run([pattern])
end
namespace :test do
diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb
index 5ea4b28acb..639875dd6e 100644
--- a/railties/test/application/rake_test.rb
+++ b/railties/test/application/rake_test.rb
@@ -30,7 +30,7 @@ module ApplicationTests
env RAILS_ENV=production bin/rake db:create db:migrate;
env RAILS_ENV=production bin/rake db:test:prepare test 2>&1`
- assert_match /ActiveRecord::ProtectedEnvironmentError/, output
+ assert_match(/ActiveRecord::ProtectedEnvironmentError/, output)
end
end
@@ -40,7 +40,7 @@ module ApplicationTests
env RAILS_ENV=test bin/rake db:create db:migrate;
env RAILS_ENV=test bin/rake db:test:prepare test 2>&1`
- refute_match /ActiveRecord::ProtectedEnvironmentError/, output
+ refute_match(/ActiveRecord::ProtectedEnvironmentError/, output)
end
end
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index ae8a73842c..bb6c6574c5 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -450,6 +450,17 @@ module ApplicationTests
assert_match(%r{cannot load such file.+test/not_exists\.rb}, error)
end
+ def test_pass_TEST_env_on_rake_test
+ create_test_file :models, 'account'
+ create_test_file :models, 'post', pass: false
+
+ output = Dir.chdir(app_path) { `bin/rake test TEST=test/models/post_test.rb` }
+
+ assert_match "PostTest", output, "passing TEST= should run selected test"
+ assert_no_match "AccountTest", output, "passing TEST= should only run selected test"
+ assert_match '1 runs, 1 assertions', output
+ end
+
private
def run_test_command(arguments = 'test/unit/test_test.rb')
Dir.chdir(app_path) { `bin/rails t #{arguments}` }
diff --git a/railties/test/commands/server_test.rb b/railties/test/commands/server_test.rb
index 3be4a74f74..0c49bd9c53 100644
--- a/railties/test/commands/server_test.rb
+++ b/railties/test/commands/server_test.rb
@@ -108,4 +108,13 @@ class Rails::ServerTest < ActiveSupport::TestCase
end
end
end
+
+ def test_default_options
+ server = Rails::Server.new
+ old_default_options = server.default_options
+
+ Dir.chdir("..") do
+ assert_equal old_default_options, server.default_options
+ end
+ end
end