diff --git a/ruby_on_rails/forms_and_authentication/form_basics.md b/ruby_on_rails/forms_and_authentication/form_basics.md index 73056cb30de..acd512f1bbc 100644 --- a/ruby_on_rails/forms_and_authentication/form_basics.md +++ b/ruby_on_rails/forms_and_authentication/form_basics.md @@ -82,7 +82,7 @@ Each one of these inputs is structured slightly differently, but there are some Will result in your `params` hash containing a key called `description` that you can access as normal, e.g. `params[:description]`, inside your controller. That's also why some inputs like radio buttons (where `type="radio"`) use the `name` attribute to know which radio buttons should be grouped together such that clicking one of them will unclick the others. The `name` attribute is surprisingly important! -Now another thing we talked about in the controller section was nesting data. You'll often want to tuck submitted data neatly into a hash instead of keeping them all at the top level. This can be useful because, as we saw with controllers, it lets you do a one-line `#create` (once you've allowed the parameters with `#require` and `#permit`). When you access `params[:user]`, it's actually a hash containing all the user's attributes, for instance `{first_name: "foo", last_name: "bar", email: "foo@bar.com"}`. How do you get your forms to submit parameters like this? It's easy! +Now another thing we talked about in the controller section was nesting data. You'll often want to tuck submitted data neatly into a hash instead of keeping them all at the top level. This can be useful because, as we saw with controllers, it lets you do a one-line `#create` (once you've allowed the parameters with `#expect`). When you access `params[:user]`, it's actually a hash containing all the user's attributes, for instance `{first_name: "foo", last_name: "bar", email: "foo@bar.com"}`. How do you get your forms to submit parameters like this? It's easy! It all comes back to the `name` attribute of your form inputs. Just use hard brackets to nest data like so: @@ -102,7 +102,7 @@ Those inputs will now get transformed into a nested hash under the `:user` key. Specific parameters of the `params` hash are accessed like any other nested hash `params[:user][:email]`. -Don't forget that you have to allow the params now in your controller using `require` and `permit` because they are a hash instead of just a flat string. See the Controller section below for a refresher on the controller side of things. +Don't forget that you have to allow the params now in your controller using `#expect` because they are a hash instead of just a flat string. See the Controller section below for a refresher on the controller side of things. This is cool stuff that you'll get a chance to play with in the project. @@ -244,7 +244,7 @@ Just as a refresher, here's a very basic controller setup for handling `#new` ac end def user_params - params.require(:user).permit(:first_name, :last_name, :other_stuff) + params.expect(user: [:first_name, :last_name, :other_stuff]) end ... ``` diff --git a/ruby_on_rails/forms_and_authentication/project_forms.md b/ruby_on_rails/forms_and_authentication/project_forms.md index 3559a13dd49..4a7d17aedec 100644 --- a/ruby_on_rails/forms_and_authentication/project_forms.md +++ b/ruby_on_rails/forms_and_authentication/project_forms.md @@ -71,7 +71,7 @@ That looks a whole lot like what you normally see when Rails does it, right? 1. You'll get some errors because now your controller will need to change. But recall that we're no longer allowed to just directly call `params[:user]` because that would return a hash and Rails' security features prevent us from doing that without first validating it. 1. Go into your controller and comment out the line in your `#create` action where you instantiated a `::new` User (we'll use it later). -1. Implement a private method at the bottom called `user_params` which will `permit` and `require` the proper fields (see the [Controllers Lesson](/lessons/ruby-on-rails-controllers) for a refresher). +1. Implement a private method at the bottom called `user_params` which will `expect` the proper fields (see the [Controllers Lesson](/lessons/ruby-on-rails-controllers) for a refresher). 1. Add a new `::new` User line which makes use of that new allow params method. 1. Submit your form now. It should work marvelously (once you debug your typos)! diff --git a/ruby_on_rails/mailers_advanced_topics/actioncable_lesson.md b/ruby_on_rails/mailers_advanced_topics/actioncable_lesson.md index c72652b0fc9..fc09e8b2e3e 100644 --- a/ruby_on_rails/mailers_advanced_topics/actioncable_lesson.md +++ b/ruby_on_rails/mailers_advanced_topics/actioncable_lesson.md @@ -459,7 +459,7 @@ end private def message_params - params.require(:message).permit(:body) + params.expect(message: [:body]) end ``` diff --git a/ruby_on_rails/rails_basics/controllers.md b/ruby_on_rails/rails_basics/controllers.md index 8b48af13715..ce642d78be0 100644 --- a/ruby_on_rails/rails_basics/controllers.md +++ b/ruby_on_rails/rails_basics/controllers.md @@ -8,7 +8,7 @@ It's pretty straightforward. Typical controllers are pretty lightweight and don' The controller's `#index` action would actually look like: -~~~ruby +```ruby PostsController < ApplicationController ... def index @@ -16,7 +16,7 @@ The controller's `#index` action would actually look like: end ... end -~~~ +``` In this action, we have the controller asking the model for something ("Hey, give me all the posts!"), packaging them up in an instance variable `@posts` so the view can use them, then will automatically render the view at `app/views/posts/index.html.erb` (we'll talk about that in a minute). @@ -34,21 +34,21 @@ This section contains a general overview of topics that you will learn in this l One way that Rails makes your life a bit easier is that it assumes things are named a certain way and then executes them behind the scenes based on those names. For instance, your controller and its action have to be named whatever you called them in your `routes.rb` file when you mapped a specific type of HTTP request to them. -The other end of the process is what the controller does when it's done. Once Rails gets to the `end` of that controller action, it grabs all the instance variables from the controller and sends them over the view file _which is named the same thing as the controller action_ and which lives in a folder named after the controller, e.g. `app/views/posts/index.html.erb`. This isn't arbitrary, this is intentional to make your life a lot easier when looking for files later. If you save your files in a different folder or hierarchy, you'll have to explicitly specify which ones you want rendered. +The other end of the process is what the controller does when it's done. Once Rails gets to the `end` of that controller action, it grabs all the instance variables from the controller and sends them over the view file *which is named the same thing as the controller action* and which lives in a folder named after the controller, e.g. `app/views/posts/index.html.erb`. This isn't arbitrary, this is intentional to make your life a lot easier when looking for files later. If you save your files in a different folder or hierarchy, you'll have to explicitly specify which ones you want rendered. ### Rendering and redirecting Although Rails will implicitly render a view file that is named the same thing as your controller action, there are plenty of situations when you might want to override it. A main case for this is when you actually want to completely redirect the user to a new page instead of rendering the result of your controller action. -Redirects typically occur after controller actions where you've submitted information like to create a new Post. There's no reason to have a `create.html.erb` view file that gets displayed once a post has been created... we usually just want to see the post we created and so we'll redirect over to the Show page for that post. The distinction here is that your application treats a redirect as _a completely new HTTP request_... so it would enter through the router again, look for the Show page corresponding to that post, and render it normally. That also means that any instance variables you set in your original `#create` controller action are wiped out along the way. +Redirects typically occur after controller actions where you've submitted information like to create a new Post. There's no reason to have a `create.html.erb` view file that gets displayed once a post has been created... we usually just want to see the post we created and so we'll redirect over to the Show page for that post. The distinction here is that your application treats a redirect as *a completely new HTTP request*... so it would enter through the router again, look for the Show page corresponding to that post, and render it normally. That also means that any instance variables you set in your original `#create` controller action are wiped out along the way. If that's the common way to deal with successfully creating an object, how about when it fails for some reason (like the user entered a too-short post title)? In that case, you can just render the view for another controller action, often the same action that created the form you just submitted (so the `#new` action). -The trick here is that the view page gets passed the instance variables from your _current_ controller action. So let's say that you tried to `#create` a Post and stored it to `@post` but it failed to save. You then rendered the `#new` action's view and that view will receive the `@post` you were just working with in the `#create` action. This is great because you don't have to wipe the form completely clean (which is really annoying as a user) -- you can just identify the fields that failed and have the user resubmit. It may sound a bit abstract now but you'll see the difference quickly when building. +The trick here is that the view page gets passed the instance variables from your *current* controller action. So let's say that you tried to `#create` a Post and stored it to `@post` but it failed to save. You then rendered the `#new` action's view and that view will receive the `@post` you were just working with in the `#create` action. This is great because you don't have to wipe the form completely clean (which is really annoying as a user) -- you can just identify the fields that failed and have the user resubmit. It may sound a bit abstract now but you'll see the difference quickly when building. Let's see it in code: -~~~ruby +```ruby # app/controllers/posts_controller.rb class PostsController < ApplicationController ... @@ -74,7 +74,7 @@ Let's see it in code: end end end -~~~ +``` So the thing to pay attention to is that, if we successfully are able to save our new post in the database, we redirect to that post's show page. Note that a shortcut you'll see plenty of times is, instead of writing `redirect_to post_path(@post.id)`, just write `redirect_to @post` because Rails knows people did that so often that they gave you the option of writing it shorthand. We'll use this in the example as we develop it further. @@ -86,7 +86,7 @@ It's important to note that `render` and `redirect_to` do NOT immediately stop y If you write something like: -~~~ruby +```ruby def show @user = User.find(params[:id]) if @user.is_male? @@ -94,7 +94,7 @@ If you write something like: end render "show-girl" end -~~~ +``` In any case where the user is male, you'll get hit with a multiple render error because you've told Rails to render both "show-boy" and "show-girl". @@ -102,7 +102,7 @@ In any case where the user is male, you'll get hit with a multiple render error In the example above, we saw `... code here to set up a new @post based on form info ...`. Okay, how do we grab that info? We keep saying that the router packages up all the parameters that were sent with the original HTTP request, but how do we access them? -With the `params` hash! It acts just like a normal Ruby hash and contains the parameters of the request, stored as `:key => value` pairs. So how do we get the ID of the post we're asking for? `params[:id]`. You can access any parameters this way which have "scalar values", e.g. strings, numbers, booleans, `nil`... anything that's "flat". +With the `params` object! The `params` object is an instance of `ActionController::Parameters`, and it behaves similarly to a normal Ruby hash. It contains the parameters of the request stored as `:key => value` pairs. So how do we get the ID of the post we're asking for? `params[:id]`. You can access any parameters this way which have "scalar values", e.g. strings, numbers, booleans, `nil`... anything that's "flat". Some forms will submit every field as a top level scalar entry in the params hash, e.g. `params[:post_title]` might be "My Test Post Title" and `params[:post_body]` might be "Body of post!" etc and these you can access with no issues. You have control over this, as you'll learn in the lessons on forms. @@ -114,15 +114,19 @@ In our example, we will assume that our `params[:post]` is giving us a hash of P The important distinction between the "scalar" parameter values like strings and more complex parameters like hashes and arrays is that Rails 4 implemented some protections in the controller, called "Strong Parameters". This is so the user can't send you harmful data (like automatically setting themselves as an admin user when they create an account). To do this, Rails makes you explicitly verify that you are willing to accept certain items of a hash or array. -_Note: This used to be done in Rails 3 by setting `attr_accessible` in the model to allow attributes, so you will probably see that in a lot of Stack Overflow posts and earlier applications._ +
-To explicitly allow parameters, you use the methods `require` and `permit`. Basically, you `require` the name of your array or hash to be in Params (otherwise it'll throw an error), and then you `permit` the individual attributes inside that hash to be used. For example: + This used to be done in Rails 3 by setting `attr_accessible` in the model to allow attributes, so you will probably see that in a lot of Stack Overflow posts and earlier applications. -~~~ruby +
+ +To explicitly allow parameters, you can call the `#expect` method on the `params`. Usually this method will be passed a key matching the name of the model you're working with (ie. a `post`) followed by an array of allowed attributes. For example: + +```ruby def allowed_post_params - params.require(:post).permit(:title,:body,:author_id) + params.expect(post: [:title, :body, :author_id]) end -~~~ +``` This will return the hash of only those params that you explicitly allow (e.g. `{:title => "your title", :body => "your body", :author_id => "1"}` ). If you didn't do this, when you tried to access params[:post] nothing would show up! Also, if there were any additional fields submitted inside the hash, these will be stripped away and made inaccessible (to protect you). @@ -130,7 +134,7 @@ It can be inconvenient, but it's Rails protecting you from bad users. You'll usu So our `#create` action above can now be filled out a bit more: -~~~ruby +```ruby # app/controllers/posts_controller.rb class PostsController < ApplicationController ... @@ -153,10 +157,24 @@ So our `#create` action above can now be filled out a bit more: # gives us back just the hash containing the params we need to # to create or update a post def allowed_post_params - params.require(:post).permit(:title,:body,:author_id) + params.expect(post: [:title, :body, :author_id]) end end -~~~ +``` + +
+ + Prior to Rails 8, strong parameters were handled differently. Instead of the `#expect` method, you had to call `#permit` on the top level key name followed by calling `#require` on the list of attributes. For example: + + ```ruby + def allowed_post_params + params.permit(:post).require(:title, :body, :author_id) + end + ``` + + This way still works, but it has a couple of security flaws that motivated the development of `#expect`. But you will likely be exposed to this way of doing it through older projects, blog posts, and StackOverflow answers. Just know that this is serving the same function as the new `#expect` method. + +
### Flash @@ -174,7 +192,7 @@ The distinction between `flash` and `flash.now` just lets Rails know when it wil Now the full controller code can be written out for our `#create` action: -~~~ruby +```ruby # app/controllers/posts_controller.rb class PostsController < ApplicationController ... @@ -195,10 +213,10 @@ Now the full controller code can be written out for our `#create` action: private def allowed_post_params - params.require(:post).permit(:title,:body,:author_id) + params.expect(post: [:title, :body, :author_id]) end end -~~~ +``` So that action did a fair bit of stuff -- grab the form data, make a new post, try to save the post, set up a success message and redirect you to the post if it works, and handle the case where it doesn't work by berating you for your foolishness and re-rendering the form. A lot of work for only 10 lines of Ruby. Now that's smart controlling. @@ -207,12 +225,14 @@ So that action did a fair bit of stuff -- grab the form data, make a new post, t That's really just a taste of the Rails controller, but you should have a pretty good idea of what's going on and what tricks you can use.
+ 1. Read the [Rails Guides chapter on Controllers](http://guides.rubyonrails.org/action_controller_overview.html), sections 1 - 4.6.3 and 5.2. We'll cover sessions (section 5.1) more in the future so don't worry about them now. +
### Knowledge check -This section contains questions for you to check your understanding of this lesson. If you’re having trouble answering the questions below on your own, review the material above to find the answer. +The following questions are an opportunity to reflect on key topics in this lesson. If you can't answer a question, click on it to review the material, but keep in mind you are not expected to memorize or master this knowledge. - [What does a controller do?](https://guides.rubyonrails.org/action_controller_overview.html#what-does-a-controller-do-questionmark) - [Why is it important to adhere to the Rails naming convention for your controllers and all of its methods?](#naming-matters) diff --git a/ruby_on_rails/rails_sprinkles/turbo.md b/ruby_on_rails/rails_sprinkles/turbo.md index 121707ad6f4..d936a7c85a8 100644 --- a/ruby_on_rails/rails_sprinkles/turbo.md +++ b/ruby_on_rails/rails_sprinkles/turbo.md @@ -250,7 +250,7 @@ class PostsController < ApplicationController private def post_params - params.require(:post).permit(:body) + params.expect(post: [:body]) end end ```