nisetegami 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.md +5 -0
- data/Rakefile +35 -0
- data/app/models/nisetegami/template.rb +157 -0
- data/lib/generators/nisetegami/templates/migrations/001_create_nisetegami_templates.rb +35 -0
- data/lib/generators/nisetegami/templates_generator.rb +27 -0
- data/lib/nisetegami/action_mailer_extensions.rb +77 -0
- data/lib/nisetegami/ar_template_resolver.rb +24 -0
- data/lib/nisetegami/asset_provider.rb +14 -0
- data/lib/nisetegami/engine.rb +18 -0
- data/lib/nisetegami/exceptions.rb +27 -0
- data/lib/nisetegami/liquid_template_handler.rb +34 -0
- data/lib/nisetegami/mapping.rb +73 -0
- data/lib/nisetegami/test/factories.rb +23 -0
- data/lib/nisetegami/test/test_mailer.rb +7 -0
- data/lib/nisetegami/test.rb +1 -0
- data/lib/nisetegami/utils.rb +15 -0
- data/lib/nisetegami/version.rb +3 -0
- data/lib/nisetegami.rb +50 -0
- data/lib/tasks/nisetegami_tasks.rake +4 -0
- metadata +212 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
|
8
|
+
begin
|
9
|
+
require 'rdoc/task'
|
10
|
+
rescue LoadError
|
11
|
+
require 'rdoc/rdoc'
|
12
|
+
require 'rake/rdoctask'
|
13
|
+
RDoc::Task = Rake::RDocTask
|
14
|
+
end
|
15
|
+
|
16
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'Nisetegami'
|
19
|
+
rdoc.options << '--line-numbers'
|
20
|
+
rdoc.rdoc_files.include('README.rdoc')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
23
|
+
|
24
|
+
Bundler::GemHelper.install_tasks
|
25
|
+
|
26
|
+
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
27
|
+
load 'rails/tasks/engine.rake'
|
28
|
+
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Default: Run all specs.'
|
35
|
+
task default: :spec
|
@@ -0,0 +1,157 @@
|
|
1
|
+
class Nisetegami::Template < ActiveRecord::Base
|
2
|
+
|
3
|
+
self.table_name = :nisetegami_templates
|
4
|
+
|
5
|
+
## constants
|
6
|
+
|
7
|
+
CONTENT = [:subject, :body_html, :body_text]
|
8
|
+
HEADERS = [:cc, :bcc, :reply_to, :from]
|
9
|
+
|
10
|
+
## scopes
|
11
|
+
|
12
|
+
scope :by_mailer, ->(mailer){ where(mailer: mailer.respond_to?(:name) ? mailer.name : mailer) }
|
13
|
+
scope :by_action, ->(action){ where(action: action) }
|
14
|
+
scope :by_layout, ->(layout){ where(layout: layout) }
|
15
|
+
scope :enabled, where(enabled: true)
|
16
|
+
scope :disabled, where(enabled: false)
|
17
|
+
scope :html, where(only_text: false)
|
18
|
+
scope :text, where(only_text: true)
|
19
|
+
|
20
|
+
## validations
|
21
|
+
email_re = '[-a-z0-9_+\.]+@([-a-z0-9]+\.)+[a-z0-9]{2,}'
|
22
|
+
address_re = "(?:[^<@]+\\s+<#{email_re}>|#{email_re})"
|
23
|
+
addresses_re = /^#{address_re}(?:\s*,\s*#{address_re})*$/i
|
24
|
+
validates_format_of :from, :reply_to, :cc, :bcc, with: addresses_re, allow_blank: true
|
25
|
+
validate :subject, :body_text, :layout_text, :name, presence: true
|
26
|
+
validate :body_html, :layout_html, presence: true, unless: :only_text?
|
27
|
+
validate :check_template_syntax
|
28
|
+
validate :check_mailer
|
29
|
+
|
30
|
+
## class-methods
|
31
|
+
|
32
|
+
# Accepts an instance of the ActionMailer::Base subclass
|
33
|
+
# and attempts to find associated template using
|
34
|
+
# name and action_name of the mailer.
|
35
|
+
def self.lookup(mailer_instance)
|
36
|
+
relation.where(mailer: mailer_instance.class.name, action: mailer_instance.action_name).first
|
37
|
+
end
|
38
|
+
|
39
|
+
public
|
40
|
+
|
41
|
+
def layout_html_path
|
42
|
+
layout_html && File.join(Nisetegami.layouts_path, layout_html)
|
43
|
+
end
|
44
|
+
|
45
|
+
def layout_text_path
|
46
|
+
layout_text && File.join(Nisetegami.layouts_path, layout_text)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns a mapping for +self+, as a Hash.
|
50
|
+
def mapping
|
51
|
+
@_mapping ||= ::Nisetegami.mapping.lookup(mailer, action)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns a list of variables for +self+,
|
55
|
+
# using a Nisetegami::Mapping definitions.
|
56
|
+
def variable_names
|
57
|
+
@_variable_names ||= mapping.keys
|
58
|
+
end
|
59
|
+
|
60
|
+
def variable_mapping
|
61
|
+
@_variable_mapping ||= begin
|
62
|
+
mapping.each_with_object([]) do |(variable, thing), array|
|
63
|
+
# @todo: store variable_mapping in mapping instead of classes [?]
|
64
|
+
meths = Nisetegami::Utils.liquid_methods_for(Nisetegami.cast[thing])
|
65
|
+
array << (meths.blank? ? variable.to_sym : { variable.to_sym => meths })
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def mailer
|
71
|
+
@_mailer ||= self[:mailer].constantize
|
72
|
+
rescue NameError
|
73
|
+
end
|
74
|
+
|
75
|
+
CONTENT.each do |attribute|
|
76
|
+
define_method "#{attribute}=" do |raw|
|
77
|
+
self[attribute] = raw.blank? ? nil : raw
|
78
|
+
instance_variable_set("@#{attribute}_template", nil)
|
79
|
+
end
|
80
|
+
|
81
|
+
define_method "render_#{attribute}" do |variables|
|
82
|
+
return nil unless self[attribute]
|
83
|
+
# do not parse invalid templates
|
84
|
+
if instance_variable_get("@#{attribute}_template").nil?
|
85
|
+
instance_variable_set("@#{attribute}_template", try_parse_template(attribute))
|
86
|
+
end
|
87
|
+
instance_variable_get("@#{attribute}_template") \
|
88
|
+
.try(:render, variables.stringify_keys.select { |k, v| k.in?(variable_names) })
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def headers(variables = {})
|
93
|
+
options = { subject: render_subject(variables) }
|
94
|
+
options[:css] = associated_stylesheets unless only_text?
|
95
|
+
HEADERS.each { |header| options[header] = self[header] unless self[header].blank? }
|
96
|
+
options
|
97
|
+
end
|
98
|
+
|
99
|
+
def message(recipient, variables)
|
100
|
+
return nil unless valid?
|
101
|
+
mailer.testing(action, recipient, variables)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def associated_stylesheets
|
107
|
+
@@asset_provider ||= Nisetegami::AssetProvider.new(Roadie.current_provider.prefix)
|
108
|
+
stylesheets = [:default]
|
109
|
+
stylesheets << layout_html unless layout_html.blank?
|
110
|
+
stylesheets.map! { |s| File.join('nisetegami', s.to_s) }
|
111
|
+
stylesheets.select { |s| @@asset_provider.exists?(s) }
|
112
|
+
end
|
113
|
+
|
114
|
+
def check_mailer
|
115
|
+
errors.add(:mailer, :undefined) if !mailer || !mailer.ancestors.include?(ActionMailer::Base)
|
116
|
+
errors.add(:action, :undefined) if mailer && !mailer.action_methods.include?(action.to_s)
|
117
|
+
end
|
118
|
+
|
119
|
+
def check_template_syntax
|
120
|
+
CONTENT.each { |attribute| try_parse_template(attribute) }
|
121
|
+
end
|
122
|
+
|
123
|
+
def try_parse_template(attribute)
|
124
|
+
Liquid::Template.parse(self[attribute])
|
125
|
+
rescue Liquid::SyntaxError => error
|
126
|
+
errors.add(attribute, :liquid_syntax_error, message: error.message)
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
# == Schema Information
|
133
|
+
#
|
134
|
+
# Table name: nisetegami_templates
|
135
|
+
#
|
136
|
+
# id :integer not null, primary key
|
137
|
+
# mailer :string(255)
|
138
|
+
# action :string(255)
|
139
|
+
# name :string(255)
|
140
|
+
# from :string(255)
|
141
|
+
# cc :string(255)
|
142
|
+
# bcc :string(255)
|
143
|
+
# reply_to :string(255)
|
144
|
+
# subject :text
|
145
|
+
# body_text :text
|
146
|
+
# body_html :text
|
147
|
+
# layout_text :string(255)
|
148
|
+
# layout_html :string(255)
|
149
|
+
# enabled :boolean default(FALSE), not null
|
150
|
+
# created_at :datetime not null
|
151
|
+
# updated_at :datetime not null
|
152
|
+
#
|
153
|
+
# Indexes
|
154
|
+
#
|
155
|
+
# index_nisetegami_templates_on_mailer_and_action (mailer,action) UNIQUE
|
156
|
+
#
|
157
|
+
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class CreateNisetegamiTemplates < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :nisetegami_templates do |t|
|
4
|
+
|
5
|
+
## mapping
|
6
|
+
t.string :mailer # EG: "TestMailer"
|
7
|
+
t.string :action # EG: "simple"
|
8
|
+
t.string :name # results in "TestMailer#simple"
|
9
|
+
|
10
|
+
## headers
|
11
|
+
t.string :from
|
12
|
+
t.string :cc
|
13
|
+
t.string :bcc
|
14
|
+
t.string :reply_to
|
15
|
+
|
16
|
+
## content
|
17
|
+
t.text :subject
|
18
|
+
t.text :body_text
|
19
|
+
t.text :body_html
|
20
|
+
t.text :layout_text
|
21
|
+
t.text :layout_html
|
22
|
+
|
23
|
+
## controls
|
24
|
+
t.boolean :enabled, null: false, default: false
|
25
|
+
t.boolean :only_text, null: false, default: true
|
26
|
+
|
27
|
+
t.timestamps
|
28
|
+
end
|
29
|
+
add_index :nisetegami_templates, [:mailer, :action], unique: true
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.down
|
33
|
+
drop_table :nisetegami_templates
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
class Nisetegami::TemplatesGenerator < Rails::Generators::Base
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
source_root File.expand_path(File.join(File.dirname(__FILE__), 'templates'))
|
8
|
+
|
9
|
+
def self.next_migration_number(dirname)
|
10
|
+
if ActiveRecord::Base.timestamped_migrations
|
11
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
12
|
+
else
|
13
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def create_migrations
|
18
|
+
Dir[File.join(self.class.source_root, 'migrations/*.rb')].sort.each do |template|
|
19
|
+
basename = File.basename(template).gsub(/^\d+_/, '')
|
20
|
+
if self.class.migration_exists?(File.join(Rails.root, 'db/migrate'), basename.sub(/\.rb$/, '')).blank?
|
21
|
+
migration_template template, "db/migrate/#{basename}"
|
22
|
+
sleep 1
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Nisetegami
|
2
|
+
module ActionMailerExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
append_view_path ARTemplateResolver.instance
|
7
|
+
append_view_path "#{Rails.root}/app/views/nisetegami"
|
8
|
+
alias_method_chain :collect_responses_and_parts_order, :required_parts_order
|
9
|
+
alias_method_chain :mail, :template
|
10
|
+
alias_method_chain :render, :layout
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def testing(action, recipient, variables)
|
15
|
+
new.testing do |instance|
|
16
|
+
instance.action_name = action.to_s
|
17
|
+
variables.each { |k, v| instance.instance_variable_set("@#{k}", v) }
|
18
|
+
instance.mail to: recipient
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def render_with_layout(*args, &block)
|
24
|
+
options = args.first
|
25
|
+
if !options[:layout] && @_ar_template
|
26
|
+
format = options[:template].identifier.split('.').last
|
27
|
+
options[:layout] = @_ar_template.send("layout_#{format}")
|
28
|
+
end
|
29
|
+
render_without_layout(*args, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def mail_with_template(headers = {}, &block)
|
33
|
+
# maybe there is better way? - do not want to do query it (despite cache is used) for second time
|
34
|
+
@_ar_template = Template.by_mailer(self.class).by_action(action_name).first
|
35
|
+
if @_ar_template
|
36
|
+
self.action_name ||= @_ar_template.action.to_s
|
37
|
+
# think about this ugly shit
|
38
|
+
vars = instance_variables.inject({}) do |hsh, var|
|
39
|
+
unless var =~ /@_/
|
40
|
+
template_var = instance_variable_get(var)
|
41
|
+
template_var = begin
|
42
|
+
instance_variable_set(var, "#{template_var.class}Decorator".constantize.decorate(template_var))
|
43
|
+
rescue NameError
|
44
|
+
template_var
|
45
|
+
end
|
46
|
+
hsh[var[1..-1].to_sym] = template_var
|
47
|
+
end
|
48
|
+
hsh
|
49
|
+
end
|
50
|
+
headers.reverse_merge!(@_ar_template.headers(vars))
|
51
|
+
end
|
52
|
+
mail_without_template(headers, &block).tap do |m|
|
53
|
+
m.perform_deliveries = testing? || !@_ar_template || @_ar_template.enabled
|
54
|
+
m.body = nil unless m.perform_deliveries # better to remove corresponding specs??
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def testing(&block)
|
59
|
+
@_testing_was, @_testing = @_testing, true
|
60
|
+
yield self
|
61
|
+
ensure
|
62
|
+
@_testing = @_testing_was
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def testing?
|
68
|
+
!!@_testing
|
69
|
+
end
|
70
|
+
|
71
|
+
def collect_responses_and_parts_order_with_required_parts_order(headers)
|
72
|
+
responses, parts_order = collect_responses_and_parts_order_without_required_parts_order(headers)
|
73
|
+
[responses, parts_order || responses.map { |r| r[:content_type] }]
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
class Nisetegami::ARTemplateResolver < ActionView::Resolver
|
4
|
+
|
5
|
+
include Singleton
|
6
|
+
|
7
|
+
def find_templates(name, prefix, partial, details)
|
8
|
+
Nisetegami::Template.by_mailer(prefix.classify).by_action(name).map do |record|
|
9
|
+
formats = [:text]
|
10
|
+
formats.unshift(:html) unless record.only_text?
|
11
|
+
formats.map do |format|
|
12
|
+
source = record.send("body_#{format}")
|
13
|
+
identifier = "Nisetegami::Template.#{record.id}.#{format}"
|
14
|
+
handler = ActionView::Template.registered_template_handler(:liquid)
|
15
|
+
details = {
|
16
|
+
format: Mime[format],
|
17
|
+
virtual_path: "#{record.mailer.to_s.underscore}/#{record.action}",
|
18
|
+
updated_at: record.updated_at
|
19
|
+
}
|
20
|
+
ActionView::Template.new(source, identifier, handler, details)
|
21
|
+
end
|
22
|
+
end.flatten
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'nisetegami'
|
2
|
+
|
3
|
+
module Nisetegami
|
4
|
+
class Engine < Rails::Engine
|
5
|
+
isolate_namespace Nisetegami
|
6
|
+
|
7
|
+
initializer 'nisetegami.action_mailer' do |app|
|
8
|
+
ActiveSupport.on_load :action_mailer do
|
9
|
+
include Nisetegami::ActionMailerExtensions
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
config.before_initialize do
|
14
|
+
Nisetegami.class_variable_set(:@@layouts_path, Rails.root.join('app/views/nisetegami/layouts').to_s)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Nisetegami
|
2
|
+
module Exceptions
|
3
|
+
|
4
|
+
class Error < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class UnknownActionError < Error
|
8
|
+
def initialize(mailer, action)
|
9
|
+
@action = "#{mailer}##{action}"
|
10
|
+
end
|
11
|
+
def message
|
12
|
+
"Action #{@action} does not exist. You should define it first."
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class MissingTemplateError < Error
|
17
|
+
def initialize(mailer_instance)
|
18
|
+
@action = "#{mailer_instance.class.name}##{mailer_instance.action_name}"
|
19
|
+
end
|
20
|
+
def message
|
21
|
+
"Template for #{@action} not found. " \
|
22
|
+
"You should define it manually or use Nisetegami.populate!"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class ActionView::Template::Handlers::Liquid
|
2
|
+
|
3
|
+
def self.call(template)
|
4
|
+
"ActionView::Template::Handlers::Liquid.new(self).render(#{template.source.inspect}, local_assigns)"
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(view)
|
8
|
+
@view = view
|
9
|
+
end
|
10
|
+
|
11
|
+
def render(template, local_assigns = {})
|
12
|
+
assigns = @view.assigns
|
13
|
+
assigns["content_for_layout"] = @view.content_for(:layout) if @view.content_for?(:layout)
|
14
|
+
assigns.merge!(local_assigns.stringify_keys)
|
15
|
+
|
16
|
+
controller = @view.controller
|
17
|
+
filters = if controller.respond_to?(:liquid_filters, true)
|
18
|
+
controller.send(:liquid_filters)
|
19
|
+
elsif controller.respond_to?(:master_helper_module)
|
20
|
+
[controller.master_helper_module]
|
21
|
+
else
|
22
|
+
[controller._helpers]
|
23
|
+
end
|
24
|
+
|
25
|
+
liquid = Liquid::Template.parse(template)
|
26
|
+
liquid.render(assigns, filters: filters, registers: { action_view: @view, controller: @view.controller })
|
27
|
+
end
|
28
|
+
|
29
|
+
def compilable?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
ActionView::Template.register_template_handler :liquid, ActionView::Template::Handlers::Liquid
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Nisetegami
|
2
|
+
class Mapping
|
3
|
+
attr_reader :mailers
|
4
|
+
|
5
|
+
SEPARATOR = "#"
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@mailers = Set.new
|
9
|
+
@mapping = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def register(mailer, action, *locals)
|
13
|
+
mailer = mailer.to_s.classify
|
14
|
+
unless action_exists?(mailer, action)
|
15
|
+
raise Exceptions::UnknownActionError.new(mailer, action)
|
16
|
+
end
|
17
|
+
@mapping["#{mailer}#{SEPARATOR}#{action}"] = prepare_locals(*locals)
|
18
|
+
@mailers << mailer
|
19
|
+
end
|
20
|
+
|
21
|
+
def lookup(mailer, action)
|
22
|
+
@mapping["#{mailer}#{SEPARATOR}#{action}"] || {}
|
23
|
+
end
|
24
|
+
|
25
|
+
def populate!
|
26
|
+
Nisetegami::Template.find_each { |template| template.destroy unless action_exists?(template[:mailer], template.action) }
|
27
|
+
|
28
|
+
@mapping.each do |route, locals|
|
29
|
+
mailer, action = route.split(SEPARATOR, 2)
|
30
|
+
next unless can_populate?(mailer, action)
|
31
|
+
variables = expand_locals(*locals).map{ |v| "{{ #{v} }}" }.join(", ")
|
32
|
+
|
33
|
+
Nisetegami::Template.create!(
|
34
|
+
subject: "Subject",
|
35
|
+
body_text: "You can use following variables: #{variables}. Format: text.",
|
36
|
+
body_html: "You can use following variables: #{variables}. Format: html.",
|
37
|
+
name: route,
|
38
|
+
mailer: mailer,
|
39
|
+
action: action,
|
40
|
+
enabled: false
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def expand_locals(*locals)
|
48
|
+
locals.each_with_object([]) do |(v, thing), array|
|
49
|
+
meths = Nisetegami::Utils.liquid_methods_for(Nisetegami.cast[thing])
|
50
|
+
array << (meths.blank? ? v : meths.map { |m| "#{v}.#{m}" })
|
51
|
+
end.flatten
|
52
|
+
end
|
53
|
+
|
54
|
+
def prepare_locals(*locals)
|
55
|
+
options = locals.extract_options!
|
56
|
+
# need to call tableize because of the bug in classify method ('master_class'.classify => 'MasterClas')
|
57
|
+
locals.each_with_object({}) { |v, hsh| hsh[v.to_s] = v.to_s.tableize.classify }.merge(options.stringify_keys)
|
58
|
+
end
|
59
|
+
|
60
|
+
def can_populate?(mailer, action)
|
61
|
+
action_exists?(mailer, action) && !Nisetegami::Template.where(mailer: mailer, action: action).exists?
|
62
|
+
end
|
63
|
+
|
64
|
+
def action_exists?(mailer, action)
|
65
|
+
klass = mailer.constantize
|
66
|
+
klass.ancestors.include?(ActionMailer::Base) &&
|
67
|
+
klass.action_methods.include?(action.to_s)
|
68
|
+
rescue NameError
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
if defined?(::FactoryGirl)
|
2
|
+
|
3
|
+
FactoryGirl.define do
|
4
|
+
factory :simple_nisetegami_template, class: 'Nisetegami::Template' do
|
5
|
+
|
6
|
+
mailer "Nisetegami::TestMailer"
|
7
|
+
sequence(:action) { |n| "simple_#{n}" }
|
8
|
+
|
9
|
+
subject "The quick brown {{ fox }} jumps over the lazy {{ dog }}."
|
10
|
+
body_text "The quick brown {{ fox }} jumps over the lazy {{ dog }}."
|
11
|
+
body_html "The quick brown {{ fox }} jumps over the lazy {{ dog }}."
|
12
|
+
layout_text "default"
|
13
|
+
layout_html "default"
|
14
|
+
|
15
|
+
after(:build) do |template|
|
16
|
+
Nisetegami::TestMailer.action_methods << template.action.to_sym
|
17
|
+
Nisetegami::TestMailer.send :alias_method, template.action.to_sym, :simple
|
18
|
+
Nisetegami.configure { |c| c.register template[:mailer], template.action, fox: 'String', dog: 'String' }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Dir[File.expand_path('../test/*.rb', __FILE__)].each { |f| require f }
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Nisetegami
|
2
|
+
module Utils
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def liquid_methods_for(thing)
|
6
|
+
return nil unless thing.const_defined?('LiquidDropClass')
|
7
|
+
thing::LiquidDropClass.public_instance_methods(false)
|
8
|
+
end
|
9
|
+
|
10
|
+
def filenames(path, format)
|
11
|
+
Dir[File.join(path, "*.#{format}.*")].map { |file| File.basename(file).sub(/\.#{format}#{File.extname(file)}$/, "") }.uniq
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
data/lib/nisetegami.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'roadie'
|
2
|
+
require 'liquid'
|
3
|
+
|
4
|
+
require 'nisetegami/action_mailer_extensions'
|
5
|
+
require 'nisetegami/liquid_template_handler'
|
6
|
+
require 'nisetegami/ar_template_resolver'
|
7
|
+
require 'nisetegami/asset_provider'
|
8
|
+
require 'nisetegami/exceptions'
|
9
|
+
require 'nisetegami/mapping'
|
10
|
+
require 'nisetegami/utils'
|
11
|
+
require 'nisetegami/engine'
|
12
|
+
|
13
|
+
module Nisetegami
|
14
|
+
|
15
|
+
mattr_reader :layouts_path
|
16
|
+
|
17
|
+
mattr_reader :mapping
|
18
|
+
@@mapping = Nisetegami::Mapping.new
|
19
|
+
|
20
|
+
def self.configure
|
21
|
+
yield self
|
22
|
+
end
|
23
|
+
|
24
|
+
# optional block to cast a thing (String, Symbol)
|
25
|
+
# into a class with liquid_methods
|
26
|
+
def self.cast(&block)
|
27
|
+
@@cast ||= ->(thing){ defined?(thing) == 'constant' ? thing : thing.to_s.constantize }
|
28
|
+
block_given? ? @@cast = block : @@cast
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.register(*args)
|
32
|
+
@@mapping.register(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.populate!
|
36
|
+
@@mapping.populate!
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
%w(text html).each do |format|
|
41
|
+
define_method("#{format}_layouts") do
|
42
|
+
# cache result in class variable unless in development
|
43
|
+
instance_variable_get(:"@#{format}_layouts") || Nisetegami::Utils.filenames(Nisetegami.layouts_path, format).tap do |layouts|
|
44
|
+
instance_variable_set(:"@#{format}_layouts", layouts) unless Rails.env.development?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
metadata
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: nisetegami
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Denis Lifanov
|
9
|
+
- Dmitry Afanasyev
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-11-05 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rails
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.2'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '3.2'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: roadie
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 2.3.1
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 2.3.1
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: liquid
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: sqlite3
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: rails
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ~>
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '3.2'
|
87
|
+
type: :development
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ~>
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '3.2'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: rspec
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
none: false
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rspec-rails
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
- !ruby/object:Gem::Dependency
|
128
|
+
name: factory_girl_rails
|
129
|
+
requirement: !ruby/object:Gem::Requirement
|
130
|
+
none: false
|
131
|
+
requirements:
|
132
|
+
- - ! '>='
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '0'
|
135
|
+
type: :development
|
136
|
+
prerelease: false
|
137
|
+
version_requirements: !ruby/object:Gem::Requirement
|
138
|
+
none: false
|
139
|
+
requirements:
|
140
|
+
- - ! '>='
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: '0'
|
143
|
+
- !ruby/object:Gem::Dependency
|
144
|
+
name: pry-debugger
|
145
|
+
requirement: !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ! '>='
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
type: :development
|
152
|
+
prerelease: false
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
154
|
+
none: false
|
155
|
+
requirements:
|
156
|
+
- - ! '>='
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
description: ActiveRecord/Liquid-based mail templates, incl. CSS-processing.
|
160
|
+
email:
|
161
|
+
- inadsence@gmail.com
|
162
|
+
- dimarzio1986@gmail.com
|
163
|
+
executables: []
|
164
|
+
extensions: []
|
165
|
+
extra_rdoc_files: []
|
166
|
+
files:
|
167
|
+
- app/models/nisetegami/template.rb
|
168
|
+
- lib/generators/nisetegami/templates/migrations/001_create_nisetegami_templates.rb
|
169
|
+
- lib/generators/nisetegami/templates_generator.rb
|
170
|
+
- lib/nisetegami/action_mailer_extensions.rb
|
171
|
+
- lib/nisetegami/ar_template_resolver.rb
|
172
|
+
- lib/nisetegami/asset_provider.rb
|
173
|
+
- lib/nisetegami/engine.rb
|
174
|
+
- lib/nisetegami/exceptions.rb
|
175
|
+
- lib/nisetegami/liquid_template_handler.rb
|
176
|
+
- lib/nisetegami/mapping.rb
|
177
|
+
- lib/nisetegami/test/factories.rb
|
178
|
+
- lib/nisetegami/test/test_mailer.rb
|
179
|
+
- lib/nisetegami/test.rb
|
180
|
+
- lib/nisetegami/utils.rb
|
181
|
+
- lib/nisetegami/version.rb
|
182
|
+
- lib/nisetegami.rb
|
183
|
+
- lib/tasks/nisetegami_tasks.rake
|
184
|
+
- MIT-LICENSE
|
185
|
+
- Rakefile
|
186
|
+
- README.md
|
187
|
+
homepage: https://github.com/icrowley/nisetegami
|
188
|
+
licenses: []
|
189
|
+
post_install_message:
|
190
|
+
rdoc_options: []
|
191
|
+
require_paths:
|
192
|
+
- lib
|
193
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
194
|
+
none: false
|
195
|
+
requirements:
|
196
|
+
- - ! '>='
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: '0'
|
199
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
200
|
+
none: false
|
201
|
+
requirements:
|
202
|
+
- - ! '>='
|
203
|
+
- !ruby/object:Gem::Version
|
204
|
+
version: '0'
|
205
|
+
requirements: []
|
206
|
+
rubyforge_project:
|
207
|
+
rubygems_version: 1.8.24
|
208
|
+
signing_key:
|
209
|
+
specification_version: 3
|
210
|
+
summary: ActiveRecord/Liquid-based mail templates, incl. CSS-processing.
|
211
|
+
test_files: []
|
212
|
+
has_rdoc:
|