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 ;)
Related
I'm trying to version my API like Stripe has. Below is given the latest API version is 2.
/api/users returns a 301 to /api/v2/users
/api/v1/users returns a 200 of users index at version 1
/api/v3/users returns a 301 to /api/v2/users
/api/asdf/users returns a 301 to /api/v2/users
So that basically anything that doesn't specify the version links to the latest unless the specified version exists then redirect to it.
This is what I have so far:
scope 'api', :format => :json do
scope 'v:api_version', :api_version => /[12]/ do
resources :users
end
match '/*path', :to => redirect { |params| "/api/v2/#{params[:path]}" }
end
The original form of this answer is wildly different, and can be found here. Just proof that there's more than one way to skin a cat.
I've updated the answer since to use namespaces and to use 301 redirects -- rather than the default of 302. Thanks to pixeltrix and Bo Jeanes for the prompting on those things.
You might want to wear a really strong helmet because this is going to blow your mind.
The Rails 3 routing API is super wicked. To write the routes for your API, as per your requirements above, you need just this:
namespace :api do
namespace :v1 do
resources :users
end
namespace :v2 do
resources :users
end
match 'v:api/*path', :to => redirect("/api/v2/%{path}")
match '*path', :to => redirect("/api/v2/%{path}")
end
If your mind is still intact after this point, let me explain.
First, we call namespace which is super handy for when you want a bunch of routes scoped to a specific path and module that are similarly named. In this case, we want all routes inside the block for our namespace to be scoped to controllers within the Api module and all requests to paths inside this route will be prefixed with api. Requests such as /api/v2/users, ya know?
Inside the namespace, we define two more namespaces (woah!). This time we're defining the "v1" namespace, so all routes for the controllers here will be inside the V1 module inside the Api module: Api::V1. By defining resources :users inside this route, the controller will be located at Api::V1::UsersController. This is version 1, and you get there by making requests like /api/v1/users.
Version 2 is only a tiny bit different. Instead of the controller serving it being at Api::V1::UsersController, it's now at Api::V2::UsersController. You get there by making requests like /api/v2/users.
Next, a match is used. This will match all API routes that go to things like /api/v3/users.
This is the part I had to look up. The :to => option allows you to specify that a specific request should be redirected somewhere else -- I knew that much -- but I didn't know how to get it to redirect to somewhere else and pass in a piece of the original request along with it.
To do this, we call the redirect method and pass it a string with a special-interpolated %{path} parameter. When a request comes in that matches this final match, it will interpolate the path parameter into the location of %{path} inside the string and redirect the user to where they need to go.
Finally, we use another match to route all remaining paths prefixed with /api and redirect them to /api/v2/%{path}. This means requests like /api/users will go to /api/v2/users.
I couldn't figure out how to get /api/asdf/users to match, because how do you determine if that is supposed to be a request to /api/<resource>/<identifier> or /api/<version>/<resource>?
A couple of things to add:
Your redirect match isn't going to work for certain routes - the *api param is greedy and will swallow up everything, e.g. /api/asdf/users/1 will redirect to /api/v2/1. You'd be better off using a regular param like :api. Admittedly it won't match cases like /api/asdf/asdf/users/1 but if you have nested resources in your api it's a better solution.
Ryan WHY U NO LIKE namespace? :-), e.g:
current_api_routes = lambda do
resources :users
end
namespace :api do
scope :module => :v2, ¤t_api_routes
namespace :v2, ¤t_api_routes
namespace :v1, ¤t_api_routes
match ":api/*path", :to => redirect("/api/v2/%{path}")
end
Which has the added benefit of versioned and generic named routes. One additional note - the convention when using :module is to use underscore notation, e.g: api/v1 not 'Api::V1'. At one point the latter didn't work but I believe it was fixed in Rails 3.1.
Also, when you release v3 of your API the routes would be updated like this:
current_api_routes = lambda do
resources :users
end
namespace :api do
scope :module => :v3, ¤t_api_routes
namespace :v3, ¤t_api_routes
namespace :v2, ¤t_api_routes
namespace :v1, ¤t_api_routes
match ":api/*path", :to => redirect("/api/v3/%{path}")
end
Of course it's likely that your API has different routes between versions in which case you can do this:
current_api_routes = lambda do
# Define latest API
end
namespace :api do
scope :module => :v3, ¤t_api_routes
namespace :v3, ¤t_api_routes
namespace :v2 do
# Define API v2 routes
end
namespace :v1 do
# Define API v1 routes
end
match ":api/*path", :to => redirect("/api/v3/%{path}")
end
If at all possible, I would suggest rethinking your urls so that the version isn't in the url, but is put into the accepts header. This stack overflow answer goes into it well:
Best practices for API versioning?
and this link shows exactly how to do that with rails routing:
http://freelancing-gods.com/posts/versioning_your_ap_is
I'm not a big fan of versioning by routes. We built VersionCake to support an easier form of API versioning.
By including the API version number in the filename of each of our respective views (jbuilder, RABL, etc), we keep the versioning unobtrusive and allow for easy degradation to support backwards compatibility (e.g. if v5 of the view doesn't exist, we render v4 of the view).
I'm not sure why you want to redirect to a specific version if a version isn't explicitly requested. Seems like you simply want to define a default version that gets served up if no version is explicitly requested. I also agree with David Bock that keeping versions out of the URL structure is a cleaner way to support versioning.
Shameless plug: Versionist supports these use cases (and more).
https://github.com/bploetz/versionist
Implemented this today and found what I believe to be the 'right way' on RailsCasts - REST API Versioning. So simple. So maintainable. So effective.
Add lib/api_constraints.rb (don't even have to change vnd.example.)
class ApiConstraints
def initialize(options)
#version = options[:version]
#default = options[:default]
end
def matches?(req)
#default || req.headers['Accept'].include?("application/vnd.example.v#{#version}")
end
end
Setup config/routes.rb like so
require 'api_constraints'
Rails.application.routes.draw do
# Squads API
namespace :api do
# ApiConstaints is a lib file to allow default API versions,
# this will help prevent having to change link names from /api/v1/squads to /api/squads, better maintainability
scope module: :v1, constraints: ApiConstraints.new(version:1, default: true) do
resources :squads do
# my stuff was here
end
end
end
resources :squads
root to: 'site#index'
Edit your controller (ie /controllers/api/v1/squads_controller.rb)
module Api
module V1
class SquadsController < BaseController
# my stuff was here
end
end
end
Then you can change all links in your app from /api/v1/squads to /api/squads and you can EASILY implement new api versions without even having to change links
Ryan Bigg answer worked for me.
If you also want to keep query parameters through the redirect, you can do it like this:
match "*path", to: redirect{ |params, request| "/api/v2/#{params[:path]}?#{request.query_string}" }
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.
Is it possible to split Rails 3.X routes.rb file?
We have so many resources it is difficult to find them. I would like to split at least APP and REST API routes.
Thanks!
You can do that:
routes.rb
require 'application_routes'
require 'rest_api_routes'
lib/application_routes.rb
YourApplication::Application.routes.draw do
# Application related routes
end
lib/rest_api_routes.rb
YourApplication::Application.routes.draw do
# REST API related routes
end
UPDATE: (This method has since been removed from Rails)
Rails edge just got a great addition, multiple route files:
# config/routes.rb
draw :admin
# config/routes/admin.rb
namespace :admin do
resources :posts
end
This will come handy for breaking down complex route files in large apps.
In Rails3, you can set the configs in config/application.rb
config.paths.config.routes.concat Dir[Rails.root.join("config/routes/*.rb")]
Rails 3.2.11
config.paths["config/routes"].concat Dir[Rails.root.join("config/routes/*.rb")]
Is it possible to split Rails 3.X routes.rb file?
We have so many resources it is difficult to find them. I would like to split at least APP and REST API routes.
Thanks!
You can do that:
routes.rb
require 'application_routes'
require 'rest_api_routes'
lib/application_routes.rb
YourApplication::Application.routes.draw do
# Application related routes
end
lib/rest_api_routes.rb
YourApplication::Application.routes.draw do
# REST API related routes
end
UPDATE: (This method has since been removed from Rails)
Rails edge just got a great addition, multiple route files:
# config/routes.rb
draw :admin
# config/routes/admin.rb
namespace :admin do
resources :posts
end
This will come handy for breaking down complex route files in large apps.
In Rails3, you can set the configs in config/application.rb
config.paths.config.routes.concat Dir[Rails.root.join("config/routes/*.rb")]
Rails 3.2.11
config.paths["config/routes"].concat Dir[Rails.root.join("config/routes/*.rb")]
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