Based on several tutorials online, my rails app uses a :mobile format to render pages optimized for mobile devices.
So for some actions, I have a *.mobile.erb file, in addition to the *.html.erb file.
But what if I want to disallow certain actions on the mobile site?
For example, for a particular resource, you can do Index and Show, but not Edit or New, when you're mobile.
What's the best way to do this? Currently, I just don't have edit.mobile.erb or new.mobile.erb, but that just shows a "Template Missing" error - not very graceful.
Any suggestions?
You could try accessing params[:format] to check if the action is valid for a particular format. If not, you could redirect or show a custom error page, or whatever else it is you want to do to gracefully disallow an action.
#..inside your controller..
def do_something
if !["html","xml"].include?(params[:format])
#invalid format. do something
end
#..do other things
end
Related
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.
I just realized I had a very hard to find bug on my website. I frequently use Model.find to retrieve data from my database.
A year ago I merged three websites causing a lot of redirections that needed to be handled. To do I created a "catch all"-functionality in my application controller as this:
around_filter :catch_not_found
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound
require 'functions/redirections'
handle_redirection(request.path)
end
in addition I have this at the bottom of my routes.rb:
match '*not_found_path', :to => 'redirections#not_found_catcher', via: :get, as: :redirect_catcher, :constraints => lambda{|req| req.path !~ /\.(png|gif|jpg|txt|js|css)$/ }
Redirection-controller has:
def not_found_catcher
handle_redirection(request.path)
end
I am not sure these things are relevant in this question but I guess it is better to tell.
My actual problem
I frequently use Model.find to retrieve data from my database. Let's say I have a Product-model with a controller like this:
def show
#product = Product.find(params[:id])
#product.country = Country.find(...some id that does not exist...)
end
# View
<%= #product.country.name %>
This is something I use in some 700+ places in my application. What I realized today was that even though the Product model will be found. Calling the Country.find() and NOT find something causes a RecordNotFound, which in turn causes a 404 error.
I have made my app around the expectation that #product.country = nil if it couldn't find that Country in the .find-search. I know now that is not the case - it will create a RecordNotFound. Basically, if I load the Product#show I will get a 404-page where I would expect to get a 500-error (since #product.country = nil and nil.name should not work).
My question
My big question now. Am I doing things wrong in my app, should I always use Model.find_by_id for queries like my Country.find(...some id...)? What is the best practise here?
Or, does the problem lie within my catch all in the Application Controller?
To answer your questions:
should I always use Model.find_by_id
If you want to find by an id, use Country.find(...some id...). If you want to find be something else, use eg. Country.find_by(name: 'Australia'). The find_by_name syntax is no longer favoured in Rails 4.
But that's an aside, and is not your problem.
Or, does the problem lie within my catch all in the Application Controller?
Yeah, that sounds like a recipe for pain to me. I'm not sure what specifically you're doing or what the nature of your redirections is, but based on the vague sense I get of what you're trying to do, here's how I'd approach it:
Your Rails app shouldn't be responsible for redirecting routes from your previous websites / applications. That should be the responsibility of your webserver (eg nginx or apache or whatever).
Essentially you want to make a big fat list of all the URLs you want to redirect FROM, and where you want to redirect them TO, and then format them in the way your webserver expects, and configure your webserver to do the redirects for you. Search for eg "301 redirect nginx" or "301 redirect apache" to find out info on how to set that up.
If you've got a lot of URLs to redirect, you'll likely want to generate the list with code (most of the logic should already be there in your handle_redirection(request.path) method).
Once you've run that code and generated the list, you can throw that code away, your webserver will be handling the redirects form the old sites, and your rails app can happily go on with no knowledge of the previous sites / URLs, and no dangerous catch-all logic in your application controller.
That is a very interesting way to handle exceptions...
In Rails you use rescue_from to handle exceptions on the controller layer:
class ApplicationController < ActionController::Base
rescue_from SomeError, with: :oh_noes
private def oh_noes
render text: 'Oh no.'
end
end
However Rails already handles some exceptions by serving static html pages (among them ActiveRecord::RecordNotFound). Which you can override with dynamic handlers.
However as #joshua.paling already pointed out you should be handling the redirects on the server level instead of in your application.
I'd like to have a a form view that can, depending on circumstances, have submit functionality disabled in a bullet-proof way so that even a clever user could not edit the HTML source (via a browser extension) to re-add the submit button.
It seems one way to do that might be to somehow inject an invalid authenticity token that replaces the (valid) rails-generated one, so that even if a user somehow re-adds the submit button (by editing the HTML via a browser extension) it would still be an invalid submission.
My thought is to have some logic in the view:
- if #form_disabled # set by controller
- somehow_invalidate_the_authenticity_token?
How might one 'break' Rails form submission?
The purpose of doing this, instead of rendering the preview in a :show action, is to have the exact same view displaying both the live-form and the dead-form.
If I were you, I would use pundit.
It's pretty simple, and has few lines of code if you need to know how it works.
I'd start to write the code here, but I realize that the example at the readme fit your needs.
At the application controller add this
At the folder app/policies put the class PostPolicy, of course, you must replace "Post" with the name of your controller in singular (even if you have not a model with that name). The update? (and create?) actions should return true/false to indicate if user is allowed or not.
A few lines down on the readme, you will find the PostsController#update action, which call to authorize with the record before the update. I think you want do the same with create (then you need a create? method at the policy class).
Pundit needs current_user controller method, if you don't have it. Just follow the user customization instructions.
Of course, new and edit actions don't call authorize because they are allowed to everybody. Only the POST & the PUT/PATCH actions are forbidden.
Yes, it's more than a surgery of one line of code. But it's simple and the right way of give access to users.
After reading my other answer, I start thinking that you can do the same that Pundit does at the controller:
def update
if <unauthorized user>
flash[:alert] = "You are not authorized to perform this action."
redirect_to(request.referrer || root_path)
else
# all the update stuff
# ...
end
end
I'm intentionally asking this question in an inflammatory way because I'm concerned that I'm missing something.
The Rails Way for dealing with an update to a model is the following:
class UsersController < ApplicationController
...
def update
#current_user = load_user
if #current_user.update_attributes params[:user]
redirect_to success_path
else
render :edit
end
end
end
This is all well and good except that you end up on an odd URL when the form submission is incorrect:
Editing User
You find yourself on the path:
users/:user_id/edit
After submitting edits that don't validate
i.e. you're going to need to fix the inputs in your form and resubmit:
users/:user_id
After submitting edits that do validate
success_path
Why the hell should you be on a different URL just because the form has errors?
The problem...
You're doing the same thing but you're now on a different URL. This is a bit odd.
In fact frankly it feels wrong. You're on a form which has not validated correctly and so has reloaded. You should still be on /users/:user_id/edit. If you'd done JS validation you would be.
Furthermore if you've got any "currently selected" logic going on in your navigation then you are in fact visually in the wrong place as the correct nav item is no longer highlighted - it looks like you're on the user profile page.
Why the hell should you be on a different URL just because the form
has errors?
Because when you first went to:
users/:user_id/edit
...you were requesting a GET.
Then you POSTed to:
users/:user_id
So by sending the form post, you have requested a different resource route, and have a different URL by definition.
The framework doesn't care what happened in the background while your request was processing - all it knows is it was a POST (which by convention is not necessarily idempotent as a GET is)
Actually it's not the "Rails Way", it's "REST Way". Wikipedia: Representational state transfer
If you follow the rules you get REST-compliant web-service for free. As I understand it the path "resource/id/edit" is specific to html documents. Web-service clients don't need form for editing.
So the guys were trying to be consistent. If you don't need web-service compatibility you can change the routes of course.