Get constraint based url using url_for based on mounted engine - ruby-on-rails

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

Related

Rails 4 url_for with host constraint

We created a multi tenancy application with multiple sites and each site is able to modify the routes from a backend.
in routes.rb we load the dynamic routes for all sites and put them into a host constraint like this
routes.rb
Frontend::Application.routes.draw do
DynamicRoutes.load
end
app/models/dynamic_routes.rb
class DynamicRoutes
# dynamically loads the routes from settings into the routes.rb file
# and adds a host constraint to just match with the current sites host
# http://codeconnoisseur.org/ramblings/creating-dynamic-routes-at-runtime-in-rails-4
def self.load
if Site.table_exists?
Frontend::Application.routes.draw do
Site.includes(:setting).each do |site|
site.routes.each do |route|
# write the route with the host constraint
constraints(:host => site.hostname) do
case route[0]
when :shop_show
match "#{route[1]}", to: 'shops#show', via: [:get], as: "shop_show_#{site.id}"
end
end
end
end
end
end
end
# allows to reload the routing
# e.g. when changes in route settings where made
#
def self.reload
Rails.application.reload_routes!
end
end
So we create all routes for each site and match them with a host constraint. This works fine unless we use the url_for helper
#site = Site.find_by(hostname: request.host)
url_for controller: 'shop', action: 'show', host: #site.hostname
url_for returns the first matching url, doesnt matter from which host it should belong to. so the host constraint is not used, even if I put a host: param
Do you have any idea, of how its possible to use url_for with host constraints?
I have had the same task in my application. url_for ignores host param. But we could create additional path helpers in our ApplicationController in the following way:
ApplicationController.rb
%w( shop_show ).each do |helper|
helper_name = "#{helper}_path".to_sym
helper_method helper_name
define_method(helper_name) { |*args| send "#{helper}_#{site.id}_path", *args }
end
After that you are able to use universal path shop_show_path in your views. Of course, you should dynamically assign site variable depending on your host/domain.

Rails routes: different domains to different places

I've got a Rails app up running on a server. It's a big project so there are lots of routes involved, and two domains point to the root at the moment. I'd like to somehow design my routes.rb to interpret one domain to take it to a certain part of the app as if it was the root, and use the other for everywhere else.
Something like this (very pseudocode, hope you get the idea):
whole_app.com
whole_app.com/documents
whole_app.com/share
whole_app.com/users
partial_app.com, :points_to => 'whole_app.com/share'
Can Rails handle this? Thank-you!
You can achieve this by overriding default url_options method in application controller. This will override host url for every request.
class ApplicationController < ActionController::Base
....
def default_url_options
if some_condition
{:host => "partial_app.com"}
else
{:host => "whole_app.com"}
end
end
....
end
And for pointing a route to some specific url, you may use:
match "/my_url" => redirect("http://google.com/"), :as => :my_url_path
The better way is to do settings on server to redirect some url to a specific location.
is it going to /share based on some kind of criteria? if so you can do this:
routes.rb
root :to => 'pages#home'
pages_controller.rb
def home
if (some condition is met)
redirect_to this_path
else
render :layout => 'that'
end
end

Setting up Rails routes based on QueryString

I've seen similar questions on this, but not quite what I'm looking for...
Forgetting for a moment the wisdom of doing this, is it possible to do this?...
/object/update/123?o=section # ==> route to SectionController#update
/object/update/456?o=question # ==> route to QuestionController#update
...and if so, how would that be done?
Assuming you're using Rails 3+, you can use an "Advanced Constraint" (read more about them at http://guides.rubyonrails.org/routing.html#advanced-constraints).
Here's how to solve your example:
module SectionConstraint
extend self
def matches?(request)
request.query_parameters["o"] == "section"
end
end
module QuestionConstraint
extend self
def matches?(request)
request.query_parameters["o"] == "question"
end
end
Rails.application.routes.draw do
match "/object/update/:id" => "section#update", :constraints => SectionConstraint
match "/object/update/:id" => "question#update", :constraints => QuestionConstraint
end
More concise than #moonmaster9000's answer for routes.rb only:
match "/object/update/:id" => "section#update",
:constraints => lambda { |request| request.params[:o] == "section" }
match "/object/update/:id" => "question#update",
:constraints => lambda { |request| request.params[:o] == "question" }
Setting aside the question of whether it is wise to do so, the answer to "is this possible" is 'yes':
class QueryControllerApp
def self.call(env)
controller_name = env['QUERY_STRING'].split('=').last
controller = (controller_name.titleize.pluralize + "Controller").constantize
controller.action(:update).call(env)
rescue NameError
raise "#{controller_name} is an invalid parameter"
end
end
MyRailsApp::Application.routes.draw do
put 'posts/update/:id' => QueryControllerApp
end
Basically the route mapper can accept any Rack application as an endpoint. Our simple app parses the query string, builds the controller name and calls the ActionController method action (which is itself a Rack application). Not shown: how to deal with query strings with any format other than 'o=<controller_name>'

