I have an 'create' action method in Rails and do:
def create
@movie = Movie.new(movie_params)
if @movie.save
redirect_to @movie, notice: "Movie successfully created"
else
render :new
end
end
Now, I have a few validations in place for the Movie
model. In case those validations fail, and @movie.save
returns false, I simply invoke the new
template (without touching the new
action, since render :new
is the same as render template: 'new'
.
I don't understand how Rails can keep the form data I already entered when it again renders that new
view. What's going on behind the hood that allows it to do this?
Let's try to understand this whole process point-wise
Instance variables defined in the controller action are shared with the rendered views.
In your case I'm assuming that there's a new
action something like
def new
@movie = Movie.new
end
And you have a corresponding view new.html.erb
where you have created a form like this
= form_for @movie do |f|
Now, as you know the @movie
object that you are passing in form_for
method is defined in new
action. Most of the times we don't pass any parameters to the new
method in new
action. The form fields are blank when you load the form because the attributes of the object(in your case @movie
) are by default blank because we just initialize an empty object(Movie.new
).
Let's assume your Movie
model has a name
attribute, Try doing this in your new
action
def new
@movie = Movie.new(name: 'Hello World!')
end
Now when you will load the new action, you will see Hello World!
populated in your name
text field because your @movie
object is initialized with this value.
Also, keep in mind that Rails Convention-Over-Configuration automatically generates the form URL in this case, by default it points to the create
action. When you submit the form
the request is made to the create action. This takes me to the next point.
When we submit the form all the filled in form values are sent to the action whose route matches with the form URL(in your case URL points to the create
action)
In create
action you are receiving parameters in the form of a hash with model attributes(Movie
attributes) as keys and the filled in information as their values. The first line in your create
action is
@movie = Movie.new(movie_params)
This is a very important line of code, try to understand this. Let's assume your form had only one text field, i.e., name
. Now movie_params
is a method that looks like this
def movie_params
params.require(:movie).permit(:name)
end
Now, the movie_params
method will return a hash something like { 'name' => 'Hello World!' }
, now you are passing this hash as a parameter to Movie.new
method.
So now, after breaking up the code, the first line of your create action looks like
@movie = Movie.new({ name: 'Hello World!' })
That means your @movie
instance variable contains an object of Movie
class with name
attribute set to Hello World!
. Here, when after initialization, if you do @movie.name
it will return Hello World!
.
Now, in the second line you are calling @movie.save
that returned false
due to failed validation in your case as you have already mentioned in the question. As it returned false
the execution will go to the else
part. Now this takes me to the next point.
Calling render :action
(in your case render :new
) in the controller renders only the view that belongs to that action and does not execute that action code.
In your case, you called render :new
, so there you are actually rendering the new.html.erb
view in create action. In other words, you are just using the code in new.html.erb
and not in new
action. Here, render :new
does not actually invoke the new action, it's still in the create action but rendering the new.html.erb
view.
Now, in new.html.erb
you have created a form that looks like
= form_for @movie do |f|
Now as my explained under my first point, the instance variables that are declared in the action are shared by the rendered view, in this case @movie
object that you have defined in create
action is shared by the rendered new.html.erb
in create action. In our case, in create
action the @movie
object was initialized with some values that were received in the parameters(movie_params
), now when new.html.erb
is rendered in the else
, the same @movie
object is used in the form by default. You got the point right, you see the magic here?
This is how Rails works and that's why its awesome when we follow the convention! :)
https://gist.github.com/jcasimir/1210155
http://guides.rubyonrails.org/v4.2/layouts_and_rendering.html
Hope the above examples cleared your doubts, if not, feel free to drop your queries in the comment box below.
form_for
helper takes data from @movie
variable. In create
action forms data assigns to @movie
variable. When you call render :new
form_for takes column's data from @movie
variable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With