Change ActionMailer Email URL Host Dynamically - ruby-on-rails

In my application.html.erb I have <%= render #objects %>, that renders a bunch of _object.html.erb partials, each with a <%= link_to(object) %>. I render the same single partial <%= render #object %> in the email that is being sent when somebody leaves a comment but I want link to start with a server url.
I have tried everything:
link_to(object)
url_for(object)
before_filter :set_mailer_host
def set_mailer_host
ActionMailer::Base.default_url_options[:host] = request.host_with_port
end
default_url_options[:host] = "example.com"
def default_url_options
{ host: 'example.com' }
end
...Nothing works. With or without :only_links
In the end, I just built a dumb helper that prepends hostname to links:
# application_controller.rb
before_filter { App::request=request }
# application_helper.rb
def hostify obj
"http://#{App::request.host_with_port}#{url_for obj}"
end
# _object.html.erb:
<%= link_to obj.title, hostify(object) %>
Is there a normal way to do this?

I can't really tell what you're trying to link to or what 'obj' is referring to, but you can do something like:
link_to obj.title, {:host => 'example.com'}
or
link_to obj.title, {:controller => 'store', :action => 'view_cart', :host => 'example.com'}

Okay after spending some time with it, I think I am starting to figure it out.
You got to use restful routes then you can set default_url_options[:host] = host in the Mailer action and :only_path to true in default_url_options for other controllers. Plus :locale.
And in the view I have: objects_url(object)
Since I have different hostnames, I pass request.host_with_port as a parameter from a controller to the Mailer when sending mail.
link_to doesn't accept parameters and url_for() can only build url from parts of it.

def url_options
{ domain: 'example.com' }
end
Or better to merge in case you use other options:
def url_options
{ domain: 'example.com' }.merge(super)
end

Related

Rails I18n - How can I change other locale with the same page [duplicate]

