Overriding named routes provided by Rails 3 Engines - ruby-on-rails

I am working on a Ruby on Rails 3(.0) application that uses a Rails engine. However, in my local application, I want to override one of the routes provided by the Rails engine.
From the engine config/routes.rb:
match 'their_named_route' => 'controller#action', :as => 'the_route'
From my application config/routes.rb:
match 'my_named_route' => 'controller#action', :as => 'the_route'
However, when I inspect the routes, both seem to be active (and their route appears to "win", at least within the engine controllers)
$ rake routes
the_route /my_named_route(.:format) {:controller=>"controller", :action=>"action"}
the_route /their_named_route(.:format) {:controller=>"controller", :action=>"action"}
Is there a good way to force my local application's named route to take priority?

I got around this by moving my engine's routes from config/routes.rb to a class method in the engine class itself:
module MyEngine
class Engine < Rails::Engine
def self.routes
MyRailsApp::Application.routes.draw do
resources :products
end
end
end
end
and then in the base app's routes file:
MyRailsApp::Application.routes.draw do
# Routes for base app including the ones overriding MyEngine::Engine.
MyEngine::Engine.routes
end
I can then happily override any routes in the base app with those in the engine.
Note that the overriding routes need to be defined before the overridden routes since the earlier defined routes take precedence over later ones.

I'm afraid that there's no such easy way. The routes are defined in lib/action_dispatch/routing/mapper.rb:271, which calls add_route on the RouteSet (defined in rack-mount-0.6.14/lib/rack/mount/route_set.rb, and on line 71 the name is attached). There's no remove_route method, and the Engine's route is added last. You can add your route manually after the application is initialized with Rails.application.routes.draw instead of having it in routes.rb, or you can patch the Engine.

There is no way to override a route within an engine. Instead, you must define an overruling route. You can do this by calling prepend on the engine's router:
An::Engine.routes.prepend do
root :to => "somewhere#action"
end
If the engine's namespace is isolated, this will use the SomewhereController from inside the engine's namespace. If not, it will use the typical SomewhereController.
If you want to override a route to return a 404, the best way I can think of is to redirect to a 404 page:
match "/route_goes_here" => redirect("/404")

You need add initializer hook to config/application.rb, like this:
class Application < Rails::Application
config.encoding = "utf-8"
...
initializer :add_routing_paths do |app|
their_routes_path = app.routes_reloader.paths.select{|path| path =~ /DIR/}.first
app.routes_reloader.paths.delete(their_routes_path)
app.routes_reloader.paths.unshift(their_routes_path)
end
end
It's load roues.rb of you engine first and you can override their routes.

You can prepend routes as suggest by Ryan Bigg above. I found that in order overrule the named route helper with my custom route I need to call append instead of prepend, like so:
An::Engine.routes.append do
root :to => "somewhere#action"
end
Otherwise the app contains both routes and the named helper for engine's router is the last definition, and therefore the one that is applied.

Routing rules defined in routes.rb are applied from the top down until a match is found. I was able to override the route defined in the mounted engine simply by moving the higher-priority rule above the line where the engine is mounted. So,
get 'about', controller: 'static', action: 'about', as: 'about'
mount My::Engine => '/'
results in the app routing /about/ requests to (in this case) the static controller, whereas:
mount My::Engine => '/'
get 'about', controller: 'static', action: 'about', as: 'about'
results in the app routing /about/ requests to the route defined in the mounted engine.

To Replace the default routes path provided by gem or engine we can achive that in following way.
For me I wanted to replace 'spree_user' with 'account' etc from all routes
/user/spree_user/sign_in I did this in following way
Step 1
Create config/some_engine_route_override.rb
Copy the content of engine route file and update the routes as per need
Step 2
in application.rb use the folllowing hook
# Override Spree Core routes in order to translate products routes
initializer "delete_spree_core_routes", after: "add_routing_paths" do |app|
new_spree_auth_route_path = File.expand_path('../../config/some_engine_route_override.rb', __FILE__)
routes_paths = app.routes_reloader.paths
spree_devise_auth_route_path = routes_paths.select{ |path| path.include?("spree_auth_devise") }.first
if spree_devise_auth_route_path.present?
spree_core_route_path_index = routes_paths.index(spree_devise_auth_route_path)
routes_paths.delete_at(spree_core_route_path_index)
routes_paths.insert(spree_core_route_path_index, new_spree_auth_route_path)
end
end

Related

Route override doubles records

