Difference between new and create (or edit and update) Rails - ruby-on-rails

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.

Related

Missing template error after adding custom validation, template exsists and works without validation

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

How to create a Rails model without an HTML view

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.

Rails - how to pass created record from the new form to a redirected page

I think this is a pretty simple question but nothing I've read has answered my question directly:
I have a new products page with a standard form. After successfully submitting the form, I redirect to a custom controller action and view called "thanks".
On the "thanks" page, I want to be able to print the name of the product just created and possibly some other attributes.
How do I pass the object just created into my new action? Right now the controller looks like this:
def create
#product = Product.new(params[:product])
if #product.save
flash[:notice] = "Successfully created Product."
redirect_to thanks_path
else
render :action => 'new'
end
end
def thanks
end
You can't send object through redirect.
There are three ways to solve your problem:
Render the 'thanks' template directly(not action #thanks)
render 'thanks' # thanks template
You can send whatever instance variable to this template directly. #thanks is no longer needed in this case.
Drawback: The url won't be changed.
Convey messages through session
If you want to show certain messages, you can prepare it in #create and send it through session or flash(part of session actually). flash is better as you don't need to clear it manually.
Note: You may want to use ActiveRecord as session storage if the message size is big, otherwise you'll meet CookiesOverflow by default setting.
Send very simple message through session say obj_id
Similar to #2 but I thinks this is better than #2. In #thanks, you can construct complex message according to if obj_id is present, what is the id and then find related data through db.
You have two fairly decent options.
First, you could adjust the thanks_path route to take an id parameter, and call it like redirect_to thanks_path(#product). Then you can call it up in your thank you method like any standard show method. It might be worth mentioning that if you are going to be displaying sensitive information on the thank you screen, you may want to use a random uuid, instead of an id, to look up the product.
A better way might be to not redirect at all, but rather adjust your view from simply drawing the form to something like this:
<% if #product && !#product.new_record %>
THANK YOU MESSAGE GOES HERE
<% else %>
EXISTING FORM GOES HERE
<% end %>

Why do we need instance variable in new?

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.

Repopulating page with previous values in rails

I have a validation that needs to be done in controller. If it fails I need to go back to the view action back again with all values populated as it is on the page.
Is there a simple way to do that (using incoming params map).
This is the basic way all Rails controllers and scaffolds work. Perhaps you should try generating scaffolds?
def create
#banner_ad = BannerAd.new(params[:banner_ad])
if #banner_ad.save
flash[:notice] = 'BannerAd was successfully created.'
redirect_to :action => "show", :id => #banner_ad
else
render :action => "new"
end
end
end
I populate a #banner_ad here, attempt to save it, if it fails, I return to the form and the #banner_ad object is available to me. I then need to have a form that uses the Rails form helpers to populate the values from the object.
Depends on the flow of your app, really.
If the validation fails, you could pull the data out fo the database ...
if invalid?
#model = model.find(:id
end
Otherwise you might need to store the original values in hidden fields in the view and use those.

Resources