/
view_hooks.rb
101 lines (90 loc) · 3.01 KB
/
view_hooks.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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],
breadcrumb: args[:show],
breadcrumb_content: args[:show],
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 "<b>ID</b>: #{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
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