diff options
Diffstat (limited to 'guides/source')
56 files changed, 4961 insertions, 2135 deletions
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md index ee9a499953..b46d5ee4db 100644 --- a/guides/source/2_3_release_notes.md +++ b/guides/source/2_3_release_notes.md @@ -415,7 +415,7 @@ select_datetime(DateTime.now, :prompt => ### AssetTag Timestamp Caching -You're likely familiar with Rails' practice of adding timestamps to static asset paths as a "cache buster." This helps ensure that stale copies of things like images and stylesheets don't get served out of the user's browser cache when you change them on the server. You can now modify this behavior with the `cache_asset_timestamps` configuration option for Action View. If you enable the cache, then Rails will calculate the timestamp once when it first serves an asset, and save that value. This means fewer (expensive) file system calls to serve static assets - but it also means that you can't modify any of the assets while the server is running and expect the changes to get picked up by clients. +You're likely familiar with Rails' practice of adding timestamps to static asset paths as a "cache buster". This helps ensure that stale copies of things like images and stylesheets don't get served out of the user's browser cache when you change them on the server. You can now modify this behavior with the `cache_asset_timestamps` configuration option for Action View. If you enable the cache, then Rails will calculate the timestamp once when it first serves an asset, and save that value. This means fewer (expensive) file system calls to serve static assets - but it also means that you can't modify any of the assets while the server is running and expect the changes to get picked up by clients. ### Asset Hosts as Objects diff --git a/guides/source/3_0_release_notes.md b/guides/source/3_0_release_notes.md index e936644daf..15704acefe 100644 --- a/guides/source/3_0_release_notes.md +++ b/guides/source/3_0_release_notes.md @@ -73,7 +73,7 @@ $ ruby script/plugin install git://github.com/rails/rails_upgrade.git You can see an example of how that works at [Rails Upgrade is now an Official Plugin](http://omgbloglol.com/post/364624593/rails-upgrade-is-now-an-official-plugin) -Aside from Rails Upgrade tool, if you need more help, there are people on IRC and [rubyonrails-talk](http://groups.google.com/group/rubyonrails-talk) that are probably doing the same thing, possibly hitting the same issues. Be sure to blog your own experiences when upgrading so others can benefit from your knowledge! +Aside from Rails Upgrade tool, if you need more help, there are people on IRC and [rubyonrails-talk](https://groups.google.com/group/rubyonrails-talk) that are probably doing the same thing, possibly hitting the same issues. Be sure to blog your own experiences when upgrading so others can benefit from your knowledge! Creating a Rails 3.0 application -------------------------------- @@ -607,6 +607,6 @@ More Information: Credits ------- -See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails 3. Kudos to all of them. +See the [full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails 3. Kudos to all of them. Rails 3.0 Release Notes were compiled by [Mikel Lindsaar](http://lindsaar.net). diff --git a/guides/source/3_1_release_notes.md b/guides/source/3_1_release_notes.md index d6981656ee..09faeea614 100644 --- a/guides/source/3_1_release_notes.md +++ b/guides/source/3_1_release_notes.md @@ -240,7 +240,7 @@ Action Pack * Added `config.action_controller.include_all_helpers`. By default `helper :all` is done in `ActionController::Base`, which includes all the helpers by default. Setting `include_all_helpers` to `false` will result in including only application_helper and the helper corresponding to controller (like foo_helper for foo_controller). -* `url_for` and named url helpers now accept `:subdomain` and `:domain` as options. +* `url_for` and named URL helpers now accept `:subdomain` and `:domain` as options. * Added `Base.http_basic_authenticate_with` to do simple http basic authentication with a single class method call. @@ -291,9 +291,9 @@ Action Pack end ``` - You can restrict it to some actions by using `:only` or `:except`. Please read the docs at [`ActionController::Streaming`](http://api.rubyonrails.org/v3.1.0/classes/ActionController/Streaming.html) for more information. + You can restrict it to some actions by using `:only` or `:except`. Please read the docs at [`ActionController::Streaming`](https://api.rubyonrails.org/v3.1.0/classes/ActionController/Streaming.html) for more information. -* The redirect route method now also accepts a hash of options which will only change the parts of the url in question, or an object which responds to call, allowing for redirects to be reused. +* The redirect route method now also accepts a hash of options which will only change the parts of the URL in question, or an object which responds to call, allowing for redirects to be reused. ### Action Dispatch @@ -556,6 +556,6 @@ Deprecations: Credits ------- -See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. +See the [full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. Rails 3.1 Release Notes were compiled by [Vijay Dev](https://github.com/vijaydev) diff --git a/guides/source/4_0_release_notes.md b/guides/source/4_0_release_notes.md index c9bc7f937b..e4b1b04681 100644 --- a/guides/source/4_0_release_notes.md +++ b/guides/source/4_0_release_notes.md @@ -74,7 +74,7 @@ Major Features * **Routing concerns** ([commit](https://github.com/rails/rails/commit/0dd24728a088fcb4ae616bb5d62734aca5276b1b)) - In the routing DSL, factor out common subroutes (`comments` from `/posts/1/comments` and `/videos/1/comments`). * **ActionController::Live** ([commit](https://github.com/rails/rails/commit/af0a9f9eefaee3a8120cfd8d05cbc431af376da3)) - Stream JSON with `response.stream`. * **Declarative ETags** ([commit](https://github.com/rails/rails/commit/ed5c938fa36995f06d4917d9543ba78ed506bb8d)) - Add controller-level etag additions that will be part of the action etag computation. -* **[Russian doll caching](http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)** ([commit](https://github.com/rails/rails/commit/4154bf012d2bec2aae79e4a49aa94a70d3e91d49)) - Cache nested fragments of views. Each fragment expires based on a set of dependencies (a cache key). The cache key is usually a template version number and a model object. +* **[Russian doll caching](https://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works)** ([commit](https://github.com/rails/rails/commit/4154bf012d2bec2aae79e4a49aa94a70d3e91d49)) - Cache nested fragments of views. Each fragment expires based on a set of dependencies (a cache key). The cache key is usually a template version number and a model object. * **Turbolinks** ([commit](https://github.com/rails/rails/commit/e35d8b18d0649c0ecc58f6b73df6b3c8d0c6bb74)) - Serve only one initial HTML page. When the user navigates to another page, use pushState to update the URL and use AJAX to update the title and body. * **Decouple ActionView from ActionController** ([commit](https://github.com/rails/rails/commit/78b0934dd1bb84e8f093fb8ef95ca99b297b51cd)) - ActionView was decoupled from ActionPack and will be moved to a separated gem in Rails 4.1. * **Do not depend on ActiveModel** ([commit](https://github.com/rails/rails/commit/166dbaa7526a96fdf046f093f25b0a134b277a68)) - ActionPack no longer depends on ActiveModel. @@ -281,4 +281,4 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/4-0-stable/a Credits ------- -See the [full list of contributors to Rails](http://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. +See the [full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. diff --git a/guides/source/4_1_release_notes.md b/guides/source/4_1_release_notes.md index b236f7ca24..d481a1f49b 100644 --- a/guides/source/4_1_release_notes.md +++ b/guides/source/4_1_release_notes.md @@ -159,7 +159,7 @@ By default, these preview classes live in `test/mailers/previews`. This can be configured using the `preview_path` option. See its -[documentation](http://api.rubyonrails.org/v4.1.0/classes/ActionMailer/Base.html#class-ActionMailer::Base-label-Previewing+emails) +[documentation](https://api.rubyonrails.org/v4.1.0/classes/ActionMailer/Base.html#class-ActionMailer::Base-label-Previewing+emails) for a detailed write up. ### Active Record enums @@ -231,7 +231,7 @@ extending it with `ActiveSupport::Concern`, then mixing it in to the `Todo` class. See its -[documentation](http://api.rubyonrails.org/v4.1.0/classes/Module/Concerning.html) +[documentation](https://api.rubyonrails.org/v4.1.0/classes/Module/Concerning.html) for a detailed write up and the intended use cases. ### CSRF protection from remote `<script>` tags @@ -727,6 +727,6 @@ Credits ------- See the -[full list of contributors to Rails](http://contributors.rubyonrails.org/) for +[full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. diff --git a/guides/source/4_2_release_notes.md b/guides/source/4_2_release_notes.md index 51d06bd07d..6bbd738826 100644 --- a/guides/source/4_2_release_notes.md +++ b/guides/source/4_2_release_notes.md @@ -154,9 +154,9 @@ remove_foreign_key :accounts, column: :owner_id ``` See the API documentation on -[add_foreign_key](http://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key) +[add_foreign_key](https://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_foreign_key) and -[remove_foreign_key](http://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_foreign_key) +[remove_foreign_key](https://api.rubyonrails.org/v4.2.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-remove_foreign_key) for a full description. @@ -877,7 +877,7 @@ Credits ------- See the -[full list of contributors to Rails](http://contributors.rubyonrails.org/) for +[full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails the stable and robust framework it is today. Kudos to all of them. diff --git a/guides/source/5_0_release_notes.md b/guides/source/5_0_release_notes.md index d63921507d..b090c71a57 100644 --- a/guides/source/5_0_release_notes.md +++ b/guides/source/5_0_release_notes.md @@ -150,7 +150,7 @@ The type of an attribute is given the opportunity to change how dirty tracking is performed. See its -[documentation](http://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html) +[documentation](https://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html) for a detailed write up. @@ -1081,7 +1081,7 @@ Credits ------- See the -[full list of contributors to Rails](http://contributors.rubyonrails.org/) for +[full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. diff --git a/guides/source/5_1_release_notes.md b/guides/source/5_1_release_notes.md index a5a7eb4b2e..f870c4c47c 100644 --- a/guides/source/5_1_release_notes.md +++ b/guides/source/5_1_release_notes.md @@ -41,8 +41,8 @@ Major Features [Pull Request](https://github.com/rails/rails/pull/26836) Rails 5.1 allows managing JavaScript dependencies -from NPM via Yarn. This will make it easy to use libraries like React, VueJS -or any other library from NPM world. The Yarn support is integrated with +from npm via Yarn. This will make it easy to use libraries like React, VueJS +or any other library from npm world. The Yarn support is integrated with the asset pipeline so that all dependencies will work seamlessly with the Rails 5.1 app. @@ -350,9 +350,9 @@ Please refer to the [Changelog][action-pack] for detailed changes. * Removed deprecated methods related to controller filters. ([Commit](https://github.com/rails/rails/commit/d7be30e8babf5e37a891522869e7b0191b79b757)) - + * Removed deprecated support to `:text` and `:nothing` in `render`. - ([Commit](https://github.com/rails/rails/commit/79a5ea9eadb4d43b62afacedc0706cbe88c54496), + ([Commit](https://github.com/rails/rails/commit/79a5ea9eadb4d43b62afacedc0706cbe88c54496), [Commit](https://github.com/rails/rails/commit/57e1c99a280bdc1b324936a690350320a1cd8111)) * Removed deprecated support for calling `HashWithIndifferentAccess` methods on `ActionController::Parameters`. @@ -644,7 +644,7 @@ Credits ------- See the -[full list of contributors to Rails](http://contributors.rubyonrails.org/) for +[full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. diff --git a/guides/source/5_2_release_notes.md b/guides/source/5_2_release_notes.md index c5b914fffc..7aac07dbbe 100644 --- a/guides/source/5_2_release_notes.md +++ b/guides/source/5_2_release_notes.md @@ -326,7 +326,7 @@ Please refer to the [Changelog][action-view] for detailed changes. select divider `option`. ([Pull Request](https://github.com/rails/rails/pull/31088)) -* Change `form_with` to generates ids by default. +* Change `form_with` to generate ids by default. ([Commit](https://github.com/rails/rails/commit/260d6f112a0ffdbe03e6f5051504cb441c1e94cd)) * Add `preload_link_tag` helper. @@ -615,6 +615,10 @@ Please refer to the [Changelog][active-record] for detailed changes. the parent class was getting deleted when the child was not. ([Commit](https://github.com/rails/rails/commit/b0fc04aa3af338d5a90608bf37248668d59fc881)) +* Idle database connections (previously just orphaned connections) are now + periodically reaped by the connection pool reaper. + ([Commit](https://github.com/rails/rails/pull/31221/commits/9027fafff6da932e6e64ddb828665f4b01fc8902)) + Active Model ------------ @@ -845,7 +849,7 @@ Credits ------- See the -[full list of contributors to Rails](http://contributors.rubyonrails.org/) +[full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. diff --git a/guides/source/6_0_release_notes.md b/guides/source/6_0_release_notes.md index f3ed21dc45..7c5478a03d 100644 --- a/guides/source/6_0_release_notes.md +++ b/guides/source/6_0_release_notes.md @@ -5,7 +5,10 @@ Ruby on Rails 6.0 Release Notes Highlights in Rails 6.0: +* Action Mailbox +* Action Text * Parallel Testing +* Action Cable Testing These release notes cover only the major changes. To learn about various bug fixes and changes, please refer to the change logs or check out the [list of @@ -28,6 +31,29 @@ guide. Major Features -------------- +### Action Mailbox + +[Pull Request](https://github.com/rails/rails/pull/34786) + +[Action Mailbox](https://github.com/rails/rails/tree/6-0-stable/actionmailbox) allows you +to route incoming emails to controller-like mailboxes. +You can read more about Action Mailbox in the [Action Mailbox Basics](action_mailbox_basics.html) guide. + +### Action Text + +[Pull Request](https://github.com/rails/rails/pull/34873) + +[Action Text](https://github.com/rails/rails/tree/6-0-stable/actiontext) +brings rich text content and editing to Rails. It includes +the [Trix editor](https://trix-editor.org) that handles everything from formatting +to links to quotes to lists to embedded images and galleries. +The rich text content generated by the Trix editor is saved in its own +RichText model that's associated with any existing Active Record model in the application. +Any embedded images (or other attachments) are automatically stored using +Active Storage and associated with the included RichText model. + +You can read more about Action Text in the [Action Text Overview](action_text_overview.html) guide. + ### Parallel Testing [Pull Request](https://github.com/rails/rails/pull/31900) @@ -37,6 +63,13 @@ test suite. While forking processes is the default method, threading is supported as well. Running tests in parallel reduces the time it takes your entire test suite to run. +### Action Cable Testing + +[Pull Request](https://github.com/rails/rails/pull/33659) + +[Action Cable testing tools](testing.html#testing-action-cable) allow you to test your +Action Cable functionality at any level: connections, channels, broadcasts. + Railties -------- @@ -44,10 +77,73 @@ Please refer to the [Changelog][railties] for detailed changes. ### Removals +* Remove deprecated `after_bundle` helper inside plugins templates. + ([Commit](https://github.com/rails/rails/commit/4d51efe24e461a2a3ed562787308484cd48370c7)) + +* Remove deprecated support to `config.ru` that uses the application + class as argument of `run`. + ([Commit](https://github.com/rails/rails/commit/553b86fc751c751db504bcbe2d033eb2bb5b6a0b)) + +* Remove deprecated `environment` argument from the rails commands. + ([Commit](https://github.com/rails/rails/commit/e20589c9be09c7272d73492d4b0f7b24e5595571)) + +* Remove deprecated `capify!` method in generators and templates. + ([Commit](https://github.com/rails/rails/commit/9d39f81d512e0d16a27e2e864ea2dd0e8dc41b17)) + +* Remove deprecated `config.secret_token`. + ([Commit](https://github.com/rails/rails/commit/46ac5fe69a20d4539a15929fe48293e1809a26b0)) + ### Deprecations +* Deprecate passing Rack server name as a regular argument to `rails server`. + ([Pull Request](https://github.com/rails/rails/pull/32058)) + +* Deprecate support for using `HOST` environment to specify server IP. + ([Pull Request](https://github.com/rails/rails/pull/32540)) + +* Deprecate accessing hashes returned by `config_for` by non-symbol keys. + ([Pull Request](https://github.com/rails/rails/pull/35198)) + ### Notable changes +* Add an explicit option `--using` or `-u` for specifying the server for the + `rails server` command. + ([Pull Request](https://github.com/rails/rails/pull/32058)) + +* Add ability to see the output of `rails routes` in expanded format. + ([Pull Request](https://github.com/rails/rails/pull/32130)) + +* Run the seed database task using inline Active Job adapter. + ([Pull Request](https://github.com/rails/rails/pull/34953)) + +* Add a command `rails db:system:change` to change the database of the application. + ([Pull Request](https://github.com/rails/rails/pull/34832)) + +* Add `rails test:channels` command to test only Action Cable channels. + ([Pull Request](https://github.com/rails/rails/pull/34947)) + +* Introduce guard against DNS rebinding attacks. + ([Pull Request](https://github.com/rails/rails/pull/33145)) + +* Add ability to abort on failure while running generator commands. + ([Pull Request](https://github.com/rails/rails/pull/34420)) + +* Make Webpacker the default JavaScript compiler for Rails 6. + ([Pull Request](https://github.com/rails/rails/pull/33079)) + +* Add multiple database support for `rails db:migrate:status` command. + ([Pull Request](https://github.com/rails/rails/pull/34137)) + +* Add ability to use different migration paths from multiple databases in + the generators. + ([Pull Request](https://github.com/rails/rails/pull/34021)) + +* Add support for multi environment credentials. + ([Pull Request](https://github.com/rails/rails/pull/33521)) + +* Make `null_store` as default cache store in test environment. + ([Pull Request](https://github.com/rails/rails/pull/33773)) + Action Cable ------------ @@ -55,10 +151,45 @@ Please refer to the [Changelog][action-cable] for detailed changes. ### Removals +* Replace `ActionCable.startDebugging()` and `ActionCable.stopDebugging()` + with `ActionCable.logger.enabled`. + ([Pull Request](https://github.com/rails/rails/pull/34370)) + ### Deprecations +* There are no deprecations for Action Cable in Rails 6.0. + ### Notable changes +* Add support for the `channel_prefix` option for PostgreSQL subscription adapters + in `cable.yml`. + ([Pull Request](https://github.com/rails/rails/pull/35276)) + +* Allow passing a custom configuration to `ActionCable::Server::Base`. + ([Pull Request](https://github.com/rails/rails/pull/34714)) + +* Add `:action_cable_connection` and `:action_cable_channel` load hooks. + ([Pull Request](https://github.com/rails/rails/pull/35094)) + +* Add `Channel::Base#broadcast_to` and `Channel::Base.broadcasting_for`. + ([Pull Request](https://github.com/rails/rails/pull/35021)) + +* Close a connection when calling `reject_unauthorized_connection` from an + `ActionCable::Connection`. + ([Pull Request](https://github.com/rails/rails/pull/34194)) + +* Convert the Action Cable JavaScript package from CoffeeScript to ES2015 and + publish the source code in the npm distribution. + ([Pull Request](https://github.com/rails/rails/pull/34370)) + +* Move the configuration of the WebSocket adapter and logger adapter + from properties of `ActionCable` to `ActionCable.adapters`. + ([Pull Request](https://github.com/rails/rails/pull/34370)) + +* Add an `id` option to the Redis adapter to distinguish Action Cable's Redis + connections. + ([Pull Request](https://github.com/rails/rails/pull/33798)) + Action Pack ----------- @@ -66,10 +197,82 @@ Please refer to the [Changelog][action-pack] for detailed changes. ### Removals +* Remove deprecated `fragment_cache_key` helper in favor of `combined_fragment_cache_key`. + ([Commit](https://github.com/rails/rails/commit/e70d3df7c9b05c129b0fdcca57f66eca316c5cfc)) + +* Remove deprecated methods in `ActionDispatch::TestResponse`: + `#success?` in favor of `#successful?`, `#missing?` in favor of `#not_found?`, + `#error?` in favor of `#server_error?`. + ([Commit](https://github.com/rails/rails/commit/13ddc92e079e59a0b894e31bf5bb4fdecbd235d1)) + ### Deprecations +* Deprecate `ActionDispatch::Http::ParameterFilter` in favor of `ActiveSupport::ParameterFilter`. + ([Pull Request](https://github.com/rails/rails/pull/34039)) + +* Deprecate controller level `force_ssl` in favor of `config.force_ssl`. + ([Pull Request](https://github.com/rails/rails/pull/32277)) + ### Notable changes +* Change `ActionDispatch::Response#content_type` returning Content-Type + header as it is. + ([Pull Request](https://github.com/rails/rails/pull/36034)) + +* Raise an `ArgumentError` if a resource param contains a colon. + ([Pull Request](https://github.com/rails/rails/pull/35236)) + +* Allow `ActionDispatch::SystemTestCase.driven_by` to be called with a block + to define specific browser capabilities. + ([Pull Request](https://github.com/rails/rails/pull/35081)) + +* Add `ActionDispatch::HostAuthorization` middleware that guards against DNS rebinding + attacks. + ([Pull Request](https://github.com/rails/rails/pull/33145)) + +* Allow the use of `parsed_body` in `ActionController::TestCase`. + ([Pull Request](https://github.com/rails/rails/pull/34717)) + +* Raise an `ArgumentError` when multiple root routes exist in the same context + without `as:` naming specifications. + ([Pull Request](https://github.com/rails/rails/pull/34494)) + +* Allow the use of `#rescue_from` for handling parameter parsing errors. + ([Pull Request](https://github.com/rails/rails/pull/34341)) + +* Add `ActionController::Parameters#each_value` for iterating through parameters. + ([Pull Request](https://github.com/rails/rails/pull/33979)) + +* Encode Content-Disposition filenames on `send_data` and `send_file`. + ([Pull Request](https://github.com/rails/rails/pull/33829)) + +* Expose `ActionController::Parameters#each_key`. + ([Pull Request](https://github.com/rails/rails/pull/33758)) + +* Add purpose and expiry metadata inside signed/encrypted cookies to prevent copying the value of + cookies into one another. + ([Pull Request](https://github.com/rails/rails/pull/32937)) + +* Raise `ActionController::RespondToMismatchError` for conflicting `respond_to` invocations. + ([Pull Request](https://github.com/rails/rails/pull/33446)) + +* Add an explicit error page for when a template is missing for a request format. + ([Pull Request](https://github.com/rails/rails/pull/29286)) + +* Introduce `ActionDispatch::DebugExceptions.register_interceptor`, a way to hook into + DebugExceptions and process the exception, before being rendered. + ([Pull Request](https://github.com/rails/rails/pull/23868)) + +* Output only one Content-Security-Policy nonce header value per request. + ([Pull Request](https://github.com/rails/rails/pull/32602)) + +* Add a module specifically for the Rails default headers configuration + that can be explicitly included in controllers. + ([Pull Request](https://github.com/rails/rails/pull/32484)) + +* Add `#dig` to `ActionDispatch::Request::Session`. + ([Pull Request](https://github.com/rails/rails/pull/32446)) + Action View ----------- @@ -77,10 +280,63 @@ Please refer to the [Changelog][action-view] for detailed changes. ### Removals +* Remove deprecated `image_alt` helper. + ([Commit](https://github.com/rails/rails/commit/60c8a03c8d1e45e48fcb1055ba4c49ed3d5ff78f)) + +* Remove an empty `RecordTagHelper` module from which the functionality + was already moved to the `record_tag_helper` gem. + ([Commit](https://github.com/rails/rails/commit/5c5ddd69b1e06fb6b2bcbb021e9b8dae17e7cb31)) + ### Deprecations +* Deprecate `ActionView::Template.finalize_compiled_template_methods` with + no replacement. + ([Pull Request](https://github.com/rails/rails/pull/35036)) + +* Deprecate `config.action_view.finalize_compiled_template_methods` with + no replacement. + ([Pull Request](https://github.com/rails/rails/pull/35036)) + +* Deprecate calling private model methods from the `options_from_collection_for_select` view helper. + ([Pull Request](https://github.com/rails/rails/pull/33547)) + ### Notable changes +* Clear Action View cache in development only on file changes, speeding up + development mode. + ([Pull Request](https://github.com/rails/rails/pull/35629)) + +* Move all of the Rails npm packages into a `@rails` scope. + ([Pull Request](https://github.com/rails/rails/pull/34905)) + +* Only accept formats from registered MIME types. + ([Pull Request](https://github.com/rails/rails/pull/35604), [Pull Request](https://github.com/rails/rails/pull/35753)) + +* Add allocations to the template and partial rendering server output. + ([Pull Request](https://github.com/rails/rails/pull/34136)) + +* Add a `year_format` option to `date_select` tag, making it possible to + customize year names. + ([Pull Request](https://github.com/rails/rails/pull/32190)) + +* Add a `nonce: true` option for `javascript_include_tag` helper to + support automatic nonce generation for a Content Security Policy. + ([Pull Request](https://github.com/rails/rails/pull/32607)) + +* Add a `action_view.finalize_compiled_template_methods` configuration to disable or + enable `ActionView::Template` finalizers. + ([Pull Request](https://github.com/rails/rails/pull/32418)) + +* Extract the JavaScript `confirm` call to its own, overridable method in `rails_ujs`. + ([Pull Request](https://github.com/rails/rails/pull/32404)) + +* Add a `action_controller.default_enforce_utf8` configuration option to handle + enforcing UTF-8 encoding. This defaults to `false`. + ([Pull Request](https://github.com/rails/rails/pull/32125)) + +* Add I18n key style support for locale keys to submit tags. + ([Pull Request](https://github.com/rails/rails/pull/26799)) + Action Mailer ------------- @@ -90,8 +346,41 @@ Please refer to the [Changelog][action-mailer] for detailed changes. ### Deprecations +* Deprecate `ActionMailer::Base.receive` in favor of Action Mailbox. + ([Commit](https://github.com/rails/rails/commit/e3f832a7433a291a51c5df397dc3dd654c1858cb)) + +* Deprecate `DeliveryJob` and `Parameterized::DeliveryJob` in favor of + `MailDeliveryJob`. + ([Pull Request](https://github.com/rails/rails/pull/34591)) + ### Notable changes +* Add `MailDeliveryJob` for delivering both regular and parameterized mail. + ([Pull Request](https://github.com/rails/rails/pull/34591)) + +* Allow custom email delivery jobs to work with the Action Mailer test assertions. + ([Pull Request](https://github.com/rails/rails/pull/34339)) + +* Allow specifying a template name for multipart emails with blocks instead of + using just the action name. + ([Pull Request](https://github.com/rails/rails/pull/22534)) + +* Add `perform_deliveries` to payload of `deliver.action_mailer` notification. + ([Pull Request](https://github.com/rails/rails/pull/33824)) + +* Improve the logging message when `perform_deliveries` is false to indicate + that sending of emails was skipped. + ([Pull Request](https://github.com/rails/rails/pull/33824)) + +* Allow calling `assert_enqueued_email_with` without block. + ([Pull Request](https://github.com/rails/rails/pull/33258)) + +* Perform the enqueued mail delivery jobs in the `assert_emails` block. + ([Pull Request](https://github.com/rails/rails/pull/32231)) + +* Allow `ActionMailer::Base` to unregister observers and interceptors. + ([Pull Request](https://github.com/rails/rails/pull/32207)) + Active Record ------------- @@ -99,10 +388,237 @@ Please refer to the [Changelog][active-record] for detailed changes. ### Removals +* Remove deprecated `#set_state` from the transaction object. + ([Commit](https://github.com/rails/rails/commit/6c745b0c5152a4437163a67707e02f4464493983)) + +* Remove deprecated `#supports_statement_cache?` from the database adapters. + ([Commit](https://github.com/rails/rails/commit/5f3ed8784383fb4eb0f9959f31a9c28a991b7553)) + +* Remove deprecated `#insert_fixtures` from the database adapters. + ([Commit](https://github.com/rails/rails/commit/400ba786e1d154448235f5f90183e48a1043eece)) + +* Remove deprecated `ActiveRecord::ConnectionAdapters::SQLite3Adapter#valid_alter_table_type?`. + ([Commit](https://github.com/rails/rails/commit/45b4d5f81f0c0ca72c18d0dea4a3a7b2ecc589bf)) + +* Remove support for passing the column name to `sum` when a block is passed. + ([Commit](https://github.com/rails/rails/commit/91ddb30083430622188d76eb9f29b78131df67f9)) + +* Remove support for passing the column name to `count` when a block is passed. + ([Commit](https://github.com/rails/rails/commit/67356f2034ab41305af7218f7c8b2fee2d614129)) + +* Remove support for delegation of missing methods in a relation to Arel. + ([Commit](https://github.com/rails/rails/commit/d97980a16d76ad190042b4d8578109714e9c53d0)) + +* Remove support for delegating missing methods in a relation to private methods of the class. + ([Commit](https://github.com/rails/rails/commit/a7becf147afc85c354e5cfa519911a948d25fc4d)) + +* Remove support for specifying a timestamp name for `#cache_key`. + ([Commit](https://github.com/rails/rails/commit/0bef23e630f62e38f20b5ae1d1d5dbfb087050ea)) + +* Remove deprecated `ActiveRecord::Migrator.migrations_path=`. + ([Commit](https://github.com/rails/rails/commit/90d7842186591cae364fab3320b524e4d31a7d7d)) + +* Remove deprecated `expand_hash_conditions_for_aggregates`. + ([Commit](https://github.com/rails/rails/commit/27b252d6a85e300c7236d034d55ec8e44f57a83e)) + + ### Deprecations +* Deprecate mismatched case-sensitivity collation comparisons for uniqueness validator. + ([Commit](https://github.com/rails/rails/commit/9def05385f1cfa41924bb93daa187615e88c95b9)) + +* Deprecate using class level querying methods if the receiver scope has leaked. + ([Pull Request](https://github.com/rails/rails/pull/35280)) + +* Deprecate `config.activerecord.sqlite3.represent_boolean_as_integer`. + ([Commit](https://github.com/rails/rails/commit/f59b08119bc0c01a00561d38279b124abc82561b)) + +* Deprecate passing `migrations_paths` to `connection.assume_migrated_upto_version`. + ([Commit](https://github.com/rails/rails/commit/c1b14aded27e063ead32fa911aa53163d7cfc21a)) + +* Deprecate `ActiveRecord::Result#to_hash` in favor of `ActiveRecord::Result#to_a`. + ([Commit](https://github.com/rails/rails/commit/16510d609c601aa7d466809f3073ec3313e08937)) + +* Deprecate methods in `DatabaseLimits`: `column_name_length`, `table_name_length`, + `columns_per_table`, `indexes_per_table`, `columns_per_multicolumn_index`, + `sql_query_length`, and `joins_per_query`. + ([Commit](https://github.com/rails/rails/commit/e0a1235f7df0fa193c7e299a5adee88db246b44f)) + +* Deprecate `update_attributes`/`!` in favor of `update`/`!`. + ([Commit](https://github.com/rails/rails/commit/5645149d3a27054450bd1130ff5715504638a5f5)) + ### Notable changes +* Bump the minimum version of the `sqlite3` gem to 1.4. + ([Pull Request](https://github.com/rails/rails/pull/35844)) + +* Add `rails db:prepare` to create a database if it doesn't exist, and run its migrations. + ([Pull Request](https://github.com/rails/rails/pull/35768)) + +* Add `after_save_commit` callback as shortcut for `after_commit :hook, on: [ :create, :update ]`. + ([Pull Request](https://github.com/rails/rails/pull/35804)) + +* Add `ActiveRecord::Relation#extract_associated` for extracting associated records from a relation. + ([Pull Request](https://github.com/rails/rails/pull/35784)) + +* Add `ActiveRecord::Relation#annotate` for adding SQL comments to ActiveRecord::Relation queries. + ([Pull Request](https://github.com/rails/rails/pull/35617)) + +* Add support for setting Optimizer Hints on databases. + ([Pull Request](https://github.com/rails/rails/pull/35615)) + +* Add `insert_all`/`insert_all!`/`upsert_all` methods for doing bulk inserts. + ([Pull Request](https://github.com/rails/rails/pull/35631)) + +* Add `rails db:seed:replant` that truncates tables of each database + for the current environment and loads the seeds. + ([Pull Request](https://github.com/rails/rails/pull/34779)) + +* Add `reselect` method, which is a short-hand for `unscope(:select).select(fields)`. + ([Pull Request](https://github.com/rails/rails/pull/33611)) + +* Add negative scopes for all enum values. + ([Pull Request](https://github.com/rails/rails/pull/35381)) + +* Add `#destroy_by` and `#delete_by` for conditional removals. + ([Pull Request](https://github.com/rails/rails/pull/35316)) + +* Add the ability to automatically switch database connections. + ([Pull Request](https://github.com/rails/rails/pull/35073)) + +* Add the ability to prevent writes to a database for the duration of a block. + ([Pull Request](https://github.com/rails/rails/pull/34505)) + +* Add an API for switching connections to support multiple databases. + ([Pull Request](https://github.com/rails/rails/pull/34052)) + +* Make timestamps with precision the default for migrations. + ([Pull Request](https://github.com/rails/rails/pull/34970)) + +* Support `:size` option to change text and blob size in MySQL. + ([Pull Request](https://github.com/rails/rails/pull/35071)) + +* Set both the foreign key and the foreign type columns to NULL for + polymorphic associations on `dependent: :nullify` strategy. + ([Pull Request](https://github.com/rails/rails/pull/28078)) + +* Allow a permitted instance of `ActionController::Parameters` to be passed as an + argument to `ActiveRecord::Relation#exists?`. + ([Pull Request](https://github.com/rails/rails/pull/34891)) + +* Add support in `#where` for endless ranges introduced in Ruby 2.6. + ([Pull Request](https://github.com/rails/rails/pull/34906)) + +* Make `ROW_FORMAT=DYNAMIC` a default create table option for MySQL. + ([Pull Request](https://github.com/rails/rails/pull/34742)) + +* Add the ability to disable scopes generated by `ActiveRecord.enum`. + ([Pull Request](https://github.com/rails/rails/pull/34605)) + +* Make implicit ordering configurable for a column. + ([Pull Request](https://github.com/rails/rails/pull/34480)) + +* Bump the minimum PostgreSQL version to 9.3, dropping support for 9.1 and 9.2. + ([Pull Request](https://github.com/rails/rails/pull/34520)) + +* Make the values of an enum frozen, raising an error when attempting to modify them. + ([Pull Request](https://github.com/rails/rails/pull/34517)) + +* Make the SQL of `ActiveRecord::StatementInvalid` errors its own error property + and include SQL binds as a separate error property. + ([Pull Request](https://github.com/rails/rails/pull/34468)) + +* Add an `:if_not_exists` option to `create_table`. + ([Pull Request](https://github.com/rails/rails/pull/31382)) + +* Add support for multiple databases to `rails db:schema:cache:dump` + and `rails db:schema:cache:clear`. + ([Pull Request](https://github.com/rails/rails/pull/34181)) + +* Add support for hash and url configs in database hash of `ActiveRecord::Base.connected_to`. + ([Pull Request](https://github.com/rails/rails/pull/34196)) + +* Add support for default expressions and expression indexes for MySQL. + ([Pull Request](https://github.com/rails/rails/pull/34307)) + +* Add an `index` option for `change_table` migration helpers. + ([Pull Request](https://github.com/rails/rails/pull/23593)) + +* Fix `transaction` reverting for migrations. Previously, commands inside of a `transaction` + in a reverted migration ran uninverted. This change fixes that. + ([Pull Request](https://github.com/rails/rails/pull/31604)) + +* Allow `ActiveRecord::Base.configurations=` to be set with a symbolized hash. + ([Pull Request](https://github.com/rails/rails/pull/33968)) + +* Fix the counter cache to only update if the record is actually saved. + ([Pull Request](https://github.com/rails/rails/pull/33913)) + +* Add expression indexes support for the SQLite adapter. + ([Pull Request](https://github.com/rails/rails/pull/33874)) + +* Allow subclasses to redefine autosave callbacks for associated records. + ([Pull Request](https://github.com/rails/rails/pull/33378)) + +* Bump the minimum MySQL version to 5.5.8. + ([Pull Request](https://github.com/rails/rails/pull/33853)) + +* Use the utf8mb4 character set by default in MySQL. + ([Pull Request](https://github.com/rails/rails/pull/33608)) + +* Add the ability to filter out sensitive data in `#inspect` + ([Pull Request](https://github.com/rails/rails/pull/33756), [Pull Request](https://github.com/rails/rails/pull/34208)) + +* Change `ActiveRecord::Base.configurations` to return an object instead of a hash. + ([Pull Request](https://github.com/rails/rails/pull/33637)) + +* Add database configuration to disable advisory locks. + ([Pull Request](https://github.com/rails/rails/pull/33691)) + +* Update SQLite3 adapter `alter_table` method to restore foreign keys. + ([Pull Request](https://github.com/rails/rails/pull/33585)) + +* Allow the `:to_table` option of `remove_foreign_key` to be invertible. + ([Pull Request](https://github.com/rails/rails/pull/33530)) + +* Fix default value for MySQL time types with specified precision. + ([Pull Request](https://github.com/rails/rails/pull/33280)) + +* Fix the `touch` option to behave consistently with `Persistence#touch` method. + ([Pull Request](https://github.com/rails/rails/pull/33107)) + +* Raise an exception for duplicate column definitions in Migrations. + ([Pull Request](https://github.com/rails/rails/pull/33029)) + +* Bump the minimum SQLite version to 3.8. + ([Pull Request](https://github.com/rails/rails/pull/32923)) + +* Fix parent records to not get saved with duplicate children records. + ([Pull Request](https://github.com/rails/rails/pull/32952)) + +* Ensure `Associations::CollectionAssociation#size` and `Associations::CollectionAssociation#empty?` + use loaded association ids if present. + ([Pull Request](https://github.com/rails/rails/pull/32617)) + +* Add support to preload associations of polymorphic associations when not all the records have the requested associations. + ([Commit](https://github.com/rails/rails/commit/75ef18c67c29b1b51314b6c8a963cee53394080b)) + +* Add `touch_all` method to `ActiveRecord::Relation`. + ([Pull Request](https://github.com/rails/rails/pull/31513)) + +* Add `ActiveRecord::Base.base_class?` predicate. + ([Pull Request](https://github.com/rails/rails/pull/32417)) + +* Add custom prefix/suffix options to `ActiveRecord::Store.store_accessor`. + ([Pull Request](https://github.com/rails/rails/pull/32306)) + +* Add `ActiveRecord::Base.create_or_find_by`/`!` to deal with the SELECT/INSERT race condition in + `ActiveRecord::Base.find_or_create_by`/`!` by leaning on unique constraints in the database. + ([Pull Request](https://github.com/rails/rails/pull/31989)) + +* Add `Relation#pick` as short-hand for single-value plucks. + ([Pull Request](https://github.com/rails/rails/pull/31941)) + Active Storage -------------- @@ -112,8 +628,72 @@ Please refer to the [Changelog][active-storage] for detailed changes. ### Deprecations +* Deprecate `config.active_storage.queue` in favor of `config.active_storage.queues.analysis` + and `config.active_storage.queues.purge`. + ([Pull Request](https://github.com/rails/rails/pull/34838)) + +* Deprecate `ActiveStorage::Downloading` in favor of `ActiveStorage::Blob#open`. + ([Commit](https://github.com/rails/rails/commit/ee21b7c2eb64def8f00887a9fafbd77b85f464f1)) + +* Deprecate using `mini_magick` directly for generating image variants in favor of + `image_processing`. + ([Commit](https://github.com/rails/rails/commit/697f4a93ad386f9fb7795f0ba68f815f16ebad0f)) + +* Deprecate `:combine_options` in Active Storage's ImageProcessing transformer + without replacement. + ([Commit](https://github.com/rails/rails/commit/697f4a93ad386f9fb7795f0ba68f815f16ebad0f)) + ### Notable changes +* Add support for generating BMP image variants. + ([Pull Request](https://github.com/rails/rails/pull/36051)) + +* Add support for generating TIFF image variants. + ([Pull Request](https://github.com/rails/rails/pull/34824)) + +* Add support for generating progressive JPEG image variants. + ([Pull Request](https://github.com/rails/rails/pull/34455)) + +* Add `ActiveStorage.routes_prefix` for configuring the Active Storage generated routes. + ([Pull Request](https://github.com/rails/rails/pull/33883)) + +* Generate a 404 Not Found response on `ActiveStorage::DiskController#show` when + the requested file is missing from the disk service. + ([Pull Request](https://github.com/rails/rails/pull/33666)) + +* Raise `ActiveStorage::FileNotFoundError` when the requested file is missing for + `ActiveStorage::Blob#download` and `ActiveStorage::Blob#open`. + ([Pull Request](https://github.com/rails/rails/pull/33666)) + +* Add a generic `ActiveStorage::Error` class that Active Storage exceptions inherit from. + ([Commit](https://github.com/rails/rails/commit/18425b837149bc0d50f8d5349e1091a623762d6b)) + +* Persist uploaded files assigned to a record to storage when the record + is saved instead of immediately. + ([Pull Request](https://github.com/rails/rails/pull/33303)) + +* Optionally replace existing files instead of adding to them when assigning to + a collection of attachments (as in `@user.update!(images: [ … ])`). Use + `config.active_storage.replace_on_assign_to_many` to control this behavior. + ([Pull Request](https://github.com/rails/rails/pull/33303), + [Pull Request](https://github.com/rails/rails/pull/36716)) + +* Add the ability to reflect on defined attachments using the existing + Active Record reflection mechanism. + ([Pull Request](https://github.com/rails/rails/pull/33018)) + +* Add `ActiveStorage::Blob#open`, which downloads a blob to a tempfile on disk + and yields the tempfile. + ([Commit](https://github.com/rails/rails/commit/ee21b7c2eb64def8f00887a9fafbd77b85f464f1)) + +* Support streaming downloads from Google Cloud Storage. Require version 1.11+ + of the `google-cloud-storage` gem. + ([Pull Request](https://github.com/rails/rails/pull/32788)) + +* Use the `image_processing` gem for Active Storage variants. This replaces using + `mini_magick` directly. + ([Pull Request](https://github.com/rails/rails/pull/32471)) + Active Model ------------ @@ -125,6 +705,42 @@ Please refer to the [Changelog][active-model] for detailed changes. ### Notable changes +* Add a configuration option to customize format of the `ActiveModel::Errors#full_message`. + ([Pull Request](https://github.com/rails/rails/pull/32956)) + +* Add support for configuring attribute name for `has_secure_password`. + ([Pull Request](https://github.com/rails/rails/pull/26764)) + +* Add `#slice!` method to `ActiveModel::Errors`. + ([Pull Request](https://github.com/rails/rails/pull/34489)) + +* Add `ActiveModel::Errors#of_kind?` to check presence of a specific error. + ([Pull Request](https://github.com/rails/rails/pull/34866)) + +* Fix `ActiveModel::Serializers::JSON#as_json` method for timestamps. + ([Pull Request](https://github.com/rails/rails/pull/31503)) + +* Fix numericality validator to still use value before type cast except Active Record. + ([Pull Request](https://github.com/rails/rails/pull/33654)) + +* Fix numericality equality validation of `BigDecimal` and `Float` + by casting to `BigDecimal` on both ends of the validation. + ([Pull Request](https://github.com/rails/rails/pull/32852)) + +* Fix year value when casting a multiparameter time hash. + ([Pull Request](https://github.com/rails/rails/pull/34990)) + +* Type cast falsy boolean symbols on boolean attribute as false. + ([Pull Request](https://github.com/rails/rails/pull/35794)) + +* Return correct date while converting parameters in `value_from_multiparameter_assignment` + for `ActiveModel::Type::Date`. + ([Pull Request](https://github.com/rails/rails/pull/29651)) + +* Fall back to parent locale before falling back to the `:errors` namespace while fetching + error translations. + ([Pull Request](https://github.com/rails/rails/pull/35424)) + Active Support -------------- @@ -132,10 +748,196 @@ Please refer to the [Changelog][active-support] for detailed changes. ### Removals +* Remove deprecated `#acronym_regex` method from `Inflections`. + ([Commit](https://github.com/rails/rails/commit/0ce67d3cd6d1b7b9576b07fecae3dd5b422a5689)) + +* Remove deprecated `Module#reachable?` method. + ([Commit](https://github.com/rails/rails/commit/6eb1d56a333fd2015610d31793ed6281acd66551)) + +* Remove `` Kernel#` `` without any replacement. + ([Pull Request](https://github.com/rails/rails/pull/31253)) + ### Deprecations +* Deprecate using negative integer arguments for `String#first` and + `String#last`. + ([Pull Request](https://github.com/rails/rails/pull/33058)) + +* Deprecate `ActiveSupport::Multibyte::Unicode#downcase/upcase/swapcase` + in favor of `String#downcase/upcase/swapcase`. + ([Pull Request](https://github.com/rails/rails/pull/34123)) + +* Deprecate `ActiveSupport::Multibyte::Unicode#normalize` + and `ActiveSupport::Multibyte::Chars#normalize` in favor of + `String#unicode_normalize`. + ([Pull Request](https://github.com/rails/rails/pull/34202)) + +* Deprecate `ActiveSupport::Multibyte::Chars.consumes?` in favor of + `String#is_utf8?`. + ([Pull Request](https://github.com/rails/rails/pull/34215)) + +* Deprecate `ActiveSupport::Multibyte::Unicode#pack_graphemes(array)` + and `ActiveSupport::Multibyte::Unicode#unpack_graphemes(string)` + in favor of `array.flatten.pack("U*")` and `string.scan(/\X/).map(&:codepoints)`, + respectively. + ([Pull Request](https://github.com/rails/rails/pull/34254)) + ### Notable changes +* Add support for parallel testing. + ([Pull Request](https://github.com/rails/rails/pull/31900)) + +* Make sure that `String#strip_heredoc` preserves frozen-ness of strings. + ([Pull Request](https://github.com/rails/rails/pull/32037)) + +* Add `String#truncate_bytes` to truncate a string to a maximum bytesize + without breaking multibyte characters or grapheme clusters. + ([Pull Request](https://github.com/rails/rails/pull/27319)) + +* Add `private` option to `delegate` method in order to delegate to + private methods. This option accepts `true/false` as the value. + ([Pull Request](https://github.com/rails/rails/pull/31944)) + +* Add support for translations through I18n for `ActiveSupport::Inflector#ordinal` + and `ActiveSupport::Inflector#ordinalize`. + ([Pull Request](https://github.com/rails/rails/pull/32168)) + +* Add `before?` and `after?` methods to `Date`, `DateTime`, + `Time`, and `TimeWithZone`. + ([Pull Request](https://github.com/rails/rails/pull/32185)) + +* Fix bug where `URI.unescape` would fail with mixed Unicode/escaped character + input. + ([Pull Request](https://github.com/rails/rails/pull/32183)) + +* Fix bug where `ActiveSupport::Cache` would massively inflate the storage + size when compression was enabled. + ([Pull Request](https://github.com/rails/rails/pull/32539)) + +* Redis cache store: `delete_matched` no longer blocks the Redis server. + ([Pull Request](https://github.com/rails/rails/pull/32614)) + +* Fix bug where `ActiveSupport::TimeZone.all` would fail when tzinfo data for + any timezone defined in `ActiveSupport::TimeZone::MAPPING` was missing. + ([Pull Request](https://github.com/rails/rails/pull/32613)) + +* Add `Enumerable#index_with` which allows creating a hash from an enumerable + with the value from a passed block or a default argument. + ([Pull Request](https://github.com/rails/rails/pull/32523)) + +* Allow `Range#===` and `Range#cover?` methods to work with `Range` argument. + ([Pull Request](https://github.com/rails/rails/pull/32938)) + +* Support key expiry in `increment/decrement` operations of RedisCacheStore. + ([Pull Request](https://github.com/rails/rails/pull/33254)) + +* Add cpu time, idle time, and allocations features to log subscriber events. + ([Pull Request](https://github.com/rails/rails/pull/33449)) + +* Add support for event object to the Active Support notification system. + ([Pull Request](https://github.com/rails/rails/pull/33451)) + +* Add support for not caching `nil` entries by introducing new option `skip_nil` + for `ActiveSupport::Cache#fetch`. + ([Pull Request](https://github.com/rails/rails/pull/25437)) + +* Add `Array#extract!` method which removes and returns the elements for which + block returns a true value. + ([Pull Request](https://github.com/rails/rails/pull/33137)) + +* Keep an HTML-safe string HTML-safe after slicing. + ([Pull Request](https://github.com/rails/rails/pull/33808)) + +* Add support for tracing constant autoloads via logging. + ([Commit](https://github.com/rails/rails/commit/c03bba4f1f03bad7dc034af555b7f2b329cf76f5)) + +* Define `unfreeze_time` as an alias of `travel_back`. + ([Pull Request](https://github.com/rails/rails/pull/33813)) + +* Change `ActiveSupport::TaggedLogging.new` to return a new logger instance + instead of mutating the one received as argument. + ([Pull Request](https://github.com/rails/rails/pull/27792)) + +* Treat `#delete_prefix`, `#delete_suffix` and `#unicode_normalize` methods + as non HTML-safe methods. + ([Pull Request](https://github.com/rails/rails/pull/33990)) + +* Fix bug where `#without` for `ActiveSupport::HashWithIndifferentAccess` + would fail with symbol arguments. + ([Pull Request](https://github.com/rails/rails/pull/34012)) + +* Rename `Module#parent`, `Module#parents`, and `Module#parent_name` to + `module_parent`, `module_parents`, and `module_parent_name`. + ([Pull Request](https://github.com/rails/rails/pull/34051)) + +* Add `ActiveSupport::ParameterFilter`. + ([Pull Request](https://github.com/rails/rails/pull/34039)) + +* Fix issue where duration was being rounded to a full second when a float + was added to the duration. + ([Pull Request](https://github.com/rails/rails/pull/34135)) + +* Make `#to_options` an alias for `#symbolize_keys` in + `ActiveSupport::HashWithIndifferentAccess`. + ([Pull Request](https://github.com/rails/rails/pull/34360)) + +* Don't raise an exception anymore if the same block is included multiple times + for a Concern. + ([Pull Request](https://github.com/rails/rails/pull/34553)) + +* Preserve key order passed to `ActiveSupport::CacheStore#fetch_multi`. + ([Pull Request](https://github.com/rails/rails/pull/34700)) + +* Fix `String#safe_constantize` to not throw a `LoadError` for incorrectly + cased constant references. + ([Pull Request](https://github.com/rails/rails/pull/34892)) + +* Add `Hash#deep_transform_values` and `Hash#deep_transform_values!`. + ([Commit](https://github.com/rails/rails/commit/b8dc06b8fdc16874160f61dcf58743fcc10e57db)) + +* Add `ActiveSupport::HashWithIndifferentAccess#assoc`. + ([Pull Request](https://github.com/rails/rails/pull/35080)) + +* Add `before_reset` callback to `CurrentAttributes` and define + `after_reset` as an alias of `resets` for symmetry. + ([Pull Request](https://github.com/rails/rails/pull/35063)) + +* Revise `ActiveSupport::Notifications.unsubscribe` to correctly + handle Regex or other multiple-pattern subscribers. + ([Pull Request](https://github.com/rails/rails/pull/32861)) + +* Add new autoloading mechanism using Zeitwerk. + ([Commit](https://github.com/rails/rails/commit/e53430fa9af239e21e11548499d814f540d421e5)) + +* Add `Array#including` and `Enumerable#including` to conveniently enlarge + a collection. + ([Commit](https://github.com/rails/rails/commit/bfaa3091c3c32b5980a614ef0f7b39cbf83f6db3)) + +* Rename `Array#without` and `Enumerable#without` to `Array#excluding` + and `Enumerable#excluding`. Old method names are retained as aliases. + ([Commit](https://github.com/rails/rails/commit/bfaa3091c3c32b5980a614ef0f7b39cbf83f6db3)) + +* Add support for supplying `locale` to `transliterate` and `parameterize`. + ([Pull Request](https://github.com/rails/rails/pull/35571)) + +* Fix `Time#advance` to work with dates before 1001-03-07. + ([Pull Request](https://github.com/rails/rails/pull/35659)) + +* Update `ActiveSupport::Notifications::Instrumenter#instrument` to allow + not passing block. + ([Pull Request](https://github.com/rails/rails/pull/35705)) + +* Use weak references in descendants tracker to allow anonymous subclasses to + be garbage collected. + ([Pull Request](https://github.com/rails/rails/pull/31442)) + +* Calling test methods with `with_info_handler` method to allow minitest-hooks + plugin to work. + ([Commit](https://github.com/rails/rails/commit/758ba117a008b6ea2d3b92c53b6a7a8d7ccbca69)) + +* Preserve `html_safe?` status on `ActiveSupport::SafeBuffer#*`. + ([Pull Request](https://github.com/rails/rails/pull/36012)) + Active Job ---------- @@ -143,10 +945,56 @@ Please refer to the [Changelog][active-job] for detailed changes. ### Removals +* Remove support for Qu gem. + ([Pull Request](https://github.com/rails/rails/pull/32300)) + ### Deprecations ### Notable changes +* Add support for custom serializers for Active Job arguments. + ([Pull Request](https://github.com/rails/rails/pull/30941)) + +* Add support for executing Active Jobs in the timezone in which + they were enqueued. + ([Pull Request](https://github.com/rails/rails/pull/32085)) + +* Allow passing multiple exceptions to `retry_on`/`discard_on`. + ([Commit](https://github.com/rails/rails/commit/3110caecbebdad7300daaf26bfdff39efda99e25)) + +* Allow calling `assert_enqueued_with` and `assert_enqueued_email_with` without a block. + ([Pull Request](https://github.com/rails/rails/pull/33258)) + +* Wrap the notifications for `enqueue` and `enqueue_at` in the `around_enqueue` + callback instead of `after_enqueue` callback. + ([Pull Request](https://github.com/rails/rails/pull/33171)) + +* Allow calling `perform_enqueued_jobs` without a block. + ([Pull Request](https://github.com/rails/rails/pull/33626)) + +* Allow calling `assert_performed_with` without a block. + ([Pull Request](https://github.com/rails/rails/pull/33635)) + +* Add `:queue` option to job assertions and helpers. + ([Pull Request](https://github.com/rails/rails/pull/33635)) + +* Add hooks to Active Job around retries and discards. + ([Pull Request](https://github.com/rails/rails/pull/33751)) + +* Add a way to test for subset of arguments when performing jobs. + ([Pull Request](https://github.com/rails/rails/pull/33995)) + +* Include deserialized arguments in jobs returned by Active Job + test helpers. + ([Pull Request](https://github.com/rails/rails/pull/34204)) + +* Allow Active Job assertion helpers to accept Proc for `only` + keyword. + ([Pull Request](https://github.com/rails/rails/pull/34339)) + +* Drop microseconds and nanoseconds from the job arguments in assertion helpers. + ([Pull Request](https://github.com/rails/rails/pull/35713)) + Ruby on Rails Guides -------------------- @@ -154,11 +1002,20 @@ Please refer to the [Changelog][guides] for detailed changes. ### Notable changes +* Add a section about troubleshooting of autoloading constants. + ([Commit](https://github.com/rails/rails/commit/c03bba4f1f03bad7dc034af555b7f2b329cf76f5)) + +* Add Action Mailbox Basics guide. + ([Pull Request](https://github.com/rails/rails/pull/34812)) + +* Add Action Text Overview guide. + ([Pull Request](https://github.com/rails/rails/pull/34878)) + Credits ------- See the -[full list of contributors to Rails](http://contributors.rubyonrails.org/) +[full list of contributors to Rails](https://contributors.rubyonrails.org/) for the many people who spent many hours making Rails, the stable and robust framework it is. Kudos to all of them. diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 2f602c3e0a..f1e2a0081f 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -27,6 +27,36 @@ client-side JavaScript framework and a server-side Ruby framework. You have access to your full domain model written with Active Record or your ORM of choice. +Terminology +----------- + +A single Action Cable server can handle multiple connection instances. It has one +connection instance per WebSocket connection. A single user may have multiple +WebSockets open to your application if they use multiple browser tabs or devices. +The client of a WebSocket connection is called the consumer. + +Each consumer can in turn subscribe to multiple cable channels. Each channel +encapsulates a logical unit of work, similar to what a controller does in +a regular MVC setup. For example, you could have a `ChatChannel` and +an `AppearancesChannel`, and a consumer could be subscribed to either +or to both of these channels. At the very least, a consumer should be subscribed +to one channel. + +When the consumer is subscribed to a channel, they act as a subscriber. +The connection between the subscriber and the channel is, surprise-surprise, +called a subscription. A consumer can act as a subscriber to a given channel +any number of times. For example, a consumer could subscribe to multiple chat rooms +at the same time. (And remember that a physical user may have multiple consumers, +one per tab/device open to your connection). + +Each channel can then again be streaming zero or more broadcastings. +A broadcasting is a pubsub link where anything transmitted by the broadcaster is +sent directly to the channel subscribers who are streaming that named broadcasting. + +As you can see, this is a fairly deep architectural stack. There's a lot of new +terminology to identify the new pieces, and on top of that, you're dealing +with both client and server side reflections of each unit. + What is Pub/Sub --------------- @@ -147,32 +177,50 @@ established using the following JavaScript, which is generated by default by Rai #### Connect Consumer ```js -// app/assets/javascripts/cable.js -//= require action_cable -//= require_self -//= require_tree ./channels +// app/javascript/channels/consumer.js +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the `rails generate channel` command. -(function() { - this.App || (this.App = {}); +import { createConsumer } from "@rails/actioncable" - App.cable = ActionCable.createConsumer(); -}).call(this); +export default createConsumer() ``` This will ready a consumer that'll connect against `/cable` on your server by default. The connection won't be established until you've also specified at least one subscription you're interested in having. +The consumer can optionally take an argument that specifies the URL to connect to. This +can be a string, or a function that returns a string that will be called when the +WebSocket is opened. + +```js +// Specify a different URL to connect to +createConsumer('https://ws.example.com/cable') + +// Use a function to dynamically generate the URL +createConsumer(getWebSocketURL) + +function getWebSocketURL { + const token = localStorage.get('auth-token') + return `https://ws.example.com/cable?token=${token}` +} +``` + #### Subscriber A consumer becomes a subscriber by creating a subscription to a given channel: -```coffeescript -# app/assets/javascripts/cable/subscriptions/chat.coffee -App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" } +```js +// app/javascript/channels/chat_channel.js +import consumer from "./consumer" + +consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }) -# app/assets/javascripts/cable/subscriptions/appearance.coffee -App.cable.subscriptions.create { channel: "AppearanceChannel" } +// app/javascript/channels/appearance_channel.js +import consumer from "./consumer" + +consumer.subscriptions.create({ channel: "AppearanceChannel" }) ``` While this creates the subscription, the functionality needed to respond to @@ -181,9 +229,12 @@ received data will be described later on. A consumer can act as a subscriber to a given channel any number of times. For example, a consumer could subscribe to multiple chat rooms at the same time: -```coffeescript -App.cable.subscriptions.create { channel: "ChatChannel", room: "1st Room" } -App.cable.subscriptions.create { channel: "ChatChannel", room: "2nd Room" } +```js +// app/javascript/channels/chat_channel.js +import consumer from "./consumer" + +consumer.subscriptions.create({ channel: "ChatChannel", room: "1st Room" }) +consumer.subscriptions.create({ channel: "ChatChannel", room: "2nd Room" }) ``` ## Client-Server Interactions @@ -256,24 +307,31 @@ When a consumer is subscribed to a channel, they act as a subscriber. This connection is called a subscription. Incoming messages are then routed to these channel subscriptions based on an identifier sent by the cable consumer. -```coffeescript -# app/assets/javascripts/cable/subscriptions/chat.coffee -# Assumes you've already requested the right to send web notifications -App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, - received: (data) -> - @appendLine(data) - - appendLine: (data) -> - html = @createLine(data) - $("[data-chat-room='Best Room']").append(html) - - createLine: (data) -> - """ - <article class="chat-line"> - <span class="speaker">#{data["sent_by"]}</span> - <span class="body">#{data["body"]}</span> - </article> - """ +```js +// app/javascript/channels/chat_channel.js +// Assumes you've already requested the right to send web notifications +import consumer from "./consumer" + +consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, { + received(data) { + this.appendLine(data) + }, + + appendLine(data) { + const html = this.createLine(data) + const element = document.querySelector("[data-chat-room='Best Room']") + element.insertAdjacentHTML("beforeend", html) + }, + + createLine(data) { + return ` + <article class="chat-line"> + <span class="speaker">${data["sent_by"]}</span> + <span class="body">${data["body"]}</span> + </article> + ` + } +}) ``` ### Passing Parameters to Channels @@ -293,23 +351,30 @@ end An object passed as the first argument to `subscriptions.create` becomes the params hash in the cable channel. The keyword `channel` is required: -```coffeescript -# app/assets/javascripts/cable/subscriptions/chat.coffee -App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, - received: (data) -> - @appendLine(data) - - appendLine: (data) -> - html = @createLine(data) - $("[data-chat-room='Best Room']").append(html) - - createLine: (data) -> - """ - <article class="chat-line"> - <span class="speaker">#{data["sent_by"]}</span> - <span class="body">#{data["body"]}</span> - </article> - """ +```js +// app/javascript/channels/chat_channel.js +import consumer from "./consumer" + +consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, { + received(data) { + this.appendLine(data) + }, + + appendLine(data) { + const html = this.createLine(data) + const element = document.querySelector("[data-chat-room='Best Room']") + element.insertAdjacentHTML("beforeend", html) + }, + + createLine(data) { + return ` + <article class="chat-line"> + <span class="speaker">${data["sent_by"]}</span> + <span class="body">${data["body"]}</span> + </article> + ` + } +}) ``` ```ruby @@ -340,13 +405,17 @@ class ChatChannel < ApplicationCable::Channel end ``` -```coffeescript -# app/assets/javascripts/cable/subscriptions/chat.coffee -App.chatChannel = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, - received: (data) -> - # data => { sent_by: "Paul", body: "This is a cool chat app." } +```js +// app/javascript/channels/chat_channel.js +import consumer from "./consumer" + +const chatChannel = consumer.subscriptions.create({ channel: "ChatChannel", room: "Best Room" }, { + received(data) { + // data => { sent_by: "Paul", body: "This is a cool chat app." } + } +} -App.chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." }) +chatChannel.send({ sent_by: "Paul", body: "This is a cool chat app." }) ``` The rebroadcast will be received by all connected clients, _including_ the @@ -396,46 +465,69 @@ appear/disappear API could be backed by Redis, a database, or whatever else. Create the client-side appearance channel subscription: -```coffeescript -# app/assets/javascripts/cable/subscriptions/appearance.coffee -App.cable.subscriptions.create "AppearanceChannel", - # Called when the subscription is ready for use on the server. - connected: -> - @install() - @appear() - - # Called when the WebSocket connection is closed. - disconnected: -> - @uninstall() - - # Called when the subscription is rejected by the server. - rejected: -> - @uninstall() - - appear: -> - # Calls `AppearanceChannel#appear(data)` on the server. - @perform("appear", appearing_on: $("main").data("appearing-on")) - - away: -> - # Calls `AppearanceChannel#away` on the server. - @perform("away") - - - buttonSelector = "[data-behavior~=appear_away]" - - install: -> - $(document).on "turbolinks:load.appearance", => - @appear() - - $(document).on "click.appearance", buttonSelector, => - @away() - false - - $(buttonSelector).show() - - uninstall: -> - $(document).off(".appearance") - $(buttonSelector).hide() +```js +// app/javascript/channels/appearance_channel.js +import consumer from "./consumer" + +consumer.subscriptions.create("AppearanceChannel", { + // Called once when the subscription is created. + initialized() { + this.update = this.update.bind(this) + }, + + // Called when the subscription is ready for use on the server. + connected() { + this.install() + this.update() + }, + + // Called when the WebSocket connection is closed. + disconnected() { + this.uninstall() + }, + + // Called when the subscription is rejected by the server. + rejected() { + this.uninstall() + }, + + update() { + this.documentIsActive ? this.appear() : this.away() + }, + + appear() { + // Calls `AppearanceChannel#appear(data)` on the server. + this.perform("appear", { appearing_on: this.appearingOn }) + }, + + away() { + // Calls `AppearanceChannel#away` on the server. + this.perform("away") + }, + + install() { + window.addEventListener("focus", this.update) + window.addEventListener("blur", this.update) + document.addEventListener("turbolinks:load", this.update) + document.addEventListener("visibilitychange", this.update) + }, + + uninstall() { + window.removeEventListener("focus", this.update) + window.removeEventListener("blur", this.update) + document.removeEventListener("turbolinks:load", this.update) + document.removeEventListener("visibilitychange", this.update) + }, + + get documentIsActive() { + return document.visibilityState == "visible" && document.hasFocus() + }, + + get appearingOn() { + const element = document.querySelector("[data-appearing-on]") + return element ? element.getAttribute("data-appearing-on") : null + } +}) ``` ##### Client-Server Interaction @@ -445,16 +537,16 @@ ActionCable.createConsumer("ws://cable.example.com")`. (`cable.js`). The **Server** identifies this connection by `current_user`. 2. **Client** subscribes to the appearance channel via -`App.cable.subscriptions.create(channel: "AppearanceChannel")`. (`appearance.coffee`) +`consumer.subscriptions.create({ channel: "AppearanceChannel" })`. (`appearance_channel.js`) 3. **Server** recognizes a new subscription has been initiated for the appearance channel and runs its `subscribed` callback, calling the `appear` method on `current_user`. (`appearance_channel.rb`) 4. **Client** recognizes that a subscription has been established and calls -`connected` (`appearance.coffee`) which in turn calls `@install` and `@appear`. -`@appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a -data hash of `{ appearing_on: $("main").data("appearing-on") }`. This is +`connected` (`appearance_channel.js`) which in turn calls `install` and `appear`. +`appear` calls `AppearanceChannel#appear(data)` on the server, and supplies a +data hash of `{ appearing_on: this.appearingOn }`. This is possible because the server-side channel instance automatically exposes all public methods declared on the class (minus the callbacks), so that these can be reached as remote procedure calls via a subscription's `perform` method. @@ -488,13 +580,17 @@ end Create the client-side web notifications channel subscription: -```coffeescript -# app/assets/javascripts/cable/subscriptions/web_notifications.coffee -# Client-side which assumes you've already requested -# the right to send web notifications. -App.cable.subscriptions.create "WebNotificationsChannel", - received: (data) -> - new Notification data["title"], body: data["body"] +```js +// app/javascript/channels/web_notifications_channel.js +// Client-side which assumes you've already requested +// the right to send web notifications. +import consumer from "./consumer" + +consumer.subscriptions.create("WebNotificationsChannel", { + received(data) { + new Notification(data["title"], body: data["body"]) + } +}) ``` Broadcast content to a web notification channel instance from elsewhere in your @@ -574,7 +670,7 @@ passed to the server config as an array. The origins can be instances of strings or regular expressions, against which a check for the match will be performed. ```ruby -config.action_cable.allowed_request_origins = ['http://rubyonrails.com', %r{http://ruby.*}] +config.action_cable.allowed_request_origins = ['https://rubyonrails.com', %r{http://ruby.*}] ``` To disable and allow requests from any origin: @@ -592,6 +688,21 @@ To configure the URL, add a call to `action_cable_meta_tag` in your HTML layout HEAD. This uses a URL or path typically set via `config.action_cable.url` in the environment configuration files. +### Worker Pool Configuration + +The worker pool is used to run connection callbacks and channel actions in +isolation from the server's main thread. Action Cable allows the application +to configure the number of simultaneously processed threads in the worker pool. + +```ruby +config.action_cable.worker_pool_size = 4 +``` + +Also, note that your server must provide at least the same number of database +connections as you have workers. The default worker pool size is set to 4, so +that means you have to make at least 4 database connections available. + You can change that in `config/database.yml` through the `pool` attribute. + ### Other Configurations The other common option to configure is the log tags applied to the @@ -609,11 +720,6 @@ config.action_cable.log_tags = [ 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 size is set to 4, so -that means you have to make at least that available. You can change that in -`config/database.yml` through the `pool` attribute. - ## Running Standalone Cable Servers ### In App @@ -629,10 +735,9 @@ class Application < Rails::Application end ``` -You can use `App.cable = ActionCable.createConsumer()` to connect to the cable -server if `action_cable_meta_tag` is invoked in the layout. A custom path is -specified as first argument to `createConsumer` (e.g. `App.cable = -ActionCable.createConsumer("/websocket")`). +You can use `ActionCable.createConsumer()` to connect to the cable +server if `action_cable_meta_tag` is invoked in the layout. Otherwise, A path is +specified as first argument to `createConsumer` (e.g. `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 Action Cable, but the use of Redis @@ -690,3 +795,8 @@ internally, irrespective of whether the application server is multi-threaded or Accordingly, Action Cable works with popular servers like Unicorn, Puma, and Passenger. + +## Testing + +You can find detailed instructions on how to test your Action Cable functionality in the +[testing guide](testing.html#testing-action-cable). diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index aa746e4731..f8367283fc 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -61,7 +61,7 @@ end The [Layouts & Rendering Guide](layouts_and_rendering.html) explains this in more detail. -`ApplicationController` inherits from `ActionController::Base`, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the [API documentation](http://api.rubyonrails.org/classes/ActionController.html) or in the source itself. +`ApplicationController` inherits from `ActionController::Base`, which defines a number of helpful methods. This guide will cover some of these, but if you're curious to see what's in there, you can see all of them in the [API documentation](https://api.rubyonrails.org/classes/ActionController.html) or in the source itself. Only public methods are callable as actions. It is a best practice to lower the visibility of methods (with `private` or `protected`) which are not intended to be actions, like auxiliary methods or filters. @@ -157,7 +157,7 @@ And, assuming that you're sending the data to `CompaniesController`, it would th { name: "acme", address: "123 Carrot Street", company: { name: "acme", address: "123 Carrot Street" } } ``` -You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](http://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html) +You can customize the name of the key or specific parameters you want to wrap by consulting the [API documentation](https://api.rubyonrails.org/classes/ActionController/ParamsWrapper.html) NOTE: Support for parsing XML parameters has been extracted into a gem named `actionpack-xml_parser`. @@ -212,7 +212,7 @@ class PeopleController < ActionController::Base end # This will pass with flying colors as long as there's a person key - # in the parameters, otherwise it'll raise a + # in the parameters, otherwise it'll raise an # ActionController::ParameterMissing exception, which will get # caught by ActionController::Base and turned into a 400 Bad # Request error. @@ -469,7 +469,7 @@ To reset the entire session, use `reset_session`. The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for passing error messages etc. -It is accessed in much the same way as the session, as a hash (it's a [FlashHash](http://api.rubyonrails.org/classes/ActionDispatch/Flash/FlashHash.html) instance). +It is accessed in much the same way as the session, as a hash (it's a [FlashHash](https://api.rubyonrails.org/classes/ActionDispatch/Flash/FlashHash.html) instance). Let's use the act of logging out as an example. The controller can send a message which will be displayed to the user on the next request: @@ -591,7 +591,7 @@ Rails also provides a signed cookie jar and an encrypted cookie jar for storing sensitive data. The signed cookie jar appends a cryptographic signature on the cookie values to protect their integrity. The encrypted cookie jar encrypts the values in addition to signing them, so that they cannot be read by the end user. -Refer to the [API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Cookies.html) +Refer to the [API documentation](https://api.rubyonrails.org/classes/ActionDispatch/Cookies.html) for more details. These special cookie jars use a serializer to serialize the assigned values into @@ -814,7 +814,7 @@ In every controller there are two accessor methods pointing to the request and t ### The `request` Object -The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the [Rails API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Request.html) and [Rack Documentation](http://www.rubydoc.info/github/rack/rack/Rack/Request). Among the properties that you can access on this object are: +The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the [Rails API documentation](https://api.rubyonrails.org/classes/ActionDispatch/Request.html) and [Rack Documentation](https://www.rubydoc.info/github/rack/rack/Rack/Request). Among the properties that you can access on this object are: | Property of `request` | Purpose | | ----------------------------------------- | -------------------------------------------------------------------------------- | @@ -836,7 +836,7 @@ Rails collects all of the parameters sent along with the request in the `params` ### The `response` Object -The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. To get a full list of the available methods, refer to the [Rails API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Response.html) and [Rack Documentation](http://www.rubydoc.info/github/rack/rack/Rack/Response). +The response object is not usually used directly, but is built up during the execution of the action and rendering of the data that is being sent back to the user, but sometimes - like in an after filter - it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values. To get a full list of the available methods, refer to the [Rails API documentation](https://api.rubyonrails.org/classes/ActionDispatch/Response.html) and [Rack Documentation](https://www.rubydoc.info/github/rack/rack/Rack/Response). | Property of `response` | Purpose | | ---------------------- | --------------------------------------------------------------------------------------------------- | diff --git a/guides/source/action_mailbox_basics.md b/guides/source/action_mailbox_basics.md new file mode 100644 index 0000000000..de92401226 --- /dev/null +++ b/guides/source/action_mailbox_basics.md @@ -0,0 +1,376 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.** + +Action Mailbox Basics +===================== + +This guide provides you with all you need to get started in receiving +emails to your application. + +After reading this guide, you will know: + +* How to receive email within a Rails application. +* How to configure Action Mailbox. +* How to generate and route emails to a mailbox. +* How to test incoming emails. + +-------------------------------------------------------------------------------- + +Introduction +------------ + +Action Mailbox routes incoming emails to controller-like mailboxes for +processing in Rails. It ships with ingresses for Mailgun, Mandrill, Postmark, +and SendGrid. You can also handle inbound mails directly via the built-in Exim, +Postfix, and Qmail ingresses. + +The inbound emails are turned into `InboundEmail` records using Active Record +and feature lifecycle tracking, storage of the original email on cloud storage +via Active Storage, and responsible data handling with +on-by-default incineration. + +These inbound emails are routed asynchronously using Active Job to one or +several dedicated mailboxes, which are capable of interacting directly +with the rest of your domain model. + +## Setup + +Install migrations needed for `InboundEmail` and ensure Active Storage is set up: + +```bash +$ rails action_mailbox:install +$ rails db:migrate +``` + +## Configuration + +### Exim + +Tell Action Mailbox to accept emails from an SMTP relay: + +```ruby +# config/environments/production.rb +config.action_mailbox.ingress = :relay +``` + +Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress. + +Use `rails credentials:edit` to add the password to your application's encrypted credentials under +`action_mailbox.ingress_password`, where Action Mailbox will automatically find it: + +```yaml +action_mailbox: + ingress_password: ... +``` + +Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable. + +Configure Exim to pipe inbound emails to `bin/rails action_mailbox:ingress:exim`, +providing the `URL` of the relay ingress and the `INGRESS_PASSWORD` you +previously generated. If your application lived at `https://example.com`, the +full command would look like this: + +```shell +bin/rails action_mailbox:ingress:exim URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=... +``` + +### Mailgun + +Give Action Mailbox your +[Mailgun API key](https://help.mailgun.com/hc/en-us/articles/203380100-Where-can-I-find-my-API-key-and-SMTP-credentials) +so it can authenticate requests to the Mailgun ingress. + +Use `rails credentials:edit` to add your API key to your application's +encrypted credentials under `action_mailbox.mailgun_api_key`, +where Action Mailbox will automatically find it: + +```yaml +action_mailbox: + mailgun_api_key: ... +``` + +Alternatively, provide your API key in the `MAILGUN_INGRESS_API_KEY` environment +variable. + +Tell Action Mailbox to accept emails from Mailgun: + +```ruby +# config/environments/production.rb +config.action_mailbox.ingress = :mailgun +``` + +[Configure Mailgun](https://documentation.mailgun.com/en/latest/user_manual.html#receiving-forwarding-and-storing-messages) +to forward inbound emails to `/rails/action_mailbox/mailgun/inbound_emails/mime`. +If your application lived at `https://example.com`, you would specify the +fully-qualified URL `https://example.com/rails/action_mailbox/mailgun/inbound_emails/mime`. + +### Mandrill + +Give Action Mailbox your Mandrill API key so it can authenticate requests to +the Mandrill ingress. + +Use `rails credentials:edit` to add your API key to your application's +encrypted credentials under `action_mailbox.mandrill_api_key`, +where Action Mailbox will automatically find it: + +```yaml +action_mailbox: + mandrill_api_key: ... +``` + +Alternatively, provide your API key in the `MANDRILL_INGRESS_API_KEY` +environment variable. + +Tell Action Mailbox to accept emails from Mandrill: + +```ruby +# config/environments/production.rb +config.action_mailbox.ingress = :mandrill +``` + +[Configure Mandrill](https://mandrill.zendesk.com/hc/en-us/articles/205583197-Inbound-Email-Processing-Overview) +to route inbound emails to `/rails/action_mailbox/mandrill/inbound_emails`. +If your application lived at `https://example.com`, you would specify +the fully-qualified URL `https://example.com/rails/action_mailbox/mandrill/inbound_emails`. + +### Postfix + +Tell Action Mailbox to accept emails from an SMTP relay: + +```ruby +# config/environments/production.rb +config.action_mailbox.ingress = :relay +``` + +Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress. + +Use `rails credentials:edit` to add the password to your application's encrypted credentials under +`action_mailbox.ingress_password`, where Action Mailbox will automatically find it: + +```yaml +action_mailbox: + ingress_password: ... +``` + +Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable. + +[Configure Postfix](https://serverfault.com/questions/258469/how-to-configure-postfix-to-pipe-all-incoming-email-to-a-script) +to pipe inbound emails to `bin/rails action_mailbox:ingress:postfix`, providing +the `URL` of the Postfix ingress and the `INGRESS_PASSWORD` you previously +generated. If your application lived at `https://example.com`, the full command +would look like this: + +```shell +$ bin/rails action_mailbox:ingress:postfix URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=... +``` + +### Postmark + +Tell Action Mailbox to accept emails from Postmark: + +```ruby +# config/environments/production.rb +config.action_mailbox.ingress = :postmark +``` + +Generate a strong password that Action Mailbox can use to authenticate +requests to the Postmark ingress. + +Use `rails credentials:edit` to add the password to your application's +encrypted credentials under `action_mailbox.ingress_password`, +where Action Mailbox will automatically find it: + +```yaml +action_mailbox: + ingress_password: ... +``` + +Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` +environment variable. + +[Configure Postmark inbound webhook](https://postmarkapp.com/manual#configure-your-inbound-webhook-url) +to forward inbound emails to `/rails/action_mailbox/postmark/inbound_emails` with the username `actionmailbox` +and the password you previously generated. If your application lived at `https://example.com`, you would +configure Postmark with the following fully-qualified URL: + +``` +https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/postmark/inbound_emails +``` + +NOTE: When configuring your Postmark inbound webhook, be sure to check the box labeled **"Include raw email content in JSON payload"**. +Action Mailbox needs the raw email content to work. + +### Qmail + +Tell Action Mailbox to accept emails from an SMTP relay: + +```ruby +# config/environments/production.rb +config.action_mailbox.ingress = :relay +``` + +Generate a strong password that Action Mailbox can use to authenticate requests to the relay ingress. + +Use `rails credentials:edit` to add the password to your application's encrypted credentials under +`action_mailbox.ingress_password`, where Action Mailbox will automatically find it: + +```yaml +action_mailbox: + ingress_password: ... +``` + +Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` environment variable. + +Configure Qmail to pipe inbound emails to `bin/rails action_mailbox:ingress:qmail`, +providing the `URL` of the relay ingress and the `INGRESS_PASSWORD` you +previously generated. If your application lived at `https://example.com`, the +full command would look like this: + +```shell +bin/rails action_mailbox:ingress:qmail URL=https://example.com/rails/action_mailbox/relay/inbound_emails INGRESS_PASSWORD=... +``` + +### SendGrid + +Tell Action Mailbox to accept emails from SendGrid: + +```ruby +# config/environments/production.rb +config.action_mailbox.ingress = :sendgrid +``` + +Generate a strong password that Action Mailbox can use to authenticate +requests to the SendGrid ingress. + +Use `rails credentials:edit` to add the password to your application's +encrypted credentials under `action_mailbox.ingress_password`, +where Action Mailbox will automatically find it: + +```yaml +action_mailbox: + ingress_password: ... +``` + +Alternatively, provide the password in the `RAILS_INBOUND_EMAIL_PASSWORD` +environment variable. + +[Configure SendGrid Inbound Parse](https://sendgrid.com/docs/for-developers/parsing-email/setting-up-the-inbound-parse-webhook/) +to forward inbound emails to +`/rails/action_mailbox/sendgrid/inbound_emails` with the username `actionmailbox` +and the password you previously generated. If your application lived at `https://example.com`, +you would configure SendGrid with the following URL: + +``` +https://actionmailbox:PASSWORD@example.com/rails/action_mailbox/sendgrid/inbound_emails +``` + +NOTE: When configuring your SendGrid Inbound Parse webhook, be sure to check the box labeled **“Post the raw, full MIME message.”** Action Mailbox needs the raw MIME message to work. + +## Examples + +Configure basic routing: + +```ruby +# app/mailboxes/application_mailbox.rb +class ApplicationMailbox < ActionMailbox::Base + routing /^save@/i => :forwards + routing /@replies\./i => :replies +end +``` + +Then set up a mailbox: + +```ruby +# Generate new mailbox +$ bin/rails generate mailbox forwards +``` + +```ruby +# app/mailboxes/forwards_mailbox.rb +class ForwardsMailbox < ApplicationMailbox + # Callbacks specify prerequisites to processing + before_processing :require_forward + + def process + if forwarder.buckets.one? + record_forward + else + stage_forward_and_request_more_details + end + end + + private + def require_forward + unless message.forward? + # Use Action Mailers to bounce incoming emails back to sender – this halts processing + bounce_with Forwards::BounceMailer.missing_forward( + inbound_email, forwarder: forwarder + ) + end + end + + def forwarder + @forwarder ||= Person.where(email_address: mail.from) + end + + def record_forward + forwarder.buckets.first.record \ + Forward.new forwarder: forwarder, subject: message.subject, content: mail.content + end + + def stage_forward_and_request_more_details + Forwards::RoutingMailer.choose_project(mail).deliver_now + end +end +``` + +## Incineration of InboundEmails + +By default, an InboundEmail that has been successfully processed will be +incinerated after 30 days. This ensures you're not holding on to people's data +willy-nilly after they may have canceled their accounts or deleted their +content. The intention is that after you've processed an email, you should have +extracted all the data you needed and turned it into domain models and content +on your side of the application. The InboundEmail simply stays in the system +for the extra time to provide debugging and forensics options. + +The actual incineration is done via the `IncinerationJob` that's scheduled +to run after `config.action_mailbox.incinerate_after` time. This value is +by default set to `30.days`, but you can change it in your production.rb +configuration. (Note that this far-future incineration scheduling relies on +your job queue being able to hold jobs for that long.) + +## Working with Action Mailbox in development + +It's helpful to be able to test incoming emails in development without actually +sending and receiving real emails. To accomplish this, there's a conductor +controller mounted at `/rails/conductor/action_mailbox/inbound_emails`, +which gives you an index of all the InboundEmails in the system, their +state of processing, and a form to create a new InboundEmail as well. + +## Testing mailboxes + +Example: + +```ruby +class ForwardsMailboxTest < ActionMailbox::TestCase + test "directly recording a client forward for a forwarder and forwardee corresponding to one project" do + assert_difference -> { people(:david).buckets.first.recordings.count } do + receive_inbound_email_from_mail \ + to: 'save@example.com', + from: people(:david).email_address, + subject: "Fwd: Status update?", + body: <<~BODY + --- Begin forwarded message --- + From: Frank Holland <frank@microsoft.com> + + What's the status? + BODY + end + + recording = people(:david).buckets.first.recordings.last + assert_equal people(:david), recording.creator + assert_equal "Status update?", recording.forward.subject + assert_match "What's the status?", recording.forward.content.to_s + end +end +``` diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md index 1acb993cad..f600cf29ce 100644 --- a/guides/source/action_mailer_basics.md +++ b/guides/source/action_mailer_basics.md @@ -3,13 +3,13 @@ Action Mailer Basics ==================== -This guide provides you with all you need to get started in sending and -receiving emails from and to your application, and many internals of Action +This guide provides you with all you need to get started in sending +emails from and to your application, and many internals of Action Mailer. It also covers how to test your mailers. After reading this guide, you will know: -* How to send and receive email within a Rails application. +* How to send email within a Rails application. * How to generate and edit an Action Mailer class and mailer view. * How to configure Action Mailer for your environment. * How to test your Action Mailer classes. @@ -168,7 +168,7 @@ view and sending it over the HTTP protocol, they are just sending it out through the email protocols instead. Due to this, it makes sense to just have your controller tell the Mailer to send an email when a user is successfully created. -Setting this up is painfully simple. +Setting this up is simple. First, let's create a simple `User` scaffold: @@ -427,7 +427,7 @@ If you would like to render a template located outside of the default `app/views ```ruby class UserMailer < ApplicationMailer prepend_view_path "custom/path/to/mailer/view" - + # This will try to load "custom/path/to/mailer/view/welcome_email" template def welcome_email # ... @@ -573,7 +573,7 @@ web addresses. Thus, you should always use the "_url" variant of named route helpers. If you did not configure the `:host` option globally make sure to pass it to the -url helper. +URL helper. ```erb <%= user_url(@user, host: 'example.com') %> @@ -651,48 +651,8 @@ class UserMailer < ApplicationMailer end ``` -Receiving Emails ----------------- - -Receiving and parsing emails with Action Mailer can be a rather complex -endeavor. Before your email reaches your Rails app, you would have had to -configure your system to somehow forward emails to your app, which needs to be -listening for that. So, to receive emails in your Rails app you'll need to: - -* Implement a `receive` method in your mailer. - -* Configure your email server to forward emails from the address(es) you would - like your app to receive to `/path/to/app/bin/rails runner - 'UserMailer.receive(STDIN.read)'`. - -Once a method called `receive` is defined in any mailer, Action Mailer will -parse the raw incoming email into an email object, decode it, instantiate a new -mailer, and pass the email object to the mailer `receive` instance -method. Here's an example: - -```ruby -class UserMailer < ApplicationMailer - def receive(email) - page = Page.find_by(address: email.to.first) - page.emails.create( - subject: email.subject, - body: email.body - ) - - if email.has_attachments? - email.attachments.each do |attachment| - page.attachments.create({ - file: attachment, - description: email.subject - }) - end - end - end -end -``` - Action Mailer Callbacks ---------------------------- +----------------------- Action Mailer allows for you to specify a `before_action`, `after_action` and `around_action`. @@ -786,7 +746,7 @@ files (environment.rb, production.rb, etc...) |`smtp_settings`|Allows detailed configuration for `:smtp` delivery method:<ul><li>`:address` - Allows you to use a remote mail server. Just change it from its default `"localhost"` setting.</li><li>`:port` - On the off chance that your mail server doesn't run on port 25, you can change it.</li><li>`:domain` - If you need to specify a HELO domain, you can do it here.</li><li>`:user_name` - If your mail server requires authentication, set the username in this setting.</li><li>`:password` - If your mail server requires authentication, set the password in this setting.</li><li>`:authentication` - If your mail server requires authentication, you need to specify the authentication type here. This is a symbol and one of `:plain` (will send the password in the clear), `:login` (will send password Base64 encoded) or `:cram_md5` (combines a Challenge/Response mechanism to exchange information and a cryptographic Message Digest 5 algorithm to hash important information)</li><li>`:enable_starttls_auto` - Detects if STARTTLS is enabled in your SMTP server and starts to use it. Defaults to `true`.</li><li>`:openssl_verify_mode` - When using TLS, you can set how OpenSSL checks the certificate. This is really useful if you need to validate a self-signed and/or a wildcard certificate. You can use the name of an OpenSSL verify constant ('none' or 'peer') or directly the constant (`OpenSSL::SSL::VERIFY_NONE` or `OpenSSL::SSL::VERIFY_PEER`).</li></ul>| |`sendmail_settings`|Allows you to override options for the `:sendmail` delivery method.<ul><li>`:location` - The location of the sendmail executable. Defaults to `/usr/sbin/sendmail`.</li><li>`:arguments` - The command line arguments to be passed to sendmail. Defaults to `-i`.</li></ul>| |`raise_delivery_errors`|Whether or not errors should be raised if the email fails to be delivered. This only works if the external email server is configured for immediate delivery.| -|`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](http://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.| +|`delivery_method`|Defines a delivery method. Possible values are:<ul><li>`:smtp` (default), can be configured by using `config.action_mailer.smtp_settings`.</li><li>`:sendmail`, can be configured by using `config.action_mailer.sendmail_settings`.</li><li>`:file`: save emails to files; can be configured by using `config.action_mailer.file_settings`.</li><li>`:test`: save emails to `ActionMailer::Base.deliveries` array.</li></ul>See [API docs](https://api.rubyonrails.org/classes/ActionMailer/Base.html) for more info.| |`perform_deliveries`|Determines whether deliveries are actually carried out when the `deliver` method is invoked on the Mail message. By default they are, but this can be turned off to help functional testing. If this value is `false`, `deliveries` array will not be populated even if `delivery_method` is `:test`.| |`deliveries`|Keeps an array of all the emails sent out through the Action Mailer with delivery_method :test. Most useful for unit and functional testing.| |`default_options`|Allows you to set default values for the `mail` method options (`:from`, `:reply_to`, etc.).| @@ -882,7 +842,7 @@ class EmailDeliveryObserver end end ``` -Like interceptors, you need to register observers with the Action Mailer framework. You can do this in an initializer file +Like interceptors, you need to register observers with the Action Mailer framework. You can do this in an initializer file `config/initializers/email_delivery_observer.rb` ```ruby diff --git a/guides/source/action_text_overview.md b/guides/source/action_text_overview.md new file mode 100644 index 0000000000..a735ec2b0e --- /dev/null +++ b/guides/source/action_text_overview.md @@ -0,0 +1,101 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.** + +Action Text Overview +==================== + +This guide provides you with all you need to get started in handling +rich text content. + +After reading this guide, you will know: + +* How to configure Action Text. +* How to handle rich text content. +* How to style rich text content. + +-------------------------------------------------------------------------------- + +Introduction +------------ + +Action Text brings rich text content and editing to Rails. It includes +the [Trix editor](https://trix-editor.org) that handles everything from formatting +to links to quotes to lists to embedded images and galleries. +The rich text content generated by the Trix editor is saved in its own +RichText model that's associated with any existing Active Record model in the application. +Any embedded images (or other attachments) are automatically stored using +Active Storage and associated with the included RichText model. + +## Trix compared to other rich text editors + +Most WYSIWYG editors are wrappers around HTML’s `contenteditable` and `execCommand` APIs, +designed by Microsoft to support live editing of web pages in Internet Explorer 5.5, +and [eventually reverse-engineered](https://blog.whatwg.org/the-road-to-html-5-contenteditable#history) +and copied by other browsers. + +Because these APIs were never fully specified or documented, +and because WYSIWYG HTML editors are enormous in scope, each +browser's implementation has its own set of bugs and quirks, +and JavaScript developers are left to resolve the inconsistencies. + +Trix sidesteps these inconsistencies by treating contenteditable +as an I/O device: when input makes its way to the editor, Trix converts that input +into an editing operation on its internal document model, then re-renders +that document back into the editor. This gives Trix complete control over what +happens after every keystroke, and avoids the need to use execCommand at all. + +## Installation + +Run `rails action_text:install` to add the Yarn package and copy over the necessary migration. +Also, you need to set up Active Storage for embedded images and other attachments. +Please refer to the [Active Storage Overview](active_storage_overview.html) guide. + +## Examples + +Adding a rich text field to an existing model: + +```ruby +# app/models/message.rb +class Message < ApplicationRecord + has_rich_text :content +end +``` + +Then refer to this field in the form for the model: + +```erb +<%# app/views/messages/_form.html.erb %> +<%= form_with(model: message) do |form| %> + <div class="field"> + <%= form.label :content %> + <%= form.rich_text_area :content %> + </div> +<% end %> +``` + +And finally display the sanitized rich text on a page: + +```erb +<%= @message.content %> +``` + +To accept the rich text content, all you have to do is permit the referenced attribute: + +```ruby +class MessagesController < ApplicationController + def create + message = Message.create! params.require(:message).permit(:title, :content) + redirect_to message + end +end +``` + +## Custom styling + +By default, the Action Text editor and content is styled by the Trix defaults. +If you want to change these defaults, you'll want to remove +the `app/assets/stylesheets/actiontext.scss` linker and base your stylings on +the [contents of that file](https://raw.githubusercontent.com/basecamp/trix/master/dist/trix.css). + +You can also style the HTML used for embedded images and other attachments (known as blobs). +On installation, Action Text will copy over a partial to +`app/views/active_storage/blobs/_blob.html.erb`, which you can specialize. diff --git a/guides/source/action_view_overview.md b/guides/source/action_view_overview.md index 495ae9d267..a1b69edd22 100644 --- a/guides/source/action_view_overview.md +++ b/guides/source/action_view_overview.md @@ -91,7 +91,7 @@ Here are some basic examples: ```ruby xml.em("emphasized") xml.em { xml.b("emph & bold") } -xml.a("A Link", "href" => "http://rubyonrails.org") +xml.a("A Link", "href" => "https://rubyonrails.org") xml.target("name" => "compile", "option" => "fast") ``` @@ -100,7 +100,7 @@ which would produce: ```html <em>emphasized</em> <em><b>emph & bold</b></em> -<a href="http://rubyonrails.org">A link</a> +<a href="https://rubyonrails.org">A link</a> <target option="fast" name="compile" /> ``` @@ -402,9 +402,9 @@ This will add `app/views/direct` to the end of the lookup paths. Overview of helpers provided by Action View ------------------------------------------- -WIP: Not all the helpers are listed here. For a full list see the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html) +WIP: Not all the helpers are listed here. For a full list see the [API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers.html) -The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the [API Documentation](http://api.rubyonrails.org/classes/ActionView/Helpers.html), which covers all of the helpers in more detail, but this should serve as a good starting point. +The following is only a brief overview summary of the helpers available in Action View. It's recommended that you review the [API Documentation](https://api.rubyonrails.org/classes/ActionView/Helpers.html), which covers all of the helpers in more detail, but this should serve as a good starting point. ### AssetTagHelper @@ -1446,7 +1446,7 @@ Sanitizes a block of CSS code. Strips all link tags from text leaving just the link text. ```ruby -strip_links('<a href="http://rubyonrails.org">Ruby on Rails</a>') +strip_links('<a href="https://rubyonrails.org">Ruby on Rails</a>') # => Ruby on Rails ``` diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 39239852ca..0925ad3d74 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -121,7 +121,7 @@ production apps will need to pick a persistent backend. Active Job has built-in adapters for multiple queuing backends (Sidekiq, Resque, Delayed Job, and others). To get an up-to-date list of the adapters -see the API Documentation for [ActiveJob::QueueAdapters](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). +see the API Documentation for [ActiveJob::QueueAdapters](https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). ### Setting the Backend @@ -165,6 +165,7 @@ Here is a noncomprehensive list of documentation: - [Sneakers](https://github.com/jondot/sneakers/wiki/How-To:-Rails-Background-Jobs-with-ActiveJob) - [Sucker Punch](https://github.com/brandonhilkert/sucker_punch#active-job) - [Queue Classic](https://github.com/QueueClassic/queue_classic#active-job) +- [Delayed Job](https://github.com/collectiveidea/delayed_job#active-job) Queues ------ @@ -454,7 +455,7 @@ class RemoteServiceJob < ApplicationJob end ``` -To get more details see the API Documentation for [ActiveJob::Exceptions](http://api.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html). +To get more details see the API Documentation for [ActiveJob::Exceptions](https://api.rubyonrails.org/classes/ActiveJob/Exceptions/ClassMethods.html). ### Deserialization diff --git a/guides/source/active_record_basics.md b/guides/source/active_record_basics.md index b9e24099b1..d765e32ac7 100644 --- a/guides/source/active_record_basics.md +++ b/guides/source/active_record_basics.md @@ -29,7 +29,7 @@ Object Relational Mapping system. ### The Active Record Pattern -[Active Record was described by Martin Fowler](http://www.martinfowler.com/eaaCatalog/activeRecord.html) +[Active Record was described by Martin Fowler](https://www.martinfowler.com/eaaCatalog/activeRecord.html) in his book _Patterns of Enterprise Application Architecture_. In Active Record, objects carry both persistent data and behavior which operates on that data. Active Record takes the opinion that ensuring @@ -45,7 +45,7 @@ relationships of the objects in an application can be easily stored and retrieved from a database without writing SQL statements directly and with less overall database access code. -NOTE: If you are not familiar enough with relational database management systems (RDBMS) or structured query language (SQL), please go through [this tutorial](https://www.w3schools.com/sql/default.asp) (or [this one](http://www.sqlcourse.com/)) or study them by other means. Understanding how relational databases work is crucial to understanding Active Records and Rails in general. +NOTE: Basic knowledge of relational database management systems (RDBMS) and structured query language (SQL) is helpful in order to fully understand Active Record. Please refer to [this tutorial](https://www.w3schools.com/sql/default.asp) (or [this one](http://www.sqlcourse.com/)) or study them by other means if you would like to learn more. ### Active Record as an ORM Framework @@ -105,9 +105,9 @@ depending on the purpose of these columns. fields that Active Record will look for when you create associations between your models. * **Primary keys** - By default, Active Record will use an integer column named - `id` as the table's primary key. When using [Active Record - Migrations](active_record_migrations.html) to create your tables, this column will be - automatically created. + `id` as the table's primary key (`bigint` for PostgreSQL and MySQL, `integer` + for SQLite). When using [Active Record Migrations](active_record_migrations.html) + to create your tables, this column will be automatically created. There are also some optional column names that will add additional features to Active Record instances: @@ -117,10 +117,10 @@ to Active Record instances: * `updated_at` - Automatically gets set to the current date and time whenever the record is created or updated. * `lock_version` - Adds [optimistic - locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to + locking](https://api.rubyonrails.org/classes/ActiveRecord/Locking.html) to a model. * `type` - Specifies that the model uses [Single Table - Inheritance](http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance). + Inheritance](https://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance). * `(association_name)_type` - Stores the type for [polymorphic associations](association_basics.html#polymorphic-associations). * `(table_name)_count` - Used to cache the number of belonging objects on @@ -202,6 +202,8 @@ class Product < ApplicationRecord end ``` +NOTE: Active Record does not support using non-primary key columns named `id`. + CRUD: Reading and Writing Data ------------------------------ @@ -307,12 +309,12 @@ user = User.find_by(name: 'David') user.destroy ``` -If you'd like to delete several records in bulk, you may use `destroy_all` -method: +If you'd like to delete several records in bulk, you may use `destroy_by` +or `destroy_all` method: ```ruby # find and delete all users named David -User.where(name: 'David').destroy_all +User.destroy_by(name: 'David') # delete all users User.destroy_all diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index ebdee446f9..617f81d37b 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -239,13 +239,12 @@ Skipping Callbacks Just as with validations, it is also possible to skip callbacks by using the following methods: -* `decrement` +* `decrement!` * `decrement_counter` * `delete` * `delete_all` -* `increment` +* `increment!` * `increment_counter` -* `toggle` * `update_column` * `update_columns` * `update_all` @@ -310,7 +309,7 @@ end ### Using `:if` and `:unless` with a `Proc` -Finally, it is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners: +It is possible to associate `:if` and `:unless` with a `Proc` object. This option is best suited when writing short validation methods, usually one-liners: ```ruby class Order < ApplicationRecord @@ -338,6 +337,20 @@ class Comment < ApplicationRecord end ``` +### Combining Callback Conditions + +When multiple conditions define whether or not a callback should happen, an `Array` can be used. Moreover, you can apply both `:if` and `:unless` to the same callback. + +```ruby +class Comment < ApplicationRecord + after_create :send_email_to_author, + if: [Proc.new { |c| c.user.allow_send_email? }, :author_wants_emails?], + unless: Proc.new { |c| c.article.ignore_comments? } +end +``` + +The callback only runs when all the `:if` conditions and none of the `:unless` conditions are evaluated to `true`. + Callback Classes ---------------- @@ -460,10 +473,25 @@ end => User was saved to database ``` -To register callbacks for both create and update actions, use `after_commit` instead. +There is also an alias for using the `after_commit` callback for both create and update together: + +* `after_save_commit` ```ruby class User < ApplicationRecord - after_commit :log_user_saved_to_db, on: [:create, :update] + after_save_commit :log_user_saved_to_db + + private + def log_user_saved_to_db + puts 'User was saved to database' + end end + +# creating a User +>> @user = User.create +=> User was saved to database + +# updating @user +>> @user.save +=> User was saved to database ``` diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md index 4d195988f8..9398244ccf 100644 --- a/guides/source/active_record_migrations.md +++ b/guides/source/active_record_migrations.md @@ -126,7 +126,7 @@ generator to handle making it for you: $ rails generate migration AddPartNumberToProducts ``` -This will create an empty but appropriately named migration: +This will create an appropriately named empty migration: ```ruby class AddPartNumberToProducts < ActiveRecord::Migration[5.0] @@ -135,9 +135,14 @@ class AddPartNumberToProducts < ActiveRecord::Migration[5.0] end ``` -If the migration name is of the form "AddXXXToYYY" or "RemoveXXXFromYYY" and is -followed by a list of column names and types then a migration containing the -appropriate `add_column` and `remove_column` statements will be created. +This generator can do much more than append a timestamp to the file name. +Based on naming conventions and additional (optional) arguments it can +also start fleshing out the migration. + +If the migration name is of the form "AddColumnToTable" or +"RemoveColumnFromTable" and is followed by a list of column names and +types then a migration containing the appropriate `add_column` and +`remove_column` statements will be created. ```bash $ rails generate migration AddPartNumberToProducts part_number:string @@ -220,6 +225,8 @@ class CreateProducts < ActiveRecord::Migration[5.0] create_table :products do |t| t.string :name t.string :part_number + + t.timestamps end end end @@ -247,7 +254,7 @@ end ``` This migration will create a `user_id` column and appropriate index. -For more `add_reference` options, visit the [API documentation](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference). +For more `add_reference` options, visit the [API documentation](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference). There is also a generator which will produce join tables if `JoinTable` is part of the name: @@ -460,7 +467,6 @@ number of digits after the decimal point. * `default` Allows to set a default value on the column. Note that if you are using a dynamic value (such as a date), the default will only be calculated the first time (i.e. on the date the migration is applied). -* `index` Adds an index for the column. * `comment` Adds a comment for the column. Some adapters may support additional options; see the adapter specific API docs @@ -479,7 +485,7 @@ add_foreign_key :articles, :authors This adds a new foreign key to the `author_id` column of the `articles` table. The key references the `id` column of the `authors` table. If the -column names can not be derived from the table names, you can use the +column names cannot be derived from the table names, you can use the `:column` and `:primary_key` options. Rails will generate a name for every foreign key starting with @@ -491,9 +497,6 @@ NOTE: Active Record only supports single column foreign keys. `execute` and `structure.sql` are required to use composite foreign keys. See [Schema Dumping and You](#schema-dumping-and-you). -NOTE: The SQLite3 adapter doesn't support `add_foreign_key` since SQLite supports -only [a limited subset of ALTER TABLE](https://www.sqlite.org/lang_altertable.html). - Removing a foreign key is easy as well: ```ruby @@ -518,12 +521,12 @@ Product.connection.execute("UPDATE products SET price = 'free' WHERE 1=1") For more details and examples of individual methods, check the API documentation. In particular the documentation for -[`ActiveRecord::ConnectionAdapters::SchemaStatements`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html) +[`ActiveRecord::ConnectionAdapters::SchemaStatements`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html) (which provides the methods available in the `change`, `up` and `down` methods), -[`ActiveRecord::ConnectionAdapters::TableDefinition`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html) +[`ActiveRecord::ConnectionAdapters::TableDefinition`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html) (which provides the methods available on the object yielded by `create_table`) and -[`ActiveRecord::ConnectionAdapters::Table`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html) +[`ActiveRecord::ConnectionAdapters::Table`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/Table.html) (which provides the methods available on the object yielded by `change_table`). ### Using the `change` Method @@ -946,7 +949,7 @@ If `:ruby` is selected, then the schema is stored in `db/schema.rb`. If you look at this file you'll find that it looks an awful lot like one very big migration: ```ruby -ActiveRecord::Schema.define(version: 20080906171750) do +ActiveRecord::Schema.define(version: 2008_09_06_171750) do create_table "authors", force: true do |t| t.string "name" t.datetime "created_at" diff --git a/guides/source/active_record_multiple_databases.md b/guides/source/active_record_multiple_databases.md new file mode 100644 index 0000000000..07be21a254 --- /dev/null +++ b/guides/source/active_record_multiple_databases.md @@ -0,0 +1,288 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.** + +Multiple Databases with Active Record +===================================== + +This guide covers using multiple databases with your Rails application. + +After reading this guide you will know: + +* How to setup your application for multiple databases. +* How automatic connection switching works. +* What features are supported and what's still a work in progress. + +-------------------------------------------------------------------------------- + +As an application grows in popularity and usage you'll need to scale the application +to support your new users and their data. One way in which your application may need +to scale is on the database level. Rails now has support for multiple databases +so you don't have to store your data all in one place. + +At this time the following features are supported: + +* Multiple primary databases and a replica for each +* Automatic connection switching for the model you're working with +* Automatic swapping between the primary and replica depending on the HTTP verb +and recent writes +* Rails tasks for creating, dropping, migrating, and interacting with the multiple +databases + +The following features are not (yet) supported: + +* Sharding +* Joining across clusters +* Load balancing replicas +* Dumping schema caches for multiple databases + +## Setting up your application + +While Rails tries to do most of the work for you there are still some steps you'll +need to do to get your application ready for multiple databases. + +Let's say we have an application with a single primary database and we need to add a +new database for some new tables we're adding. The name of the new database will be +"animals". + +The database.yml looks like this: + +```yaml +production: + database: my_primary_database + user: root + adapter: mysql +``` + +Let's add a replica for the primary, a new writer called animals and a replica for that +as well. To do this we need to change our database.yml from a 2-tier to a 3-tier config. + +```yaml +production: + primary: + database: my_primary_database + user: root + adapter: mysql + primary_replica: + database: my_primary_database + user: root_readonly + adapter: mysql + replica: true + animals: + database: my_animals_database + user: animals_root + adapter: mysql + migrations_paths: db/animals_migrate + animals_replica: + database: my_animals_database + user: animals_readonly + adapter: mysql + replica: true +``` + +When using multiple databases there are a few important settings. + +First, the database name for the primary and replica should be the same because they contain +the same data. Second, the username for the primary and replica should be different, and the +replica user's permissions should be to read and not write. + +When using a replica database you need to add a `replica: true` entry to the replica in the +`database.yml`. This is because Rails otherwise has no way of knowing which one is a replica +and which one is the primary. + +Lastly, for new primary databases you need to set the `migrations_paths` to the directory +where you will store migrations for that database. We'll look more at `migrations_paths` +later on in this guide. + +Now that we have a new database, let's set up the model. In order to use the new database we +need to create a new abstract class and connect to the animals databases. + +```ruby +class AnimalsBase < ApplicationRecord + self.abstract_class = true + + connects_to database: { writing: :animals, reading: :animals_replica } +end +``` + Then we need to +update `ApplicationRecord` to be aware of our new replica. + +```ruby +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true + + connects_to database: { writing: :primary, reading: :primary_replica } +end +``` + +By default Rails expects the database roles to be `writing` and `reading` for the primary +and replica respectively. If you have a legacy system you may already have roles set up that +you don't want to change. In that case you can set a new role name in your application config. + +```ruby +config.active_record.writing_role = :default +config.active_record.reading_role = :readonly +``` + +It's important to connect to your database in a single model and then inherit from that model +for the tables rather than connect multiple individual models to the same database. Database +clients have a limit to the number of open connections there can be and if you do this it will +multiply the number of connections you have since Rails uses the model class name for the +connection specification name. + +Now that we have the database.yml and the new model set up it's time to create the databases. +Rails 6.0 ships with all the rails tasks you need to use multiple databases in Rails. + +You can run `rails -T` to see all the commands you're able to run. You should see the following: + +``` +$ rails -T +rails db:create # Creates the database from DATABASE_URL or config/database.yml for the ... +rails db:create:animals # Create animals database for current environment +rails db:create:primary # Create primary database for current environment +rails db:drop # Drops the database from DATABASE_URL or config/database.yml for the cu... +rails db:drop:animals # Drop animals database for current environment +rails db:drop:primary # Drop primary database for current environment +rails db:migrate # Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog) +rails db:migrate:animals # Migrate animals database for current environment +rails db:migrate:primary # Migrate primary database for current environment +rails db:migrate:status # Display status of migrations +rails db:migrate:status:animals # Display status of migrations for animals database +rails db:migrate:status:primary # Display status of migrations for primary database +``` + +Running a command like `rails db:create` will create both the primary and animals databases. +Note that there is no command for creating the users and you'll need to do that manually +to support the readonly users for your replicas. If you want to create just the animals +database you can run `rails db:create:animals`. + +## Migrations + +Migrations for multiple databases should live in their own folders prefixed with the +name of the database key in the configuration. + +You also need to set the `migrations_paths` in the database configurations to tell Rails +where to find the migrations. + +For example the `animals` database would look in the `db/animals_migrate` directory and +`primary` would look in `db/migrate`. Rails generators now take a `--database` option +so that the file is generated in the correct directory. The command can be run like so: + +``` +$ rails g migration CreateDogs name:string --database animals +``` + +## Activating automatic connection switching + +Finally, in order to use the read-only replica in your application you'll need to activate +the middleware for automatic switching. + +Automatic switching allows the application to switch from the primary to replica or replica +to primary based on the HTTP verb and whether there was a recent write. + +If the application is receiving a POST, PUT, DELETE, or PATCH request the application will +automatically write to the primary. For the specified time after the write the application +will read from the primary. For a GET or HEAD request the application will read from the +replica unless there was a recent write. + +To activate the automatic connection switching middleware, add or uncomment the following +lines in your application config. + +```ruby +config.active_record.database_selector = { delay: 2.seconds } +config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver +config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session +``` + +Rails guarantees "read your own write" and will send your GET or HEAD request to the +primary if it's within the `delay` window. By default the delay is set to 2 seconds. You +should change this based on your database infrastructure. Rails doesn't guarantee "read +a recent write" for other users within the delay window and will send GET and HEAD requests +to the replicas unless they wrote recently. + +The automatic connection switching in Rails is relatively primitive and deliberately doesn't +do a whole lot. The goal was a system that demonstrated how to do automatic connection +switching that was flexible enough to be customizable by app developers. + +The setup in Rails allows you to easily change how the switching is done and what +parameters it's based on. Let's say you want to use a cookie instead of a session to +decide when to swap connections. You can write your own class: + +```ruby +class MyCookieResolver + # code for your cookie class +end +``` + +And then pass it to the middleware: + +```ruby +config.active_record.database_selector = { delay: 2.seconds } +config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver +config.active_record.database_resolver_context = MyCookieResolver +``` + +## Using manual connection switching + +There are some cases where you may want your application to connect to a primary or a replica +and the automatic connection switching isn't adequate. For example, you may know that for a +particular request you always want to send the request to a replica, even when you are in a +POST request path. + +To do this Rails provides a `connected_to` method that will switch to the connection you +need. + +```ruby +ActiveRecord::Base.connected_to(role: :reading) do + # all code in this block will be connected to the reading role +end +``` + +The "role" in the `connected_to` call looks up the connections that are connected on that +connection handler (or role). The `reading` connection handler will hold all the connections +that were connected via `connects_to` with the role name of `reading`. + +There also may be a case where you have a database that you don't always want to connect to +on application boot but may need for a slow query or analytics. After defining that database +in the database.yml you can connect by passing a database argument to `connected_to` + +```ruby +ActiveRecord::Base.connected_to(database: { reading_slow: :animals_slow_replica }) do + # do something while connected to the slow replica +end +``` + +The `database` argument for `connected_to` will take a symbol or a config hash. + +Note that `connected_to` with a role will look up an existing connection and switch +using the connection specification name. This means that if you pass an unknown role +like `connected_to(role: :nonexistent)` you will get an error that says +`ActiveRecord::ConnectionNotEstablished (No connection pool with 'AnimalsBase' found +for the 'nonexistent' role.)` + +## Caveats + +### Sharding + +As noted at the top, Rails doesn't (yet) support sharding. We had to do a lot of work +to support multiple databases for Rails 6.0. The lack of support for sharding isn't +an oversight, but does require additional work that didn't make it in for 6.0. For now +if you need sharding it may be advisable to continue using one of the many gems +that supports this. + +### Load Balancing Replicas + +Rails also doesn't support automatic load balancing of replicas. This is very +dependent on your infrastructure. We may implement basic, primitive load balancing +in the future, but for an application at scale this should be something your application +handles outside of Rails. + +### Joining Across Databases + +Applications cannot join across databases. Rails 6.1 will support using `has_many` +relationships and creating 2 queries instead of joining, but Rails 6.0 will require +you to split the joins into 2 selects manually. + +### Schema Cache + +If you use a schema cache and multiple databases you'll need to write an initializer +that loads the schema cache from your app. This wasn't an issue we could resolve in +time for Rails 6.0 but hope to have it in a future version soon. diff --git a/guides/source/active_record_postgresql.md b/guides/source/active_record_postgresql.md index 16c1567c69..12115d01ef 100644 --- a/guides/source/active_record_postgresql.md +++ b/guides/source/active_record_postgresql.md @@ -14,7 +14,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -In order to use the PostgreSQL adapter you need to have at least version 9.1 +In order to use the PostgreSQL adapter you need to have at least version 9.3 installed. Older versions are not supported. To get started with PostgreSQL have a look at the @@ -150,7 +150,7 @@ Event.where("payload->>'kind' = ?", "user_renamed") * [type definition](https://www.postgresql.org/docs/current/static/rangetypes.html) * [functions and operators](https://www.postgresql.org/docs/current/static/functions-range.html) -This type is mapped to Ruby [`Range`](http://www.ruby-doc.org/core-2.2.2/Range.html) objects. +This type is mapped to Ruby [`Range`](https://ruby-doc.org/core-2.2.2/Range.html) objects. ```ruby # db/migrate/20130923065404_create_events.rb @@ -367,7 +367,7 @@ user.save! * [type definition](https://www.postgresql.org/docs/current/static/datatype-net-types.html) The types `inet` and `cidr` are mapped to Ruby -[`IPAddr`](http://www.ruby-doc.org/stdlib-2.2.2/libdoc/ipaddr/rdoc/IPAddr.html) +[`IPAddr`](https://ruby-doc.org/stdlib-2.2.2/libdoc/ipaddr/rdoc/IPAddr.html) objects. The `macaddr` type is mapped to normal text. ```ruby diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 02055e59f0..5fb030fad4 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -59,11 +59,13 @@ To retrieve objects from the database, Active Record provides several finder met The methods are: +* `annotate` * `find` * `create_with` * `distinct` * `eager_load` * `extending` +* `extract_associated` * `from` * `group` * `having` @@ -74,11 +76,13 @@ The methods are: * `lock` * `none` * `offset` +* `optimizer_hints` * `order` * `preload` * `readonly` * `references` * `reorder` +* `reselect` * `reverse_order` * `select` * `where` @@ -611,7 +615,8 @@ If you want to call `order` multiple times, subsequent orders will be appended t Client.order("orders_count ASC").order("created_at DESC") # SELECT * FROM clients ORDER BY orders_count ASC, created_at DESC ``` -WARNING: If you are using **MySQL 5.7.5** and above, then on selecting fields from a result set using methods like `select`, `pluck` and `ids`; the `order` method will raise an `ActiveRecord::StatementInvalid` exception unless the field(s) used in `order` clause are included in the select list. See the next section for selecting fields from the result set. + +WARNING: In most database systems, on selecting fields with `distinct` from a result set using methods like `select`, `pluck` and `ids`; the `order` method will raise an `ActiveRecord::StatementInvalid` exception unless the field(s) used in `order` clause are included in the select list. See the next section for selecting fields from the result set. Selecting Specific Fields ------------------------- @@ -623,6 +628,8 @@ To select only a subset of fields from the result set, you can specify the subse For example, to select only `viewable_by` and `locked` columns: ```ruby +Client.select(:viewable_by, :locked) +# OR Client.select("viewable_by, locked") ``` @@ -805,6 +812,32 @@ SELECT * FROM articles WHERE id > 10 ORDER BY id DESC LIMIT 20 ``` +### `reselect` + +The `reselect` method overrides an existing select statement. For example: + +```ruby +Post.select(:title, :body).reselect(:created_at) +``` + +The SQL that would be executed: + +```sql +SELECT `posts`.`created_at` FROM `posts` +``` + +In case the `reselect` clause is not used, + +```ruby +Post.select(:title, :body).select(:created_at) +``` + +the SQL executed would be: + +```sql +SELECT `posts`.`title`, `posts`.`body`, `posts`.`created_at` FROM `posts` +``` + ### `reorder` The `reorder` method overrides the default scope order. For example: @@ -1267,7 +1300,7 @@ This is because it is ambiguous whether they should appear on the parent record, Scopes ------ -Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as `where`, `joins` and `includes`. All scope methods will return an `ActiveRecord::Relation` object which will allow for further methods (such as other scopes) to be called on it. +Scoping allows you to specify commonly-used queries which can be referenced as method calls on the association objects or models. With these scopes, you can use every method previously covered such as `where`, `joins` and `includes`. All scope bodies should return an `ActiveRecord::Relation` or `nil` to allow for further methods (such as other scopes) to be called on it. To define a simple scope, we use the `scope` method inside the class, passing the query that we'd like to run when this scope is called: @@ -1524,7 +1557,7 @@ book.available? # => false ``` Read the full documentation about enums -[in the Rails API docs](http://api.rubyonrails.org/classes/ActiveRecord/Enum.html). +[in the Rails API docs](https://api.rubyonrails.org/classes/ActiveRecord/Enum.html). Understanding The Method Chaining --------------------------------- @@ -1702,10 +1735,13 @@ Client.find_by_sql("SELECT * FROM clients ### `select_all` -`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. This method will return an instance of `ActiveRecord::Result` class and calling `to_hash` on this object would return you an array of hashes where each hash indicates a record. +`find_by_sql` has a close relative called `connection#select_all`. `select_all` will retrieve +objects from the database using custom SQL just like `find_by_sql` but will not instantiate them. +This method will return an instance of `ActiveRecord::Result` class and calling `to_a` on this +object would return you an array of hashes where each hash indicates a record. ```ruby -Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'").to_hash +Client.connection.select_all("SELECT first_name, created_at FROM clients WHERE id = '1'").to_a # => [ # {"first_name"=>"Rafael", "created_at"=>"2012-11-10 23:23:45.281189"}, # {"first_name"=>"Eileen", "created_at"=>"2013-12-09 11:22:35.221282"} @@ -1785,6 +1821,21 @@ Client.limit(1).pluck(:name) # => ["David"] ``` +NOTE: You should also know that using `pluck` will trigger eager loading if the relation object contains include values, even if the eager loading is not necessary for the query. For example: + +```ruby +# store association for reusing it +assoc = Company.includes(:account) +assoc.pluck(:id) +# SELECT "companies"."id" FROM "companies" LEFT OUTER JOIN "accounts" ON "accounts"."id" = "companies"."account_id" +``` + +One way to avoid this is to `unscope` the includes: + +```ruby +assoc.unscope(:includes).pluck(:id) +``` + ### `ids` `ids` can be used to pluck all the IDs for the relation using the table's primary key. @@ -2035,9 +2086,9 @@ under MySQL and MariaDB. Interpretation of the output of EXPLAIN is beyond the scope of this guide. The following pointers may be helpful: -* SQLite3: [EXPLAIN QUERY PLAN](http://www.sqlite.org/eqp.html) +* SQLite3: [EXPLAIN QUERY PLAN](https://www.sqlite.org/eqp.html) -* MySQL: [EXPLAIN Output Format](http://dev.mysql.com/doc/refman/5.7/en/explain-output.html) +* MySQL: [EXPLAIN Output Format](https://dev.mysql.com/doc/refman/5.7/en/explain-output.html) * MariaDB: [EXPLAIN](https://mariadb.com/kb/en/mariadb/explain/) diff --git a/guides/source/active_record_validations.md b/guides/source/active_record_validations.md index 0fda7c5cfd..f904d4de65 100644 --- a/guides/source/active_record_validations.md +++ b/guides/source/active_record_validations.md @@ -639,7 +639,7 @@ class Holiday < ApplicationRecord message: "should happen once per year" } end ``` -Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](http://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](https://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns. +Should you wish to create a database constraint to prevent possible violations of a uniqueness validation using the `:scope` option, you must create a unique index on both columns in your database. See [the MySQL manual](https://dev.mysql.com/doc/refman/5.7/en/multiple-column-indexes.html) for more details about multiple column indexes or [the PostgreSQL manual](https://www.postgresql.org/docs/current/static/ddl-constraints.html) for examples of unique constraints that refer to a group of columns. There is also a `:case_sensitive` option that you can use to define whether the uniqueness constraint will be case sensitive or not. This option defaults to @@ -664,7 +664,7 @@ This helper passes the record to a separate class for validation. class GoodnessValidator < ActiveModel::Validator def validate(record) if record.first_name == "Evil" - record.errors[:base] << "This person is evil" + record.errors.add :base, "This person is evil" end end end @@ -692,7 +692,7 @@ validator class as `options`: class GoodnessValidator < ActiveModel::Validator def validate(record) if options[:fields].any?{|field| record.send(field) == "Evil" } - record.errors[:base] << "This person is evil" + record.errors.add :base, "This person is evil" end end end @@ -723,7 +723,7 @@ class GoodnessValidator def validate if some_complex_condition_involving_ivars_and_private_methods? - @person.errors[:base] << "This person is evil" + @person.errors.add :base, "This person is evil" end end @@ -934,7 +934,7 @@ end ### Using a Proc with `:if` and `:unless` -Finally, it's possible to associate `:if` and `:unless` with a `Proc` object +It is possible to associate `:if` and `:unless` with a `Proc` object which will be called. Using a `Proc` object gives you the ability to write an inline condition instead of a separate method. This option is best suited for one-liners. @@ -1004,7 +1004,7 @@ and performs the validation on it. The custom validator is called using the class MyValidator < ActiveModel::Validator def validate(record) unless record.name.starts_with? 'X' - record.errors[:name] << 'Need a name starting with X please!' + record.errors.add :name, "Need a name starting with X please!" end end end @@ -1026,7 +1026,7 @@ instance. class EmailValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i - record.errors[attribute] << (options[:message] || "is not an email") + record.errors.add attribute, (options[:message] || "is not an email") end end end @@ -1044,7 +1044,7 @@ own custom validators. You can also create methods that verify the state of your models and add messages to the `errors` collection when they are invalid. You must then register these methods by using the `validate` -([API](http://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validate)) +([API](https://api.rubyonrails.org/classes/ActiveModel/Validations/ClassMethods.html#method-i-validate)) class method, passing in the symbols for the validation methods' names. You can pass more than one symbol for each class method and the respective @@ -1203,7 +1203,7 @@ You can add error messages that are related to the object's state as a whole, in ```ruby class Person < ApplicationRecord def a_method_used_for_validation_purposes - errors[:base] << "This person is invalid because ..." + errors.add :base, "This person is invalid because ..." end end ``` diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md index d5387219f5..932a5dc2e9 100644 --- a/guides/source/active_storage_overview.md +++ b/guides/source/active_storage_overview.md @@ -41,6 +41,8 @@ application (or upgrading your application to Rails 5.2), run `rails active_storage:install` to generate a migration that creates these tables. Use `rails db:migrate` to run the migration. +WARNING: `active_storage_attachments` is a polymorphic join table that stores your model's class name. If your model's class name changes, you will need to run a migration on this table to update the underlying `record_type` to your model's new class name. + Declare Active Storage services in `config/storage.yml`. For each service your application uses, provide a name and the requisite configuration. The example below declares three services named `local`, `test`, and `amazon`: @@ -189,14 +191,21 @@ gem "google-cloud-storage", "~> 1.11", require: false ### Mirror Service -You can keep multiple services in sync by defining a mirror service. When a file -is uploaded or deleted, it's done across all the mirrored services. Mirrored -services can be used to facilitate a migration between services in production. -You can start mirroring to the new service, copy existing files from the old -service to the new, then go all-in on the new service. Define each of the -services you'd like to use as described above and reference them from a mirrored +You can keep multiple services in sync by defining a mirror service. A mirror +service replicates uploads and deletes across two or more subordinate services. + +A mirror service is intended to be used temporarily during a migration between +services in production. You can start mirroring to a new service, copy +pre-existing files from the old service to the new, then go all-in on the new service. +NOTE: Mirroring is not atomic. It is possible for an upload to succeed on the +primary service and fail on any of the subordinate services. Before going +all-in on a new service, verify that all files have been copied. + +Define each of the services you'd like to mirror as described above. Reference +them by name when defining a mirror service: + ```yaml s3_west_coast: service: S3 @@ -219,9 +228,12 @@ production: - s3_west_coast ``` -NOTE: Files are served from the primary service. +Although all secondary services receive uploads, downloads are always handled +by the primary service. -NOTE: This is not compatible with the [direct uploads](#direct-uploads) feature. +Mirror services are compatible with direct uploads. New files are directly +uploaded to the primary service. When a directly-uploaded file is attached to a +record, a background job is enqueued to copy it to the secondary services. Attaching Files to Records -------------------------- @@ -421,7 +433,7 @@ Transforming Images To create a variation of the image, call `variant` on the `Blob`. You can pass any transformation to the method supported by the processor. The default processor is [MiniMagick](https://github.com/minimagick/minimagick), but you -can also use [Vips](http://www.rubydoc.info/gems/ruby-vips/Vips/Image). +can also use [Vips](https://www.rubydoc.info/gems/ruby-vips/Vips/Image). To enable variants, add the `image_processing` gem to your `Gemfile`: @@ -434,7 +446,7 @@ original blob into the specified format and redirect to its new service location. ```erb -<%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %> +<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %> ``` To switch to the Vips processor, you would add the following to @@ -489,8 +501,7 @@ directly from the client to the cloud. Using the npm package: ```js - import * as ActiveStorage from "activestorage" - ActiveStorage.start() + require("@rails/activestorage").start() ``` 2. Annotate file inputs with the direct upload URL. @@ -616,7 +627,7 @@ of choice, instantiate a DirectUpload and call its create method. Create takes a callback to invoke when the upload completes. ```js -import { DirectUpload } from "activestorage" +import { DirectUpload } from "@rails/activestorage" const input = document.querySelector('input[type=file]') @@ -635,7 +646,7 @@ input.addEventListener('change', (event) => { input.value = null }) -const uploadFile = (file) { +const uploadFile = (file) => { // your form needs the file_field direct_upload: true, which // provides data-direct-upload-url const url = input.dataset.directUploadUrl @@ -664,7 +675,7 @@ will call the object's `directUploadWillStoreFileWithXHR` method. You can then bind your own progress handler on the XHR. ```js -import { DirectUpload } from "activestorage" +import { DirectUpload } from "@rails/activestorage" class Uploader { constructor(file, url) { @@ -742,16 +753,22 @@ during the test are complete and you won't receive an error from Active Storage saying it can't find a file. ```ruby +module RemoveUploadedFiles + def after_teardown + super + remove_uploaded_files + end + + private + + def remove_uploaded_files + FileUtils.rm_rf(Rails.root.join('tmp', 'storage')) + end +end + module ActionDispatch class IntegrationTest - def remove_uploaded_files - FileUtils.rm_rf(Rails.root.join('tmp', 'storage')) - end - - def after_teardown - super - remove_uploaded_files - end + prepend RemoveUploadedFiles end end ``` diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 6b0554bb5f..f36cacfe8d 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -155,15 +155,6 @@ Complex(1).duplicable? # => true 1.method(:+).duplicable? # => false ``` -`duplicable?` matches the current Ruby version's `dup` behavior, -so results will vary according the version of Ruby you're using. -In Ruby 2.4, for example, Complex and Rational are not duplicable: - -```ruby -Rational(1).duplicable? # => false -Complex(1).duplicable? # => false -``` - WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case. NOTE: Defined in `active_support/core_ext/object/duplicable.rb`. @@ -1201,6 +1192,8 @@ The `inquiry` method converts a string into a `StringInquirer` object making equ "active".inquiry.inactive? # => false ``` +NOTE: Defined in `active_support/core_ext/string/inquiry.rb`. + ### `starts_with?` and `ends_with?` Active Support defines 3rd person aliases of `String#start_with?` and `String#end_with?`: @@ -1339,7 +1332,7 @@ The method `pluralize` returns the plural of its receiver: "equipment".pluralize # => "equipment" ``` -As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Built-in rules can be extended in `config/initializers/inflections.rb`. That file is generated by the `rails` command and has instructions in comments. +As the previous example shows, Active Support knows some irregular plurals and uncountable nouns. Built-in rules can be extended in `config/initializers/inflections.rb`. This file is generated by default, by the `rails new` command and has instructions in comments. `pluralize` can also take an optional `count` parameter. If `count == 1` the singular form will be returned. For any other value of `count` the plural form will be returned: @@ -1999,7 +1992,7 @@ Addition only assumes the elements respond to `+`: ```ruby [[1, 2], [2, 3], [3, 4]].sum # => [1, 2, 2, 3, 3, 4] %w(foo bar baz).sum # => "foobarbaz" -{a: 1, b: 2, c: 3}.sum # => [:b, 2, :c, 3, :a, 1] +{a: 1, b: 2, c: 3}.sum # => [:a, 1, :b, 2, :c, 3] ``` The sum of an empty collection is zero by default, but this is customizable: @@ -2132,30 +2125,6 @@ The methods `second`, `third`, `fourth`, and `fifth` return the corresponding el NOTE: Defined in `active_support/core_ext/array/access.rb`. -### Adding Elements - -#### `prepend` - -This method is an alias of `Array#unshift`. - -```ruby -%w(a b c d).prepend('e') # => ["e", "a", "b", "c", "d"] -[].prepend(10) # => [10] -``` - -NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`. - -#### `append` - -This method is an alias of `Array#<<`. - -```ruby -%w(a b c d).append('e') # => ["a", "b", "c", "d", "e"] -[].append([1,2]) # => [[1, 2]] -``` - -NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`. - ### Extracting The method `extract!` removes and returns the elements for which the block returns a true value. @@ -2380,10 +2349,6 @@ There's also a related idiom that uses the splat operator: [*object] ``` -which in Ruby 1.8 returns `[nil]` for `nil`, and calls to `Array(object)` otherwise. (Please if you know the exact behavior in 1.9 contact fxn.) - -Thus, in this case the behavior is different for `nil`, and the differences with `Kernel#Array` explained above apply to the rest of `object`s. - NOTE: Defined in `active_support/core_ext/array/wrap.rb`. ### Duplicating @@ -2646,48 +2611,6 @@ There's also the bang variant `except!` that removes keys in the very receiver. NOTE: Defined in `active_support/core_ext/hash/except.rb`. -#### `transform_keys` and `transform_keys!` - -The method `transform_keys` accepts a block and returns a hash that has applied the block operations to each of the keys in the receiver: - -```ruby -{nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase } -# => {"" => nil, "1" => 1, "A" => :a} -``` - -In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: - -```ruby -{"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase } -# The result could either be -# => {"A"=>2} -# or -# => {"A"=>1} -``` - -This method may be useful for example to build specialized conversions. For instance `stringify_keys` and `symbolize_keys` use `transform_keys` to perform their key conversions: - -```ruby -def stringify_keys - transform_keys { |key| key.to_s } -end -... -def symbolize_keys - transform_keys { |key| key.to_sym rescue key } -end -``` - -There's also the bang variant `transform_keys!` that applies the block operations to keys in the very receiver. - -Besides that, one can use `deep_transform_keys` and `deep_transform_keys!` to perform the block operation on all the keys in the given hash and all the hashes nested into it. An example of the result is: - -```ruby -{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys { |key| key.to_s.upcase } -# => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}} -``` - -NOTE: Defined in `active_support/core_ext/hash/keys.rb`. - #### `stringify_keys` and `stringify_keys!` The method `stringify_keys` returns a hash that has a stringified version of the keys in the receiver. It does so by sending `to_s` to them: @@ -2697,14 +2620,12 @@ The method `stringify_keys` returns a hash that has a stringified version of the # => {"" => nil, "1" => 1, "a" => :a} ``` -In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: +In case of key collision, the value will be the one most recently inserted into the hash: ```ruby {"a" => 1, a: 2}.stringify_keys -# The result could either be +# The result will be # => {"a"=>2} -# or -# => {"a"=>1} ``` This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionView::Helpers::FormHelper` defines: @@ -2741,14 +2662,12 @@ The method `symbolize_keys` returns a hash that has a symbolized version of the WARNING. Note in the previous example only one key was symbolized. -In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash: +In case of key collision, the value will be the one most recently inserted into the hash: ```ruby {"a" => 1, a: 2}.symbolize_keys -# The result could either be +# The result will be # => {:a=>2} -# or -# => {:a=>1} ``` This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionController::UrlRewriter` defines @@ -2795,26 +2714,7 @@ NOTE: Defined in `active_support/core_ext/hash/keys.rb`. ### Slicing -Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes: - -```ruby -{a: 1, b: 2, c: 3}.slice(:a, :c) -# => {:a=>1, :c=>3} - -{a: 1, b: 2, c: 3}.slice(:b, :X) -# => {:b=>2} # non-existing keys are ignored -``` - -If the receiver responds to `convert_key` keys are normalized: - -```ruby -{a: 1, b: 2}.with_indifferent_access.slice("a") -# => {:a=>1} -``` - -NOTE. Slicing may come in handy for sanitizing option hashes with a white list of keys. - -There's also `slice!` which in addition to perform a slice in place returns what's removed: +The method `slice!` replaces the hash with only the given keys and returns a hash containing the removed key/value pairs. ```ruby hash = {a: 1, b: 2} @@ -2872,10 +2772,10 @@ Regexp.new('.', Regexp::MULTILINE).multiline? # => true Rails uses this method in a single place, also in the routing code. Multiline regexps are disallowed for route requirements and this flag eases enforcing that constraint. ```ruby -def assign_route_options(segments, defaults, requirements) +def verify_regexp_requirements(requirements) ... if requirement.multiline? - raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}" + raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}" end ... end @@ -3503,56 +3403,56 @@ NOTE: Defined in `active_support/core_ext/date_and_time/calculations.rb`. #### `prev_day`, `next_day` -In Ruby 1.9 `prev_day` and `next_day` return the date in the last or next day: +`prev_day` and `next_day` return the time in the last or next day: ```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.prev_day # => Fri, 07 May 2010 -d.next_day # => Sun, 09 May 2010 +t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900 +t.prev_day # => 2010-05-07 00:00:00 +0900 +t.next_day # => 2010-05-09 00:00:00 +0900 ``` -NOTE: Defined in `active_support/core_ext/date_and_time/calculations.rb`. +NOTE: Defined in `active_support/core_ext/time/calculations.rb`. #### `prev_month`, `next_month` -In Ruby 1.9 `prev_month` and `next_month` return the date with the same day in the last or next month: +`prev_month` and `next_month` return the time with the same day in the last or next month: ```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.prev_month # => Thu, 08 Apr 2010 -d.next_month # => Tue, 08 Jun 2010 +t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900 +t.prev_month # => 2010-04-08 00:00:00 +0900 +t.next_month # => 2010-06-08 00:00:00 +0900 ``` If such a day does not exist, the last day of the corresponding month is returned: ```ruby -Date.new(2000, 5, 31).prev_month # => Sun, 30 Apr 2000 -Date.new(2000, 3, 31).prev_month # => Tue, 29 Feb 2000 -Date.new(2000, 5, 31).next_month # => Fri, 30 Jun 2000 -Date.new(2000, 1, 31).next_month # => Tue, 29 Feb 2000 +Time.new(2000, 5, 31).prev_month # => 2000-04-30 00:00:00 +0900 +Time.new(2000, 3, 31).prev_month # => 2000-02-29 00:00:00 +0900 +Time.new(2000, 5, 31).next_month # => 2000-06-30 00:00:00 +0900 +Time.new(2000, 1, 31).next_month # => 2000-02-29 00:00:00 +0900 ``` -NOTE: Defined in `active_support/core_ext/date_and_time/calculations.rb`. +NOTE: Defined in `active_support/core_ext/time/calculations.rb`. #### `prev_year`, `next_year` -In Ruby 1.9 `prev_year` and `next_year` return a date with the same day/month in the last or next year: +`prev_year` and `next_year` return a time with the same day/month in the last or next year: ```ruby -d = Date.new(2010, 5, 8) # => Sat, 08 May 2010 -d.prev_year # => Fri, 08 May 2009 -d.next_year # => Sun, 08 May 2011 +t = Time.new(2010, 5, 8) # => 2010-05-08 00:00:00 +0900 +t.prev_year # => 2009-05-08 00:00:00 +0900 +t.next_year # => 2011-05-08 00:00:00 +0900 ``` If date is the 29th of February of a leap year, you obtain the 28th: ```ruby -d = Date.new(2000, 2, 29) # => Tue, 29 Feb 2000 -d.prev_year # => Sun, 28 Feb 1999 -d.next_year # => Wed, 28 Feb 2001 +t = Time.new(2000, 2, 29) # => 2000-02-29 00:00:00 +0900 +t.prev_year # => 1999-02-28 00:00:00 +0900 +t.next_year # => 2001-02-28 00:00:00 +0900 ``` -NOTE: Defined in `active_support/core_ext/date_and_time/calculations.rb`. +NOTE: Defined in `active_support/core_ext/time/calculations.rb`. #### `prev_quarter`, `next_quarter` diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 64db141381..9f15e70da6 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -203,6 +203,15 @@ INFO. Additional keys may be added by the caller. | ------- | ---------------- | | `:keys` | Unpermitted keys | +Action Dispatch +--------------- + +### process_middleware.action_dispatch + +| Key | Value | +| ------------- | ---------------------- | +| `:middleware` | Name of the middleware | + Action View ----------- @@ -255,13 +264,16 @@ Active Record ### sql.active_record -| Key | Value | -| ---------------- | ---------------------------------------- | -| `:sql` | SQL statement | -| `:name` | Name of the operation | -| `:connection_id` | `self.object_id` | -| `:binds` | Bind parameters | -| `:cached` | `true` is added when cached queries used | +| Key | Value | +| -------------------- | ---------------------------------------- | +| `:sql` | SQL statement | +| `:name` | Name of the operation | +| `:connection_id` | Object ID of the connection object | +| `:connection` | Connection object | +| `:binds` | Bind parameters | +| `:type_casted_binds` | Typecasted bind parameters | +| `:statement_name` | SQL Statement name | +| `:cached` | `true` is added when cached queries used | INFO. The adapters will add their own data as well. @@ -270,7 +282,10 @@ INFO. The adapters will add their own data as well. sql: "SELECT \"posts\".* FROM \"posts\" ", name: "Post Load", connection_id: 70307250813140, - binds: [] + connection: #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f7a838850>, + binds: [#<ActiveModel::Attribute::WithCastValue:0x00007fe19d15dc00>], + type_casted_binds: [11], + statement_name: nil } ``` @@ -291,32 +306,6 @@ INFO. The adapters will add their own data as well. Action Mailer ------------- -### receive.action_mailer - -| Key | Value | -| ------------- | -------------------------------------------- | -| `:mailer` | Name of the mailer class | -| `:message_id` | ID of the message, generated by the Mail gem | -| `:subject` | Subject of the mail | -| `:to` | To address(es) of the mail | -| `:from` | From address of the mail | -| `:bcc` | BCC addresses of the mail | -| `:cc` | CC addresses of the mail | -| `:date` | Date of the mail | -| `:mail` | The encoded form of the mail | - -```ruby -{ - mailer: "Notification", - message_id: "4f5b5491f1774_181b23fc3d4434d38138e5@mba.local.mail", - subject: "Rails Guides", - to: ["users@rails.com", "dhh@rails.com"], - from: ["me@rails.com"], - date: Sat, 10 Mar 2012 14:18:09 +0100, - mail: "..." # omitted for brevity -} -``` - ### deliver.action_mailer | Key | Value | @@ -444,7 +433,7 @@ INFO. Cache stores may add their own keys ``` Active Job --------- +---------- ### enqueue_at.active_job @@ -556,6 +545,14 @@ Active Storage | `:key` | Secure token | | `:service` | Name of the service | +### service_download_chunk.active_storage + +| Key | Value | +| ------------ | ------------------------------- | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:range` | Byte range attempted to be read | + ### service_download.active_storage | Key | Value | @@ -591,7 +588,24 @@ Active Storage | ------------ | ------------------- | | `:key` | Secure token | | `:service` | Name of the service | -| `:url` | Generated url | +| `:url` | Generated URL | + +### service_update_metadata.active_storage + +| Key | Value | +| --------------- | ------------------------------ | +| `:key` | Secure token | +| `:service` | Name of the service | +| `:content_type` | HTTP Content-Type field | +| `:disposition` | HTTP Content-Disposition field | + +INFO. The only ActiveStorage service that provides this hook so far is GCS. + +### preview.active_storage + +| Key | Value | +| ------------ | ------------------- | +| `:key` | Secure token | Railties -------- @@ -629,7 +643,16 @@ The block receives the following arguments: ```ruby ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data| # your own custom stuff - Rails.logger.info "#{name} Received!" + Rails.logger.info "#{name} Received! (started: #{started}, finished: #{finished})" # process_action.action_controller Received (started: 2019-05-05 13:43:57 -0800, finished: 2019-05-05 13:43:58 -0800) +end +``` + +If you are concerned about the accuracy of `started` and `finished` to compute a precise elapsed time then use `ActiveSupport::Notifications.monotonic_subscribe`. The given block would receive the same arguments as above but the `started` and `finished` will have values with an accurate monotonic time instead of wall-clock time. + +```ruby +ActiveSupport::Notifications.monotonic_subscribe "process_action.action_controller" do |name, started, finished, unique_id, data| + # your own custom stuff + Rails.logger.info "#{name} Received! (started: #{started}, finished: #{finished})" # process_action.action_controller Received (started: 1560978.425334, finished: 1560979.429234) end ``` @@ -648,6 +671,18 @@ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*a end ``` +You may also pass block with only one argument, it will yield an event object to the block: + +```ruby +ActiveSupport::Notifications.subscribe "process_action.action_controller" do |event| + event.name # => "process_action.action_controller" + event.duration # => 10 (in milliseconds) + event.payload # => {:extra=>information} + + Rails.logger.info "#{event} Received!" +end +``` + Most times you only care about the data itself. Here is a shortcut to just get the data. ```ruby @@ -672,7 +707,7 @@ Creating custom events Adding your own events is easy as well. `ActiveSupport::Notifications` will take care of all the heavy lifting for you. Simply call `instrument` with a `name`, `payload` and a block. The notification will be sent after the block returns. `ActiveSupport` will generate the start and end times -and add the instrumenter's unique ID. All data passed into the `instrument` call will make +and add the instrumenter's unique ID. All data passed into the `instrument` call will make it into the payload. Here's an example: @@ -691,5 +726,16 @@ ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, fini end ``` +You also have the option to call instrument without passing a block. This lets you leverage the +instrumentation infrastructure for other messaging uses. + +```ruby +ActiveSupport::Notifications.instrument "my.custom.event", this: :data + +ActiveSupport::Notifications.subscribe "my.custom.event" do |name, started, finished, unique_id, data| + puts data.inspect # {:this=>:data} +end +``` + You should follow Rails conventions when defining your own events. The format is: `event.library`. If your application is sending Tweets, you should create an event named `tweet.twitter`. diff --git a/guides/source/api_app.md b/guides/source/api_app.md index 85367c50e7..181d39e7e0 100644 --- a/guides/source/api_app.md +++ b/guides/source/api_app.md @@ -76,7 +76,7 @@ Handled at the middleware layer: - Conditional GETs: Rails handles conditional `GET` (`ETag` and `Last-Modified`) processing request headers and returning the correct response headers and status code. All you need to do is use the - [`stale?`](http://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F) + [`stale?`](https://api.rubyonrails.org/classes/ActionController/ConditionalGet.html#method-i-stale-3F) check in your controller, and Rails will handle all of the HTTP details for you. - HEAD requests: Rails will transparently convert `HEAD` requests into `GET` ones, and return just the headers on the way out. This makes `HEAD` work reliably in @@ -287,7 +287,7 @@ environment's configuration file. You can learn more about how to use `Rack::Sendfile` with popular front-ends in [the Rack::Sendfile -documentation](http://rubydoc.info/github/rack/rack/master/Rack/Sendfile). +documentation](https://www.rubydoc.info/github/rack/rack/master/Rack/Sendfile). Here are some values for this header for some popular servers, once these servers are configured to support accelerated file sending: @@ -374,7 +374,7 @@ controller modules by default: - `ActionController::Renderers::All`: Support for `render :json` and friends. - `ActionController::ConditionalGet`: Support for `stale?`. - `ActionController::BasicImplicitRender`: Makes sure to return an empty response, if there isn't an explicit one. -- `ActionController::StrongParameters`: Support for parameters white-listing in combination with Active Model mass assignment. +- `ActionController::StrongParameters`: Support for parameters filtering in combination with Active Model mass assignment. - `ActionController::DataStreaming`: Support for `send_file` and `send_data`. - `AbstractController::Callbacks`: Support for `before_action` and similar helpers. @@ -420,6 +420,15 @@ Some common modules you might want to add: - `ActionController::MimeResponds`: Support for `respond_to`. - `ActionController::Cookies`: Support for `cookies`, which includes support for signed and encrypted cookies. This requires the cookies middleware. +- `ActionController::Caching`: Support view caching for the API controller. Please notice that + you will need to manually specify cache store inside the controller like: + ```ruby + class ApplicationController < ActionController::API + include ::ActionController::Caching + self.cache_store = :mem_cached_store + end + ``` + Rails does *not* pass this configuration automatically. The best place to add a module is in your `ApplicationController`, but you can also add modules to individual controllers. diff --git a/guides/source/api_documentation_guidelines.md b/guides/source/api_documentation_guidelines.md index b6ee7354f9..a7ffa4fbd4 100644 --- a/guides/source/api_documentation_guidelines.md +++ b/guides/source/api_documentation_guidelines.md @@ -15,7 +15,7 @@ After reading this guide, you will know: RDoc ---- -The [Rails API documentation](http://api.rubyonrails.org) is generated with +The [Rails API documentation](https://api.rubyonrails.org) is generated with [RDoc](https://ruby.github.io/rdoc/). To generate it, make sure you are in the rails root directory, run `bundle install` and execute: diff --git a/guides/source/asset_pipeline.md b/guides/source/asset_pipeline.md index 500e230ff9..d853559440 100644 --- a/guides/source/asset_pipeline.md +++ b/guides/source/asset_pipeline.md @@ -33,13 +33,11 @@ passing the `--skip-sprockets` option. rails new appname --skip-sprockets ``` -Rails automatically adds the `sass-rails`, `coffee-rails` and `uglifier` -gems to your `Gemfile`, which are used by Sprockets for asset compression: +Rails automatically adds the `sass-rails` gem to your `Gemfile`, which is used +by Sprockets for asset compression: ```ruby gem 'sass-rails' -gem 'uglifier' -gem 'coffee-rails' ``` Using the `--skip-sprockets` option will prevent Rails from adding @@ -126,7 +124,7 @@ The query string strategy has several disadvantages: 1. **Not all caches will reliably cache content where the filename only differs by query parameters** - [Steve Souders recommends](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/), + [Steve Souders recommends](https://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/), "...avoiding a querystring for cacheable resources". He found that in this case 5-20% of requests will not be cached. Query strings in particular do not work at all with some CDNs for cache invalidation. @@ -176,8 +174,7 @@ in `app/assets` are never served directly in production. ### Controller Specific Assets -When you generate a scaffold or a controller, Rails also generates a JavaScript -file (or CoffeeScript file if the `coffee-rails` gem is in the `Gemfile`) and a +When you generate a scaffold or a controller, Rails also generates a Cascading Style Sheet file (or SCSS file if `sass-rails` is in the `Gemfile`) for that controller. Additionally, when generating a scaffold, Rails generates the file `scaffolds.css` (or `scaffolds.scss` if `sass-rails` is in the @@ -434,9 +431,8 @@ one file rather than many, the load time of pages can be greatly reduced because the browser makes fewer requests. Compression also reduces file size, enabling the browser to download them faster. - -For example, a new Rails application includes a default -`app/assets/javascripts/application.js` file containing the following lines: +For example, with a `app/assets/javascripts/application.js` file containing the +following lines: ```js // ... @@ -476,8 +472,7 @@ which contains these lines: */ ``` -Rails creates both `app/assets/javascripts/application.js` and -`app/assets/stylesheets/application.css` regardless of whether the +Rails create `app/assets/stylesheets/application.css` regardless of whether the --skip-sprockets option is used when creating a new Rails application. This is so you can easily add asset pipelining later if you like. @@ -489,7 +484,7 @@ one, requiring all stylesheets from the current directory. In this example, `require_self` is used. This puts the CSS contained within the file (if any) at the precise location of the `require_self` call. -NOTE. If you want to use multiple Sass files, you should generally use the [Sass `@import` rule](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) +NOTE. If you want to use multiple Sass files, you should generally use the [Sass `@import` rule](https://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html#import) instead of these Sprockets directives. When using Sprockets directives, Sass files exist within their own scope, making variables or mixins only available within the document they were defined in. @@ -517,8 +512,7 @@ The file extensions used on an asset determine what preprocessing is applied. When a controller or a scaffold is generated with the default Rails gemset, a CoffeeScript file and a SCSS file are generated in place of a regular JavaScript and CSS file. The example used before was a controller called "projects", which -generated an `app/assets/javascripts/projects.coffee` and an -`app/assets/stylesheets/projects.scss` file. +generated an `app/assets/stylesheets/projects.scss` file. In development mode, or if the asset pipeline is disabled, when these files are requested they are processed by the processors provided by the `coffee-script` @@ -745,7 +739,7 @@ mapping requests back to Sprockets. A typical manifest file looks like: The default location for the manifest is the root of the location specified in `config.assets.prefix` ('/assets' by default). -NOTE: If there are missing precompiled files in production you will get an +NOTE: If there are missing precompiled files in production you will get a `Sprockets::Helpers::RailsHelper::AssetPaths::AssetNotPrecompiledError` exception indicating the name of the missing file(s). @@ -961,7 +955,7 @@ is present. ##### CDN Header Debugging One way to check the headers are cached properly in your CDN is by using [curl]( -http://explainshell.com/explain?cmd=curl+-I+http%3A%2F%2Fwww.example.com). You +https://explainshell.com/explain?cmd=curl+-I+http%3A%2F%2Fwww.example.com). You can request the headers from both your server and your CDN to verify they are the same: @@ -1009,7 +1003,7 @@ such as `X-Cache` or for any additional headers they may add. ##### CDNs and the Cache-Control Header The [cache control -header](http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) is a W3C +header](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9) is a W3C specification that describes how a request can be cached. When no CDN is used, a browser will use this information to cache contents. This is very helpful for assets that are not modified so that a browser does not need to re-download a @@ -1083,7 +1077,7 @@ Possible options for JavaScript compression are `:closure`, `:uglifier` and `:yui`. These require the use of the `closure-compiler`, `uglifier` or `yui-compressor` gems, respectively. -The default `Gemfile` includes [uglifier](https://github.com/lautis/uglifier). +Take the `uglifier` gem, for example. This gem wraps [UglifyJS](https://github.com/mishoo/UglifyJS) (written for NodeJS) in Ruby. It compresses your code by removing white space and comments, shortening local variable names, and performing other micro-optimizations such @@ -1101,7 +1095,7 @@ Windows you have a JavaScript runtime installed in your operating system. -### Serving GZipped version of assets +### GZipping your assets By default, gzipped version of compiled assets will be generated, along with the non-gzipped version of assets. Gzipped assets help reduce the transmission @@ -1111,6 +1105,8 @@ of data over the wire. You can configure this by setting the `gzip` flag. config.assets.gzip = false # disable gzipped assets generation ``` +Refer to your web server's documentation for instructions on how to serve gzipped assets. + ### Using Your Own Compressor The compressor config settings for CSS and JavaScript also take any object. @@ -1152,7 +1148,7 @@ The X-Sendfile header is a directive to the web server to ignore the response from the application, and instead serve a specified file from disk. This option is off by default, but can be enabled if your server supports it. When enabled, this passes responsibility for serving the file to the web server, which is -faster. Have a look at [send_file](http://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file) +faster. Have a look at [send_file](https://api.rubyonrails.org/classes/ActionController/DataStreaming.html#method-i-send_file) on how to use this feature. Apache and NGINX support this option, which can be enabled in @@ -1170,7 +1166,7 @@ and any other environments you define with production behavior (not TIP: For further details have a look at the docs of your production web server: - [Apache](https://tn123.org/mod_xsendfile/) -- [NGINX](http://wiki.nginx.org/XSendfile) +- [NGINX](https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/) Assets Cache Store ------------------ @@ -1228,4 +1224,3 @@ it as a preprocessor for your mime type. ```ruby Sprockets.register_preprocessor 'text/css', AddComment ``` - diff --git a/guides/source/association_basics.md b/guides/source/association_basics.md index 4f3e8b2cff..27387ac3ac 100644 --- a/guides/source/association_basics.md +++ b/guides/source/association_basics.md @@ -384,7 +384,7 @@ end The corresponding migration might look like this: ```ruby -class CreateSuppliers < ActiveRecord::Migration[5.0] +class CreateSuppliers < ActiveRecord::Migration[5.2] def change create_table :suppliers do |t| t.string :name @@ -392,7 +392,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0] end create_table :accounts do |t| - t.integer :supplier_id + t.bigint :supplier_id t.string :account_number t.timestamps end @@ -402,7 +402,7 @@ class CreateSuppliers < ActiveRecord::Migration[5.0] end ``` -NOTE: Using `t.integer :supplier_id` makes the foreign key naming obvious and explicit. In current versions of Rails, you can abstract away this implementation detail by using `t.references :supplier` instead. +NOTE: Using `t.bigint :supplier_id` makes the foreign key naming obvious and explicit. In current versions of Rails, you can abstract away this implementation detail by using `t.references :supplier` instead. ### Choosing Between `has_many :through` and `has_and_belongs_to_many` @@ -466,11 +466,11 @@ Similarly, you can retrieve `@product.pictures`. If you have an instance of the `Picture` model, you can get to its parent via `@picture.imageable`. To make this work, you need to declare both a foreign key column and a type column in the model that declares the polymorphic interface: ```ruby -class CreatePictures < ActiveRecord::Migration[5.0] +class CreatePictures < ActiveRecord::Migration[5.2] def change create_table :pictures do |t| t.string :name - t.integer :imageable_id + t.bigint :imageable_id t.string :imageable_type t.timestamps end @@ -619,11 +619,11 @@ end These need to be backed up by a migration to create the `assemblies_parts` table. This table should be created without a primary key: ```ruby -class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.0] +class CreateAssembliesPartsJoinTable < ActiveRecord::Migration[5.2] def change create_table :assemblies_parts, id: false do |t| - t.integer :assembly_id - t.integer :part_id + t.bigint :assembly_id + t.bigint :part_id end add_index :assemblies_parts, :assembly_id @@ -1231,6 +1231,7 @@ The `has_one` association supports these options: * `:source` * `:source_type` * `:through` +* `:touch` * `:validate` ##### `:as` @@ -1257,7 +1258,7 @@ Controls what happens to the associated object when its owner is destroyed: * `:destroy` causes the associated object to also be destroyed * `:delete` causes the associated object to be deleted directly from the database (so callbacks will not execute) -* `:nullify` causes the foreign key to be set to `NULL`. Callbacks are not executed. +* `:nullify` causes the foreign key to be set to `NULL`. Polymorphic type column is also nullified on polymorphic associations. Callbacks are not executed. * `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there is an associated record * `:restrict_with_error` causes an error to be added to the owner if there is an associated object @@ -1305,10 +1306,47 @@ The `:source` option specifies the source association name for a `has_one :throu The `:source_type` option specifies the source association type for a `has_one :through` association that proceeds through a polymorphic association. +```ruby +class Book < ApplicationRecord + has_one :format, polymorphic: true + has_one :dust_jacket, through: :format, source: :dust_jacket, source_type: "Hardback" +end + +class Paperback < ApplicationRecord; end + +class Hardback < ApplicationRecord + has_one :dust_jacket +end + +class DustJacket < ApplicationRecord; end +``` + ##### `:through` The `:through` option specifies a join model through which to perform the query. `has_one :through` associations were discussed in detail [earlier in this guide](#the-has-one-through-association). +##### `:touch` + +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 Supplier < ApplicationRecord + has_one :account, touch: true +end + +class Account < ApplicationRecord + belongs_to :supplier +end +``` + +In this case, saving or destroying a supplier will update the timestamp on the associated account. You can also specify a particular timestamp attribute to update: + +```ruby +class Supplier < ApplicationRecord + has_one :account, touch: :suppliers_updated_at +end +``` + ##### `:validate` If you set the `:validate` option to `true`, then associated objects will be validated whenever you save this object. By default, this is `false`: associated objects will not be validated when this object is saved. @@ -1544,7 +1582,7 @@ The `collection.size` method returns the number of objects in the collection. ##### `collection.find(...)` The `collection.find` method finds objects within the collection. It uses the same syntax and options as -[`ActiveRecord::Base.find`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). +[`ActiveRecord::Base.find`](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). ```ruby @available_book = @author.books.find(1) @@ -1563,7 +1601,7 @@ The `collection.where` method finds objects within the collection based on the c The `collection.exists?` method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as -[`ActiveRecord::Base.exists?`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). +[`ActiveRecord::Base.exists?`](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). ##### `collection.build(attributes = {}, ...)` @@ -1658,7 +1696,7 @@ Controls what happens to the associated objects when their owner is destroyed: * `:destroy` causes all the associated objects to also be destroyed * `:delete_all` causes all the associated objects to be deleted directly from the database (so callbacks will not execute) -* `:nullify` causes the foreign keys to be set to `NULL`. Callbacks are not executed. +* `:nullify` causes the foreign key to be set to `NULL`. Polymorphic type column is also nullified on polymorphic associations. Callbacks are not executed. * `:restrict_with_exception` causes an `ActiveRecord::DeleteRestrictionError` exception to be raised if there are any associated records * `:restrict_with_error` causes an error to be added to the owner if there are any associated objects @@ -1717,6 +1755,20 @@ The `:source` option specifies the source association name for a `has_many :thro The `:source_type` option specifies the source association type for a `has_many :through` association that proceeds through a polymorphic association. +```ruby +class Author < ApplicationRecord + has_many :books + has_many :paperbacks, through: :books, source: :format, source_type: "Paperback" +end + +class Book < ApplicationRecord + has_one :format, polymorphic: true +end + +class Hardback < ApplicationRecord; end +class Paperback < ApplicationRecord; end +``` + ##### `:through` The `:through` option specifies a join model through which to perform the query. `has_many :through` associations provide a way to implement many-to-many relationships, as discussed [earlier in this guide](#the-has-many-through-association). @@ -2077,7 +2129,7 @@ The `collection.size` method returns the number of objects in the collection. ##### `collection.find(...)` The `collection.find` method finds objects within the collection. It uses the same syntax and options as -[`ActiveRecord::Base.find`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). +[`ActiveRecord::Base.find`](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-find). ```ruby @assembly = @part.assemblies.find(1) @@ -2095,7 +2147,7 @@ The `collection.where` method finds objects within the collection based on the c The `collection.exists?` method checks whether an object meeting the supplied conditions exists in the collection. It uses the same syntax and options as -[`ActiveRecord::Base.exists?`](http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). +[`ActiveRecord::Base.exists?`](https://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html#method-i-exists-3F). ##### `collection.build(attributes = {})` @@ -2350,6 +2402,17 @@ end If a `before_add` callback throws an exception, the object does not get added to the collection. Similarly, if a `before_remove` callback throws an exception, the object does not get removed from the collection. +NOTE: These callbacks are called only when the associated objects are added or removed through the association collection: + +```ruby +# Triggers `before_add` callback +author.books << book +author.books = [book, book2] + +# Does not trigger the `before_add` callback +book.update(author_id: 1) +``` + ### Association Extensions 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: @@ -2388,8 +2451,8 @@ Extensions can refer to the internals of the association proxy using these three * `proxy_association.reflection` returns the reflection object that describes the association. * `proxy_association.target` returns the associated object for `belongs_to` or `has_one`, or the collection of associated objects for `has_many` or `has_and_belongs_to_many`. -Single Table Inheritance ------------------------- +Single Table Inheritance (STI) +------------------------------ Sometimes, you may want to share fields and behavior between different models. Let's say we have Car, Motorcycle, and Bicycle models. We will want to share diff --git a/guides/source/autoloading_and_reloading_constants.md b/guides/source/autoloading_and_reloading_constants.md index b3f923a017..8cd2d353de 100644 --- a/guides/source/autoloading_and_reloading_constants.md +++ b/guides/source/autoloading_and_reloading_constants.md @@ -1,18 +1,18 @@ **DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.** -Autoloading and Reloading Constants -=================================== +Autoloading and Reloading Constants (Zeitwerk Mode) +====================================================== -This guide documents how constant autoloading and reloading works. +This guide documents how autoloading and reloading works in `zeitwerk` mode. After reading this guide, you will know: -* Key aspects of Ruby constants -* What are the `autoload_paths` and how does eager loading work in production? -* How constant autoloading works -* What is `require_dependency` -* How constant reloading works -* Solutions to common autoloading gotchas +* Autoloading modes +* Related Rails configuration +* Project structure +* Autoloading, reloading, and eager loading +* Single Table Inheritance +* And more -------------------------------------------------------------------------------- @@ -20,13 +20,15 @@ After reading this guide, you will know: Introduction ------------ -Ruby on Rails allows applications to be written as if their code was preloaded. +INFO. This guide documents autoloading in `zeitwerk` mode, which is new in Rails 6. If you'd like to read about `classic` mode instead, please check [Autoloading and Reloading Constants (Classic Mode)](autoloading_and_reloading_constants_classic_mode.html). -In a normal Ruby program classes need to load their dependencies: +In a normal Ruby program, dependencies need to be loaded by hand. For example, the following controller uses classes `ApplicationController` and `Post`, and normally you'd need to put `require` calls for them: ```ruby -require 'application_controller' -require 'post' +# DO NOT DO THIS. +require "application_controller" +require "post" +# DO NOT DO THIS. class PostsController < ApplicationController def index @@ -35,16 +37,7 @@ class PostsController < ApplicationController end ``` -Our Rubyist instinct quickly sees some redundancy in there: If classes were -defined in files matching their name, couldn't their loading be automated -somehow? We could save scanning the file for dependencies, which is brittle. - -Moreover, `Kernel#require` loads files once, but development is much more smooth -if code gets refreshed when it changes without restarting the server. It would -be nice to be able to use `Kernel#load` in development, and `Kernel#require` in -production. - -Indeed, those features are provided by Ruby on Rails, where we just write +This is not the case in Rails applications, where application classes and modules are just available everywhere: ```ruby class PostsController < ApplicationController @@ -54,1330 +47,255 @@ class PostsController < ApplicationController end ``` -This guide documents how that works. - - -Constants Refresher -------------------- - -While constants are trivial in most programming languages, they are a rich -topic in Ruby. - -It is beyond the scope of this guide to document Ruby constants, but we are -nevertheless going to highlight a few key topics. Truly grasping the following -sections is instrumental to understanding constant autoloading and reloading. - -### Nesting - -Class and module definitions can be nested to create namespaces: - -```ruby -module XML - class SAXParser - # (1) - end -end -``` - -The *nesting* at any given place is the collection of enclosing nested class and -module objects outwards. The nesting at any given place can be inspected with -`Module.nesting`. For example, in the previous example, the nesting at -(1) is - -```ruby -[XML::SAXParser, XML] -``` - -It is important to understand that the nesting is composed of class and module -*objects*, it has nothing to do with the constants used to access them, and is -also unrelated to their names. - -For instance, while this definition is similar to the previous one: - -```ruby -class XML::SAXParser - # (2) -end -``` - -the nesting in (2) is different: - -```ruby -[XML::SAXParser] -``` - -`XML` does not belong to it. - -We can see in this example that the name of a class or module that belongs to a -certain nesting does not necessarily correlate with the namespaces at the spot. - -Even more, they are totally independent, take for instance - -```ruby -module X - module Y - end -end - -module A - module B - end -end - -module X::Y - module A::B - # (3) - end -end -``` - -The nesting in (3) consists of two module objects: - -```ruby -[A::B, X::Y] -``` - -So, it not only doesn't end in `A`, which does not even belong to the nesting, -but it also contains `X::Y`, which is independent from `A::B`. - -The nesting is an internal stack maintained by the interpreter, and it gets -modified according to these rules: - -* The class object following a `class` keyword gets pushed when its body is -executed, and popped after it. - -* The module object following a `module` keyword gets pushed when its body is -executed, and popped after it. - -* A singleton class opened with `class << object` gets pushed, and popped later. - -* When `instance_eval` is called using a string argument, -the singleton class of the receiver is pushed to the nesting of the eval'ed -code. When `class_eval` or `module_eval` is called using a string argument, -the receiver is pushed to the nesting of the eval'ed code. - -* The nesting at the top-level of code interpreted by `Kernel#load` is empty -unless the `load` call receives a true value as second argument, in which case -a newly created anonymous module is pushed by Ruby. - -It is interesting to observe that blocks do not modify the stack. In particular -the blocks that may be passed to `Class.new` and `Module.new` do not get the -class or module being defined pushed to their nesting. That's one of the -differences between defining classes and modules in one way or another. - -### Class and Module Definitions are Constant Assignments - -Let's suppose the following snippet creates a class (rather than reopening it): - -```ruby -class C -end -``` - -Ruby creates a constant `C` in `Object` and stores in that constant a class -object. The name of the class instance is "C", a string, named after the -constant. +Idiomatic Rails applications only issue `require` calls to load stuff from their `lib` directory, the Ruby standard library, Ruby gems, etc. That is, anything that does not belong to their autoload paths, explained below. -That is, -```ruby -class Project < ApplicationRecord -end -``` +Enabling Zeitwerk Mode +---------------------- -performs a constant assignment equivalent to +The autoloading `zeitwerk` mode is enabled by default in Rails 6 applications running on CRuby: ```ruby -Project = Class.new(ApplicationRecord) +# config/application.rb +config.load_defaults "6.x" # enables zeitwerk mode in CRuby ``` -including setting the name of the class as a side-effect: +In `zeitwerk` mode, Rails uses [Zeitwerk](https://github.com/fxn/zeitwerk) internally to autoload, reload, and eager load. Rails instantiates and configures a dedicated Zeitwerk instance that manages the project. -```ruby -Project.name # => "Project" -``` +INFO. You do not configure Zeitwerk manually in a Rails application. Rather, you configure the application using the portable configuration points explained in this guide, and Rails translates that to Zeitwerk on your behalf. -Constant assignment has a special rule to make that happen: if the object -being assigned is an anonymous class or module, Ruby sets the object's name to -the name of the constant. +Project Structure +----------------- -INFO. From then on, what happens to the constant and the instance does not -matter. For example, the constant could be deleted, the class object could be -assigned to a different constant, be stored in no constant anymore, etc. Once -the name is set, it doesn't change. +In a Rails application file names have to match the constants they define, with directories acting as namespaces. -Similarly, module creation using the `module` keyword as in +For example, the file `app/helpers/users_helper.rb` should define `UsersHelper` and the file `app/controllers/admin/payments_controller.rb` should define `Admin::PaymentsController`. -```ruby -module Admin -end -``` - -performs a constant assignment equivalent to +Rails configures Zeitwerk to inflect file names with `String#camelize`. For example, it expects that `app/controllers/users_controller.rb` defines the constant `UsersController` because ```ruby -Admin = Module.new +"users_controller".camelize # => UsersController ``` -including setting the name as a side-effect: +If you need to customize any of these inflections, for example to add an acronym, please have a look at `config/initializers/inflections.rb`. -```ruby -Admin.name # => "Admin" -``` +Please, check the [Zeitwerk documentation](https://github.com/fxn/zeitwerk#file-structure) for further details. -WARNING. The execution context of a block passed to `Class.new` or `Module.new` -is not entirely equivalent to the one of the body of the definitions using the -`class` and `module` keywords. But both idioms result in the same constant -assignment. - -Thus, an informal expression like "the `String` class" technically means the -class object stored in the constant called "String". That constant, in turn, -belongs to the class object stored in the constant called "Object". - -`String` is an ordinary constant, and everything related to them such as -resolution algorithms applies to it. - -Likewise, in the controller - -```ruby -class PostsController < ApplicationController - def index - @posts = Post.all - end -end -``` - -`Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If -all is good, the constant is evaluated to an object that responds to `all`. +Autoload paths +-------------- -That is why we talk about *constant* autoloading, Rails has the ability to -load constants on the fly. +We call _autoload paths_ to the list of application directories whose contents are to be autoloaded. For example, `app/models`. Such directories represent the root namespace: `Object`. -### Constants are Stored in Modules +INFO. Autoload paths are called _root directories_ in Zeitwerk documentation, but we'll stay with "autoload path" in this guide. -Constants belong to modules in a very literal sense. Classes and modules have -a constant table; think of it as a hash table. +Within an autoload path, file names must match the constants they define as documented [here](https://github.com/fxn/zeitwerk#file-structure). -Let's analyze an example to really understand what that means. While common -abuses of language like "the `String` class" are convenient, the exposition is -going to be precise here for didactic purposes. +By default, the autoload paths of an application consist of all the subdirectories of `app` that exist when the application boots ---except for `aasets`, `javascripts`, `views`,--- plus the autoload paths of engines it might depend on. -Let's consider the following module definition: +For example, if `UsersHelper` is implemented in `app/helpers/users_helper.rb`, the module is autoloadable, you do not need (and should not write) a `require` call for it: -```ruby -module Colors - RED = '0xff0000' -end ``` - -First, when the `module` keyword is processed, the interpreter creates a new -entry in the constant table of the class object stored in the `Object` constant. -Said entry associates the name "Colors" to a newly created module object. -Furthermore, the interpreter sets the name of the new module object to be the -string "Colors". - -Later, when the body of the module definition is interpreted, a new entry is -created in the constant table of the module object stored in the `Colors` -constant. That entry maps the name "RED" to the string "0xff0000". - -In particular, `Colors::RED` is totally unrelated to any other `RED` constant -that may live in any other class or module object. If there were any, they -would have separate entries in their respective constant tables. - -Pay special attention in the previous paragraphs to the distinction between -class and module objects, constant names, and value objects associated to them -in constant tables. - -### Resolution Algorithms - -#### Resolution Algorithm for Relative Constants - -At any given place in the code, let's define *cref* to be the first element of -the nesting if it is not empty, or `Object` otherwise. - -Without getting too much into the details, the resolution algorithm for relative -constant references goes like this: - -1. If the nesting is not empty the constant is looked up in its elements and in -order. The ancestors of those elements are ignored. - -2. If not found, then the algorithm walks up the ancestor chain of the cref. - -3. If not found and the cref is a module, the constant is looked up in `Object`. - -4. If not found, `const_missing` is invoked on the cref. The default -implementation of `const_missing` raises `NameError`, but it can be overridden. - -Rails autoloading **does not emulate this algorithm**, but its starting point is -the name of the constant to be autoloaded, and the cref. See more in [Relative -References](#autoloading-algorithms-relative-references). - -#### Resolution Algorithm for Qualified Constants - -Qualified constants look like this: - -```ruby -Billing::Invoice +$ bin/rails runner 'p UsersHelper' +UsersHelper ``` -`Billing::Invoice` is composed of two constants: `Billing` is relative and is -resolved using the algorithm of the previous section. - -INFO. Leading colons would make the first segment absolute rather than -relative: `::Billing::Invoice`. That would force `Billing` to be looked up -only as a top-level constant. - -`Invoice` on the other hand is qualified by `Billing` and we are going to see -its resolution next. Let's define *parent* to be that qualifying class or module -object, that is, `Billing` in the example above. The algorithm for qualified -constants goes like this: - -1. The constant is looked up in the parent and its ancestors. In Ruby >= 2.5, -`Object` is skipped if present among the ancestors. `Kernel` and `BasicObject` -are still checked though. +Autoload paths automatically pick any custom directories under `app`. For example, if your application has `app/presenters`, or `app/services`, etc., they are added to autoload paths. -2. If the lookup fails, `const_missing` is invoked in the parent. The default -implementation of `const_missing` raises `NameError`, but it can be overridden. +The array of autoload paths can be extended by mutating `config.autoload_paths`, in `config/application.rb`, but nowadays this is discouraged. -INFO. In Ruby < 2.5 `String::Hash` evaluates to `Hash` and the interpreter -issues a warning: "toplevel constant Hash referenced by String::Hash". Starting -with 2.5, `String::Hash` raises `NameError` because `Object` is skipped. +WARNING. Please, do not mutate `ActiveSupport::Dependencies.autoload_paths`, the public interface to change autoload paths is `config.autoload_paths`. -As you see, this algorithm is simpler than the one for relative constants. In -particular, the nesting plays no role here, and modules are not special-cased, -if neither they nor their ancestors have the constants, `Object` is **not** -checked. -Rails autoloading **does not emulate this algorithm**, but its starting point is -the name of the constant to be autoloaded, and the parent. See more in -[Qualified References](#autoloading-algorithms-qualified-references). - - -Vocabulary +$LOAD_PATH ---------- -### Parent Namespaces - -Given a string with a constant path we define its *parent namespace* to be the -string that results from removing its rightmost segment. - -For example, the parent namespace of the string "A::B::C" is the string "A::B", -the parent namespace of "A::B" is "A", and the parent namespace of "A" is "". - -The interpretation of a parent namespace when thinking about classes and modules -is tricky though. Let's consider a module M named "A::B": - -* The parent namespace, "A", may not reflect nesting at a given spot. - -* The constant `A` may no longer exist, some code could have removed it from -`Object`. - -* If `A` exists, the class or module that was originally in `A` may not be there -anymore. For example, if after a constant removal there was another constant -assignment there would generally be a different object in there. - -* In such case, it could even happen that the reassigned `A` held a new class or -module called also "A"! - -* In the previous scenarios M would no longer be reachable through `A::B` but -the module object itself could still be alive somewhere and its name would -still be "A::B". - -The idea of a parent namespace is at the core of the autoloading algorithms -and helps explain and understand their motivation intuitively, but as you see -that metaphor leaks easily. Given an edge case to reason about, take always into -account that by "parent namespace" the guide means exactly that specific string -derivation. - -### Loading Mechanism - -Rails autoloads files with `Kernel#load` when `config.cache_classes` is false, -the default in development mode, and with `Kernel#require` otherwise, the -default in production mode. - -`Kernel#load` allows Rails to execute files more than once if [constant -reloading](#constant-reloading) is enabled. - -This guide uses the word "load" freely to mean a given file is interpreted, but -the actual mechanism can be `Kernel#load` or `Kernel#require` depending on that -flag. - - -Autoloading Availability ------------------------- - -Rails is always able to autoload provided its environment is in place. For -example the `runner` command autoloads: - -``` -$ rails runner 'p User.column_names' -["id", "email", "created_at", "updated_at"] -``` - -The console autoloads, the test suite autoloads, and of course the application -autoloads. - -By default, Rails eager loads the application files when it boots in production -mode, so most of the autoloading going on in development does not happen. But -autoloading may still be triggered during eager loading. - -For example, given - -```ruby -class BeachHouse < House -end -``` - -if `House` is still unknown when `app/models/beach_house.rb` is being eager -loaded, Rails autoloads it. - - -autoload_paths and eager_load_paths ------------------------------------ - -As you probably know, when `require` gets a relative file name: +Autoload paths are added to `$LOAD_PATH` by default. However, Zeitwerk uses absolute file names internally, and your application should not issue `require` calls for autoloadable files, so those directories are actually not needed there. You can opt-out with this flag: ```ruby -require 'erb' +config.add_autoload_paths_to_load_path = false ``` -Ruby looks for the file in the directories listed in `$LOAD_PATH`. That is, Ruby -iterates over all its directories and for each one of them checks whether they -have a file called "erb.rb", or "erb.so", or "erb.o", or "erb.dll". If it finds -any of them, the interpreter loads it and ends the search. Otherwise, it tries -again in the next directory of the list. If the list gets exhausted, `LoadError` -is raised. - -We are going to cover how constant autoloading works in more detail later, but -the idea is that when a constant like `Post` is hit and missing, if there's a -`post.rb` file for example in `app/models` Rails is going to find it, evaluate -it, and have `Post` defined as a side-effect. - -All right, Rails has a collection of directories similar to `$LOAD_PATH` in which -to look up `post.rb`. That collection is called `autoload_paths` and by -default it contains: - -* All subdirectories of `app` in the application and engines present at boot - time. For example, `app/controllers`. They do not need to be the default - ones, any custom directories like `app/workers` belong automatically to - `autoload_paths`. +That may speed legit `require` calls a bit, since there are less lookups. Also, if your application uses [Bootsnap](https://github.com/Shopify/bootsnap), that saves the library from building unnecessary indexes, and saves the RAM they would need. -* Any existing second level directories called `app/*/concerns` in the - application and engines. -* The directory `test/mailers/previews`. +Reloading +--------- -`eager_load_paths` is initially the `app` paths above +Rails automatically reloads classes and modules if application files change. -How files are autoloaded depends on `eager_load` and `cache_classes` config settings which typically vary in development, production, and test modes: +More precisely, if the web server is running and application files have been modified, Rails unloads all autoloaded constants just before the next request is processed. That way, application classes or modules used during that request are going to be autoloaded, thus picking up their current implementation in the file system. - * In **development**, you want quicker startup with incremental loading of application code. So `eager_load` should be set to `false`, and Rails will autoload files as needed (see [Autoloading Algorithms](#autoloading-algorithms) below) -- and then reload them when they change (see [Constant Reloading](#constant-reloading) below). - * In **production**, however, you want consistency and thread-safety and can live with a longer boot time. So `eager_load` is set to `true`, and then during boot (before the app is ready to receive requests) Rails loads all files in the `eager_load_paths` and then turns off auto loading (NB: autoloading may be needed during eager loading). Not autoloading after boot is a `good thing`, as autoloading can cause the app to be have thread-safety problems. - * In **test**, for speed of execution (of individual tests) `eager_load` is `false`, so Rails follows development behaviour. +Reloading can be enabled or disabled. The setting that controls this behavior is `config.cache_classes`, which is false by default in `development` mode (reloading enabled), and true by default in `production` mode (reloading disabled). -What is described above are the defaults with a newly generated Rails app. There are multiple ways this can be configured differently (see [Configuring Rails Applications](configuring.html#rails-general-configuration). -). But using `autoload_paths` on its own in the past (before Rails 5) developers might configure `autoload_paths` to add in extra locations (e.g. `lib` which used to be an autoload path list years ago, but no longer is). However this is now discouraged for most purposes, as it is likely to lead to production-only errors. It is possible to add new locations to both `config.eager_load_paths` and `config.autoload_paths` but use at your own risk. +Rails detects files have changed using an evented file monitor (default), or walking the autoload paths, depending on `config.file_watcher`. -See also [Autoloading in the Test Environment](#autoloading-in-the-test-environment). +In a Rails console there is no file watcher active regardless of the value of `config.cache_classes`. This is so because, normally, it would be confusing to have code reloaded in the middle of a console session, the same way you generally want an individual request to be served by a consistent, non-changing set of application classes and modules. -`config.autoload_paths` is not changeable from environment-specific configuration files. - -The value of `autoload_paths` can be inspected. In a just-generated application -it is (edited): +However, you can force a reload in the console executing `reload!`: ``` -$ rails r 'puts ActiveSupport::Dependencies.autoload_paths' -.../app/assets -.../app/channels -.../app/controllers -.../app/controllers/concerns -.../app/helpers -.../app/jobs -.../app/mailers -.../app/models -.../app/models/concerns -.../activestorage/app/assets -.../activestorage/app/controllers -.../activestorage/app/javascript -.../activestorage/app/jobs -.../activestorage/app/models -.../actioncable/app/assets -.../actionview/app/assets -.../test/mailers/previews +$ bin/rails c +Loading development environment (Rails 6.0.0) +irb(main):001:0> User.object_id +=> 70136277390120 +irb(main):002:0> reload! +Reloading... +=> true +irb(main):003:0> User.object_id +=> 70136284426020 ``` -INFO. `autoload_paths` is computed and cached during the initialization process. -The application needs to be restarted to reflect any changes in the directory -structure. +as you can see, the class object stored in the `User` constant is different after reloading. +### Reloading and Stale Objects -Autoloading Algorithms ----------------------- +It is very important to understand that Ruby does not have a way to truly reload classes and modules in memory, and have that reflected everywhere they are already used. Technically, "unloading" the `User` class means removing the `User` constant via `Object.send(:remove_const, "User")`. -### Relative References +Therefore, if you store a reloadable class or module object in a place that is not reloaded, that value is going to become stale. -A relative constant reference may appear in several places, for example, in +For example, if an initializer stores and caches a certain class object ```ruby -class PostsController < ApplicationController - def index - @posts = Post.all - end -end +# config/initializers/configure_payment_gateway.rb +# DO NOT DO THIS. +$PAYMENT_GATEWAY = Rails.env.production? ? RealGateway : MockedGateway +# DO NOT DO THIS. ``` -all three constant references are relative. - -#### Constants after the `class` and `module` Keywords - -Ruby performs a lookup for the constant that follows a `class` or `module` -keyword because it needs to know if the class or module is going to be created -or reopened. - -If the constant is not defined at that point it is not considered to be a -missing constant, autoloading is **not** triggered. - -So, in the previous example, if `PostsController` is not defined when the file -is interpreted Rails autoloading is not going to be triggered, Ruby will just -define the controller. - -#### Top-Level Constants - -On the contrary, if `ApplicationController` is unknown, the constant is -considered missing and an autoload is going to be attempted by Rails. - -In order to load `ApplicationController`, Rails iterates over `autoload_paths`. -First it checks if `app/assets/application_controller.rb` exists. If it does not, -which is normally the case, it continues and finds -`app/controllers/application_controller.rb`. +and `MockedGateway` gets reloaded, `$PAYMENT_GATEWAY` still stores the class object `MockedGateway` evaluated to when the initializer ran. Reloading does not change the class object stored in `$PAYMENT_GATEWAY`. -If the file defines the constant `ApplicationController` all is fine, otherwise -`LoadError` is raised: +Similarly, in the Rails console, if you have a user instance and reload: ``` -unable to autoload constant ApplicationController, expected -<full path to application_controller.rb> to define it (LoadError) +> user = User.new +> reload! ``` -INFO. Rails does not require the value of autoloaded constants to be a class or -module object. For example, if the file `app/models/max_clients.rb` defines -`MAX_CLIENTS = 100` autoloading `MAX_CLIENTS` works just fine. - -#### Namespaces +the `user` object is instance of a stale class object. Ruby gives you a new class if you evaluate `User` again, but does not update the class `user` is instance of. -Autoloading `ApplicationController` looks directly under the directories of -`autoload_paths` because the nesting in that spot is empty. The situation of -`Post` is different, the nesting in that line is `[PostsController]` and support -for namespaces comes into play. - -The basic idea is that given +Another use case of this gotcha is subclassing reloadable classes in a place that is not reloaded: ```ruby -module Admin - class BaseController < ApplicationController - @@all_roles = Role.all - end +# lib/vip_user.rb +class VipUser < User end ``` -to autoload `Role` we are going to check if it is defined in the current or -parent namespaces, one at a time. So, conceptually we want to try to autoload -any of - -``` -Admin::BaseController::Role -Admin::Role -Role -``` - -in that order. That's the idea. To do so, Rails looks in `autoload_paths` -respectively for file names like these: - -``` -admin/base_controller/role.rb -admin/role.rb -role.rb -``` - -modulus some additional directory lookups we are going to cover soon. - -INFO. `'Constant::Name'.underscore` gives the relative path without extension of -the file name where `Constant::Name` is expected to be defined. - -Let's see how Rails autoloads the `Post` constant in the `PostsController` -above assuming the application has a `Post` model defined in -`app/models/post.rb`. - -First it checks for `posts_controller/post.rb` in `autoload_paths`: - -``` -app/assets/posts_controller/post.rb -app/controllers/posts_controller/post.rb -app/helpers/posts_controller/post.rb -... -test/mailers/previews/posts_controller/post.rb -``` - -Since the lookup is exhausted without success, a similar search for a directory -is performed, we are going to see why in the [next section](#automatic-modules): +if `User` is reloaded, since `VipUser` is not, the superclass of `VipUser` is the original stale class object. -``` -app/assets/posts_controller/post -app/controllers/posts_controller/post -app/helpers/posts_controller/post -... -test/mailers/previews/posts_controller/post -``` +Bottom line: **do not cache reloadable classes or modules**. -If all those attempts fail, then Rails starts the lookup again in the parent -namespace. In this case only the top-level remains: -``` -app/assets/post.rb -app/controllers/post.rb -app/helpers/post.rb -app/mailers/post.rb -app/models/post.rb -``` +Eager Loading +------------- -A matching file is found in `app/models/post.rb`. The lookup stops there and the -file is loaded. If the file actually defines `Post` all is fine, otherwise -`LoadError` is raised. +In production-like environments it is generally better to load all the application code when the application boots. Eager loading puts everything in memory ready to serve requests right away, and it is also [CoW](https://en.wikipedia.org/wiki/Copy-on-write)-friendly. -### Qualified References +Eager loading is controlled by the flag `config.eager_load`, which is enabled by default in `production` mode. -When a qualified constant is missing Rails does not look for it in the parent -namespaces. But there is a caveat: when a constant is missing, Rails is -unable to tell if the trigger was a relative reference or a qualified one. +The order in which files are eager loaded is undefined. -For example, consider +if the `Zeitwerk` constant is defined, Rails invokes `Zeitwerk::Loader.eager_load_all` regardless of the application autoloading mode. That ensures dependencies managed by Zeitwerk are eager loaded. -```ruby -module Admin - User -end -``` -and - -```ruby -Admin::User -``` - -If `User` is missing, in either case all Rails knows is that a constant called -"User" was missing in a module called "Admin". +Single Table Inheritance +------------------------ -If there is a top-level `User` Ruby would resolve it in the former example, but -wouldn't in the latter. In general, Rails does not emulate the Ruby constant -resolution algorithms, but in this case it tries using the following heuristic: +Single Table Inheritance is a feature that doesn't play well with lazy loading. Reason is, its API generally needs to be able to enumerate the STI hierarchy to work correctly, whereas lazy loading defers loading classes until they are referenced. You can't enumerate what you haven't referenced yet. -> If none of the parent namespaces of the class or module has the missing -> constant then Rails assumes the reference is relative. Otherwise qualified. +In a sense, applications need to eager load STI hierarchies regardless of the loading mode. -For example, if this code triggers autoloading +Of course, if the application eager loads on boot, that is already accomplished. When it does not, it is in practice enough to instantiate the existing types in the database, which in development or test modes is usually fine. One way to do that is to throw this module into the `lib` directory: ```ruby -Admin::User -``` - -and the `User` constant is already present in `Object`, it is not possible that -the situation is - -```ruby -module Admin - User -end -``` - -because otherwise Ruby would have resolved `User` and no autoloading would have -been triggered in the first place. Thus, Rails assumes a qualified reference and -considers the file `admin/user.rb` and directory `admin/user` to be the only -valid options. - -In practice, this works quite well as long as the nesting matches all parent -namespaces respectively and the constants that make the rule apply are known at -that time. - -However, autoloading happens on demand. If by chance the top-level `User` was -not yet loaded, then Rails assumes a relative reference by contract. - -Naming conflicts of this kind are rare in practice, but if one occurs, -`require_dependency` provides a solution by ensuring that the constant needed -to trigger the heuristic is defined in the conflicting place. - -### Automatic Modules - -When a module acts as a namespace, Rails does not require the application to -define a file for it, a directory matching the namespace is enough. +module StiPreload + unless Rails.application.config.eager_load + extend ActiveSupport::Concern -Suppose an application has a back office whose controllers are stored in -`app/controllers/admin`. If the `Admin` module is not yet loaded when -`Admin::UsersController` is hit, Rails needs first to autoload the constant -`Admin`. - -If `autoload_paths` has a file called `admin.rb` Rails is going to load that -one, but if there's no such file and a directory called `admin` is found, Rails -creates an empty module and assigns it to the `Admin` constant on the fly. - -### Generic Procedure - -Relative references are reported to be missing in the cref where they were hit, -and qualified references are reported to be missing in their parent (see -[Resolution Algorithm for Relative -Constants](#resolution-algorithm-for-relative-constants) at the beginning of -this guide for the definition of *cref*, and [Resolution Algorithm for Qualified -Constants](#resolution-algorithm-for-qualified-constants) for the definition of -*parent*). - -The procedure to autoload constant `C` in an arbitrary situation is as follows: - -``` -if the class or module in which C is missing is Object - let ns = '' -else - let M = the class or module in which C is missing - - if M is anonymous - let ns = '' - else - let ns = M.name - end -end - -loop do - # Look for a regular file. - for dir in autoload_paths - if the file "#{dir}/#{ns.underscore}/c.rb" exists - load/require "#{dir}/#{ns.underscore}/c.rb" - - if C is now defined - return - else - raise LoadError - end + included do + cattr_accessor :preloaded, instance_accessor: false end - end - # Look for an automatic module. - for dir in autoload_paths - if the directory "#{dir}/#{ns.underscore}/c" exists - if ns is an empty string - let C = Module.new in Object and return - else - let C = Module.new in ns.constantize and return + class_methods do + def descendants + preload_sti unless preloaded + super end - end - end - - if ns is empty - # We reached the top-level without finding the constant. - raise NameError - else - if C exists in any of the parent namespaces - # Qualified constants heuristic. - raise NameError - else - # Try again in the parent namespace. - let ns = the parent namespace of ns and retry - end - end -end -``` - - -require_dependency ------------------- - -Constant autoloading is triggered on demand and therefore code that uses a -certain constant may have it already defined or may trigger an autoload. That -depends on the execution path and it may vary between runs. - -There are times, however, in which you want to make sure a certain constant is -known when the execution reaches some code. `require_dependency` provides a way -to load a file using the current [loading mechanism](#loading-mechanism), and -keeping track of constants defined in that file as if they were autoloaded to -have them reloaded as needed. - -`require_dependency` is rarely needed, but see a couple of use-cases in -[Autoloading and STI](#autoloading-and-sti) and [When Constants aren't -Triggered](#when-constants-aren-t-missed). - -WARNING. Unlike autoloading, `require_dependency` does not expect the file to -define any particular constant. Exploiting this behavior would be a bad practice -though, file and constant paths should match. - - -Constant Reloading ------------------- - -When `config.cache_classes` is false Rails is able to reload autoloaded -constants. -For example, if you're in a console session and edit some file behind the -scenes, the code can be reloaded with the `reload!` command: - -``` -> reload! -``` - -When the application runs, code is reloaded when something relevant to this -logic changes. In order to do that, Rails monitors a number of things: - -* `config/routes.rb`. - -* Locales. - -* Ruby files under `autoload_paths`. - -* `db/schema.rb` and `db/structure.sql`. - -If anything in there changes, there is a middleware that detects it and reloads -the code. - -Autoloading keeps track of autoloaded constants. Reloading is implemented by -removing them all from their respective classes and modules using -`Module#remove_const`. That way, when the code goes on, those constants are -going to be unknown again, and files reloaded on demand. - -INFO. This is an all-or-nothing operation, Rails does not attempt to reload only -what changed since dependencies between classes makes that really tricky. -Instead, everything is wiped. - - -Module#autoload isn't Involved ------------------------------- - -`Module#autoload` provides a lazy way to load constants that is fully integrated -with the Ruby constant lookup algorithms, dynamic constant API, etc. It is quite -transparent. - -Rails internals make extensive use of it to defer as much work as possible from -the boot process. But constant autoloading in Rails is **not** implemented with -`Module#autoload`. - -One possible implementation based on `Module#autoload` would be to walk the -application tree and issue `autoload` calls that map existing file names to -their conventional constant name. - -There are a number of reasons that prevent Rails from using that implementation. - -For example, `Module#autoload` is only capable of loading files using `require`, -so reloading would not be possible. Not only that, it uses an internal `require` -which is not `Kernel#require`. - -Then, it provides no way to remove declarations in case a file is deleted. If a -constant gets removed with `Module#remove_const` its `autoload` is not triggered -again. Also, it doesn't support qualified names, so files with namespaces should -be interpreted during the walk tree to install their own `autoload` calls, but -those files could have constant references not yet configured. - -An implementation based on `Module#autoload` would be awesome but, as you see, -at least as of today it is not possible. Constant autoloading in Rails is -implemented with `Module#const_missing`, and that's why it has its own contract, -documented in this guide. - - -Common Gotchas --------------- - -### Nesting and Qualified Constants - -Let's consider - -```ruby -module Admin - class UsersController < ApplicationController - def index - @users = User.all + # Constantizes all types present in the database. There might be more on + # disk, but that does not matter in practice as far as the STI API is + # concerned. + # + # Assumes store_full_sti_class is true, the default. + def preload_sti + types_in_db = \ + base_class. + select(inheritance_column). + distinct. + pluck(inheritance_column). + compact. + each(&:constantize) + + types_in_db.each do |type| + logger.debug("Preloading STI type #{type}") + type.constantize + end + + self.preloaded = true + end end end end ``` -and +and then include it in the STI root classes of your project: ```ruby -class Admin::UsersController < ApplicationController - def index - @users = User.all - end -end -``` - -To resolve `User` Ruby checks `Admin` in the former case, but it does not in -the latter because it does not belong to the nesting (see [Nesting](#nesting) -and [Resolution Algorithms](#resolution-algorithms)). - -Unfortunately Rails autoloading does not know the nesting in the spot where the -constant was missing and so it is not able to act as Ruby would. In particular, -`Admin::User` will get autoloaded in either case. - -Albeit qualified constants with `class` and `module` keywords may technically -work with autoloading in some cases, it is preferable to use relative constants -instead: +# app/models/shape.rb +require "sti_preload" -```ruby -module Admin - class UsersController < ApplicationController - def index - @users = User.all - end - end +class Shape < ApplicationRecord + include StiPreload # Only in the root class. end -``` -### Autoloading and STI - -Single Table Inheritance (STI) is a feature of Active Record that enables -storing a hierarchy of models in one single table. The API of such models is -aware of the hierarchy and encapsulates some common needs. For example, given -these classes: - -```ruby # app/models/polygon.rb -class Polygon < ApplicationRecord +class Polygon < Shape end # app/models/triangle.rb class Triangle < Polygon end - -# app/models/rectangle.rb -class Rectangle < Polygon -end -``` - -`Triangle.create` creates a row that represents a triangle, and -`Rectangle.create` creates a row that represents a rectangle. If `id` is the -ID of an existing record, `Polygon.find(id)` returns an object of the correct -type. - -Methods that operate on collections are also aware of the hierarchy. For -example, `Polygon.all` returns all the records of the table, because all -rectangles and triangles are polygons. Active Record takes care of returning -instances of their corresponding class in the result set. - -Types are autoloaded as needed. For example, if `Polygon.first` is a rectangle -and `Rectangle` has not yet been loaded, Active Record autoloads it and the -record is correctly instantiated. - -All good, but if instead of performing queries based on the root class we need -to work on some subclass, things get interesting. - -While working with `Polygon` you do not need to be aware of all its descendants, -because anything in the table is by definition a polygon, but when working with -subclasses Active Record needs to be able to enumerate the types it is looking -for. Let's see an example. - -`Rectangle.all` only loads rectangles by adding a type constraint to the query: - -```sql -SELECT "polygons".* FROM "polygons" -WHERE "polygons"."type" IN ("Rectangle") -``` - -Let's introduce now a subclass of `Rectangle`: - -```ruby -# app/models/square.rb -class Square < Rectangle -end -``` - -`Rectangle.all` should now return rectangles **and** squares: - -```sql -SELECT "polygons".* FROM "polygons" -WHERE "polygons"."type" IN ("Rectangle", "Square") -``` - -But there's a caveat here: How does Active Record know that the class `Square` -exists at all? - -Even if the file `app/models/square.rb` exists and defines the `Square` class, -if no code yet used that class, `Rectangle.all` issues the query - -```sql -SELECT "polygons".* FROM "polygons" -WHERE "polygons"."type" IN ("Rectangle") -``` - -That is not a bug, the query includes all *known* descendants of `Rectangle`. - -A way to ensure this works correctly regardless of the order of execution is to -manually load the direct subclasses at the bottom of the file that defines each -intermediate class: - -```ruby -# app/models/rectangle.rb -class Rectangle < Polygon -end -require_dependency 'square' -``` - -This needs to happen for every intermediate (non-root and non-leaf) class. The -root class does not scope the query by type, and therefore does not necessarily -have to know all its descendants. - -### Autoloading and `require` - -Files defining constants to be autoloaded should never be `require`d: - -```ruby -require 'user' # DO NOT DO THIS - -class UsersController < ApplicationController - ... -end -``` - -There are two possible gotchas here in development mode: - -1. If `User` is autoloaded before reaching the `require`, `app/models/user.rb` -runs again because `load` does not update `$LOADED_FEATURES`. - -2. If the `require` runs first Rails does not mark `User` as an autoloaded -constant and changes to `app/models/user.rb` aren't reloaded. - -Just follow the flow and use constant autoloading always, never mix -autoloading and `require`. As a last resort, if some file absolutely needs to -load a certain file use `require_dependency` to play nice with constant -autoloading. This option is rarely needed in practice, though. - -Of course, using `require` in autoloaded files to load ordinary 3rd party -libraries is fine, and Rails is able to distinguish their constants, they are -not marked as autoloaded. - -### Autoloading and Initializers - -Consider this assignment in `config/initializers/set_auth_service.rb`: - -```ruby -AUTH_SERVICE = if Rails.env.production? - RealAuthService -else - MockedAuthService -end -``` - -The purpose of this setup would be that the application uses the class that -corresponds to the environment via `AUTH_SERVICE`. In development mode -`MockedAuthService` gets autoloaded when the initializer runs. Let's suppose -we do some requests, change its implementation, and hit the application again. -To our surprise the changes are not reflected. Why? - -As [we saw earlier](#constant-reloading), Rails removes autoloaded constants, -but `AUTH_SERVICE` stores the original class object. Stale, non-reachable -using the original constant, but perfectly functional. - -The following code summarizes the situation: - -```ruby -class C - def quack - 'quack!' - end -end - -X = C -Object.instance_eval { remove_const(:C) } -X.new.quack # => quack! -X.name # => C -C # => uninitialized constant C (NameError) -``` - -Because of that, it is not a good idea to autoload constants on application -initialization. - -In the case above we could implement a dynamic access point: - -```ruby -# app/models/auth_service.rb -class AuthService - if Rails.env.production? - def self.instance - RealAuthService - end - else - def self.instance - MockedAuthService - end - end -end -``` - -and have the application use `AuthService.instance` instead. `AuthService` -would be loaded on demand and be autoload-friendly. - -### `require_dependency` and Initializers - -As we saw before, `require_dependency` loads files in an autoloading-friendly -way. Normally, though, such a call does not make sense in an initializer. - -One could think about doing some [`require_dependency`](#require-dependency) -calls in an initializer to make sure certain constants are loaded upfront, for -example as an attempt to address the [gotcha with STIs](#autoloading-and-sti). - -Problem is, in development mode [autoloaded constants are wiped](#constant-reloading) -if there is any relevant change in the file system. If that happens then -we are in the very same situation the initializer wanted to avoid! - -Calls to `require_dependency` have to be strategically written in autoloaded -spots. - -### When Constants aren't Missed - -#### Relative References - -Let's consider a flight simulator. The application has a default flight model - -```ruby -# app/models/flight_model.rb -class FlightModel -end -``` - -that can be overridden by each airplane, for instance - -```ruby -# app/models/bell_x1/flight_model.rb -module BellX1 - class FlightModel < FlightModel - end -end - -# app/models/bell_x1/aircraft.rb -module BellX1 - class Aircraft - def initialize - @flight_model = FlightModel.new - end - end -end -``` - -The initializer wants to create a `BellX1::FlightModel` and nesting has -`BellX1`, that looks good. But if the default flight model is loaded and the -one for the Bell-X1 is not, the interpreter is able to resolve the top-level -`FlightModel` and autoloading is thus not triggered for `BellX1::FlightModel`. - -That code depends on the execution path. - -These kind of ambiguities can often be resolved using qualified constants: - -```ruby -module BellX1 - class Plane - def flight_model - @flight_model ||= BellX1::FlightModel.new - end - end -end -``` - -Also, `require_dependency` is a solution: - -```ruby -require_dependency 'bell_x1/flight_model' - -module BellX1 - class Plane - def flight_model - @flight_model ||= FlightModel.new - end - end -end -``` - -#### Qualified References - -WARNING. This gotcha is only possible in Ruby < 2.5. - -Given - -```ruby -# app/models/hotel.rb -class Hotel -end - -# app/models/image.rb -class Image -end - -# app/models/hotel/image.rb -class Hotel - class Image < Image - end -end -``` - -the expression `Hotel::Image` is ambiguous because it depends on the execution -path. - -As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks -up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has -been loaded but `app/models/hotel/image.rb` hasn't, Ruby does not find `Image` -in `Hotel`, but it does in `Object`: - -``` -$ rails r 'Image; p Hotel::Image' 2>/dev/null -Image # NOT Hotel::Image! -``` - -The code evaluating `Hotel::Image` needs to make sure -`app/models/hotel/image.rb` has been loaded, possibly with -`require_dependency`. - -In these cases the interpreter issues a warning though: - -``` -warning: toplevel constant Image referenced by Hotel::Image -``` - -This surprising constant resolution can be observed with any qualifying class: - -``` -2.1.5 :001 > String::Array -(irb):1: warning: toplevel constant Array referenced by String::Array - => Array -``` - -WARNING. To find this gotcha the qualifying namespace has to be a class, -`Object` is not an ancestor of modules. - -### Autoloading within Singleton Classes - -Let's suppose we have these class definitions: - -```ruby -# app/models/hotel/services.rb -module Hotel - class Services - end -end - -# app/models/hotel/geo_location.rb -module Hotel - class GeoLocation - class << self - Services - end - end -end ``` -If `Hotel::Services` is known by the time `app/models/hotel/geo_location.rb` -is being loaded, `Services` is resolved by Ruby because `Hotel` belongs to the -nesting when the singleton class of `Hotel::GeoLocation` is opened. +Rails.autoloaders +----------------- -But if `Hotel::Services` is not known, Rails is not able to autoload it, the -application raises `NameError`. - -The reason is that autoloading is triggered for the singleton class, which is -anonymous, and as [we saw before](#generic-procedure), Rails only checks the -top-level namespace in that edge case. - -An easy solution to this caveat is to qualify the constant: +The Zeitwerk instances managing your application are availabe at ```ruby -module Hotel - class GeoLocation - class << self - Hotel::Services - end - end -end +Rails.autoloaders.main +Rails.autoloaders.once ``` -### Autoloading in `BasicObject` - -Direct descendants of `BasicObject` do not have `Object` among their ancestors -and cannot resolve top-level constants: - -```ruby -class C < BasicObject - String # NameError: uninitialized constant C::String -end -``` +The former is the main one. The latter is there mostly for backwards compatibily reasons, in case the application has something in `config.autoload_once_paths` (this is discouraged nowadays). -When autoloading is involved that plot has a twist. Let's consider: +You can check if `zeitwerk` mode is enabled with ```ruby -class C < BasicObject - def user - User # WRONG - end -end +Rails.autoloaders.zeitwerk_enabled? ``` -Since Rails checks the top-level namespace `User` gets autoloaded just fine the -first time the `user` method is invoked. You only get the exception if the -`User` constant is known at that point, in particular in a *second* call to -`user`: - -```ruby -c = C.new -c.user # surprisingly fine, User -c.user # NameError: uninitialized constant C::User -``` - -because it detects that a parent namespace already has the constant (see [Qualified -References](#autoloading-algorithms-qualified-references)). - -As with pure Ruby, within the body of a direct descendant of `BasicObject` use -always absolute constant paths: - -```ruby -class C < BasicObject - ::String # RIGHT - - def user - ::User # RIGHT - end -end -``` - -### Autoloading in the Test Environment - -When configuring the `test` environment for autoloading you might consider multiple factors. - -For example it might be worth running your tests with an identical setup to production (`config.eager_load = true`, `config.cache_classes = true`) in order to catch any problems before they hit production (this is compensation for the lack of dev-prod parity). However this will slow down the boot time for individual tests on a dev machine (and is not immediately compatible with spring see below). So one possibility is to do this on a -[CI](https://en.wikipedia.org/wiki/Continuous_integration) machine only (which should run without spring). - -On a development machine you can then have your tests running with whatever is fastest (ideally `config.eager_load = false`). - -With the [Spring](https://github.com/rails/spring) pre-loader (included with new Rails apps), you ideally keep `config.eager_load = false` as per development. Sometimes you may end up with a hybrid configuration (`config.eager_load = true`, `config.cache_classes = true` AND `config.enable_dependency_loading = true`), see [spring issue](https://github.com/rails/spring/issues/519#issuecomment-348324369). However it might be simpler to keep the same configuration as development, and work out whatever it is that is causing autoloading to fail (perhaps by the results of your CI test results). - -Occasionally you may need to explicitly eager_load by using `Rails -.application.eager_load!` in the setup of your tests -- this might occur if your [tests involve multithreading](https://stackoverflow.com/questions/25796409/in-rails-how-can-i-eager-load-all-code-before-a-specific-rspec-test). - -## Troubleshooting - -### Tracing Autoloads - -Active Support is able to report constants as they are autoloaded. To enable these traces in a Rails application, put the following two lines in some initializer: - -```ruby -ActiveSupport::Dependencies.logger = Rails.logger -ActiveSupport::Dependencies.verbose = true -``` - -### Where is a Given Autoload Triggered? +Opting Out +---------- -If constant `Foo` is being autoloaded, and you'd like to know where is that autoload coming from, just throw +Applications can load Rails 6 defaults and still use the classic autoloader this way: ```ruby -puts caller +# config/application.rb +config.load_defaults "6.x" +config.autoloader = :classic ``` -at the top of `foo.rb` and inspect the printed stack trace. - -### Which Constants Have Been Autoloaded? - -At any given time, - -```ruby -ActiveSupport::Dependencies.autoloaded_constants -``` +That may be handy if upgrading to Rails 6 in different phases, but classic mode is discouraged for new applications. -has the collection of constants that have been autoloaded so far. +`zeitwerk` mode is not available in versions of Rails previous to 6.0. diff --git a/guides/source/autoloading_and_reloading_constants_classic_mode.md b/guides/source/autoloading_and_reloading_constants_classic_mode.md new file mode 100644 index 0000000000..d0d9e076d6 --- /dev/null +++ b/guides/source/autoloading_and_reloading_constants_classic_mode.md @@ -0,0 +1,1351 @@ +**DO NOT READ THIS FILE ON GITHUB, GUIDES ARE PUBLISHED ON https://guides.rubyonrails.org.** + +Autoloading and Reloading Constants (Classic Mode) +================================================== + +This guide documents how constant autoloading and reloading works in `classic` mode. + +After reading this guide, you will know: + +* Key aspects of Ruby constants +* What are the `autoload_paths` and how does eager loading work in production? +* How constant autoloading works +* What is `require_dependency` +* How constant reloading works +* Solutions to common autoloading gotchas + +-------------------------------------------------------------------------------- + + +Introduction +------------ + +INFO. This guide documents autoloading in `classic` mode, which is the traditional one. If you'd like to read about `zeiwerk` mode instead, the new one in Rails 6, please check [Autoloading and Reloading Constants (Zeitwerk Mode)](autoloading_and_reloading_constants.html). + +Ruby on Rails allows applications to be written as if their code was preloaded. + +In a normal Ruby program classes need to load their dependencies: + +```ruby +require 'application_controller' +require 'post' + +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +Our Rubyist instinct quickly sees some redundancy in there: If classes were +defined in files matching their name, couldn't their loading be automated +somehow? We could save scanning the file for dependencies, which is brittle. + +Moreover, `Kernel#require` loads files once, but development is much more smooth +if code gets refreshed when it changes without restarting the server. It would +be nice to be able to use `Kernel#load` in development, and `Kernel#require` in +production. + +Indeed, those features are provided by Ruby on Rails, where we just write + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +This guide documents how that works. + + +Constants Refresher +------------------- + +While constants are trivial in most programming languages, they are a rich +topic in Ruby. + +It is beyond the scope of this guide to document Ruby constants, but we are +nevertheless going to highlight a few key topics. Truly grasping the following +sections is instrumental to understanding constant autoloading and reloading. + +### Nesting + +Class and module definitions can be nested to create namespaces: + +```ruby +module XML + class SAXParser + # (1) + end +end +``` + +The *nesting* at any given place is the collection of enclosing nested class and +module objects outwards. The nesting at any given place can be inspected with +`Module.nesting`. For example, in the previous example, the nesting at +(1) is + +```ruby +[XML::SAXParser, XML] +``` + +It is important to understand that the nesting is composed of class and module +*objects*, it has nothing to do with the constants used to access them, and is +also unrelated to their names. + +For instance, while this definition is similar to the previous one: + +```ruby +class XML::SAXParser + # (2) +end +``` + +the nesting in (2) is different: + +```ruby +[XML::SAXParser] +``` + +`XML` does not belong to it. + +We can see in this example that the name of a class or module that belongs to a +certain nesting does not necessarily correlate with the namespaces at the spot. + +Even more, they are totally independent, take for instance + +```ruby +module X + module Y + end +end + +module A + module B + end +end + +module X::Y + module A::B + # (3) + end +end +``` + +The nesting in (3) consists of two module objects: + +```ruby +[A::B, X::Y] +``` + +So, it not only doesn't end in `A`, which does not even belong to the nesting, +but it also contains `X::Y`, which is independent from `A::B`. + +The nesting is an internal stack maintained by the interpreter, and it gets +modified according to these rules: + +* The class object following a `class` keyword gets pushed when its body is +executed, and popped after it. + +* The module object following a `module` keyword gets pushed when its body is +executed, and popped after it. + +* A singleton class opened with `class << object` gets pushed, and popped later. + +* When `instance_eval` is called using a string argument, +the singleton class of the receiver is pushed to the nesting of the eval'ed +code. When `class_eval` or `module_eval` is called using a string argument, +the receiver is pushed to the nesting of the eval'ed code. + +* The nesting at the top-level of code interpreted by `Kernel#load` is empty +unless the `load` call receives a true value as second argument, in which case +a newly created anonymous module is pushed by Ruby. + +It is interesting to observe that blocks do not modify the stack. In particular +the blocks that may be passed to `Class.new` and `Module.new` do not get the +class or module being defined pushed to their nesting. That's one of the +differences between defining classes and modules in one way or another. + +### Class and Module Definitions are Constant Assignments + +Let's suppose the following snippet creates a class (rather than reopening it): + +```ruby +class C +end +``` + +Ruby creates a constant `C` in `Object` and stores in that constant a class +object. The name of the class instance is "C", a string, named after the +constant. + +That is, + +```ruby +class Project < ApplicationRecord +end +``` + +performs a constant assignment equivalent to + +```ruby +Project = Class.new(ApplicationRecord) +``` + +including setting the name of the class as a side-effect: + +```ruby +Project.name # => "Project" +``` + +Constant assignment has a special rule to make that happen: if the object +being assigned is an anonymous class or module, Ruby sets the object's name to +the name of the constant. + +INFO. From then on, what happens to the constant and the instance does not +matter. For example, the constant could be deleted, the class object could be +assigned to a different constant, be stored in no constant anymore, etc. Once +the name is set, it doesn't change. + +Similarly, module creation using the `module` keyword as in + +```ruby +module Admin +end +``` + +performs a constant assignment equivalent to + +```ruby +Admin = Module.new +``` + +including setting the name as a side-effect: + +```ruby +Admin.name # => "Admin" +``` + +WARNING. The execution context of a block passed to `Class.new` or `Module.new` +is not entirely equivalent to the one of the body of the definitions using the +`class` and `module` keywords. But both idioms result in the same constant +assignment. + +Thus, an informal expression like "the `String` class" technically means the +class object stored in the constant called "String". That constant, in turn, +belongs to the class object stored in the constant called "Object". + +`String` is an ordinary constant, and everything related to them such as +resolution algorithms applies to it. + +Likewise, in the controller + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +`Post` is not syntax for a class. Rather, `Post` is a regular Ruby constant. If +all is good, the constant is evaluated to an object that responds to `all`. + +That is why we talk about *constant* autoloading, Rails has the ability to +load constants on the fly. + +### Constants are Stored in Modules + +Constants belong to modules in a very literal sense. Classes and modules have +a constant table; think of it as a hash table. + +Let's analyze an example to really understand what that means. While common +abuses of language like "the `String` class" are convenient, the exposition is +going to be precise here for didactic purposes. + +Let's consider the following module definition: + +```ruby +module Colors + RED = '0xff0000' +end +``` + +First, when the `module` keyword is processed, the interpreter creates a new +entry in the constant table of the class object stored in the `Object` constant. +Said entry associates the name "Colors" to a newly created module object. +Furthermore, the interpreter sets the name of the new module object to be the +string "Colors". + +Later, when the body of the module definition is interpreted, a new entry is +created in the constant table of the module object stored in the `Colors` +constant. That entry maps the name "RED" to the string "0xff0000". + +In particular, `Colors::RED` is totally unrelated to any other `RED` constant +that may live in any other class or module object. If there were any, they +would have separate entries in their respective constant tables. + +Pay special attention in the previous paragraphs to the distinction between +class and module objects, constant names, and value objects associated to them +in constant tables. + +### Resolution Algorithms + +#### Resolution Algorithm for Relative Constants + +At any given place in the code, let's define *cref* to be the first element of +the nesting if it is not empty, or `Object` otherwise. + +Without getting too much into the details, the resolution algorithm for relative +constant references goes like this: + +1. If the nesting is not empty the constant is looked up in its elements and in +order. The ancestors of those elements are ignored. + +2. If not found, then the algorithm walks up the ancestor chain of the cref. + +3. If not found and the cref is a module, the constant is looked up in `Object`. + +4. If not found, `const_missing` is invoked on the cref. The default +implementation of `const_missing` raises `NameError`, but it can be overridden. + +Rails autoloading **does not emulate this algorithm**, but its starting point is +the name of the constant to be autoloaded, and the cref. See more in [Relative +References](#autoloading-algorithms-relative-references). + +#### Resolution Algorithm for Qualified Constants + +Qualified constants look like this: + +```ruby +Billing::Invoice +``` + +`Billing::Invoice` is composed of two constants: `Billing` is relative and is +resolved using the algorithm of the previous section. + +INFO. Leading colons would make the first segment absolute rather than +relative: `::Billing::Invoice`. That would force `Billing` to be looked up +only as a top-level constant. + +`Invoice` on the other hand is qualified by `Billing` and we are going to see +its resolution next. Let's define *parent* to be that qualifying class or module +object, that is, `Billing` in the example above. The algorithm for qualified +constants goes like this: + +1. The constant is looked up in the parent and its ancestors. In Ruby >= 2.5, +`Object` is skipped if present among the ancestors. `Kernel` and `BasicObject` +are still checked though. + +2. If the lookup fails, `const_missing` is invoked in the parent. The default +implementation of `const_missing` raises `NameError`, but it can be overridden. + +INFO. In Ruby < 2.5 `String::Hash` evaluates to `Hash` and the interpreter +issues a warning: "toplevel constant Hash referenced by String::Hash". Starting +with 2.5, `String::Hash` raises `NameError` because `Object` is skipped. + +As you see, this algorithm is simpler than the one for relative constants. In +particular, the nesting plays no role here, and modules are not special-cased, +if neither they nor their ancestors have the constants, `Object` is **not** +checked. + +Rails autoloading **does not emulate this algorithm**, but its starting point is +the name of the constant to be autoloaded, and the parent. See more in +[Qualified References](#autoloading-algorithms-qualified-references). + + +Vocabulary +---------- + +### Parent Namespaces + +Given a string with a constant path we define its *parent namespace* to be the +string that results from removing its rightmost segment. + +For example, the parent namespace of the string "A::B::C" is the string "A::B", +the parent namespace of "A::B" is "A", and the parent namespace of "A" is "". + +The interpretation of a parent namespace when thinking about classes and modules +is tricky though. Let's consider a module M named "A::B": + +* The parent namespace, "A", may not reflect nesting at a given spot. + +* The constant `A` may no longer exist, some code could have removed it from +`Object`. + +* If `A` exists, the class or module that was originally in `A` may not be there +anymore. For example, if after a constant removal there was another constant +assignment there would generally be a different object in there. + +* In such case, it could even happen that the reassigned `A` held a new class or +module called also "A"! + +* In the previous scenarios M would no longer be reachable through `A::B` but +the module object itself could still be alive somewhere and its name would +still be "A::B". + +The idea of a parent namespace is at the core of the autoloading algorithms +and helps explain and understand their motivation intuitively, but as you see +that metaphor leaks easily. Given an edge case to reason about, take always into +account that by "parent namespace" the guide means exactly that specific string +derivation. + +### Loading Mechanism + +Rails autoloads files with `Kernel#load` when `config.cache_classes` is false, +the default in development mode, and with `Kernel#require` otherwise, the +default in production mode. + +`Kernel#load` allows Rails to execute files more than once if [constant +reloading](#constant-reloading) is enabled. + +This guide uses the word "load" freely to mean a given file is interpreted, but +the actual mechanism can be `Kernel#load` or `Kernel#require` depending on that +flag. + + +Autoloading Availability +------------------------ + +Rails is always able to autoload provided its environment is in place. For +example the `runner` command autoloads: + +``` +$ rails runner 'p User.column_names' +["id", "email", "created_at", "updated_at"] +``` + +The console autoloads, the test suite autoloads, and of course the application +autoloads. + +By default, Rails eager loads the application files when it boots in production +mode, so most of the autoloading going on in development does not happen. But +autoloading may still be triggered during eager loading. + +For example, given + +```ruby +class BeachHouse < House +end +``` + +if `House` is still unknown when `app/models/beach_house.rb` is being eager +loaded, Rails autoloads it. + + +autoload_paths and eager_load_paths +----------------------------------- + +As you probably know, when `require` gets a relative file name: + +```ruby +require 'erb' +``` + +Ruby looks for the file in the directories listed in `$LOAD_PATH`. That is, Ruby +iterates over all its directories and for each one of them checks whether they +have a file called "erb.rb", or "erb.so", or "erb.o", or "erb.dll". If it finds +any of them, the interpreter loads it and ends the search. Otherwise, it tries +again in the next directory of the list. If the list gets exhausted, `LoadError` +is raised. + +We are going to cover how constant autoloading works in more detail later, but +the idea is that when a constant like `Post` is hit and missing, if there's a +`post.rb` file for example in `app/models` Rails is going to find it, evaluate +it, and have `Post` defined as a side-effect. + +All right, Rails has a collection of directories similar to `$LOAD_PATH` in which +to look up `post.rb`. That collection is called `autoload_paths` and by +default it contains: + +* All subdirectories of `app` in the application and engines present at boot + time. For example, `app/controllers`. They do not need to be the default + ones, any custom directories like `app/workers` belong automatically to + `autoload_paths`. + +* Any existing second level directories called `app/*/concerns` in the + application and engines. + +* The directory `test/mailers/previews`. + +`eager_load_paths` is initially the `app` paths above + +How files are autoloaded depends on `eager_load` and `cache_classes` config settings which typically vary in development, production, and test modes: + + * In **development**, you want quicker startup with incremental loading of application code. So `eager_load` should be set to `false`, and Rails will autoload files as needed (see [Autoloading Algorithms](#autoloading-algorithms) below) -- and then reload them when they change (see [Constant Reloading](#constant-reloading) below). + * In **production**, however, you want consistency and thread-safety and can live with a longer boot time. So `eager_load` is set to `true`, and then during boot (before the app is ready to receive requests) Rails loads all files in the `eager_load_paths` and then turns off auto loading (NB: autoloading may be needed during eager loading). Not autoloading after boot is a `good thing`, as autoloading can cause the app to be have thread-safety problems. + * In **test**, for speed of execution (of individual tests) `eager_load` is `false`, so Rails follows development behaviour. + +What is described above are the defaults with a newly generated Rails app. There are multiple ways this can be configured differently (see [Configuring Rails Applications](configuring.html#rails-general-configuration). +). But using `autoload_paths` on its own in the past (before Rails 5) developers might configure `autoload_paths` to add in extra locations (e.g. `lib` which used to be an autoload path list years ago, but no longer is). However this is now discouraged for most purposes, as it is likely to lead to production-only errors. It is possible to add new locations to both `config.eager_load_paths` and `config.autoload_paths` but use at your own risk. + +See also [Autoloading in the Test Environment](#autoloading-in-the-test-environment). + +`config.autoload_paths` is not changeable from environment-specific configuration files. + +The value of `autoload_paths` can be inspected. In a just-generated application +it is (edited): + +``` +$ rails r 'puts ActiveSupport::Dependencies.autoload_paths' +.../app/assets +.../app/channels +.../app/controllers +.../app/controllers/concerns +.../app/helpers +.../app/jobs +.../app/mailers +.../app/models +.../app/models/concerns +.../activestorage/app/assets +.../activestorage/app/controllers +.../activestorage/app/javascript +.../activestorage/app/jobs +.../activestorage/app/models +.../actioncable/app/assets +.../actionview/app/assets +.../test/mailers/previews +``` + +INFO. `autoload_paths` is computed and cached during the initialization process. +The application needs to be restarted to reflect any changes in the directory +structure. + + +Autoloading Algorithms +---------------------- + +### Relative References + +A relative constant reference may appear in several places, for example, in + +```ruby +class PostsController < ApplicationController + def index + @posts = Post.all + end +end +``` + +all three constant references are relative. + +#### Constants after the `class` and `module` Keywords + +Ruby performs a lookup for the constant that follows a `class` or `module` +keyword because it needs to know if the class or module is going to be created +or reopened. + +If the constant is not defined at that point it is not considered to be a +missing constant, autoloading is **not** triggered. + +So, in the previous example, if `PostsController` is not defined when the file +is interpreted Rails autoloading is not going to be triggered, Ruby will just +define the controller. + +#### Top-Level Constants + +On the contrary, if `ApplicationController` is unknown, the constant is +considered missing and an autoload is going to be attempted by Rails. + +In order to load `ApplicationController`, Rails iterates over `autoload_paths`. +First it checks if `app/assets/application_controller.rb` exists. If it does not, +which is normally the case, it continues and finds +`app/controllers/application_controller.rb`. + +If the file defines the constant `ApplicationController` all is fine, otherwise +`LoadError` is raised: + +``` +unable to autoload constant ApplicationController, expected +<full path to application_controller.rb> to define it (LoadError) +``` + +INFO. Rails does not require the value of autoloaded constants to be a class or +module object. For example, if the file `app/models/max_clients.rb` defines +`MAX_CLIENTS = 100` autoloading `MAX_CLIENTS` works just fine. + +#### Namespaces + +Autoloading `ApplicationController` looks directly under the directories of +`autoload_paths` because the nesting in that spot is empty. The situation of +`Post` is different, the nesting in that line is `[PostsController]` and support +for namespaces comes into play. + +The basic idea is that given + +```ruby +module Admin + class BaseController < ApplicationController + @@all_roles = Role.all + end +end +``` + +to autoload `Role` we are going to check if it is defined in the current or +parent namespaces, one at a time. So, conceptually we want to try to autoload +any of + +``` +Admin::BaseController::Role +Admin::Role +Role +``` + +in that order. That's the idea. To do so, Rails looks in `autoload_paths` +respectively for file names like these: + +``` +admin/base_controller/role.rb +admin/role.rb +role.rb +``` + +modulus some additional directory lookups we are going to cover soon. + +INFO. `'Constant::Name'.underscore` gives the relative path without extension of +the file name where `Constant::Name` is expected to be defined. + +Let's see how Rails autoloads the `Post` constant in the `PostsController` +above assuming the application has a `Post` model defined in +`app/models/post.rb`. + +First it checks for `posts_controller/post.rb` in `autoload_paths`: + +``` +app/assets/posts_controller/post.rb +app/controllers/posts_controller/post.rb +app/helpers/posts_controller/post.rb +... +test/mailers/previews/posts_controller/post.rb +``` + +Since the lookup is exhausted without success, a similar search for a directory +is performed, we are going to see why in the [next section](#automatic-modules): + +``` +app/assets/posts_controller/post +app/controllers/posts_controller/post +app/helpers/posts_controller/post +... +test/mailers/previews/posts_controller/post +``` + +If all those attempts fail, then Rails starts the lookup again in the parent +namespace. In this case only the top-level remains: + +``` +app/assets/post.rb +app/controllers/post.rb +app/helpers/post.rb +app/mailers/post.rb +app/models/post.rb +``` + +A matching file is found in `app/models/post.rb`. The lookup stops there and the +file is loaded. If the file actually defines `Post` all is fine, otherwise +`LoadError` is raised. + +### Qualified References + +When a qualified constant is missing Rails does not look for it in the parent +namespaces. But there is a caveat: when a constant is missing, Rails is +unable to tell if the trigger was a relative reference or a qualified one. + +For example, consider + +```ruby +module Admin + User +end +``` + +and + +```ruby +Admin::User +``` + +If `User` is missing, in either case all Rails knows is that a constant called +"User" was missing in a module called "Admin". + +If there is a top-level `User` Ruby would resolve it in the former example, but +wouldn't in the latter. In general, Rails does not emulate the Ruby constant +resolution algorithms, but in this case it tries using the following heuristic: + +> If none of the parent namespaces of the class or module has the missing +> constant then Rails assumes the reference is relative. Otherwise qualified. + +For example, if this code triggers autoloading + +```ruby +Admin::User +``` + +and the `User` constant is already present in `Object`, it is not possible that +the situation is + +```ruby +module Admin + User +end +``` + +because otherwise Ruby would have resolved `User` and no autoloading would have +been triggered in the first place. Thus, Rails assumes a qualified reference and +considers the file `admin/user.rb` and directory `admin/user` to be the only +valid options. + +In practice, this works quite well as long as the nesting matches all parent +namespaces respectively and the constants that make the rule apply are known at +that time. + +However, autoloading happens on demand. If by chance the top-level `User` was +not yet loaded, then Rails assumes a relative reference by contract. + +Naming conflicts of this kind are rare in practice, but if one occurs, +`require_dependency` provides a solution by ensuring that the constant needed +to trigger the heuristic is defined in the conflicting place. + +### Automatic Modules + +When a module acts as a namespace, Rails does not require the application to +define a file for it, a directory matching the namespace is enough. + +Suppose an application has a back office whose controllers are stored in +`app/controllers/admin`. If the `Admin` module is not yet loaded when +`Admin::UsersController` is hit, Rails needs first to autoload the constant +`Admin`. + +If `autoload_paths` has a file called `admin.rb` Rails is going to load that +one, but if there's no such file and a directory called `admin` is found, Rails +creates an empty module and assigns it to the `Admin` constant on the fly. + +### Generic Procedure + +Relative references are reported to be missing in the cref where they were hit, +and qualified references are reported to be missing in their parent (see +[Resolution Algorithm for Relative +Constants](#resolution-algorithm-for-relative-constants) at the beginning of +this guide for the definition of *cref*, and [Resolution Algorithm for Qualified +Constants](#resolution-algorithm-for-qualified-constants) for the definition of +*parent*). + +The procedure to autoload constant `C` in an arbitrary situation is as follows: + +``` +if the class or module in which C is missing is Object + let ns = '' +else + let M = the class or module in which C is missing + + if M is anonymous + let ns = '' + else + let ns = M.name + end +end + +loop do + # Look for a regular file. + for dir in autoload_paths + if the file "#{dir}/#{ns.underscore}/c.rb" exists + load/require "#{dir}/#{ns.underscore}/c.rb" + + if C is now defined + return + else + raise LoadError + end + end + end + + # Look for an automatic module. + for dir in autoload_paths + if the directory "#{dir}/#{ns.underscore}/c" exists + if ns is an empty string + let C = Module.new in Object and return + else + let C = Module.new in ns.constantize and return + end + end + end + + if ns is empty + # We reached the top-level without finding the constant. + raise NameError + else + if C exists in any of the parent namespaces + # Qualified constants heuristic. + raise NameError + else + # Try again in the parent namespace. + let ns = the parent namespace of ns and retry + end + end +end +``` + + +require_dependency +------------------ + +Constant autoloading is triggered on demand and therefore code that uses a +certain constant may have it already defined or may trigger an autoload. That +depends on the execution path and it may vary between runs. + +There are times, however, in which you want to make sure a certain constant is +known when the execution reaches some code. `require_dependency` provides a way +to load a file using the current [loading mechanism](#loading-mechanism), and +keeping track of constants defined in that file as if they were autoloaded to +have them reloaded as needed. + +`require_dependency` is rarely needed, but see a couple of use cases in +[Autoloading and STI](#autoloading-and-sti) and [When Constants aren't +Triggered](#when-constants-aren-t-missed). + +WARNING. Unlike autoloading, `require_dependency` does not expect the file to +define any particular constant. Exploiting this behavior would be a bad practice +though, file and constant paths should match. + + +Constant Reloading +------------------ + +When `config.cache_classes` is false Rails is able to reload autoloaded +constants. + +For example, if you're in a console session and edit some file behind the +scenes, the code can be reloaded with the `reload!` command: + +``` +> reload! +``` + +When the application runs, code is reloaded when something relevant to this +logic changes. In order to do that, Rails monitors a number of things: + +* `config/routes.rb`. + +* Locales. + +* Ruby files under `autoload_paths`. + +* `db/schema.rb` and `db/structure.sql`. + +If anything in there changes, there is a middleware that detects it and reloads +the code. + +Autoloading keeps track of autoloaded constants. Reloading is implemented by +removing them all from their respective classes and modules using +`Module#remove_const`. That way, when the code goes on, those constants are +going to be unknown again, and files reloaded on demand. + +INFO. This is an all-or-nothing operation, Rails does not attempt to reload only +what changed since dependencies between classes makes that really tricky. +Instead, everything is wiped. + +Common Gotchas +-------------- + +### Nesting and Qualified Constants + +Let's consider + +```ruby +module Admin + class UsersController < ApplicationController + def index + @users = User.all + end + end +end +``` + +and + +```ruby +class Admin::UsersController < ApplicationController + def index + @users = User.all + end +end +``` + +To resolve `User` Ruby checks `Admin` in the former case, but it does not in +the latter because it does not belong to the nesting (see [Nesting](#nesting) +and [Resolution Algorithms](#resolution-algorithms)). + +Unfortunately Rails autoloading does not know the nesting in the spot where the +constant was missing and so it is not able to act as Ruby would. In particular, +`Admin::User` will get autoloaded in either case. + +Albeit qualified constants with `class` and `module` keywords may technically +work with autoloading in some cases, it is preferable to use relative constants +instead: + +```ruby +module Admin + class UsersController < ApplicationController + def index + @users = User.all + end + end +end +``` + +### Autoloading and STI + +Single Table Inheritance (STI) is a feature of Active Record that enables +storing a hierarchy of models in one single table. The API of such models is +aware of the hierarchy and encapsulates some common needs. For example, given +these classes: + +```ruby +# app/models/polygon.rb +class Polygon < ApplicationRecord +end + +# app/models/triangle.rb +class Triangle < Polygon +end + +# app/models/rectangle.rb +class Rectangle < Polygon +end +``` + +`Triangle.create` creates a row that represents a triangle, and +`Rectangle.create` creates a row that represents a rectangle. If `id` is the +ID of an existing record, `Polygon.find(id)` returns an object of the correct +type. + +Methods that operate on collections are also aware of the hierarchy. For +example, `Polygon.all` returns all the records of the table, because all +rectangles and triangles are polygons. Active Record takes care of returning +instances of their corresponding class in the result set. + +Types are autoloaded as needed. For example, if `Polygon.first` is a rectangle +and `Rectangle` has not yet been loaded, Active Record autoloads it and the +record is correctly instantiated. + +All good, but if instead of performing queries based on the root class we need +to work on some subclass, things get interesting. + +While working with `Polygon` you do not need to be aware of all its descendants, +because anything in the table is by definition a polygon, but when working with +subclasses Active Record needs to be able to enumerate the types it is looking +for. Let's see an example. + +`Rectangle.all` only loads rectangles by adding a type constraint to the query: + +```sql +SELECT "polygons".* FROM "polygons" +WHERE "polygons"."type" IN ("Rectangle") +``` + +Let's introduce now a subclass of `Rectangle`: + +```ruby +# app/models/square.rb +class Square < Rectangle +end +``` + +`Rectangle.all` should now return rectangles **and** squares: + +```sql +SELECT "polygons".* FROM "polygons" +WHERE "polygons"."type" IN ("Rectangle", "Square") +``` + +But there's a caveat here: How does Active Record know that the class `Square` +exists at all? + +Even if the file `app/models/square.rb` exists and defines the `Square` class, +if no code yet used that class, `Rectangle.all` issues the query + +```sql +SELECT "polygons".* FROM "polygons" +WHERE "polygons"."type" IN ("Rectangle") +``` + +That is not a bug, the query includes all *known* descendants of `Rectangle`. + +A way to ensure this works correctly regardless of the order of execution is to +manually load the direct subclasses at the bottom of the file that defines each +intermediate class: + +```ruby +# app/models/rectangle.rb +class Rectangle < Polygon +end +require_dependency 'square' +``` + +This needs to happen for every intermediate (non-root and non-leaf) class. The +root class does not scope the query by type, and therefore does not necessarily +have to know all its descendants. + +### Autoloading and `require` + +Files defining constants to be autoloaded should never be `require`d: + +```ruby +require 'user' # DO NOT DO THIS + +class UsersController < ApplicationController + ... +end +``` + +There are two possible gotchas here in development mode: + +1. If `User` is autoloaded before reaching the `require`, `app/models/user.rb` +runs again because `load` does not update `$LOADED_FEATURES`. + +2. If the `require` runs first Rails does not mark `User` as an autoloaded +constant and changes to `app/models/user.rb` aren't reloaded. + +Just follow the flow and use constant autoloading always, never mix +autoloading and `require`. As a last resort, if some file absolutely needs to +load a certain file use `require_dependency` to play nice with constant +autoloading. This option is rarely needed in practice, though. + +Of course, using `require` in autoloaded files to load ordinary 3rd party +libraries is fine, and Rails is able to distinguish their constants, they are +not marked as autoloaded. + +### Autoloading and Initializers + +Consider this assignment in `config/initializers/set_auth_service.rb`: + +```ruby +AUTH_SERVICE = if Rails.env.production? + RealAuthService +else + MockedAuthService +end +``` + +The purpose of this setup would be that the application uses the class that +corresponds to the environment via `AUTH_SERVICE`. In development mode +`MockedAuthService` gets autoloaded when the initializer runs. Let's suppose +we do some requests, change its implementation, and hit the application again. +To our surprise the changes are not reflected. Why? + +As [we saw earlier](#constant-reloading), Rails removes autoloaded constants, +but `AUTH_SERVICE` stores the original class object. Stale, non-reachable +using the original constant, but perfectly functional. + +The following code summarizes the situation: + +```ruby +class C + def quack + 'quack!' + end +end + +X = C +Object.instance_eval { remove_const(:C) } +X.new.quack # => quack! +X.name # => C +C # => uninitialized constant C (NameError) +``` + +Because of that, it is not a good idea to autoload constants on application +initialization. + +In the case above we could implement a dynamic access point: + +```ruby +# app/models/auth_service.rb +class AuthService + if Rails.env.production? + def self.instance + RealAuthService + end + else + def self.instance + MockedAuthService + end + end +end +``` + +and have the application use `AuthService.instance` instead. `AuthService` +would be loaded on demand and be autoload-friendly. + +### `require_dependency` and Initializers + +As we saw before, `require_dependency` loads files in an autoloading-friendly +way. Normally, though, such a call does not make sense in an initializer. + +One could think about doing some [`require_dependency`](#require-dependency) +calls in an initializer to make sure certain constants are loaded upfront, for +example as an attempt to address the [gotcha with STIs](#autoloading-and-sti). + +Problem is, in development mode [autoloaded constants are wiped](#constant-reloading) +if there is any relevant change in the file system. If that happens then +we are in the very same situation the initializer wanted to avoid! + +Calls to `require_dependency` have to be strategically written in autoloaded +spots. + +### When Constants aren't Missed + +#### Relative References + +Let's consider a flight simulator. The application has a default flight model + +```ruby +# app/models/flight_model.rb +class FlightModel +end +``` + +that can be overridden by each airplane, for instance + +```ruby +# app/models/bell_x1/flight_model.rb +module BellX1 + class FlightModel < FlightModel + end +end + +# app/models/bell_x1/aircraft.rb +module BellX1 + class Aircraft + def initialize + @flight_model = FlightModel.new + end + end +end +``` + +The initializer wants to create a `BellX1::FlightModel` and nesting has +`BellX1`, that looks good. But if the default flight model is loaded and the +one for the Bell-X1 is not, the interpreter is able to resolve the top-level +`FlightModel` and autoloading is thus not triggered for `BellX1::FlightModel`. + +That code depends on the execution path. + +These kind of ambiguities can often be resolved using qualified constants: + +```ruby +module BellX1 + class Plane + def flight_model + @flight_model ||= BellX1::FlightModel.new + end + end +end +``` + +Also, `require_dependency` is a solution: + +```ruby +require_dependency 'bell_x1/flight_model' + +module BellX1 + class Plane + def flight_model + @flight_model ||= FlightModel.new + end + end +end +``` + +#### Qualified References + +WARNING. This gotcha is only possible in Ruby < 2.5. + +Given + +```ruby +# app/models/hotel.rb +class Hotel +end + +# app/models/image.rb +class Image +end + +# app/models/hotel/image.rb +class Hotel + class Image < Image + end +end +``` + +the expression `Hotel::Image` is ambiguous because it depends on the execution +path. + +As [we saw before](#resolution-algorithm-for-qualified-constants), Ruby looks +up the constant in `Hotel` and its ancestors. If `app/models/image.rb` has +been loaded but `app/models/hotel/image.rb` hasn't, Ruby does not find `Image` +in `Hotel`, but it does in `Object`: + +``` +$ rails r 'Image; p Hotel::Image' 2>/dev/null +Image # NOT Hotel::Image! +``` + +The code evaluating `Hotel::Image` needs to make sure +`app/models/hotel/image.rb` has been loaded, possibly with +`require_dependency`. + +In these cases the interpreter issues a warning though: + +``` +warning: toplevel constant Image referenced by Hotel::Image +``` + +This surprising constant resolution can be observed with any qualifying class: + +``` +2.1.5 :001 > String::Array +(irb):1: warning: toplevel constant Array referenced by String::Array + => Array +``` + +WARNING. To find this gotcha the qualifying namespace has to be a class, +`Object` is not an ancestor of modules. + +### Autoloading within Singleton Classes + +Let's suppose we have these class definitions: + +```ruby +# app/models/hotel/services.rb +module Hotel + class Services + end +end + +# app/models/hotel/geo_location.rb +module Hotel + class GeoLocation + class << self + Services + end + end +end +``` + +If `Hotel::Services` is known by the time `app/models/hotel/geo_location.rb` +is being loaded, `Services` is resolved by Ruby because `Hotel` belongs to the +nesting when the singleton class of `Hotel::GeoLocation` is opened. + +But if `Hotel::Services` is not known, Rails is not able to autoload it, the +application raises `NameError`. + +The reason is that autoloading is triggered for the singleton class, which is +anonymous, and as [we saw before](#generic-procedure), Rails only checks the +top-level namespace in that edge case. + +An easy solution to this caveat is to qualify the constant: + +```ruby +module Hotel + class GeoLocation + class << self + Hotel::Services + end + end +end +``` + +### Autoloading in `BasicObject` + +Direct descendants of `BasicObject` do not have `Object` among their ancestors +and cannot resolve top-level constants: + +```ruby +class C < BasicObject + String # NameError: uninitialized constant C::String +end +``` + +When autoloading is involved that plot has a twist. Let's consider: + +```ruby +class C < BasicObject + def user + User # WRONG + end +end +``` + +Since Rails checks the top-level namespace `User` gets autoloaded just fine the +first time the `user` method is invoked. You only get the exception if the +`User` constant is known at that point, in particular in a *second* call to +`user`: + +```ruby +c = C.new +c.user # surprisingly fine, User +c.user # NameError: uninitialized constant C::User +``` + +because it detects that a parent namespace already has the constant (see [Qualified +References](#autoloading-algorithms-qualified-references)). + +As with pure Ruby, within the body of a direct descendant of `BasicObject` use +always absolute constant paths: + +```ruby +class C < BasicObject + ::String # RIGHT + + def user + ::User # RIGHT + end +end +``` + +### Autoloading in the Test Environment + +When configuring the `test` environment for autoloading you might consider multiple factors. + +For example it might be worth running your tests with an identical setup to production (`config.eager_load = true`, `config.cache_classes = true`) in order to catch any problems before they hit production (this is compensation for the lack of dev-prod parity). However this will slow down the boot time for individual tests on a dev machine (and is not immediately compatible with spring see below). So one possibility is to do this on a +[CI](https://en.wikipedia.org/wiki/Continuous_integration) machine only (which should run without spring). + +On a development machine you can then have your tests running with whatever is fastest (ideally `config.eager_load = false`). + +With the [Spring](https://github.com/rails/spring) pre-loader (included with new Rails apps), you ideally keep `config.eager_load = false` as per development. Sometimes you may end up with a hybrid configuration (`config.eager_load = true`, `config.cache_classes = true` AND `config.enable_dependency_loading = true`), see [spring issue](https://github.com/rails/spring/issues/519#issuecomment-348324369). However it might be simpler to keep the same configuration as development, and work out whatever it is that is causing autoloading to fail (perhaps by the results of your CI test results). + +Occasionally you may need to explicitly eager_load by using `Rails +.application.eager_load!` in the setup of your tests -- this might occur if your [tests involve multithreading](https://stackoverflow.com/questions/25796409/in-rails-how-can-i-eager-load-all-code-before-a-specific-rspec-test). + +## Troubleshooting + +### Tracing Autoloads + +Active Support is able to report constants as they are autoloaded. To enable these traces in a Rails application, put the following two lines in some initializer: + +```ruby +ActiveSupport::Dependencies.logger = Rails.logger +ActiveSupport::Dependencies.verbose = true +``` + +### Where is a Given Autoload Triggered? + +If constant `Foo` is being autoloaded, and you'd like to know where is that autoload coming from, just throw + +```ruby +puts caller +``` + +at the top of `foo.rb` and inspect the printed stack trace. + +### Which Constants Have Been Autoloaded? + +At any given time, + +```ruby +ActiveSupport::Dependencies.autoloaded_constants +``` + +has the collection of constants that have been autoloaded so far. diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index 67b097f2ae..3f013fff3a 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -51,10 +51,10 @@ For instance, it will not impact low-level caching, that we address ### Page Caching Page caching is a Rails mechanism which allows the request for a generated page -to be fulfilled by the webserver (i.e. Apache or NGINX) without having to go +to be fulfilled by the web server (i.e. Apache or NGINX) without having to go through the entire Rails stack. While this is super fast it can't be applied to every situation (such as pages that need authentication). Also, because the -webserver is serving a file directly from the filesystem you will need to +web server is serving a file directly from the filesystem you will need to implement cache expiration. INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_caching gem](https://github.com/rails/actionpack-page_caching). @@ -63,7 +63,7 @@ INFO: Page Caching has been removed from Rails 4. See the [actionpack-page_cachi Page Caching cannot be used for actions that have before filters - for example, pages that require authentication. This is where Action Caching comes in. Action Caching works like Page Caching except the incoming web request hits the Rails stack so that before filters can be run on it before the cache is served. This allows authentication and other restrictions to be run while still serving the result of the output from a cached copy. -INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](http://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. +INFO: Action Caching has been removed from Rails 4. See the [actionpack-action_caching gem](https://github.com/rails/actionpack-action_caching). See [DHH's key-based cache expiration overview](https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works) for the newly-preferred method. ### Fragment Caching @@ -295,14 +295,14 @@ Consider the following example. An application has a `Product` model with an ins ```ruby class Product < ApplicationRecord def competing_price - Rails.cache.fetch("#{cache_key}/competing_price", expires_in: 12.hours) do + Rails.cache.fetch("#{cache_key_with_version}/competing_price", expires_in: 12.hours) do Competitor::API.find_price(id) end end end ``` -NOTE: Notice that in this example we used the `cache_key_with_version` method, so the resulting cache key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key_with_version` generates a string based on the model's `id` and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key. +NOTE: Notice that in this example we used the `cache_key_with_version` method, so the resulting cache key will be something like `products/233-20140225082222765838000/competing_price`. `cache_key_with_version` generates a string based on the model's class name, `id`, and `updated_at` attributes. This is a common convention and has the benefit of invalidating the cache whenever the product is updated. In general, when you use low-level caching for instance level information, you need to generate a cache key. ### SQL Caching diff --git a/guides/source/command_line.md b/guides/source/command_line.md index bbebf97c3f..4681574edd 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -92,6 +92,28 @@ $ rails new commandsapp Rails will set you up with what seems like a huge amount of stuff for such a tiny command! You've got the entire Rails directory structure now with all the code you need to run our simple application right out of the box. +If you wish to skip some files or components from being generated, you can append the following arguments to your `rails new` command: + +| Argument | Description | +| ----------------------- | ----------------------------------------------------------- | +| `--skip-gemfile` | Don't create a Gemfile | +| `--skip-git` | Skip .gitignore file | +| `--skip-keeps` | Skip source control .keep files | +| `--skip-action-mailer` | Skip Action Mailer files | +| `--skip-action-text` | Skip Action Text gem | +| `--skip-active-record` | Skip Active Record files | +| `--skip-active-storage` | Skip Active Storage files | +| `--skip-puma` | Skip Puma related files | +| `--skip-action-cable` | Skip Action Cable files | +| `--skip-sprockets` | Skip Sprockets files | +| `--skip-spring` | Don't install Spring application preloader | +| `--skip-listen` | Don't generate configuration that depends on the listen gem | +| `--skip-javascript` | Skip JavaScript files | +| `--skip-turbolinks` | Skip turbolinks gem | +| `--skip-test` | Skip test files | +| `--skip-system-test` | Skip system test files | +| `--skip-bootsnap` | Skip bootsnap gem | + ### `rails server` 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. @@ -238,17 +260,20 @@ Usage: ... -Active Record options: - [--migration] # Indicates when to generate migration - # Default: true +ActiveRecord options: + [--migration], [--no-migration] # Indicates when to generate migration + # Default: true ... Description: - Create rails files for model generator. + Stubs out a new model. Pass the model name, either CamelCased or + under_scored, and an optional list of attribute pairs as arguments. + +... ``` -NOTE: For a list of available field types for the `type` parameter, refer to the [API documentation](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_column) for the add_column method for the `SchemaStatements` module. The `index` parameter generates a corresponding index for the column. +NOTE: For a list of available field types for the `type` parameter, refer to the [API documentation](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_column) for the add_column method for the `SchemaStatements` module. The `index` parameter generates a corresponding index for the column. But instead of generating a model directly (which we'll be doing later), let's set up a scaffold. A **scaffold** in Rails is a full set of model, database migration for that model, controller to manipulate it, views to view and manipulate the data, and a test suite for each of the above. @@ -257,7 +282,7 @@ We will set up a simple resource called "HighScore" that will keep track of our ```bash $ rails generate scaffold HighScore game:string score:integer invoke active_record - create db/migrate/20130717151933_create_high_scores.rb + create db/migrate/20190416145729_create_high_scores.rb create app/models/high_score.rb invoke test_unit create test/models/high_score_test.rb @@ -275,20 +300,19 @@ $ rails generate scaffold HighScore game:string score:integer create app/views/high_scores/_form.html.erb invoke test_unit create test/controllers/high_scores_controller_test.rb + create test/system/high_scores_test.rb invoke helper create app/helpers/high_scores_helper.rb + invoke test_unit invoke jbuilder create app/views/high_scores/index.json.jbuilder create app/views/high_scores/show.json.jbuilder - invoke test_unit - create test/system/high_scores_test.rb + create app/views/high_scores/_high_score.json.jbuilder invoke assets - invoke coffee - create app/assets/javascripts/high_scores.coffee invoke scss create app/assets/stylesheets/high_scores.scss invoke scss - identical app/assets/stylesheets/scaffolds.scss + create app/assets/stylesheets/scaffolds.scss ``` The generator checks that there exist the directories for models, controllers, helpers, layouts, functional and unit tests, stylesheets, creates the views, controller, model and database migration for HighScore (creating the `high_scores` table and fields), takes care of the route for the **resource**, and new tests for everything. @@ -344,7 +368,7 @@ irb(main):001:0> Inside the `rails console` you have access to the `app` and `helper` instances. -With the `app` method you can access url and path helpers, as well as do requests. +With the `app` method you can access URL and path helpers, as well as do requests. ```bash >> app.root_path @@ -481,6 +505,22 @@ lib/school.rb: * [ 17] [FIXME] ``` +#### Tags + +You can add more default tags to search for by using `config.annotations.register_tags`. It receives a list of tags. + +```ruby +config.annotations.register_tags("DEPRECATEME", "TESTME") +``` + +```bash +$ rails notes +app/controllers/admin/users_controller.rb: + * [ 20] [TODO] do A/B testing on this + * [ 42] [TESTME] this needs more functional tests + * [132] [DEPRECATEME] ensure this method is deprecated in next release +``` + #### Directories You can add more default directories to search from by using `config.annotations.register_directories`. It receives a list of directory names. @@ -606,7 +646,7 @@ $ rails "task_name[value 1]" # entire argument string should be quoted $ rails db:nothing ``` -NOTE: If your need to interact with your application models, perform database queries, and so on, your task should depend on the `environment` task, which will load your application code. +NOTE: If you need to interact with your application models, perform database queries, and so on, your task should depend on the `environment` task, which will load your application code. The Rails Advanced Command Line ------------------------------- diff --git a/guides/source/configuring.md b/guides/source/configuring.md index de6766e12e..ded985debe 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -64,10 +64,12 @@ These configuration methods are to be called on a `Rails::Railtie` object, such * `config.autoload_paths` accepts an array of paths from which Rails will autoload constants. Default is all directories under `app`. It is no longer recommended to adjust this. See [Autoloading and Reloading Constants](autoloading_and_reloading_constants.html#autoload-paths-and-eager-load-paths) +* `config.add_autoload_paths_to_load_path` says whether autoload paths have to be added to `$LOAD_PATH`. This flag is `true` by default, but it is recommended to be set to `false` in `:zeitwerk` mode early, in `config/application.rb`. Zeitwerk uses absolute paths internally, and applications running in `:zeitwerk` mode do not need `require_dependency`, so models, controllers, jobs, etc. do not need to be in `$LOAD_PATH`. Setting this to `false` saves Ruby from checking these directories when resolving `require` calls with relative paths, and saves Bootsnap work and RAM, since it does not need to build an index for them. + * `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to `false` in development mode, and `true` in test and production modes. * `config.beginning_of_week` sets the default beginning of week for the -application. Accepts a valid week day symbol (e.g. `:monday`). +application. Accepts a valid day of week as a symbol (e.g. `:monday`). * `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, `:redis_cache_store`, or an object that implements the cache API. Defaults to `:file_store`. @@ -86,6 +88,8 @@ application. Accepts a valid week day symbol (e.g. `:monday`). end ``` +* `config.disable_sandbox` controls whether or not someone can start a console in sandbox mode. This is helpful to avoid a long running session of sandbox console, that could lead a database server to run out of memory. Defaults to false. + * `config.eager_load` when `true`, eager loads all registered `config.eager_load_namespaces`. This includes your application, engines, Rails frameworks, and any other registered namespace. * `config.eager_load_namespaces` registers namespaces that are eager loaded when `config.eager_load` is `true`. All namespaces in the list must respond to the `eager_load!` method. @@ -106,7 +110,7 @@ application. Accepts a valid week day symbol (e.g. `:monday`). you don't want shown in the logs, such as passwords or credit card numbers. It also filters out sensitive values of database columns when call `#inspect` on an Active Record object. By default, Rails filters out passwords by adding `Rails.application.config.filter_parameters += [:password]` in `config/initializers/filter_parameter_logging.rb`. Parameters filter works by partial matching regular expression. -* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](http://api.rubyonrails.org/classes/ActionDispatch/SSL.html) for details. +* `config.force_ssl` forces all requests to be served over HTTPS by using the `ActionDispatch::SSL` middleware, and sets `config.action_mailer.default_url_options` to be `{ protocol: 'https' }`. This can be configured by setting `config.ssl_options` - see the [ActionDispatch::SSL documentation](https://api.rubyonrails.org/classes/ActionDispatch/SSL.html) for details. * `config.log_formatter` defines the formatter of the Rails logger. This option defaults to an instance of `ActiveSupport::Logger::SimpleFormatter` for all modes. If you are setting a value for `config.logger` you must manually pass the value of your formatter to your logger before it is wrapped in an `ActiveSupport::TaggedLogging` instance, Rails will not do it for you. @@ -135,9 +139,13 @@ defaults to `:debug` for all environments. The available log levels are: `:debug * `config.reload_classes_only_on_change` enables or disables reloading of classes only when tracked files change. By default tracks everything on autoload paths and is set to `true`. If `config.cache_classes` is `true`, this option is ignored. +* `config.credentials.content_path` configures lookup path for encrypted credentials. + +* `config.credentials.key_path` configures lookup path for encryption key. + * `secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get a random generated key in test and development environments, other environments should set one in `config/credentials.yml.enc`. -* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to `true`, but in the production environment it is set to `false` because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to `true.` Otherwise, you won't be able to use page caching and request for files that exist under the public directory. +* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to `true`, but in the production environment it is set to `false` because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to `true`. Otherwise, you won't be able to use page caching and request for files that exist under the public directory. * `config.session_store` specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Defaults to a cookie store with application name as the session key. Custom session stores can also be specified: @@ -149,6 +157,13 @@ defaults to `:debug` for all environments. The available log levels are: `:debug * `config.time_zone` sets the default time zone for the application and enables time zone awareness for Active Record. +* `config.autoloader` sets the autoloading mode. This option defaults to `:zeitwerk` if `6.0` is specified in `config.load_defaults`. Applications can still use the classic autoloader by setting this value to `:classic` after loading the framework defaults: + + ```ruby + config.load_defaults "6.0" + config.autoloader = :classic + ``` + ### Configuring Assets * `config.assets.enabled` a flag that controls whether the asset @@ -304,7 +319,7 @@ All these configuration options are delegated to the `I18n` library. ### Configuring Active Model -* `config.active_model.i18n_full_message` is a boolean value which controls whether the `full_message` error format can be overridden at the attribute or model level in the locale files. This is `false` by default. +* `config.active_model.i18n_customize_full_message` is a boolean value which controls whether the `full_message` error format can be overridden at the attribute or model level in the locale files. This is `false` by default. ### Configuring Active Record @@ -338,7 +353,7 @@ All these configuration options are delegated to the `I18n` library. * `config.active_record.lock_optimistically` controls whether Active Record will use optimistic locking and is `true` by default. -* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:nsec`. +* `config.active_record.cache_timestamp_format` controls the format of the timestamp value in the cache key. Default is `:usec`. * `config.active_record.record_timestamps` is a boolean value which controls whether or not timestamping of `create` and `update` operations on a model occur. The default value is `true`. @@ -375,6 +390,12 @@ All these configuration options are delegated to the `I18n` library. having to send a query to the database to get this information. Defaults to `true`. +* `config.active_record.collection_cache_versioning` enables the same cache key + to be reused when the object being cached of type `ActiveRecord::Relation` + changes by moving the volatile information (max updated at and count) of + the relation's cache key into the cache version to support recycling cache key. + Defaults to `false`. + The MySQL adapter adds one additional configuration option: * `ActiveRecord::ConnectionAdapters::Mysql2Adapter.emulate_booleans` controls whether Active Record will consider all `tinyint(1)` columns as booleans. Defaults to `true`. @@ -387,28 +408,6 @@ The PostgreSQL adapter adds one additional configuration option: highly recommended that you do not enable this in a production environment. Defaults to `false` in all environments. -The SQLite3Adapter adapter adds one additional configuration option: - -* `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer` -indicates whether boolean values are stored in sqlite3 databases as 1 and 0 or -'t' and 'f'. Leaving `ActiveRecord::ConnectionAdapters::SQLite3Adapter.represent_boolean_as_integer` -set to false is deprecated. SQLite databases have used 't' and 'f' to serialize -boolean values and must have old data converted to 1 and 0 (its native boolean -serialization) before setting this flag to true. Conversion can be accomplished -by setting up a Rake task which runs - - ```ruby - ExampleModel.where("boolean_column = 't'").update_all(boolean_column: 1) - ExampleModel.where("boolean_column = 'f'").update_all(boolean_column: 0) - ``` - - for all models and all boolean columns, after which the flag must be set to true -by adding the following to your `application.rb` file: - - ```ruby - Rails.application.config.active_record.sqlite3.represent_boolean_as_integer = true - ``` - The schema dumper adds two additional configuration options: * `ActiveRecord::SchemaDumper.ignore_tables` accepts an array of tables that should _not_ be included in any generated schema file. @@ -425,7 +424,7 @@ The schema dumper adds two additional configuration options: * `config.action_controller.asset_host` sets the host for the assets. Useful when CDNs are used for hosting assets rather than the application server itself. -* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to `false` in development mode, `true` in production. +* `config.action_controller.perform_caching` configures whether the application should perform the caching features provided by the Action Controller component or not. Set to `false` in development mode, `true` in production. If it's not specified, the default will be `true`. * `config.action_controller.default_static_extension` configures the extension used for cached pages. Defaults to `.html`. @@ -441,7 +440,7 @@ The schema dumper adds two additional configuration options: * `config.action_controller.per_form_csrf_tokens` configures whether CSRF tokens are only valid for the method/action they were generated for. -* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController:Base`. This is false by default, but enabled when loading defaults for Rails 5.2. +* `config.action_controller.default_protect_from_forgery` determines whether forgery protection is added on `ActionController:Base`. This is false by default. * `config.action_controller.relative_url_root` can be used to tell Rails that you are [deploying to a subdirectory](configuring.html#deploy-to-a-subdirectory-relative-url-root). The default is `ENV['RAILS_RELATIVE_URL_ROOT']`. @@ -554,6 +553,10 @@ Defaults to `'signed cookie'`. Any exceptions that are not configured will be mapped to 500 Internal Server Error. +* `config.action_dispatch.return_only_media_type_on_content_type` change the + return value of `ActionDispatch::Response#content_type` to the Content-Type + header without modification. Defaults to `false`. + * `ActionDispatch::Callbacks.before` takes a block of code to run before the request. * `ActionDispatch::Callbacks.after` takes a block of code to run after the request. @@ -599,7 +602,7 @@ Defaults to `'signed cookie'`. The default setting is `true`, which uses the partial at `/admin/articles/_article.erb`. Setting the value to `false` would render `/articles/_article.erb`, which is the same behavior as rendering from a non-namespaced controller such as `ArticlesController`. * `config.action_view.raise_on_missing_translations` determines whether an - error should be raised for missing translations. + error should be raised for missing translations. This defaults to `false`. * `config.action_view.automatically_disable_submit_tag` determines whether `submit_tag` should automatically disable on click, this defaults to `true`. @@ -608,16 +611,32 @@ Defaults to `'signed cookie'`. * `config.action_view.form_with_generates_remote_forms` determines whether `form_with` generates remote forms or not. This defaults to `true`. -* `config.action_view.form_with_generates_ids` determines whether `form_with` generates ids on inputs. This defaults to `true`. +* `config.action_view.form_with_generates_ids` determines whether `form_with` generates ids on inputs. This defaults to `false`. * `config.action_view.default_enforce_utf8` determines whether forms are generated with a hidden tag that forces older versions of Internet Explorer to submit forms encoded in UTF-8. This defaults to `false`. -* `config.action_view.finalize_compiled_template_methods` determines - whether the methods on `ActionView::CompiledTemplates` that templates - compile themselves to are removed when template instances are - destroyed by the garbage collector. This helps prevent memory leaks in - development mode, but for large test suites, disabling this option in - the test environment can improve performance. This defaults to `true`. + +### Configuring Action Mailbox + +`config.action_mailbox` provides the following configuration options: + +* `config.action_mailbox.logger` contains the logger used by Action Mailbox. It accepts a logger conforming to the interface of Log4r or the default Ruby Logger class. The default is `Rails.logger`. + + ```ruby + config.action_mailbox.logger = ActiveSupport::Logger.new(STDOUT) + ``` + +* `config.action_mailbox.incinerate_after` accepts an `ActiveSupport::Duration` indicating how long after processing `ActionMailbox::InboundEmail` records should be destroyed. It defaults to `30.days`. + + ```ruby + # Incinerate inbound emails 14 days after processing. + config.action_mailbox.incinerate_after = 14.days + ``` + +* `config.action_mailbox.queues.incineration` accepts a symbol indicating the Active Job queue to use for incineration jobs. It defaults to `:action_mailbox_incineration`. + +* `config.action_mailbox.queues.routing` accepts a symbol indicating the Active Job queue to use for routing jobs. It defaults to `:action_mailbox_routing`. + ### Configuring Action Mailer @@ -696,7 +715,9 @@ There are a number of settings available on `config.action_mailer`: * `config.action_mailer.deliver_later_queue_name` specifies the queue name for mailers. By default this is `mailers`. -* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. By default this is `false` in all environments. +* `config.action_mailer.perform_caching` specifies whether the mailer templates should perform fragment caching or not. If it's not specified, the default will be `true`. + +* `config.action_mailer.delivery_job` specifies delivery job for mail. Defaults to `ActionMailer::DeliveryJob`. ### Configuring Active Support @@ -715,7 +736,7 @@ There are a few configuration options available in Active Support: * `config.active_support.use_sha1_digests` specifies whether to use SHA-1 instead of MD5 to generate non-sensitive digests, such as the ETag header. Defaults to false. -* `config.active_support.use_authenticated_message_encryption` specifies whether to use AES-256-GCM authenticated encryption as the default cipher for encrypting messages instead of AES-256-CBC. This is false by default, but enabled when loading defaults for Rails 5.2. +* `config.active_support.use_authenticated_message_encryption` specifies whether to use AES-256-GCM authenticated encryption as the default cipher for encrypting messages instead of AES-256-CBC. This is false by default. * `ActiveSupport::Logger.silencer` is set to `false` to disable the ability to silence logging in a block. The default is `true`. @@ -725,13 +746,13 @@ There are a few configuration options available in Active Support: * `ActiveSupport::Deprecation.silence` takes a block in which all deprecation warnings are silenced. -* `ActiveSupport::Deprecation.silenced` sets whether or not to display deprecation warnings. +* `ActiveSupport::Deprecation.silenced` sets whether or not to display deprecation warnings. The default is `false`. ### Configuring Active Job `config.active_job` provides the following configuration options: -* `config.active_job.queue_adapter` sets the adapter for the queuing backend. The default adapter is `:async`. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](http://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). +* `config.active_job.queue_adapter` sets the adapter for the queuing backend. The default adapter is `:async`. For an up-to-date list of built-in adapters see the [ActiveJob::QueueAdapters API documentation](https://api.rubyonrails.org/classes/ActiveJob/QueueAdapters.html). ```ruby # Be sure to have the adapter's gem in your Gemfile @@ -782,6 +803,8 @@ There are a few configuration options available in Active Support: * `config.active_job.custom_serializers` allows to set custom argument serializers. Defaults to `[]`. +* `config.active_job.return_false_on_aborted_enqueue` change the return value of `#enqueue` to false instead of the job instance when the enqueuing is aborted. Defaults to `false`. + ### Configuring Action Cable * `config.action_cable.url` accepts a string for the URL for where @@ -793,6 +816,9 @@ main application. You can set this as nil to not mount Action Cable as part of your normal Rails server. +You can find more detailed configuration options in the +[Action Cable Overview](action_cable_overview.html#configuration). + ### Configuring Active Storage @@ -813,15 +839,27 @@ normal Rails server. config.active_storage.paths[:ffprobe] = '/usr/local/bin/ffprobe' ``` -* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/vnd.adobe.photoshop image/vnd.microsoft.icon)`. +* `config.active_storage.variable_content_types` accepts an array of strings indicating the content types that Active Storage can transform through ImageMagick. The default is `%w(image/png image/gif image/jpg image/jpeg image/pjpeg image/tiff image/bmp image/vnd.adobe.photoshop image/vnd.microsoft.icon)`. * `config.active_storage.content_types_to_serve_as_binary` accepts an array of strings indicating the content types that Active Storage will always serve as an attachment, rather than inline. The default is `%w(text/html -text/javascript image/svg+xml application/postscript application/x-shockwave-flash text/xml application/xml application/xhtml+xml)`. +text/javascript image/svg+xml application/postscript application/x-shockwave-flash text/xml application/xml application/xhtml+xml application/mathml+xml text/cache-manifest)`. + +* `config.active_storage.queues.analysis` accepts a symbol indicating the Active Job queue to use for analysis jobs. When this option is `nil`, analysis jobs are sent to the default Active Job queue (see `config.active_job.default_queue_name`). + + ```ruby + config.active_storage.queues.analysis = :low_priority + ``` + +* `config.active_storage.queues.purge` accepts a symbol indicating the Active Job queue to use for purge jobs. When this option is `nil`, purge jobs are sent to the default Active Job queue (see `config.active_job.default_queue_name`). + + ```ruby + config.active_storage.queues.purge = :low_priority + ``` -* `config.active_storage.queue` can be used to set the name of the Active Job queue used to perform jobs like analyzing the content of a blob or purging a blog. +* `config.active_storage.queues.mirror` accepts a symbol indicating the Active Job queue to use for direct upload mirroring jobs. The default is `:active_storage_mirror`. ```ruby - config.active_storage.queue = :low_priority + config.active_storage.queues.mirror = :low_priority ``` * `config.active_storage.logger` can be used to set the logger used by Active Storage. Accepts a logger conforming to the interface of Log4r or the default Ruby Logger class. @@ -843,7 +881,48 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla config.active_storage.routes_prefix = '/files' ``` - The default is `/rails/active_storage` + The default is `/rails/active_storage`. + +* `config.active_storage.replace_on_assign_to_many` determines whether assigning to a collection of attachments declared with `has_many_attached` replaces any existing attachments or appends to them. The default is `true`. + +* `config.active_storage.draw_routes` can be used to toggle Active Storage route generation. The default is `true`. + +### Results of `load_defaults` + +#### With '5.0': + +- `config.action_controller.per_form_csrf_tokens`: `true` +- `config.action_controller.forgery_protection_origin_check`: `true` +- `ActiveSupport.to_time_preserves_timezone`: `true` +- `config.active_record.belongs_to_required_by_default`: `true` +- `config.ssl_options`: `{ hsts: { subdomains: true } }` + +#### With '5.1': + +- `config.assets.unknown_asset_fallback`: `false` +- `config.action_view.form_with_generates_remote_forms`: `true` + +#### With '5.2': + +- `config.active_record.cache_versioning`: `true` +- `config.action_dispatch.use_authenticated_cookie_encryption`: `true` +- `config.active_support.use_authenticated_message_encryption`: `true` +- `config.active_support.use_sha1_digests`: `true` +- `config.action_controller.default_protect_from_forgery`: `true` +- `config.action_view.form_with_generates_ids`: `true` + +#### With '6.0': + +- `config.autoloader`: `:zeitwerk` +- `config.action_view.default_enforce_utf8`: `false` +- `config.action_dispatch.use_cookies_with_metadata`: `true` +- `config.action_dispatch.return_only_media_type_on_content_type`: `false` +- `config.action_mailer.delivery_job`: `"ActionMailer::MailDeliveryJob"` +- `config.active_job.return_false_on_aborted_enqueue`: `true` +- `config.active_storage.queues.analysis`: `:active_storage_analysis` +- `config.active_storage.queues.purge`: `:active_storage_purge` +- `config.active_storage.replace_on_assign_to_many`: `true` +- `config.active_record.collection_cache_versioning`: `true` ### Configuring a Database @@ -1116,7 +1195,7 @@ Imagine you have a server which mirrors the production environment but is only u That environment is no different than the default ones, start a server with `rails server -e staging`, a console with `rails console -e staging`, `Rails.env.staging?` works, etc. -### Deploy to a subdirectory (relative url root) +### Deploy to a subdirectory (relative URL root) By default Rails expects that your application is running at the root (eg. `/`). This section explains how to run your application inside a directory. @@ -1195,6 +1274,8 @@ Using Initializer Files After loading the framework and any gems in your application, Rails turns to loading initializers. An initializer is any Ruby file stored under `config/initializers` in your application. You can use initializers to hold configuration settings that should be made after all of the frameworks and gems are loaded, such as options to configure settings for these parts. +NOTE: There is no guarantee that your initializers will run after all the gem initializers, so any initialization code that depends on a given gem having been initialized should go into a `config.after_initialize` block. + NOTE: You can use subfolders to organize your initializers if you like, because Rails will look into the whole file hierarchy from the initializers folder on down. TIP: While Rails supports numbering of initializer file names for load ordering purposes, a better technique is to place any code that need to load in a specific order within the same file. This reduces file name churn, makes dependencies more explicit, and can help surface new concepts within your application. @@ -1402,7 +1483,7 @@ Custom configuration You can configure your own code through the Rails configuration object with custom configuration under either the `config.x` namespace, or `config` directly. The key difference between these two is that you should be using `config.x` if you -are defining _nested_ configuration (ex: `config.x.nested.nested.hi`), and just +are defining _nested_ configuration (ex: `config.x.nested.hi`), and just `config` for _single level_ configuration (ex: `config.hello`). ```ruby @@ -1465,7 +1546,7 @@ Disallow: / ``` To block just specific pages, it's necessary to use a more complex syntax. Learn -it on the [official documentation](http://www.robotstxt.org/robotstxt.html). +it on the [official documentation](https://www.robotstxt.org/robotstxt.html). Evented File System Monitor --------------------------- diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md index 709a5146e9..9c28ff6a9c 100644 --- a/guides/source/contributing_to_ruby_on_rails.md +++ b/guides/source/contributing_to_ruby_on_rails.md @@ -13,10 +13,10 @@ After reading this guide, you will know: * How to contribute to the Ruby on Rails documentation. * How to contribute to the Ruby on Rails code. -Ruby on Rails is not "someone else's framework." Over the years, thousands of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. +Ruby on Rails is not "someone else's framework". Over the years, thousands of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches. As mentioned in [Rails' -README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](http://rubyonrails.org/conduct/). +README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](https://rubyonrails.org/conduct/). -------------------------------------------------------------------------------- @@ -53,7 +53,7 @@ You can then share your executable test case as a [gist](https://gist.github.com ### Special Treatment for Security Issues -WARNING: Please do not report security vulnerabilities with public GitHub issue reports. The [Rails security policy page](http://rubyonrails.org/security) details the procedure to follow for security issues. +WARNING: Please do not report security vulnerabilities with public GitHub issue reports. The [Rails security policy page](https://rubyonrails.org/security) details the procedure to follow for security issues. ### What about Feature Requests? @@ -76,7 +76,7 @@ a patch, please send an email to the [rails-core mailing list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You might get no response, which means that everyone is indifferent. You might find someone who's also interested in building that feature. You might get a "This -won't be accepted." But it's the proper place to discuss new ideas. GitHub +won't be accepted". But it's the proper place to discuss new ideas. GitHub Issues are not a particularly good venue for the sometimes long and involved discussions new features require. @@ -139,7 +139,7 @@ changes to the master branch. When working with documentation, please take into account the [API Documentation Guidelines](api_documentation_guidelines.html) and the [Ruby on Rails Guides Guidelines](ruby_on_rails_guides_guidelines.html). -NOTE: To help our CI servers you should add [ci skip] to your documentation commit message to skip build on that commit. Please remember to use it for commits containing only documentation changes. +NOTE: For documentation changes, your commit message should include [ci skip]. This will skip running the test suite, helping us to cut down on our server costs. Keep in mind that you should only skip CI when your change touches documentation exclusively. Translating Rails Guides ------------------------ @@ -247,7 +247,7 @@ Rails follows a simple set of coding style conventions: * Two spaces, no tabs (for indentation). * No trailing whitespace. Blank lines should not have any spaces. -* Indent after private/protected. +* Indent and no blank line after private/protected. * Use Ruby >= 1.9 syntax for hashes. Prefer `{ a: :b }` over `{ :a => :b }`. * Prefer `&&`/`||` over `and`/`or`. * Prefer class << self over self.method for class methods. @@ -302,7 +302,7 @@ the recommended workflow with the [rails-dev-box](https://github.com/rails/rails As a compromise, test what your code obviously affects, and if the change is not in railties, run the whole test suite of the affected component. If all tests are passing, that's enough to propose your contribution. We have -[Travis CI](https://travis-ci.org/rails/rails) as a safety net for catching +[Buildkite](https://buildkite.com/rails/rails) as a safety net for catching unexpected breakages elsewhere. #### Entire Rails: @@ -340,7 +340,7 @@ $ TEST_DIR=generators bundle exec rake test You can run the tests for a particular file by using: ```bash -$ cd actionpack +$ cd actionview $ bundle exec ruby -w -Itest test/template/form_helper_test.rb ``` @@ -418,7 +418,7 @@ To run a single test against all adapters, use: $ bundle exec rake TEST=test/cases/associations/has_many_associations_test.rb ``` -You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests, or the file `ci/travis.rb` for the test suite run by the continuous integration server. +You can invoke `test_jdbcmysql`, `test_jdbcsqlite3` or `test_jdbcpostgresql` also. See the file `activerecord/RUNNING_UNIT_TESTS.rdoc` for information on running more targeted database tests. ### Warnings @@ -677,11 +677,11 @@ $ git apply ~/my_changes.patch This works well for simple changes. However, if your changes are complicated or if the code in master has deviated significantly from your target branch, it might require more work on your part. The difficulty of a backport varies greatly from case to case, and sometimes it is simply not worth the effort. -Once you have resolved all conflicts and made sure all the tests are passing, push your changes and open a separate pull request for your backport. It is also worth noting that older branches might have a different set of build targets than master. When possible, it is best to first test your backport locally against the Ruby versions listed in `.travis.yml` before submitting your pull request. +Once you have resolved all conflicts and made sure all the tests are passing, push your changes and open a separate pull request for your backport. It is also worth noting that older branches might have a different set of build targets than master. When possible, it is best to first test your backport locally against the oldest Ruby version permitted by the target branch's `rails.gemspec` before submitting your pull request. And then... think about your next contribution! Rails Contributors ------------------ -All contributions get credit in [Rails Contributors](http://contributors.rubyonrails.org). +All contributions get credit in [Rails Contributors](https://contributors.rubyonrails.org). diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index 3a383cbd4d..170c22905b 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -147,7 +147,7 @@ TIP: The default Rails log level is `debug` in all environments. ### Sending Messages -To write in the current log use the `logger.(debug|info|warn|error|fatal)` method from within a controller, model, or mailer: +To write in the current log use the `logger.(debug|info|warn|error|fatal|unknown)` method from within a controller, model, or mailer: ```ruby logger.debug "Person attributes hash: #{@person.attributes.inspect}" @@ -962,15 +962,8 @@ Plugins for Debugging There are some Rails plugins to help you to find errors and debug your application. Here is a list of useful plugins for debugging: -* [Footnotes](https://github.com/josevalim/rails-footnotes) Every Rails page has -footnotes that give request information and link back to your source via -TextMate. * [Query Trace](https://github.com/ruckus/active-record-query-trace/tree/master) Adds query origin tracing to your logs. -* [Query Reviewer](https://github.com/nesquena/query_reviewer) This Rails plugin -not only runs "EXPLAIN" before each of your select queries in development, but -provides a small DIV in the rendered output of each page with the summary of -warnings for each query that it analyzed. * [Exception Notifier](https://github.com/smartinez87/exception_notification/tree/master) Provides a mailer object and a default set of templates for sending email notifications when errors occur in a Rails application. diff --git a/guides/source/development_dependencies_install.md b/guides/source/development_dependencies_install.md index d52946be08..e84e5561f2 100644 --- a/guides/source/development_dependencies_install.md +++ b/guides/source/development_dependencies_install.md @@ -156,7 +156,7 @@ To install all run: # portmaster databases/redis ``` -Or install everyting through ports (these packages are located under the +Or install everything through ports (these packages are located under the `databases` folder). NOTE: If you run into troubles during the installation of MySQL, please see @@ -227,7 +227,6 @@ If you're using another database, check the file `activerecord/test/config.yml` If you installed Yarn, you will need to install the javascript dependencies: ```bash -$ cd activestorage $ yarn install ``` diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml index 25c159d471..da11236064 100644 --- a/guides/source/documents.yaml +++ b/guides/source/documents.yaml @@ -74,7 +74,17 @@ - name: Action Mailer Basics url: action_mailer_basics.html - description: This guide describes how to use Action Mailer to send and receive emails. + description: This guide describes how to use Action Mailer to send emails. + - + name: Action Mailbox Basics + work_in_progress: true + url: action_mailbox_basics.html + description: This guide describes how to use Action Mailbox to receive emails. + - + name: Action Text Overview + work_in_progress: true + url: action_text_overview.html + description: This guide describes how to use Action Text to handle rich text content. - name: Active Job Basics url: active_job_basics.html @@ -129,9 +139,13 @@ url: initialization.html description: This guide explains the internals of the Rails initialization process. - - name: Autoloading and Reloading Constants + name: Autoloading and Reloading Constants (Zeitwerk Mode) url: autoloading_and_reloading_constants.html - description: This guide documents how autoloading and reloading constants work. + description: This guide documents how autoloading and reloading constants work (Zeitwerk mode). + - + name: Autoloading and Reloading Constants (Classic Mode) + url: autoloading_and_reloading_constants_classic_mode.html + description: This guide documents how autoloading and reloading constants work (Classic mode). - name: "Caching with Rails: An Overview" url: caching_with_rails.html @@ -145,6 +159,16 @@ name: Using Rails for API-only Applications url: api_app.html description: This guide explains how to effectively use Rails to develop a JSON API application. + - + name: Active Record and PostgreSQL + work_in_progress: true + url: active_record_postgresql.html + description: This guide covers PostgreSQL specific usage of Active Record. + - + name: Multiple Databases with Active Record + work_in_progress: true + url: active_record_multiple_databases.html + description: This guide covers using multiple databases in your application. - name: Extending Rails @@ -178,7 +202,7 @@ - name: Contributing to Ruby on Rails url: contributing_to_ruby_on_rails.html - description: Rails is not 'somebody else's framework.' This guide covers a variety of ways that you can get involved in the ongoing development of Rails. + description: Rails is not "someone else's framework". This guide covers a variety of ways that you can get involved in the ongoing development of Rails. - name: API Documentation Guidelines url: api_documentation_guidelines.html diff --git a/guides/source/engines.md b/guides/source/engines.md index 1e93a19c84..8961a079b5 100644 --- a/guides/source/engines.md +++ b/guides/source/engines.md @@ -217,12 +217,10 @@ important parts about namespacing, and is discussed later in the #### `app` Directory Inside the `app` directory are the standard `assets`, `controllers`, `helpers`, -`mailers`, `models` and `views` directories that you should be familiar with -from an application. The `helpers`, `mailers` and `models` directories are -empty, so they aren't described in this section. We'll look more into models in -a future section, when we're writing the engine. +`jobs`, `mailers`, `models`, and `views` directories that you should be familiar with +from an application. We'll look more into models in a future section, when we're writing the engine. -Within the `app/assets` directory, there are the `images`, `javascripts` and +Within the `app/assets` directory, there are the `images` and `stylesheets` directories which, again, you should be familiar with due to their similarity to an application. One difference here, however, is that each directory contains a sub-directory with the engine name. Because this engine is @@ -261,6 +259,30 @@ WARNING: Don't use `require` because it will break the automatic reloading of cl in the development environment - using `require_dependency` ensures that classes are loaded and unloaded in the correct manner. +Within the `app/helpers` directory there is a `blorgh` directory that +contains a file called `application_helper.rb`. This file will provide any +common functionality for the helpers of the engine. The `blorgh` directory +is where the other helpers for the engine will go. By placing them within +this namespaced directory, you prevent them from possibly clashing with +identically-named helpers within other engines or even within the +application. + +Within the `app/jobs` directory there is a `blorgh` directory that +contains a file called `application_job.rb`. This file will provide any +common functionality for the jobs of the engine. The `blorgh` directory +is where the other jobs for the engine will go. By placing them within +this namespaced directory, you prevent them from possibly clashing with +identically-named jobs within other engines or even within the +application. + +Within the `app/mailers` directory there is a `blorgh` directory that +contains a file called `application_mailer.rb`. This file will provide any +common functionality for the mailers of the engine. The `blorgh` directory +is where the other mailers for the engine will go. By placing them within +this namespaced directory, you prevent them from possibly clashing with +identically-named mailers within other engines or even within the +application. + Lastly, the `app/views` directory contains a `layouts` folder, which contains a file at `blorgh/application.html.erb`. This file allows you to specify a layout for the engine. If this engine is to be used as a stand-alone engine, then you @@ -347,14 +369,11 @@ create app/views/blorgh/articles/new.html.erb create app/views/blorgh/articles/_form.html.erb invoke test_unit create test/controllers/blorgh/articles_controller_test.rb +create test/system/blorgh/articles_test.rb invoke helper create app/helpers/blorgh/articles_helper.rb -invoke test_unit -create test/application_system_test_case.rb -create test/system/articles_test.rb +invoke test_unit invoke assets -invoke js -create app/assets/javascripts/blorgh/articles.js invoke css create app/assets/stylesheets/blorgh/articles.css invoke css @@ -394,9 +413,8 @@ be isolated from those routes that are within the application. The Next, the `scaffold_controller` generator is invoked, generating a controller called `Blorgh::ArticlesController` (at `app/controllers/blorgh/articles_controller.rb`) and its related views at -`app/views/blorgh/articles`. This generator also generates a test for the -controller (`test/controllers/blorgh/articles_controller_test.rb`) and a helper -(`app/helpers/blorgh/articles_helper.rb`). +`app/views/blorgh/articles`. This generator also generates tests for the +controller (`test/controllers/blorgh/articles_controller_test.rb` and `test/system/blorgh/articles_test.rb`) and a helper (`app/helpers/blorgh/articles_helper.rb`). Everything this generator has created is neatly namespaced. The controller's class is defined within the `Blorgh` module: @@ -425,10 +443,7 @@ end This helps prevent conflicts with any other engine or application that may have an article resource as well. -Finally, the assets for this resource are generated in two files: -`app/assets/javascripts/blorgh/articles.js` and -`app/assets/stylesheets/blorgh/articles.css`. You'll see how to use these a little -later. +Finally, the assets for this resource are generated in one file: `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 `rails db:migrate` at the root of our engine to run the migration generated by the scaffold generator, and then @@ -579,9 +594,8 @@ invoke test_unit create test/controllers/blorgh/comments_controller_test.rb invoke helper create app/helpers/blorgh/comments_helper.rb +invoke test_unit invoke assets -invoke js -create app/assets/javascripts/blorgh/comments.js invoke css create app/assets/stylesheets/blorgh/comments.css ``` @@ -1091,16 +1105,15 @@ main Rails application. Engine model and controller classes can be extended by open classing them in the main Rails application (since model and controller classes are just Ruby classes that inherit Rails specific functionality). Open classing an Engine class -redefines it for use in the main application. This is usually implemented by -using the decorator pattern. +redefines it for use in the main application. For simple class modifications, use `Class#class_eval`. For complex class modifications, consider using `ActiveSupport::Concern`. -#### A note on Decorators and Loading Code +#### A note on Overriding and Loading Code -Because these decorators are not referenced by your Rails application itself, -Rails' autoloading system will not kick in and load your decorators. This means +Because these overrides are not referenced by your Rails application itself, +Rails' autoloading system will not kick in and load your overrides. This means that you need to require them yourself. Here is some sample code to do this: @@ -1112,7 +1125,7 @@ module Blorgh isolate_namespace Blorgh config.to_prepare do - Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c| + Dir.glob(Rails.root + "app/overrides/**/*_override*.rb").each do |c| require_dependency(c) end end @@ -1120,15 +1133,15 @@ module Blorgh end ``` -This doesn't apply to just Decorators, but anything that you add in an engine +This doesn't apply to just overrides, but anything that you add in an engine that isn't referenced by your main application. -#### Implementing Decorator Pattern Using Class#class_eval +#### Reopening existing classes using Class#class_eval **Adding** `Article#time_since_created`: ```ruby -# MyApp/app/decorators/models/blorgh/article_decorator.rb +# MyApp/app/overrides/models/blorgh/article_override.rb Blorgh::Article.class_eval do def time_since_created @@ -1138,10 +1151,11 @@ end ``` ```ruby -# Blorgh/app/models/article.rb - -class Article < ApplicationRecord - has_many :comments +# Blorgh/app/models/blorgh/article.rb +module Blorgh + class Article < ApplicationRecord + has_many :comments + end end ``` @@ -1149,7 +1163,7 @@ end **Overriding** `Article#summary`: ```ruby -# MyApp/app/decorators/models/blorgh/article_decorator.rb +# MyApp/app/overrides/models/blorgh/article_override.rb Blorgh::Article.class_eval do def summary @@ -1159,21 +1173,22 @@ end ``` ```ruby -# Blorgh/app/models/article.rb - -class Article < ApplicationRecord - has_many :comments - def summary - "#{title}" +# Blorgh/app/models/blorgh/article.rb +module Blorgh + class Article < ApplicationRecord + has_many :comments + def summary + "#{title}" + end end end ``` -#### Implementing Decorator Pattern Using ActiveSupport::Concern +#### Reopening existing classes using ActiveSupport::Concern Using `Class#class_eval` is great for simple adjustments, but for more complex class modifications, you might want to consider using [`ActiveSupport::Concern`] -(http://api.rubyonrails.org/classes/ActiveSupport/Concern.html). +(https://api.rubyonrails.org/classes/ActiveSupport/Concern.html). ActiveSupport::Concern manages load order of interlinked dependent modules and classes at run time allowing you to significantly modularize your code. @@ -1196,10 +1211,11 @@ end ``` ```ruby -# Blorgh/app/models/article.rb - -class Article < ApplicationRecord - include Blorgh::Concerns::Models::Article +# Blorgh/app/models/blorgh/article.rb +module Blorgh + class Article < ApplicationRecord + include Blorgh::Concerns::Models::Article + end end ``` @@ -1402,7 +1418,7 @@ s.add_development_dependency "moo" Both kinds of dependencies will be installed when `bundle install` is run inside of the application. The development dependencies for the gem will only be used -when the tests for the engine are running. +when the development and tests for the engine are running. Note that if you want to immediately require dependencies when the engine is required, you should require them before the engine's initialization. For @@ -1495,25 +1511,35 @@ These are the hooks you can use in your own code. To hook into the initialization process of one of the following classes use the available hook. -| Class | Available Hooks | -| --------------------------------- | ------------------------------------ | -| `ActionCable` | `action_cable` | -| `ActionController::API` | `action_controller_api` | -| `ActionController::API` | `action_controller` | -| `ActionController::Base` | `action_controller_base` | -| `ActionController::Base` | `action_controller` | -| `ActionController::TestCase` | `action_controller_test_case` | -| `ActionDispatch::IntegrationTest` | `action_dispatch_integration_test` | -| `ActionDispatch::SystemTestCase` | `action_dispatch_system_test_case` | -| `ActionMailer::Base` | `action_mailer` | -| `ActionMailer::TestCase` | `action_mailer_test_case` | -| `ActionView::Base` | `action_view` | -| `ActionView::TestCase` | `action_view_test_case` | -| `ActiveJob::Base` | `active_job` | -| `ActiveJob::TestCase` | `active_job_test_case` | -| `ActiveRecord::Base` | `active_record` | -| `ActiveSupport::TestCase` | `active_support_test_case` | -| `i18n` | `i18n` | +| Class | Available Hooks | +| -------------------------------------| ------------------------------------ | +| `ActionCable` | `action_cable` | +| `ActionCable::Channel::Base` | `action_cable_channel` | +| `ActionCable::Connection::Base` | `action_cable_connection` | +| `ActionCable::Connection::TestCase` | `action_cable_connection_test_case` | +| `ActionController::API` | `action_controller_api` | +| `ActionController::API` | `action_controller` | +| `ActionController::Base` | `action_controller_base` | +| `ActionController::Base` | `action_controller` | +| `ActionController::TestCase` | `action_controller_test_case` | +| `ActionDispatch::IntegrationTest` | `action_dispatch_integration_test` | +| `ActionDispatch::SystemTestCase` | `action_dispatch_system_test_case` | +| `ActionMailbox::Base` | `action_mailbox` | +| `ActionMailbox::InboundEmail` | `action_mailbox_inbound_email` | +| `ActionMailbox::TestCase` | `action_mailbox_test_case` | +| `ActionMailer::Base` | `action_mailer` | +| `ActionMailer::TestCase` | `action_mailer_test_case` | +| `ActionText::Content` | `action_text_content` | +| `ActionText::RichText` | `action_text_rich_text` | +| `ActionView::Base` | `action_view` | +| `ActionView::TestCase` | `action_view_test_case` | +| `ActiveJob::Base` | `active_job` | +| `ActiveJob::TestCase` | `active_job_test_case` | +| `ActiveRecord::Base` | `active_record` | +| `ActiveStorage::Attachment` | `active_storage_attachment` | +| `ActiveStorage::Blob` | `active_storage_blob` | +| `ActiveSupport::TestCase` | `active_support_test_case` | +| `i18n` | `i18n` | ## Configuration hooks @@ -1528,4 +1554,6 @@ These are the available configuration hooks. They do not hook into any particula ### Example -`config.before_configuration { puts 'I am called before any initializers' }` +```ruby +config.before_configuration { puts 'I am called before any initializers' } +``` diff --git a/guides/source/form_helpers.md b/guides/source/form_helpers.md index b5e2c49487..6418005921 100644 --- a/guides/source/form_helpers.md +++ b/guides/source/form_helpers.md @@ -17,7 +17,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit [the Rails API documentation](http://api.rubyonrails.org/) for a complete reference. +NOTE: This guide is not intended to be a complete documentation of available form helpers and their arguments. Please visit [the Rails API documentation](https://api.rubyonrails.org/) for a complete reference. Dealing with Basic Forms ------------------------ @@ -89,7 +89,7 @@ value entered by the user for that field. For example, if the form contains `<%= text_field_tag(:query) %>`, then you would be able to get the value of this field in the controller with `params[:query]`. -When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in chapter [Understanding Parameter Naming Conventions](#understanding-parameter-naming-conventions) of this guide. For details on the precise usage of these helpers, please refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html). +When naming inputs, Rails uses certain conventions that make it possible to submit parameters with non-scalar values such as arrays or hashes, which will also be accessible in `params`. You can read more about them in chapter [Understanding Parameter Naming Conventions](#understanding-parameter-naming-conventions) of this guide. For details on the precise usage of these helpers, please refer to the [API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers/FormTagHelper.html). #### Checkboxes @@ -248,7 +248,7 @@ There are a few things to note here: * `@article` is the actual object being edited. * There is a single hash of options. HTML options (except `id` and `class`) are passed in the `:html` hash. Also you can provide a `:namespace` option for your form to ensure uniqueness of id attributes on form elements. The scope attribute will be prefixed with underscore on the generated HTML id. * The `form_with` method yields a **form builder** object (the `f` variable). -* If you wish to direct your form request to a particular url, you would use `form_with url: my_nifty_url_path` instead. To see more in depth options on what `form_with` accepts be sure to [check out the API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with). +* If you wish to direct your form request to a particular URL, you would use `form_with url: my_nifty_url_path` instead. To see more in depth options on what `form_with` accepts be sure to [check out the API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with). * Methods to create form controls are called **on** the form builder object `f`. The resulting HTML is: @@ -527,13 +527,13 @@ NOTE: Pairs passed to `options_for_select` should have the text first and the va ### Time Zone and Country Select -To leverage time zone support in Rails, you have to ask your users what time zone they are in. Doing so would require generating select options from a list of pre-defined [`ActiveSupport::TimeZone`](http://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html) objects using `collection_select`, but you can simply use the `time_zone_select` helper that already wraps this: +To leverage time zone support in Rails, you have to ask your users what time zone they are in. Doing so would require generating select options from a list of pre-defined [`ActiveSupport::TimeZone`](https://api.rubyonrails.org/classes/ActiveSupport/TimeZone.html) objects using `collection_select`, but you can simply use the `time_zone_select` helper that already wraps this: ```erb <%= time_zone_select(:person, :time_zone) %> ``` -There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-time_zone_options_for_select) to learn about the possible arguments for these two methods. +There is also `time_zone_options_for_select` helper for a more manual (therefore more customizable) way of doing this. Read the [API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers/FormOptionsHelper.html#method-i-time_zone_options_for_select) to learn about the possible arguments for these two methods. Rails _used_ to have a `country_select` helper for choosing countries, but this has been extracted to the [country_select plugin](https://github.com/stefanpenner/country_select). @@ -604,7 +604,7 @@ When this is passed to `Person.new` (or `update`), Active Record spots that thes ### Common Options -Both families of helpers use the same core set of functions to generate the individual select tags and so both accept largely the same options. In particular, by default Rails will generate year options 5 years either side of the current year. If this is not an appropriate range, the `:start_year` and `:end_year` options override this. For an exhaustive list of the available options, refer to the [API documentation](http://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html). +Both families of helpers use the same core set of functions to generate the individual select tags and so both accept largely the same options. In particular, by default Rails will generate year options 5 years either side of the current year. If this is not an appropriate range, the `:start_year` and `:end_year` options override this. For an exhaustive list of the available options, refer to the [API documentation](https://api.rubyonrails.org/classes/ActionView/Helpers/DateHelper.html). As a rule of thumb you should be using `date_select` when working with model objects and `select_date` in other cases, such as a search form which filters results by date. @@ -642,7 +642,7 @@ Rails provides the usual pair of helpers: the barebones `file_field_tag` and the ### What Gets Uploaded -The object in the `params` hash is an instance of [`ActionDispatch::Http::UploadedFile`](http://api.rubyonrails.org/classes/ActionDispatch/Http/UploadedFile.html). The following snippet saves the uploaded file in `#{Rails.root}/public/uploads` under the same name as the original file. +The object in the `params` hash is an instance of [`ActionDispatch::Http::UploadedFile`](https://api.rubyonrails.org/classes/ActionDispatch/Http/UploadedFile.html). The following snippet saves the uploaded file in `#{Rails.root}/public/uploads` under the same name as the original file. ```ruby def upload @@ -658,7 +658,7 @@ Once a file has been uploaded, there are a multitude of potential tasks, ranging Customizing Form Builders ------------------------- -The object yielded by `form_with` and `fields_for` is an instance of [`ActionView::Helpers::FormBuilder`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html). Form builders encapsulate the notion of displaying form elements for a single object. While you can write helpers for your forms in the usual way, you can also create subclass `ActionView::Helpers::FormBuilder` and add the helpers there. For example: +The object yielded by `form_with` and `fields_for` is an instance of [`ActionView::Helpers::FormBuilder`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html). Form builders encapsulate the notion of displaying form elements for a single object. While you can write helpers for your forms in the usual way, you can also create subclass `ActionView::Helpers::FormBuilder` and add the helpers there. For example: ```erb <%= form_with model: @person do |f| %> diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index e2f558d74c..ce45dbb2a7 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -28,7 +28,7 @@ curve diving straight into Rails. There are several curated lists of online reso for learning Ruby: * [Official Ruby Programming Language website](https://www.ruby-lang.org/en/documentation/) -* [List of Free Programming Books](https://github.com/vhf/free-programming-books/blob/master/free-programming-books.md#ruby) +* [List of Free Programming Books](https://github.com/EbookFoundation/free-programming-books/blob/master/free-programming-books.md#ruby) Be aware that some resources, while still excellent, cover versions of Ruby as old as 1.6, and commonly 1.8, and will not include some syntax that you will see in day-to-day @@ -55,7 +55,7 @@ The Rails philosophy includes two major guiding principles: * **Don't Repeat Yourself:** DRY is a principle of software development which states that "Every piece of knowledge must have a single, unambiguous, authoritative - representation within a system." By not writing the same information over and over + representation within a system". By not writing the same information over and over again, our code is more maintainable, more extensible, and less buggy. * **Convention Over Configuration:** Rails has opinions about the best way to do many things in a web application, and defaults to this set of conventions, rather than @@ -90,7 +90,7 @@ $ ruby -v ruby 2.5.0 ``` -Rails requires Ruby version 2.4.1 or later. If the version number returned is +Rails requires Ruby version 2.5.0 or later. If the version number returned is less than that number, you'll need to install a fresh copy of Ruby. TIP: To quickly install Ruby and Ruby on Rails on your system in Windows, you can use @@ -126,7 +126,7 @@ run the following: $ rails --version ``` -If it says something like "Rails 5.2.1", you are ready to continue. +If it says something like "Rails 6.0.0", you are ready to continue. ### Creating the Blog Application @@ -205,12 +205,10 @@ $ rails server TIP: If you are using Windows, you have to pass the scripts under the `bin` folder directly to the Ruby interpreter e.g. `ruby bin\rails server`. -TIP: Compiling CoffeeScript and JavaScript asset compression requires you +TIP: JavaScript asset compression requires you have a JavaScript runtime available on your system, in the absence -of a runtime you will see an `execjs` error during asset compilation. +of a runtime you will see an `execjs` error during asset compression. Usually macOS and Windows come with a JavaScript runtime installed. -Rails adds the `mini_racer` gem to the generated `Gemfile` in a -commented line for new apps and you can uncomment if you need it. `therubyrhino` is the recommended runtime for JRuby users and is added by default to the `Gemfile` in apps generated under JRuby. You can investigate all the supported runtimes at [ExecJS](https://github.com/rails/execjs#readme). @@ -461,22 +459,19 @@ available, Rails will raise an exception. Let's look at the full error message again: ->ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you're loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we're showing an error to be extra-clear. If you expect 204 No Content, carry on. That's what you'll get from an XHR or API request. Give it a shot. +>ArticlesController#new is missing a template for request formats: text/html -That's quite a lot of text! Let's quickly go through and understand what each -part of it means. +>NOTE! +>Unless told otherwise, Rails expects an action to render a template with the same name, contained in a folder named after its controller. If this controller is an API responding with 204 (No Content), which does not require a template, then this error will occur when trying to access it via browser, since we expect an HTML template to be rendered for such requests. If that's the case, carry on. -The first part identifies which template is missing. In this case, it's the +The message identifies which template is missing. In this case, it's the `articles/new` template. Rails will first look for this template. If not found, -then it will attempt to load a template called `application/new`. It looks for -one here because the `ArticlesController` inherits from `ApplicationController`. +then it will attempt to load a template called `application/new`, because the +`ArticlesController` inherits from `ApplicationController`. -The next part of the message contains `request.formats` which specifies -the format of template to be served in response. It is set to `text/html` as we -requested this page via browser, so Rails is looking for an HTML template. -`request.variant` specifies what kind of physical devices would be served by -the response and helps Rails determine which template to use in the response. -It is empty because no information has been provided. +Next the message contains `request.formats` which specifies the format of +template to be served in response. It is set to `text/html` as we requested +this page via browser, so Rails is looking for an HTML template. The simplest template that would work in this case would be one located at `app/views/articles/new.html.erb`. The extension of this file name is important: @@ -686,7 +681,7 @@ If you look in the `db/migrate/YYYYMMDDHHMMSS_create_articles.rb` file (remember, yours will have a slightly different name), here's what you'll find: ```ruby -class CreateArticles < ActiveRecord::Migration[5.0] +class CreateArticles < ActiveRecord::Migration[6.0] def change create_table :articles do |t| t.string :title @@ -1212,7 +1207,7 @@ view above, will cause form helpers to fill in form fields with the correspondin values of the object. Passing in a symbol scope such as `scope: :article`, as was done in the new view, only creates empty form fields. More details can be found in [form_with documentation] -(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with). +(https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with). Next, we need to create the `update` action in `app/controllers/articles_controller.rb`. @@ -1348,7 +1343,7 @@ to stand in for either of the other forms is that `@article` is a *resource* corresponding to a full set of RESTful routes, and Rails is able to infer which URI and method to use. For more information about this use of `form_with`, see [Resource-oriented style] -(http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with-label-Resource-oriented+style). +(https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with-label-Resource-oriented+style). Now, let's update the `app/views/articles/new.html.erb` view to use this new partial, rewriting it completely: @@ -1558,12 +1553,12 @@ In addition to the model, Rails has also made a migration to create the corresponding database table: ```ruby -class CreateComments < ActiveRecord::Migration[5.0] +class CreateComments < ActiveRecord::Migration[6.0] def change create_table :comments do |t| t.string :commenter t.text :body - t.references :article, foreign_key: true + t.references :article, null: false, foreign_key: true t.timestamps end @@ -1655,7 +1650,7 @@ controller. Again, we'll use the same generator we used before: $ rails generate controller Comments ``` -This creates five files and one empty directory: +This creates four files and one empty directory: | File/Directory | Purpose | | -------------------------------------------- | ---------------------------------------- | diff --git a/guides/source/i18n.md b/guides/source/i18n.md index 10b1a6de7e..5d91a6df33 100644 --- a/guides/source/i18n.md +++ b/guides/source/i18n.md @@ -139,10 +139,12 @@ Note that appending directly to `I18n.load_paths` instead of to the application' ### Managing the Locale across Requests -The default locale is used for all translations unless `I18n.locale` is explicitly set. - A localized application will likely need to provide support for multiple locales. To accomplish this, the locale should be set at the beginning of each request so that all strings are translated using the desired locale during the lifetime of that request. +The default locale is used for all translations unless `I18n.locale=` or `I18n.with_locale` is used. + +`I18n.locale` can leak into subsequent requests served by the same thread/process if it is not consistently set in every controller. For example executing `I18n.locale = :es` in one POST requests will have effects for all later requests to controllers that don't set the locale, but only in that particular thread/process. For that reason, instead of `I18n.locale =` you can use `I18n.with_locale` which does not have this leak issue. + The locale can be set in an `around_action` in the `ApplicationController`: ```ruby @@ -220,7 +222,7 @@ This approach has almost the same set of advantages as setting the locale from t Getting the locale from `params` and setting it accordingly is not hard; including it in every URL and thus **passing it through the requests** is. To include an explicit option in every URL, e.g. `link_to(books_url(locale: I18n.locale))`, would be tedious and probably impossible, of course. -Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding `default_url_options`). +Rails contains infrastructure for "centralizing dynamic decisions about the URLs" in its [`ApplicationController#default_url_options`](https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Base.html#method-i-default_url_options), which is useful precisely in this scenario: it enables us to set "defaults" for [`url_for`](https://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for) and helper methods dependent on it (by implementing/overriding `default_url_options`). We can include something like this in our `ApplicationController` then: @@ -235,7 +237,7 @@ Every helper method dependent on `url_for` (e.g. helpers for named routes like ` You may be satisfied with this. It does impact the readability of URLs, though, when the locale "hangs" at the end of every URL in your application. Moreover, from the architectural standpoint, locale is usually hierarchically above the other parts of the application domain: and URLs should reflect this. -You probably want URLs to look like this: `http://www.example.com/en/books` (which loads the English locale) and `http://www.example.com/nl/books` (which loads the Dutch locale). This is achievable with the "over-riding `default_url_options`" strategy from above: you just have to set up your routes with [`scope`](http://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html): +You probably want URLs to look like this: `http://www.example.com/en/books` (which loads the English locale) and `http://www.example.com/nl/books` (which loads the Dutch locale). This is achievable with the "over-riding `default_url_options`" strategy from above: you just have to set up your routes with [`scope`](https://api.rubyonrails.org/classes/ActionDispatch/Routing/Mapper/Scoping.html): ```ruby # config/routes.rb @@ -299,7 +301,7 @@ A trivial implementation of using an `Accept-Language` header would be: def switch_locale(&action) logger.debug "* Accept-Language: #{request.env['HTTP_ACCEPT_LANGUAGE']}" locale = extract_locale_from_accept_language_header - logger.debug "* Locale set to '#{I18n.locale}'" + logger.debug "* Locale set to '#{locale}'" I18n.with_locale(locale, &action) end @@ -594,7 +596,7 @@ You should have a good understanding of using the i18n library now and know how to internationalize a basic Rails application. In the following chapters, we'll cover its features in more depth. -These chapters will show examples using both the `I18n.translate` method as well as the [`translate` view helper method](http://api.rubyonrails.org/classes/ActionView/Helpers/TranslationHelper.html#method-i-translate) (noting the additional feature provide by the view helper method). +These chapters will show examples using both the `I18n.translate` method as well as the [`translate` view helper method](https://api.rubyonrails.org/classes/ActionView/Helpers/TranslationHelper.html#method-i-translate) (noting the additional feature provide by the view helper method). Covered are features like these: @@ -990,27 +992,6 @@ So, for example, instead of the default error message `"cannot be blank"` you co | numericality | :odd | :odd | - | | numericality | :even | :even | - | -#### Translations for the Active Record `error_messages_for` Helper - -If you are using the Active Record `error_messages_for` helper, you will want to add -translations for it. - -Rails ships with the following translations: - -```yaml -en: - activerecord: - errors: - template: - header: - one: "1 error prohibited this %{model} from being saved" - other: "%{count} errors prohibited this %{model} from being saved" - body: "There were problems with the following fields:" -``` - -NOTE: In order to use this helper, you need to install [DynamicForm](https://github.com/joelmoss/dynamic_form) -gem by adding this line to your `Gemfile`: `gem 'dynamic_form'`. - ### Translations for Action Mailer E-Mail Subjects If you don't pass a subject to the `mail` method, Action Mailer will try to find @@ -1204,7 +1185,7 @@ The I18n API described in this guide is primarily intended for translating inter Several gems can help with this: * [Globalize](https://github.com/globalize/globalize): Store translations on separate translation tables, one for each translated model -* [Mobility](https://github.com/shioyama/mobility): Provides support for storing translations in many formats, including translation tables, json columns (Postgres), etc. +* [Mobility](https://github.com/shioyama/mobility): Provides support for storing translations in many formats, including translation tables, json columns (PostgreSQL), etc. * [Traco](https://github.com/barsoom/traco): Translatable columns for Rails 3 and 4, stored in the model table itself Conclusion diff --git a/guides/source/index.html.erb b/guides/source/index.html.erb index 76f01fea0a..10e388774c 100644 --- a/guides/source/index.html.erb +++ b/guides/source/index.html.erb @@ -1,6 +1,5 @@ -<% content_for :page_title do %> -Ruby on Rails Guides -<% end %> +<% content_for :page_title, "Ruby on Rails Guides" %> +<% content_for :description, "Ruby on Rails Guides" %> <% content_for :header_section do %> <%= render 'welcome' %> diff --git a/guides/source/initialization.md b/guides/source/initialization.md index c41eae18cf..556c85cc0f 100644 --- a/guides/source/initialization.md +++ b/guides/source/initialization.md @@ -108,6 +108,8 @@ A standard Rails application depends on several gems, specifically: * activerecord * activestorage * activesupport +* actionmailbox +* actiontext * arel * builder * bundler @@ -160,8 +162,8 @@ namespace and executes the command if found. If Rails doesn't recognize the command, it hands the reins over to Rake to run a task of the same name. -As shown, `Rails::Command` displays the help output automatically if the `args` -are empty. +As shown, `Rails::Command` displays the help output automatically if the `namespace` +is empty. ```ruby module Rails::Command @@ -289,7 +291,7 @@ def default_options environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup, daemonize: false, caching: nil, - pid: Options::DEFAULT_PID_PATH, + pid: ENV.fetch("PIDFILE", Options::DEFAULT_PIDFILE).dup, restart_cmd: restart_command) end ``` @@ -538,6 +540,8 @@ require "rails" action_mailer/railtie active_job/railtie action_cable/engine + action_mailbox/engine + action_text/engine rails/test_unit/railtie sprockets/railtie ).each do |railtie| diff --git a/guides/source/layout.html.erb b/guides/source/layout.html.erb index 1f42d72756..3ffd7ff1ac 100644 --- a/guides/source/layout.html.erb +++ b/guides/source/layout.html.erb @@ -3,7 +3,7 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title><%= yield(:page_title) || 'Ruby on Rails Guides' %></title> + <title><%= yield(:page_title) %></title> <link rel="stylesheet" type="text/css" href="stylesheets/style.css" data-turbolinks-track="reload"> <link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print"> <link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" data-turbolinks-track="reload"> @@ -14,6 +14,13 @@ <script src="javascripts/turbolinks.js" data-turbolinks-track="reload"></script> <script src="javascripts/guides.js" data-turbolinks-track="reload"></script> <script src="javascripts/responsive-tables.js" data-turbolinks-track="reload"></script> + <meta property="og:title" content="<%= yield(:page_title) %>" /> + <meta name="description" content="<%= yield(:description) %>" /> + <meta property="og:description" content="<%= yield(:description) %>" /> + <meta property="og:locale" content="en_US" /> + <meta property="og:site_name" content="Ruby on Rails Guides" /> + <meta property="og:image" content="https://avatars.githubusercontent.com/u/4223" /> + <meta property="og:type" content="website" /> </head> <body class="guide"> <% if @edge %> @@ -23,14 +30,14 @@ <% end %> <div id="topNav"> <div class="wrapper"> - <strong class="more-info-label">More at <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong> + <strong class="more-info-label">More at <a href="https://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="https://weblog.rubyonrails.org/">Blog</a></li> <li class="more-info"><a href="https://guides.rubyonrails.org/">Guides</a></li> - <li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li> + <li class="more-info"><a href="https://api.rubyonrails.org/">API</a></li> <li class="more-info"><a href="https://stackoverflow.com/questions/tagged/ruby-on-rails">Ask for help</a></li> <li class="more-info"><a href="https://github.com/rails/rails">Contribute on GitHub</a></li> </ul> diff --git a/guides/source/layouts_and_rendering.md b/guides/source/layouts_and_rendering.md index ad08e5a5a9..ce90a60e36 100644 --- a/guides/source/layouts_and_rendering.md +++ b/guides/source/layouts_and_rendering.md @@ -97,7 +97,7 @@ If we want to display the properties of all the books in our view, we can do so <%= link_to "New book", new_book_path %> ``` -NOTE: The actual rendering is done by nested classes of the module [`ActionView::Template::Handlers`](http://api.rubyonrails.org/classes/ActionView/Template/Handlers.html). This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. +NOTE: The actual rendering is done by nested classes of the module [`ActionView::Template::Handlers`](https://api.rubyonrails.org/classes/ActionView/Template/Handlers.html). This guide does not dig into that process, but it's important to know that the file extension on your view controls the choice of template handler. ### Using `render` @@ -149,25 +149,6 @@ Rails knows that this view belongs to a different controller because of the embe render template: "products/show" ``` -#### Rendering an Arbitrary File - -The `render` method can also use a view that's entirely outside of your application: - -```ruby -render file: "/u/apps/warehouse_app/current/app/views/products/show" -``` - -The `:file` option takes an absolute file-system path. Of course, you need to have rights -to the view that you're using to render the content. - -NOTE: Using the `:file` option in combination with users input can lead to security problems -since an attacker could use this action to access security sensitive files in your file system. - -NOTE: By default, the file is rendered using the current layout. - -TIP: If you're running Rails on Microsoft Windows, you should use the `:file` option to -render a file, because Windows filenames do not have the same format as Unix filenames. - #### Wrapping it up The above three ways of rendering (rendering another template within the controller, rendering a template within another controller, and rendering an arbitrary file on the file system) are actually variants of the same action. @@ -178,17 +159,9 @@ In fact, in the BooksController class, inside of the update action where we want render :edit render action: :edit render "edit" -render "edit.html.erb" render action: "edit" -render action: "edit.html.erb" render "books/edit" -render "books/edit.html.erb" render template: "books/edit" -render template: "books/edit.html.erb" -render "/path/to/rails/app/views/books/edit" -render "/path/to/rails/app/views/books/edit.html.erb" -render file: "/path/to/rails/app/views/books/edit" -render file: "/path/to/rails/app/views/books/edit.html.erb" ``` Which one you use is really a matter of style and convention, but the rule of thumb is to use the simplest one that makes sense for the code you are writing. @@ -287,6 +260,23 @@ time. NOTE: Unless overridden, your response returned from this render option will be `text/plain`, as that is the default content type of Action Dispatch response. +#### Rendering raw file + +Rails can render a raw file from an absolute path. This is useful for +conditionally rendering static files like error pages. + +```ruby +render file: "#{Rails.root}/public/404.html", layout: false +``` + +This renders the raw file (it doesn't support ERB or other handlers). By +default it is rendered within the current layout. + +WARNING: Using the `:file` option in combination with users input can lead to security problems +since an attacker could use this action to access security sensitive files in your file system. + +TIP: `send_file` is often a faster and better option if a layout isn't required. + #### Options for `render` Calls to the `render` method generally accept five options: @@ -296,13 +286,14 @@ Calls to the `render` method generally accept five options: * `:location` * `:status` * `:formats` +* `:variants` ##### The `:content_type` Option By default, Rails will serve the results of a rendering operation with the MIME content-type of `text/html` (or `application/json` if you use the `:json` option, or `application/xml` for the `:xml` option.). There are times when you might like to change this, and you can do so by setting the `:content_type` option: ```ruby -render file: filename, content_type: "application/rss" +render template: "feed", content_type: "application/rss" ``` ##### The `:layout` Option @@ -417,6 +408,44 @@ render formats: [:json, :xml] If a template with the specified format does not exist an `ActionView::MissingTemplate` error is raised. +##### The `:variants` Option + +This tells Rails to look for template variations of the same format. +You can specify a list of variants by passing the `:variants` option with a symbol or an array. + +An example of use would be this. + +```ruby +# called in HomeController#index +render variants: [:mobile, :desktop] +``` + +With this set of variants Rails will look for the following set of templates and use the first that exists. + +- `app/views/home/index.html+mobile.erb` +- `app/views/home/index.html+desktop.erb` +- `app/views/home/index.html.erb` + +If a template with the specified format does not exist an `ActionView::MissingTemplate` error is raised. + +Instead of setting the variant on the render call you may also set it on the request object in your controller action. + +```ruby +def index + request.variant = determine_variant +end + +private + +def determine_variant + variant = nil + # some code to determine the variant(s) to use + variant = :mobile if session[:use_mobile] + + variant +end +``` + #### Finding Layouts To find the current layout, Rails first looks for a file in `app/views/layouts` with the same base name as the controller. For example, rendering actions from the `PhotosController` class will use `app/views/layouts/photos.html.erb` (or `app/views/layouts/photos.builder`). If there is no such controller-specific layout, Rails will use `app/views/layouts/application.html.erb` or `app/views/layouts/application.builder`. If there is no `.erb` layout, Rails will use a `.builder` layout if one exists. Rails also provides several ways to more precisely assign specific layouts to individual controllers and actions. @@ -650,7 +679,7 @@ Just like the `:status` option for `render`, `:status` for `redirect_to` accepts #### The Difference Between `render` and `redirect_to` -Sometimes inexperienced developers think of `redirect_to` as a sort of `goto` command, moving execution from one place to another in your Rails code. This is _not_ correct. Your code stops running and waits for a new request for the browser. It just happens that you've told the browser what request it should make next, by sending back an HTTP 302 status code. +Sometimes inexperienced developers think of `redirect_to` as a sort of `goto` command, moving execution from one place to another in your Rails code. This is _not_ correct. Your code stops running and waits for a new request from the browser. It just happens that you've told the browser what request it should make next, by sending back an HTTP 302 status code. Consider these actions to see the difference: diff --git a/guides/source/rails_application_templates.md b/guides/source/rails_application_templates.md index bc68a555c5..e0e79fc41b 100644 --- a/guides/source/rails_application_templates.md +++ b/guides/source/rails_application_templates.md @@ -82,10 +82,10 @@ end Adds the given source to the generated application's `Gemfile`. -For example, if you need to source a gem from `"http://code.whytheluckystiff.net"`: +For example, if you need to source a gem from `"http://gems.github.com"`: ```ruby -add_source "http://code.whytheluckystiff.net" +add_source "http://gems.github.com" ``` If block is given, gem entries in block are wrapped into the source group. @@ -195,6 +195,12 @@ You can also run commands as a super-user: rails_command "log:clear", sudo: true ``` +You can also run commands that should abort application generation if they fail: + +```ruby +rails_command "db:migrate", abort_on_failure: true +``` + ### route(routing_code) Adds a routing entry to the `config/routes.rb` file. In the steps above, we generated a person scaffold and also removed `README.rdoc`. Now, to make `PeopleController#index` the default page for the application: diff --git a/guides/source/rails_on_rack.md b/guides/source/rails_on_rack.md index c33851a0f9..d60e53b052 100644 --- a/guides/source/rails_on_rack.md +++ b/guides/source/rails_on_rack.md @@ -13,7 +13,7 @@ After reading this guide, you will know: -------------------------------------------------------------------------------- -WARNING: This guide assumes a working knowledge of Rack protocol and Rack concepts such as middlewares, url maps, and `Rack::Builder`. +WARNING: This guide assumes a working knowledge of Rack protocol and Rack concepts such as middlewares, URL maps, and `Rack::Builder`. Introduction to Rack -------------------- @@ -35,7 +35,7 @@ application. Any Rack compliant web server should be using ### `rails server` -`rails server` does the basic job of creating a `Rack::Server` object and starting the webserver. +`rails server` does the basic job of creating a `Rack::Server` object and starting the web server. Here's how `rails server` creates an instance of `Rack::Server` diff --git a/guides/source/routing.md b/guides/source/routing.md index 84de727c11..4aeb9ee585 100644 --- a/guides/source/routing.md +++ b/guides/source/routing.md @@ -260,7 +260,7 @@ In each of these cases, the named routes remain the same as if you did not use ` | PATCH/PUT | /admin/articles/:id | articles#update | article_path(:id) | | DELETE | /admin/articles/:id | articles#destroy | article_path(:id) | -TIP: _If you need to use a different controller namespace inside a `namespace` block you can specify an absolute controller path, e.g: `get '/foo' => '/foo#index'`._ +TIP: _If you need to use a different controller namespace inside a `namespace` block you can specify an absolute controller path, e.g: `get '/foo', to: '/foo#index'`._ ### Nested Resources @@ -508,7 +508,7 @@ end This will recognize `/photos/1/preview` with GET, and route to the `preview` action of `PhotosController`, with the resource id value passed in `params[:id]`. It will also create the `preview_photo_url` and `preview_photo_path` helpers. -Within the block of member routes, each route name specifies the HTTP verb +Within the block of member routes, each route name specifies the HTTP verb that will be recognized. You can use `get`, `patch`, `put`, `post`, or `delete` here . If you don't have multiple `member` routes, you can also pass `:on` to a route, eliminating the block: @@ -519,7 +519,7 @@ resources :photos do end ``` -You can leave out the `:on` option, this will create the same member route except that the resource id value will be available in `params[:photo_id]` instead of `params[:id]`. +You can leave out the `:on` option, this will create the same member route except that the resource id value will be available in `params[:photo_id]` instead of `params[:id]`. Route helpers will also be renamed from `preview_photo_url` and `preview_photo_path` to `photo_preview_url` and `photo_preview_path`. #### Adding Collection Routes @@ -543,6 +543,8 @@ resources :photos do end ``` +NOTE: If you're defining additional resource routes with a symbol as the first positional argument, be mindful that it is not equivalent to using a string. Symbols infer controller actions while strings infer paths. + #### Adding Routes for Additional New Actions To add an alternate new action using the `:on` shortcut: @@ -1188,6 +1190,33 @@ For example, here's a small section of the `rails routes` output for a RESTful r edit_user GET /users/:id/edit(.:format) users#edit ``` +You can also use the `--expanded` option to turn on the expanded table formatting mode. + +``` +$ rails routes --expanded + +--[ Route 1 ]---------------------------------------------------- +Prefix | users +Verb | GET +URI | /users(.:format) +Controller#Action | users#index +--[ Route 2 ]---------------------------------------------------- +Prefix | +Verb | POST +URI | /users(.:format) +Controller#Action | users#create +--[ Route 3 ]---------------------------------------------------- +Prefix | new_user +Verb | GET +URI | /users/new(.:format) +Controller#Action | users#new +--[ Route 4 ]---------------------------------------------------- +Prefix | edit_user +Verb | GET +URI | /users/:id/edit(.:format) +Controller#Action | users#edit +``` + You can search through your routes with the grep option: -g. This outputs any routes that partially match the URL helper method name, the HTTP verb, or the URL path. ``` @@ -1205,11 +1234,11 @@ $ rails routes -c Comments $ rails routes -c Articles::CommentsController ``` -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. You can also use --expanded option to turn on the expanded table formatting mode. +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 -Routes should be included in your testing strategy (just like the rest of your application). Rails offers three [built-in assertions](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html) designed to make testing routes simpler: +Routes should be included in your testing strategy (just like the rest of your application). Rails offers three [built-in assertions](https://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html) designed to make testing routes simpler: * `assert_generates` * `assert_recognizes` diff --git a/guides/source/ruby_on_rails_guides_guidelines.md b/guides/source/ruby_on_rails_guides_guidelines.md index 4b56cf6296..67b0e523a7 100644 --- a/guides/source/ruby_on_rails_guides_guidelines.md +++ b/guides/source/ruby_on_rails_guides_guidelines.md @@ -58,7 +58,7 @@ Links to the API (`api.rubyonrails.org`) are processed by the guides generator i Links that include a release tag are left untouched. For example ``` -http://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html +https://api.rubyonrails.org/v5.0.1/classes/ActiveRecord/Attributes/ClassMethods.html ``` is not modified. @@ -68,25 +68,25 @@ Please use these in release notes, since they should point to the corresponding If the link does not include a release tag and edge guides are being generated, the domain is replaced by `edgeapi.rubyonrails.org`. For example, ``` -http://api.rubyonrails.org/classes/ActionDispatch/Response.html +https://api.rubyonrails.org/classes/ActionDispatch/Response.html ``` becomes ``` -http://edgeapi.rubyonrails.org/classes/ActionDispatch/Response.html +https://edgeapi.rubyonrails.org/classes/ActionDispatch/Response.html ``` If the link does not include a release tag and release guides are being generated, the Rails version is injected. For example, if we are generating the guides for v5.1.0 the link ``` -http://api.rubyonrails.org/classes/ActionDispatch/Response.html +https://api.rubyonrails.org/classes/ActionDispatch/Response.html ``` becomes ``` -http://api.rubyonrails.org/v5.1.0/classes/ActionDispatch/Response.html +https://api.rubyonrails.org/v5.1.0/classes/ActionDispatch/Response.html ``` Please don't link to `edgeapi.rubyonrails.org` manually. diff --git a/guides/source/security.md b/guides/source/security.md index dbec3cdd2d..5bb7a51524 100644 --- a/guides/source/security.md +++ b/guides/source/security.md @@ -32,27 +32,17 @@ In order to develop secure web applications you have to keep up to date on all l Sessions -------- -A good place to start looking at security is with sessions, which can be vulnerable to particular attacks. +This chapter describes some particular attacks related to sessions, and security measures to protect your session data. ### What are Sessions? -NOTE: _HTTP is a stateless protocol. Sessions make it stateful._ +INFO: Sessions enable the application to maintain user-specific state, while users interact with the application. For example, sessions allow users to authenticate once and remain signed in for future requests. -Most applications need to keep track of certain state of a particular user. This could be the contents of a shopping basket or the user id of the currently logged in user. Without the idea of sessions, the user would have to identify, and probably authenticate, on every request. -Rails will create a new session automatically if a new user accesses the application. It will load an existing session if the user has already used the application. +Most applications need to keep track of state for users that interact with the application. This could be the contents of a shopping basket, or the user id of the currently logged in user. This kind of user-specific state can be stored in the session. -A session usually consists of a hash of values and a session ID, usually a 32-character string, to identify the hash. Every cookie sent to the client's browser includes the session ID. And the other way round: the browser will send it to the server on every request from the client. In Rails you can save and retrieve values using the session method: +Rails provides a session object for each user that accesses the application. If the user already has an active session, Rails uses the existing session. Otherwise a new session is created. -```ruby -session[:user_id] = @current_user.id -User.find(session[:user_id]) -``` - -### Session ID - -NOTE: _The session ID is a 32-character random hex string._ - -The session ID is generated using `SecureRandom.hex` which generates a random hex string using platform specific methods (such as OpenSSL, /dev/urandom or Win32 CryptoAPI) for generating cryptographically secure random numbers. Currently it is not feasible to brute-force Rails' session IDs. +NOTE: Read more about sessions and how to use them in [Action Controller Overview Guide](action_controller_overview.html#session). ### Session Hijacking @@ -76,70 +66,43 @@ Hence, the cookie serves as temporary authentication for the web application. An The main objective of most attackers is to make money. The underground prices for stolen bank login accounts range from 0.5%-10% of account balance, $0.5-$30 for credit card numbers ($20-$60 with full details), $0.1-$1.5 for identities (Name, SSN & DOB), $20-$50 for retailer accounts, and $6-$10 for cloud service provider accounts, according to the [Symantec Internet Security Threat Report (2017)](https://www.symantec.com/content/dam/symantec/docs/reports/istr-22-2017-en.pdf). -### Session Guidelines - -Here are some general guidelines on sessions. - -* _Do not store large objects in a session_. Instead you should store them in the database and save their id in the session. This will eliminate synchronization headaches and it won't fill up your session storage space (depending on what session storage you chose, see below). -This will also be a good idea, if you modify the structure of an object and old versions of it are still in some user's cookies. With server-side session storages you can clear out the sessions, but with client-side storages, this is hard to mitigate. - -* _Critical data should not be stored in session_. If the user clears their cookies or closes the browser, they will be lost. And with a client-side session storage, the user can read the data. +### Session Storage -### Encrypted Session Storage +NOTE: Rails uses `ActionDispatch::Session::CookieStore` as the default session storage. -NOTE: _Rails provides several storage mechanisms for the session hashes. The most important is `ActionDispatch::Session::CookieStore`._ +TIP: Learn more about other session storages in [Action Controller Overview Guide](action_controller_overview.html#session). -The `CookieStore` saves the session hash directly in a cookie on the -client-side. The server retrieves the session hash from the cookie and +Rails `CookieStore` saves the session hash in a cookie on the client-side. +The server retrieves the session hash from the cookie and eliminates the need for a session ID. That will greatly increase the speed of the application, but it is a controversial storage option and you have to think about the security implications and storage limitations of it: -* Cookies imply a strict size limit of 4kB. This is fine as you should - not store large amounts of data in a session anyway, as described - before. Storing the current user's database id in a session is common - practice. +* Cookies have a size limit of 4kB. Use cookies only for data which is relevant for the session. + +* Cookies are stored on the client-side. The client may preserve cookie contents even for expired cookies. The client may copy cookies to other machines. Avoid storing sensitive data in cookies. + +* Cookies are temporary by nature. The server can set expiration time for the cookie, but the client may delete the cookie and its contents before that. Persist all data that is of more permanent nature on the server side. * Session cookies do not invalidate themselves and can be maliciously reused. It may be a good idea to have your application invalidate old session cookies using a stored timestamp. +* Rails encrypts cookies by default. The client cannot read or edit the contents of the cookie, without breaking encryption. If you take appropriate care of your secrets, you can consider your cookies to be generally secured. + The `CookieStore` uses the -[encrypted](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-encrypted) +[encrypted](https://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-encrypted) cookie jar to provide a secure, encrypted location to store session data. Cookie-based sessions thus provide both integrity as well as confidentiality to their contents. The encryption key, as well as the verification key used for -[signed](http://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-signed) +[signed](https://api.rubyonrails.org/classes/ActionDispatch/Cookies/ChainedCookieJars.html#method-i-signed) cookies, is derived from the `secret_key_base` configuration value. -As of Rails 5.2 encrypted cookies and sessions are protected using AES -GCM encryption. This form of encryption is a type of Authenticated -Encryption and couples authentication and encryption in single step -while also producing shorter ciphertexts as compared to other -algorithms previously used. The key for cookies encrypted with AES GCM -are derived using a salt value defined by the -`config.action_dispatch.authenticated_encrypted_cookie_salt` -configuration value. - -Prior to this version, encrypted cookies were secured using AES in CBC -mode with HMAC using SHA1 for authentication. The keys for this type of -encryption and for HMAC verification were derived via the salts defined -by `config.action_dispatch.encrypted_cookie_salt` and -`config.action_dispatch.encrypted_signed_cookie_salt` respectively. - -Prior to Rails version 4 in both versions 2 and 3, session cookies were -protected using only HMAC verification. As such, these session cookies -only provided integrity to their content because the actual session data -was stored in plaintext encoded as base64. This is how `signed` cookies -work in the current version of Rails. These kinds of cookies are still -useful for protecting the integrity of certain client-stored data and -information. - -__Do not use a trivial secret for the `secret_key_base`, i.e. a word -from a dictionary, or one which is shorter than 30 characters! Instead -use `rails secret` to generate secret keys!__ +TIP: Secrets must be long and random. Use `rails secret` to get new unique secrets. + +INFO: Learn more about [managing credentials later in this guide](security.html#custom-credentials) It is also important to use different salt values for encrypted and signed cookies. Using the same value for different salt configuration @@ -150,7 +113,7 @@ In test and development applications get a `secret_key_base` derived from the ap secret_key_base: 492f... -If you have received an application where the secret was exposed (e.g. an application whose source was shared), strongly consider changing the secret. +WARNING: If your application's secrets may have been exposed, strongly consider changing them. Changing `secret_key_base` will expire currently active sessions. ### Rotating Encrypted and Signed Cookies Configurations @@ -192,9 +155,9 @@ rotations going at any one time. For more details on key rotation with encrypted and signed messages as well as the various options the `rotate` method accepts, please refer to the -[MessageEncryptor API](http://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html) +[MessageEncryptor API](https://api.rubyonrails.org/classes/ActiveSupport/MessageEncryptor.html) and -[MessageVerifier API](http://api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html) +[MessageVerifier API](https://api.rubyonrails.org/classes/ActiveSupport/MessageVerifier.html) documentation. ### Replay Attacks for CookieStore Sessions @@ -1188,7 +1151,7 @@ The same works with `javascript_include_tag`: <%= javascript_include_tag "script", nonce: true %> ``` -Use [`csp_meta_tag`](http://api.rubyonrails.org/classes/ActionView/Helpers/CspHelper.html#method-i-csp_meta_tag) +Use [`csp_meta_tag`](https://api.rubyonrails.org/classes/ActionView/Helpers/CspHelper.html#method-i-csp_meta_tag) helper to create a meta tag "csp-nonce" with the per-session nonce value for allowing inline `<script>` tags. @@ -1204,23 +1167,18 @@ loaded inline `<script>` elements. Environmental Security ---------------------- -It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, and your server-side secret, e.g. stored in `config/secrets.yml`. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information. +It is beyond the scope of this guide to inform you on how to secure your application code and environments. However, please secure your database configuration, e.g. `config/database.yml`, master key for `credentials.yml`, and other unencrypted secrets. You may want to further restrict access, using environment-specific versions of these files and any others that may contain sensitive information. ### Custom credentials -Rails generates a `config/credentials.yml.enc` to store third-party credentials -within the repo. This is only viable because Rails encrypts the file with a master -key that's generated into a version control ignored `config/master.key` — Rails -will also look for that key in `ENV["RAILS_MASTER_KEY"]`. Rails also requires the -key to boot in production, so the credentials can be read. +Rails stores secrets in `config/credentials.yml.enc`, which is encrypted and hence cannot be edited directly. Rails uses `config/master.key` or alternatively looks for environment variable `ENV["RAILS_MASTER_KEY"]` to encrypt the credentials file. The credentials file can be stored in version control, as long as master key is kept safe. -To edit stored credentials use `rails credentials:edit`. +To add new secret to credentials, first run `rails secret` to get a new secret. Then run `rails credentials:edit` to edit credentials, and add the secret. Running `credentials:edit` creates new credentials file and master key, if they did not already exist. By default, this file contains the application's -`secret_key_base`, but it could also be used to store other credentials such as -access keys for external APIs. +`secret_key_base`, but it could also be used to store other credentials such as access keys for external APIs. -The credentials added to this file are accessible via `Rails.application.credentials`. +The secrets kept in credentials file are accessible via `Rails.application.credentials`. For example, with the following decrypted `config/credentials.yml.enc`: secret_key_base: 3b7cd727ee24e8444053437c36cc66c3 @@ -1235,6 +1193,11 @@ version: Rails.application.credentials.some_api_key! # => raises KeyError: :some_api_key is blank ``` + +TIP: Learn more about credentials with `rails credentials:help`. + +WARNING: Keep your master key safe. Do not commit your master key. + Dependency Management and CVEs ------------------------------ @@ -1248,4 +1211,4 @@ The security landscape shifts and it is important to keep up to date, because mi * Subscribe to the Rails security [mailing list](https://groups.google.com/forum/#!forum/rubyonrails-security). * [Brakeman - Rails Security Scanner](https://brakemanscanner.org/) - To perform static security analysis for Rails applications. * [Keep up to date on the other application layers](http://secunia.com/) (they have a weekly newsletter, too). -* A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet](https://www.owasp.org/index.php/DOM_based_XSS_Prevention_Cheat_Sheet). +* A [good security blog](https://www.owasp.org) including the [Cross-Site scripting Cheat Sheet](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.md). diff --git a/guides/source/testing.md b/guides/source/testing.md index 8c21ccfba6..41bc54b924 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -33,11 +33,11 @@ Rails creates a `test` directory for you as soon as you create a Rails project u ```bash $ ls -F test -application_system_test_case.rb fixtures/ integration/ models/ test_helper.rb -controllers/ helpers/ mailers/ system/ +application_system_test_case.rb controllers/ helpers/ mailers/ system/ +channels/ fixtures/ integration/ models/ test_helper.rb ``` -The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers. +The `helpers`, `mailers`, and `models` directories are meant to hold tests for view helpers, mailers, and models, respectively. The `channels` directory is meant to hold tests for Action Cable connection and channels. The `controllers` directory is meant to hold tests for controllers, routes, and views. The `integration` directory is meant to hold tests for interactions between controllers. The system test directory holds system tests, which are used for full browser testing of your application. System tests allow you to test your application @@ -350,15 +350,15 @@ Rails adds some custom assertions of its own to the `minitest` framework: | Assertion | Purpose | | --------------------------------------------------------------------------------- | ------- | -| [`assert_difference(expressions, difference = 1, message = nil) {...}`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference) | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| -| [`assert_no_difference(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_difference) | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| -| [`assert_changes(expressions, message = nil, from:, to:, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_changes) | Test that the result of evaluating an expression is changed after invoking the passed in block.| -| [`assert_no_changes(expressions, message = nil, &block)`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_changes) | Test the result of evaluating an expression is not changed after invoking the passed in block.| -| [`assert_nothing_raised { block }`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_nothing_raised) | Ensures that the given block doesn't raise any exceptions.| -| [`assert_recognizes(expected_options, path, extras={}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes) | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| -| [`assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_generates) | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| -| [`assert_response(type, message = nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_response) | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](http://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| -| [`assert_redirected_to(options = {}, message=nil)`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_redirected_to) | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.| +| [`assert_difference(expressions, difference = 1, message = nil) {...}`](https://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_difference) | Test numeric difference between the return value of an expression as a result of what is evaluated in the yielded block.| +| [`assert_no_difference(expressions, message = nil, &block)`](https://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_difference) | Asserts that the numeric result of evaluating an expression is not changed before and after invoking the passed in block.| +| [`assert_changes(expressions, message = nil, from:, to:, &block)`](https://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_changes) | Test that the result of evaluating an expression is changed after invoking the passed in block.| +| [`assert_no_changes(expressions, message = nil, &block)`](https://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_no_changes) | Test the result of evaluating an expression is not changed after invoking the passed in block.| +| [`assert_nothing_raised { block }`](https://api.rubyonrails.org/classes/ActiveSupport/Testing/Assertions.html#method-i-assert_nothing_raised) | Ensures that the given block doesn't raise any exceptions.| +| [`assert_recognizes(expected_options, path, extras={}, message=nil)`](https://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes) | Asserts that the routing of the given path was handled correctly and that the parsed options (given in the expected_options hash) match path. Basically, it asserts that Rails recognizes the route given by expected_options.| +| [`assert_generates(expected_path, options, defaults={}, extras = {}, message=nil)`](https://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_generates) | Asserts that the provided options can be used to generate the provided path. This is the inverse of assert_recognizes. The extras parameter is used to tell the request the names and values of additional request parameters that would be in a query string. The message parameter allows you to specify a custom error message for assertion failures.| +| [`assert_response(type, message = nil)`](https://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_response) | Asserts that the response comes with a specific status code. You can specify `:success` to indicate 200-299, `:redirect` to indicate 300-399, `:missing` to indicate 404, or `:error` to match the 500-599 range. You can also pass an explicit status number or its symbolic equivalent. For more information, see [full list of status codes](http://rubydoc.info/github/rack/rack/master/Rack/Utils#HTTP_STATUS_CODES-constant) and how their [mapping](https://rubydoc.info/github/rack/rack/master/Rack/Utils#SYMBOL_TO_STATUS_CODE-constant) works.| +| [`assert_redirected_to(options = {}, message=nil)`](https://api.rubyonrails.org/classes/ActionDispatch/Assertions/ResponseAssertions.html#method-i-assert_redirected_to) | Asserts that the redirection options passed in match those of the redirect called in the latest action. This match can be partial, such that `assert_redirected_to(controller: "weblog")` will also match the redirection of `redirect_to(controller: "weblog", action: "show")` and so on. You can also pass named routes such as `assert_redirected_to root_path` and Active Record objects such as `assert_redirected_to @article`.| You'll see the usage of some of these assertions in the next chapter. @@ -366,13 +366,13 @@ You'll see the usage of some of these assertions in the next chapter. All the basic assertions such as `assert_equal` defined in `Minitest::Assertions` are also available in the classes we use in our own test cases. In fact, Rails provides the following classes for you to inherit from: -* [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html) -* [`ActionMailer::TestCase`](http://api.rubyonrails.org/classes/ActionMailer/TestCase.html) -* [`ActionView::TestCase`](http://api.rubyonrails.org/classes/ActionView/TestCase.html) -* [`ActiveJob::TestCase`](http://api.rubyonrails.org/classes/ActiveJob/TestCase.html) -* [`ActionDispatch::IntegrationTest`](http://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html) -* [`ActionDispatch::SystemTestCase`](http://api.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html) -* [`Rails::Generators::TestCase`](http://api.rubyonrails.org/classes/Rails/Generators/TestCase.html) +* [`ActiveSupport::TestCase`](https://api.rubyonrails.org/classes/ActiveSupport/TestCase.html) +* [`ActionMailer::TestCase`](https://api.rubyonrails.org/classes/ActionMailer/TestCase.html) +* [`ActionView::TestCase`](https://api.rubyonrails.org/classes/ActionView/TestCase.html) +* [`ActiveJob::TestCase`](https://api.rubyonrails.org/classes/ActiveJob/TestCase.html) +* [`ActionDispatch::IntegrationTest`](https://api.rubyonrails.org/classes/ActionDispatch/IntegrationTest.html) +* [`ActionDispatch::SystemTestCase`](https://api.rubyonrails.org/classes/ActionDispatch/SystemTestCase.html) +* [`Rails::Generators::TestCase`](https://api.rubyonrails.org/classes/Rails/Generators/TestCase.html) Each of these classes include `Minitest::Assertions`, allowing us to use all of the basic assertions in our tests. @@ -473,13 +473,12 @@ takes your entire test suite to run. ### Parallel testing with processes The default parallelization method is to fork processes using Ruby's DRb system. The processes -are forked based on the number of workers provided. The default is 2, but can be changed by the -number passed to the parallelize method. Active Record automatically handles creating and -migrating a new database for each worker to use. +are forked based on the number of workers provided. The default number is the actual core count +on the machine you are on, but can be changed by the number passed to the parallelize method. To enable parallelization add the following to your `test_helper.rb`: -``` +```ruby class ActiveSupport::TestCase parallelize(workers: 2) end @@ -489,35 +488,35 @@ The number of workers passed is the number of times the process will be forked. parallelize your local test suite differently from your CI, so an environment variable is provided to be able to easily change the number of workers a test run should use: -``` +```bash PARALLEL_WORKERS=15 rails test ``` -When parallelizing tests, Active Record automatically handles creating and migrating a database for each +When parallelizing tests, Active Record automatically handles creating a database and loading the schema into the database for each process. The databases will be suffixed with the number corresponding to the worker. For example, if you have 2 workers the tests will create `test-database-0` and `test-database-1` respectively. If the number of workers passed is 1 or fewer the processes will not be forked and the tests will not be parallelized and the tests will use the original `test-database` database. -Two hooks are provided, one runs when the process is forked, and one runs before the processes are closed. +Two hooks are provided, one runs when the process is forked, and one runs before the forked process is closed. These can be useful if your app uses multiple databases or perform other tasks that depend on the number of workers. The `parallelize_setup` method is called right after the processes are forked. The `parallelize_teardown` method is called right before the processes are closed. -``` +```ruby class ActiveSupport::TestCase parallelize_setup do |worker| # setup databases end parallelize_teardown do |worker| - # cleanup database + # cleanup databases end - parallelize(workers: 2) + parallelize(workers: :number_of_processors) end ``` @@ -530,9 +529,9 @@ parallelizer is backed by Minitest's `Parallel::Executor`. To change the parallelization method to use threads over forks put the following in your `test_helper.rb` -``` +```ruby class ActiveSupport::TestCase - parallelize(workers: 2, with: :threads) + parallelize(workers: :number_of_processors, with: :threads) end ``` @@ -542,7 +541,7 @@ The number of workers passed to `parallelize` determines the number of threads t want to parallelize your local test suite differently from your CI, so an environment variable is provided to be able to easily change the number of workers a test run should use: -``` +```bash PARALLEL_WORKERS=15 rails test ``` @@ -573,7 +572,7 @@ be rebuilt. This can be done by executing `rails db:test:prepare`. For good tests, you'll need to give some thought to setting up test data. In Rails, you can handle this by defining and customizing fixtures. -You can find comprehensive documentation in the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). +You can find comprehensive documentation in the [Fixtures API documentation](https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). #### What Are Fixtures? @@ -622,7 +621,7 @@ first: Notice the `category` key of the `first` article found in `fixtures/articles.yml` has a value of `about`. This tells Rails to load the category `about` found in `fixtures/categories.yml`. -NOTE: For associations to reference one another by name, you can use the fixture name instead of specifying the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). +NOTE: For associations to reference one another by name, you can use the fixture name instead of specifying the `id:` attribute on the associated fixtures. Rails will auto assign a primary key to be consistent between runs. For more information on this association behavior please read the [Fixtures API documentation](https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html). #### ERB'in It Up @@ -685,7 +684,7 @@ create test/models/article_test.rb create test/fixtures/articles.yml ``` -Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from [`ActiveSupport::TestCase`](http://api.rubyonrails.org/classes/ActiveSupport/TestCase.html). +Model tests don't have their own superclass like `ActionMailer::TestCase` instead they inherit from [`ActiveSupport::TestCase`](https://api.rubyonrails.org/classes/ActiveSupport/TestCase.html). System Testing -------------- @@ -782,7 +781,7 @@ This can be helpful for viewing the browser at the point a test failed, or to view screenshots later for debugging. Two methods are provided: `take_screenshot` and `take_failed_screenshot`. -`take_failed_screenshot` is automatically included in `after_teardown` inside +`take_failed_screenshot` is automatically included in `before_teardown` inside Rails. The `take_screenshot` helper method can be included anywhere in your tests to @@ -933,11 +932,11 @@ Here the test is inheriting from `ActionDispatch::IntegrationTest`. This makes s In addition to the standard testing helpers, inheriting from `ActionDispatch::IntegrationTest` comes with some additional helpers available when writing integration tests. Let's get briefly introduced to the three categories of helpers we get to choose from. -For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html). +For dealing with the integration test runner, see [`ActionDispatch::Integration::Runner`](https://api.rubyonrails.org/classes/ActionDispatch/Integration/Runner.html). -When performing requests, we will have [`ActionDispatch::Integration::RequestHelpers`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for our use. +When performing requests, we will have [`ActionDispatch::Integration::RequestHelpers`](https://api.rubyonrails.org/classes/ActionDispatch/Integration/RequestHelpers.html) available for our use. -If we need to modify the session, or state of our integration test, take a look at [`ActionDispatch::Integration::Session`](http://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help. +If we need to modify the session, or state of our integration test, take a look at [`ActionDispatch::Integration::Session`](https://api.rubyonrails.org/classes/ActionDispatch/Integration/Session.html) to help. ### Implementing an integration test @@ -1013,7 +1012,7 @@ Finally we can assert that our response was successful and our new article is re #### Taking it further -We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editing comments. Integration tests are a great place to experiment with all kinds of use-cases for our applications. +We were able to successfully test a very small workflow for visiting our blog and creating a new article. If we wanted to take this further we could add tests for commenting, removing articles, or editing comments. Integration tests are a great place to experiment with all kinds of use cases for our applications. Functional Tests for Your Controllers @@ -1145,7 +1144,7 @@ test "ajax request" do get article_url(article), xhr: true assert_equal 'hello world', @response.body - assert_equal "text/javascript", @response.content_type + assert_equal "text/javascript", @response.media_type end ``` @@ -1213,7 +1212,7 @@ Let's start by adding this assertion to our `test_should_create_article` test: ```ruby test "should create article" do assert_difference('Article.count') do - post article_url, params: { article: { title: 'Some title' } } + post articles_url, params: { article: { title: 'Some title' } } end assert_redirected_to article_path(Article.last) @@ -1398,6 +1397,56 @@ class ProfileControllerTest < ActionDispatch::IntegrationTest end ``` +#### Using Separate Files + +If you find your helpers are cluttering `test_helper.rb`, you can extract them into separate files. One good place to store them is `lib/test`. + +```ruby +# lib/test/multiple_assertions.rb +module MultipleAssertions + def assert_multiple_of_forty_two(number) + assert (number % 42 == 0), 'expected #{number} to be a multiple of 42' + end +end +``` + +These helpers can then be explicitly required as needed and included as needed + +```ruby +require 'test_helper' +require 'test/multiple_assertions' + +class NumberTest < ActiveSupport::TestCase + include MultipleAssertions + + test '420 is a multiple of forty two' do + assert_multiple_of_forty_two 420 + end +end +``` + +or they can continue to be included directly into the relevant parent classes + +```ruby +# test/test_helper.rb +require 'test/sign_in_helper' + +class ActionDispatch::IntegrationTest + include SignInHelper +end +``` + +#### Eagerly Requiring Helpers + +You may find it convenient to eagerly require helpers in `test_helper.rb` so your test files have implicit access to them. This can be accomplished using globbing, as follows + +```ruby +# test/test_helper.rb +Dir[Rails.root.join('lib', 'test', '**', '*.rb')].each { |file| require file } +``` + +This has the downside of increasing the boot-up time, as opposed to manually requiring only the necessary files in your individual tests. + Testing Routes -------------- @@ -1405,7 +1454,7 @@ Like everything else in your Rails application, you can test your routes. Route NOTE: If your application has complex routes, Rails provides a number of useful helpers to test them. -For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html). +For more information on routing assertions available in Rails, see the API documentation for [`ActionDispatch::Assertions::RoutingAssertions`](https://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html). Testing Views ------------- @@ -1593,27 +1642,43 @@ NOTE: The `ActionMailer::Base.deliveries` array is only reset automatically in If you want to have a clean slate outside these test cases, you can reset it manually with: `ActionMailer::Base.deliveries.clear` -### Functional Testing +### Functional and System Testing -Functional testing for mailers involves more than just checking that the email body, recipients, and so forth are correct. In functional mail tests you call the mail deliver methods and check that the appropriate emails have been appended to the delivery list. It is fairly safe to assume that the deliver methods themselves do their job. You are probably more interested in whether your own business logic is sending emails when you expect them to go out. For example, you can check that the invite friend operation is sending an email appropriately: +Unit testing allows us to test the attributes of the email while functional and system testing allows us to test whether user interactions appropriately trigger the email to be delivered. For example, you can check that the invite friend operation is sending an email appropriately: ```ruby +# Integration Test require 'test_helper' class UsersControllerTest < ActionDispatch::IntegrationTest test "invite friend" do - assert_difference 'ActionMailer::Base.deliveries.size', +1 do + # Asserts the difference in the ActionMailer::Base.deliveries + assert_emails 1 do post invite_friend_url, params: { email: 'friend@example.com' } end - invite_email = ActionMailer::Base.deliveries.last + end +end +``` + +```ruby +# System Test +require 'test_helper' + +class UsersTest < ActionDispatch::SystemTestCase + driven_by :selenium, using: :headless_chrome - assert_equal "You have been invited by me@example.com", invite_email.subject - assert_equal 'friend@example.com', invite_email.to[0] - assert_match(/Hi friend@example\.com/, invite_email.body.to_s) + test "inviting a friend" do + visit invite_users_url + fill_in 'Email', with: 'friend@example.com' + assert_emails 1 do + click_on 'Invite' + end end end ``` +NOTE: The `assert_emails` method is not tied to a particular deliver method and will work with emails delivered with either the `deliver_now` or `deliver_later` method. If we explicitly want to assert that the email has been enqueued we can use the `assert_enqueued_emails` method. More information can be found in the [documentation here](https://api.rubyonrails.org/classes/ActionMailer/TestHelper.html). + Testing Jobs ------------ @@ -1647,7 +1712,7 @@ no jobs have already been executed in the scope of each test. ### Custom Assertions And Testing Jobs Inside Other Components -Active Job ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActiveJob::TestHelper`](http://api.rubyonrails.org/classes/ActiveJob/TestHelper.html). +Active Job ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActiveJob::TestHelper`](https://api.rubyonrails.org/classes/ActiveJob/TestHelper.html). It's a good practice to ensure that your jobs correctly get enqueued or performed wherever you invoke them (e.g. inside your controllers). This is precisely where @@ -1657,7 +1722,9 @@ within a model: ```ruby require 'test_helper' -class ProductTest < ActiveJob::TestCase +class ProductTest < ActiveSupport::TestCase + include ActiveJob::TestHelper + test 'billing job scheduling' do assert_enqueued_with(job: BillingJob) do product.charge(account) @@ -1666,6 +1733,158 @@ class ProductTest < ActiveJob::TestCase end ``` +### Asserting Time Arguments in Jobs + +When serializing job arguments, `Time`, `DateTime`, and `ActiveSupport::TimeWithZone` lose microsecond precision. This means comparing deserialized time with actual time doesn't always work. To compensate for the loss of precision, `assert_enqueued_with` and `assert_performed_with` will remove microseconds from time objects in argument assertions. + +```ruby +require 'test_helper' + +class ProductTest < ActiveSupport::TestCase + include ActiveJob::TestHelper + + test 'that product is reserved at a given time' do + now = Time.now + assert_performed_with(job: ReservationJob, args: [product, now]) do + product.reserve(now) + end + end +end +``` + +Testing Action Cable +-------------------- + +Since Action Cable is used at different levels inside your application, +you'll need to test both the channels, connection classes themselves, and that other +entities broadcast correct messages. + +### Connection Test Case + +By default, when you generate new Rails application with Action Cable, a test for the base connection class (`ApplicationCable::Connection`) is generated as well under `test/channels/application_cable` directory. + +Connection tests aim to check whether a connection's identifiers get assigned properly +or that any improper connection requests are rejected. Here is an example: + +```ruby +class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase + test "connects with params" do + # Simulate a connection opening by calling the `connect` method + connect params: { user_id: 42 } + + # You can access the Connection object via `connection` in tests + assert_equal connection.user_id, "42" + end + + test "rejects connection without params" do + # Use `assert_reject_connection` matcher to verify that + # connection is rejected + assert_reject_connection { connect } + end +end +``` + +You can also specify request cookies the same way you do in integration tests: + +```ruby +test "connects with cookies" do + cookies.signed[:user_id] = "42" + + connect + + assert_equal connection.user_id, "42" +end +``` + +See the API documentation for [`ActionCable::Connection::TestCase`](https://api.rubyonrails.org/classes/ActionCable/Connection/TestCase.html) for more information. + +### Channel Test Case + +By default, when you generate a channel, an associated test will be generated as well +under the `test/channels` directory. Here's an example test with a chat channel: + +```ruby +require "test_helper" + +class ChatChannelTest < ActionCable::Channel::TestCase + test "subscribes and stream for room" do + # Simulate a subscription creation by calling `subscribe` + subscribe room: "15" + + # You can access the Channel object via `subscription` in tests + assert subscription.confirmed? + assert_has_stream "chat_15" + end +end +``` + +This test is pretty simple and only asserts that the channel subscribes the connection to a particular stream. + +You can also specify the underlying connection identifiers. Here's an example test with a web notifications channel: + +```ruby +require "test_helper" + +class WebNotificationsChannelTest < ActionCable::Channel::TestCase + test "subscribes and stream for user" do + stub_connection current_user: users(:john) + + subscribe + + assert_has_stream_for users(:john) + end +end +``` + +See the API documentation for [`ActionCable::Channel::TestCase`](https://api.rubyonrails.org/classes/ActionCable/Channel/TestCase.html) for more information. + +### Custom Assertions And Testing Broadcasts Inside Other Components + +Action Cable ships with a bunch of custom assertions that can be used to lessen the verbosity of tests. For a full list of available assertions, see the API documentation for [`ActionCable::TestHelper`](https://api.rubyonrails.org/classes/ActionCable/TestHelper.html). + +It's a good practice to ensure that the correct message has been broadcasted inside other components (e.g. inside your controllers). This is precisely where +the custom assertions provided by Action Cable are pretty useful. For instance, +within a model: + +```ruby +require 'test_helper' + +class ProductTest < ActionCable::TestCase + test "broadcast status after charge" do + assert_broadcast_on("products:#{product.id}", type: "charged") do + product.charge(account) + end + end +end +``` + +If you want to test the broadcasting made with `Channel.broadcast_to`, you shoud use +`Channel.broadcasting_for` to generate an underlying stream name: + +```ruby +# app/jobs/chat_relay_job.rb +class ChatRelayJob < ApplicationJob + def perform_later(room, message) + ChatChannel.broadcast_to room, text: message + end +end + +# test/jobs/chat_relay_job_test.rb +require 'test_helper' + +class ChatRelayJobTest < ActiveJob::TestCase + include ActionCable::TestHelper + + test "broadcast message to room" do + room = rooms(:all) + + assert_broadcast_on(ChatChannel.broadcasting_for(room), text: "Hi!") do + ChatRelayJob.perform_now(room, "Hi!") + end + end +end +``` + Additional Testing Resources ---------------------------- @@ -1673,7 +1892,7 @@ Additional Testing Resources Rails provides built-in helper methods that enable you to assert that your time-sensitive code works as expected. -Here is an example using the [`travel_to`](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-travel_to) helper: +Here is an example using the [`travel_to`](https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html#method-i-travel_to) helper: ```ruby # Lets say that a user is eligible for gifting a month after they register. @@ -1686,5 +1905,5 @@ end assert_equal Date.new(2004, 10, 24), user.activation_date # The change was visible only inside the `travel_to` block. ``` -Please see [`ActiveSupport::Testing::TimeHelpers` API Documentation](http://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html) +Please see [`ActiveSupport::Testing::TimeHelpers` API Documentation](https://api.rubyonrails.org/classes/ActiveSupport/Testing/TimeHelpers.html) for in-depth information about the available time helpers. diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index a0553c1ccc..05980d1614 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -35,7 +35,7 @@ You can find a list of all released Rails versions [here](https://rubygems.org/g Rails generally stays close to the latest released Ruby version when it's released: -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. * Rails 5 requires Ruby 2.2.2 or newer. * Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer. * Rails 3.2.x is the last branch to support Ruby 1.8.7. @@ -85,18 +85,367 @@ Rails 6.1. You are encouraged to enable `config.force_ssl` to enforce HTTPS connections throughout your application. If you need to exempt certain endpoints from redirection, you can use `config.ssl_options` to configure that behavior. -### Purpose in signed or encrypted cookie is now embedded in the cookies values +### Purpose and expiry metadata is now embedded inside signed and encrypted cookies for increased security -To improve security, Rails now embeds the purpose information in encrypted or signed cookies value. -Rails can now thwart attacks that attempt to copy signed/encrypted value +To improve security, Rails embeds the purpose and expiry metadata inside encrypted or signed cookies value. + +Rails can then thwart attacks that attempt to copy the signed/encrypted value of a cookie and use it as the value of another cookie. -This new embed information make those cookies incompatible with versions of Rails older than 6.0. +This new embed metadata make those cookies incompatible with versions of Rails older than 6.0. -If you require your cookies to be read by 5.2 and older, or you are still validating your 6.0 deploy and want -to allow you to rollback set +If you require your cookies to be read by Rails 5.2 and older, or you are still validating your 6.0 deploy and want +to be able to rollback set `Rails.application.config.action_dispatch.use_cookies_with_metadata` to `false`. +### Action Cable JavaScript API Changes + +The Action Cable JavaScript package has been converted from CoffeeScript +to ES2015, and we now publish the source code in the npm distribution. + +This release includes some breaking changes to optional parts of the +Action Cable JavaScript API: + +- Configuration of the WebSocket adapter and logger adapter have been moved + from properties of `ActionCable` to properties of `ActionCable.adapters`. + If you are configuring these adapters you will need to make + these changes: + + ```diff + - ActionCable.WebSocket = MyWebSocket + + ActionCable.adapters.WebSocket = MyWebSocket + ``` + ```diff + - ActionCable.logger = myLogger + + ActionCable.adapters.logger = myLogger + ``` + +- The `ActionCable.startDebugging()` and `ActionCable.stopDebugging()` + methods have been removed and replaced with the property + `ActionCable.logger.enabled`. If you are using these methods you + will need to make these changes: + + ```diff + - ActionCable.startDebugging() + + ActionCable.logger.enabled = true + ``` + ```diff + - ActionCable.stopDebugging() + + ActionCable.logger.enabled = false + ``` + +### `ActionDispatch::Response#content_type` now returns the Content-Type header without modification + +Previously, the return value of `ActionDispatch::Response#content_type` did NOT contain the charset part. +This behavior has changed to include the previously omitted charset part as well. + +If you want just the MIME type, please use `ActionDispatch::Response#media_type` instead. + +Before: + +```ruby +resp = ActionDispatch::Response.new(200, "Content-Type" => "text/csv; header=present; charset=utf-16") +resp.content_type #=> "text/csv; header=present" +``` + +After: + +```ruby +resp = ActionDispatch::Response.new(200, "Content-Type" => "text/csv; header=present; charset=utf-16") +resp.content_type #=> "text/csv; header=present; charset=utf-16" +resp.media_type #=> "text/csv" +``` + +### Autoloading + +The default configuration for Rails 6 + +```ruby +# config/application.rb + +config.load_defaults "6.0" +``` + +enables `zeitwerk` autoloading mode on CRuby. In that mode, autoloading, reloading, and eager loading are managed by [Zeitwerk](https://github.com/fxn/zeitwerk). + +#### Public API + +In general, applications do not need to use the API of Zeitwerk directly. Rails sets things up according to the existing contract: `config.autoload_paths`, `config.cache_classes`, etc. + +While applications should stick to that interface, the actual Zeitwerk loader object can be accessed as + +```ruby +Rails.autoloaders.main +``` + +That may be handy if you need to preload STIs or configure a custom inflector, for example. + +#### Project Structure + +If the application being upgraded autoloads correctly, the project structure should be already mostly compatible. + +However, `classic` mode infers file names from missing constant names (`underscore`), whereas `zeitwerk` mode infers constant names from file names (`camelize`). These helpers are not always inverse of each other, in particular if acronyms are involved. For instance, `"FOO".underscore` is `"foo"`, but `"foo".camelize` is `"Foo"`, not `"FOO"`. + +Compatibility can be checked with the `zeitwerk:check` task: + +``` +$ bin/rails zeitwerk:check +Hold on, I am eager loading the application. +All is good! +``` + +#### require_dependency + +All known use cases of `require_dependency` have been eliminated, you should grep the project and delete them. + +If your application has STIs, please check their section in the guide [Autoloading and Reloading Constants (Zeitwerk Mode)](autoloading_and_reloading_constants.html#single-table-inheritance). + +#### Qualified names in class and module definitions + +You can now robustly use constant paths in class and module definitions: + +```ruby +# Autoloading in this class' body matches Ruby semantics now. +class Admin::UsersController < ApplicationController + # ... +end +``` + +A gotcha to be aware of is that, depending on the order of execution, the classic autoloader could sometimes be able to autoload `Foo::Wadus` in + +```ruby +class Foo::Bar + Wadus +end +``` + +That does not match Ruby semantics because `Foo` is not in the nesting, and won't work at all in `zeitwerk` mode. If you find such corner case you can use the qualified name `Foo::Wadus`: + +```ruby +class Foo::Bar + Foo::Wadus +end +``` + +or add `Foo` to the nesting: + +```ruby +module Foo + class Bar + Wadus + end +end +``` + +#### Concerns + +You can autoload and eager load from a standard structure like + +``` +app/models +app/models/concerns +``` + +In that case, `app/models/concerns` is assumed to be a root directory (because it belongs to the autoload paths), and it is ignored as namespace. So, `app/models/concerns/foo.rb` should define `Foo`, not `Concerns::Foo`. + +The `Concerns::` namespace worked with the classic autoloader as a side-effect of the implementation, but it was not really an intended behavior. An application using `Concerns::` needs to rename those classes and modules to be able to run in `zeitwerk` mode. + +#### Having `app` in the autoload paths + +Some projects want something like `app/api/base.rb` to define `API::Base`, and add `app` to the autoload paths to accomplish that in `classic` mode. Since Rails adds all subdirectories of `app` to the autoload paths automatically, we have another situation in which there are nested root directories, so that setup no longer works. Similar principle we explained above with `concerns`. + +If you want to keep that structure, you'll need to delete the subdirectory from the autoload paths in an initializer: + +```ruby +ActiveSupport::Dependencies.autoload_paths.delete("#{Rails.root}/app/api") +``` + +#### Autoloaded Constants and Explicit Namespaces + +If a namespace is defined in a file, as `Hotel` is here: + +``` +app/models/hotel.rb # Defines Hotel. +app/models/hotel/pricing.rb # Defines Hotel::Pricing. +``` + +the `Hotel` constant has to be set using the `class` or `module` keywords. For example: + +```ruby +class Hotel +end +``` + +is good. + +Alternatives like + +```ruby +Hotel = Class.new +``` + +or + +```ruby +Hotel = Struct.new +``` + +won't work, child objects like `Hotel::Pricing` won't be found. + +This restriction only applies to explicit namespaces. Classes and modules not defining a namespace can be defined using those idioms. + +#### One file, one constant (at the same top-level) + +In `classic` mode you could technically define several constants at the same top-level and have them all reloaded. For example, given + +```ruby +# app/models/foo.rb + +class Foo +end + +class Bar +end +``` + +while `Bar` could not be autoloaded, autoloading `Foo` would mark `Bar` as autoloaded too. This is not the case in `zeitwerk` mode, you need to move `Bar` to its own file `bar.rb`. One file, one constant. + +This affects only to constants at the same top-level as in the example above. Inner classes and modules are fine. For example, consider + +```ruby +# app/models/foo.rb + +class Foo + class InnerClass + end +end +``` + +If the application reloads `Foo`, it will reload `Foo::InnerClass` too. + +#### Spring and the `test` Environment + +Spring reloads the application code if something changes. In the `test` environment you need to enable reloading for that to work: + +```ruby +# config/environments/test.rb + +config.cache_classes = false +``` + +Otherwise you'll get this error: + +``` +reloading is disabled because config.cache_classes is true +``` + +#### Bootsnap + +Bootsnap should be at least version 1.4.2. + +In addition to that, Bootsnap needs to disable the iseq cache due to a bug in the interpreter if running Ruby 2.5. Please make sure to depend on at least Bootsnap 1.4.4 in that case. + +#### `config.add_autoload_paths_to_load_path` + +The new configuration point + +```ruby +config.add_autoload_paths_to_load_path +``` + +is `true` by default for backwards compatibility, but allows you to opt-out from adding the autoload paths to `$LOAD_PATH`. + +This makes sense in most applications, since you never should require a file in `app/models`, for example, and Zeitwerk only uses absolute file names internally. + +By opting-out you optimize `$LOAD_PATH` lookups (less directories to check), and save Bootsnap work and memory consumption, since it does not need to build an index for these directories. + +#### Thread-safety + +In classic mode, constant autoloading is not thread-safe, though Rails has locks in place for example to make web requests thread-safe when autoloading is enabled, as it is common in `development` mode. + +Constant autoloading is thread-safe in `zeitwerk` mode. For example, you can now autoload in multi-threaded scripts executed by the `runner` command. + +#### Globs in config.autoload_paths + +Beware of configurations like + +```ruby +config.autoload_paths += Dir["#{config.root}/lib/**/"] +``` + +Every element of `config.autoload_paths` should represent the top-level namespace (`Object`) and they cannot be nested in consequence (with the exception of `concerns` directories explained above). + +To fix this, just remove the wildcards: + +```ruby +config.autoload_paths << "#{config.root}/lib" +``` + +#### Eager loading and autoloading are consistent + +In `classic` mode, if `app/models/foo.rb` defines `Bar`, you won't be able to autoload that file, but eager loading will work because it loads files recursively blindly. This can be a source of errors if you test things first eager loading, execution may fail later autoloading. + +In `zeitwerk` mode both loading modes are consistent, they fail and err in the same files. + +#### How to Use the Classic Autoloader in Rails 6 + +Applications can load Rails 6 defaults and still use the classic autoloader by setting `config.autoloader` this way: + +```ruby +# config/application.rb + +config.load_defaults "6.0" +config.autoloader = :classic +``` + +### Active Storage assignment behavior change + +In Rails 5.2, assigning to a collection of attachments declared with `has_many_attached` appended new files: + +```ruby +class User < ApplicationRecord + has_many_attached :highlights +end + +user.highlights.attach(filename: "funky.jpg", ...) +user.higlights.count # => 1 + +blob = ActiveStorage::Blob.create_after_upload!(filename: "town.jpg", ...) +user.update!(highlights: [ blob ]) + +user.highlights.count # => 2 +user.highlights.first.filename # => "funky.jpg" +user.highlights.second.filename # => "town.jpg" +``` + +With the default configuration for Rails 6.0, assigning to a collection of attachments replaces existing files +instead of appending to them. This matches Active Record behavior when assigning to a collection association: + +```ruby +user.highlights.attach(filename: "funky.jpg", ...) +user.highlights.count # => 1 + +blob = ActiveStorage::Blob.create_after_upload!(filename: "town.jpg", ...) +user.update!(highlights: [ blob ]) + +user.highlights.count # => 1 +user.highlights.first.filename # => "town.jpg" +``` + +`#attach` can be used to add new attachments without removing the existing ones: + +```ruby +blob = ActiveStorage::Blob.create_after_upload!(filename: "town.jpg", ...) +user.highlights.attach(blob) + +user.highlights.count # => 2 +user.highlights.first.filename # => "funky.jpg" +user.highlights.second.filename # => "town.jpg" +``` + +Opt in to the new default behavior by setting `config.active_storage.replace_on_assign_to_many` to `true`. +The old behavior will be deprecated in Rails 6.1 and removed in a subsequent release. + Upgrading from Rails 5.1 to Rails 5.2 ------------------------------------- @@ -407,7 +756,7 @@ want to add this feature it will need to be turned on in an initializer. Rails 5 now supports per-form CSRF tokens to mitigate against code-injection attacks with forms created by JavaScript. With this option turned on, forms in your application will each have their -own CSRF token that is specified to the action and method for that form. +own CSRF token that is specific to the action and method for that form. config.action_controller.per_form_csrf_tokens = true @@ -622,7 +971,7 @@ gem 'rails-deprecated_sanitizer' ### Rails DOM Testing -The [`TagAssertions` module](http://api.rubyonrails.org/v4.1/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing). +The [`TagAssertions` module](https://api.rubyonrails.org/v4.1/classes/ActionDispatch/Assertions/TagAssertions.html) (containing methods such as `assert_tag`), [has been deprecated](https://github.com/rails/rails/blob/6061472b8c310158a2a2e8e9a6b81a1aef6b60fe/actionpack/lib/action_dispatch/testing/assertions/dom.rb) in favor of the `assert_select` methods from the `SelectorAssertions` module, which has been extracted into the [rails-dom-testing gem](https://github.com/rails/rails-dom-testing). ### Masked Authenticity Tokens @@ -1521,7 +1870,7 @@ config.assets.enabled = true config.assets.version = '1.0' ``` -If your application is using an "/assets" route for a resource you may want change the prefix used for assets to avoid conflicts: +If your application is using an "/assets" route for a resource you may want to change the prefix used for assets to avoid conflicts: ```ruby # Defaults to '/assets' diff --git a/guides/source/working_with_javascript_in_rails.md b/guides/source/working_with_javascript_in_rails.md index c36b3faa6c..8cf8efefd0 100644 --- a/guides/source/working_with_javascript_in_rails.md +++ b/guides/source/working_with_javascript_in_rails.md @@ -160,7 +160,7 @@ remote elements inside your application. #### form_with -[`form_with`](http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with) +[`form_with`](https://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-form_with) is a helper that assists with writing forms. By default, `form_with` assumes that your form will be using Ajax. You can opt out of this behavior by passing the `:local` option `form_with`. @@ -204,7 +204,7 @@ have been bundled into `event.detail`. For information about the previously used #### link_to -[`link_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to) +[`link_to`](https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to) is a helper that assists with generating links. It has a `:remote` option you can use like this: @@ -236,7 +236,7 @@ $ -> #### button_to -[`button_to`](http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to) is a helper that helps you create buttons. It has a `:remote` option that you can call like this: +[`button_to`](https://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-button_to) is a helper that helps you create buttons. It has a `:remote` option that you can call like this: ```erb <%= button_to "An article", @article, remote: true %> |