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."
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)
Is there a simple way to write a ruby if statement to redirect a dynamic page to a static page?
I’m writing this in my pages controller where the site pages are generated. This is what I’m attempting to do.
If “/index/page1”
redirect_to “page2”
return
end
This redirects all the pages created in the pages controller to “page2”. I know the syntax is incorrect. I need help in writing out the correct way to test for the first condition.
Any help is appreciated. Thank you!
Here is an update / more information to my question.
Here is my show action in the PagesController
def show
#page = Page.find_by_url_path("/#{params[:url_path]}")
layout = "templates/#{#page.pageable.class.name.underscore}"
respond_to do |format|
format.html { render layout: layout }
end
I need to write an if statement that looks for one specific URL that gets generated. This page is created by the show action.
e.g. https://host.com/products/page1
Then redirect it to another specific URL. This is a static page on the site.
e.g. https://host.com/page2
I am having difficulty in writing the if statement to find the first page. This is what I've tried.
if "/products/page1"
redirt_to "/page2"
return
end
Depending where I put the code within the show action, I either get a double render error (as subparry explains below). Or I redirect all the pages generated through the show action to "/page2".
This application was written by a more experienced Ruby developer and I'm doing my best to maintain / update it. If I need to post more detailed information, please let me know. Thank you.
Well, as usual with these kind of requirements, there are many ways to achieve it.
As you know, different pages (views) in controllers are represented by instance methods (or Actions), for example I can imagine your PagesController looking something like this:
class PagesController < ApplicationController
def page1
# do something...
end
def page2
#do something else...
end
end
So, the easiest way would be to trigger a redirect from page1 redirect_to "https://host.com/page2", I don't know the reasons behind the decision to redirect. If it is a temporary redirect, it might be the best solution because of ease of change later, but if it is a more permanent redirection, I would implement it at the web server level (Nginx for example)
It depends on your use case.
PS: Don't forget that if you redirect in your action, it does not imply a return, so if you have more code below and another call to render or redirect, it will fail (double render error) so either you remove further renders/redirects or insert an early return.
EDIT:
Ok, now I understand better your case. You have a model called Pages which has a column called url_path which details the location of each page.
So, if I understand correctly, you'll have to do the conditional statement like this:
def show
if params[:url_path] == 'page1'
redirect_to 'https://host.com/page2'
return
end
# Rest of action code...
end
I don't know for sure how are paths stored in url_path, but you get the idea!
PS2: When you write if 'products/page1' you are basically saying if true and always entering the condition because only nil and false are falsy values, everything else is truthy.
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.
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.
As a long-time Ruby and Rails user, it never struck me until today to really think about the get-and-redirect pattern in Rails. The typical example of this would be calling a create() action, and then redirecting the user to a show() action to display the newly-created item:
class JournalEntries
def index
#entries = JournalEntry.all
end
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
redirect_to :action => "index"
end
end
However, this has the inherent disadvantage that you are doubling your network traffic. This both slows down your users' site experience, as well as increasing your bandwidth charges.
So why not just do this instead:
def create
#entry = JournalEntry.new( :name => "to-do list" )
#entry.save
index
Same output, and no extra overhead required. But in addition to this, there is an even more substantial problem: redirect_to can only redirect using GET. This causes major problems for RESTful apps that use four different HTTP methods.
In my case, I wanted a user to be able to call /journals/8 and retrieve the Journal with that ID. If it wasn't found, I wanted to create a new, empty Journal object. In either case, the Journal object would then be sent to the caller.
Note that the create() method in RESTful Rails is routed from "POST /players". But since redirect_to (and the underlying HTTP redirect) can only send GET requests, it actually redirects to "GET /players", which is the index() method. This behavior is clearly wrong.
The only solution I could think of was to simply call create() instead of redirect_to() as in my example above. It seems to work fine.
Any thoughts on why redirect_to is preferred to calling actions directly?
If they do a page refresh they don't get that annoying "Resend data?" popup
It's not just that the popup is annoying (and makes no sense to most users) -- if the user clicks "yes, re-do the POST", he'll end up creating another Journal Entry (or whatever).
Also, it's annoying for the URL to read /posts/create instead of /posts since the user cannot copy / re-use it.
The reason for it is as you point out. You redirect to a GET request, which is correct when it comes to REST (only do updates with POST/PUT, only get data with GET).
A redirect surely gives a little overhead with the redirect, but since no data is actually being sent between the browser and the server except for the POST data and the redirect (which is only sending the new url to the browser) I don't think that the issue of bandwith is of concern.
But on another point, you should not redirect to /journals (by calling redirect_to :index), you should redirect it to the newly created journal entry (by calling redirect_to #entry) which will work if you set up the routes correctly by, for instance map.resources :journals
Update:
I think, for creating the Journal when one doesn't exist, you should ask the user for more input. What is the reason for you to create the entry? The entry should have some text or some other input from the user, so I think from a REST (and rails) perspective you should actually redirect it to the new() method (with a GET request) where the user can input the additional information, that one will then POST the input and create the entry and after redirect to the newly created entry.
If you don't have any extra information that needs to put in, I'm not sure how to do it in a RESTful way, but I would probably have done it by putting the creation logic in a separate method that I would call from the create() and the show() method and then just continue with the show(), not redirecting at all, but also not calling the resource method.
I'm a Python/Django person, but the reasons for the redirect is language agnostic:
If they do a page refresh they don't get that annoying "Resend data?" popup.
This gives you a completely clean, RESTful URL for the page they are looking at. If you used POST it might not matter that much, but if GET was used for the update then you definitely want to get rid of any dangling params.