Getting Rails URL helpers to automatically output https urls - ruby-on-rails

I am developing a site that mixes http and https a lot - whats the best/easiest way to make the links use the right protocol for the route - can it be specified in the routes file?
Say I have the following route in Rails 3.
match "/test" => "test#index", :as => :test, :constraints => { :protocol => 'https' }
If I'm on a http page, and I use test_url(), it'll output http://domain.com/test. I want https://domain.com/test instead.
I know I can use test_url(:secure => true), but that's duplicating logic.
I know I could have http://domain.com/test to https://domain.com/test, but that's an extra redirect, plus it fails on form posts.
Ideas?

Use test_url(:protocol => 'https') for https urls.

Haven't tried but add this in your ApplicationController:
def default_url_options(options={})
{ :secure => true }
end

def default_url_options(options={})
{ :protocol => "https" }
end

For Rails 3.2 I used a combination of #apneadiving's answer. Adding the below code to my ApplicationController
def default_url_options(options={})
options.merge{ :protocol => "https" }
end

It looks like this will be solved in Rails 4! https://github.com/rails/rails/commit/9b4514c3b8ecfbc40a44dbd4c2ebd4ce67f4a459

Rails 3 SSL routing redirects from https to http answers this question pretty well. In short, there's not a great way to do it. I submitted the following Rails bug: https://github.com/rails/rails/issues/3571

To generate https url, add an option called protocol with value https
test_url(protocol: 'https')

You can use a plugin called ss_requirement, it will provide you with methods like
ssl_required
ssl_allowed
You can simply add this in your controller to enable ot disable https on any action
ssl_allowed :login, :update_profile
https://github.com/rails/ssl_requirement

Related

How to add a trailing_slash to all urls without in Rails 4?

I've tried adding this in application.rb
config.action_controller.default_url_options = { :trailing_slash => true }
as well as having :trailing_slash => true in routes.rb
match '/download', to: 'welcome#download', via: 'get', :trailing_slash => true
But neither seems to work. I searched through rails 4.0 doc but couldn't find related info. What am I missing here?
Update:
I've tried adding
Rails.application.default_url_options[:trailing_slash] = true
in filter_parameter_logging.rb since this is the only place in the whole project where I could find Rails.application.*, but it's not working either. I found the line here among the releases and I am using 4.0.4. Am I adding this in the wrong place? And I did restarted server before rechecking.
And sorry for the simple question but from what I've gathered isn't trailing_slash supposed to be reflected in browser url as well, if not primarily? Because this is what I need, to go with historyjs.
I think you have the meaning of :trailing_slash => true wrong.
All it does is add the / to the end of you path helpers. No redirecting involved.
Your routes will still respond to both with and without the trailing slash.
If you want to redirect all non-trailing_slash uri's like /download to /download/ using a nginx http server you would do something like this:
rewrite ^([^.\?]*[^/])$ $1/ permanent;
You would still want to add the :trailing_slash => true to your routes so your path/url helpers generate the the correct uri's (so user don’t need to redirect).
Trailing_slash refers to a / after the name like page/ not like /page.
You have given your routes wrongly.
Change it to
match 'download/', to: 'welcome#download', via: 'get', :trailing_slash => true
There is also other way to achieve this by giving a trailing_slash => true option directly to your link_to helper.
link_to 'Downloads', downloads_path(:trailing_slash => true)
Though this work in Rails 3,not sure about Rails 4.
For more details see this SO.
I am using rails 4.0.2 for me it's working
routes.rb
get 'admin/update_price_qty' => 'admin#update_price_qty', :trailing_slash => true,:as => "price"
in console :-
irb(main):003:0* app.price_path
=> "/admin/update_price_qty/"
routes.rb
match '/download', to: 'welcome#index', via: 'get', :trailing_slash => true,:as => "welcome_price"
in console :-
`irb(main):002:0> app.welcome_price_path
=> "/download/"`
But I've tried adding this in application.rb
config.action_controller.default_url_options = { :trailing_slash => true }
not working.
You may add this line to config/application.rb:
config.action_controller.default_url_options = { trailing_slash: true }
If you do this, when you call a Rails path helper inside a controller or helper, the generated path will have a / at the end:
class ApplicationController
def index
download_path # returns "/download/"
end
end
module PathHelper
def path
download_path # returns "/download/"
end
end
If you need to use path helpers outside controllers and helpers, you need to include Rails.application.routes.url_helpers, but apparently, this ignores the trailing_slash configuration above:
class SomeClass
include Rails.application.routes.url_helpers
def path
download_path # returns "/download"
end
end
In this case, you should add { trailing_slash: true } as a parameter:
class SomeClass
include Rails.application.routes.url_helpers
def path
download_path(trailing_slash: true) # returns "/download/"
end
end

Rails routes redirection for subdomains

