Accessing URL helpers in routes.rb - ruby-on-rails

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"

Related

Rails same named route but with parameter raises an error

As of Rails 4.2, I cannot do the following:
get 'profile', to: 'profile#index', as: 'profile'
get 'profile/:slug', to: 'profile#show', as: 'profile'
because it will raise error saying route is already defined. Why is that? Obviously profile_path and profile_path(User.last.slug) are not the same, and there should be no difficulty differentiating the two even if they happen to share the same base name (You check if a param is passed).
Thoughts?
In rails, the helper names for different routes should be different. And hence, as you rightly understood, you will receive an error if you use the same helper name (ie as: 'profile' in your case) for two different routes.
This restriction in Rails helps maintain sanity in your routes.rb file as well as in your application. For instance consider two methods for a controller:
class XyzController < ApplicationController
def method_a(param1)
end
def method_b(param1)
end
end
In your routes file if there was no restriction of keeping helper names different, you could have used :
get 'xyz/method_a', to: 'profile#method_a', as: 'profile_method'
get 'xyz/method_b', to: 'profile#method_b', as: 'profile_method'
Correspondingly in your view file:
link_to 'link_1', profile_method_path('param1') #intended to route for method_a
link_to 'link_2', profile_method_path('param2') #intended to route for method_b
As obvious, in the view file, not only is it difficult to make out which route is intended for which method, its also not possible to route to any other controller method using the helper 'profile_method' except the method that is first to use this helper in your routes.rb file (as routes are read sequentially).
Hope this helps :)
For both routes you specified as: 'profile' and that's your problem here. Besides that, use pluralized route names for #index action, e.g:
get 'profiles', to: 'profile#index'

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.

rails controller action routing

I'm looking to "automatically" generate routes based on actions defined in a controller. resources routing (as far as I know) only automatically generates routes & helpers for http verbs. In this instance I'm serving mostly static pages using rails and have no need for http verbs in my controller.
Specifically:
In a controller I have defined actions which refer to those mostly static pages.
def action
end
In the routes file I have a bunch of
match "/url" => 'controller#action'
I'd like all those matched routes to be generated automatically based on the actions in the controller. Something CONCEPTUALLY along the lines of:
for actions in controller.erb do |action|
'match "/action" => "controller#action"
end
Is this possible? Would I write the code in the routes file directly?
I also have some nested actions to consider... a controller action may be:
def action
def nested_action
end
end
I'd appreciate any thoughts on this matter. Thanks.
What's wrong with the normal /:controller/:action idea?
That won't deal with nested actions, but... I'm having a difficult time understanding why you'd ever want that.
You can do something like this:
controller :controller_name do
get "path/action" => :method_name, :as => :path_action
post "path/save" => :method_name, :as => :path_save
end
That is, you can group different routes within a controller using the method above.

Routes with Dash `-` Instead of Underscore `_` in Ruby on Rails

I want my urls to use dash - instead of underscore _ as word separators. For example controller/my-action instead of controller/my_action.
I'm surprised about two things:
Google et al. continue to distinguish them.
That Ruby on Rails doesn't have a simple, global configuration parameter to map - to _ in the routing. Or does it?
The best solution I've is to use :as or a named route.
My idea is to modify the Rails routing to check for that global config and change - to _ before dispatching to a controller action.
Is there a better way?
With Rails 3 and later you can do like this:
resources :user_bundles, :path => '/user-bundles'
Another option is to modify Rails, via an initializer.
I don't recommend this though, since it may break in future versions (edit: doesn't work in Rails 5).
Using :path as shown above is better.
# Using private APIs is not recommended and may break in future Rails versions.
# https://github.com/rails/rails/blob/4-1-stable/actionpack/lib/action_dispatch/routing/mapper.rb#L1012
#
# config/initializers/adjust-route-paths.rb
module ActionDispatch
module Routing
class Mapper
module Resources
class Resource
def path
#path.dasherize
end
end
end
end
end
end
You can overload controller and action names to use dashes:
# config/routes.rb
resources :my_resources, path: 'my-resources' do
collection do
get 'my-method', to: :my_method
end
end
You can test in console:
rails routes -g my_resources
my_method_my_resources GET /my-resources/my-method(.:format) my_resources#my_method
You can use named routes. It will allow using '-' as word seperators. In routes.rb,
map.name_of_route 'a-b-c', :controller => 'my_controller', :action => "my_action"
Now urls like http://my_application/a-b-c would go to specified controller and action.
Also, for creating dynamic urls
map.name_of_route 'id1-:id2-:id3', :controller => 'my_controller', :action => "my_action"
in this case 'id1, id2 & id2 would be passed as http params to the action
In you actions and views,
name_of_route_url(:id1=>val1, :id2=>val2, :id3=>val3)
would evaluate to url 'http://my_application/val1-val2-val3'.
if you use underscores in a controller and view file then just use dashes in your routes file, and it will work..
get 'blog/example-text' this is my route for this controller
def example_text
end <-- this is my controller
and example_text.html.erb is the file
and this is the actual link site.com/blog/example-text
i figured this is works for me, and it's more effective than underscores SEO wise

