In actionmailer how can you override the default_url_options at runtime? - ruby-on-rails

I need to be able to generate links to sites that all run off my app but which have different domains (I'm running a whitelabel service).
The email being sent on behalf of these domains to set a different host depending on the mailing.
Normally I'd setup the host value application.rb:
config.action_mailer.default_url_options[:host] = 'myhost.com'
However, because my host varies according to the link I'm trying to do this at runtime instead.
user_mailer.rb:
Rails.configuration.action_mailer.default_url_options[:host] = new_host
mail(...)
The problem is that every time I run this it continues to use whatever's defined in application.rb. I can't seem to get the application to respect the newly defined value of default_url_optiions[:host]. What am I doing wrong?

The default_url_options method is set on ActionMailer using class_attribute which is defined in ActiveSupport core extensions for Class. According to the documentation it also provides an instance level accessor that can be overridden on a per instance basis without affecting the class level method. So you should be able to override the host setting directly for each email
class UserMailer < ActionMailer::Base
def welcome(user)
#user = user
# don't need this if you override #mail method.
self.default_url_options = default_url_options.merge(host: #user.host)
mail(to: user.email, subject: "Welcome")
end
# might be better to override mail method if you need it for all emails in
# a particular mailer
private
def mail(headers, &block)
self.default_url_options = default_url_options.merge(host: #user.host)
super
end
end
This should allow you to modify the setting at runtime; please ignore the #user.host call and replace it with however you would determine the host.

If there aren't a whole lot of views you could simply define the host on the url_for helper, and if there are too many views I'd suggest you write your own helper that wraps the url_for helper with the :host => 'mysite.com'.

Related

How can I get url of my attachment stored in active storage in my rails controller

How can I get url of my has_one model attachment stored in active storage in my rails controller. So, that I would be able to send it as full link as api in json.
So far, I have tried following methods but each of them are giving various issues:
current_user.image.service_url ---- undefined method `service_url' for #<ActiveStorage::Attached::One:0x....
Rails.application.routes.url_helpers.rails_disk_blob_path(current_user.image, only_path: true), it gives me an output like:
"/rails/blobs/%23%3CActiveStorage::Attached::One:0x007f991c7b41b8%3E"
but this is not a url, right? I am not able to hit and get image on browser.
url_for ----
undefined method `active_storage_attachment_url' for #<Api::V1::UsersController:0x007f991c1eaa98
Use the method rails_blob_path for attachements in a controller and models
For example, if you need to assign a variable (e.g. cover_url) in a controller, first you should include url_helpers and after use method rails_blob_path with some parameters. You can do the same in any model, worker etc.
Complete example below:
class ApplicationController < ActionController::Base
include Rails.application.routes.url_helpers
def index
#event = Event.first
cover_url = rails_blob_path(#event.cover, disposition: "attachment", only_path: true)
end
end
Sometimes, e.g. an API needs to return the full url with host / protocol for the clients (e.g. mobile phones etc.). In this case, passing the host parameter to all of the rails_blob_url calls is repetitive and not DRY. Even, you might need different settings in dev/test/prod to make it work.
If you are using ActionMailer and have already configuring that host/protocol in the environments/*.rb you can reuse the setting with rails_blob_url or rails_representation_url.
# in your config/environments/*.rb you might be already configuring ActionMailer
config.action_mailer.default_url_options = { host: 'www.my-site.com', protocol: 'https' }
I would recommend just calling the full Rails.application.url_helpers.rails_blob_url instead of dumping at least 50 methods into your model class (depending on your routes.rb), when you only need 2.
class MyModel < ApplicationModel
has_one_attached :logo
# linking to a variant full url
def logo_medium_variant_url
variant = logo.variant(resize: "1600x200>")
Rails.application.routes.url_helpers.rails_representation_url(
variant,
Rails.application.config.action_mailer.default_url_options
)
end
# linking to a original blob full url
def logo_blob_url
Rails.application.routes.url_helpers.rails_blob_url(
logo.blob,
Rails.application.config.action_mailer.default_url_options
)
end
end
I didn't have used rails active storage but what i have read in documentation this might help you
Try rails_blob_url(model.image)
For more http://edgeguides.rubyonrails.org/active_storage_overview.html
I was able to view the image in the browser using the following:
<%= link_to image_tag(upload.variant(resize: "100x100")), upload %>
Where upload is an attached image.

Override mail function in rails ActionMailer

I use this class for Action Mailer:
class UserMailer < ActionMailer::Base
default from: "something#example.com",
reply_to: 'whatever#example.com'
def mail_method
mail(to: 'email#example.com', subject: "SUBJECT")
end
end
So like this I got many classes and methods which send emails like this from smtp delivery method.
But now I want to perform_deliveries , i.e. send emails only on production environment not in development or test environment.
So for that I want to use my email only, which is why I need to override mail method.
Things I have tried.
-> Making a function to return email, where function name is get_right_email
class UserMailer < ActionMailer::Base
default from: "something#example.com",
reply_to: 'whatever#example.com'
def mail_method
mail(to: get_right_email('email#example.com'), subject: "SUBJECT")
end
end
And definition of get_right_email is as follows:
def get_right_email(email)
if(Rails.env=='production')
return email
else
return 'myPersonalEmail#example.com'
end
end
It would need some refactoring but it is still manageable. Will take a few hours and I can do, but is there a quicker way where I can just override mail function.
In your config > enviroments folder you should have a file for production, development and test. Here you can specify your settings for each one.
Settings in these folders overide those in config > application.rb
For example when testing I don't usually actually send the emails but I do want to be able to test the emails so I use
config.action_mailer.delivery_method = :test
This makes the emails accessible by calling ActionMailer::Base.deliveries
You can set the default from email address in these files as well using:
config.action_mailer.default_options = {
:from => "foo#bar.com"
}
Theres a gem called letter_opener managed by the fantastic Ryan Bates which instead of sending an email in development opens the email in a new tab. This makes testing out emails in development a breeze.
Letter Opener
UPDATE BELOW ------
Apologies, I didn't quite follow what you were looking for.
Rails has webhooks you can use to intercept emails and redirect them. You'll want to use an environment different than production.
The test environment is typically used for automated testing, to keep things clear you might want to consider setting up a new environment (eg: staging).
To create a new environment just create a new file in config/environments/ and give it a suitable name - eg: staging.rb
You can then call Rails.env.staging? where ever you like.
Anyway back to the main event...
To intercept the emails first create an intercept class:
class StagingEmailInterceptor
def self.delivering_email(message)
message.to = ['my#email.com']
end
end
and then create an initializer file, eg:
config/initializers/staging_email_interceptor.rb
and inside do this:
if Rails.env.staging?
ActionMailer::Base.register_interceptor(StagingEmailInterceptor)
end
That way all emails sent in the staging environment will be sent to your email.

Why do I need to require 'mail' on top of controller to use it outside ActionMailer?

I noticed that Mail gem doesn't get loaded outside ActionMailer context, but the gem is present in the proper gem group of Gemfile and then "loaded" upon Rails initialization.
To create a Mail object in a controller using Mail.new I need to put
require 'mail'
at the top of example_controller.rb, before
class ExampleController < ApplicationController
Could someone explain to me why?
Background:
I need to build an app whose functionality is primarily based on receiving emails of various types. I need to expose only one email address, where people will send all of these emails.
My mail server will pipe the raw email to a ruby script that sends the raw email as a POST request to my app.
Then, I need to identify which kind of email has arrived inferring that from its content (primarily attachments).
For example, let's have emails of types A, B and C. My email processing function will call one of three methods once it identifies the type of the email.
Where should I put the processing function?
You shouldn't be creating mail objects in your controllers. You should create a mailer and then call that mailer from your controller to send any emails you need to.
For example, say you have the following mailer in app/mailers/customer_mailer.rb:
class CustomerMailer < ActionMailer::Base
def send_reminder(user)
#user = user
mail to: user.email, subject: 'Reminder'
end
end
You can then call this from your controller as needed:
class ExampleController < ApplicationController
def some_action
#user = User.find(params[:id])
CustomerMailer.send_reminder(#user).deliver_now
end
end
This way your controller can focus on the implementations specific to controlling the request, and leave the mailer to worry about how to send email.
It's worth noting you also have access to a deliver_later method if you're using a background job runner.
Take a look at the documentation for more details: http://guides.rubyonrails.org/action_mailer_basics.html

Custom variables in Devise reset password instructions?

I need to be able to customise the rails devise mailer view for reset password instructions.
for this I need to do two things.
Specify a custom URL for the link, so that its a host/domain based on a certain business logic. This host and domain comes from the URL in the browser, i.e. the request object, when the user clicks forgot password. So I do not have the request object in delayed_job to process it as I need, hence I need to be able to do this at some point in the delayed_job that is sending the email.
Pass custom variables to the mailer view, so that I can add in various other logic for the view, hiding and showing bits as I need.
Can anyone help? I can see that you can generate the mailer views for devise, but I need to be able pass over various items to it also. Do I need to somehow override the functions myself in my User model and password controller for example?
Overriding the whole controller method and adding param in send_reset_password_instructions opts parameters will fix it.
#resource.send_reset_password_instructions(
email: #email,
provider: 'email',
redirect_url: #redirect_url,
client_config: params[:config_name],
parameter_passed: params[:parameter_passed],
)
You can access the param in the view as message['parameter_passed']
I struggled with this too before I realized that declaring custom variables BEFORE calling super will work.
def reset_password_instructions(record, token, opts={})
#custom_variable = "Greetings, world"
# your gorgeous code
mailer_object = super
mailer_object
end
so, after much ado and searching and hacking around with stuff... this is just not possible. so I ended up writing my own mailer and bypassing the devise reset password methods in the controllers, to generate my own reset token, set my variables I needed, called my usermailer.... and embedded the devise url in my mail to get it back calling devise once the password reset link was clicked, and all was fine then....
I hated having to rewrite the logic, but in the end its the quickest and cleanest solution.
One approach that nearly worked, was using a non activerecord attribute on my user model to store the bits I needed and "hacking" that into the #resource in the devise view, but it was causing some grief in devise doing so, as a result, I went with the option above...
I needed to add a source to be included into the reset password view, here's what I implemented:
class User < ActiveRecord::Base
prepend ResetPasswordWithSource
devise :recoverable
....
end
module User::ResetPasswordWithSource
def send_reset_password_instructions(source=nil)
#source = source
super()
end
def send_devise_notification(notification, *args)
args.last.merge!({ source: #source })
super
end
end
From here you can just call user.send_reset_password_instructions('special_source')
And can access in views via #options[:source] = 'special_source'
You just need to add a flag to display in the view mailer. From here you can just call a method and pass the parameter.
#user.send_reset_password_instructions("true")
Now override the method send_reset_password_instructions
def send_reset_password_instructions(option = nil)
token = set_reset_password_token
send_reset_password_instructions_notification(token, option)
token
end
def send_reset_password_instructions_notification(token, option = nil)
send_devise_notification(:reset_password_instructions, token, :option => option)
end
Then you can access the parameter by using:
message[:option]

Override ActionMailer's mail to address in development environment

In my development environment, I use a copy of the production database when testing locally. For reasons both for testing and simply for protection against sending out test/dev emails to real users, what's the best way to override the mail-to address when in development mode?
I know I can write logic in each mailer, but I have several and it would be nice to put it all in once place. Can I override the mail() method somehow to make the :to parameter always point at an email address I specify?
I use an ActionMailer interceptor so all mails sent while in development or test environments are sent to the same address. Like this:
# config/initializers/override_mail_recipient.rb
if Rails.env.development? or Rails.env.test?
class OverrideMailRecipient
def self.delivering_email(mail)
mail.to = 'to#example.com'
end
ActionMailer::Base.register_interceptor(OverrideMailRecipient)
end
end
I think the best option is to use a service like mailtrap.io: http://mailtrap.io or mailcatcher gem: https://rubygems.org/gems/mailcatcher
What I like to do is configure action mailer in the development environment to use mailtrap.
You could do a default of
class UserMailer < ActionMailer::Base
default :to=> "to#example.com"
end
And then make the address an option in the methods. That way it would default to the :to you set. Another idea I had was a bit more:
class UserMailer < ActionMailer::Base
attr_accessor :email_address
def initialize
if RAILS_ENV == "development"
#email_address = "to#example.com"
end
end
end
That would require you to set a new address in your code but it would be overwritten each time in Development.

Resources