Using responders gem with Rails 5 - ruby-on-rails

I'm using responders gem to dry up my controllers. Here's my current code:
class OfficehoursController < ApplicationController
def new
#officehour = Officehour.new
end
def create
#officehour = Officehour.create(officehour_params)
respond_with(#officehour, location: officehours_path)
end
def officehour_params
params.require(:officehour).permit(:end, :start, :status)
end
end
The problem that I'm facing right now is:
When I send valid parameters to create, it redirects to officehours/ as expected, however when I get 422 (validation error), it changes the URL from officehours/new to officehours/ (however it stays at the form page... idk why). The same happens for edit/update actions.
So, I want to stay at the .../new or .../edit when I get 422 error, how can I do this?

I don't think the issue comes from the gem. It just follows RESTFUL API, so does Rails.
That means the path for creating office hours is /officehours. Let's talk about your case:
There is nothing to say when we creating successfully. In your case, you redirect users to officehours_path.
When we creating unsuccessfully, we need to re-render the error form to users. But we were rendering the form in create action. As I said above, the URL for creating is /officehours, so you will see the /officehours instead of officehours/new
In order to keep the url /officehours/new:
We can set /officehours/new instead of /officehours for :create action. But you should not do that, are we going to break RESTFUL API?
Creating via AJAX to keep the URL persisted and you have to handle everything, eg: set flash messages on client, using Javascript to redirect in case of success, render the form error in case failure, ... And I don't think the gem help you dry up your application anymore.
Hope it helps!

I don't think so that it's a problem in responders gem, as I've noticed the same in rails applications. It seems like the default behaviour of rails applications.
take a look at this link for the explanation.

Related

Rails: Model.find() or Model.find_by_id() to avoid RecordNotFound

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.

Make 301-redirects administratable by the user in Rails?

we are currently relaunching a bigger website from PHP (Magento with a quite exhaustive forum) into a Rails-app while keeping the forum.
During this undertaking we will migrate quite a lot of content to new URLs, which means we'll have to 301 redirect a lot of them.
Now we all know about Apache/NGINX-rewrites. I also found https://github.com/jtrupiano/rack-rewrite for RACK.
But is there a good way to make 301-redirects administratable by our users with Rails? (I'm basically looking for a GEM or RACK-app, where our users can log in, then see and edit the existing redirects).
Thanks for any help.
You could store all redirects in a model with attributes "from" and "to". Then, you can manage this redirects from your admin area as you want.
Then, in your ApplicationController, you can wrap your actions in a around filter as it says here:
around_filter :catch_not_found
private
def catch_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect = Redirect.where(from: request.original_fullpath).first
redirect_to "#{request.base_url}#{redirect.to}" if redirect
end

Using response_with in Rails, how can I prevent execution of create action on not accepted MIME types?

I'm building a RESTful API using Rails 3.2.21. The API should only response to xml or json for now. I have a simple resource called Users with a create action, that creates a new user on a POST Request.
Here is the Code for doing that:
respond_to :json, :xml
def create
#user = User.new(params[:user])
#user.save
respond_with(#user)
end
Everything goes fine so far, but then I tried to check the error cases. So if I do a POST request to /users.html, the answer is '406 Not Acceptable'. What is correct. But then I saw in the database, that the user was created anyway. So the create action is executed although the requested accept format (html) is not supported and a 406 error is responded.
I don't know if this is intended. Until now I really liked response_with from Rails, because it does lots of stuff for me, but this behaviour seems to be odd. From the perspective of a client you try to create a new user, receive 406 and you obviously assume that the request failed, so the user is not created, right?
Since I've defined the accepted MIME types in the class method respond_to, it should be possible for Rails, to prevent execution of the entire action. Sure, respond_to is only related to the http response, but then for the client it is still unknown which part of the POST request succeeded and which failed.
Are there any setting or additional functions in Rails what I've overseen or is this just 'not thought to the end' in case of POST requests using respond_with or is this behaviour even intended to react like that? Of course I can add some custom before_filters for checking the MIME types, but then I can also remove respond_with and handle everything on my own.
I'm looking for a nice and clean solution for that problem using respond_with.

Create a generic template for JSON from a rails application

I'm writing a rails application with an AngularJS front-end, this is part of a tutorial series I'm writing on connecting rails and angularjs. This means my rails application communicates with the browser exclusively in JSON.
In the angularjs $http documentation it describes a potential json security vulnerability where the json request can be embedded into a script tag, plus some tricky use of jsonp, to allow something akin to a cross-site scripting attack. I've found a few other pages, one in particular I thought described this well, and dates from 2008, so this isn't a new issue.
Apparently this isn't a vulnerability in standard rails json rendering, as rails by default provides back an object containing an array. But when working with angularjs we appear to set root: false (although I have to confess I can't find where I did that, but it's definitely not giving the root node).
Anyway, the bottom line is that the angular documentation recommends prefixing any json response with )]}', so:
['one','two']
Becomes
)]}',
['one','two']
Angular then automatically strips that off again.
I'm looking for a way to do this elegantly. I've seen a lot of questions and answers on stackoverflow about this, but most of those either relate to much earlier versions of rails before JSON handling was more thoroughly embedded, or seem to require me to create a lot of boilerplate code. I'm looking for a method that I can apply to the application controller, or as a helper method, that will work everywhere.
The controller that I'm currently using looks as follows:
class ClubsController < ApplicationController
respond_to :json
# GET /clubs.json
def index
#clubs = Club.all
render json: #clubs
end
end
This doesn't call any templates - the render action skips the templating engine. I can get this working by changing the render line instead to:
respond_with json: #clubs
And creating a template file views/clubs/index.json.erb that contains
)]}',
<%= raw(#clubs.to_json) %>
But I'd then have to create a template for every action on every controller, which feels like boilerplate. I'd like instead to be able to change views/layouts/application.json.erb to have something like:
)]}',
<%= yield %>
But that doesn't work because we only get templating if we call respond_with. And if we call respond_with, we have no way to put the #clubs into the response - so we end up with:
)]}',
As the entirety of the response.
An alternative would perhaps be to override the as_json method to prepend what I want, but that seems a bit like a sledgehammer. Ideally there would be a place I could introduce a helper method, something like:
render prepend_vulnerability_protection(json: #clubs)
So, after all that, two questions:
Is this even a real problem, or does Rails already have some other protection that means I don't need to worry about this at all
Is there a way to do this centrally, or do I need to bite the bullet and create all the boilerplate templates? I can modify the scaffold generators to do it, so it's not the end of the world, but it does seem like a lot of boilerplate code
So, no responses as yet. I'm going to write down what I find from my research, and my current answer.
Firstly, I think this is a genuine vulnerability in rails. Unfortunately the rails and JSON/JSONP area has had some other recent vulnerabilities relating to the JSON parser at the Rails end. That has really drowned out any google search relating to this specific XSS issue.
There are a couple of approaches to resolving this:
Have your application only respond to put/post/delete requests. That's not really an option when integrating to Angular - well, it is, but it means overriding a bunch of standard behaviour
Insert something at the front of your returned JSON - this can be the root node (default rails behaviour in rails 3, no longer in 3.1), a closure like )]};, or a loop like while (1);. Angular expects and can deal with )]}',
I've looked at using a json template in my rails app. You can do this with one of many gems, the one I like the look of is JBuilder (railscast 320), but RABL is perhaps more powerful (railscast 322).
This does mean a template for each of the actions on each of the controllers. However, I've also just completed working out how to have rails scaffold those for me automatically, so it's not as scary as it was when I first asked the question, and I can see some other reasons that I might want more control over the json that is returned from my application.
Having said that, I couldn't immediately see a way to get JBuilder to prepend an arbitrary string - it seems to only want to prepare valid JSON (and this I think is not valid JSON). RABL looks like it can do it, but it is a bit more complex. It can definitely be done through just using ERB, but I feel kinda wrong in doing that.
The other alternative I've identified is a helper method in application_controller.rb, which I then call in each of my controller methods. This is reasonably elegant, and I can quite easily change my template to do it. So I'm going with this for now:
class ApplicationController < ActionController::Base
def render_with_protection(json_content, parameters = {})
render parameters.merge(content_type: 'application/json', text: ")]}',\n" + json_content)
end
end
class ClubsController < ApplicationController
respond_to :json
# GET /clubs.json
def index
#clubs = Club.all
render_with_protection #clubs.to_json
end
# GET /clubs/1.json
def show
#club = Club.find(params[:id])
render_with_protection #club.to_json
end
# POST /clubs.json
def create
#club = Club.new(params[:club])
if #club.save
render_with_protection #club.to_json, {status: :created, location: #club}
else
render_with_protection #club.errors.to_json, {status: :unprocessable_entity}
end
end
end
Note that you should be also including CSRF protection in your application controller - so see this as additive to the security precautions you were already taking, not a replacement.