I am adding I18N to my rails application by passing the locale using url params. My urls are looking like http://example.com/en/users and http://example.com/ar/users (for the english and arabic locales respectively).
In my routes file, I have defined my routes with a :path_prefix option:
map.resources :users, :path_prefix => '/:locale'
And locale is being set using a before_filter defined in ApplicationController
def set_locale
I18n.locale = params[:locale]
end
I also defined ApplicationController#default_url_options, to add locale to all urls generated by the application:
def default_url_options(options={})
{:locale => I18n.locale}
end
What I want is to add a link in the layout header (displayed in all pages) that would link to the same page but with the other locale.
For instance, if I am browsing the arabic locale, I want a "English" link in the header, that will redirect me back to my current page, and set the locale to english. Is there a way to do this in rails?
Took me a while to find this but here is my solution:
link_to 'English', url_for( :locale => 'en' )
link_to 'Deutch', url_for( :locale => 'de' )
From the docs here: http://api.rubyonrails.org/classes/ActionController/Base.html#M000649
When generating a new URL, missing
values may be filled in from the
current request‘s parameters. For
example, url_for :action =>
‘some_action‘ will retain the current
controller, as expected. This behavior
extends to other parameters, including
:controller, :id, and any other
parameters that are placed into a
Route‘s path.
So using url_for will default to the current request's parameters, just change the one's you want in your code. In this case all I changed was :locale, so everything else stays the same.
Note this also works for "hidden" :parameters. So if you have:
map.my_map ':locale/my_map', :controller => 'home', :action => 'my_map'
using the above url_for in the page /en/my_map will not have 'home' in the url (ie /en/home/my_map). Bonus.
So I found a way to more explicitly do this with out relying on (as much) rails magic.
url_for(params.merge({:your_new_parameter => value}))
This should work in any link_to.
All its doing is taking the current request's parameters and merging your new desired hash into them and then creating a new url for that.
Link to current page with different locales
Tested on Rails 4
Hello all.
After some time of research I decide to write my own solution for this.
link_to 'English', url_for( :locale => 'en' )
link_to 'Deutch', url_for( :locale => 'de' )
This works perfect, but it allows XSS Vulnerability just passing parameters in your URL like below:
http://localhost:3000/en/about?host=www.fishingsiteorbadurl.com/%23&port=80
Or worst case:
http://localhost:3000/en/about?host=%D0%BE%D1%87%D0%B5%D0%BD%D1%8C%D0%BF%D0%BB%D0%BE%D1%85%D0%BE%D0%B9%D1%81%D0%B0%D0%B9%D1%82.%D1%80%D1%84
Check out what URLs you will get after going through this link in your application.
My production solution.
Method "change language" redirects to any page with proper locale just using HTTP_REFERER in request object.
Please note: URI.path method for get only path, not whole url
Make "change language" method in any controller:
def change_lang
if request.referer.nil?
refer = root_url
else
uri = URI(request.referer)
refer = uri.path
end
lang = params[:lang]
cookies[:locale] = lang
redirect_to refer
end
application_controller.rb
before_action :set_locale
def set_locale
# -- Get lang from cookies or url parameter locale
user_locale = cookies[:locale] || params[:locale]
# -- If present
if user_locale.present?
# -- If it is has 2 symbols
user_locale = user_locale.scan(/[a-zA-Z]{2}/)
else
# -- If no - use default en locale
user_locale = 'en'
end
# -- Check, is this locale available for using.
# Please note: this needed for disable invalid locale warning.
if I18n.available_locales.include?(user_locale[0].to_sym)
I18n.locale = user_locale[0]
else
I18n.locale = "en"
end
end
add this to your layout
<%= link_to 'English', change_lang_path('en') %> <%= link_to 'Russian', change_lang_path('ru') %>
config/routes.rb
scope "(:locale)", locale: /[a-zA-Z]{2}/ do
get "change_lang/:lang" => "users#change_lang", :as => "change_lang"
end
There is no need to use params.merge or any monkey-patch solution.
I hope this helps, because I personally spent a lot of time to solve it.
A much quicker avenue - and convenient if you have many parameters that change in different places... avoid the clutter with an anchor tag that just merges the new locale param to the existing ones (and actually killing the old locale param).
<%= link_to "ру", request.params.merge( locale: 'ru' ) %>
But yes, one needs to whitelist parameters at that point, according to application's context.
You can parse request_uri, and replace your locale in the path with regular expression
Ok, here is helper example. If I correctly understand the goal
def locale_url(url, locale)
url.gsub(/\/\w*$/, "/#{locale}")
end
url = "http://www.domain.com/products/1/ru" # or request.request_uri
locale = "en"
locale_url(url, locale) #=> "http://www.domain.com/products/1/en"
This is a start point, so you can make some different stuff that you need
You can safely use url_for to switch locales with url params if you set only_path: true:
<%= link_to I18n.t('language_name', locale: I18n.locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ) %>
We .clone the params before permitting them all (.permit!), to preserve strong parameters elsewhere. The only more secure solution I could find would be to time consumingly whitelist all params instead...
Robust I18n implementation:
Add a locales.rb initializer to define what I18n.available_locales you support:
# config/initializers/locales.rb
# Permitted locales available for the application
I18n.available_locales = [:en, :fr]
Set a language_name value in each language's locale file (e.g. fr.yml):
fr:
language_name: "Français"
As you add more languages, this ERB will let you generically switch between them:
// app/views/layouts/_languages.html.erb
<span class="languages">
<% I18n.available_locales.each do |locale| %>
<% if I18n.locale == locale %>
<%= link_to I18n.t('language_name', locale: locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ), {style: "display:none" } %>
<% else %>
<%= link_to I18n.t('language_name', locale: locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ) %>
<% end %>
<% end %>
</span>
For the controller, we automatically find the correct language for the user by detecting their browser's Accept-Language HTTP header (using the http_accept_language gem).
Set a session cookie to preserve locale across requests.
Or optionally, use default_url_options to insert the ?locale= param into your app's url. I do both.
Controller:
class ApplicationController < ActionController::Base
before_action :set_locale
private
def set_locale
I18n.locale = begin
extract_locale ||
session[:locale] ||
http_accept_language.compatible_language_from(I18n.available_locales) ||
I18n.default_locale
end
session[:locale] = I18n.locale
end
def extract_locale
parsed_locale = params[:locale].dup
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
def default_url_options
{ locale: I18n.locale }
end
end
This is what worked for me, which preserves params and protects against xss:
= link_to_unless_current "English", url_for( request.params.merge(locale: 'en'))
You could use link_to instead of link_to_unless_current
Have a look at this, though it may not be DRY and proper one, but works perfectly for me. It reads all the parameters you supplied replacing only the locale
EX urls : http://example.com:3000/us/users?t=123&m=343 etc
def us_link
link_to "US", form_locale_url("/us")
end
def jp_link
link_to "Japan",form_locale_url("/jp")
end
def form_locale_url(locale)
new_url = request.request_uri
new_locale_url = new_us_url = new_jp_url = new_url
if new_url == "/"
new_locale_url.sub!(/\//,locale)
elsif (new_url =~/\/us/) == 0
new_us_url.sub!(/\/us/,locale)
elsif (new_url =~/\/jp/) == 0
new_jp_url.sub!(/\/jp/,locale)
end
end

rack-affiliates gem with localhost

I'm messing with Rack::Affiliates but I don't know if it works with the domain localhost in development environment.
1º This is my config in application.rb file:
config.middleware.use Rack::Affiliates, {:param => 'aff_id', :ttl => 6.months, :domain => '.localhost'}
2º I send a email with a link and param aff_id something like:
<%= link_to "accept invite", new_user_registration_url(:aff_id => #user.id) %>
3º In root action:
def index
if request.env['affiliate.tag'] && affiliate = User.find_by_affiliate_tag(request.env['affiliate.tag'])
logger.info "Halo, referral! You've been referred here by #{affiliate.name} from #{request.env['affiliate.from']} # #{Time.at(env['affiliate.time'])}"
else
logger.info "We're glad you found us on your own!"
end
respond_to do |format|
format.html
end
end
I'm getting the message on console:
We're glad you found us on your own!
What am I doing wrong?
Thanks!
Did you remember to include config.middleware.use Rack::Affiliates in your config/application.rb file?
If not, add it and see what happens.
Otherwise you can try debugging by changing the if statement to:
if request.env['affiliate.tag']
logger.info "request.env['affiliate.tag'] = #{request.env['affiliate.tag']}"
else
logger.info "We're glad you found us on your own!"
end
This should tell you if the affiliate.tag is getting set and if so to what value.
It's all due to User.find_by_affiliate_tag. have you any column named affiliate_tag.
If your are inviting using this link <%= link_to "accept invite", new_user_registration_url(:aff_id => #user.id) %> where you are using #user.id as aff_id.
So you have to use User.find_by_id instead of User.find_by_affiliate_tag
Final code snippet of exmaple contoller will look like
class ExampleController < ApplicationController
def index
str = if request.env['affiliate.tag'] && affiliate = User.find_by_id(request.env['affiliate.tag'])
"Halo, referral! You've been referred here by #{affiliate.name} from #{request.env['affiliate.from']} # #{Time.at(env['affiliate.time'])}"
else
"We're glad you found us on your own!"
end
render :text => str
end
end

RubyOnRails: url_for application root

i know that doing
url_for(:only_path => false, :controller => 'home')
I will get, for example, http://localhost/home
But how do i handle to genereate http://localhost
This is an old question, but it still ranks high in searches. Currently, use root_url.
e.g.
<%= link_to "fully qualified root", root_url %>
will generate
fully qualified root
to get http://localhost, you'll simply:
<%= link_to "Home", root_path %>
That'll generate: Home which will effectively link to http://localhost
Depending on what your goals are, there are a few ways to use the server name or base URL. For the general case of, "I just need a reliable base URL that I can use anywhere," I use the config method.
# via routes.rb
map.root :controller => "foo", :action => "bar"
# view/controller:
root_url # inflexible. root_url will only ever be one URL
# via request object
url_for("http://"+request.host) # not available in models
# via config file (see railscast 85)
# environment.rb
APP_CONFIG = YAML.load_file("#{RAILS_ROOT}/config/config.yml")[RAILS_ENV]
# config/config.yml
development:
server_name: localhost:3000
production:
server_name: foo.com
# view/controller:
url_for(APP_CONFIG('server_name'))
You can also use:
ActionController::Base.relative_url_root
i.e. #{ActionController::Base.relative_url_root}/images/my_img.jpg

Rails link to current page and passing parameters to it

I am adding I18N to my rails application by passing the locale using url params. My urls are looking like http://example.com/en/users and http://example.com/ar/users (for the english and arabic locales respectively).
In my routes file, I have defined my routes with a :path_prefix option:
map.resources :users, :path_prefix => '/:locale'
And locale is being set using a before_filter defined in ApplicationController
def set_locale
I18n.locale = params[:locale]
end
I also defined ApplicationController#default_url_options, to add locale to all urls generated by the application:
def default_url_options(options={})
{:locale => I18n.locale}
end
What I want is to add a link in the layout header (displayed in all pages) that would link to the same page but with the other locale.
For instance, if I am browsing the arabic locale, I want a "English" link in the header, that will redirect me back to my current page, and set the locale to english. Is there a way to do this in rails?
Took me a while to find this but here is my solution:
link_to 'English', url_for( :locale => 'en' )
link_to 'Deutch', url_for( :locale => 'de' )
From the docs here: http://api.rubyonrails.org/classes/ActionController/Base.html#M000649
When generating a new URL, missing
values may be filled in from the
current request‘s parameters. For
example, url_for :action =>
‘some_action‘ will retain the current
controller, as expected. This behavior
extends to other parameters, including
:controller, :id, and any other
parameters that are placed into a
Route‘s path.
So using url_for will default to the current request's parameters, just change the one's you want in your code. In this case all I changed was :locale, so everything else stays the same.
Note this also works for "hidden" :parameters. So if you have:
map.my_map ':locale/my_map', :controller => 'home', :action => 'my_map'
using the above url_for in the page /en/my_map will not have 'home' in the url (ie /en/home/my_map). Bonus.
So I found a way to more explicitly do this with out relying on (as much) rails magic.
url_for(params.merge({:your_new_parameter => value}))
This should work in any link_to.
All its doing is taking the current request's parameters and merging your new desired hash into them and then creating a new url for that.
Link to current page with different locales
Tested on Rails 4
Hello all.
After some time of research I decide to write my own solution for this.
link_to 'English', url_for( :locale => 'en' )
link_to 'Deutch', url_for( :locale => 'de' )
This works perfect, but it allows XSS Vulnerability just passing parameters in your URL like below:
http://localhost:3000/en/about?host=www.fishingsiteorbadurl.com/%23&port=80
Or worst case:
http://localhost:3000/en/about?host=%D0%BE%D1%87%D0%B5%D0%BD%D1%8C%D0%BF%D0%BB%D0%BE%D1%85%D0%BE%D0%B9%D1%81%D0%B0%D0%B9%D1%82.%D1%80%D1%84
Check out what URLs you will get after going through this link in your application.
My production solution.
Method "change language" redirects to any page with proper locale just using HTTP_REFERER in request object.
Please note: URI.path method for get only path, not whole url
Make "change language" method in any controller:
def change_lang
if request.referer.nil?
refer = root_url
else
uri = URI(request.referer)
refer = uri.path
end
lang = params[:lang]
cookies[:locale] = lang
redirect_to refer
end
application_controller.rb
before_action :set_locale
def set_locale
# -- Get lang from cookies or url parameter locale
user_locale = cookies[:locale] || params[:locale]
# -- If present
if user_locale.present?
# -- If it is has 2 symbols
user_locale = user_locale.scan(/[a-zA-Z]{2}/)
else
# -- If no - use default en locale
user_locale = 'en'
end
# -- Check, is this locale available for using.
# Please note: this needed for disable invalid locale warning.
if I18n.available_locales.include?(user_locale[0].to_sym)
I18n.locale = user_locale[0]
else
I18n.locale = "en"
end
end
add this to your layout
<%= link_to 'English', change_lang_path('en') %> <%= link_to 'Russian', change_lang_path('ru') %>
config/routes.rb
scope "(:locale)", locale: /[a-zA-Z]{2}/ do
get "change_lang/:lang" => "users#change_lang", :as => "change_lang"
end
There is no need to use params.merge or any monkey-patch solution.
I hope this helps, because I personally spent a lot of time to solve it.
A much quicker avenue - and convenient if you have many parameters that change in different places... avoid the clutter with an anchor tag that just merges the new locale param to the existing ones (and actually killing the old locale param).
<%= link_to "ру", request.params.merge( locale: 'ru' ) %>
But yes, one needs to whitelist parameters at that point, according to application's context.
You can parse request_uri, and replace your locale in the path with regular expression
Ok, here is helper example. If I correctly understand the goal
def locale_url(url, locale)
url.gsub(/\/\w*$/, "/#{locale}")
end
url = "http://www.domain.com/products/1/ru" # or request.request_uri
locale = "en"
locale_url(url, locale) #=> "http://www.domain.com/products/1/en"
This is a start point, so you can make some different stuff that you need
You can safely use url_for to switch locales with url params if you set only_path: true:
<%= link_to I18n.t('language_name', locale: I18n.locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ) %>
We .clone the params before permitting them all (.permit!), to preserve strong parameters elsewhere. The only more secure solution I could find would be to time consumingly whitelist all params instead...
Robust I18n implementation:
Add a locales.rb initializer to define what I18n.available_locales you support:
# config/initializers/locales.rb
# Permitted locales available for the application
I18n.available_locales = [:en, :fr]
Set a language_name value in each language's locale file (e.g. fr.yml):
fr:
language_name: "Français"
As you add more languages, this ERB will let you generically switch between them:
// app/views/layouts/_languages.html.erb
<span class="languages">
<% I18n.available_locales.each do |locale| %>
<% if I18n.locale == locale %>
<%= link_to I18n.t('language_name', locale: locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ), {style: "display:none" } %>
<% else %>
<%= link_to I18n.t('language_name', locale: locale), url_for( params.clone.permit!.merge(locale: locale, only_path: true ) %>
<% end %>
<% end %>
</span>
For the controller, we automatically find the correct language for the user by detecting their browser's Accept-Language HTTP header (using the http_accept_language gem).
Set a session cookie to preserve locale across requests.
Or optionally, use default_url_options to insert the ?locale= param into your app's url. I do both.
Controller:
class ApplicationController < ActionController::Base
before_action :set_locale
private
def set_locale
I18n.locale = begin
extract_locale ||
session[:locale] ||
http_accept_language.compatible_language_from(I18n.available_locales) ||
I18n.default_locale
end
session[:locale] = I18n.locale
end
def extract_locale
parsed_locale = params[:locale].dup
I18n.available_locales.map(&:to_s).include?(parsed_locale) ? parsed_locale : nil
end
def default_url_options
{ locale: I18n.locale }
end
end
This is what worked for me, which preserves params and protects against xss:
= link_to_unless_current "English", url_for( request.params.merge(locale: 'en'))
You could use link_to instead of link_to_unless_current
Have a look at this, though it may not be DRY and proper one, but works perfectly for me. It reads all the parameters you supplied replacing only the locale
EX urls : http://example.com:3000/us/users?t=123&m=343 etc
def us_link
link_to "US", form_locale_url("/us")
end
def jp_link
link_to "Japan",form_locale_url("/jp")
end
def form_locale_url(locale)
new_url = request.request_uri
new_locale_url = new_us_url = new_jp_url = new_url
if new_url == "/"
new_locale_url.sub!(/\//,locale)
elsif (new_url =~/\/us/) == 0
new_us_url.sub!(/\/us/,locale)
elsif (new_url =~/\/jp/) == 0
new_jp_url.sub!(/\/jp/,locale)
end
end

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