Is it a bad idea to reload routes dynamically in Rails?

I have an application I'm writing where I'm allowing the administrators to add aliases for pages, categories, etc, and I would like to use a different controller/action depending on the alias (without redirecting, and I've found that render doesn't actually call the method. I just renders the template). I have tried a catch all route, but I'm not crazy about causing and catching a DoubleRender exception that gets thrown everytime.
The solution for this I've come up with is dynamically generated routes when the server is started, and using callbacks from the Alias model to reload routes when an alias is created/updated/destroyed.
Here is the code from my routes.rb:
Alias.find(:all).each do |alias_to_add|
map.connect alias_to_add.name,
:controller => alias_to_add.page_type.controller,
:action => alias_to_add.page_type.action,
:navigation_node_id => alias_to_add.navigation_node.id
end
I am using callbacks in my Alias model as follows:
after_save :rebuild_routes
after_destroy :rebuild_routes
def rebuild_routes
ActionController::Routing::Routes.reload!
end
Is this against Rails best practices? Is there a better solution?
Ben,
I find the method you're already using to be the best. Using Rails 3, you'd have to change the code a bit, to:
MyNewApplication::Application.reload_routes!
That's all.
Quick Solution
Have a catch-all route at the bottom of routes.rb. Implement any alias lookup logic you want in the action that route routes you to.
In my implementation, I have a table which maps defined URLs to a controller, action, and parameter hash. I just pluck them out of the database, then call the appropriate action and then try to render the default template for the action. If the action already rendered something, that throws a DoubleRenderError, which I catch and ignore.
You can extend this technique to be as complicated as you want, although as it gets more complicated it makes more sense to implement it by tweaking either your routes or the Rails default routing logic rather than by essentially reimplementing all the routing logic yourself.
If you don't find an alias, you can throw the 404 or 500 error as you deem appropriate.
Stuff to keep in mind:
Caching: Not knowing your URLs a priori can make page caching an absolute bear. Remember, it caches based on the URI supplied, NOT on the url_for (:action_you_actually_executed). This means that if you alias
/foo_action/bar_method
to
/some-wonderful-alias
you'll get some-wonderful-alias.html living in your cache directory. And when you try to sweep foo's bar, you won't sweep that file unless you specify it explicitly.
Fault Tolerance: Check to make sure someone doesn't accidentally alias over an existing route. You can do this trivially by forcing all aliases into a "directory" which is known to not otherwise be routable (in which case, the alias being textually unique is enough to make sure they never collide), but that isn't a maximally desirable solution for a few of the applications I can think of of this.
First, as other have suggested, create a catch-all route at the bottom of routes.rb:
map.connect ':name', :controller => 'aliases', :action => 'show'
Then, in AliasesController, you can use render_component to render the aliased action:
class AliasesController < ApplicationController
def show
if alias = Alias.find_by_name(params[:name])
render_component(:controller => alias.page_type.controller,
:action => alias.page_type.action,
:navigation_node_id => alias.navigation_node.id)
else
render :file => "#{RAILS_ROOT}/public/404.html", :status => :not_found
end
end
end
I'm not sure I fully understand the question, but you could use method_missing in your controllers and then lookup the alias, maybe like this:
class MyController
def method_missing(sym, *args)
aliased = Alias.find_by_action_name(sym)
# sanity check here in case no alias
self.send( aliased.real_action_name )
# sanity check here in case the real action calls a different render explicitly
render :action => aliased.real_action_name
end
def normal_action
#thing = Things.find(params[:id])
end
end
If you wanted to optimize that, you could put a define_method in the method_missing, so it would only be 'missing' on the first invocation, and would be a normal method from then on.

Resources