Very general question I was hoping someone could clarify for me. I'm looking at the basic generated scaffold code for a model called products. I noticed the new and show actions in the controller don't have much written in them. In fact, show is entirely empty and new only has the line "#product = Product.new". I know these 2 actions are supposed to go to a separate view. A view to show the resource, and a new form view to input info and create a resource, respectively.
So, I'm curious how that actually happens. Other actions have redirect_to :some_path which makes sense, but how exactly does "render action 'show', location: #product " bring up the items show page when the action is empty? Also how is that different from redirect_to #product ?
thanks
Will
Render produces a string that will displayed as the response to the request to the application.
redirect_to produces a response header resulting in a new request to the application.
The render action 'show', location: #product uses the the file app/views/products/show.html.erb with #product as a parameter to produce the html which will be returned.
The reason some of the controller functions are empty is that rails are using defaults. So if you don't tell rails what to render then rails will look for a file in the appropriate location.
Methods ending with redirect_to are usually post/patch requests saving something in your database and after the requested action has been performed they redirect the user to a method meant for displaying information.
Related
So I'm working on a web application and I have a blog page, with two controllers, a posts controller for the blog and then a subscribers controller that simply allows the user to add a new subscriber. So currently I render the content using a view defined in posts, and then within that I have a partial, _subscriber, to handle the subscription model.
The problem comes when the user tries to subscribe. I want to be able to render the new action to show validations, like this:
def create
#subscriber = Subscriber.new(subscriber_params)
if #subscriber.save
flash[:success] = "Thank you for subscribing!"
redirect_to subscribers_url
else
render :new, status: :unprocessable_entity
end
end
Besides the fact that this results in a duplicate view (new.html.erb is identical to the partial), because it is not a partial view, it reloads the entire page and now replaces all of the post with just the subscription form. Currently, my workaround is to do a redirect when the user clicks the button, but then validation errors don't show and it isn't really an ideal solution.
I tried just rendering the partial, instead of the :new action, like this:
render partial: 'subscriber', status: :unprocessable_entity
But has the same effect as just calling redirect; my validation errors won't show.
Perhaps the ideal solution is to use something like AJAX. I tried to understand Turbo Frames and Turbo Streams, but I'm a little confused how I would implement that in this situation. Essentially, I could wrap the subscribe section of my posts page in a turbo frame but then how would I update the turbo frame? I don't want to have a different post page because that wouldn't make sense. I only want to change the content of the view inside.
What would the correct/best practices way of implementing this functionality be? Any suggestions would be appreciated!
If you don't want to actually reload the page, then yes, you'll need to do something with either AJAX / UJS or Turbo frames.
My favorite AJAX / UJS tutorial
A good Turbo tutorial
(Or go to the dark side with React or Node or some other JS solution)
When a user makes an invalid create request for a resource, I want to send them back to the form and show them an error. As far as I can tell, calling render "new" is the standard way to do it.
This seems like a bad idea to me, because the create request has taken the user to /resources, whereas my "new" form is otherwise at /resources/new. If I use render "new", the URL in the address bar will not reflect what the user sees. If they make a GET request there, they'll end up on a different page.
I thought I could solve this problem by using redirect_to new_[resource]_path, but if I do that I lose access to the form data and errors. Surely this is not an uncommon problem. Is there a better way to deal with it?
I have the same problem with edit/update and other form/submit action pairs.
TL;DR:
class ResourcesController < ApplicationController
def new
#resource = Resource.new(resource_params)
if resource_params.present?
#resource.validate
end
end
def create
#resource = Resource.new(resource_params)
if #resource.save
redirect_to #resource, notice: 'Resource has been created'
else
redirect_to new_resource_url(resource: resource_params)
end
end
private
def resource_params
params.fetch(:resource, {}).permit(...)
end
end
I asked this myself as well in my early beginner days, on why Rails scaffold generator generates the def create action to render :new if saving failed, instead of redirecting to the correct URL just something like above, which would confuse the users because their URL would have changed from /resources/new into /resources even though they would still see the exact same Resource form on the page, and therefore they would not be able to reload the page or copy this /resources URL (for example, if they want to share this URL to someone else), because should they share this /resources URL, the others would see a list of Resources on the page instead of what the original user would have expected to see from the copied URL: which is the form page.
After the user submits the form, the URL they see on the address bar should have changed into POST http://localhost:3000/resources instead of just simply http://localhost:3000/resources. The browser hides the HTTP Method being used, that's why this leads to their possible confusion that /resources seems to have been both sometimes: a form page, sometimes a list of resources page. However, speaking of UX, every time anyone enters something or pastes something in the URL address bar of the browser, it is always automatically implied to be doing a GET request. Therefore, it makes sense for the browser-developers to just simply hide the HTTP method (i.e. GET in particular) from the users as to not confuse them.
From my answer above, I only used something like this once before (because there was a certain action that demanded me not to change the URL from the referrer form page). However, I normally render :new instead of redirect_to new_resources_url, simply because:
a redirect_to new_resource_url(resource_params) would take twice as much time and data-transmitted than simply rendering :new. Why? Because you redirect (opening up a new request) with the exact same parameters anyway. Just imagine if your form page is soooo big, and has so many input fields.
and also that there's a limit to how long a URL can be, of which won't guarantee to work if you have a very big form with very long text fields. See this SO
Updated:
As you have said though, why not just POST /resources/new instead of POST /resources when the form is submitted, right? This will solve the redirect_to new_resource_url(resource_params) problem I've shown above, because the URL after form-submit would have been the same and you can just simply render :new, then. And I actually agree on that, and I've used something like this also before long time ago. The main reason I don't use this is that it is not inline with REST standards. That is: POST /resources/new means that you're creating a Resource object "inside" the resources/new location, which then means by REST, after submitting the form, I would and should be able to access this newly created resource by doing something like GET /resources/new/the_newly_created_record, except that... you can't.
But you can still use POST /resources/new, although I would not recommend it on a normal Rails application like yours, and however strictly discourage it on an API-based Rails application.
You may be overthinking this. How about get /resources the index page vs post /resources the create action? Same url in the address bar!
It's not a problem. Neither a technical problem or an aesthetic problem.
render "new" means: render this template, not: go to this route. And although templates often have the name of the corresponding action, it's not a requirement.
I'm following the Ruby getting started guide, section 5.10 asks us to add code to check if the data added to the model is valid, if it isn't then we should call render 'new' to refresh the page with the users data:
def create
#article = Article.new(article_params)
if #article.save
redirect_to #article
else
render 'new'
end
end
I've noticed though that the moment I submit wrong data my url changes from :
http://localhost:3000/articles/new to http://localhost:3000/articles, why is that? The rendering seems to work since I get the error messages indicating my invalid input just like in the tutorial.
The html output looks slightly off too, there's an extra space between one of the labels and the text input field.
URL change because it's a PUT HTTP method, RoR use REST for CRUD actions, "In Rails, a resourceful route provides a mapping between HTTP verbs and URLs to controller actions. By convention, each action also maps to particular CRUD operations in a database."
here is a frequent scenario: user has a dashboard in order to show some related information to the user. also, use can do some actions in his dashboard. according to abstraction, each of these actions, has its own controller and they are not all in the dashboard controller. for example, in order to update some information about user, you have user controller and not the dashboard controller. dashboard controller is only for rendering some information.
ok? now you have a form in your dashboard, posting the inputs to an arbitrary controller and we want to render the results(success message if succeeded and pre-populated form with errors if failed) in the dashboard. if we use redirect_to in the controller, we lose the validation errors and pre-population. if we use render, there will be uninstantiated variables in the dashboard template and we run into some errors.
what are the tricks to handle this?
P.S: there were some similiar questions but the answers were using render but it leads to the mentioned problem and doesn't work.
You could do it via ajax and remove/add appropriate html elements dependent on success/failure.
Or you could have
if #user.save?
redirect_to dashboard_path
else
#my_instance_variable = InstanceVariable.first
render "dashboard"
end
Please explain why do we need this code in a controller? What is the significance of this block of code?
respond_to do |format|
format.html # index.html.erb
format.json { render json: #users }
end
It allows you to format output differently depending on the format the user/caller requests. If you were to access http://yourhost/controller/index.html, the controller would respond with the ERB template index.html.erb (or HAML or whatever). If you were to access http://yourhost/controller/index.json, it would respond with the JSON template index.json.erb.
This allows you to have a single controller action that can prepare data and then select the view for rendering based on the requested format.
Defines mime types that are rendered by default when invoking respond_with.
So basically, this means that your controller action can be hit in different formats(html, json in your case), and still provide data back to whatever is calling it. This is helpful for API development, and many other things.
For example: You want to get a json list of all your users to do something with javascript. You'd call /users.json and this would go to your user_controller#index action and know to render a json object of all your users.
The above code is scaffold generated, and provides a way to render *.html and *.json views for your controller, making it easy to have access to data for implementing an API or normal views for your web application.
You can also create XML output:
format.xml { render xml: #users }
and other formats like PDF, or DOC, depending on the gems you are using.
See the Rails Guide Action Controller Overview for more information.