nisetegami 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|