Dynamically preset a url for development and production environments - ruby-on-rails

I'm creating a password reset functionality for my site in rails and in my mailer password_reset.text.erb file I'm currently sending
http://localhost:3000/password_resets/<%=#user.password_reset_token%>/edit/
in my development environment. This will hit my controller for password reset and redirect the user to edit the password if the token matches with the saved model.
However, I'd like to configure this dynamically so when I deploy to heroku it will know to change that to mywebsite.com/password_resets/...
How would I do this?
EDIT:
def password_reset(user)
#user = user
mail(to: user.email, subject: "Bitelist Password Reset")
end

Typically I configure the host information for the mailer in an appropriate config/environment file.
config.action_mailer.default_url_options = { :host => "example.com" }
You can take a look at the "Generating URLs" section of this page: http://rails.rubyonrails.org/classes/ActionMailer/Base.html
With that configuration set typical routing structures seem to work quite nicely. I am not 100% positive what routes will be available in your situation so you may still need to construct the full URL somewhat manually to include the reset token component.
To generate the actual URLs you can potentially use named routes if your routes are set up in a way that you have a named route that takes a user token. i.e.
<%= edit_password_resets_url(#user.password_reset_token) %>
If you do not have the token integrated into an existing route you may need to manually build the URL:
<%= "#{url_for(:controller => 'password_resets', :action => 'whatever_action_this_is')}/#{#user.password_reset_token}/edit/" %>
or even build it completely manually using default_url_options[:host] and then add the rest that you have above.
If need be you could also set the host at request time although that may be overkill (and will not be thread safe).
def set_mailer_host
ActionMailer::Base.default_url_options[:host] = request.host_with_port
end

Related

Why Action Mailer generated URLs are not reaching the site? Could it be my mail subdomain config?

I have a Rails app hosted in 'www.example.com'. My objective is to send an email containing a link to a url pointing to a specific place in my app.
The issue: In Development everything works OK but in Production my mailer link redirects to a Server DNS that can't be found.
When I checked the URL generated I noticed it points to: 'http://email.mail.example.com' instead of 'http://example.com' (where I'd have expected to point out to) but I haven't been able to change this or figure out why it happens.
My current code is:
config/environments/production.rb
config.action_mailer.default_url_options = { host: 'example.com' }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
:authentication => :plain,
:address => "smtp.mailgun.org",
:port => 587,
:domain => ENV['MAILGUN_DOMAIN'],
:user_name => ENV['MAILGUN_USERNAME'],
:password => ENV['MAILGUN_PASSWORD']
}
the link in my mailer view
<%= link_to 'click here', url_for(#post) %>
I've tried
Given the 'email.mail' bit in the URL I'm assuming there's an issue with my domain DNS configuration. Following Mailgun's advice (when adding a domain with them) I'm using a subdomain ('mail') for transactional emails.
As suggested I added a CNAME record (Host: email.mail.example.com, Value: mailgun.org) when setting this up.
So I've tried changing this record or even deleting it all together without success.
Also, I've changed the host value like below but it seems that regardless what I do the URL always starts with 'email.mail'
config.action_mailer.default_url_option = { host: 'mail.example.com' }
I've also tried changing the link to see if that provided other path like:
post_url(#post)
I wonder if you can help me with this. Should the host settings at Production.rb be different given I'm using a subdomain 'mail' for transactional emails? I've been trying to find an answer online but I haven't been able to find any similar cases.
Additional info
the app is supposed to send an email to some users every time there's a new post
My mailer code
class PostsMailer < ApplicationMailer
def new_post_notification(user, post)
#user = user
#post = post
mail to: "#{#user.email}", subject: "#{#post.user.username.capitalize} just posted"
end
end
My mailer view
<body>
<h2><%= #post.user.username.capitalize %> has just posted</h2>
<p>Check <%= #post.user.username.capitalize %>'s <%= link_to 'new post here', url_for(#post) %></p>
</body>
I was completely wrong on what was happening here. The urls generated by my helpers were OK the problem is that Mailgun (as most transactional email providers) will generate some made-up hashed URLs that direct the user to your site while being able to track if the users are clicking or opening the emails.
In Mailgun those hashed URLs are appended, by default, with any subdomains set up in your email domain. So even with prefix appended the URL should reach the site.
It still not clear what is the problem but it's definitely a Mailgun one. I moved to SendGrid added a white-labeled domain with a subdomain and it worked just fine. Not to mention it was easier to verify.
SendGrid has the added benefit that you can white-label the link with a different subdomain which worked as well perfectly.
I asked Mailgun for a reason why this happened and still waiting an answer. I'm assuming there's probably something else to add in the Domain's DNS settings.
I'll post here as soon as I get an answer in case someone ran into a similar problem.

How to run rails Devise method edit_password_url by hand?

We have a user whose mail provider seems to be blocking the account from which we send the password reset emails.
I wanted to just get the reset-password-URL running from irb, and mail it by hand. I can't seem to figure out how to run this thing "edit_password_url" or where it lives or in what scope it is defined.
Any tips on how to generate a reset-password url for a user by hand in irb?
You can do it through the console with a little bit of work. Here is how I approached it:
start rails console in your terminal:
$rails c
I looked at the devise mailer view to see what it was calling to create the reset password URL:
<p><%= link_to 'Change my password', edit_password_url(#resource, :reset_password_token => #token) %></p>
The #resource in this code is your user, and the #token is their reset password token
Find your user by id, email or whatever. Then find their reset password token:
u = User.find(1)
token = u.reset_password_token
To get access to the view you will need to create an instance of ActionView::Base
view = ActionView::Base.new
I then tried to access the url helper, but devise complains about
NoMethodError: undefined method `main_app' for #<ActionView::Base>
So I had to type a method into the console to fix that error (see this):
def main_app
Rails.application.class.routes.url_helpers
end
Depending on whether you have your mailer configured correctly in the rails console environment you are in, you may get some errors about the :host param not being set up. To avoid this you could just call _path instead of _url. Now you can call the url helper and pass the variables you set for user and token:
edit_password_path(u, :reset_password_token => token)
=> /users/password/edit?reset_password_token=123
The short answer is that you need to find their reset_password_token and append it to this URL:
http://yourdomain.com/users/password/edit?reset_password_token=<password-token-here>
In Devise 3.3, I did the following:
$ bin/rails c
> include Devise::Controllers::UrlHelpers
I recently found myself in a similar spam-folder situation.
This snippet worked nicely for me with rails 4, devise 4.3.
It also sends the reset password email, but for the sake of simplicity I think that's fine.
user = User.find(123)
token = user.send_reset_password_instructions
Rails
.application
.routes
.url_helpers
.edit_user_password_url(user, reset_password_token: token)

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.

No route matches - after login attempt - even though the route exists?

I am working on a rails application and added a simple login system according to a book.
I created the controller admin:
rails generate controller admin login logout index
It added the following routes to routes.db
get "admin/login"
get "admin/logout"
get "admin/index"
I can got to http://localhost:3000/admin/login there is no problem at all.
But when I try to login I get: No route matches "/admin/login"!
Now, the first confusing part is that the "login" method of my AdminController is not executed at all.
The second confusing part is that this code works like a charm - redirects everything to /admin/login:
def authorize
unless User.find_by_id(session[:user_id])
flash[:notice] = "you need to login"
redirect_to :controller => 'admin', :action => 'login'
end
end
Sidenotes:
I restarted the server several times.
I tried a different browser - to be sure there is no caching problem.
Try
match "/admin/login" => "admin#login"
match "/admin/logout" => "admin#logout"
match "/admin/index" => "admin#index"
(notice the leading /)
As an aside, unless you're creating a login system to learn about Rails and/or authentication, you're probably better off using something like Devise.
Following on from David Sulc's answer:
You're defining the routes as get requests, meaning to go to them you must perform a GET /admin/login request which is basically what happens when you type the URL into your address bar or follow a link that uses it.
However when you try to use these URLs in a form, the form does a POST request and because you've defined all of these as get-only requests, Rails will not be able to find a compatible route.
I definitely agree with David that you should look at an alternative system such as Devise.

Resources