We're migrating a site from a proprietary framework to Ruby on Rails (v2.3). The current framework sometimes puts /base/ at the start of the URL for no discernible reason, and I'd like the existing URL to work, even though we won't give it out any more.
My current solution, which I don't like, is to define the routes once on the main map and once on a 'base' scope:
def draw_routes(map)
# do my routing here
end
ActionController::Routing::Routes.draw do |map|
map.with_options :path_prefix => '/base' do |base|
draw_map(base)
end
draw_map(map)
end
what I'd like to do is something like:
ActionController::Routing::Routes.draw do |map|
map.strip 'base'
# do my routing here
end
is there a solution of that form?
You can write a middleware to remove the base from the url.
In lib/remove_base.rb:
class RemoveBase
def initialize(app)
#app = app
end
def call(env)
env['REQUEST_PATH'].gsub!(/^\/base/, '')
env['PATH_INFO'].gsub!(/^\/base/, '')
env['REQUEST_URI'].gsub!(/^\/base/, '')
#status, #headers, #response = #app.call(env)
[#status, #headers, self]
end
def each(&block)
#response.each(&block)
end
end
and add this line in config/environment.rb
config.middleware.use "RemoveBase"
I've tested it in 2.3.8 with mongrel, and it seems to work.
I think that you could simply do something like:
map.connect 'base/:controller/:action/:id'
That should route you to the right controller, action and id.
Related
could one advise me how to get a url like this in rails
http://www.example.com/users/5/ian
i tried the below but unsure:
route file:
devise_for :users
resources :users do
resources :socials
end
get '/users/:id/:firstname', controller: 'users', action: 'show'
users_controller.rb
def show
#user = User.find(params[:id], params[:firstname])
end
If you are trying to achieve 'friendly urls' then I suggest using this:
You don't have to create a special route:
get '/users/:id', controller: 'users', action: 'show'
Instead you have your model overwrite the to_param method:
class User
...
def to_param
"#{id}-#{firstname.try(:parameterize)}"
end
...
end
The url helper calls to_param to build the urls. If you overwrite it this way, you will receive a url like this:
http://localhost:3000/users/1-artloe
The rails find method calls .to_i on the params[:id] which, thankfully, interprets strings as number until it arrives at a character that can't become a number.
Examples:
'123abcde'.to_i # 123
'123-asdf'.to_i # 123
'asdf-123'.to_i # 0
So except for overwriting to_param, you don't have to do anything.
Try replacing this
def show
#user = User.find_by_id_and_firstname(params[:id], params[:firstname])
end
If what you are trying accomplish is "friendly urls" you would do it by:
# GET /users/1
# GET /users/joe
def show
#user = User.find_by!('id = :x OR firstname = :x', x: params[:id])
end
However you must ensure that property you are using in URLs is URL safe and unique. Usually a separate username or slug field is used.
Nothing special is needed in terms of routes.
These gems provide "friendly urls":
stringex
friendly_id
Is there any way that i can make url_for to return the url based on the request.host during action dispatch routing ?
mount Collaborate::Engine => '/apps/collaborate', :constraints => {:host => 'example.com' }
mount Collaborate::Engine => '/apps/worktogether'
Example:
When the user is on example.com host
collaborate_path => /apps/collaborate
When the user is on any other host
collaborate_path => /apps/worktogether
After a lot of research, i realize that RouteSet class has named_routes which does not consider the constraints to return the url.
I've tried overriding #set in action_dispatch/routing/route_set.rb to pickup from rails application but dint work as expected
#search_set = Rails.application.routes.set.routes.select{|x| x.defaults[:host] == options[:host] }[0]
#set = #search_set unless #search_set.blank?
Remove .com in your example
mount Collaborate::Engine => '/apps/collaborate', :constraints => {:host => 'examplesite' }
mount Collaborate::Engine => '/apps/worktogether'
Should just work
If you need a more advanced constraint, make your own constraint:
class CustomConstraint
def initialize
# Things you need for initialization
end
def matches?(request)
# Do your thing here with the request object
# http://guides.rubyonrails.org/action_controller_overview.html#the-request-object
request.host == "example"
end
end
Rails.application.routes.draw do
get 'foo', to: 'bar#baz',
constraints: CustomConstraint.new
end
You can also specify constraints as a lambda:
Rails.application.routes.draw do
get 'foo', to: 'foo#bar',
constraints: lambda { |request| request.remote_ip == '127.0.0.1' }
end
source: http://guides.rubyonrails.org/routing.html#advanced-constraints
As for as my concern if you handle it at middleware level then it would be good. This is what my assumption.
Add this line in config/application.rb
config.middleware.insert_before ActionDispatch::ParamsParser, "SelectiveStack"
Add a middleware in app directory with middleware directory as a Convention
app/middleware/selective_stack.rb
class SelectiveStack
def initialize(app)
#app = app
end
def call(env)
debugger
if env["SERVER_NAME"] == "example.com"
"/apps/collaborate"
else
"/apps/worktogether"
end
end
end
Hope this will solve your issue.!!!
Alright, here's a shot in the dark; maybe you've tried it already or maybe I'm really missing something. On the surface, it really looks like you're just trying to override a path helper method for apps. So why not set up an override in the application_helper.rb? Something like:
module ApplicationHelper
def collaborate_path
if request.domain == "example.com"
"/apps/collaborate"
else
"/apps/worktogether"
end
end
end
So currently I am manually directing from a naked domain due to restrictions with my hosting provider (Heroku). Everything works just fine. The problem is that if a users visits mydomain.com/route, a redirect will be issued back to www.mydomain.com without the /route. How would I go about re-appending the route, but still redirecting to www. ?
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :ensure_domain
APP_DOMAIN = 'www.domain.com'
def index
end
def ensure_domain
if Rails.env.production?
if request.env['HTTP_HOST'] != APP_DOMAIN
redirect_to "http://#{APP_DOMAIN}", :status => 301
end
end
end
end
EDIT
I removed my code above from my ApplicationController, and opted for using the refraction gem as suggested by hurikhan77, which solved my problem.
Here is refraction_rules.rb I used.
Refraction.configure do |req|
if req.host == "domain.com"
req.permanent! :host => "www.domain.com"
end
end
I suggest using the refraction gem for this: http://rubygems.org/gems/refraction
Ideally, you would set up rules like that in your web server configuration. Requests would become faster, because they would not even reach the rails stack. There would be no need to add any code to your app either.
However, if you are running in some restricted environment, like heroku, I'd advise adding a rack middleware. (Just for guidelines, can't guarantee if this particular code is bug free)
class Redirector
SUBDOMAIN = 'www'
def initialize(app)
#app = app
end
def call(env)
#env = env
if redirect?
redirect
else
#app.call(env)
end
end
private
def redirect?
# do some regex to figure out if you want to redirect
end
def redirect
headers = {
"location" => redirect_url
}
[302, headers, ["You are being redirected..."]] # 302 for temp, 301 for permanent
end
def redirect_url
scheme = #env["rack.url_scheme"]
if #env['SERVER_PORT'] == '80'
port = ''
else
port = ":#{#env['SERVER_PORT']}"
end
path = #env["PATH_INFO"]
query_string = ""
if !#env["QUERY_STRING"].empty?
query_string = "?" + #env["QUERY_STRING"]
end
host = "://#{SUBDOMAIN}." + domain # this is where we add the subdomain
"#{scheme}#{host}#{path}#{query_string}"
end
def domain
# extract domain from request or get it from an environment variable etc.
end
end
You can also test the whole thing in isolation
describe Redirector do
include Rack::Test::Methods
def default_app
lambda { |env|
headers = {'Content-Type' => "text/html"}
headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly"
[200, headers, ["default body"]]
}
end
def app()
#app ||= Rack::Lint.new(Redirector.new(default_app))
end
it "redirects unsupported subdomains" do
get "http://example.com/zomg?a=1"
last_response.status.should eq 301
last_response.header['location'].should eq "http://www.example.com/zomg?a=1"
end
# and so on
end
Then you can add it to production (or any preferred environments) only
# production.rb
# ...
config.middleware.insert_after 'ActionDispatch::Static', 'Redirector'
If you want to test it in development, add the same line to development.rb and add a record to your hosts file (usually /etc/hosts) to treat yoursubdomain.localhost as 127.0.0.1
Not sure if this is the best solution but you could regex the request.referrer and pull out anything after .com and append it to the APP_DOMAIN
Or I guess you could just take out everything before the first . in request.env['HTTP_HOST'] add replace with http://www. assuming you don't plan on using subdomains.
I'm trying to insert a locale at the beginning of a request URI in a Rails 3.1 app if it is missing. I created a Ruby script that does what I want:
uri = "/products"
re = /\A\/((?:[a-z]{2,2})(?:[-|_](?:[A-Z]{2,2}))?)(\/.*)\Z/
unless uri =~ re
uri = "/en#{uri}"
end
puts uri
So, if the request URI is /en-GB/products (the locale is already present), it doesn't do anything. If it is /products (like the example above), it spits out /en/products.
Now I'm trying to get it to work in my routes file. Here's what I've attempted:
match "(*all)", :to => redirect do |params, request|
uri = request.path_info
re = /\A\/((?:[a-z]{2,2})(?:[-|_](?:[A-Z]{2,2}))?)(\/.*)\Z/
unless uri =~ re
uri = "/en#{uri}"
end
"#{request.scheme}://#{request.host_with_port}#{uri}"
end
My problem is that I can't even get inside the match block. I keep getting an ArgumentError: redirection argument not supported.
I've tried changing it to match "(*all)" => redirect do |params, request| to no avail.
I'm looking at the Rails 3.1 API documentation for these examples.
Is the routes file the place to try and do this? It makes the most sense to me.
Introducing logic in routes smells for me. Controllers are meant for that, and I would use optional scope in routes and before_filter in controller with redirect_to
routes.rb - keep it simple:
scope '(:locale)', :constraints => {:locale=> /[a-z]{2}(-[A-Z]{2})?/ } do
match 'url1' ...
match 'other' ...
end
controller:
before_filter :check_locale
protected
def check_locale
redirect_to "/en#{request.path_info}" if params[:locale].blank?
end
(the above is written from memory)
I find these lines in a before_filter in the ActionController quite usefull.
These lines extracts a locale an redirects e.g. foo.com/fie to foo.com/en/fie (or wahtever locale the current locale is). If the user has a not supported locale, he gets a hint, that he can gon on with english...
def set_locale
params_locale = params[:locale]
if params_locale
if (!Supportedlocale::SUPPORTED.include?(params_locale))
redirect_to "/en/localenotsupported/"
end
end
language_locale = locale_from_accept_language
default_locale = I18n.default_locale
I18n.locale = params_locale || language_locale || default_locale
if params_locale.blank?
redirect_to "/#{I18n.locale}#{request.path_info}"
end
end
def locale_from_accept_language
accepted_lang = request.env['HTTP_ACCEPT_LANGUAGE']
if (!accepted_lang.nil?)
accepted_lang.scan(/^[a-z]{2}/).first
else
"en" #en is default!
end
end
In order to keep parameter like pagination do something like :
def check_locale
if params[:locale].blank?
I18n.locale = :en
redirect_to params.merge!(:locale => I18n.locale)
end
end
So
/controler/action?page=10&search=dada => /en/controler/action?page=10&search=dada
I have the following route in routes.rb:
map.resources 'protégés', :controller => 'Proteges', :only => [:index]
#
# this version doesn't work any better:
# map.resources 'proteges', :as => 'protégés', :only => [:index]
When I go to "http://localhost:3000/protégés" I get the following:
No route matches "/prot%C3%A9g%C3%A9s" with {:method=>:get}
I figured that the HTTP server I was using (Mongrel) wasn't unescaping properly. I also tried Apache with Passenger to no avail. I tried adding a Rack middleware:
require 'cgi'
class UtfUrlMiddleware
def initialize(app)
#app = app
end
def call(env)
request = Rack::Request.new(env)
puts "before: #{request.path_info}"
if request.path_info =~ /%[0-9a-fA-F]/
request.path_info = CGI.unescape(request.path_info)
end
puts "after: #{request.path_info}"
#app.call(env)
end
end
I see the correct information in the logs:
before: /prot%C3%A9g%C3%A9s
after: /protégés
but I still see the same "No route matches" error.
How do I convince Rails to use the internationalized route? I'm on Rails 2.3.5 for what it's worth.
The problem is that Rails uses the "REQUEST_URI" environment variable. Thus, the following works:
# in UtfUrlMiddleware:
def call(env)
if env['REQUEST_URI'] =~ /%[0-9a-fA-F]/
env['REQUEST_URI'] = CGI.unescape(env['REQUEST_URI'])
end
#app.call(env)
end