Check for the existence of multiple params - ruby-on-rails

I'm creating an entry form that I want to only be accessible when three url params are in place: example.com/entries/new/2011/01/27 If someone tries to access any other url (i.e. example.com/entries/new or example.com/entries/new/2011/) I want Rails to set an :alert and bounce the user back to the index page.
Currently, I only have this code in my routes.rb match '/entries/new/:year/:month/:day' => 'entries#new'. What do I need to do to control the redirection if the proper params aren't in the URL? Would I check for each param in the controller and then perform a redirect_to, or is this something I can do from the routes.rb file exclusively? If it's the former, is there an easier way to check that all three params exist other than:
if params[:year].nil && params[:month].nil && params[:day].nil redirect_to ...

This route requires the presence of all three parameters:
match '/entries/new/:year/:month/:day' => 'entries#new'
With only that route, GET /entries/new will result in:
No route matches "/entries/new"
You can redirect from within routes.rb like this:
match '/entries' => 'entries#index'
match '/entries/new/:year/:month/:day' => 'entries#new'
match "/entries/new/(*other)" => redirect('/entries')
The second line matches paths where all three parameters are present. The third line matches all other cases of /entries/new using "route globbing", and does the redirect. Requests matched by the third line will not hit EntriesController#new.
Note: you may not need the first line if you've already defined a route to EntriesController#index -- but watch out for resources :entries, which will redefine index and new.
More info can be found in the guide Rails Routing From the Outside In. When using date parameters, constraints are a good idea (Section 4.2)

Related

How does this rails route match any incoming action to it's correlated method on the controller?

