def new
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.json { render json: #post }
end
end
def create
#post = Post.new(params[:post])
#something else
end
Here since when we are actually creating a new post we call the method create where Page.new(params[:page]) is used, method new should only be used to call the view new.html.erb. So why we still need an instance variable #post in new method here?
You don't need any instance variables in new or any other action, but by default, Rails's scaffolding uses them. There are some minor benefits that come with using instance variables, and there's some convention around them, but frankly it's sloppy code and should not be the default. In a proper MVC framework, the controller's instance variables wouldn't even be visible to the view object.
I prefer to be explicit, use local variables, and pass them to the view as locals:
def new
post = Post.new
respond_to do |format|
format.html { render locals: { post: post } }
format.json { render json: post }
end
end
This is more explicit, and makes your intent clear. The view becomes more flexible with locals, since you don't have to worry about setting instance variables before rendering a partial from inside another view. It properly encapsulates the data and doesn't expose your post outside the action.
If you're trying to quickly prototype/spike something, you might save a few characters by using instance variables, but it's not clean code.
If you did not create a new instance variable then you wouldn't have a model to hold the data in which the model is supposed to be managing - not to mention you would be adding a lot more boiler plate for your forms instead of using the form helpers that take a model. Without using the model you're not using the MVC framework fully as intended.
Now that all being said, it's by no means required to do anything it's just following the MVC structure that Rails is built on top of. No pattern is required, there are always other solutions, it's just that the accepted method of performing this action involves a model - albeit and empty one.
Finally, I don't know when or why you'd want to respond with a new route with JSON since the new route is generally used for displaying a form to create an object while the create function is the one used to actually create a new instance of the model.
Related
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.
I know that this question has been asked many times on StackOverflow, but i have a more specific question:
I know that new is used to create a form and it doesn't save nothing on db.
Instead, the create action saves and validates data on db.
Let's see these:
def new
#country = Country.new
end
def create
#country = Country.new(params[:country])
respond_to do |format|
if #country.save
format.html { redirect_to countries_index_path, notice: 'Provincia Creata.' }
format.json { render :json => countries_index_path, :status => :created, :location => #country }
else
format.html { render :action => "new" }
format.json { render :json => #country.errors, :status => :unprocessable_entity }
end
end
end
I want to know , why the framework doesn't permit to use a single variable , passed from new to create to handle the creation of a resource?
I mean , in new and create we create every time two different variable, Country.new: we use one to create the form , and the other to pass the data of the form Country.new(params[:country]).
Or better , why we can't collide the two action new and create to one action? (for the Restfull theory we can't I think). It should seems a stupid question but i want to have clear this concept in my mind.
Thank you.
These two actions do two completely different things - the one action renders something and never writes s.th. to the DB while the other ones main purpose is not to render s.th. but to write s.th. to the DB.
Of course you can write a whole application with just one single action. But you would end up which a huge cascade of ifs. So you use routing to clear up that mess, and separate what does not belong together.
And the one action is idempotent while the other one is not - which is mirrored by one being a GET and the other one being a POSTrequest.
why the framework doesn't permit to use a single variable , passed from new to create to handle the creation of a resource
Well, many reasons. One is that those variables are not identical. This creates a blank country to render an empty form:
#country = Country.new
While this one is part one of two-step process (new+save). It creates country object from submitted data.
#country = Country.new(params[:country])
Or better , why we can't collide the two action new and create to one action?
It's possible to implement such action. Tell me, how would you then differentiate between "I want to render a blank form, not saving anything" and "I want to save an empty object, taking all default values"? Not to mention, you would now have to branch in the view, separating two distinct logical states ("new form" and "page for object that has just been created")
The new and create are very much different in their implementation
The action new is used to render an empty form.Thus new uses an HTTP GET,because GET request isn't supposed to modify any data. new only creates the local object but does not attempt to validate or save it to the DB.
But in case of create since our aim is to create new data we are using an HTTP POST to the create controller. create instantiates the new object, validates it, and then saves it to the database.
This one is a bit challenging so bear with me. Here is the summary. I added a custom validation to one of my models. After I added this everything works fine with all actions except the update action. If I restrict the new validator to only the create action, then the update action works fine. Below is the related code:
In my model
validate :start_must_be_before_end_time
def start_must_be_before_end_time
return if customer_start.blank? || customer_end.blank?
if customer_start > customer_end
errors.add(:customer_start, "start time must be before end time")
end
end
In my controller for the update action:
def update
#handover = Handover.find(params[:id])
if #handover.update_attributes(params[:handover])
UpdatedHandover.perform_async(#handover.id)
flash[:success] = "Handover Template Updated and Approvals Sent!"
redirect_to view_context.select_handover_cal(current_user)
else
flash[:error] = "Please correct the following errors in your form!"
render edit_handover_path(#handover.id)
end
end
So if the start time is before the end time in the create action, everything works fine. It renders the new action and displays the error. If this happens in the update action it gives me a missing template error for the edit action. The edit file is in the proper place and this works if the validator is restricted to the create action. I cannot figure out for the life of me why this is giving me so much trouble. This is rails 3.2.18. Thanks for your help!
You should pass template name to render method, not a path. So if you want to render 'edit.html.erb', pass 'edit'.
Change
render edit_handover_path(#handover.id)
to
render 'edit'
Note that if you used extra instance variables in edit template, you would need to set them in update action.
For better understanding of render:
When you use render, you pass the instantiated object (newly created or updated). When attempting to update the object, validation was triggered and, if somehow unsuccessful, you render edit, since your in memory object contains necessary validation errors.
But edit_something_path is used when you use redirect_to. When you have the object saved and you can get the persisted data from the database.
You problem can be solved in two ways:
render 'edit'
render :edit
# Your validation errors will persist
or
redirect_to edit_handover_path(#handover.id)
# Your validation errors will be gone
I have a model that I only want to ever return JSON, regardless of any conneg or file-like extensions on the URI (e.g. /app/model.json). Google-fu is coming up short and this can't be that difficult.
In your controllers you simple have to create a respond_to block that only responds to JSON:
respond_to do |format|
format.json { render :json => #model }
end
This is actually a decision made by the controller, not because there is/is not a model or view present. In your controller you can:
render json: #your_model
However you will quickly find that the default implementation of to_json (which is what is used internally, above) can be annoying hard to do exactly what you want. When you reach that point you can use RABL to create views that massage the JSON from your model(s) exactly as you want.
I am learning RoR, and I have a general design issue that I'm trying to work around. I want to get some input on the answer to make sure I follow some kind of best practice.
I have a page that's made up of 3 subsections, let's call them A, B, and C. Certain actions cause each of these to refresh via AJAX, so I want to have them each have a controller action that allows any of them to render individually, without the page chrome.
The way I've structured this right now is with a controller that has 4 actions: index, A, B, C
Each of A, B, and C renders its view with layout=>false so I can render just that piece via AJAX when needed. Then, you have index, which renders some extra stuff along with the view of A, B, and C.
Esentially what I want here are 3 subcontrollers, and a master controller that invokes the subcontrollers as needed. Or I think that is what I want. What I think I don't want is partial views, because there is some setup I do in the controller for each of A,B,C and I would then have to duplicate the setup code in both the controller for index and the controller for A,B,C.
The code I have in mind is something like this:
my_controller.rb:
class MyController < ApplicationController
def index
#aOutput = A
#bOutput = B
#cOutput = C
render //can use #aOutput, #bOutput, #cOutput in the view
end
def A
render :layout => false
end
def B
render :layout => false
end
def C
render :layout => false
end
end
This would enable me to now access MyController#index when I want the full page, and MyController#A, etc when I want to re-render the contents of each subsection.
The problem here is if the code is structured like this, you will run into DoubleRender errors when accessing MyController#index. What is the correct way to approach this kind of thing? Feel free to blow up any assumptions I made here, with the only requirement being a page that has three sections that can update individually as needed without reloading the whole page.
I am not sure if I understand the problem clearly but I think here is what I think your problem is.
You want to be able to render only a view without invoking controller action entire.
This can be done by using render :action => :action_name
You want to render some text only for ajax calls
Use respond_to block in order to achieve this. For example.
def whatever_action
.... # Your action voodoo here
respond_to do |format|
format.html { ... } # If it a normal HTTP request
format.js {...} # If it's an ajax or JSON request
end
end
The design that you are proposing has basic flaws like non-adherence to some of the SOLID design principles. I strongly recommend reading a good book on basics of rails.