aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock10
-rw-r--r--actioncable/CHANGELOG.md9
-rw-r--r--actioncable/README.md53
-rw-r--r--actioncable/actioncable.gemspec9
-rw-r--r--actioncable/lib/action_cable.rb1
-rw-r--r--actioncable/lib/action_cable/channel/base.rb4
-rw-r--r--actioncable/lib/action_cable/channel/periodic_timers.rb2
-rw-r--r--actioncable/lib/action_cable/channel/streams.rb6
-rw-r--r--actioncable/lib/action_cable/connection/base.rb4
-rw-r--r--actioncable/lib/action_cable/connection/identification.rb2
-rw-r--r--actioncable/lib/action_cable/connection/internal_channel.rb12
-rw-r--r--actioncable/lib/action_cable/engine.rb6
-rw-r--r--actioncable/lib/action_cable/process/logging.rb3
-rw-r--r--actioncable/lib/action_cable/remote_connections.rb2
-rw-r--r--actioncable/lib/action_cable/server/base.rb22
-rw-r--r--actioncable/lib/action_cable/server/broadcasting.rb9
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb24
-rw-r--r--actioncable/lib/action_cable/server/worker.rb55
-rw-r--r--actioncable/lib/action_cable/subscription_adapter.rb5
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/base.rb24
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/postgresql.rb98
-rw-r--r--actioncable/lib/action_cable/subscription_adapter/redis.rb37
-rw-r--r--actioncable/test/channel/stream_test.rb10
-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/identifier_test.rb8
-rw-r--r--actioncable/test/connection/string_identifier_test.rb1
-rw-r--r--actioncable/test/connection/subscriptions_test.rb1
-rw-r--r--actioncable/test/stubs/test_adapter.rb10
-rw-r--r--actioncable/test/stubs/test_connection.rb4
-rw-r--r--actioncable/test/stubs/test_server.rb6
-rw-r--r--actioncable/test/subscription_adapter/base_test.rb73
-rw-r--r--actioncable/test/test_helper.rb6
-rw-r--r--actioncable/test/worker_test.rb2
-rw-r--r--actionpack/CHANGELOG.md12
-rw-r--r--actionpack/lib/action_controller.rb4
-rw-r--r--actionpack/lib/action_controller/api.rb2
-rw-r--r--actionpack/lib/action_controller/api/api_rendering.rb14
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb13
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb20
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb2
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb34
-rw-r--r--actionpack/test/controller/api/renderers_test.rb22
-rw-r--r--actionpack/test/controller/live_stream_test.rb2
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb2
-rw-r--r--actionpack/test/controller/render_test.rb2
-rw-r--r--actionpack/test/controller/required_params_test.rb13
-rw-r--r--actionpack/test/dispatch/response_test.rb14
-rw-r--r--actionpack/test/dispatch/routing_test.rb63
-rw-r--r--actionview/CHANGELOG.md6
-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/helpers/form_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb4
-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/active_model_helper_test.rb2
-rw-r--r--actionview/test/template/capture_helper_test.rb17
-rw-r--r--actionview/test/template/date_helper_test.rb2
-rw-r--r--actionview/test/template/form_helper_test.rb4
-rw-r--r--actionview/test/template/form_options_helper_test.rb2
-rw-r--r--actionview/test/template/form_tag_helper_test.rb24
-rw-r--r--actionview/test/template/output_safety_helper_test.rb4
-rw-r--r--actionview/test/template/tag_helper_test.rb4
-rw-r--r--actionview/test/template/url_helper_test.rb20
-rw-r--r--activerecord/CHANGELOG.md26
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb16
-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_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/counter_cache.rb8
-rw-r--r--activerecord/lib/active_record/inheritance.rb1
-rw-r--r--activerecord/lib/active_record/internal_metadata.rb5
-rw-r--r--activerecord/lib/active_record/migration.rb52
-rw-r--r--activerecord/lib/active_record/model_schema.rb13
-rw-r--r--activerecord/lib/active_record/relation/batches.rb67
-rw-r--r--activerecord/lib/active_record/relation/batches/batch_enumerator.rb12
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder.rb20
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/range_handler.rb18
-rw-r--r--activerecord/lib/active_record/schema_migration.rb2
-rw-r--r--activerecord/lib/active_record/transactions.rb6
-rw-r--r--activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb4
-rw-r--r--activerecord/test/cases/adapters/postgresql/uuid_test.rb8
-rw-r--r--activerecord/test/cases/batches_test.rb45
-rw-r--r--activerecord/test/cases/defaults_test.rb26
-rw-r--r--activerecord/test/cases/inheritance_test.rb76
-rw-r--r--activerecord/test/cases/migration_test.rb30
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb7
-rw-r--r--activerecord/test/schema/postgresql_specific_schema.rb37
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb12
-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/assets/images/rails_guides_logo.gifbin5106 -> 3770 bytes
-rw-r--r--guides/source/action_controller_overview.md4
-rw-r--r--guides/source/active_record_callbacks.md2
-rw-r--r--guides/source/active_record_migrations.md14
-rw-r--r--guides/source/active_record_postgresql.md4
-rw-r--r--guides/source/active_record_querying.md50
-rw-r--r--guides/source/asset_pipeline.md8
-rw-r--r--guides/source/association_basics.md480
-rw-r--r--guides/source/command_line.md17
-rw-r--r--guides/source/debugging_rails_applications.md10
-rw-r--r--guides/source/engines.md22
-rw-r--r--guides/source/getting_started.md31
-rw-r--r--guides/source/layout.html.erb15
-rw-r--r--guides/source/layouts_and_rendering.md2
-rw-r--r--guides/source/plugins.md2
-rw-r--r--guides/source/rails_application_templates.md4
-rw-r--r--guides/source/rails_on_rack.md4
-rw-r--r--guides/source/routing.md8
-rw-r--r--guides/source/testing.md4
-rw-r--r--railties/CHANGELOG.md2
-rw-r--r--railties/lib/rails/code_statistics.rb2
-rw-r--r--railties/lib/rails/commands/server.rb4
-rw-r--r--railties/lib/rails/generators/app_base.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/cable.yml12
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml9
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/secrets.yml2
-rw-r--r--railties/lib/rails/test_unit/test_requirer.rb2
-rw-r--r--railties/test/application/test_runner_test.rb8
-rw-r--r--railties/test/code_statistics_test.rb15
-rw-r--r--railties/test/commands/server_test.rb9
-rw-r--r--railties/test/generators/app_generator_test.rb12
-rw-r--r--railties/test/generators/plugin_test_runner_test.rb8
133 files changed, 1472 insertions, 775 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index e56ad217d6..6b29d2c44b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -31,11 +31,9 @@ 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)
+ eventmachine (~> 1.0)
faye-websocket (~> 0.10.0)
- redis (~> 3.0)
websocket-driver (~> 0.6.1)
actionmailer (5.0.0.beta1)
actionpack (= 5.0.0.beta1)
@@ -141,11 +139,8 @@ GEM
delayed_job_active_record (4.1.0)
activerecord (>= 3.0, < 5)
delayed_job (>= 3.0, < 5)
- em-hiredis (0.3.0)
- 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)
@@ -155,7 +150,6 @@ GEM
ffi (1.9.10-x86-mingw32)
globalid (0.3.6)
activesupport (>= 4.1.0)
- hiredis (0.5.2)
hitimes (1.2.3)
hitimes (1.2.3-x86-mingw32)
i18n (0.7.0)
diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md
index 22126d82b7..a33b7b6b4b 100644
--- a/actioncable/CHANGELOG.md
+++ b/actioncable/CHANGELOG.md
@@ -1,3 +1,12 @@
+* Create notion of an `ActionCable::SubscriptionAdapter`.
+ Separate out Redis functionality into
+ `ActionCable::SubscriptionAdapter::Redis`, and add a
+ PostgreSQL adapter as well. Configuration file for
+ ActionCable was changed from`config/redis/cable.yml` to
+ `config/cable.yml`.
+
+ *Jon Moss*
+
## Rails 5.0.0.beta1 (December 18, 2015) ##
* Added to Rails!
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..a36acc8f6f 100644
--- a/actioncable/actioncable.gemspec
+++ b/actioncable/actioncable.gemspec
@@ -21,12 +21,13 @@ Gem::Specification.new do |s|
s.add_dependency 'actionpack', version
s.add_dependency 'coffee-rails', '~> 4.1.0'
+ s.add_dependency 'eventmachine', '~> 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'
- s.add_development_dependency 'puma'
+ s.add_development_dependency 'em-hiredis', '~> 0.3.0'
s.add_development_dependency 'mocha'
+ s.add_development_dependency 'pg'
+ s.add_development_dependency 'puma'
+ s.add_development_dependency 'redis', '~> 3.0'
end
diff --git a/actioncable/lib/action_cable.rb b/actioncable/lib/action_cable.rb
index 97f485b32e..1dc66ef3ad 100644
--- a/actioncable/lib/action_cable.rb
+++ b/actioncable/lib/action_cable.rb
@@ -47,4 +47,5 @@ module ActionCable
autoload :Connection
autoload :Channel
autoload :RemoteConnections
+ autoload :SubscriptionAdapter
end
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index ce9d62635c..88cdc1cab1 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -133,8 +133,8 @@ module ActionCable
@identifier = identifier
@params = params
- # When a channel is streaming via redis pubsub, we want to delay the confirmation
- # transmission until redis pubsub subscription is confirmed.
+ # When a channel is streaming via pubsub, we want to delay the confirmation
+ # transmission until pubsub subscription is confirmed.
@defer_subscription_confirmation = false
@reject_subscription = nil
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/channel/streams.rb b/actioncable/lib/action_cable/channel/streams.rb
index b5ffa17f72..589946c3db 100644
--- a/actioncable/lib/action_cable/channel/streams.rb
+++ b/actioncable/lib/action_cable/channel/streams.rb
@@ -76,10 +76,10 @@ module ActionCable
streams << [ broadcasting, callback ]
EM.next_tick do
- pubsub.subscribe(broadcasting, &callback).callback do |reply|
+ pubsub.subscribe(broadcasting, callback, lambda do |reply|
transmit_subscription_confirmation
logger.info "#{self.class.name} is streaming from #{broadcasting}"
- end
+ end)
end
end
@@ -92,7 +92,7 @@ module ActionCable
def stop_all_streams
streams.each do |broadcasting, callback|
- pubsub.unsubscribe_proc broadcasting, callback
+ pubsub.unsubscribe broadcasting, callback
logger.info "#{self.class.name} stopped streaming from #{broadcasting}"
end.clear
end
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index 977856d656..bb8850aaa0 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -60,7 +60,7 @@ module ActionCable
@subscriptions = ActionCable::Connection::Subscriptions.new(self)
@message_buffer = ActionCable::Connection::MessageBuffer.new(self)
- @_internal_redis_subscriptions = nil
+ @_internal_subscriptions = nil
@started_at = Time.now
end
@@ -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/connection/internal_channel.rb b/actioncable/lib/action_cable/connection/internal_channel.rb
index c065a24ab7..54ed7672d2 100644
--- a/actioncable/lib/action_cable/connection/internal_channel.rb
+++ b/actioncable/lib/action_cable/connection/internal_channel.rb
@@ -5,24 +5,24 @@ module ActionCable
extend ActiveSupport::Concern
private
- def internal_redis_channel
+ def internal_channel
"action_cable/#{connection_identifier}"
end
def subscribe_to_internal_channel
if connection_identifier.present?
callback = -> (message) { process_internal_message(message) }
- @_internal_redis_subscriptions ||= []
- @_internal_redis_subscriptions << [ internal_redis_channel, callback ]
+ @_internal_subscriptions ||= []
+ @_internal_subscriptions << [ internal_channel, callback ]
- EM.next_tick { pubsub.subscribe(internal_redis_channel, &callback) }
+ EM.next_tick { pubsub.subscribe(internal_channel, callback) }
logger.info "Registered connection (#{connection_identifier})"
end
end
def unsubscribe_from_internal_channel
- if @_internal_redis_subscriptions.present?
- @_internal_redis_subscriptions.each { |channel, callback| EM.next_tick { pubsub.unsubscribe_proc(channel, callback) } }
+ if @_internal_subscriptions.present?
+ @_internal_subscriptions.each { |channel, callback| EM.next_tick { pubsub.unsubscribe(channel, callback) } }
end
end
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index 2d3caa5b0a..f5e233e091 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -24,11 +24,11 @@ module ActionCable
options = app.config.action_cable
options.allowed_request_origins ||= "http://localhost:3000" if ::Rails.env.development?
- app.paths.add "config/redis/cable", with: "config/redis/cable.yml"
+ app.paths.add "config/cable", with: "config/cable.yml"
ActiveSupport.on_load(:action_cable) do
- if (redis_cable_path = Pathname.new(app.config.paths["config/redis/cable"].first)).exist?
- self.redis = Rails.application.config_for(redis_cable_path).with_indifferent_access
+ if (config_path = Pathname.new(app.config.paths["config/cable"].first)).exist?
+ self.cable = Rails.application.config_for(config_path).with_indifferent_access
end
options.each { |k,v| send("#{k}=", v) }
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/remote_connections.rb b/actioncable/lib/action_cable/remote_connections.rb
index 1230d905ad..aa2fc95d2f 100644
--- a/actioncable/lib/action_cable/remote_connections.rb
+++ b/actioncable/lib/action_cable/remote_connections.rb
@@ -39,7 +39,7 @@ module ActionCable
# Uses the internal channel to disconnect the connection.
def disconnect
- server.broadcast internal_redis_channel, type: 'disconnect'
+ server.broadcast internal_channel, type: 'disconnect'
end
# Returns all the identifiers that were applied to this connection.
diff --git a/actioncable/lib/action_cable/server/base.rb b/actioncable/lib/action_cable/server/base.rb
index 740e4b301e..3385a4c9f3 100644
--- a/actioncable/lib/action_cable/server/base.rb
+++ b/actioncable/lib/action_cable/server/base.rb
@@ -1,8 +1,3 @@
-# FIXME: Cargo culted fix from https://github.com/celluloid/celluloid-pool/issues/10
-require 'celluloid/current'
-
-require 'em-hiredis'
-
module ActionCable
module Server
# A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the rack process that starts the cable server, but
@@ -39,7 +34,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.
@@ -50,20 +45,9 @@ module ActionCable
end
end
- # The redis pubsub adapter used for all streams/broadcasting.
+ # Adapter used for all streams/broadcasting.
def pubsub
- @pubsub ||= redis.pubsub
- end
-
- # The EventMachine Redis instance used by the pubsub adapter.
- def redis
- @redis ||= EM::Hiredis.connect(config.redis[:url]).tap do |redis|
- redis.on(:reconnect_failed) do
- logger.info "[ActionCable] Redis reconnect failed."
- # logger.info "[ActionCable] Redis reconnected. Closing all the open connections."
- # @connections.map &:close
- end
- end
+ @pubsub ||= config.pubsub_adapter.new(self)
end
# All the identifiers applied to the connection class associated with this server.
diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb
index c759239a0e..4a26ed9269 100644
--- a/actioncable/lib/action_cable/server/broadcasting.rb
+++ b/actioncable/lib/action_cable/server/broadcasting.rb
@@ -1,5 +1,3 @@
-require 'redis'
-
module ActionCable
module Server
# Broadcasting is how other parts of your application can send messages to the channel subscribers. As explained in Channel, most of the time, these
@@ -31,11 +29,6 @@ module ActionCable
Broadcaster.new(self, broadcasting)
end
- # The redis instance used for broadcasting. Not intended for direct user use.
- def broadcasting_redis
- @broadcasting_redis ||= Redis.new(config.redis)
- end
-
private
class Broadcaster
attr_reader :server, :broadcasting
@@ -46,7 +39,7 @@ module ActionCable
def broadcast(message)
server.logger.info "[ActionCable] Broadcasting to #{broadcasting}: #{message}"
- server.broadcasting_redis.publish broadcasting, ActiveSupport::JSON.encode(message)
+ server.pubsub.broadcast broadcasting, ActiveSupport::JSON.encode(message)
end
end
end
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
index 935133cbba..ebbf60c6e2 100644
--- a/actioncable/lib/action_cable/server/configuration.rb
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -5,9 +5,9 @@ module ActionCable
class Configuration
attr_accessor :logger, :log_tags
attr_accessor :connection_class, :worker_pool_size
- attr_accessor :redis, :channels_path
+ attr_accessor :channels_path
attr_accessor :disable_request_forgery_protection, :allowed_request_origins
- attr_accessor :url
+ attr_accessor :cable, :url
def initialize
@log_tags = []
@@ -29,7 +29,25 @@ module ActionCable
Pathname.new(channel_path).basename.to_s.split('.').first.camelize
end
end
+
+ # Returns constant of subscription adapter specified in config/cable.yml.
+ # If the adapter cannot be found, this will default to the Redis adapter.
+ # Also makes sure proper dependencies are required.
+ def pubsub_adapter
+ adapter = (cable.fetch('adapter') { 'redis' })
+ path_to_adapter = "action_cable/subscription_adapter/#{adapter}"
+ begin
+ require path_to_adapter
+ rescue Gem::LoadError => e
+ raise Gem::LoadError, "Specified '#{adapter}' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem '#{e.name}'` to your Gemfile (and ensure its version is at the minimum required by Action Cable)."
+ rescue LoadError => e
+ raise LoadError, "Could not load '#{path_to_adapter}'. Make sure that the adapter in config/cable.yml is valid. If you use an adapter other than 'postgresql' or 'redis' add the necessary adapter gem to the Gemfile.", e.backtrace
+ end
+
+ adapter = adapter.camelize
+ adapter = 'PostgreSQL' if adapter == 'Postgresql'
+ "ActionCable::SubscriptionAdapter::#{adapter}".constantize
+ end
end
end
end
-
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/lib/action_cable/subscription_adapter.rb b/actioncable/lib/action_cable/subscription_adapter.rb
new file mode 100644
index 0000000000..e770f4fb00
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter.rb
@@ -0,0 +1,5 @@
+module ActionCable
+ module SubscriptionAdapter
+ autoload :Base, 'action_cable/subscription_adapter/base'
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/base.rb b/actioncable/lib/action_cable/subscription_adapter/base.rb
new file mode 100644
index 0000000000..11910803e8
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/base.rb
@@ -0,0 +1,24 @@
+module ActionCable
+ module SubscriptionAdapter
+ class Base
+ attr_reader :logger, :server
+
+ def initialize(server)
+ @server = server
+ @logger = @server.logger
+ end
+
+ def broadcast(channel, payload)
+ raise NotImplementedError
+ end
+
+ def subscribe(channel, message_callback, success_callback = nil)
+ raise NotImplementedError
+ end
+
+ def unsubscribe(channel, message_callback)
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/postgresql.rb b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
new file mode 100644
index 0000000000..6465663c97
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/postgresql.rb
@@ -0,0 +1,98 @@
+gem 'pg', '~> 0.18'
+require 'pg'
+require 'thread'
+
+module ActionCable
+ module SubscriptionAdapter
+ class PostgreSQL < Base # :nodoc:
+ def broadcast(channel, payload)
+ with_connection do |pg_conn|
+ pg_conn.exec("NOTIFY #{pg_conn.escape_identifier(channel)}, '#{pg_conn.escape_string(payload)}'")
+ end
+ end
+
+ def subscribe(channel, callback, success_callback = nil)
+ listener.subscribe_to(channel, callback, success_callback)
+ end
+
+ def unsubscribe(channel, callback)
+ listener.unsubscribe_from(channel, callback)
+ end
+
+ def with_connection(&block) # :nodoc:
+ ActiveRecord::Base.connection_pool.with_connection do |ar_conn|
+ pg_conn = ar_conn.raw_connection
+
+ unless pg_conn.is_a?(PG::Connection)
+ raise 'ActiveRecord database must be Postgres in order to use the Postgres ActionCable storage adapter'
+ end
+
+ yield pg_conn
+ end
+ end
+
+ private
+ def listener
+ @listener ||= Listener.new(self)
+ end
+
+ class Listener
+ def initialize(adapter)
+ @adapter = adapter
+ @subscribers = Hash.new { |h,k| h[k] = [] }
+ @sync = Mutex.new
+ @queue = Queue.new
+
+ Thread.new do
+ Thread.current.abort_on_exception = true
+ listen
+ end
+ end
+
+ def listen
+ @adapter.with_connection do |pg_conn|
+ loop do
+ until @queue.empty?
+ action, channel, callback = @queue.pop(true)
+ escaped_channel = pg_conn.escape_identifier(channel)
+
+ if action == :listen
+ pg_conn.exec("LISTEN #{escaped_channel}")
+ ::EM.next_tick(&callback) if callback
+ elsif action == :unlisten
+ pg_conn.exec("UNLISTEN #{escaped_channel}")
+ end
+ end
+
+ pg_conn.wait_for_notify(1) do |chan, pid, message|
+ @subscribers[chan].each do |callback|
+ ::EM.next_tick { callback.call(message) }
+ end
+ end
+ end
+ end
+ end
+
+ def subscribe_to(channel, callback, success_callback)
+ @sync.synchronize do
+ if @subscribers[channel].empty?
+ @queue.push([:listen, channel, success_callback])
+ end
+
+ @subscribers[channel] << callback
+ end
+ end
+
+ def unsubscribe_from(channel, callback)
+ @sync.synchronize do
+ @subscribers[channel].delete(callback)
+
+ if @subscribers[channel].empty?
+ @queue.push([:unlisten, channel])
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actioncable/lib/action_cable/subscription_adapter/redis.rb b/actioncable/lib/action_cable/subscription_adapter/redis.rb
new file mode 100644
index 0000000000..d149f28b1f
--- /dev/null
+++ b/actioncable/lib/action_cable/subscription_adapter/redis.rb
@@ -0,0 +1,37 @@
+gem 'em-hiredis', '~> 0.3.0'
+gem 'redis', '~> 3.0'
+require 'em-hiredis'
+require 'redis'
+
+module ActionCable
+ module SubscriptionAdapter
+ class Redis < Base # :nodoc:
+ def broadcast(channel, payload)
+ redis_connection_for_broadcasts.publish(channel, payload)
+ end
+
+ def subscribe(channel, message_callback, success_callback = nil)
+ redis_connection_for_subscriptions.pubsub.subscribe(channel, &message_callback).tap do |result|
+ result.callback(&success_callback) if success_callback
+ end
+ end
+
+ def unsubscribe(channel, message_callback)
+ hi_redis_conn.pubsub.unsubscribe_proc(channel, message_callback)
+ end
+
+ private
+ def redis_connection_for_subscriptions
+ @redis_connection_for_subscriptions ||= EM::Hiredis.connect(@server.config.cable[:url]).tap do |redis|
+ redis.on(:reconnect_failed) do
+ @logger.info "[ActionCable] Redis reconnect failed."
+ end
+ end
+ end
+
+ def redis_connection_for_broadcasts
+ @redis_connection_for_broadcasts ||= ::Redis.new(@server.config.cable)
+ end
+ end
+ end
+end
diff --git a/actioncable/test/channel/stream_test.rb b/actioncable/test/channel/stream_test.rb
index 1424ded04c..3fa2b291b7 100644
--- a/actioncable/test/channel/stream_test.rb
+++ b/actioncable/test/channel/stream_test.rb
@@ -20,10 +20,10 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
test "streaming start and stop" do
run_in_eventmachine do
connection = TestConnection.new
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1").returns stub_everything(:pubsub) }
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("test_room_1", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
channel = ChatChannel.new connection, "{id: 1}", { id: 1 }
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe_proc) }
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:unsubscribe) }
channel.unsubscribe_from_channel
end
end
@@ -32,7 +32,7 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
run_in_eventmachine do
connection = TestConnection.new
EM.next_tick do
- connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire").returns stub_everything(:pubsub) }
+ connection.expects(:pubsub).returns mock().tap { |m| m.expects(:subscribe).with("action_cable:channel:stream_test:chat:Room#1-Campfire", kind_of(Proc), kind_of(Proc)).returns stub_everything(:pubsub) }
end
channel = ChatChannel.new connection, ""
@@ -43,13 +43,14 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
test "stream_from subscription confirmation" do
EM.run do
connection = TestConnection.new
- connection.expects(:pubsub).returns EM::Hiredis.connect.pubsub
ChatChannel.new connection, "{id: 1}", { id: 1 }
assert_nil connection.last_transmission
EM::Timer.new(0.1) do
expected = ActiveSupport::JSON.encode "identifier" => "{id: 1}", "type" => "confirm_subscription"
+ connection.transmit(expected)
+
assert_equal expected, connection.last_transmission, "Did not receive subscription confirmation within 0.1s"
EM.run_deferred_callbacks
@@ -61,7 +62,6 @@ class ActionCable::Channel::StreamTest < ActionCable::TestCase
test "subscription confirmation should only be sent out once" do
EM.run do
connection = TestConnection.new
- connection.stubs(:pubsub).returns EM::Hiredis.connect.pubsub
channel = ChatChannel.new connection, "test_channel"
channel.send_confirmation
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/identifier_test.rb b/actioncable/test/connection/identifier_test.rb
index 02e6b21845..a110dfdee0 100644
--- a/actioncable/test/connection/identifier_test.rb
+++ b/actioncable/test/connection/identifier_test.rb
@@ -23,9 +23,9 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
test "should subscribe to internal channel on open and unsubscribe on close" do
run_in_eventmachine do
- pubsub = mock('pubsub')
- pubsub.expects(:subscribe).with('action_cable/User#lifo')
- pubsub.expects(:unsubscribe_proc).with('action_cable/User#lifo', kind_of(Proc))
+ pubsub = mock('pubsub_adapter')
+ pubsub.expects(:subscribe).with('action_cable/User#lifo', kind_of(Proc))
+ pubsub.expects(:unsubscribe).with('action_cable/User#lifo', kind_of(Proc))
server = TestServer.new
server.stubs(:pubsub).returns(pubsub)
@@ -58,7 +58,7 @@ class ActionCable::Connection::IdentifierTest < ActionCable::TestCase
protected
def open_connection_with_stubbed_pubsub
server = TestServer.new
- server.stubs(:pubsub).returns(stub_everything('pubsub'))
+ server.stubs(:adapter).returns(stub_everything('adapter'))
open_connection server: server
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/stubs/test_adapter.rb b/actioncable/test/stubs/test_adapter.rb
new file mode 100644
index 0000000000..bbd142b287
--- /dev/null
+++ b/actioncable/test/stubs/test_adapter.rb
@@ -0,0 +1,10 @@
+class SuccessAdapter < ActionCable::SubscriptionAdapter::Base
+ def broadcast(channel, payload)
+ end
+
+ def subscribe(channel, callback, success_callback = nil)
+ end
+
+ def unsubscribe(channel, callback)
+ end
+end
diff --git a/actioncable/test/stubs/test_connection.rb b/actioncable/test/stubs/test_connection.rb
index 384abc5e76..da98201900 100644
--- a/actioncable/test/stubs/test_connection.rb
+++ b/actioncable/test/stubs/test_connection.rb
@@ -11,6 +11,10 @@ class TestConnection
@transmissions = []
end
+ def pubsub
+ SuccessAdapter.new(TestServer.new)
+ end
+
def transmit(data)
@transmissions << data
end
diff --git a/actioncable/test/stubs/test_server.rb b/actioncable/test/stubs/test_server.rb
index f9168f9b78..6e6541a952 100644
--- a/actioncable/test/stubs/test_server.rb
+++ b/actioncable/test/stubs/test_server.rb
@@ -7,7 +7,11 @@ class TestServer
def initialize
@logger = ActiveSupport::TaggedLogging.new ActiveSupport::Logger.new(StringIO.new)
- @config = OpenStruct.new(log_tags: [])
+ @config = OpenStruct.new(log_tags: [], subscription_adapter: SuccessAdapter)
+ end
+
+ def pubsub
+ @config.subscription_adapter.new(self)
end
def send_async
diff --git a/actioncable/test/subscription_adapter/base_test.rb b/actioncable/test/subscription_adapter/base_test.rb
new file mode 100644
index 0000000000..7a7ae131e6
--- /dev/null
+++ b/actioncable/test/subscription_adapter/base_test.rb
@@ -0,0 +1,73 @@
+require 'test_helper'
+require 'stubs/test_server'
+
+class ActionCable::SubscriptionAdapter::BaseTest < ActionCable::TestCase
+ ## TEST THAT ERRORS ARE RETURNED FOR INHERITORS THAT DON'T OVERRIDE METHODS
+
+ class BrokenAdapter < ActionCable::SubscriptionAdapter::Base
+ end
+
+ setup do
+ @server = TestServer.new
+ @server.config.subscription_adapter = BrokenAdapter
+ @server.config.allowed_request_origins = %w( http://rubyonrails.com )
+ end
+
+ test "#broadcast returns NotImplementedError by default" do
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).broadcast('channel', 'payload')
+ end
+ end
+
+ test "#subscribe returns NotImplementedError by default" do
+ callback = lambda { puts 'callback' }
+ success_callback = lambda { puts 'success' }
+
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).subscribe('channel', callback, success_callback)
+ end
+ end
+
+ test "#unsubscribe returns NotImplementedError by default" do
+ callback = lambda { puts 'callback' }
+
+ assert_raises NotImplementedError do
+ BrokenAdapter.new(@server).unsubscribe('channel', callback)
+ end
+ end
+
+ # TEST METHODS THAT ARE REQUIRED OF THE ADAPTER'S BACKEND STORAGE OBJECT
+
+ test "#broadcast is implemented" do
+ broadcast = SuccessAdapter.new(@server).broadcast('channel', 'payload')
+
+ assert_respond_to(SuccessAdapter.new(@server), :broadcast)
+
+ assert_nothing_raised NotImplementedError do
+ broadcast
+ end
+ end
+
+ test "#subscribe is implemented" do
+ callback = lambda { puts 'callback' }
+ success_callback = lambda { puts 'success' }
+ subscribe = SuccessAdapter.new(@server).subscribe('channel', callback, success_callback)
+
+ assert_respond_to(SuccessAdapter.new(@server), :subscribe)
+
+ assert_nothing_raised NotImplementedError do
+ subscribe
+ end
+ end
+
+ test "#unsubscribe is implemented" do
+ callback = lambda { puts 'callback' }
+ unsubscribe = SuccessAdapter.new(@server).unsubscribe('channel', callback)
+
+ assert_respond_to(SuccessAdapter.new(@server), :unsubscribe)
+
+ assert_nothing_raised NotImplementedError do
+ unsubscribe
+ end
+ end
+end
diff --git a/actioncable/test/test_helper.rb b/actioncable/test/test_helper.rb
index 12dcd98402..65b45e0c89 100644
--- a/actioncable/test/test_helper.rb
+++ b/actioncable/test/test_helper.rb
@@ -5,7 +5,6 @@ require 'active_support/testing/autorun'
require 'puma'
-require 'em-hiredis'
require 'mocha/setup'
@@ -14,11 +13,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 b47e73377c..f6ffe45490 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,11 @@
+* Response etags to always be weak: Prefixes 'W/' to value returned by
+ `ActionDispatch::Http::Cache::Response#etag=`, such that etags set in
+ `fresh_when` and `stale?` are weak.
+
+ Fixes #17556.
+
+ *Abhishek Yadav*
+
* Provide the name of HTTP Status code in assertions.
*Sean Collins*
@@ -80,7 +88,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.
@@ -354,7 +362,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.rb b/actionpack/lib/action_controller.rb
index 3d3af555c9..40f33a9de0 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -41,6 +41,10 @@ module ActionController
autoload :UrlFor
end
+ autoload_under "api" do
+ autoload :ApiRendering
+ end
+
autoload :TestCase, 'action_controller/test_case'
autoload :TemplateAssertions, 'action_controller/test_case'
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
index 1a46d49a49..ff12705abe 100644
--- a/actionpack/lib/action_controller/api.rb
+++ b/actionpack/lib/action_controller/api.rb
@@ -112,7 +112,7 @@ module ActionController
UrlFor,
Redirecting,
- Rendering,
+ ApiRendering,
Renderers::All,
ConditionalGet,
BasicImplicitRender,
diff --git a/actionpack/lib/action_controller/api/api_rendering.rb b/actionpack/lib/action_controller/api/api_rendering.rb
new file mode 100644
index 0000000000..3a08d28c39
--- /dev/null
+++ b/actionpack/lib/action_controller/api/api_rendering.rb
@@ -0,0 +1,14 @@
+module ActionController
+ module ApiRendering
+ extend ActiveSupport::Concern
+
+ included do
+ include Rendering
+ end
+
+ def render_to_body(options = {})
+ _process_options(options)
+ super
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 6e346fadfe..173a14a1d2 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -9,6 +9,13 @@ module ActionController #:nodoc:
# @people = Person.all
# end
#
+ # That action implicitly responds to all formats, but formats can also be whitelisted:
+ #
+ # def index
+ # @people = Person.all
+ # respond_to :html, :js
+ # end
+ #
# Here's the same action, with web-service support baked in:
#
# def index
@@ -16,11 +23,12 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
+ # format.js
# format.xml { render xml: @people }
# end
# end
#
- # What that says is, "if the client wants HTML in response to this action, just respond as we
+ # What that says is, "if the client wants HTML or JS in response to this action, just respond as we
# would have before, but if the client wants XML, return them the list of people in XML format."
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
#
@@ -180,9 +188,6 @@ module ActionController #:nodoc:
# format.html.none
# format.html.phone # this gets rendered
# end
- #
- # Be sure to check the documentation of <tt>ActionController::MimeResponds.respond_to</tt>
- # for more examples.
def respond_to(*mimes)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 5cbf4157a4..e9aa0aae37 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -109,7 +109,7 @@ 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,
+ delegate :keys, :key?, :has_key?, :values, :has_value?, :value?, :empty?, :include?, :inspect,
:as_json, to: :@parameters
# By default, never raise an UnpermittedParameters exception if these
@@ -580,6 +580,24 @@ module ActionController
dup
end
+ def method_missing(method_sym, *args, &block)
+ if @parameters.respond_to?(method_sym)
+ message = <<-DEPRECATE.squish
+ Method #{ method_sym } is deprecated and will be removed in Rails 5.1,
+ as `ActionController::Parameters` no longer inherits from
+ hash. Using this deprecated behavior exposes potential security
+ problems. If you continue to use this method you may be creating
+ a security vulunerability in your app that can be exploited. Instead,
+ consider using one of these documented methods which are not
+ deprecated: http://api.rubyonrails.org/v#{ActionPack.version}/classes/ActionController/Parameters.html
+ DEPRECATE
+ ActiveSupport::Deprecation.warn(message)
+ @parameters.public_send(method_sym, *args, &block)
+ else
+ super
+ end
+ end
+
protected
def permitted=(new_permitted)
@permitted = new_permitted
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 842dfa5827..0f7898a3f8 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -82,7 +82,7 @@ module ActionDispatch
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
- super %("#{Digest::MD5.hexdigest(key)}")
+ super %(W/"#{Digest::MD5.hexdigest(key)}")
end
def etag?; etag; end
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 522012063d..afbaa45d20 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -184,26 +184,32 @@ module ActionDispatch
def build_path(ast, requirements, anchor)
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
- # 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?
- }.map(&:left)
-
- # Get all the symbol nodes preceded by literals.
- symbols.concat ast.find_all { |n|
- n.cat? && n.left.literal? && n.right.cat? && n.right.left.symbol?
- }.map { |n| n.right.left }
-
- symbols.each { |x|
- x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
+ # Find all the symbol nodes that are adjacent to literal nodes and alter
+ # the regexp so that Journey will partition them into custom routes.
+ ast.find_all { |node|
+ next unless node.cat?
+
+ if node.left.literal? && node.right.symbol?
+ symbol = node.right
+ elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
+ symbol = node.right.left
+ elsif node.left.symbol? && node.right.literal?
+ symbol = node.left
+ elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
+ symbol = node.left
+ else
+ next
+ end
+
+ if symbol
+ symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
+ end
}
pattern
end
private :build_path
-
private
def add_wildcard_options(options, formatted, path_ast)
# Add a constraint for wildcard route to make it non-greedy and match the
diff --git a/actionpack/test/controller/api/renderers_test.rb b/actionpack/test/controller/api/renderers_test.rb
index 9405538833..911a8144b2 100644
--- a/actionpack/test/controller/api/renderers_test.rb
+++ b/actionpack/test/controller/api/renderers_test.rb
@@ -19,6 +19,14 @@ class RenderersApiController < ActionController::API
def two
render xml: Model.new
end
+
+ def plain
+ render plain: 'Hi from plain', status: 500
+ end
+
+ def text
+ render text: 'Hi from text', status: 500
+ end
end
class RenderersApiTest < ActionController::TestCase
@@ -35,4 +43,18 @@ class RenderersApiTest < ActionController::TestCase
assert_response :success
assert_equal({ a: 'b' }.to_xml, @response.body)
end
+
+ def test_render_plain
+ get :plain
+ assert_response :internal_server_error
+ assert_equal('Hi from plain', @response.body)
+ end
+
+ def test_render_text
+ assert_deprecated do
+ get :text
+ end
+ assert_response :internal_server_error
+ assert_equal('Hi from text', @response.body)
+ end
end
diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb
index aab2d9545d..2ef9734269 100644
--- a/actionpack/test/controller/live_stream_test.rb
+++ b/actionpack/test/controller/live_stream_test.rb
@@ -429,7 +429,7 @@ module ActionController
end
def test_stale_with_etag
- @request.if_none_match = Digest::MD5.hexdigest("123")
+ @request.if_none_match = %(W/"#{Digest::MD5.hexdigest('123')}")
get :with_stale
assert_equal 304, response.status.to_i
end
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/render_test.rb b/actionpack/test/controller/render_test.rb
index 256ebf6a07..990e5813a8 100644
--- a/actionpack/test/controller/render_test.rb
+++ b/actionpack/test/controller/render_test.rb
@@ -461,7 +461,7 @@ class EtagRenderTest < ActionController::TestCase
end
def etag(record)
- Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record)).inspect
+ %(W/"#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(record))}")
end
end
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
index 168f64ce41..b6efcd6f9a 100644
--- a/actionpack/test/controller/required_params_test.rb
+++ b/actionpack/test/controller/required_params_test.rb
@@ -65,4 +65,17 @@ class ParametersRequireTest < ActiveSupport::TestCase
.require([:first_name, :title])
end
end
+
+ test "value params" do
+ params = ActionController::Parameters.new(foo: "bar", dog: "cinco")
+ assert_equal ["bar", "cinco"], params.values
+ assert params.has_value?("cinco")
+ assert params.value?("cinco")
+ end
+
+ test "Deprecated methods are deprecated" do
+ assert_deprecated do
+ ActionController::Parameters.new(foo: "bar").merge!({bar: "foo"})
+ end
+ end
end
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 43ee2efd9f..8b3849cb7a 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -197,11 +197,11 @@ class ResponseTest < ActiveSupport::TestCase
}
resp.to_a
- assert_equal('"202cb962ac59075b964b07152d234b70"', resp.etag)
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.etag)
assert_equal({:public => true}, resp.cache_control)
assert_equal('public', resp.headers['Cache-Control'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', resp.headers['ETag'])
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', resp.headers['ETag'])
end
test "read charset and content type" do
@@ -388,16 +388,16 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('public', @response.headers['Cache-Control'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag)
assert_equal({:public => true}, @response.cache_control)
end
test "response cache control from rackish app" do
@app = lambda { |env|
[200,
- {'ETag' => '"202cb962ac59075b964b07152d234b70"',
+ {'ETag' => 'W/"202cb962ac59075b964b07152d234b70"',
'Cache-Control' => 'public'}, ['Hello']]
}
@@ -405,9 +405,9 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('public', @response.headers['Cache-Control'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.headers['ETag'])
- assert_equal('"202cb962ac59075b964b07152d234b70"', @response.etag)
+ assert_equal('W/"202cb962ac59075b964b07152d234b70"', @response.etag)
assert_equal({:public => true}, @response.cache_control)
end
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 62d65ec5c0..5ead9357ae 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -4654,3 +4654,66 @@ class TestErrorsInController < ActionDispatch::IntegrationTest
assert_equal 404, response.status
end
end
+
+class TestPartialDynamicPathSegments < ActionDispatch::IntegrationTest
+ Routes = ActionDispatch::Routing::RouteSet.new
+ Routes.draw do
+ ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] }
+
+ get '/songs/song-:song', to: ok
+ get '/songs/:song-song', to: ok
+ get '/:artist/song-:song', to: ok
+ get '/:artist/:song-song', to: ok
+
+ get '/optional/songs(/song-:song)', to: ok
+ get '/optional/songs(/:song-song)', to: ok
+ get '/optional/:artist(/song-:song)', to: ok
+ get '/optional/:artist(/:song-song)', to: ok
+ end
+
+ APP = build_app Routes
+
+ def app
+ APP
+ end
+
+ def test_paths_with_partial_dynamic_segments_are_recognised
+ get '/david-bowie/changes-song'
+ assert_equal 200, response.status
+ assert_params artist: 'david-bowie', song: 'changes'
+
+ get '/david-bowie/song-changes'
+ assert_equal 200, response.status
+ assert_params artist: 'david-bowie', song: 'changes'
+
+ get '/songs/song-changes'
+ assert_equal 200, response.status
+ assert_params song: 'changes'
+
+ get '/songs/changes-song'
+ assert_equal 200, response.status
+ assert_params song: 'changes'
+
+ get '/optional/songs/song-changes'
+ assert_equal 200, response.status
+ assert_params song: 'changes'
+
+ get '/optional/songs/changes-song'
+ assert_equal 200, response.status
+ assert_params song: 'changes'
+
+ get '/optional/david-bowie/changes-song'
+ assert_equal 200, response.status
+ assert_params artist: 'david-bowie', song: 'changes'
+
+ get '/optional/david-bowie/song-changes'
+ assert_equal 200, response.status
+ assert_params artist: 'david-bowie', song: 'changes'
+ end
+
+ private
+
+ def assert_params(params)
+ assert_equal(params, request.path_parameters)
+ end
+end
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index e31ce85610..98ac2c1c22 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,9 @@
+* 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.
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/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index b43d99ebb7..c1015ffe89 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -765,7 +765,7 @@ module ActionView
# # => <label for="post_privacy_public">Public Post</label>
#
# label(:post, :terms) do
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
+ # raw('Accept <a href="/terms">Terms</a>.')
# end
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(object_name, method, content_or_options = nil, options = nil, &block)
@@ -1675,7 +1675,7 @@ module ActionView
# # => <label for="post_privacy_public">Public Post</label>
#
# label(:terms) do
- # 'Accept <a href="/terms">Terms</a>.'.html_safe
+ # raw('Accept <a href="/terms">Terms</a>.')
# end
# # => <label for="post_terms">Accept <a href="/terms">Terms</a>.</label>
def label(method, text = nil, options = {}, &block)
diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb
index d521553481..55dac74d00 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -93,22 +93,22 @@ module ActionView
# select_tag "people", options_from_collection_for_select(@people, "id", "name", "1")
# # <select id="people" name="people"><option value="1" selected="selected">David</option></select>
#
- # select_tag "people", "<option>David</option>".html_safe
+ # select_tag "people", raw("<option>David</option>")
# # => <select id="people" name="people"><option>David</option></select>
#
- # select_tag "count", "<option>1</option><option>2</option><option>3</option><option>4</option>".html_safe
+ # select_tag "count", raw("<option>1</option><option>2</option><option>3</option><option>4</option>")
# # => <select id="count" name="count"><option>1</option><option>2</option>
# # <option>3</option><option>4</option></select>
#
- # select_tag "colors", "<option>Red</option><option>Green</option><option>Blue</option>".html_safe, multiple: true
+ # select_tag "colors", raw("<option>Red</option><option>Green</option><option>Blue</option>"), multiple: true
# # => <select id="colors" multiple="multiple" name="colors[]"><option>Red</option>
# # <option>Green</option><option>Blue</option></select>
#
- # select_tag "locations", "<option>Home</option><option selected='selected'>Work</option><option>Out</option>".html_safe
+ # select_tag "locations", raw("<option>Home</option><option selected='selected'>Work</option><option>Out</option>")
# # => <select id="locations" name="locations"><option>Home</option><option selected='selected'>Work</option>
# # <option>Out</option></select>
#
- # select_tag "access", "<option>Read</option><option>Write</option>".html_safe, multiple: true, class: 'form_input', id: 'unique_id'
+ # select_tag "access", raw("<option>Read</option><option>Write</option>"), multiple: true, class: 'form_input', id: 'unique_id'
# # => <select class="form_input" id="unique_id" multiple="multiple" name="access[]"><option>Read</option>
# # <option>Write</option></select>
#
@@ -121,7 +121,7 @@ module ActionView
# select_tag "people", options_from_collection_for_select(@people, "id", "name"), prompt: "Select something"
# # => <select id="people" name="people"><option value="">Select something</option><option value="1">David</option></select>
#
- # select_tag "destination", "<option>NYC</option><option>Paris</option><option>Rome</option>".html_safe, disabled: true
+ # select_tag "destination", raw("<option>NYC</option><option>Paris</option><option>Rome</option>"), disabled: true
# # => <select disabled="disabled" id="destination" name="destination"><option>NYC</option>
# # <option>Paris</option><option>Rome</option></select>
#
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index 1c2a400245..c0fc3b820f 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -22,10 +22,10 @@ module ActionView #:nodoc:
# the supplied separator, are HTML escaped unless they are HTML
# safe, and the returned string is marked as HTML safe.
#
- # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
+ # safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
# # => "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;"
#
- # safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
+ # safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />")
# # => "<p>foo</p><br /><p>bar</p>"
#
def safe_join(array, sep=$,)
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/active_model_helper_test.rb b/actionview/test/template/active_model_helper_test.rb
index 86bccdfade..55d62cf692 100644
--- a/actionview/test/template/active_model_helper_test.rb
+++ b/actionview/test/template/active_model_helper_test.rb
@@ -85,7 +85,7 @@ class ActiveModelHelperTest < ActionView::TestCase
def test_field_error_proc
old_proc = ActionView::Base.field_error_proc
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance|
- %(<div class=\"field_with_errors\">#{html_tag} <span class="error">#{[instance.error_message].join(', ')}</span></div>).html_safe
+ raw(%(<div class=\"field_with_errors\">#{html_tag} <span class="error">#{[instance.error_message].join(', ')}</span></div>))
end
assert_dom_equal(
diff --git a/actionview/test/template/capture_helper_test.rb b/actionview/test/template/capture_helper_test.rb
index 1e099d482c..ffaf773c53 100644
--- a/actionview/test/template/capture_helper_test.rb
+++ b/actionview/test/template/capture_helper_test.rb
@@ -34,7 +34,7 @@ class CaptureHelperTest < ActionView::TestCase
end
def test_capture_doesnt_escape_twice
- string = @av.capture { '&lt;em&gt;bar&lt;/em&gt;'.html_safe }
+ string = @av.capture { raw('&lt;em&gt;bar&lt;/em&gt;') }
assert_equal '&lt;em&gt;bar&lt;/em&gt;', string
end
@@ -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"
@@ -158,7 +171,7 @@ class CaptureHelperTest < ActionView::TestCase
@view_flow = ActionView::OutputFlow.new
provide :title, "hi"
- provide :title, "<p>title</p>".html_safe
+ provide :title, raw("<p>title</p>")
assert_equal "hi<p>title</p>", content_for(:title)
end
diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb
index 92e77599f4..4678998bdc 100644
--- a/actionview/test/template/date_helper_test.rb
+++ b/actionview/test/template/date_helper_test.rb
@@ -3207,7 +3207,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_time_tag_with_given_block
- assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ '<span>Right now</span>'.html_safe })
+ assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ raw('<span>Right now</span>') })
end
def test_time_tag_with_different_format
diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb
index 1be1c68c14..034b8a4bf6 100644
--- a/actionview/test/template/form_helper_test.rb
+++ b/actionview/test/template/form_helper_test.rb
@@ -336,7 +336,7 @@ class FormHelperTest < ActionView::TestCase
def test_label_with_block_and_html
assert_dom_equal(
'<label for="post_terms">Accept <a href="/terms">Terms</a>.</label>',
- label(:post, :terms) { 'Accept <a href="/terms">Terms</a>.'.html_safe }
+ label(:post, :terms) { raw('Accept <a href="/terms">Terms</a>.') }
)
end
@@ -351,7 +351,7 @@ class FormHelperTest < ActionView::TestCase
with_locale :label do
assert_dom_equal(
'<label for="post_body"><b>Write entire text here</b></label>',
- label(:post, :body) { |b| "<b>#{b.translation}</b>".html_safe }
+ label(:post, :body) { |b| raw("<b>#{b.translation}</b>") }
)
end
end
diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb
index 6b97cec34c..c5b63d33f1 100644
--- a/actionview/test/template/form_options_helper_test.rb
+++ b/actionview/test/template/form_options_helper_test.rb
@@ -588,7 +588,7 @@ class FormOptionsHelperTest < ActionView::TestCase
def test_select_under_fields_for_with_string_and_given_prompt
@post = Post.new
- options = "<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>".html_safe
+ options = raw("<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>")
output_buffer = fields_for :post, @post do |f|
concat f.select(:category, options, :prompt => 'The prompt')
diff --git a/actionview/test/template/form_tag_helper_test.rb b/actionview/test/template/form_tag_helper_test.rb
index 359ecbc637..07b3fba754 100644
--- a/actionview/test/template/form_tag_helper_test.rb
+++ b/actionview/test/template/form_tag_helper_test.rb
@@ -216,19 +216,19 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_select_tag
- actual = select_tag "people", "<option>david</option>".html_safe
+ actual = select_tag "people", raw("<option>david</option>")
expected = %(<select id="people" name="people"><option>david</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_multiple
- actual = select_tag "colors", "<option>Red</option><option>Blue</option><option>Green</option>".html_safe, multiple: true
+ actual = select_tag "colors", raw("<option>Red</option><option>Blue</option><option>Green</option>"), multiple: true
expected = %(<select id="colors" multiple="multiple" name="colors[]"><option>Red</option><option>Blue</option><option>Green</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_disabled
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, disabled: true
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), disabled: true
expected = %(<select id="places" disabled="disabled" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
@@ -239,37 +239,37 @@ class FormTagHelperTest < ActionView::TestCase
end
def test_select_tag_with_include_blank
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :include_blank => true
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :include_blank => true
expected = %(<select id="places" name="places"><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_include_blank_false
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: false
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: false
expected = %(<select id="places" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_include_blank_string
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, include_blank: 'Choose'
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: 'Choose'
expected = %(<select id="places" name="places"><option value="">Choose</option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_prompt
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string"
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "string"
expected = %(<select id="places" name="places"><option value="">string</option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_escapes_prompt
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "<script>alert(1337)</script>"
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "<script>alert(1337)</script>"
expected = %(<select id="places" name="places"><option value="">&lt;script&gt;alert(1337)&lt;/script&gt;</option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
def test_select_tag_with_prompt_and_include_blank
- actual = select_tag "places", "<option>Home</option><option>Work</option><option>Pub</option>".html_safe, :prompt => "string", :include_blank => true
+ actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), :prompt => "string", :include_blank => true
expected = %(<select name="places" id="places"><option value="">string</option><option value=""></option><option>Home</option><option>Work</option><option>Pub</option></select>)
assert_dom_equal expected, actual
end
@@ -433,9 +433,9 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal %(<input checked="checked" disabled="disabled" id="admin" name="admin" readonly="readonly" type="checkbox" value="1" />), check_box_tag("admin", 1, true, 'disabled' => true, :readonly => "yes")
assert_dom_equal %(<input checked="checked" id="admin" name="admin" type="checkbox" value="1" />), check_box_tag("admin", 1, true, :disabled => false, :readonly => nil)
assert_dom_equal %(<input type="checkbox" />), tag(:input, :type => "checkbox", :checked => false)
- assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", "<option>david</option>".html_safe, :multiple => true)
- assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", "<option>david</option>".html_safe, :multiple => true)
- assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", "<option>david</option>".html_safe, :multiple => nil)
+ assert_dom_equal %(<select id="people" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people", raw("<option>david</option>"), :multiple => true)
+ assert_dom_equal %(<select id="people_" multiple="multiple" name="people[]"><option>david</option></select>), select_tag("people[]", raw("<option>david</option>"), :multiple => true)
+ assert_dom_equal %(<select id="people" name="people"><option>david</option></select>), select_tag("people", raw("<option>david</option>"), :multiple => nil)
end
def test_stringify_symbol_keys
diff --git a/actionview/test/template/output_safety_helper_test.rb b/actionview/test/template/output_safety_helper_test.rb
index a1bf0e1a5f..8de0ae2f6f 100644
--- a/actionview/test/template/output_safety_helper_test.rb
+++ b/actionview/test/template/output_safety_helper_test.rb
@@ -18,10 +18,10 @@ class OutputSafetyHelperTest < ActionView::TestCase
end
test "safe_join should html_escape any items, including the separator, if they are not html_safe" do
- joined = safe_join(["<p>foo</p>".html_safe, "<p>bar</p>"], "<br />")
+ joined = safe_join([raw("<p>foo</p>"), "<p>bar</p>"], "<br />")
assert_equal "<p>foo</p>&lt;br /&gt;&lt;p&gt;bar&lt;/p&gt;", joined
- joined = safe_join(["<p>foo</p>".html_safe, "<p>bar</p>".html_safe], "<br />".html_safe)
+ joined = safe_join([raw("<p>foo</p>"), raw("<p>bar</p>")], raw("<br />"))
assert_equal "<p>foo</p><br /><p>bar</p>", joined
end
diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb
index d037447567..6f7a78ccef 100644
--- a/actionview/test/template/tag_helper_test.rb
+++ b/actionview/test/template/tag_helper_test.rb
@@ -143,10 +143,10 @@ class TagHelperTest < ActionView::TestCase
end
def test_tag_honors_html_safe_with_escaped_array_class
- str = tag('p', :class => ['song>', 'play>'.html_safe])
+ str = tag('p', :class => ['song>', raw('play>')])
assert_equal '<p class="song&gt; play>" />', str
- str = tag('p', :class => ['song>'.html_safe, 'play>'])
+ str = tag('p', :class => [raw('song>'), 'play>'])
assert_equal '<p class="song> play&gt;" />', str
end
diff --git a/actionview/test/template/url_helper_test.rb b/actionview/test/template/url_helper_test.rb
index 89cabb8f6b..3010656166 100644
--- a/actionview/test/template/url_helper_test.rb
+++ b/actionview/test/template/url_helper_test.rb
@@ -78,7 +78,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_button_to_with_path
assert_dom_equal(
%{<form method="post" action="/article/Hello" class="button_to"><input type="submit" value="Hello" /></form>},
- button_to("Hello", article_path("Hello".html_safe))
+ button_to("Hello", article_path("Hello"))
)
end
@@ -106,7 +106,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_button_to_with_html_safe_URL
- assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", "http://www.example.com/q1=v1&amp;q2=v2".html_safe)
+ assert_dom_equal %{<form method="post" action="http://www.example.com/q1=v1&amp;q2=v2" class="button_to"><input type="submit" value="Hello" /></form>}, button_to("Hello", raw("http://www.example.com/q1=v1&amp;q2=v2"))
end
def test_button_to_with_query_and_no_name
@@ -232,7 +232,7 @@ class UrlHelperTest < ActiveSupport::TestCase
end
def test_link_tag_with_img
- link = link_to("<img src='/favicon.jpg' />".html_safe, "/")
+ link = link_to(raw("<img src='/favicon.jpg' />"), "/")
expected = %{<a href="/"><img src='/favicon.jpg' /></a>}
assert_dom_equal expected, link
end
@@ -358,7 +358,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_link_tag_with_html_safe_string
assert_dom_equal(
%{<a href="/article/Gerd_M%C3%BCller">Gerd Müller</a>},
- link_to("Gerd Müller", article_path("Gerd_Müller".html_safe))
+ link_to("Gerd Müller", article_path("Gerd_Müller"))
)
end
@@ -369,7 +369,7 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_link_tag_does_not_escape_html_safe_content
assert_dom_equal %{<a href="/">Malicious <script>content</script></a>},
- link_to("Malicious <script>content</script>".html_safe, "/")
+ link_to(raw("Malicious <script>content</script>"), "/")
end
def test_link_to_unless
@@ -380,7 +380,7 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal "<strong>Showing</strong>",
link_to_unless(true, "Showing", url_hash) { |name|
- "<strong>#{name}</strong>".html_safe
+ raw "<strong>#{name}</strong>"
}
assert_equal "test",
@@ -390,8 +390,8 @@ class UrlHelperTest < ActiveSupport::TestCase
assert_equal %{&lt;b&gt;Showing&lt;/b&gt;}, link_to_unless(true, "<b>Showing</b>", url_hash)
assert_equal %{<a href="/">&lt;b&gt;Showing&lt;/b&gt;</a>}, link_to_unless(false, "<b>Showing</b>", url_hash)
- assert_equal %{<b>Showing</b>}, link_to_unless(true, "<b>Showing</b>".html_safe, url_hash)
- assert_equal %{<a href="/"><b>Showing</b></a>}, link_to_unless(false, "<b>Showing</b>".html_safe, url_hash)
+ assert_equal %{<b>Showing</b>}, link_to_unless(true, raw("<b>Showing</b>"), url_hash)
+ assert_equal %{<a href="/"><b>Showing</b></a>}, link_to_unless(false, raw("<b>Showing</b>"), url_hash)
end
def test_link_to_if
@@ -541,13 +541,13 @@ class UrlHelperTest < ActiveSupport::TestCase
def test_mail_to_with_img
assert_dom_equal %{<a href="mailto:feedback@example.com"><img src="/feedback.png" /></a>},
- mail_to('feedback@example.com', '<img src="/feedback.png" />'.html_safe)
+ mail_to('feedback@example.com', raw('<img src="/feedback.png" />'))
end
def test_mail_to_with_html_safe_string
assert_dom_equal(
%{<a href="mailto:david@loudthinking.com">david@loudthinking.com</a>},
- mail_to("david@loudthinking.com".html_safe)
+ mail_to(raw("david@loudthinking.com"))
)
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index eaeb808144..81799b65d6 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,25 @@
+* Run `type` attributes through attributes API type-casting before
+ instantiating the corresponding subclass. This makes it possible to define
+ custom STI mappings.
+
+ Fixes #21986.
+
+ *Yves Senn*
+
+* Don't try to quote functions or expressions passed to `:default` option if
+ they are passed as procs.
+
+ This will generate proper query with the passed function or expression for
+ the default option, instead of trying to quote it in incorrect fashion.
+
+ 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.
@@ -624,7 +646,7 @@
* Add `ActiveRecord::Relation#in_batches` to work with records and relations
in batches.
- Available options are `of` (batch size), `load`, `begin_at`, and `end_at`.
+ Available options are `of` (batch size), `load`, `start`, and `finish`.
Examples:
@@ -1272,7 +1294,7 @@
*Yves Senn*
-* `find_in_batches` now accepts an `:end_at` parameter that complements the `:start`
+* `find_in_batches` now accepts an `:finish` parameter that complements the `:start`
parameter to specify where to stop batch processing.
*Vipul A M*
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 c4f525336b..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 = [])
@@ -353,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.
@@ -379,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_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/counter_cache.rb b/activerecord/lib/active_record/counter_cache.rb
index 9e7d391c70..1b6817554d 100644
--- a/activerecord/lib/active_record/counter_cache.rb
+++ b/activerecord/lib/active_record/counter_cache.rb
@@ -97,8 +97,8 @@ module ActiveRecord
#
# ==== Examples
#
- # # Increment the post_count column for the record with an id of 5
- # DiscussionBoard.increment_counter(:post_count, 5)
+ # # Increment the posts_count column for the record with an id of 5
+ # DiscussionBoard.increment_counter(:posts_count, 5)
def increment_counter(counter_name, id)
update_counters(id, counter_name => 1)
end
@@ -115,8 +115,8 @@ module ActiveRecord
#
# ==== Examples
#
- # # Decrement the post_count column for the record with an id of 5
- # DiscussionBoard.decrement_counter(:post_count, 5)
+ # # Decrement the posts_count column for the record with an id of 5
+ # DiscussionBoard.decrement_counter(:posts_count, 5)
def decrement_counter(counter_name, id)
update_counters(id, counter_name => -1)
end
diff --git a/activerecord/lib/active_record/inheritance.rb b/activerecord/lib/active_record/inheritance.rb
index 6259c4cd33..3a17f74b1d 100644
--- a/activerecord/lib/active_record/inheritance.rb
+++ b/activerecord/lib/active_record/inheritance.rb
@@ -167,6 +167,7 @@ module ActiveRecord
end
def find_sti_class(type_name)
+ type_name = base_class.type_for_attribute(inheritance_column).cast(type_name)
subclass = begin
if store_full_sti_class
ActiveSupport::Dependencies.constantize(type_name)
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 4a6e9c12fe..4419a7b1e7 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:environment:set"
+ msg = "Environment data not found in the schema. To resolve this issue, run: \n\n\tbin/rails db:environment:set"
if defined?(Rails.env)
super("#{msg} RAILS_ENV=#{::Rails.env}")
else
@@ -168,7 +168,7 @@ module ActiveRecord
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"
+ msg << "\tbin/rails db:environment:set"
if defined?(Rails.env)
super("#{msg} RAILS_ENV=#{::Rails.env}")
else
@@ -1170,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
@@ -1238,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/model_schema.rb b/activerecord/lib/active_record/model_schema.rb
index f26c8471bc..722d7b5fce 100644
--- a/activerecord/lib/active_record/model_schema.rb
+++ b/activerecord/lib/active_record/model_schema.rb
@@ -255,7 +255,18 @@ module ActiveRecord
@attribute_types ||= Hash.new(Type::Value.new)
end
- def type_for_attribute(attr_name) # :nodoc:
+ # Returns the type of the attribute with the given name, after applying
+ # all modifiers. This method is the only valid source of information for
+ # anything related to the types of a model's attributes. This method will
+ # access the database and load the model's schema if it is required.
+ #
+ # The return value of this method will implement the interface described
+ # by ActiveModel::Type::Value (though the object itself may not subclass
+ # it).
+ #
+ # +attr_name+ The name of the attribute to retrieve the type for. Must be
+ # a string
+ def type_for_attribute(attr_name)
attribute_types[attr_name]
end
diff --git a/activerecord/lib/active_record/relation/batches.rb b/activerecord/lib/active_record/relation/batches.rb
index 221bc73680..54587ae18e 100644
--- a/activerecord/lib/active_record/relation/batches.rb
+++ b/activerecord/lib/active_record/relation/batches.rb
@@ -29,15 +29,15 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
- # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
- # (by setting the +:begin_at+ and +:end_at+ option on each worker).
+ # (by setting the +:start+ and +:finish+ option on each worker).
#
# # Let's process for a batch of 2000 records, skipping the first 2000 rows
- # Person.find_each(begin_at: 2000, batch_size: 2000) do |person|
+ # Person.find_each(start: 2000, batch_size: 2000) do |person|
# person.party_all_night!
# end
#
@@ -48,22 +48,15 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_each(begin_at: nil, end_at: nil, batch_size: 1000, start: nil)
- if start
- begin_at = start
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing `start` value to find_each is deprecated, and will be removed in Rails 5.1.
- Please pass `begin_at` instead.
- MSG
- end
+ def find_each(start: nil, finish: nil, batch_size: 1000)
if block_given?
- find_in_batches(begin_at: begin_at, end_at: end_at, batch_size: batch_size) do |records|
+ find_in_batches(start: start, finish: finish, batch_size: batch_size) do |records|
records.each { |record| yield record }
end
else
- enum_for(:find_each, begin_at: begin_at, end_at: end_at, batch_size: batch_size) do
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size) do
relation = self
- apply_limits(relation, begin_at, end_at).size
+ apply_limits(relation, start, finish).size
end
end
end
@@ -88,15 +81,15 @@ module ActiveRecord
#
# ==== Options
# * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
- # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
- # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
# This is especially useful if you want multiple workers dealing with
# the same processing queue. You can make worker 1 handle all the records
# between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
- # (by setting the +:begin_at+ and +:end_at+ option on each worker).
+ # (by setting the +:start+ and +:finish+ option on each worker).
#
# # Let's process the next 2000 records
- # Person.find_in_batches(begin_at: 2000, batch_size: 2000) do |group|
+ # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
# group.each { |person| person.party_all_night! }
# end
#
@@ -107,24 +100,16 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control
# the batch sizes.
- def find_in_batches(begin_at: nil, end_at: nil, batch_size: 1000, start: nil)
- if start
- begin_at = start
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Passing `start` value to find_in_batches is deprecated, and will be removed in Rails 5.1.
- Please pass `begin_at` instead.
- MSG
- end
-
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000)
relation = self
unless block_given?
- return to_enum(:find_in_batches, begin_at: begin_at, end_at: end_at, batch_size: batch_size) do
- total = apply_limits(relation, begin_at, end_at).size
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size) do
+ total = apply_limits(relation, start, finish).size
(total - 1).div(batch_size) + 1
end
end
- in_batches(of: batch_size, begin_at: begin_at, end_at: end_at, load: true) do |batch|
+ in_batches(of: batch_size, start: start, finish: finish, load: true) do |batch|
yield batch.to_a
end
end
@@ -153,18 +138,18 @@ module ActiveRecord
# ==== Options
# * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
# * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
- # * <tt>:begin_at</tt> - Specifies the primary key value to start from, inclusive of the value.
- # * <tt>:end_at</tt> - Specifies the primary key value to end at, inclusive of the value.
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
#
# This is especially useful if you want to work with the
# ActiveRecord::Relation object instead of the array of records, or if
# you want multiple workers dealing with the same processing queue. You can
# make worker 1 handle all the records between id 0 and 10,000 and worker 2
- # handle from 10,000 and beyond (by setting the +:begin_at+ and +:end_at+
+ # handle from 10,000 and beyond (by setting the +:start+ and +:finish+
# option on each worker).
#
# # Let's process the next 2000 records
- # Person.in_batches(of: 2000, begin_at: 2000).update_all(awesome: true)
+ # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
#
# An example of calling where query method on the relation:
#
@@ -186,10 +171,10 @@ module ActiveRecord
#
# NOTE: You can't set the limit either, that's used to control the batch
# sizes.
- def in_batches(of: 1000, begin_at: nil, end_at: nil, load: false)
+ def in_batches(of: 1000, start: nil, finish: nil, load: false)
relation = self
unless block_given?
- return BatchEnumerator.new(of: of, begin_at: begin_at, end_at: end_at, relation: self)
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
end
if logger && (arel.orders.present? || arel.taken.present?)
@@ -197,7 +182,7 @@ module ActiveRecord
end
relation = relation.reorder(batch_order).limit(of)
- relation = apply_limits(relation, begin_at, end_at)
+ relation = apply_limits(relation, start, finish)
batch_relation = relation
loop do
@@ -225,9 +210,9 @@ module ActiveRecord
private
- def apply_limits(relation, begin_at, end_at)
- relation = relation.where(table[primary_key].gteq(begin_at)) if begin_at
- relation = relation.where(table[primary_key].lteq(end_at)) if end_at
+ def apply_limits(relation, start, finish)
+ relation = relation.where(table[primary_key].gteq(start)) if start
+ relation = relation.where(table[primary_key].lteq(finish)) if finish
relation
end
diff --git a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
index 153aae9584..c6e39814dd 100644
--- a/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
+++ b/activerecord/lib/active_record/relation/batches/batch_enumerator.rb
@@ -3,11 +3,11 @@ module ActiveRecord
class BatchEnumerator
include Enumerable
- def initialize(of: 1000, begin_at: nil, end_at: nil, relation:) #:nodoc:
+ def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
@of = of
@relation = relation
- @begin_at = begin_at
- @end_at = end_at
+ @start = start
+ @finish = finish
end
# Looping through a collection of records from the database (using the
@@ -34,7 +34,7 @@ module ActiveRecord
def each_record
return to_enum(:each_record) unless block_given?
- @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: true).each do |relation|
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
relation.to_a.each { |record| yield record }
end
end
@@ -46,7 +46,7 @@ module ActiveRecord
# People.in_batches.update_all('age = age + 1')
[:delete_all, :update_all, :destroy_all].each do |method|
define_method(method) do |*args, &block|
- @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false).each do |relation|
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
relation.send(method, *args, &block)
end
end
@@ -58,7 +58,7 @@ module ActiveRecord
# relation.update_all(awesome: true)
# end
def each
- enum = @relation.to_enum(:in_batches, of: @of, begin_at: @begin_at, end_at: @end_at, load: false)
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
return enum.each { |relation| yield relation } if block_given?
enum
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder.rb b/activerecord/lib/active_record/relation/predicate_builder.rb
index 39e7b42629..0f88791d92 100644
--- a/activerecord/lib/active_record/relation/predicate_builder.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder.rb
@@ -18,6 +18,7 @@ module ActiveRecord
register_handler(Class, ClassHandler.new(self))
register_handler(Base, BaseHandler.new(self))
register_handler(Range, RangeHandler.new(self))
+ register_handler(RangeHandler::RangeWithBinds, RangeHandler.new(self))
register_handler(Relation, RelationHandler.new)
register_handler(Array, ArrayHandler.new(self))
register_handler(AssociationQueryValue, AssociationQueryHandler.new(self))
@@ -105,10 +106,23 @@ module ActiveRecord
binds += bvs
when Relation
binds += value.bound_attributes
+ when Range
+ first = value.begin
+ last = value.end
+ unless first.respond_to?(:infinite?) && first.infinite?
+ binds << build_bind_param(column_name, first)
+ first = Arel::Nodes::BindParam.new
+ end
+ unless last.respond_to?(:infinite?) && last.infinite?
+ binds << build_bind_param(column_name, last)
+ last = Arel::Nodes::BindParam.new
+ end
+
+ result[column_name] = RangeHandler::RangeWithBinds.new(first, last, value.exclude_end?)
else
if can_be_bound?(column_name, value)
result[column_name] = Arel::Nodes::BindParam.new
- binds << Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
+ binds << build_bind_param(column_name, value)
end
end
end
@@ -145,5 +159,9 @@ module ActiveRecord
handler_for(value).is_a?(BasicObjectHandler) &&
!table.associated_with?(column_name)
end
+
+ def build_bind_param(column_name, value)
+ Relation::QueryAttribute.new(column_name.to_s, value, table.type(column_name))
+ end
end
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
index 1b3849e3ad..306d4694ae 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/range_handler.rb
@@ -1,12 +1,28 @@
module ActiveRecord
class PredicateBuilder
class RangeHandler # :nodoc:
+ RangeWithBinds = Struct.new(:begin, :end, :exclude_end?)
+
def initialize(predicate_builder)
@predicate_builder = predicate_builder
end
def call(attribute, value)
- attribute.between(value)
+ if value.begin.respond_to?(:infinite?) && value.begin.infinite?
+ if value.end.respond_to?(:infinite?) && value.end.infinite?
+ attribute.not_in([])
+ elsif value.exclude_end?
+ attribute.lt(value.end)
+ else
+ attribute.lteq(value.end)
+ end
+ elsif value.end.respond_to?(:infinite?) && value.end.infinite?
+ attribute.gteq(value.begin)
+ elsif value.exclude_end?
+ attribute.gteq(value.begin).and(attribute.lt(value.end))
+ else
+ attribute.between(value)
+ end
end
protected
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/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 38ab1f3fc6..77c2845d88 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -233,19 +233,19 @@ module ActiveRecord
set_callback(:commit, :after, *args, &block)
end
- # Shortcut for +after_commit :hook, on: :create+.
+ # Shortcut for <tt>after_commit :hook, on: :create</tt>.
def after_create_commit(*args, &block)
set_options_for_callbacks!(args, on: :create)
set_callback(:commit, :after, *args, &block)
end
- # Shortcut for +after_commit :hook, on: :update+.
+ # Shortcut for <tt>after_commit :hook, on: :update</tt>.
def after_update_commit(*args, &block)
set_options_for_callbacks!(args, on: :update)
set_callback(:commit, :after, *args, &block)
end
- # Shortcut for +after_commit :hook, on: :destroy+.
+ # Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
def after_destroy_commit(*args, &block)
set_options_for_callbacks!(args, on: :destroy)
set_callback(:commit, :after, *args, &block)
diff --git a/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb b/activerecord/test/cases/adapters/postgresql/postgresql_adapter_test.rb
index cbf0f27020..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
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/batches_test.rb b/activerecord/test/cases/batches_test.rb
index da65336305..3602ee7ba2 100644
--- a/activerecord/test/cases/batches_test.rb
+++ b/activerecord/test/cases/batches_test.rb
@@ -38,7 +38,7 @@ class EachTest < ActiveRecord::TestCase
if Enumerator.method_defined? :size
def test_each_should_return_a_sized_enumerator
assert_equal 11, Post.find_each(batch_size: 1).size
- assert_equal 5, Post.find_each(batch_size: 2, begin_at: 7).size
+ assert_equal 5, Post.find_each(batch_size: 2, start: 7).size
assert_equal 11, Post.find_each(batch_size: 10_000).size
end
end
@@ -101,16 +101,16 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_start_from_the_start_option
assert_queries(@total) do
- Post.find_in_batches(batch_size: 1, begin_at: 2) do |batch|
+ Post.find_in_batches(batch_size: 1, start: 2) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
end
end
end
- def test_find_in_batches_should_end_at_the_end_option
+ def test_find_in_batches_should_finish_the_end_option
assert_queries(6) do
- Post.find_in_batches(batch_size: 1, end_at: 5) do |batch|
+ Post.find_in_batches(batch_size: 1, finish: 5) do |batch|
assert_kind_of Array, batch
assert_kind_of Post, batch.first
end
@@ -175,7 +175,7 @@ class EachTest < ActiveRecord::TestCase
def test_find_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.find_in_batches({ batch_size: 42, begin_at: 1 }.freeze){}
+ Post.find_in_batches({ batch_size: 42, start: 1 }.freeze){}
end
end
@@ -184,7 +184,7 @@ class EachTest < ActiveRecord::TestCase
start_nick = nick_order_subscribers.second.nick
subscribers = []
- Subscriber.find_in_batches(batch_size: 1, begin_at: start_nick) do |batch|
+ Subscriber.find_in_batches(batch_size: 1, start: start_nick) do |batch|
subscribers.concat(batch)
end
@@ -311,15 +311,15 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_start_from_the_start_option
post = Post.order('id ASC').where('id >= ?', 2).first
assert_queries(2) do
- relation = Post.in_batches(of: 1, begin_at: 2).first
+ relation = Post.in_batches(of: 1, start: 2).first
assert_equal post, relation.first
end
end
- def test_in_batches_should_end_at_the_end_option
+ def test_in_batches_should_finish_the_end_option
post = Post.order('id DESC').where('id <= ?', 5).first
assert_queries(7) do
- relation = Post.in_batches(of: 1, end_at: 5, load: true).reverse_each.first
+ relation = Post.in_batches(of: 1, finish: 5, load: true).reverse_each.first
assert_equal post, relation.last
end
end
@@ -371,7 +371,7 @@ class EachTest < ActiveRecord::TestCase
def test_in_batches_should_not_modify_passed_options
assert_nothing_raised do
- Post.in_batches({ of: 42, begin_at: 1 }.freeze){}
+ Post.in_batches({ of: 42, start: 1 }.freeze){}
end
end
@@ -380,7 +380,7 @@ class EachTest < ActiveRecord::TestCase
start_nick = nick_order_subscribers.second.nick
subscribers = []
- Subscriber.in_batches(of: 1, begin_at: start_nick) do |relation|
+ Subscriber.in_batches(of: 1, start: start_nick) do |relation|
subscribers.concat(relation)
end
@@ -441,32 +441,11 @@ class EachTest < ActiveRecord::TestCase
assert_equal 2, person.reload.author_id # incremented only once
end
- def test_find_in_batches_start_deprecated
- assert_deprecated do
- assert_queries(@total) do
- Post.find_in_batches(batch_size: 1, start: 2) do |batch|
- assert_kind_of Array, batch
- assert_kind_of Post, batch.first
- end
- end
- end
- end
-
- def test_find_each_start_deprecated
- assert_deprecated do
- assert_queries(@total) do
- Post.find_each(batch_size: 1, start: 2) do |post|
- assert_kind_of Post, post
- end
- end
- end
- end
-
if Enumerator.method_defined? :size
def test_find_in_batches_should_return_a_sized_enumerator
assert_equal 11, Post.find_in_batches(:batch_size => 1).size
assert_equal 6, Post.find_in_batches(:batch_size => 2).size
- assert_equal 4, Post.find_in_batches(batch_size: 2, begin_at: 4).size
+ assert_equal 4, Post.find_in_batches(batch_size: 2, start: 4).size
assert_equal 4, Post.find_in_batches(:batch_size => 3).size
assert_equal 1, Post.find_in_batches(:batch_size => 10_000).size
end
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/inheritance_test.rb b/activerecord/test/cases/inheritance_test.rb
index 03bce547da..c870247a4a 100644
--- a/activerecord/test/cases/inheritance_test.rb
+++ b/activerecord/test/cases/inheritance_test.rb
@@ -7,6 +7,7 @@ require 'models/project'
require 'models/subscriber'
require 'models/vegetables'
require 'models/shop'
+require 'models/sponsor'
module InheritanceTestHelper
def with_store_full_sti_class(&block)
@@ -524,3 +525,78 @@ class InheritanceAttributeTest < ActiveRecord::TestCase
assert_instance_of Empire, empire
end
end
+
+class InheritanceAttributeMappingTest < ActiveRecord::TestCase
+ setup do
+ @old_registry = ActiveRecord::Type.registry
+ ActiveRecord::Type.registry = ActiveRecord::Type::AdapterSpecificRegistry.new
+ ActiveRecord::Type.register :omg_sti, InheritanceAttributeMappingTest::OmgStiType
+ Company.delete_all
+ Sponsor.delete_all
+ end
+
+ teardown do
+ ActiveRecord::Type.registry = @old_registry
+ end
+
+ class OmgStiType < ActiveRecord::Type::String
+ def cast_value(value)
+ if value =~ /\Aomg_(.+)\z/
+ $1.classify
+ else
+ value
+ end
+ end
+
+ def serialize(value)
+ if value
+ "omg_%s" % value.underscore
+ end
+ end
+ end
+
+ class Company < ActiveRecord::Base
+ self.table_name = 'companies'
+ attribute :type, :omg_sti
+ end
+
+ class Startup < Company; end
+ class Empire < Company; end
+
+ class Sponsor < ActiveRecord::Base
+ self.table_name = 'sponsors'
+ attribute :sponsorable_type, :omg_sti
+
+ belongs_to :sponsorable, polymorphic: true
+ end
+
+ def test_sti_with_custom_type
+ Startup.create! name: 'a Startup'
+ Empire.create! name: 'an Empire'
+
+ assert_equal [["a Startup", "omg_inheritance_attribute_mapping_test/startup"],
+ ["an Empire", "omg_inheritance_attribute_mapping_test/empire"]], ActiveRecord::Base.connection.select_rows('SELECT name, type FROM companies').sort
+ assert_equal [["a Startup", "InheritanceAttributeMappingTest::Startup"],
+ ["an Empire", "InheritanceAttributeMappingTest::Empire"]], Company.all.map { |a| [a.name, a.type] }.sort
+
+ startup = Startup.first
+ startup.becomes! Empire
+ startup.save!
+
+ assert_equal [["a Startup", "omg_inheritance_attribute_mapping_test/empire"],
+ ["an Empire", "omg_inheritance_attribute_mapping_test/empire"]], ActiveRecord::Base.connection.select_rows('SELECT name, type FROM companies').sort
+
+ assert_equal [["a Startup", "InheritanceAttributeMappingTest::Empire"],
+ ["an Empire", "InheritanceAttributeMappingTest::Empire"]], Company.all.map { |a| [a.name, a.type] }.sort
+ end
+
+ def test_polymorphic_associations_custom_type
+ startup = Startup.create! name: 'a Startup'
+ sponsor = Sponsor.create! sponsorable: startup
+
+ assert_equal ["omg_inheritance_attribute_mapping_test/company"], ActiveRecord::Base.connection.select_values('SELECT sponsorable_type FROM sponsors')
+
+ sponsor = Sponsor.first
+ assert_equal startup, sponsor.sponsorable
+ end
+end
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/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/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/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/assets/images/rails_guides_logo.gif b/guides/assets/images/rails_guides_logo.gif
index 9b0ad5af28..f7149a0415 100644
--- a/guides/assets/images/rails_guides_logo.gif
+++ b/guides/assets/images/rails_guides_logo.gif
Binary files differ
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md
index f68f2d1faf..9ef2c1a441 100644
--- a/guides/source/action_controller_overview.md
+++ b/guides/source/action_controller_overview.md
@@ -995,10 +995,6 @@ you would like in a response object. The `ActionController::Live` module allows
you to create a persistent connection with a browser. Using this module, you will
be able to send arbitrary data to the browser at specific points in time.
-NOTE: The default Rails server (WEBrick) is a buffering web server and does not
-support streaming. In order to use this feature, you'll need to use a non buffering
-server like [Puma](http://puma.io), [Rainbows](http://rainbows.bogomips.org)
-or [Passenger](https://www.phusionpassenger.com).
#### Incorporating Live Streaming
diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md
index d95c6c0e78..fb5d2065d3 100644
--- a/guides/source/active_record_callbacks.md
+++ b/guides/source/active_record_callbacks.md
@@ -258,7 +258,7 @@ As you start registering new callbacks for your models, they will be queued for
The whole callback chain is wrapped in a transaction. If any _before_ callback method returns exactly `false` or raises an exception, the execution chain gets halted and a ROLLBACK is issued; _after_ callbacks can only accomplish that by raising an exception.
-WARNING. Any exception that is not `ActiveRecord::Rollback` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception.
+WARNING. Any exception that is not `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception.
Relational Callbacks
--------------------
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index a4a23395fb..83f4b951ee 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -12,7 +12,7 @@ After reading this guide, you will know:
* The generators you can use to create them.
* The methods Active Record provides to manipulate your database.
-* The Rake tasks that manipulate migrations and your schema.
+* The bin/rails tasks that manipulate migrations and your schema.
* How migrations relate to `schema.rb`.
--------------------------------------------------------------------------------
@@ -717,9 +717,9 @@ you will have to use `structure.sql` as dump method. See
Running Migrations
------------------
-Rails provides a set of Rake tasks to run certain sets of migrations.
+Rails provides a set of bin/rails tasks to run certain sets of migrations.
-The very first migration related Rake task you will use will probably be
+The very first migration related bin/rails task you will use will probably be
`rails db:migrate`. In its most basic form it just runs the `change` or `up`
method for all the migrations that have not yet been run. If there are
no such migrations, it exits. It will run these migrations in order based
@@ -772,7 +772,7 @@ if you need to go more than one version back, for example:
$ bin/rails db:migrate:redo STEP=3
```
-Neither of these Rake tasks do anything you could not do with `db:migrate`. They
+Neither of these bin/rails tasks do anything you could not do with `db:migrate`. They
are simply more convenient, since you do not need to explicitly specify the
version to migrate to.
@@ -784,7 +784,7 @@ it with the seed data.
### Resetting the Database
The `rails db:reset` task will drop the database and set it up again. This is
-functionally equivalent to `rake db:drop db:setup`.
+functionally equivalent to `rails db:drop db:setup`.
NOTE: This is not the same as running all the migrations. It will only use the
contents of the current `db/schema.rb` or `db/structure.sql` file. If a migration can't be rolled back,
@@ -809,7 +809,7 @@ Active Record believes that it has already been run.
### Running Migrations in Different Environments
-By default running `rake db:migrate` will run in the `development` environment.
+By default running `bin/rails db:migrate` will run in the `development` environment.
To run migrations against another environment you can specify it using the
`RAILS_ENV` environment variable while running the command. For example to run
migrations against the `test` environment you could run:
@@ -886,7 +886,7 @@ Occasionally you will make a mistake when writing a migration. If you have
already run the migration then you cannot just edit the migration and run the
migration again: Rails thinks it has already run the migration and so will do
nothing when you run `rails db:migrate`. You must rollback the migration (for
-example with `rake db:rollback`), edit your migration and then run
+example with `bin/rails db:rollback`), edit your migration and then run
`rails db:migrate` to run the corrected version.
In general, editing existing migrations is not a good idea. You will be
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 fd5399baec..784be91845 100644
--- a/guides/source/active_record_querying.md
+++ b/guides/source/active_record_querying.md
@@ -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`
@@ -322,7 +348,7 @@ end
The `find_each` method accepts most of the options allowed by the regular `find` method, except for `:order` and `:limit`, which are reserved for internal use by `find_each`.
-Three additional options, `:batch_size`, `:begin_at` and `:end_at`, are available as well.
+Three additional options, `:batch_size`, `:start` and `:finish`, are available as well.
**`:batch_size`**
@@ -334,34 +360,34 @@ User.find_each(batch_size: 5000) do |user|
end
```
-**`:begin_at`**
+**`:start`**
-By default, records are fetched in ascending order of the primary key, which must be an integer. The `:begin_at` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
+By default, records are fetched in ascending order of the primary key, which must be an integer. The `:start` option allows you to configure the first ID of the sequence whenever the lowest ID is not the one you need. This would be useful, for example, if you wanted to resume an interrupted batch process, provided you saved the last processed ID as a checkpoint.
For example, to send newsletters only to users with the primary key starting from 2000, and to retrieve them in batches of 5000:
```ruby
-User.find_each(begin_at: 2000, batch_size: 5000) do |user|
+User.find_each(start: 2000, batch_size: 5000) do |user|
NewsMailer.weekly(user).deliver_now
end
```
-**`:end_at`**
+**`:finish`**
-Similar to the `:begin_at` option, `:end_at` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need.
-This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:begin_at` and `:end_at`
+Similar to the `:start` option, `:finish` allows you to configure the last ID of the sequence whenever the highest ID is not the one you need.
+This would be useful, for example, if you wanted to run a batch process, using a subset of records based on `:start` and `:finish`
For example, to send newsletters only to users with the primary key starting from 2000 up to 10000 and to retrieve them in batches of 5000:
```ruby
-User.find_each(begin_at: 2000, end_at: 10000, batch_size: 5000) do |user|
+User.find_each(start: 2000, finish: 10000, batch_size: 5000) do |user|
NewsMailer.weekly(user).deliver_now
end
```
Another example would be if you wanted multiple workers handling the same
processing queue. You could have each worker handle 10000 records by setting the
-appropriate `:begin_at` and `:end_at` options on each worker.
+appropriate `:start` and `:finish` options on each worker.
#### `find_in_batches`
@@ -376,7 +402,7 @@ end
##### Options for `find_in_batches`
-The `find_in_batches` method accepts the same `:batch_size`, `:begin_at` and `:end_at` options as `find_each`.
+The `find_in_batches` method accepts the same `:batch_size`, `:start` and `:finish` options as `find_each`.
Conditions
----------
diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md
index 0083fc0e6c..60f78d011c 100644
--- a/guides/source/asset_pipeline.md
+++ b/guides/source/asset_pipeline.md
@@ -1280,8 +1280,9 @@ config.assets.debug = true
And in `production.rb`:
```ruby
-# Choose the compressors to use (if any) config.assets.js_compressor =
-# :uglifier config.assets.css_compressor = :yui
+# Choose the compressors to use (if any)
+config.assets.js_compressor = :uglifier
+# config.assets.css_compressor = :yui
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
@@ -1290,7 +1291,8 @@ config.assets.compile = false
config.assets.digest = true
# Precompile additional assets (application.js, application.css, and all
-# non-JS/CSS are already added) config.assets.precompile += %w( search.js )
+# non-JS/CSS are already added)
+# config.assets.precompile += %w( search.js )
```
Rails 4 no longer sets default config values for Sprockets in `test.rb`, so
diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md
index d83dda7228..accce5a904 100644
--- a/guides/source/association_basics.md
+++ b/guides/source/association_basics.md
@@ -16,54 +16,54 @@ After reading this guide, you will know:
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:
+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 authors and a model for books. Each author can have many books. Without associations, the model declarations would look like this:
```ruby
-class Customer < ApplicationRecord
+class Author < ApplicationRecord
end
-class Order < ApplicationRecord
+class Book < ApplicationRecord
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 an 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 an 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,13 +803,13 @@ 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.
```ruby
-@customer = @order.reload.customer
+@author = @book.reload.author
```
##### `association=(associate)`
@@ -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 an 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 an 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/guides/source/command_line.md b/guides/source/command_line.md
index 5240b5f343..6c95cbff6e 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -55,20 +55,21 @@ Rails will set you up with what seems like a huge amount of stuff for such a tin
### `rails server`
-The `rails server` command launches a small web server named WEBrick which comes bundled with Ruby. You'll use this any time you want to access your application through a web browser.
+The `rails server` command launches a web server named Puma which comes bundled with Rails. You'll use this any time you want to access your application through a web browser.
With no further work, `rails server` will run our new shiny Rails app:
```bash
$ cd commandsapp
$ bin/rails server
-=> Booting WEBrick
-=> Rails 5.0.0 application starting in development on http://localhost:3000
+=> Booting Puma
+=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
-[2013-08-07 02:00:01] INFO WEBrick 1.3.1
-[2013-08-07 02:00:01] INFO ruby 2.2.2 (2015-06-27) [x86_64-darwin11.2.0]
-[2013-08-07 02:00:01] INFO WEBrick::HTTPServer#start: pid=69680 port=3000
+Puma 2.15.3 starting...
+* Min threads: 0, max threads: 16
+* Environment: development
+* Listening on tcp://localhost:3000
```
With just three commands we whipped up a Rails server listening on port 3000. Go to your browser and open [http://localhost:3000](http://localhost:3000), you will see a basic Rails app running.
@@ -181,7 +182,7 @@ Fire up your server using `rails server`.
```bash
$ bin/rails server
-=> Booting WEBrick...
+=> Booting Puma...
```
The URL will be [http://localhost:3000/greetings/hello](http://localhost:3000/greetings/hello).
@@ -253,7 +254,7 @@ The generator checks that there exist the directories for models, controllers, h
The migration requires that we **migrate**, that is, run some Ruby code (living in that `20130717151933_create_high_scores.rb`) to modify the schema of our database. Which database? The SQLite3 database that Rails will create for you when we run the `rake db:migrate` command. We'll talk more about Rake in-depth in a little while.
```bash
-$ bin/rake db:migrate
+$ bin/rails db:migrate
== CreateHighScores: migrating ===============================================
-- create_table(:high_scores)
-> 0.0017s
diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md
index 0046ff7b4e..35ad6eb705 100644
--- a/guides/source/debugging_rails_applications.md
+++ b/guides/source/debugging_rails_applications.md
@@ -311,14 +311,14 @@ processing the entire request.
For example:
```bash
-=> Booting WEBrick
+=> Booting Puma
=> Rails 5.0.0 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
-=> Notice: server is listening on all interfaces (0.0.0.0). Consider using 127.0.0.1 (--binding option)
=> Ctrl-C to shutdown server
-[2014-04-11 13:11:47] INFO WEBrick 1.3.1
-[2014-04-11 13:11:47] INFO ruby 2.2.2 (2015-04-13) [i686-linux]
-[2014-04-11 13:11:47] INFO WEBrick::HTTPServer#start: pid=6370 port=3000
+Puma 2.15.3 starting...
+* Min threads: 0, max threads: 16
+* Environment: development
+* Listening on tcp://localhost:3000
Started GET "/" for 127.0.0.1 at 2014-04-11 13:11:48 +0200
diff --git a/guides/source/engines.md b/guides/source/engines.md
index 8382bde4d3..697938434c 100644
--- a/guides/source/engines.md
+++ b/guides/source/engines.md
@@ -423,7 +423,7 @@ Finally, the assets for this resource are generated in two files:
`app/assets/stylesheets/blorgh/articles.css`. You'll see how to use these a little
later.
-You can see what the engine has so far by running `rake db:migrate` at the root
+You can see what the engine has so far by running `bin/rails db:migrate` at the root
of our engine to run the migration generated by the scaffold generator, and then
running `rails server` in `test/dummy`. When you open
`http://localhost:3000/blorgh/articles` you will see the default scaffold that has
@@ -485,7 +485,7 @@ called `Blorgh::Comment`. Now run the migration to create our blorgh_comments
table:
```bash
-$ rake db:migrate
+$ bin/rails db:migrate
```
To show the comments on an article, edit `app/views/blorgh/articles/show.html.erb` and
@@ -694,14 +694,14 @@ engine's models can query them correctly. To copy these migrations into the
application run the following command from the `test/dummy` directory of your Rails engine:
```bash
-$ rake blorgh:install:migrations
+$ bin/rails blorgh:install:migrations
```
If you have multiple engines that need migrations copied over, use
`railties:install:migrations` instead:
```bash
-$ rake railties:install:migrations
+$ bin/rails railties:install:migrations
```
This command, when run for the first time, will copy over all the migrations
@@ -719,7 +719,7 @@ timestamp (`[timestamp_2]`) will be the current time plus a second. The reason
for this is so that the migrations for the engine are run after any existing
migrations in the application.
-To run these migrations within the context of the application, simply run `rake
+To run these migrations within the context of the application, simply run `bin/rails
db:migrate`. When accessing the engine through `http://localhost:3000/blog`, the
articles will be empty. This is because the table created inside the application is
different from the one created within the engine. Go ahead, play around with the
@@ -730,14 +730,14 @@ If you would like to run migrations only from one engine, you can do it by
specifying `SCOPE`:
```bash
-rake db:migrate SCOPE=blorgh
+bin/rails db:migrate SCOPE=blorgh
```
This may be useful if you want to revert engine's migrations before removing it.
To revert all migrations from blorgh engine you can run code such as:
```bash
-rake db:migrate SCOPE=blorgh VERSION=0
+bin/rails db:migrate SCOPE=blorgh VERSION=0
```
### Using a Class Provided by the Application
@@ -764,7 +764,7 @@ application:
rails g model user name:string
```
-The `rake db:migrate` command needs to be run here to ensure that our
+The `bin/rails db:migrate` command needs to be run here to ensure that our
application has the `users` table for future use.
Also, to keep it simple, the articles form will have a new text field called
@@ -836,7 +836,7 @@ This migration will need to be run on the application. To do that, it must first
be copied using this command:
```bash
-$ rake blorgh:install:migrations
+$ bin/rails blorgh:install:migrations
```
Notice that only _one_ migration was copied over here. This is because the first
@@ -851,7 +851,7 @@ Copied migration [timestamp]_add_author_id_to_blorgh_articles.blorgh.rb from blo
Run the migration using:
```bash
-$ rake db:migrate
+$ bin/rails db:migrate
```
Now with all the pieces in place, an action will take place that will associate
@@ -1354,7 +1354,7 @@ need to require `admin.css` or `admin.js`. Only the gem's admin layout needs
these assets. It doesn't make sense for the host app to include
`"blorgh/admin.css"` in its stylesheets. In this situation, you should
explicitly define these assets for precompilation. This tells sprockets to add
-your engine assets when `rake assets:precompile` is triggered.
+your engine assets when `bin/rails assets:precompile` is triggered.
You can define assets for precompilation in `engine.rb`:
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index 9f38de6247..9677ab1583 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -208,7 +208,7 @@ commented line for new apps and you can uncomment if you need it.
default to the `Gemfile` in apps generated under JRuby. You can investigate
all the supported runtimes at [ExecJS](https://github.com/rails/execjs#readme).
-This will fire up WEBrick, a web server distributed with Ruby by default. To see
+This will fire up Puma, a web server distributed with Rails by default. To see
your application in action, open a browser window and navigate to
<http://localhost:3000>. You should see the Rails default information page:
@@ -300,7 +300,7 @@ Rails.application.routes.draw do
# The priority is based upon order of creation:
# first created -> highest priority.
- # See how all your routes lay out with "rake routes".
+ # See how all your routes lay out with "bin/rails routes".
#
# You can have the root of your site routed with "root"
# root 'welcome#index'
@@ -359,13 +359,13 @@ Rails.application.routes.draw do
end
```
-If you run `bin/rake routes`, you'll see that it has defined routes for all the
+If you run `bin/rails routes`, you'll see that it has defined routes for all the
standard RESTful actions. The meaning of the prefix column (and other columns)
will be seen later, but for now notice that Rails has inferred the
singular form `article` and makes meaningful use of the distinction.
```bash
-$ bin/rake routes
+$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
@@ -559,10 +559,10 @@ this:
In this example, the `articles_path` helper is passed to the `:url` option.
To see what Rails will do with this, we look back at the output of
-`bin/rake routes`:
+`bin/rails routes`:
```bash
-$ bin/rake routes
+$ bin/rails routes
Prefix Verb URI Pattern Controller#Action
articles GET /articles(.:format) articles#index
POST /articles(.:format) articles#create
@@ -702,10 +702,10 @@ two timestamp fields to allow Rails to track article creation and update times.
TIP: For more information about migrations, refer to [Rails Database Migrations]
(migrations.html).
-At this point, you can use a rake command to run the migration:
+At this point, you can use a bin/rails command to run the migration:
```bash
-$ bin/rake db:migrate
+$ bin/rails db:migrate
```
Rails will execute this migration command and tell you it created the Articles
@@ -722,7 +722,7 @@ NOTE. Because you're working in the development environment by default, this
command will apply to the database defined in the `development` section of your
`config/database.yml` file. If you would like to execute migrations in another
environment, for instance in production, you must explicitly pass it when
-invoking the command: `bin/rake db:migrate RAILS_ENV=production`.
+invoking the command: `bin/rails db:migrate RAILS_ENV=production`.
### Saving data in the controller
@@ -809,7 +809,7 @@ If you submit the form again now, Rails will complain about not finding the
`show` action. That's not very useful though, so let's add the `show` action
before proceeding.
-As we have seen in the output of `bin/rake routes`, the route for `show` action is
+As we have seen in the output of `bin/rails routes`, the route for `show` action is
as follows:
```
@@ -871,7 +871,7 @@ Visit <http://localhost:3000/articles/new> and give it a try!
### Listing all articles
We still need a way to list all our articles, so let's do that.
-The route for this as per output of `bin/rake routes` is:
+The route for this as per output of `bin/rails routes` is:
```
articles GET /articles(.:format) articles#index
@@ -1366,7 +1366,7 @@ Then do the same for the `app/views/articles/edit.html.erb` view:
We're now ready to cover the "D" part of CRUD, deleting articles from the
database. Following the REST convention, the route for
-deleting articles as per output of `bin/rake routes` is:
+deleting articles as per output of `bin/rails routes` is:
```ruby
DELETE /articles/:id(.:format) articles#destroy
@@ -1540,6 +1540,11 @@ This is very similar to the `Article` model that you saw earlier. The difference
is the line `belongs_to :article`, which sets up an Active Record _association_.
You'll learn a little about associations in the next section of this guide.
+The (`:references`) keyword used in the bash command is a special data type for models.
+It creates a new column on your database table with the provided model name appended with an `_id`
+that can hold integer values. You can get a better understanding after analyzing the
+`db/schema.rb` file below.
+
In addition to the model, Rails has also made a migration to create the
corresponding database table:
@@ -1562,7 +1567,7 @@ for it, and a foreign key constraint that points to the `id` column of the `arti
table. Go ahead and run the migration:
```bash
-$ bin/rake db:migrate
+$ bin/rails db:migrate
```
Rails is smart enough to only execute the migrations that have not already been
diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb
index 1005057ca9..1f81ea4694 100644
--- a/guides/source/layout.html.erb
+++ b/guides/source/layout.html.erb
@@ -24,20 +24,7 @@
<% end %>
<div id="topNav">
<div class="wrapper">
- <strong class="more-info-label">More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
- <span class="red-button more-info-button">
- More Ruby on Rails
- </span>
- <ul class="more-info-links s-hidden">
- <li class="more-info"><a href="http://rubyonrails.org/">Overview</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/download">Download</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/deploy">Deploy</a></li>
- <li class="more-info"><a href="https://github.com/rails/rails">Code</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/screencasts">Screencasts</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/documentation">Documentation</a></li>
- <li class="more-info"><a href="http://rubyonrails.org/community">Community</a></li>
- <li class="more-info"><a href="http://weblog.rubyonrails.org/">Blog</a></li>
- </ul>
+ <strong class="more-info-label">←<a href="http://rubyonrails.org/">Back to rubyonrails.org:</a> </strong>
</div>
</div>
<div id="header">
diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md
index 9b7f916b9e..d55e1007ee 100644
--- a/guides/source/layouts_and_rendering.md
+++ b/guides/source/layouts_and_rendering.md
@@ -238,7 +238,7 @@ TIP: This is useful when you're rendering a small snippet of HTML code.
However, you might want to consider moving it to a template file if the markup
is complex.
-NOTE: This option will escape HTML entities if the string is not HTML safe.
+NOTE: When using `html:` option, HTML entities will be escaped if the string is not marked as HTML safe by using `html_safe` method.
#### Rendering JSON
diff --git a/guides/source/plugins.md b/guides/source/plugins.md
index 68e54f2414..8f055f8fe3 100644
--- a/guides/source/plugins.md
+++ b/guides/source/plugins.md
@@ -247,7 +247,7 @@ and migrating the database. First, run:
```bash
$ cd test/dummy
-$ bin/rake db:migrate
+$ bin/rails db:migrate
```
While you are here, change the Hickwall and Wickwall models so that they know that they are supposed to act
diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md
index edd54826cf..5a46baff2d 100644
--- a/guides/source/rails_application_templates.md
+++ b/guides/source/rails_application_templates.md
@@ -25,8 +25,8 @@ $ rails new blog -m http://example.com/template.rb
You can use the rake task `rails:template` to apply templates to an existing Rails application. The location of the template needs to be passed in to an environment variable named LOCATION. Again, this can either be path to a file or a URL.
```bash
-$ bin/rake rails:template LOCATION=~/template.rb
-$ bin/rake rails:template LOCATION=http://example.com/template.rb
+$ bin/rails rails:template LOCATION=~/template.rb
+$ bin/rails rails:template LOCATION=http://example.com/template.rb
```
Template API
diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md
index 273fbc08e2..934693252e 100644
--- a/guides/source/rails_on_rack.md
+++ b/guides/source/rails_on_rack.md
@@ -96,7 +96,7 @@ NOTE: `ActionDispatch::MiddlewareStack` is Rails equivalent of `Rack::Builder`,
Rails has a handy rake task for inspecting the middleware stack in use:
```bash
-$ bin/rake middleware
+$ bin/rails middleware
```
For a freshly generated Rails application, this might produce something like:
@@ -178,7 +178,7 @@ And now if you inspect the middleware stack, you'll find that `Rack::Lock` is
not a part of it.
```bash
-$ bin/rake middleware
+$ bin/rails middleware
(in /Users/lifo/Rails/blog)
use ActionDispatch::Static
use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x00000001c304c8>
diff --git a/guides/source/routing.md b/guides/source/routing.md
index 9401132500..09491f3c9a 100644
--- a/guides/source/routing.md
+++ b/guides/source/routing.md
@@ -1116,7 +1116,7 @@ Rails offers facilities for inspecting and testing your routes.
### Listing Existing Routes
-To get a complete list of the available routes in your application, visit `http://localhost:3000/rails/info/routes` in your browser while your server is running in the **development** environment. You can also execute the `rake routes` command in your terminal to produce the same output.
+To get a complete list of the available routes in your application, visit `http://localhost:3000/rails/info/routes` in your browser while your server is running in the **development** environment. You can also execute the `rails routes` command in your terminal to produce the same output.
Both methods will list all of your routes, in the same order that they appear in `config/routes.rb`. For each route, you'll see:
@@ -1125,7 +1125,7 @@ Both methods will list all of your routes, in the same order that they appear in
* The URL pattern to match
* The routing parameters for the route
-For example, here's a small section of the `rake routes` output for a RESTful route:
+For example, here's a small section of the `rails routes` output for a RESTful route:
```
users GET /users(.:format) users#index
@@ -1137,10 +1137,10 @@ edit_user GET /users/:id/edit(.:format) users#edit
You may restrict the listing to the routes that map to a particular controller setting the `CONTROLLER` environment variable:
```bash
-$ CONTROLLER=users bin/rake routes
+$ CONTROLLER=users bin/rails routes
```
-TIP: You'll find that the output from `rake routes` is much more readable if you widen your terminal window until the output lines don't wrap.
+TIP: You'll find that the output from `rails routes` is much more readable if you widen your terminal window until the output lines don't wrap.
### Testing Routes
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 84f80e3c37..bb6d322357 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -399,11 +399,11 @@ structure. The test helper checks whether your test database has any pending
migrations. If so, it will try to load your `db/schema.rb` or `db/structure.sql`
into the test database. If migrations are still pending, an error will be
raised. Usually this indicates that your schema is not fully migrated. Running
-the migrations against the development database (`bin/rake db:migrate`) will
+the migrations against the development database (`bin/rails db:migrate`) will
bring the schema up to date.
NOTE: If existing migrations required modifications, the test database needs to
-be rebuilt. This can be done by executing `bin/rake db:test:prepare`.
+be rebuilt. This can be done by executing `bin/rails db:test:prepare`.
### The Low-Down on Fixtures
diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md
index 4a28074f2c..2c363c55da 100644
--- a/railties/CHANGELOG.md
+++ b/railties/CHANGELOG.md
@@ -6,7 +6,7 @@
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/code_statistics.rb b/railties/lib/rails/code_statistics.rb
index 8e9097e1ef..0997414482 100644
--- a/railties/lib/rails/code_statistics.rb
+++ b/railties/lib/rails/code_statistics.rb
@@ -33,7 +33,7 @@ class CodeStatistics #:nodoc:
Hash[@pairs.map{|pair| [pair.first, calculate_directory_statistics(pair.last)]}]
end
- def calculate_directory_statistics(directory, pattern = /.*\.(rb|js|coffee|rake)$/)
+ def calculate_directory_statistics(directory, pattern = /^(?!\.).*?\.(rb|js|coffee|rake)$/)
stats = CodeStatisticsCalculator.new
Dir.foreach(directory) do |file_name|
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/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 297ccb1dbf..c629459d95 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -117,6 +117,7 @@ module Rails
javascript_gemfile_entry,
jbuilder_gemfile_entry,
psych_gemfile_entry,
+ cable_gemfile_entry,
@extra_entries].flatten.find_all(&@gem_filter)
end
@@ -339,6 +340,15 @@ module Rails
GemfileEntry.new('psych', '~> 2.0', comment, platforms: :rbx)
end
+ def cable_gemfile_entry
+ return [] if options[:skip_action_cable]
+ comment = 'Action Cable dependencies for the Redis adapter'
+ gems = []
+ gems << GemfileEntry.new("em-hiredis", '~> 0.3.0', comment)
+ gems << GemfileEntry.new("redis", '~> 3.0', comment)
+ gems
+ end
+
def bundle_command(command)
say_status :run, "bundle #{command}"
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index f4deec7135..7ec4d3bbd3 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -78,11 +78,11 @@ module Rails
template "application.rb"
template "environment.rb"
template "secrets.yml"
+ template "cable.yml" unless options[:skip_action_cable]
directory "environments"
directory "initializers"
directory "locales"
- directory "redis" unless options[:skip_action_cable]
end
end
@@ -315,7 +315,7 @@ module Rails
def delete_action_cable_files_skipping_action_cable
if options[:skip_action_cable]
- remove_file 'config/redis/cable.yml'
+ remove_file 'config/cable.yml'
remove_file 'app/assets/javascripts/cable.coffee'
remove_dir 'app/channels'
end
diff --git a/railties/lib/rails/generators/rails/app/templates/config/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
new file mode 100644
index 0000000000..004adb7b3c
--- /dev/null
+++ b/railties/lib/rails/generators/rails/app/templates/config/cable.yml
@@ -0,0 +1,12 @@
+# Action Cable uses Redis by default to administer connections, channels, and sending/receiving messages over the WebSocket.
+production:
+ adapter: redis
+ url: redis://localhost:6379/1
+
+development:
+ adapter: redis
+ url: redis://localhost:6379/2
+
+test:
+ adapter: redis
+ url: redis://localhost:6379/3
diff --git a/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml b/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml
deleted file mode 100644
index 0176be24f9..0000000000
--- a/railties/lib/rails/generators/rails/app/templates/config/redis/cable.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-# Action Cable uses Redis to administer connections, channels, and sending/receiving messages over the WebSocket.
-production:
- url: redis://localhost:6379/1
-
-development:
- url: redis://localhost:6379/2
-
-test:
- url: redis://localhost:6379/3
diff --git a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
index b2669a0f79..cdea2fd060 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
+++ b/railties/lib/rails/generators/rails/app/templates/config/secrets.yml
@@ -5,7 +5,7 @@
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
-# You can use `rake secret` to generate a secure secret key.
+# You can use `rails secret` to generate a secure secret key.
# Make sure the secrets in this file are kept private
# if you're sharing your code publicly.
diff --git a/railties/lib/rails/test_unit/test_requirer.rb b/railties/lib/rails/test_unit/test_requirer.rb
index 8b9933bed4..8b211ce130 100644
--- a/railties/lib/rails/test_unit/test_requirer.rb
+++ b/railties/lib/rails/test_unit/test_requirer.rb
@@ -15,7 +15,7 @@ module Rails
private
def expand_patterns(patterns)
patterns.map do |arg|
- arg = arg.gsub(/(:\d*)+?$/, '')
+ arg = arg.gsub(/(:\d+)+?$/, '')
if Dir.exist?(arg)
"#{arg}/**/*_test.rb"
else
diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb
index bb6c6574c5..0745abf381 100644
--- a/railties/test/application/test_runner_test.rb
+++ b/railties/test/application/test_runner_test.rb
@@ -382,14 +382,6 @@ module ApplicationTests
end
end
- def test_line_filter_without_line_runs_all_tests
- create_test_file :models, 'account'
-
- run_test_command('test/models/account_test.rb:').tap do |output|
- assert_match 'AccountTest', output
- end
- end
-
def test_shows_filtered_backtrace_by_default
create_backtrace_test
diff --git a/railties/test/code_statistics_test.rb b/railties/test/code_statistics_test.rb
index 1b1ff80bc1..4d80901217 100644
--- a/railties/test/code_statistics_test.rb
+++ b/railties/test/code_statistics_test.rb
@@ -4,7 +4,7 @@ require 'rails/code_statistics'
class CodeStatisticsTest < ActiveSupport::TestCase
def setup
@tmp_path = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp'))
- @dir_js = File.expand_path(File.join(File.dirname(__FILE__), 'fixtures', 'tmp', 'lib.js'))
+ @dir_js = File.join(@tmp_path, 'lib.js')
FileUtils.mkdir_p(@dir_js)
end
@@ -17,4 +17,17 @@ class CodeStatisticsTest < ActiveSupport::TestCase
@code_statistics = CodeStatistics.new(['tmp dir', @tmp_path])
end
end
+
+ test 'ignores hidden files' do
+ File.write File.join(@tmp_path, '.example.rb'), <<-CODE
+ def foo
+ puts 'foo'
+ end
+ CODE
+
+ assert_nothing_raised do
+ CodeStatistics.new(['hidden file', @tmp_path])
+ end
+ end
+
end
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
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index e5480180ce..c0f7e58b59 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -392,9 +392,19 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_generator_if_skip_action_cable_is_given
run_generator [destination_root, "--skip-action-cable"]
assert_file "config/application.rb", /#\s+require\s+["']action_cable\/engine["']/
- assert_no_file "config/redis/cable.yml"
+ assert_no_file "config/cable.yml"
assert_no_file "app/assets/javascripts/cable.coffee"
assert_no_file "app/channels"
+ assert_file "Gemfile" do |content|
+ assert_no_match(/em-hiredis/, content)
+ assert_no_match(/redis/, content)
+ end
+ end
+
+ def test_action_cable_redis_gems
+ run_generator
+ assert_gem 'em-hiredis'
+ assert_gem 'redis'
end
def test_inclusion_of_javascript_runtime
diff --git a/railties/test/generators/plugin_test_runner_test.rb b/railties/test/generators/plugin_test_runner_test.rb
index f0fb63c208..f492cd49ef 100644
--- a/railties/test/generators/plugin_test_runner_test.rb
+++ b/railties/test/generators/plugin_test_runner_test.rb
@@ -59,14 +59,6 @@ class PluginTestRunnerTest < ActiveSupport::TestCase
end
end
- def test_line_filter_without_line_runs_all_tests
- create_test_file 'account'
-
- run_test_command('test/account_test.rb:').tap do |output|
- assert_match 'AccountTest', output
- end
- end
-
def test_output_inline_by_default
create_test_file 'post', pass: false