How are params passed from the New to Create action in Rails? - ruby-on-rails

I understand that the new action for a rails form normally instantiates a model and provides a view with inputs for permitted params.
def new
#post = Post.new
end
What I don't get is how that same information submitted by the user is carried over to Post.new method inside the create action:
def create
#post = Post.new(post_params)
...
end
How does rails channel these params to create despite the page refresh that occurs?

You are mixing two requests with each other. When you hit the new action, a view that contains your form gets rendered. As it gets rendered, the new action finishes, and now the new action has nothing to do with create action.
You need new action to create a form through form_for method in which you actually pass an object, in your case #post.
The create action is independent, and params it receives, they have nothing to do with new method, those params are received through form rendered in the new.html.erb file of views.
You can also invoke the create method in your controller by sending the data through AJAX or even using cURL or POSTMAN - a chrome extension.
And as you asked:
How does rails channel these params to create despite the page refresh that occurs?
Rails doesn't channel these params, Rails run at back-end, Rails just receives those params. They are sent through an HTML form, and as I said earlier there are other ways to send params as well.

Related

How does Rails keep the form data when validations fail?

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.
I'm not sure how deep under the hood you want to go, but basically when you POST to the create method the data is passed to the params, the params being just a key:value pairs where the key and the value are strings, but rails has a special syntax and methods for turning into hashes. params data is passed the Movie model to be processed and the result stored in #movie. When the form is rendered the #movie date is passed back to the form - that data is used to repopulate the form.
I would recommend this blog post and the rails guidefor further reading.
I will try to explain little bit:
in method create first of all we set instance variable
#movie = Movie.new(movie_params)
#movie at this moment has fields filled with movie_params
and after validates brakes we say to Rails 'render :new' with variable #movie.
This is the same if we assign attributes into form:
= form_for Movie.new(movie_params) do ...
When you submit your form. You call create method where all values of movie_params are initializes in #movie. Now due to any reason code break then you call render new for same object (#movie). So form come up with values.
Means in whole process your #movie object persisted.

why strong parameters for the CREATE, but not NEW, in a rails controller?

I am confused as to why, in an example Articles Controller, the create method is utilizing strong parameters, but the new method isn't?
def new
#article = Article.new
end
def create
#article = Article.new(article_param)
if #article.save
redirect_to #article
else
render "new"
end
end
Because strong_parameters are there for whitelist params before update or create a record. While on new or edit action there are not any action over db records, and it isn't necessary to whitelist any params. On update and create controller actions there are action over the db, and any parameters that is not whitelisted is forbidden.
Also rails guides show the same definition: "With strong parameters, Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted. This means you'll have to make a conscious choice about which attributes to allow for mass updating and thus prevent accidentally exposing that which shouldn't be exposed."
The most common example is when: In your browser you can edit a field name and change <input name=user[name] ...> to <input name=user[admin] ...> then at the form change value to '1' and submit. Without strong parameters user[:admin] is a valid parameter and get changed at the database. Further, at the new or edit action, there are no risk of any impact on the db, because you are only sending a form to the browser.
The new method just instantiates a new object. The create method is responsible for assigning the attributes and writing them to the database.
After calling .new, your article is just an empty shell, therefore no params are needed.
The new article is used so the page can render the proper form. Also, on the new action there aren't any params in the controller, the user has simply clicked a new button so they can receive the view to create the new article.
Your params represent user entered data and aren't there until after the form is submitted.
The new method is the Form itself where the data is inputted by the User. It is submitted through the create method which is where your app will recognize the specifications of your form and will accept or reject elements inputted by the User. For example if you have an integer assigned to a table element and a User input a float/decimal into your form the new form will accept the input but actually only create an integer without a decimal for that number inputted when it is rendered in the views.

Skipping the New Method in Controller (Go Right to the Create Method)