restful rails model validation

I am working on a rails application (I have some experience with rails). But, this time I am using RESTful to build it. I am wondering how do I validate my models in a RESTful fashion? What I mean by that is when a user enters data into a form, but the model validations prevent the model from being created what is a RESTful way to redirect the user back to the new action with the data they entered still present in the form?
REST only affects your controllers and routes!
Model validations in a RESTful Rails app are the same as the validations in any other Rails app.
Josh - you mention wanting to know how to redirect the user back to create if it errored out. If you are use to earlier versions of Rails just make sure you are using the form_for helper rather then the start_form_tag from early. Your controller code will look pretty similar to how you might be used to... for example (a Customer model):
def create
#customer = Customer.new(params[:customer])
if #customer.save
flash[:notice] = 'Customer was successfully created.'
redirect_to(#customer)
else
render :action => "new"
end
end
You'll notice now the redirect_to(#customer) that forwards to the record that was created in the transaction. But on failure it's the same old render :action.
Whether developing in a RESTful or regular fashion, the backend implementation remains generally the same. Just as in a non-RESTful app, you would simply re-render the create page with the form with the instance the user is trying to create. Really with REST, all you are doing is creating a uniform set of URLs which respond to different HTTP requests, everything else remains the same.
use the scaffold generator to view the example codes on restful controllers

Resources