diff options
Diffstat (limited to 'guides/source/action_controller_overview.md')
-rw-r--r-- | guides/source/action_controller_overview.md | 128 |
1 files changed, 66 insertions, 62 deletions
diff --git a/guides/source/action_controller_overview.md b/guides/source/action_controller_overview.md index 8c1551f4a1..6ecfb57db3 100644 --- a/guides/source/action_controller_overview.md +++ b/guides/source/action_controller_overview.md @@ -21,9 +21,9 @@ After reading this guide, you will know: What Does a Controller Do? -------------------------- -Action Controller is the C in MVC. After routing has determined which controller to use for a request, the controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible. +Action Controller is the C in [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). After the router has determined which controller to use for a request, the controller is responsible for making sense of the request, and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible. -For most conventional [RESTful](http://en.wikipedia.org/wiki/Representational_state_transfer) applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work. +For most conventional [RESTful](https://en.wikipedia.org/wiki/Representational_state_transfer) applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that's not a problem, this is just the most common way for a controller to work. A controller can thus be thought of as a middleman between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates user data to the model. @@ -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 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](http://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. @@ -145,7 +145,7 @@ So for example, if you are sending this JSON content: Your controller will receive `params[:company]` as `{ "name" => "acme", "address" => "123 Carrot Street" }`. -Also, if you've turned on `config.wrap_parameters` in your initializer or called `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. In this case, the parameters will be cloned and wrapped with a key chosen based on your controller's name. So the above JSON POST can be written as: +Also, if you've turned on `config.wrap_parameters` in your initializer or called `wrap_parameters` in your controller, you can safely omit the root element in the JSON parameter. In this case, the parameters will be cloned and wrapped with a key chosen based on your controller's name. So the above JSON request can be written as: ```json { "name": "acme", "address": "123 Carrot Street" } @@ -185,7 +185,9 @@ end These options will be used as a starting point when generating URLs, so it's possible they'll be overridden by the options passed to `url_for` calls. -If you define `default_url_options` in `ApplicationController`, as in the example above, it will be used for all URL generation. The method can also be defined in a specific controller, in which case it only affects URLs generated there. +If you define `default_url_options` in `ApplicationController`, as in the example above, these defaults will be used for all URL generation. The method can also be defined in a specific controller, in which case it only affects URLs generated there. + +In a given request, the method is not actually called for every single generated URL; for performance reasons, the returned hash is cached, there is at most one invocation per request. ### Strong Parameters @@ -197,11 +199,12 @@ practice to help prevent accidentally allowing users to update sensitive model attributes. In addition, parameters can be marked as required and will flow through a -predefined raise/rescue flow to end up as a 400 Bad Request. +predefined raise/rescue flow that will result in a 400 Bad Request being +returned if not all required parameters are passed in. ```ruby class PeopleController < ActionController::Base - # This will raise an ActiveModel::ForbiddenAttributes exception + # This will raise an ActiveModel::ForbiddenAttributesError exception # because it's using mass assignment without an explicit permit # step. def create @@ -211,8 +214,8 @@ class PeopleController < ActionController::Base # This will pass with flying colors as long as there's a person key # in the parameters, otherwise it'll raise a # ActionController::ParameterMissing exception, which will get - # caught by ActionController::Base and turned into that 400 Bad - # Request reply. + # caught by ActionController::Base and turned into a 400 Bad + # Request error. def update person = current_account.people.find(params[:id]) person.update!(person_params) @@ -255,6 +258,17 @@ scalar values, map the key to an empty array: params.permit(id: []) ``` +Sometimes it is not possible or convenient to declare the valid keys of +a hash parameter or its internal structure. Just map to an empty hash: + +```ruby +params.permit(preferences: {}) +``` + +but be careful because this opens the door to arbitrary input. In this +case, `permit` ensures values in the returned structure are permitted +scalars and filters out anything else. + To whitelist an entire hash of parameters, the `permit!` method can be used: @@ -262,9 +276,10 @@ used: params.require(:log_entry).permit! ``` -This will mark the `:log_entry` parameters hash and any sub-hash of it as -permitted. Extreme care should be taken when using `permit!`, as it -will allow all current and future model attributes to be mass-assigned. +This marks the `:log_entry` parameters hash and any sub-hash of it as +permitted and does not check for permitted scalars, anything is accepted. +Extreme care should be taken when using `permit!`, as it will allow all current +and future model attributes to be mass-assigned. #### Nested Parameters @@ -359,7 +374,7 @@ If your user sessions don't store critical data or don't need to be around for l Read more about session storage in the [Security Guide](security.html). -If you need a different session storage mechanism, you can change it in the `config/initializers/session_store.rb` file: +If you need a different session storage mechanism, you can change it in an initializer: ```ruby # Use the database for sessions instead of the cookie-based default, @@ -368,7 +383,7 @@ If you need a different session storage mechanism, you can change it in the `con # Rails.application.config.session_store :active_record_store ``` -Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in `config/initializers/session_store.rb`: +Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in an initializer: ```ruby # Be sure to restart your server when you modify this file. @@ -382,34 +397,18 @@ You can also pass a `:domain` key and specify the domain name for the cookie: Rails.application.config.session_store :cookie_store, key: '_your_app_session', domain: ".example.com" ``` -Rails sets up (for the CookieStore) a secret key used for signing the session data. This can be changed in `config/secrets.yml` +Rails sets up (for the CookieStore) a secret key used for signing the session data in `config/credentials.yml.enc`. This can be changed with `bin/rails credentials:edit`. ```ruby -# Be sure to restart your server when you modify this file. - -# Your secret key is used for verifying the integrity of signed cookies. -# If you change this key, all old signed cookies will become invalid! +# aws: +# access_key_id: 123 +# secret_access_key: 345 -# Make sure the secret is at least 30 characters and all random, -# no regular words or you'll be exposed to dictionary attacks. -# You can use `rake secret` to generate a secure secret key. - -# Make sure the secrets in this file are kept private -# if you're sharing your code publicly. - -development: - secret_key_base: a75d... - -test: - secret_key_base: 492f... - -# Do not keep production secrets in the repository, -# instead read values from the environment. -production: - secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> +# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. +secret_key_base: 492f... ``` -NOTE: Changing the secret when using the `CookieStore` will invalidate all existing sessions. +NOTE: Changing the secret_key_base when using the `CookieStore` will invalidate all existing sessions. ### Accessing the Session @@ -655,8 +654,8 @@ class UsersController < ApplicationController @users = User.all respond_to do |format| format.html # index.html.erb - format.xml { render xml: @users} - format.json { render json: @users} + format.xml { render xml: @users } + format.json { render json: @users } end end end @@ -667,11 +666,11 @@ You may notice in the above code that we're using `render xml: @users`, not `ren Filters ------- -Filters are methods that are run before, after or "around" a controller action. +Filters are methods that are run "before", "after" or "around" a controller action. Filters are inherited, so if you set a filter on `ApplicationController`, it will be run on every controller in your application. -"Before" filters may halt the request cycle. A common "before" filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way: +"before" filters may halt the request cycle. A common "before" filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way: ```ruby class ApplicationController < ActionController::Base @@ -698,15 +697,18 @@ class LoginsController < ApplicationController end ``` -Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to only skip this filter for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. +Now, the `LoginsController`'s `new` and `create` actions will work as before without requiring the user to be logged in. The `:only` option is used to skip this filter only for these actions, and there is also an `:except` option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place. + +NOTE: Calling the same filter multiple times with different options will not work, +since the last filter definition will overwrite the previous ones. ### After Filters and Around Filters In addition to "before" filters, you can also run filters after an action has been executed, or both before and after. -"After" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running. +"after" filters are similar to "before" filters, but because the action has already been run they have access to the response data that's about to be sent to the client. Obviously, "after" filters cannot stop the action from running. Please note that "after" filters are executed only after a successful action, but not when an exception is raised in the request cycle. -"Around" filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work. +"around" filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work. For example, in a website where changes have an approval workflow an administrator could be able to preview them easily, just apply them within a transaction: @@ -736,7 +738,7 @@ You can choose not to yield and build the response yourself, in which case the a While the most common way to use filters is by creating private methods and using *_action to add them, there are two other ways to do the same thing. -The first is to use a block directly with the *\_action methods. The block receives the controller as an argument, and the `require_login` filter from above could be rewritten to use a block: +The first is to use a block directly with the *\_action methods. The block receives the controller as an argument. The `require_login` filter from above could be rewritten to use a block: ```ruby class ApplicationController < ActionController::Base @@ -749,7 +751,7 @@ class ApplicationController < ActionController::Base end ``` -Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter is not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful. +Note that the filter in this case uses `send` because the `logged_in?` method is private and the filter does not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful. The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex and cannot be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class: @@ -782,9 +784,9 @@ The way this is done is to add a non-guessable token which is only known to your If you generate a form like this: ```erb -<%= form_for @user do |f| %> - <%= f.text_field :username %> - <%= f.text_field :password %> +<%= form_with model: @user, local: true do |form| %> + <%= form.text_field :username %> + <%= form.text_field :password %> <% end %> ``` @@ -808,11 +810,11 @@ The [Security Guide](security.html) has more about this and a lot of other secur The Request and Response Objects -------------------------------- -In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The `request` method contains an instance of `AbstractRequest` and the `response` method returns a response object representing what is going to be sent back to the client. +In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The `request` method contains an instance of `ActionDispatch::Request` and the `response` method returns a response object representing what is going to be sent back to the client. ### 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 [API documentation](http://api.rubyonrails.org/classes/ActionDispatch/Request.html). 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](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: | Property of `request` | Purpose | | ----------------------------------------- | -------------------------------------------------------------------------------- | @@ -834,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. +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). | Property of `response` | Purpose | | ---------------------- | --------------------------------------------------------------------------------------------------- | @@ -993,10 +995,6 @@ you would like in a response object. The `ActionController::Live` module allows you to create a persistent connection with a browser. Using this module, you will be able to send arbitrary data to the browser at specific points in time. -NOTE: The default Rails server (WEBrick) is a buffering web server and does not -support streaming. In order to use this feature, you'll need to use a non buffering -server like [Puma](http://puma.io), [Rainbows](http://rainbows.bogomips.org) -or [Passenger](https://www.phusionpassenger.com). #### Incorporating Live Streaming @@ -1027,7 +1025,7 @@ There are a couple of things to notice in the above example. We need to make sure to close the response stream. Forgetting to close the stream will leave the socket open forever. We also have to set the content type to `text/event-stream` before we write to the response stream. This is because headers cannot be written -after the response has been committed (when `response.committed` returns a truthy +after the response has been committed (when `response.committed?` returns a truthy value), which occurs when you `write` or `commit` the response stream. #### Example Usage @@ -1090,6 +1088,8 @@ You can filter out sensitive request parameters from your log files by appending config.filter_parameters << :password ``` +NOTE: Provided parameters will be filtered out by partial matching regular expression. Rails adds default `:password` in the appropriate initializer (`initializers/filter_parameter_logging.rb`) and cares about typical application parameters `password` and `password_confirmation`. + ### Redirects Filtering Sometimes it's desirable to filter out from log files some sensitive locations your application is redirecting to. @@ -1112,11 +1112,11 @@ Rescue Most likely your application is going to contain bugs or otherwise throw an exception that needs to be handled. For example, if the user follows a link to a resource that no longer exists in the database, Active Record will throw the `ActiveRecord::RecordNotFound` exception. -Rails' default exception handling displays a "500 Server Error" message for all exceptions. If the request was made locally, a nice traceback and some added information gets displayed so you can figure out what went wrong and deal with it. If the request was remote Rails will just display a simple "500 Server Error" message to the user, or a "404 Not Found" if there was a routing error or a record could not be found. Sometimes you might want to customize how these errors are caught and how they're displayed to the user. There are several levels of exception handling available in a Rails application: +Rails default exception handling displays a "500 Server Error" message for all exceptions. If the request was made locally, a nice traceback and some added information gets displayed so you can figure out what went wrong and deal with it. If the request was remote Rails will just display a simple "500 Server Error" message to the user, or a "404 Not Found" if there was a routing error or a record could not be found. Sometimes you might want to customize how these errors are caught and how they're displayed to the user. There are several levels of exception handling available in a Rails application: ### The Default 500 and 404 Templates -By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the `public` folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and layout, but remember that they are static; i.e. you can't use RHTML or layouts in them, just plain HTML. +By default a production application will render either a 404 or a 500 error message, in the development environment all unhandled exceptions are raised. These messages are contained in static HTML files in the public folder, in `404.html` and `500.html` respectively. You can customize these files to add some extra information and style, but remember that they are static HTML; i.e. you can't use ERB, SCSS, CoffeeScript, or layouts for them. ### `rescue_from` @@ -1148,7 +1148,7 @@ class ApplicationController < ActionController::Base def user_not_authorized flash[:error] = "You don't have access to this section." - redirect_to :back + redirect_back(fallback_location: root_path) end end @@ -1170,9 +1170,13 @@ class ClientsController < ApplicationController end ``` -WARNING: You shouldn't do `rescue_from Exception` or `rescue_from StandardError` unless you have a particular reason as it will cause serious side-effects (e.g. you won't be able to see exception details and tracebacks during development). +WARNING: Using `rescue_from` with `Exception` or `StandardError` would cause serious side-effects as it prevents Rails from handling exceptions properly. As such, it is not recommended to do so unless there is a strong reason. + +NOTE: When running in the production environment, all +`ActiveRecord::RecordNotFound` errors render the 404 error page. Unless you need +a custom behavior you don't need to handle this. -NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik's [article](http://m.onkey.org/2008/7/20/rescue-from-dispatching) on the subject for more information. +NOTE: Certain exceptions are only rescuable from the `ApplicationController` class, as they are raised before the controller gets initialized and the action gets executed. Force HTTPS protocol -------------------- |