module RailsEmailPreview
# Add hooks before, after, or replacing a UI element
class ViewHooks
args = {
index: [:list, :previews].freeze,
show: [:mail, :preview].freeze
}
# All valid hooks and their argument names
SCHEMA = {
list: args[:index],
headers_and_nav: args[:show],
headers: args[:show],
headers_content: args[:show],
nav: args[:show],
nav_i18n: args[:show],
nav_format: args[:show],
nav_send: args[:show],
email_body: args[:show],
}
POSITIONS = [:before, :replace, :after].freeze
def initialize
@hooks = Hooks.new
end
# @param [Symbol] id
# @param [:before, :replace, :after] pos
# @example
# view_hooks.add_render :list, :before, partial: 'shared/hello'
def add_render(id, pos, *render_args)
render_args = render_args.dup
render_opts = render_args.extract_options!.dup
add id, pos do |locals = {}|
render *render_args, render_opts.merge(
locals: (render_opts[:locals] || {}).merge(locals))
end
end
# @param [Symbol] id
# @param [:before, :replace, :after] pos
# @example
# view_hooks.add :headers_content, :after do |mail:, preview:|
# raw "ID: #{h mail.header['X-APP-EMAIL-ID']}"
# end
def add(id, pos, &block)
@hooks[id][pos] << block
end
def render(hook_id, locals, template, &content)
at = @hooks[hook_id]
validate_locals! hook_id, locals
parts = [
render_providers(at[:before], locals, template),
if at[:replace].present?
render_providers(at[:replace], locals, template)
else
template.capture { content.call(locals) }
end,
render_providers(at[:after], locals, template)
]
template.safe_join(parts, '')
end
# Find available hooks with simple static analysis of the gem's views
# @return [Array] return hook names
private
def render_providers(providers, locals, template)
template.safe_join providers.map { |provider| template.instance_exec(locals, &provider) }, ''
end
def validate_locals!(hook_id, locals)
if locals.keys.sort != SCHEMA[hook_id].sort
raise ArgumentError.new("Invalid arguments #{locals.keys}. Valid: #{SCHEMA[hook_id]}")
end
end
class Hooks < DelegateClass(Hash)
def initialize
super Hash.new { |h, id|
validate_id! id
h[id] = Hash.new { |hh, pos|
validate_pos! pos
hh[pos] = []
}
}
end
private
def validate_id!(id)
raise ArgumentError.new('hook id must be a symbol') unless Symbol === id
raise ArgumentError.new("Invalid hook #{id}. Valid: #{SCHEMA.keys * ', '}.") unless SCHEMA.key?(id)
end
def validate_pos!(pos)
raise ArgumentError.new("Invalid position #{pos}. Valid: #{POSITIONS * ', '}") unless POSITIONS.include?(pos)
end
end
end
end