Rails URL generator switch http / https - ruby-on-rails

Is there a way to put in links to external resources that automatically adds the protocol based on the current protocol?
For example I want to show images from Facebook's Graph API. I was hoping I could do something like:
image_tag url_for("/1234567/picture", :host => "graph.facebook.com")
So that the url_for just bases the protocol on the current request's protocol.
I know this works but I'm hoping there's a better way:
image_tag("#{request.protocol}://graph.facebook.com/1234567/picture")

You can add the protocol option to url_for.
image_tag url_for("/1234567/picture", :host => 'graph.facebook.com', :protocol => request.protocol)
or maybe a better way would be to create a helper (if you do this a lot) called url_for_same_protocol (or whatever you want):
class ApplicationHelper
def url_for_same_protocol(url, options)
options[:protocol] ||= request.protocol
url_for url, options
end
end
.. and then just replace your url_for call with url_for_same_protocol.

Related

Rails: configuring a form action's host using custom URL from settings

I have a Rails app that I am feeding cross domain in production. It needs absolute references. Because of this, I have enabled the following in my config/environments/production.rb:
config.action_controller.asset_host = "http://myapp.herokuapp.com"
That works fine for images and resources but my input form that looks like this:
<%= form_tag('/plans/collapse_plans', :method => 'post', :remote => true ) do %>
is still getting this in the console:
Failed to load resource file://localhost/plans/collapse_plan
How can I change it so that form action will automatically include the specified host, instead of defaulting to localhost? Can I set this anywhere in config?
This seems like it will work:
https://github.com/binarylogic/settingslogic
Then I can just do:
<%= form_tag mysettings.myspecifiedhost + plans_collapse_plans_path, :method => 'post', :remote => true do %>
I may be on the wrong track here, but:
Asset host is not your application's host, asset host is a host that serves you /app/assets folder and this is configurable so you can set up a CDN for example, it's not intended for hosting action points.
If you want to target the full url of your own host use rake routes to get the route name corresponding to /plans/collapse_plans which probably looks something in the lines of plans_collapse_plans and then you can use plans_collapse_plans_url and rails will render the correct full URL for you.
If you're using the default host name rails provides automagically this will "just work", i.e.
[2] pry(#<#<Class:0x000000048fd780>>)> account_edit_url
=> "http://dev:3000/account/edit"
If this doesn't "just work", you can override all url helpers in the app by overriding default_url_options in your ApplicationController:
def default_url_options
{:host => HOST}
end
and be sure to set the HOST constant in your application's environment, for example:
[1] pry(#<#<Class:0x00000005047d10>>)> account_edit_url
=> "http://o7ms:3000/account/edit"
If you need to override this just in certain situations you can leave the ApplicationController alone and do:
[3] pry(#<#<Class:0x000000048fd780>>)> account_edit_url(host: MY_HOST_FOR_THE_OTHER_THINGY)
=> "http://foo:3000/account/edit"
In all cases you'll set up a config option in one place and all endpoints in the app will adjust.
EDIT
If you want to go fancy,
see default_url_options and rails 3,
by overriding url_options you may be able to implement pretty calls like account_edit_url(ajax_host: true), the url_options method would look something like this if this works:
def url_options
options = super
if super.delete(:ajax_host)
{host: AJAX_HOST}.merge(options)
else
options
end
end
what you are trying cannot be done normally for ajax calls.
see http://en.wikipedia.org/wiki/Same-origin_policy
Two approaches:--
1.) <%= form_tag root_url + plans_collapse_plans_path, :method => 'post', :remote => true do %>
concatenation:-- root_url + plans_collapse_plans_path
2.) in config/environments/production.rb
MyApp::Application.configure do
# general configurations
config.after_initialize do
Rails.application.routes.default_url_options[:host] = 'root_url' #'localhost:3000'
end
end

url_for not using default_url_options[:host] value

I've got a view for an ActionMailer that includes a few different links. I'm running it on localhost:3000 right now, and so I've set that as such in a file called setup_mail.rb in app/initializers (as indicated here):
ActionMailer::Base.default_url_options[:host] = "localhost:3000"
When I go to use url_for in the view, it doesn't seem to pull this value. If I then add :host => "localhost:3000" to each url_for tag, they work properly. But they don't work without that included.
I have another tag, project_url, which is as it appears: a link to a specified Project. This functions, including the host value, with just project_url(#project). Why would one work but not the other?
From everything I've read, setting the default_url_options[:host] in an initializer should allow me to omit the :host value in the url_for tag. Obviously, it's not the worst thing in the world to just add that value, but it seems unnecessary and it means that when I eventually host the project somewhere I'll have to go through and change that value all over the place. But worse than that, it's something that I don't understand. I'm still learning as I go here and so I'd like to know what I'm doing wrong.
The documentation is pretty clear on this
When you decide to set a default :host for your mailers, then you need to make sure to use the :only_path => false option when using url_for. Since the url_for view helper will generate relative URLs by default when a :host option isn’t explicitly provided, passing :only_path => false will ensure that absolute URLs are generated.
You could create your own helper to use instead of the url_for to force :only_path to be false
def your_url_for(options = {})
options.reverse_merge! only_path: false
url_for(options)
end
You could also monkey patch rails to force this as the default, but that's left up to you :)
This all would be in addition to adding
config.action_mailer.default_url_options = { host: "YOUR HOST" }
to config/application.rb or equivalent.
It seems :only_path option is false which is by default. so that is why you need to provide [:host] either explicitly for every tag or set default options for url_for which would apply to all tags. here is how to set default host:
put this code in your Application controller & it should work.
helper_method :url_for
def default_url_options(options)
{ host: 'localhost:3000' }
end
For more details check set url_for defaults
Instead of tampering with the global default setting which imho shouldn't be changed after initialization you can simply define a method default_url_options in your mailer just like you can do it in a controller:
class UserMailer < ActionMailer::Base
def default_url_options
{ host: Tenant.current(true).host }
end
def confirm(user)
#user = user
mail(to: #user.email, subject: t(".subject_confirm"))
end
end
You're setting the default in ActionMailer::Base, but appear to expect it to reset the default for ActionController::Base.
A <%= link_to %> inside your mailer view doesn't necessarily know anything about the fact that it's inside a mailer view.

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.

Full URL with url_for in Rails

How can I get a full url in rails?
url_for #book is returning only a path like /book/1 and not www.domain.com/book/1
Thanks (and sorry if the answer is obvious. Im learning rails!)
According to the docs, this shouldn't happen. The option you're looking for is :only_path and it's false by default. What happens if you set it to false explicitly?
url_for(#book, :only_path => false)
While you can use url_for you should prefer Ryan's method when you can - book_url(#book) for a full url or book_path(#book) for the path.
If it's a RESTful resource you'll be able to use this:
book_url(#book)
In Rails 4, url_for only takes one argument, so you need to pass an array with an explicit hash inside for the only_path option.
Good:
url_for([#post, #comment, {only_path: true}])
Bad:
url_for(#post, #comment, {only_path: true})
url_for([#post, #comment], {only_path: true})
From the source, url_for with an Array input just calls:
polymorphic_url([#post, #comment], {only_path: true})
as shown in #moose's answer.
As noted by #lime, only_path is generally not needed for polymorphic_url since you distinguish that with the _url _path suffixes.
It seems that this would work:
url_for(#book)
But it does not. The url_for method accepts only one argument, which can be either a string, an instance of a model, or a hash of options. This is rather unfortunate, as it would seem like you may need to link to #book and add options like :only_path or :host as well.
One way around it is to use polymorphic_url, which would render the correct absolute url even though your model is (likely) not actually polymorphic:
polymorphic_url(#book, :host => "domain.com")
Perhaps the best route would be to use a named route, which is set up automatically for you when declaring resources in your routes or using the :as option:
# in routes.rb:
resources :books
# or
get "books/:id" => "books#show", as: :book
# in your view:
book_path(#book, :host => "domain.com")
Use the :host option. For example, you can use:
url_for(#book, :host => "domain.com")
Note: with Rails 3 and above, use polymorphic_url instead of url_for.
In Rails 5, if you want the full url for the current controller/action (== current page), just use:
url_for(only_path: false)
Long answer:
In Rails 5, url_for in a view is ActionView::RoutingUrlFor#url_for. If you look at it's source code (https://api.rubyonrails.org/classes/ActionView/RoutingUrlFor.html#method-i-url_for), you'll see if you pass a Hash (keyword parameters are cast into a Hash by Ruby), it actually calls super, thus invoking the method of same name in it's ancestor.
ActionView::RoutingUrlFor.ancestors reveals that it's first ancestor is ActionDispatch::Routing::UrlFor.
Checking it's source code (https://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html#method-i-url_for), you'll read this:
Missing routes keys may be filled in from the current request's
parameters (e.g. :controller, :action, :id and any other parameters
that are placed in the path).
This is very nice, since it will build automatically a URL for you for the current page (or path, if you just invoke url_for without the only_path: false). It will also intelligently ignore the query string params; if you need to merge those, you can use url_for(request.params.merge({arbitrary_argument:'value'})).

Modifying rails route helper

I'd like to modify the behavior of the rails route helper *_url for a single route/page.
Here's what I'm try to do:
User visits:
http://test1.myapp.com/account
All the *_url routing helpers resolve to http://test1.myapp.com/ as normal.
But, then if the user goes to https://myapp.heroku.com/account/billing?id=test1
I'd like all the *_url routing helpers on that page to resolve to: http://test1.myapp.com/
instead of http://myapp.heroku.com/
So, is it possible to change the domain bit for all the *_url helper calls for a specific page?
For those interested, I'm trying to use heroku's piggyback ssl method for my app for just a secured billing page.
You can actually just modify the links that point to the billing area:
<%= link_to "Billing", my_helper_url(test1, :host => "myapp.heroku.com", :protocol => "https") %>

Resources