Rails4: How to wrap an engine into a routing scope - ruby-on-rails

Is there a way to "wrap" an existing rails 4 routing from a rails engine into a routing scope like described here: Rails routes with optional scope ":locale" ?
I want to use the spree gem (https://github.com/spree/spree) and insert the current locale as the leftmost part of the path, like /en/products/t-shirt

You should be able to do it the same way:
Sandbox::Application.routes.draw do
scope "(:locale)", locale: /en|fr/ do
mount Spree::Core::Engine, :at => '/'
end
end
You'll need to do more work to tie this locale in to Spree's locale, but that's a good place to start.

Related

Rails Route Scoping to value from database

I'm working with an internationalized rails app using the locale as part of the url. We only have certain languages translated in the database so it's not meaningful to allow users to access any locale in the url.
For example, we cover english and spanish, so our current routes.rb places pretty much everything inside of a scope "(:locale)", locale: /en|es/ do... block. My understanding is that this forces :locale, if it exists, to be one of "en" or "es", which works fine for now.
My concern is that different clients will want the system to support other languages (and only those languages). They will be responsible for creating the internationalization records which contain locale information. I'm thinking I'd like to automatically allow locale to be any that is already defined in the database, so I added a class method to Translation (the internationalization record)
def self.available_locales
Translation.uniq.pluck(:locale)
end
and changed routes.rb to scope "(:locale)", locale: Translation.available_locales do... however this just made every route go to /en/es/the_rest_of_the_url.
Is it possible to tell rails that routes must use a locale value from the resulting array from available_locales?
I'd use Advanced Constraints for that:
# routes.rb
scope "(:locale)", constraints: LocaleConstraint.new
# i.e. /lib/locale_constraint.rb
class LocaleConstraint
def initialize
# get available locales from DB or so
#locales = Locale.all
end
def matches?(request)
#locales.include?(request.params[:locale])
end
end
This way you could also write a backend to manage available locales, etc.
You could just turn it back into a regex, so it matches your first example:
Regexp.new([:en, :es].join("|")) #=> /en|es/
Or using your class method (ABMagil's edit):
Regexp.new(Translation.available_locales.join("|")) #=> /en|es/

Rails I18n locale routing and RSpec testing

My application was written in English and it was all good. Yesterday I starts to play with the Rails.I18n internationalization support. It is all good. When I browse http://localhost:3000/jp/discounts it is in Japanese, and 'http://localhost:3000/discounts' gives me the default English locale (when locale is not specified).
Here is my route.rb and as you can see, the admin namespace is not localized:
scope '(:locale)' do
resources :discounts do
resource :map, only: :show
collection do
get :featured_city
end
end
end
namespace :admin do
resources :users do
collection do
get :members
get :search
end
end
end
However my RSpec starts to fail.
Failure/Error: it { should route_to('admin/users#edit', id: '1') }
The recognized options <{"action"=>"edit", "controller"=>"users", "locale"=>"admin", "id"=>"1"}>
did not match <{"id"=>"1", "controller"=>"admin/users", "action"=>"edit"}>,
difference: <{"controller"=>"admin/users", "locale"=>"admin"}>.
<{"id"=>"1", "controller"=>"admin/users", "action"=>"edit"}> expected but was
<{"action"=>"edit", "controller"=>"users", "locale"=>"admin", "id"=>"1"}>
The tests related to admin all have this kind of problem. How can I resolve this? It works fine in development.
Here are other locale-related code:
application_controller.rb
def default_url_options
{ locale: I18n.locale }
end
config/initializers/i18n.rb
#encoding: utf-8
I18n.default_locale = :en
LANGUAGES = [
['English', 'en'],
["Japanese", 'jp']
]
When Rails attempts to match a given URL to a route, it starts at the top of the config/routes.rb file and stops at the first route that it considers to be a match. Since, in your original question, you had the scope block first, Rails thought your /admin URLs indicated a route with :locale => 'admin'.
You need Rails to match paths beginning in /admin to your admin namespace. By placing that first in your routes file, you cause Rails to "stop looking" once it finds that match.
This is a gross oversimplification, but I hope it's helpful.
Also check out the Rails routing guide if you haven't already.

Translating routes in Rails 3.1 without any gem

In a previous Rails 2.3 project I used the translate_routes gem to perform the translation of the routes. It worked great.
In my new Rails 3.1 project, again, I need route translation. Unfortunately, translate_routes doesn't work any longer and Raul its developer announced that he would no longer maintain the gem.
I tried to work with one of the project's fork that is supposed to be ok on Rails 3.1, but I couldn't do much of it.
Is there a way to build route translations without a gem ?
Here an example of a working route without translation.
constraints(:subdomain => 'admin') do
scope "(:locale)", :locale => /fr|de/ do
resources :country, :languages
match '/' => 'home#admin', :as => :admin_home
end
end
As you can see, I also want to have a default route without locale that is used for my default locale : en.
Has anyone done that before?
Thanks
Probably a little bit late for you, but it may be helpful for others, try a fork of translate_routes:
https://github.com/francesc/rails-translate-routes
Saw your post earlier, but found out another sollution later.
I wanted to translate Rails routes and their default resource actions, but I didn't like they way rails-translate-routes added _nl to my default path-names.
I ended up doing this (also works in rails 4.0), which should be a good sollution when you are presenting your app in only 1 or 2 languages.
# config/routes.rb
Testapp::Application.routes.draw do
# This scope changes resources methods names
scope(path_names: { new: I18n.t('routename.new'), edit: I18n.t('routename.edit') }) do
# devise works fine with this technique
devise_for :users, path: I18n.t('routename.userspath')
# resource path names can be translated like this
resources :cars, path: I18n.t('routename.carspath')
# url prefixes can be translated to
get "#{I18n.t('routename.carspath')}/export", to: 'cars#export'
end
end
And
# config/locales/nl.yml
nl:
routename:
## methods
new: 'nieuw'
edit: 'aanpassen'
## resources, etc.
userpath: 'gebruikers'
carspath: 'voertuigen'
Result in:
/voertuigen
/voertuigen/nieuw
/voertuigen/aanpassen
/voertuigen/export
update and destroy are not neccesairy since they link into the root as post actions. Save your work ;)

Named routes in mounted rails engine

I'm making a small rails engine which I mount like this:
mount BasicApp::Engine => "/app"
Using this answer I have verified that all the routes in the engine are as the should be:
However - when I (inside the engine) link to a named route (defined inside the engine) I get this error
undefined local variable or method `new_post_path' for #<#<Class:0x000000065e0c08>:0x000000065d71d0>
Running "rake route" clearly verifies that "new_post" should be a named path, so I have no idea why Rails (3.1.0) can't figure it out. Any help is welcome
my config/route.rb (for the engine) look like this
BasicApp::Engine.routes.draw do
resources :posts, :path => '' do
resources :post_comments
resources :post_images
end
end
I should add that it is and isolated engine. However paths like main_app.root_path works fine - while root_path does not
The right way
I believe the best solution is to call new_post_path on the Engine's routes proxy, which is available as a helper method. In your case, the helper method will default to basic_app_engine, so you can call basic_app_engine.new_post_path in your views or helpers.
If you want, you can set the name in one of two ways.
# in engine/lib/basic_app/engine.rb:
module BasicApp
class Engine < ::Rails::Engine
engine_name 'basic'
end
end
or
# in app/config/routes.rb:
mount BasicApp::Engine => '/app', :as => 'basic'
In either case, you could then call basic.new_posts_path in your views or helpers.
Another way
Another option is to not use a mounted engine and instead have the engine add the routes directly to the app. Thoughtbot's HighVoltage does this. I don't love this solution because it is likely to cause namespace conflicts when you add many engines, but it does work.
# in engine/config/routes.rb
Rails.application.routes.draw do
resources :posts, :path => '' do
resources :post_comments
resources :post_images
end
end
# in app/config/routes.rb:
# (no mention of the engine)
On Rails 4 the engine_name directive did not work for me.
To access a named route defined in engine's routes from engine's own view or controller, I ended up using the verbose
BasicApp::Engine.routes.url_helpers.new_post_path
I recommend defining a simple helper method to make this more usable
# in /helpers/basic_app/application_helper.rb
module BasicApp::ApplicationHelper
def basic_app_engine
##basic_app_engine_url_helpers ||= BasicApp::Engine.routes.url_helpers
end
end
With this in place you can now use
basic_app_engine.new_post_path
In case you need to access your main application helper from the engine you can just use main_app:
main_app.root_path
use the below in you app to access the engine routes
MyApp::Engine.routes.url_helpers.new_post_path

Remove Routes Specified in a Gem?

Is there a way to remove routes specified in a gem in Rails 3? The exception logger gem specifies routes which I don't want. I need to specify constraints on the routes like so:
scope :constraints => {:subdomain => 'secure', :protocol => 'https'} do
collection do
post :query
post :destroy_all
get :feed
end
end
Based on the Rails Engine docs, I thought I could create a monkey patch and add a routes file with no routes specified to the paths["config/routes"].paths Array but the file doesn't get added to ExceptionLogger::Engine.paths["config/routes"].paths
File: config/initializers/exception_logger_hacks.rb
ExceptionLogger::Engine.paths["config/routes"].paths.unshift(File.expand_path(File.join(File.dirname(__FILE__), "exception_logger_routes.rb")))
Am I way off base here? Maybe there is a better way of doing this?
It is possible to prevent Rails from loading the routes of a specific gem, this way none of the gem routes are added, so you will have to add the ones you want manually:
Add an initializer in application.rb like this:
class Application < Rails::Application
...
initializer "myinitializer", :after => "add_routing_paths" do |app|
app.routes_reloader.paths.delete_if{ |path| path.include?("NAME_OF_GEM_GOES_HERE") }
end
Here's one way that's worked for me.
It doesn't "remove" routes but lets you take control of where they match. You probably want every route requested to match something, even if it is a catch all 404 at the bottom.
Your application routes (MyApp/config/routes.rb) will be loaded first (unless you've modified the default load process). And routes matched first will take precedence.
So you could redefine the routes you want to block explicitely, or block them with a catch all route at the bottom of YourApp/config/routes.rb file.
Named routes, unfortunately, seem to follow ruby's "last definition wins" rule. So if the routes are named and your app or the engine uses those names, you need to define the routes both first (so yours match first), and last (so named routes point as you intended, not as the engine defines.)
To redefine the engine's routes after the engine adds them, create a file called something like
# config/named_routes_overrides.rb
Rails.application.routes.draw do
# put your named routes here, which you also included in config/routes.rb
end
# config/application.rb
class Application < Rails::Application
# ...
initializer 'add named route overrides' do |app|
app.routes_reloader.paths << File.expand_path('../named_routes_overrides.rb',__FILE__)
# this seems to cause these extra routes to be loaded last, so they will define named routes last.
end
end
You can test this routing sandwich in the console:
> Rails.application.routes.url_helpers.my_named_route_path
=> # before your fix, this will be the engine's named route, since it was defined last.
> Rails.application.routes.recognize_path("/route/you/want/to/stop/gem/from/controlling")
=> # before your fix, this will route to the controller and method you defined, rather than what the engine defined, because your route comes first.
After your fix, these calls should match each other.
(I posted this originally on the refinery gem google group here: https://groups.google.com/forum/?fromgroups#!topic/refinery-cms/N5F-Insm9co)

Resources