Rails 3 SSL routing redirects from https to http

This question relates to this SO question and answer (rails-3-ssl-deprecation
) where its suggested to handle ssl in rails 3 using routes.rb and routes like:
resources :sessions, :constraints => { :protocol => "https" }
# Redirect /foos and anything starting with /foos/ to https.
match "foos(/*path)", :to => redirect { |_, request| "https://" + request.host_with_port + request.fullpath }
My problem is that links use relative paths(i think thats the correct term) and once I'm on a https page all the other links to other pages on the site then use https.
1) Whats the best way to get back to http for pages where https isn't required? Do I have to setup redirects for all them(I hope note) or is there a better way. Would the redirects be like this:
match "foos(/*path)", :to => redirect { |_, request| "http://" + request.host_with_port + request.fullpath }
2) If redirects back to http are required, how do I handle a case where I want all methods to be http except one? ie foos(/*path) would be for all foos methods. But say I wanted foos/upload_foos to use ssl. I know how to require it
scope :constraints => { :protocol => "https" } do
match 'upload_foos' => 'foos#upload_foos', :via => :post, :as => :upload_foos
end
but if I put in the http redirect to the foos path what happens to https upload_foos?
If you want all your links to be able to switch between http and https, you have to stop using the _path helper and switch to _url helpers.
After that, using a scope with the protocol parameter forced and protocol constraint makes the urls automatically switch.
routes.rb
scope :protocol => 'https://', :constraints => { :protocol => 'https://' } do
resources :sessions
end
resources :gizmos
And now in your views:
<%= sessions_url # => https://..../sessions %>
<%= gizmos_url # => http://..../gizmos %>
Edit
This doesn't fix urls that go back to http when you are in https. To fix that you need to override url_for.
In any helper
module ApplicationHelper
def url_for(options = nil)
if Hash === options
options[:protocol] ||= 'http'
end
super(options)
end
end
This will set the protocol to 'http' unless it was explicitly set (in routes or when calling the helper).
This was a long time ago and I'm sure it can be improved, but back on some old version of rails I had this code in application controller. Not sure this is still valid for Rails 3, but it may be of some help:
private
SECURE_ACTIONS = {
:login => ["login", "login_customer", "remind_password", "add_customer", "add_or_login_customer"],
:store => ["checkout", "save_order"],
:order => ["show"] }
# Called as a before_filter in controllers that have some https:// actions
def require_ssl
unless ENV['RAILS_ENV'] != 'production' or #request.ssl?
redirect_to :protocol => 'https://', :action => action_name
# we don't want to continue with the action, so return false from the filter
return false
end
end
def default_url_options(options)
defaults = {}
if USE_EXPLICIT_HOST_IN_ALL_LINKS
# This will OVERRIDE only_path => true, not just set the default.
options[:only_path] = false
# Now set the default protocol appropriately:
if actions = SECURE_ACTIONS[ (options[:controller] || controller_name).to_sym ] and
actions.include? options[:action]
defaults[:protocol] = 'https://'
defaults[:host] = SECURE_SERVER if defined? SECURE_SERVER
else
defaults[:protocol] = 'http://'
defaults[:host] = NON_SECURE_SERVER if defined? NON_SECURE_SERVER
end
end
return defaults
end
The USE_EXPLICIT_HOST_IN_ALL_LINKS was some global configuration option, but you can ignore this.
In each controller that required https, I'd add before_filter :require_ssl and add that controller name and its methods to SECURE_ACTIONS. This probably can be improved by passing the action names to the before filter, or something.

Multiple robots.txt for subdomains in rails

I have a site with multiple subdomains and I want the named subdomains robots.txt to be different from the www one.
I tried to use .htaccess, but the FastCGI doesn't look at it.
So, I was trying to set up routes, but it doesn't seem that you can't do a direct rewrite since every routes needs a controller:
map.connect '/robots.txt', :controller => ?, :path => '/robots.www.txt', :conditions => { :subdomain => 'www' }
map.connect '/robots.txt', :controller => ?, :path => '/robots.club.txt'
What would be the best way to approach this problem?
(I am using the request_routing plugin for subdomains)
Actually, you probably want to set a mime type in mime_types.rb and do it in a respond_to block so it doesn't return it as 'text/html':
Mime::Type.register "text/plain", :txt
Then, your routes would look like this:
map.robots '/robots.txt', :controller => 'robots', :action => 'robots'
For rails3:
match '/robots.txt' => 'robots#robots'
and the controller something like this (put the file(s) where ever you like):
class RobotsController < ApplicationController
def robots
subdomain = # get subdomain, escape
robots = File.read(RAILS_ROOT + "/config/robots.#{subdomain}.txt")
respond_to do |format|
format.txt { render :text => robots, :layout => false }
end
end
end
at the risk of overengineering it, I might even be tempted to cache the file read operation...
Oh, yeah, you'll almost certainly have to remove/move the existing 'public/robots.txt' file.
Astute readers will notice that you can easily substitute RAILS_ENV for subdomain...
Why not to use rails built in views?
In your controller add this method:
class StaticPagesController < ApplicationController
def robots
render :layout => false, :content_type => "text/plain", :formats => :txt
end
end
In the view create a file: app/views/static_pages/robots.txt.erb with robots.txt content
In routes.rb place:
get '/robots.txt' => 'static_pages#robots'
Delete the file /public/robots.txt
You can add a specific business logic as needed, but this way we don't read any custom files.
As of Rails 6.0 this has been greatly simplified.
By default, if you use the :plain option, the text is rendered without
using the current layout. If you want Rails to put the text into the
current layout, you need to add the layout: true option and use the
.text.erb extension for the layout file. Source
class RobotsController < ApplicationController
def robots
subdomain = request.subdomain # Whatever logic you need
robots = File.read( "#{Rails.root}/config/robots.#{subdomain}.txt")
render plain: robots
end
end
In routes.rb
get '/robots.txt', to: 'robots#robots'
For Rails 3:
Create a controller RobotsController:
class RobotsController < ApplicationController
#This controller will render the correct 'robots' view depending on your subdomain.
def robots
subdomain = request.subdomain # you should also check for emptyness
render "robots.#{request.subdomain}"
end
end
Create robots views (1 per subdomain):
views/robots/robots.subdomain1.txt
views/robots/robots.subdomain2.txt
etc...
Add a new route in config/routes.rb: (note the :txt format option)
match '/robots.txt' => 'robots#robots', :format => :txt
And of course, you should declare the :txt format in config/initializers/Mime_types.rb:
Mime::Type.register "text/plain", :txt
Hope it helps.
If you can't configure your http server to do this before the request is sent to rails, I would just setup a 'robots' controller that renders a template like:
def show_robot
subdomain = # get subdomain, escape
render :text => open('robots.#{subdomain}.txt').read, :layout => false
end
Depending on what you're trying to accomplish you could also use a single template instead of a bunch of different files.
I liked TA Tyree's solution but it is very Rails 2.x centric so here is what I came up with for Rail 3.1.x
mime_types.rb
Mime::Type.register "text/plain", :txt
By adding the format in the routes you don't have to worry about using a respond_to block in the controller.
routes.rb
match '/robots.txt' => 'robots#robots', :format => "text"
I added a little something extra on this one. The SEO people were complaining about duplicated content both in subdomains and in SSL pages so I created a two robot files one for production and one for not production which is also going to be served with any SSL/HTTPS requests in production.
robots_controller.rb
class RobotsController < ApplicationController
def robots
site = request.host
protocol = request.protocol
(site.eql?("mysite.com") || site.eql?("www.mysite.com")) && protocol.eql?("http://") ? domain = "production" : domain = "nonproduction"
robots = File.read( "#{Rails.root}/config/robots-#{domain}.txt")
render :text => robots, :layout => false
end
end

Resources