We can't change the server configuration files, so we need to do our redirections at the rails level.
I have no problem with path redirections to external sites, like:
match "/meow" => redirect("http://meow.com/")
The issue is with the subdomains. I need to redirect for example:
http://my.example.com => http://example.com
How can this be done using routes.rb?
According to #cfernandezlinux's amazing answer, here's the same in Rails 4/Ruby 2 syntax:
constraints subdomain: "meow" do
get "/" => redirect { |params| "http://www.externalurl.com" }
end
match in routes.rb is not allowed in Rails 4.0 anymore. You have to use explicitly get, post, etc.
hashrocket syntax (=>) is for old Ruby, now in Ruby 2.0 we use param: 'value' syntax
I ended up doing something like this:
constraints :subdomain => "meow" do
match "/" => redirect { |params| "http://www.externalurl.com" }
end
If you don't want to hard-code the URL (so, for example, you can test/use this locally), you could do:
constraints subdomain: 'subdomain_from' do
get '/' => redirect(subdomain: :new_subdomain, path: '')
end
So now subdomain_from.google.com will redirect to new_subdomain.google.com.

Handling mix of HTTP and HTTPS links on a page

My setup: Rails 3.0.9, Ruby 1.9.2
My app requires that only a certain part of my site be SSL protected and the rest not. In case anyone thinks this isn't normal behavior, check out Amazon. When merely browsing for products, it's in HTTP mode, during checkout, it switches to HTTPS. Even in the middle of a secure checkout transaction, there are several other links on the same page that are HTTP only.
I looked at ssl_requirement gem and decided not to use it because it isn't a complete solution for my needs. I ended up setting up specific SSL routes like
resources :projects do
resources :tasks, :constraints => { :protocol => "https" }
end
In my view code, for HTTP specific links
<%= link_to 'Projects', project_url(#project, :protocol => "http") %>
and to handle HTTPS specific link
<%= link_to 'Task', new_project_task_url(#project, :protocol => "https") %>
I understand this isn't the cleanest way but it's what I have decided to do. The problem with this setup is how to properly set both HTTP and HTTPS links on every page. There is a proposed solution here but it requires wholesale changes _path to _url and I prefer to avoid that if at all possible. The solutions involves adding this method in
application_helper.rb
module ApplicationHelper
def url_for(options = nil)
if Hash === options
options[:protocol] ||= 'http'
end
super(options)
end
end
So my question is it possible to change this method or another one to change _path calls to explicit urls so I can use the above method to set the proper protocol.
you could try this, although I'm not 100% sure it works:
Use the proposed changes from the stackoverflow answer
Add this to application_controll.rb:
class ApplicationController < ActionController::Base
def url_options
{ :host => request.host }.merge(super)
end
end
According to the Docs it should add the full url even if you use _path:
:only_path - If true, returns the relative URL (omitting the protocol,
host name, and port) (true by default unless :host is specified).
My app requires that only a certain part of my site be SSL protected and the rest not.
That's your faulty assumption. The secure premise is that if some of your app requires SSL, then all of your app requires SSL. The correct assumption then is that your entire app requires SSL.
If your app requires SSL, then you should use something simple like rack-ssl which sets the HSTS header and enforces the secure flag on cookies in all responses.

Problem with Rails page caching and automatic extensions

I have a JSON and XML based API that needs to be page cached. I have setup my routes on the api to include the format as part of the URL, such that URL's like this work:
http://example.com/foo/1/bar/2/xml
http://example.com/foo/1/bar/2/json
The problem I am seeing is that in the server's public folder, the files are being saved as xml.xml and json.json, and this causes cache misses the next time the URL is accessed.
Is there a way to either:
Turn off the auto extension generation so that they are saved without an extension at all? (EX: RAILS_ROOT/public/foo/1/bar/2/json)
Force all the extensions to be .html for every call. (EX: RAILS_ROOT/public/foo/1/bar/2/json.html)
Either of these would cause my server to return the cached file instead of a miss. How can I do this?
EDIT:
Somebody asked for the relevant route:
scope '(foo/:foo_id)', :foo_id => /\d+/ do
get '/bar/:bar_id/:format' => 'bars#show', :bar_id => /\d+/, :format => /json|xml|html/
end
SOLUTION:
While I was looking for an official way to make this happen using the built in page caching support, I ended up just using an after filter and my own page cache method, as suggested by Anton
# application_controller.rb
def cache_api_page
if REDACTEDServer::Application.config.action_controller.perform_caching
self.class.cache_page(response.body, request.path, '')
puts "CACHED PATH: #{request.path}"
end
end
# bar_controller.rb
after_filter :cache_api_page, :only => [ :show, :index ]
You can do that like this:
class FooController < ApplicationController
after_filter(:only => :show, :if => Proc.new { |c| c.request.format.json? }) do |controller|
controller.class.cache_page(controller.response.body, controller.request.path, '.html')
end
end
When http://example.com/foo/1/bar/2/json is accessed, it will write page to cache (RAILS_ROOT/public/foo/1/bar/2/json.html)
And if you get http://example.com/foo/1/bar/2/json again, you receive RAILS_ROOT/public/foo/1/bar/2/json.html, but your http server(Apache?) should know about content type of this files.
Otherwise content type will set to 'text/html'
UPDATE
To you .htaccess
<FilesMatch "\/json$">
<IfModule mod_headers.c>
Header set Content-Type "text/json"
</IfModule>
</FilesMatch>
<FilesMatch "\/xml$">
<IfModule mod_headers.c>
Header set Content-Type "text/xml"
</IfModule>
</FilesMatch>
In your application.rb configuration block try adding:
config.action_controller.page_cache_extension = '.html'
It should ignore the extension calculated from the request, and always use this.
You could also try using this with an empty string instead.
EDIT: actually this won't work because this only sets a default. If the request has an extension (in your case, concluded from :format) it will be used.
I suggest changing :format in your routes to something else, that rails will not give special meaning to, like :fmt. Then rails should not add the extension and default to '.html'.
EDIT2: If you have to use :format you can monkeypatch Rails:
ActionController::Caching::Pages::ClassMethods
private
def page_cache_file(path)
name = (path.empty? || path == "/") ? "/index" : URI.unescape(path.chomp('/'))
name << page_cache_extension #unless (name.split('/').last || name).include? '.'
return name
end
end
Notice the comment sign I've added before 'unless'. This is the part that overrides the default from my first answer.

How do I redirect without www using Rails 3 / Rack?

I understand there are a lot of questions that answer this. I'm familiar with .htaccess and nginx.conf methods, but I do not have access to such traditional configuration methods on Heroku.
Simone Carletti gave this answer that leverages Rails 2.x Metals, but I'm using Rails 3 and this isn't compatible:
Redirect non-www requests to www URLs in Ruby on Rails
Please note:
I'm not looking for a simple before_filter in my ApplicationController. I'd like to accomplish a rewrite similar to Simone's. I believe this is job for the webserver or middleware like Rack at the very least, so I'd like to leave this bit out of the actual application code.
Goal
redirect to status
----------------------------------------------------
www.foo.com foo.com 301
www.foo.com/whatever foo.com/whatever 301
Only hosts matching /^www\./ should be redirected. All other requests should be ignored.
In Ruby on Rails 4, removing www. from any URL whilst maintaining the pathname can be achieved simply by using:
# config/routes.rb
constraints subdomain: 'www' do
get ':any', to: redirect(subdomain: nil, path: '/%{any}'), any: /.*/
end
In contrast, adding www. to the beginning of any URL that doesn't already have it can be achieved by:
# config/routes.rb
constraints subdomain: false do
get ':any', to: redirect(subdomain: 'www', path: '/%{any}'), any: /.*/
end
There's a better approach if you're using Rails 3. Just take advantage of the routing awesomeness.
Foo::Application.routes.draw do
constraints(:host => /^example.com/) do
root :to => redirect("http://www.example.com")
match '/*path', :to => redirect {|params| "http://www.example.com/#{params[:path]}"}
end
end
I really like using the Rails Router for such things. Previous answers were good, but I wanted something general purpose I can use for any url that starts with "www".
I think this is a good solution:
constraints(:host => /^www\./) do
match "(*x)" => redirect { |params, request|
URI.parse(request.url).tap {|url| url.host.sub!('www.', '') }.to_s
}
end
Take a look at this middleware, it should do precisely what you want:
http://github.com/iSabanin/www_ditcher
Let me know if that worked for you.
A one-line version of Duke's solution. Just add to the top of routes.rb
match '(*any)' => redirect { |p, req| req.url.sub('www.', '') }, :constraints => { :host => /^www\./ }
In Rails 3
#config/routes.rb
Example::Application.routes.draw do
constraints(:host => "www.example.net") do
match "(*x)" => redirect { |params, request|
URI.parse(request.url).tap { |x| x.host = "example.net" }.to_s
}
end
# ....
# .. more routes ..
# ....
end
If you want to redirect from the top-level domain (TLD) to the www subdomain, use this code:
constraints :subdomain => '' do
match '(*any)' => redirect { |p, req| req.url.sub('//', '//www.') }
end
Note: This code the use of sub, not gsub, because sub replaces the first occurrence of the double-slashes where gsub would replace all double-slashes.
For Rails 4 the above solutions have to be appended with the Verb construction e.g. via: [:get, :post]. Duke's solution becomes:
constraints(:host => /^www\./) do
match "(*x)" => redirect { |params, request|
URI.parse(request.url).tap {|url| url.host.sub!('www.', '') }.to_s
}, via: [:get, :post]
end
Nothing wrong with the approaches above, but there are also a couple of gems that provide Rack middleware to do this.
I like the way that they keep this behaviour separate from the app itself, but it's not a particularly strong argument either way. I also use middleware to do this when working with Sinatra, so prefer to use a technique that I can use on apps built from Rails and/or Sinatra (I often run Nesta embedded in Rails).
Anyway, here they are:
https://github.com/cwninja/rack-force_domain
https://github.com/tylerhunt/rack-canonical-host
The first is simpler (and the one I've been using) while the second offers a couple more features (that I'm yet to need, but appreciate).

Resources