I'd like to override a default path of an Spree/Rails extension.
The extension spree_contact_us defines default route in it's config/routes.rb this way:
Spree::Core::Engine.routes.draw do
resources :contacts,
:controller => 'contact_us/contacts',
:only => [:new, :create]
match 'contact-us' => 'contact_us/contacts#new', :as => :contact_us
end
In the routes table there is just one record for route named contact-us:
contact_us /contact-us(.:format) spree/contact_us/contacts#new
If I pass following override in main application's config/routes.rb to routes.prepend method
Spree::Core::Engine.routes.prepend do
match 'napiste-nam' => 'contact_us/contacts#new', :as => :contact_us
end
rake routes displays routes to a new named path twice, when passed to routes.append even three times:
contact_us /napiste-nam(.:format) spree/contact_us/contacts#new
contact_us /napiste-nam(.:format) spree/contact_us/contacts#new
Can anybody explain this behaviour ?
The problem here is that you will be creating an ambiguous named route :contact_us which when referenced by contact_us_path will return the path for the last entry in routes because you are redefining it.
The duplication does seem strange but I have not looked at how spree handles these things.
In order to avoid this you could rename the secondary route such as
Spree::Core::Engine.routes.append do
match 'napiste-nam' => 'contact_us/contacts#new', :as => :contact_us_czech
end
This should create 2 routes in which you could use contact_us_path and contact_us_czech_path which will both lead to the same place. then create a method to determine which to use.
Or just add the new route directly into the spree routing tables as (PROBABLY NOT VALID DUE TO CALL TO routes_reloader in Spree Core.
match 'napiste-nam' => 'contact_us/contacts#new', :as => :contact_us
match 'contact_us' => 'contact_us/contacts#new', :as => :contact_us
Just remember that this means that contact_us_path with always reference the second route.
Edit
It seems Spree builds the default routes and then reloads them after initializing as is stated in the code
# We need to reload the routes here due to how Spree sets them up.
# The different facets of Spree (backend, frontend, etc.) append/prepend
# routes to Core *after* Core has been loaded.
#
# So we wait until after initialization is complete to do one final reload.
# This then makes the appended/prepended routes available to the application.
config.after_initialize do
Rails.application.routes_reloader.reload!
end
I believe this is causing the named route :contact_us to be routed to it's defined route meaning that you defined it as contact_us and then redefined it as napiste-nam and since a variable can have only 1 value it held on to the second one on reload!. Due to this fact I am not sure you can do this directly through Spree.
Using
Spree::Core::Engine.routes.draw
instead of
Spree::Core::Engine.routes.prepend
solved the routes duplication problem for me.

Rails view template path loading seems incorrect

I have a problem where Rails is searching an additional subdirectory based on the controller path. Is there a way to get rails to stop searching one extra subdirectory? I kind of like the directory structure that I have now. Here's the details:
Rails will return this error message. As you can see, it's going for v1 twice:
Template is missing
Missing template api/v1/v1/print
I have a controller in app/controllers/api/v1/v1_controller.rb and a view in app/views/api/v1/print.html.erb
The specific route in config/routes.rb is (semi-truncated):
namespace :api do
scope module: :v1 do
match "v1/print",
:to => "v1#print"
end
end
Based on the routes, it looks OK. Rake routes show this:
api_v1_print GET|POST /api/v1/print(.:format) api/v1/v1#print {:format=>"html"}
Why is it going one directory too deep?
The problem is that Rails assumes there's a subdirectory per each controller. The duplication is formed since you have v1 in the module and in the controller name. I wouldn't go against Rails' conventions. Instead I would change the name of the controller to API controller (or something similar) and put the templates under the directory called API.
In case you still want to do this, simply use render within your print action and specify the exact file you'd like to use (see here)
just remove the v1 from the match, like this:
namespace :api do
scope module: :v1 do
match "print",
:to => "v1#print"
end
end
EDIT
sorry, the problem is in your template folder.
app/views/api/v1/print.html.erb
app/views/(namespace)/(module)/(action) <- you have forgoten the controller
the right one would be:
app/views/api/v1/v1/print.html.erb

Splitting Routes File Into Multiple Files

I'm working w/ a Rails 3 application and I want to split up the routes into separate files depending on the subdomain. Right now I have this in my routes.rb file:
Skateparks::Application.routes.draw do
constraints(:subdomain => 'api') do
load 'routes/api.rb'
end
end
And In my routes/api.rb file I have:
resources :skateparks
This doesn't seem to work though because if I run rake routes I get
undefined method `resources' for main:Object
Also, if I try to navigate to http://0.0.0.0:3000/ I get:
Routing Error
No route matches "/"
In Rails 3.2, config.paths is now a hash, so #sunkencity's solution can be modified to:
# config/application.rb
config.paths["config/routes"] << File.join(Rails.root, "config/routes/fooroutes.rb")
Sunkencity's answer seems to be identical to the following link, but for completeness' sake: https://rails-bestpractices.com/posts/2011/05/04/split-route-namespaces-into-different-files/
Note that routes defined later will override routes defined earlier. However, if you use something like
config.paths.config.routes.concat(
Dir[Rails.root.join('config/routes/*.rb')])
you don't know in what order the files will be read. So use
config.paths.config.routes.concat(
Dir[Rails.root.join('config/routes/*.rb')].sort)
instead, so you at least know they will be in alphabetical order.
Add the route file to the app route loading path:
# config/application.rb
config.paths.config.routes << File.join(Rails.root, "config/routes/fooroutes.rb")
Wrap your other route file in a block like this.
#config/routes/fooroutes.rb
Rails.application.routes.draw do |map|
match 'FOO' => 'foo/bar'
end
Works for me in rails 3.0
We used this in our app:
config.paths['config/routes'] = Dir["config/routes/*.rb"]
If you try to access config.paths['config/routes'] normally, it returns the relative path to config/routes.rb, so by doing the above you're giving it relative paths to all of the files in your routes folder and removing the reference to config/routes.rb

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

Sinatra & Rails 3 routes issue

I have just setup Sinatra v1.1.0 inside my rails (v3.0.1) app. But I can't invoke any routes that are more than 1 level deep, meaning this works - http://localhost/customer/3,
but this one does not work - http://localhost/customer/3/edit and I get a "Routing Error"
Here's the Sinatra object
class CustomerApp < Sinatra::Base
# this works
get "/customer/:id" do
"Hello Customer"
end
# this does NOT work
get "/customer/:id/edit" do
"Hello Customer"
end
end
This is what I have in my rails routes.rb file -
match '/customer/(:string)' => CustomerApp
I am guessing I need some magic in the routes file? What could be the problem?
In your routes file, you can specify the mapping this way:
mount CustomerApp, :at => '/customer'
Now, inside your sinatra application, you can specify your routes without the /customer part.
Dont't forget to require your sinatra application somewhere (you can do it directly in the route file)
You need to add an additional route to match the different URL:
match '/customer/(:string)/edit' => CustomerApp

Resources