Add default params to named route helper in rails - ruby-on-rails

I have this route
get "/articles/:id" => "articles#show", as: :article
Which generates the path helper article_path(article).
Is it possible to add some default parameters to this path helper, like /articles/123?token=123456? For example I use the article_path helper a dozens of times and it would be tedious to always do something like
<%= article_path(article, token: article.token) %>
I've played around with the defaults of a route, but this does not affect the URL generation, only the params object when I'm on this route
get "/articles/:id" => "articles#show", as: :article, defaults: { token: "my token" }
The only thing I can think of, is to overwrite / monkey patch the helper, but as I need this logic on other helpers too, I was wondering if there is a build in way to do this.

This logic does not really belong in the routing layer and query string parameters are for the most part completely transparent to the router.
Instead you can just create your own helper method:
def article_path(article_or_id, **opts)
if opts[:token].nil? && article_or_id.respond_to?(:token)
super(article_or_id, opts.merge(token: article.token))
else
super
end
end

Related

Rails Routes: How to match Rails routes based on a pattern

I have some routes like:
get 'route1' => 'controller#route1', as: 'route1'
get 'route2' => 'controller#route2', as: 'route2'
get 'route3' => 'controller#route3', as: 'route3'
How can I match more routes automatically with this pattern, e.g. 4, 5...
I am not sure how you can handle as part of route. But you can write this code in another way. You can create a route that handle all such routes at the end of your primary route as below:
get '/:route' => 'controller#route_for_all_views'
In your controller you should have this route_for_all_views action, which can handle all pages.
class SomeController < ApplicationController
def route_for_all_views
# handle your views and code with params[:route] here
end
end
I think you can do something like this:
get "/:action", to: "controller", constraints: {action: /route\d+/}
Please see dynamic segments for routes.
(also note that this would raise an exception if your controller doesn't have the method so you might need to use something like method_missing)
This may be a messy solution, but you could also do something like this, which will give you the *_path and *_url url helpers, that you get when you use the :as option.
%w{ route1 route2 route3 route4 route5 }.each do |route|
get route, to: "controller##{route}", as: route
end

route is redirecting to wrong path

This is what my routes currently look like:
which gives
On my homepage I have a create vacancy button
<%= link_to "plaats", new_employer_vacancy_path(:employer_id)%>
Which should be linked to the line from the first image
get '/employers/:employer_id/vacancies/new', to: 'vacancies#new', as: 'new_employer_vacancy'
In the vacancies_controller#new - create I have:
def new
#vacancy = Vacancy.new
#employervacancy = Employervacancy.new
end
def create
#vacancy = Vacancy.create(vacancy_params)
createEmployervacancy
redirect_to employer_vacancy_path(current_employer, #vacancy)
end
def createEmployervacancy
#employer = current_employer
Employervacancy.create(vacancy_id: #vacancy.id, employer_id: #employer.id)
end
But whenever I click the button I get redirected to some other method in my vacancies_controller that is totally irrelevant.
How is this even possible? Don't I clearly define that when that path is clicked he should go to vacancies#new? and not to vacancies#show_specific_employer_vacancies?
EDIT
After following the answers I am indeed being linked to the correct route.
First, it gave me this error.
After trying to pass the current_employer.id instead of #employer like suggested I got following error:
For your routes, you'd better to change into nested route for easily maintaining routes.
Remove these codes:
get '/employers/:employer_id/vacancies/:id', to:"vacancies#show_specific_employer_vacancies", as: "employer_vacancy"
get '/employers/:employer_id/vacancies/edit/:id' ...
get '/employers/:employer_id/vacancies/index' ...
get '/employers/:employer_id/vacancies/new' ...
path '/employers/:employer_id/vacancies/:id' ...
change into:
resources :employers do
resources :vacancies
end
Try to use basic routes here because you use standard simple form url. For example:
<%= simple_form_for(#employee, #vacancy) %>
The simple_form_for will generate url well if you use nested routes above.
Finally, in your link you have to add #employer_id
<%= link_to "plaats", new_employer_vacancy_path(:employer_id => #employer_id)%>
I hope this help you
Your router cannot tell the difference between your employer_vacancy and new_emplyer_vacancy routes because the :id parameter accepts anything. Because of this, when you point your browser to "/employers/5/vacancies/new", the route is taking your employer_vacancy route and assigning {:employer_id => 5, :id => "new"} instead of going to your new_employer_vacancy route (because routes are first-come-first-serve).
To correct this, add a constraint to your first route to ensure that only numbers (and not the string "new") is accepted into the employer_vacancy route:
get '/employers/:employer_id/vacancies/:id',
to: 'vacancies#show_specific_emplyer_vacancies',
as: 'employer_vacancy',
constraints: { id: /\d+/ } # <- This line
As Wes Foster said rails router is trying to find a first match.
It means that given a path /employers/999/vacancies/new your router looks through the routes and when it sees get '/employers/:employer_id/vacancies/:id he thinks that this route matches. So :employer_id is 999 and :id is new.
I'd suggest to put the route with :id at the end of employers routes:
...
get '/employers/:employer_id/vacancies/new'
...
get '/employers/:employer_id/vacancies/:id'
Btw this is better than adding a constraint because:
It is easier
It doesn't pollute routes file
Later you may want to change ids to be hashed or alphabetic and then you'd have to change the constraint

understanding passing rails parameters between controllers

I am just starting to wrap my head around parameters in rails. I am currently working on a project that isn't accessible to the public, so keeping params secure isn't exactly a priority in this case.
I have a link_to to a different controller action that requires an object id to fulfil the controller action.
=link_to "Barcode", print_barcode_label_admin_items_path(:item_to_print => { :article_id => article.id })
Then in the relevant controller
def print_barcode_label
if params[:item_to_print][:article_id].present?
return if force_format :pdf
..........
private
def params_document
params.require(:document).permit!
end
As I was writing the code for this controller I am certain the parameters were being passed (I am using the better-errors gem to debug along the way so I could see them being passed in the request parameters hash). But now, not sure what I have done, but i get the error
undefined method `[]' for nil:NilClass
failing at line two in my above controller action. I am sure there is something really basic I am missing. What is it? Is there a more favourable way of doing this?
Update
So I started playing with other possible solutions, and one is naming a route that specifically carries the parameter
get 'print_barcode_label/:article_id', to: 'documents#print_barcode_label', as: 'print_barcode_label'
This seems a more robust and sensible approach. Howeever, despite passing the variable in the link, like this
=link_to "Barcode", print_barcode_label_admin_items_path(article.id)
Gives a no route matches error
No route matches {:action=>"print_barcode_label", :controller=>"admin/documents"} missing required keys: [:article_id]
It is hard to answer this question without seeing more code with some context. But if you want to do rails way you should propably create custom action on document resource.
In your routes.rb:
namespace :admin do
resources :documents
get :print_barcode_label, :on => :member
end
end
And then you can create link to this action:
= link_to 'Barcode', print_barcode_label_admin_document_path(article)

Custom path generator

I thought I knew how to override a path helper, but I'm not getting my expected behavior.
I tried adding something like this to my ApplicationHelper:
def post_path(post)
"/posts/#{post.id}/#{post.url}"
end
But for some reason, in one of my controllers when I try to use post_path(#post) it just returns the full url, something like /posts/4faddb375d9a1e045e000068/asdf (which is the current url in the browser) rather than /posts/4faddb375d9a1e045e000068/post-title-here.
In my routes file:
get '/posts/:id/:slug' => 'posts#show', :as => 'post'
The strange thing is if I use post_path(#post, #post.url), it works correctly. And if in a view I use post_path(#post) it works correctly. (#post.url returns the url friendly title)
In case you can't tell, I'm trying to eventually get the behavior similar to stackoverflow's where the url contains the id and a slug at the end and if the slug doesn't match the model with the given id, it'll redirect to the correct url.
What I'd try would be to put the whole def post_path in the application_controller.rb and make it a helper with helper_method :post_path. You'll get the best of both worlds.

Accessing URL helpers in routes.rb

I would like to redirect a path in routes using the following lines:
get 'privacy_policy', :controller => :pages, :as => 'privacy_policy'
get 'privacypolicy.php' => redirect(privacy_policy_url)
So that /privacypolicy.php gets redirected to the correct page defined right above it.
However, it's throwing the following error:
undefined local variable or method `privacy_policy_url'
So I'm guessing one cannot use URL helpers in routes.rb. Is there a way to use URL helpers in the route file, and is it advisable to do so?
I know I'm a little late here, but this question is one of the top hits when googling "use url_helpers in routes.rb", and I initially found it when I had stumbled upon this problem, so I'd like to share my solution.
As #martinjlowm mentioned in his answer, one cannot use URL helpers when drawing new routes. However, there is one way to define a redirecting route rule using URL helpers. The thing is, ActionDispatch::Routing::Redirection#redirect can take a block (or a #call-able), which is later (when the user hits the route) invoked with two parameters, params and request, to return a new route, a string. And because the routes are properly drawn at that moment, it is completely valid to call URL helpers inside the block!
get 'privacypolicy.php', to: redirect { |_params, _request|
Rails.application.routes.url_helpers.privacy_policy_path
}
Furthermore, we can employ Ruby metaprogramming facilities to add some sugar:
class UrlHelpersRedirector
def self.method_missing(method, *args, **kwargs) # rubocop:disable Style/MethodMissing
new(method, args, kwargs)
end
def initialize(url_helper, args, kwargs)
#url_helper = url_helper
#args = args
#kwargs = kwargs
end
def call(_params, _request)
url_helpers.public_send(#url_helper, *#args, **#kwargs)
end
private
def url_helpers
Rails.application.routes.url_helpers
end
end
# ...
Rails.application.routes.draw do
get 'privacypolicy.php', to: redirect(UrlHelperRedirector.privacy_policy_path)
end
URL Helpers are created from the routes. Therefore they won't be usable when drawing new routes.
You will have to use gayavat's approach.
-- or --
Redirect using the exact URL like http://guides.rubyonrails.org/routing.html does.
edit:
If it's more than just the one '...php' route, you might want to consider making a redirect controller. Take a look here, how to se it up: http://palexander.posterous.com/provide-valid-301-redirects-using-rails-route
Inside your routes file, you should add this at the bottom, so it doesn't interfere with other routes:
get '/:url' => 'redirect#index'
Something like:
get 'privacypolicy.php' => "privacy_policy#show"

Resources