I have a Rails route defined like this:
match '/test_report(/:action)' => 'test_report#index', via: :all
I am not an expert on Rails routing, but using context clues I would have expected that this route to map any request like /test_report/some_action or /test_report/other_action to the index method on the TestReportController, because of the part of the route definition after the hash rocket: => 'test_report#index'.
However, this is not the behavior. Instead, I can create another method on the TestReportController called update_report, and then I can POST to /test_report/update_report to trigger the update_report method. From the route definition I'm using, I wouldn't expect this to work, but it does. I would have expected the POST to hit the index method on the controller.
Note: Just so we're clear, I do understand that the (/:action) part of the route marks it as an optional part of the URL, and the :action symbol is special here and is interpreted as an action's name.
TL;DR:
If the => 'test_report#index' in the above route doesn't map all requests to the index method on the controller, what does it actually do?
As you probably know, whatever parameters you define within the route pattern become available in the params hash in the controller.
So in your example, :action becomes available within the controller as params[:action], which is also used internally by Rails to load the correct action. (If you inspect, you'll see params[:route], params[:controller], params[:action], etc.)
I'm guessing that defining your own :action in the URL pattern like that causes Rails to prefer that over what's in your mapping (skipping the index in test_report#index).
If you want to live in both worlds, you'll probably want to name it something other than :action.

What is the right Rails route for passing in a different parameter than id, to return a JSON User object? Controller method provided

EDIT: This is Rails 4
Rails code in the users_controller.rb file
def showobjectdata
#users = User.all
#user = User.find_by(:username => params[:username])
render :json => #user
end
I have been trying lots of routes, but (add the "localhost" part to the beginning of this URL) /users/showobjectdata/existingusername in my browser
returns null.
Please Note: I am able to render JSON data about all users or a specific user, if I look up the user some other way than passing in a parameter which is not an id in the browser's URL field. Like in the controller method I can specifically look up a user by a specific email address. And users/show/:id renders the JSON user data of that id, because I have defined the show controller method to render JSON user data (for now).
Here is an example of a route I tried in my routes.rb file:
match 'users/showobjectdata/:username', to: 'users#showobjectdata', via: [:get, :post]
I tried various combinations with plain GET, plain POST, nested parentheses, etc. I always get null except for plain POST which doesn't work.
Try this
match 'users/showobjectdata/:username', to: 'users#showobjectdata', via: [:get, :post], param: 'username'
This is the right answer.
Basically, my username parameter (firstname.lastname) was not being passed as a full string. It is was being passed as firstname instead of firstname.lastname, with the Rails application considering "." to be where the format parameter started ('lastname' was considered a format input in the passed in parameters). I saw these passed in parameters appear in my browser ironically only when I got another error trying something new (basically my application was not responding to 'respond_to |format|' in the 'showobjectdata' method when I tried it pretty randomly - this of course led to these parameters showing up at the bottom of the screen and the googling of a solution. Yes after getting this insight on the parameters, I skipped the respond_to way and once again just rendered the json user object directly as before, without differentiating between the HTML and JSON formats).
So, basically this is the right route that worked for me:
match 'users/showobjectdata/:username', to: 'users#showobjectdata', via: [:get, :post], :constraints => { :username => /[^\/]+/ }
The controller method as originally posted is fine!
Source for the ":constraints =>" part:
Why do routes with a dot in a parameter fail to match?

Submit a form to a Rails route with a dynamic segment

I have a route that looks like
match 'solar_systems/:planet_num/:moon_num' => 'solar_system#moon', :as => :moon
I'd like to have a form with a select box for planet number and moon number and have it submit to this route. However I cannot use moon_path because it will have an error if the dynamic parameters are not included in it like this moon_path(4, 1). Is what I want even possible? If so, what do I give to the form tag for the route?
You don't have to use the routing helper methods, and here you can't since at the time of rendering your form you do not know the required parameters. You do, however, know the controller and action, which is really all that's needed for the destination URL. So this should work:
= form_tag('/solar_systems/moon') do
= select_tag(:planet_num, ...
= select_tag(:moon_num, ...
This should render the form tag. To process the request, you will also have to add another route so the right controller action is called:
match 'solar_systems#moon' => 'solar_system#moon', :via => :post
Or, if it makes more sense in the context of your application, you could modify your existing route to make the parameters optional:
match 'solar_systems(/:planet_num(/:moon_num')) => 'solar_system#moon', :as => :moon
See this Rails guide for more details on non-resourceful routes.
If you use this params on controller you need to specified what params is each one, btw in you helper you need to do something like this
moon_path(planet_moon: 4, moon_num: 1)
Cheers!

RSpec controller example param with periods

In my controller spec, when I execute the line:
post :register_pass, :device_id => 'DEV1C3', :pass_type_id => 'pass.com.example.GSPassType', :serial_no => '5ER14L'
I get an ActionController::RoutingError:
Failure/Error: post :register_pass, :device_id => 'DEV1C3', :pass_type_id => 'pass.com.example.GSPassType', :serial_no => '5ER14L'
ActionController::RoutingError:
No route matches {:action=>"register_pass", :controller=>"purchases/passbook/registrations", :pass_type_id=>"pass.com.example.GSPassType", :serial_no=>"5ER14L", :device_id=>"DEV1C3"}
Even though rake routes | grep register_pass returns
register_pass POST /v1/devices/:device_id/registrations/:pass_type_id/:serial_no(.:format) {:action=>"register_pass", :controller=>"purchases/passbook/registrations"}
However, when I remove the periods in the :pass_type_id value, the post line listed above executes and the route is recognized (I verified this using rspec, and even curled it directly, putting a breakpoint in the expected controller action, the route works).
I tried using Rack::Utils.escape on the value with periods, but that failed too. I also tried manually changing the periods to their URL-encoded values, but rails didn't seem to decode them in the params hash.
Why don't periods work in this case?
And how can I get a value with periods passed in (without manually decoding it using String#gsub in the controller?
There are two problems you're encountering: 1) a period is a valid character in a URL sequence, so it's won't be replaced by Rack::Utils.escape or URI.escape, 2) Rails treats a period as a path separator to enable format parsing at the end of urls, e.g. .json, .xml, etc. (as defined in ActionController::Routing::SEPARATORS).
For this use case, I'd recommend adding constraints to this route that allow you to be permissive of any character for :post_id. I don't see what your route file looks like, but it could be something like this with the change:
post "/v1/devices/:device_id/registrations/:pass_type_id/:serial_no(.:format)" =>
"purchases/passbook/registrations#register_pass",
:constraints => { :pass_type_id => /.*/ }
Lots of great info on other ways to add this constraint in the Rails guide on routing

Use a string with forward slashes as a single parameter in routes.rb

Sorry about that confusing title :) I have a resource, ComatosePage (used in the comatose cms plugin), which has a table called comatose_pages which has a field 'full_path' which has values like this: "en/home/logged-in/subscriber/school-top" to set up a route so that i can use this full_path field to load a ComatosePage from the db, instead of the standard id field, so that this url:
/comatose_admin/en/home/logged-in/subscriber/school-top
loads the comatose_admin controller's edit action, passing everything after comatose_admin/ through as a parameter, ie generates this for rails:
Parameters: {:controller => "comatose_admin", :action => "edit", :full_path => "en/home/logged-in/subscriber/school-top"}
The complication lies in the fact that the string is broken up with forward slashes, which is going to confuse routes, i think. Can i set up routes to take everything after "comatose_admin/" and put it into a single parameter?
You can use wildcards in your routes that will match forward slashes. Try something like this:
"/comatose_admin/*full_path"
Then params[:full_path] should contain the rest of the request path.
See Route Globbing

Resources