Rails I18n locale routing and RSpec testing - ruby-on-rails

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.

Related

Constraints not working when using block syntax in Rails

I tried to add constraints to a group of scoped routes like so:
constraints locale: 'de' do
scope 'magazin' do
get '', to: 'magazine#index', as: 'magazine'
# more routes
end
end
It doesn't make use of the restriction.
Whereas putting the restriction to a single route works as expected.
get '', to: 'magazine#index', as: 'magazine', constraints: { locale: 'de' }
I tried to use the constraints block in different positions, inside and outside the scope block. Without any change in the result.
The Rails Guide for Routing has this example which I pretty much copied:
namespace :admin do
constraints subdomain: 'admin' do
resources :photos
end
end
Any ideas what's wrong with the code?
Without having the whole routes.rb file it is hard to say why it doesn't work as expected.
Is it possible you have some kind of scope defined for locale??
Imagine sth like
scope '/:locale', locale: /de|en/ do
# lots of routes so you are not aware of the scope
constraints locale: "de" do
scope 'magazin' do
get '', to: 'magazine#index', as: 'magazine'
end
end
end
With this your are actually setting a constraint to locale to be either de or en. The constraint from the scope has precedence over the constraints block.
While this is not clear from the rails guide I found a merge request that proves my argumentation.

Route Constraints Not Working As Expected

I hoped route constraints would allow me to have admin.example.com/widgets/ and example.com/admin/widgets/ be effectively the same, with the URL helper admin_widgets_path pointing to the correct one based on the current subdomain. However, the route helper seems to only point to one or the other regardless of constraints.
Am I doing something wrong? Is this a bug? Is there a better way to solve this problem?
A rails app with an example of the problem has been published here, but the relevant details are below.
My config/routes.rb
class Subdomains
# any domain that starts with 'admin.'
def self.admin?
-> (request) { request.host =~ /^admin\./ }
end
# any other domain
def self.primary?
-> (request) { !admin?.(request) }
end
end
Rails.application.routes.draw do
constraints(Subdomains.primary?) do
namespace :admin do
resources :widgets
end
end
constraints(Subdomains.admin?) do
scope module: "admin", as: :admin do
resources :widgets
end
end
root to: "admin/widgets#index"
end
My app/views/admin/widgets/index.html.erb
<%= link_to "Widgets", admin_widgets_url %>
In this configuration, admin_widgets_url always returns /admin/widgets/ which isn't a valid route on admin.example.com so clicking the link results in a routing error on that subdomain. If the admin subdomain constraint block is put first in the routes, then the URL helper always returns /widgets/, breaking the link on a non-admin domain.
The routes otherwise work, but not sure how to get the URL helpers to point to the correct one.
Your expectations are wrong - Rails reads the entire routes definition as part of the setup phase. This is also when the route helpers are created. The constraint is not actually evaluated until later when the request is matched.
Rails splits this into distinct phases to allow forking.
Instead you may need to override the route helpers.

Routing Error with Post/Put requests (Passenger Headers)

I've run into a weird problem and after a bunch of research can't get any closer. I've got several forms that upload files via Carrierwave. When I upload the information, part of the route gets cut off (I think).
For example, I have a multi-part form submitting to:
https:/domain/programs/223/add_file as POST
but on submission I get the error
No route matches [POST] "/223/add_file"
even though what's in my address bar is the complete route. And if submit the complete route as a GET request it works fine. When I run rake routes the route shows up just fine.
Here is a subset of my route:
resources :programs do
match "add_file" => "programs#add_file"
If it matters, I'm running Rails 3.2.2 with Passenger on Apache. The problem only happens on this production server, never in development.
Any ideas? I'm stuck on this one as it effects multiple routes and I've tried defining a custom route just for that form with no luck.
Update:
When I remove multi-part => true or the file_field_tag from the form it fixes the problem. It's still an issue but seems to be less about routing than about the form with file uploads.
Create passenger_extension.rb in the lib folder with this code:
Passenger 3
module PhusionPassenger
module Utils
protected
NULL = "\0".freeze
def split_by_null_into_hash(data)
args = data.split(NULL, -1)
args.pop
headers_hash = Hash.new
args.each_slice(2).to_a.each do |pair|
headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first
end
return headers_hash
end
end
end
Passenger 5
module PhusionPassenger
module Utils
# Utility functions that can potentially be accelerated by native_support functions.
module NativeSupportUtils
extend self
NULL = "\0".freeze
class ProcessTimes < Struct.new(:utime, :stime)
end
def split_by_null_into_hash(data)
args = data.split(NULL, -1)
args.pop
headers_hash = Hash.new
args.each_slice(2).to_a.each do |pair|
headers_hash[pair.first] = pair.last unless headers_hash.keys.include? pair.first
end
return headers_hash
end
def process_times
times = Process.times
return ProcessTimes.new((times.utime * 1_000_000).to_i,
(times.stime * 1_000_000).to_i)
end
end
end # module Utils
end # module PhusionPassenger
And then in 'config/application.rb' do:
class Application < Rails::Application
...
config.autoload_paths += %W(#{config.root}/lib)
require 'passenger_extension'
end
And then restart a webserver.
NOTICE: I'm not sure that this doesn't break any other functionality so use it on your own risk and please let me know if you find any harm from this approach.
One issue here is you're not specifying whether the route is defined on the collection or a member. Which one of these is the correct route?
programs/:id/add_file
programs/add_file
You should construct your routes like this:
resources :programs do
post 'add_file', :on => :member
end
or
resources :programs do
member do
post 'add_file'
end
end
The above will take post requests at programs/:id/add_file and send them to ProgramsController.add_file with the params[:id] as the program id.
If you want this on the collection, you could do:
resources :programs do
post 'add_file', :on => :collection
end
or
resources :programs do
collection do
post 'add_file'
end
end
This would take post requests at programs/add_file and send them to ProgramsController.add_file, but no params[:id] would be set.
In general you should always specify whether routes are on the collection or member, and you should specify which verb a route should accept (ie use 'get' or 'post' etc. instead of 'match').
Try the above and see if that solves your problem, if not please let me know and I'll take another look.
I think you may need to add
:via => [:post]
to your route specification. It seems odd that it'd work on development and not on production, but as I understand rails routing, the matcher that you've added is only going to respond to get.
Try changing your match to
match "add_file" => "programs#add_file", :via => [:post]
Also, based on the answer just submitted by Andrew, you're probably better off using the member specifier to be explicit about the fact that the operation is happening on a particular Program with a particular id, and not the collection. It also should save some code in your add_file method which is probably working hard to get the id parameter from the url.

scope in routes not working with Cucumber/Capybara, default scope parameter not working in WEBrick

I have this route to separate users of different agencies:
scope "/:agency" do
resources :users
end
In ApplicationController I've added this behavior to default_url_options:
def default_url_options(options={})
{ :agency => params[:agency] }
end
Everything looks ok with the server and params[:agency] reports what's in the URL.
But while testing with Cucumber + Capybara
user_path(1)
becomes
http://www.example.com/1/users
instead of
http://www.example.com/theagency/users/1
Reading a bit of documentation I tried this:
scope "/:agency", :defaults => { :agency => 'test-agency'} do
resources :users
end
The tests appear to be working (the URL are built correctly) but in fact all URLs are built with test-agency as the first parameter, whatever the parameter is.
Any idea to have the tests work with this setup?

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 ;)

Resources