ActionMailer::Preview callbacks? - ruby

ActionMailer::Base provides call backs like ActionController::Base does which allows for common code to be called before a mailer function/action. It seems this is not supported for ActionMailer::Preview classes but could be useful. Is there a module I can include that would give me this functionality so I can call something like:
class MailerPreview < ActionMailer::Preview
include SomeCallBackModule
before_action :create_records
def mailer_a
# Don't need this anymore because create_records gets called
# #user = FactoryBot.create(:some_factory)
# #widget = FactoryBot.create(:another_factory, user: #user)
Mailer.mailer_a(#user, #widget)
end
def mailer_b
# Don't need this anymore because create_records gets called
# #user = FactoryBot.create(:some_factory)
# #widget = FactoryBot.create(:another_factory, user: #user)
Mailer.mailer_b(#user, #widget)
def
private
def create_records
#user = FactoryBot.create(:some_factory)
#widget = FactoryBot.create(:another_factory, user: #user)
end
end

ActionMailer::Preview indeed doesn't support callbacks out of the box.
In your example, I would suggest calling a simple private method directly instead of with a callback:
class MailerPreview < ActionMailer::Preview
def mailer_a
Mailer.mailer_a(user)
end
def mailer_b
Mailer.mailer_b(user)
def
private
def user
#user ||= User.first
end
end

Related

Rails before_action for ActionMailer that would use mailer arguments

Suppose I have a mailer that sends different emails, but is expected to be called with the same parameters. I want to process those parameters for all mailer actions. So, calling a before_action that would read the parameters sent to the mailer method
/mailers/my_mailer.rb
class MyMailer < ApplicationMailer
before_filter do |c|
# c.prepare_mail # Will fail, because I need to pass `same_param` arguments
# # I want to send the original arguments
# c.prepare_mail(same_param) # How do I get `same_param` here ?
end
def action1(same_param)
# email view is going to use #to, #from, #context
method_only_specific_to_action1
end
def action2(same_param)
# email view is going to use #to, #from, #context
method_only_specific_to_action2
end
private
def prepare_mail(same_params)
#to = same_params.recipient
#from = same_params.initiator
#context = same_params.context
end
end
Then in my controller/service I do somewhere
MyMailer.actionx(*mailer_params).deliver_now
How can I access the same_param arguments list inside the before_action block ?
EDIT :
I want to refactor from
/mailers/my_mailer.rb
class MyMailer < ApplicationMailer
def action1(same_param)
#to = same_params.recipient
#from = same_params.initiator
#context = same_params.context
method_only_specific_to_action1
end
def action2(same_param)
#to = same_params.recipient
#from = same_params.initiator
#context = same_params.context
method_only_specific_to_action2
end
def actionx
...
end
end
And this refactoring
/mailers/my_mailer.rb
class MyMailer < ApplicationMailer
def action1(same_param)
prepare_mail(same_params)
method_only_specific_to_action1
end
def action2(same_param)
prepare_mail(same_params)
method_only_specific_to_action2
end
def actionx
...
end
private
def prepare_mail(same_params)
#to = same_params.recipient
#from = same_params.initiator
#context = same_params.context
end
end
Feels non-optimal (prepare_mail(same_params) duplicated in every action)
Hence what was suggested above
ActionMailer uses the AbstractController::Callbacks module. I tried it and it seems to work for me.
The code
class MyMailer < ApplicationMailer
def process_action(*args)
# process the args here
puts args
super
end
def some_mail(*args)
end
end
MyMailer.some_mail(1, 2) #=> prints ['some_mail', 1, 2]
The documentation
UPDATE
If you're using Rails 5.1, you can have a look at ActionMailer::Parameterized
Solution1:
I would suggest you use this if you do not care about the format
MyMailer.generic("actionx", *mailer_params).deliver_now
def generic(actiontype, *mailer_params)
# custom logic goes here to construct the to, from, etc.,
# new_options from custom logic
self.send(actiontype, new_options)
end
alternative solution below using method_missing from the parent controller
Its not right to put your logic there, but if you still want to do it, you can use the method_missing to put your logic there and skip the action1 and action2 methods.
Original method_missing from action_mailer which can be used as a reference:
def method_missing(method_name, *args)
if action_methods.include?(method_name.to_s)
MessageDelivery.new(self, method_name, *args)
else
super
end
end
https://github.com/rails/rails/blob/c8a18aaf44a84f1ef0df007aa3f8446752dc327d/actionmailer/lib/action_mailer/base.rb#L561-L567
Based on Sairam's answer I though of the following but that feels a bit weird, can it not be done with before_action callback ??
class MyMailer < ApplicationMailer
# Simulation of before_action callback that would support passing the *args to the callback method
def self.method_missing(method_name, *args)
method_name = :"#{method_name.to_s}_headers_prefilled"
if action_methods.include?(method_name)
mailer = MyMailer.generic(*args) # The before_action callback that passes *args
mailer.send(method_name, *args) # The action itself
else
super
end
end
def generic(*mailer_params)
# custom logic goes here to construct the headers to, from, etc.,
end
def action1_headers_prefilled(mailer_params)
# Logic only relevant for action1
end
Also I lose all the cool stuff from before_action (passing an only or except array, etc.)

Improving module structure

I created small API library, everything worked fine, until I realized, that I need multiple configurations.
It looks like this:
module Store
class Api
class << self
attr_accessor :configuration
def configure
self.configuration ||= Configuration.new
yield configuration
end
def get options = {}
url = "#{configuration.domain}/#{options[:resource]}"
# ResClient url ...
end
end
end
class Configuration
attr_accessor :domain
def initialize options = {}
#domain = options[:domain]
end
end
class Product
def self.get
sleep 5
Api.get resource: 'products'
end
end
end
When I run it simultaneously, it override module configuration.
Thread.new do
10.times do
Store::Api.configure do |c|
c.domain = "apple2.com"
end
p Store::Product.get
end
end
10.times do
Store::Api.configure do |c|
c.domain = "apple.com"
end
p Store::Product.get
end
I can't figure out, how make this module better. Thanks for your advise
Well, if you don't want multiple threads to compete for one resource, you shouldn't have made it a singleton. Try moving object configuration from class to its instances, then instantiate and configure them separately.
There is more to refactor here, but this solves your problem:
module Store
class API
attr_reader :domain
def initialize(options = {})
#domain = options[:domain]
end
def products
sleep 5
get resource: 'products'
end
private
def get(options = {})
url = "#{configuration.domain}/#{options[:resource]}"
# ResClient url ...
end
end
end
Thread.new do
10.times do
api = Store::API.new(domain: 'apple2.com')
p api.products
end
end
10.times do
api = Store::API.new(domain: 'apple.com')
p api.products
end

implement a rails before_filter in ruby without rails

I am using g a logger in all of my classes.
I want each msg to begin with class name and method name like so:
Class_name::Method_name
this is what i'm doing now :
class FOO
def initialize
end
def bar
msg_prefix = "#{self.class}::#{__method__}"
... some code ...
#logeer = "#{msg_prefix} msg ..."
end
def bar2
msg_prefix = "#{self.class}::#{__method__}"
... some code 2 ...
#logeer = "#{msg_prefix} msg2 ..."
end
end
i want to use a before_filter like in rails to prevent duplicity,
I am using sinatra but the classes are plain old ruby 1.9.3 classes
ideas??
You can get a callback on any method being created with Module#method_added, alias the old method, then define a new method that calls the before_filter method first. Here's my (extremely) rough first concept:
module Filter
def before_filter name
##filter = name
end
def method_added name
return if #filtering # Don't add filters to original_ methods
return if ##filter == name # Don't filter filters
return if name == :initialize
#filtering = true
alias_method :"original_#{name}", name
define_method name do |*args|
self.send ##filter, name
self.send :"original_#{name}", *args
end
#filtering = false
end
end
class FilterTest
extend Filter
before_filter :prepare_logs
def baz
puts "#{#msg_prefix} message goes here"
end
def prepare_logs name
#msg_prefix = "#{self.class}::#{name}"
end
end
ft = FilterTest.new
ft.baz
By using __method__ like you were in create_prefix, you'll get the name of the filter method, not the original method, so you have to pass the method name in. There might be other solutions to make that a bit cleaner.
You can use ActiveModel::Callbacks to get before_filter-like behaviour in plain Ruby classes (though perhaps in your case it's overkill for just executing one line):
require 'active_model'
class FOO
extend ActiveModel::Callbacks
define_model_callbacks :baz, only: :before
before_baz :create_prefix
def initialize
end
def bar
run_callbacks :baz do
... some code ...
#logeer = "#{#msg_prefix} msg ..."
end
end
def bar2
run_callbacks :baz do
... some code 2 ...
#logeer = "#{#msg_prefix} msg2 ..."
end
end
private
def create_prefix
#msg_prefix = "#{self.class}::#{__method__}"
end
end

Ruby instance variable access

Just curious what the best practice is for accessing an instance variable from within a class assuming attr_accessor is set.
class Test
attr_accessor :user
def initializer(user)
#user = user
end
def foo
#user
end
end
or
class Test
attr_accessor :user
def initializer(user)
#user = user
end
def foo
self.user
end
end
So by instance variable (#user) or getter method (Test#user)?
Getter method, because it's easier to refactor. Say you want to update a time stamp at the point of access.
class Test
def user
#user.last_read = Time.now
#user
end
end
And all your references to user are updated with the new logic. Not so easy if your references are to #user.

Setting new class variables inside a module

I have a plugin I have been working on that adds publishing to ActiveRecord classes. I extend my classes with my publisher like so:
class Note < ActiveRecord::Base
# ...
publishable :related_attributes => [:taggings]
end
My publisher is structured like:
module Publisher
def self.included(base)
base.send(:extend, ClassMethods)
##publishing_options = [] # does not seem to be available
end
module ClassMethods
def publishable options={}
include InstanceMethods
##publishing_options = options
# does not work as class_variable_set is a private method
# self.class_variable_set(:##publishing_options, options)
# results in: uninitialized class variable ##publishing_options in Publisher::ClassMethods
puts "##publishing_options: #{##publishing_options.inspect}"
# ...
end
# ...
end
module InstanceMethods
# results in: uninitialized class variable ##publishing_options in Publisher::InstanceMethods
def related_attributes
##publishing_options[:related_attributes]
end
# ...
end
end
Any ideas on how to pass options to publishable and have them available as a class variable?
I am presuming that you want one set of publishing_options per class. In that case you just want to prefix your variable with a single #. Remember the class itself is an instance of the class Class so when you are in the context of a class method you actually want to set an instance variable on your class. Something like the following:
module Publishable
module ClassMethods
def publishable(options)
#publishing_options = options
end
def publishing_options
#publishing_options
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
Then if ActiveRecord::Base is extended as follows:
ActiveRecord::Base.send :include, Publishable
You can do:
class Note < ActiveRecord::Base
publishable :related_attributes => [:taggings]
end
class Other < ActiveRecord::Base
publishable :related_attributes => [:other]
end
Note.publishing_options
=> {:related_attributes=>[:taggings]}
Other.publishing_options
=> {:related_attributes=>[:other]}

Resources