In Rails, I'd like to skip the "new" method in a controller entirely and go directly to the create method. (I don't need any form data to be submitted, and just want to go directly to creating the object based on data from the currently logged in user.)
In rake routes, I don't see a prefix that allows me to link directly to the controller's create method, so I think I should link to the new method and have that redirect to the create action without accepting any input.
I tried doing this with the following:
def new
create
end
def create
#request = Request.new
#request.requestor_id = current_user.id
#request.status = "S1"
#request.save
respond_with(#request, :location => "/products/findexchanges")
end
When browsing the DB, I can see that this is calling the create action and is adding the record to the db, but after it is done it is redirecting me to new.html.erb rather than the location defined at the end of the create method.
A create action should be triggered by a POST, not GET, which is why there is no specific route for it.
Use button_to instead of link_to. I tried using link_to and even after specifying method: :post, action: :create, it still takes me to index using GET. After using button_to and passing params in ####_path, it directly went to the create method and added data to database. Although I am not sure its correct way or safe way to do this.

What is causing this redirect_to to fail?

I am trying to use this redirect_to
redirect_to :controller => :note_categories, :action => :destroy, :note_id => params[:id]
This is the URL that results
http://localhost:3000/note_categories/272?note_id=272
and this is the error message
Unknown action
No action responded to show. Actions: destroy
The reason I am redirecting to the note_categories destroy action, and passing in the note id, is that in the destroy action, I am finding all the note_categories related to note, running some code on them, then destroying them. I know this isn't a great way to be doing this, but I couldn't use :dependant => :destroy because the code I have to run on the note_category before I delete it needs access to current_user, which can't happen in the note_category model.
So yeah, can someone please tell me what am I doing wrong in my redirect_to? Thanks for reading.
The redirect_to method is essentially the Rails implementation of the Post/Redirect/Get (PRG) web design pattern. It's used to prevent duplicate form submissions caused by the user clicking the browser's Refresh button after submitting a form.
The typical Rails usage is like this for creating an object:
A form for creating an object is displayed (new action/HTTP GET)
The user fills in the form
The form is submitted (create action/HTTP POST)
The object is created and saved
A redirect_to is performed with an HTTP 301/302 status to the object's show view or perhaps index
—for editing an object it's:
A form for edit an existing object is displayed (edit action/HTTP GET)
The user fills in the form
The form is submitted (update action/HTTP PUT)
The object is updated and saved
A redirect_to is performed with an HTTP 301/302 status to the object's show view or perhaps index
You can't redirect directly to the destroy action because in RESTful Rails that's intended to be invoked as a result of an HTTP DELETE request and doesn't render a template when it's invoked. The redirect_to method always redirects to a template.
You haven't shown us the code for destroying notes, but I suspect that what you're trying to achieve can be done with a before filter and by having the controller passing the current user to a model method.

Passing instance variable from one form to the a different controller's action in Rails

Simple question - I have a form where I create an instance of an object. After I create that object (aka submit the form), I want to redirect to a new form that is associated with a different controller's action and carry that instance variable to populate some of that form's fields.
I know I typically have 2 options, store that instance variable in the session or pass it via params. I can't use sessions (for a variety of reasons I won't bore you with). The params option I am confused on.
I should know this. :( How would you go about doing this? Any examples greatly appreciated!!
Betsy
You'll have two methods on your controller. One for each form (rendered by the associated template). The first form should post to the second action. The second action can then transfer the request parameters into instance variables, to be available within the second template.
class FooController
def bar
# setup instance variables and render first form
end
def baz
#bar_values = params[:bar]
# setup other instance variables and render second form
end
end
UPDATE0 Do it across two controllers using session.
class FooController
def new_baz
# setup instance variables and render the first form
end
def create_baz
# respond to posting of form data
session[:current_baz_values] = params
redirect_to :action => "baq", :controller => "bar"
end
end
class BarController
def baq
#baz_values = session[:current_baz_values]
# setup other instance variables and render the second form
end
end
Could you somehow just do a find of the newly created record in the other controller, and then use that to populate the info you need?
Also, unless you are using AJAX you usually don't want to have modification actions on the show page for a record. Those belong on the edit or update page. If you always want people to be able to edit a record on the same page I would either use some AJAX on the show page, or just always return the edit/update page instead...
If you do not want to use sessions, you could use the flash variable to store your parameter. Something like, flash[:my_params] = params, and then reading it back in the next request with params = flash[:my_params]. The good thing about flash, is that persists for only the next request, and auto-clears after that.
If you are looking for passing values from the client side when using Ajax, then probably setting a hidden field with the parameters is going to pass them on to the next request.

Resources