has_web_fallback 0.1.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/README +43 -0
- data/lib/has_web_fallback.rb +1 -0
- data/lib/leftbrained/has_web_fallback.rb +139 -0
- data/test/has_web_fallback_test.rb +124 -0
- data/test/test_helper.rb +6 -0
- metadata +87 -0
data/README
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
HasWebFallback
|
2
|
+
=============
|
3
|
+
|
4
|
+
Extracts the common behaviour of wanting online versions of html emails.
|
5
|
+
|
6
|
+
If a mailer function is described as has_web_fallback two things will happen.
|
7
|
+
|
8
|
+
1. The template will get access to an @web_fallback_url, a url (relative to /) to the web version of the email
|
9
|
+
2. After sending, if the email has an html part, that part will be written out to the public directory.
|
10
|
+
|
11
|
+
Future Features
|
12
|
+
===============
|
13
|
+
Customizable urls/paths
|
14
|
+
|
15
|
+
Installation
|
16
|
+
============
|
17
|
+
Right now, HasWebFallback requires UUIDTools to generate the names for the emails.
|
18
|
+
config.gem 'uuidtools'
|
19
|
+
|
20
|
+
|
21
|
+
Example
|
22
|
+
=======
|
23
|
+
|
24
|
+
class ExampleMailer < ActionMailer::Base
|
25
|
+
|
26
|
+
has_web_fallback :welcome, :keep_for=>7.days
|
27
|
+
has_web_fallback :goodbye, :keep_for=>10.minutes
|
28
|
+
|
29
|
+
def welcome
|
30
|
+
#
|
31
|
+
end
|
32
|
+
|
33
|
+
def goodbye
|
34
|
+
#
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
... and in the template
|
39
|
+
|
40
|
+
<%= link_to("View this email online", @web_fallback_url, :path_only=>false) %>
|
41
|
+
|
42
|
+
|
43
|
+
Copyright (c) 2009 Gavin Montague, released under the MIT license
|
@@ -0,0 +1 @@
|
|
1
|
+
require "leftbrained/has_web_fallback"
|
@@ -0,0 +1,139 @@
|
|
1
|
+
require 'find'
|
2
|
+
require 'ftools'
|
3
|
+
require 'uuidtools'
|
4
|
+
|
5
|
+
module Leftbrained
|
6
|
+
|
7
|
+
module HasWebFallback #:nodoc:
|
8
|
+
|
9
|
+
WEB_FALLBACK_PATH = File.join(File.expand_path(RAILS_ROOT), "public")
|
10
|
+
|
11
|
+
def self.included(base)
|
12
|
+
base.extend(ClassMethods)
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
# mattr_accessor :write_web_fallbacks
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
def has_web_fallback(name, options={})
|
21
|
+
write_inheritable_attribute(:methods_with_web_fallback, {}) if methods_with_web_fallback.nil?
|
22
|
+
methods_with_web_fallback[name] = { :keep_for=>7.days }.merge(options)
|
23
|
+
|
24
|
+
class_eval <<-EOV
|
25
|
+
include Leftbrained::HasWebFallback::InstanceMethods
|
26
|
+
EOV
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_web_fallback?(method_name)
|
30
|
+
methods_with_web_fallback.include? method_name.to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
def web_fallback_uuid_for(method_name)
|
34
|
+
UUIDTools::UUID.random_create.to_s<<".html"
|
35
|
+
end
|
36
|
+
|
37
|
+
def methods_with_web_fallback
|
38
|
+
read_inheritable_attribute(:methods_with_web_fallback)
|
39
|
+
end
|
40
|
+
|
41
|
+
def cache_deadline_for_web_fallback(method_name)
|
42
|
+
Time.now - cache_period_for_method_with_web_fallback(method_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
def clear_web_fallback_caches
|
46
|
+
methods_with_web_fallback.keys.each do |meth_name|
|
47
|
+
clear_web_fallback_cache meth_name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def clear_web_fallback_cache(method_name)
|
52
|
+
web_fallback_caches_to_be_cleared(method_name).each do |file|
|
53
|
+
File.unlink(file)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def web_fallback_caches_to_be_cleared(method_name)
|
58
|
+
return [] unless has_web_fallback? method_name
|
59
|
+
cut_off_time = cache_deadline_for_web_fallback(method_name)
|
60
|
+
path = path_for_web_fallback(method_name)
|
61
|
+
deletable = []
|
62
|
+
if File.exists? path
|
63
|
+
Find.find(path) do |path|
|
64
|
+
deletable << path if (File.mtime(path) <= cut_off_time) && File.file?(path)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
deletable
|
68
|
+
end
|
69
|
+
|
70
|
+
def cache_period_for_method_with_web_fallback(method_name)
|
71
|
+
methods_with_web_fallback[method_name.to_sym][:keep_for]
|
72
|
+
end
|
73
|
+
|
74
|
+
def url_for_web_fallback(method_name)
|
75
|
+
"/system/web_fallback/#{self.to_s.underscore.gsub("_mailer", '')}/#{method_name}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def path_for_web_fallback(method_name)
|
79
|
+
File.join(WEB_FALLBACK_PATH, url_for_web_fallback(method_name))
|
80
|
+
end
|
81
|
+
|
82
|
+
end # ClassMethods
|
83
|
+
|
84
|
+
module InstanceMethods
|
85
|
+
|
86
|
+
def web_fallback_uuid
|
87
|
+
@web_fallback_uuid ||= self.class.web_fallback_uuid_for(@action_name)
|
88
|
+
end
|
89
|
+
|
90
|
+
def web_fallback_url
|
91
|
+
File.join(self.class.url_for_web_fallback(@action_name), web_fallback_uuid)
|
92
|
+
end
|
93
|
+
|
94
|
+
def web_fallback_path
|
95
|
+
File.join(self.class.path_for_web_fallback(@action_name), web_fallback_uuid)
|
96
|
+
end
|
97
|
+
|
98
|
+
def has_web_fallback?
|
99
|
+
self.class.has_web_fallback? @action_name
|
100
|
+
end
|
101
|
+
|
102
|
+
# If this method needs a fallback version set an instance variable in the template
|
103
|
+
# so we can actually tell the user what the URL will be.
|
104
|
+
def render_message(method_name, body)
|
105
|
+
body[:web_fallback_url] = web_fallback_url if has_web_fallback?
|
106
|
+
super
|
107
|
+
end
|
108
|
+
|
109
|
+
def write_web_fallback
|
110
|
+
return unless has_html_part?
|
111
|
+
FileUtils.mkdir_p(File.dirname(web_fallback_path))
|
112
|
+
f = File.new(web_fallback_path, 'w')
|
113
|
+
f.puts(html_part)
|
114
|
+
f.close
|
115
|
+
end
|
116
|
+
|
117
|
+
def has_html_part?
|
118
|
+
!!mail.parts.find{ |part| 'text/html' == part.content_type }
|
119
|
+
end
|
120
|
+
|
121
|
+
def html_part
|
122
|
+
mail.parts.find{ |part| 'text/html' == part.content_type }.body
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
# After delivering any mail, pop a version out to the file system
|
127
|
+
def deliver!(mail=@mail)
|
128
|
+
super(mail)
|
129
|
+
write_web_fallback if has_web_fallback?
|
130
|
+
mail
|
131
|
+
end
|
132
|
+
|
133
|
+
end # InstanceMethods
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
|
139
|
+
ActionMailer::Base.class_eval { include Leftbrained::HasWebFallback }
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
|
4
|
+
require "#{File.dirname(__FILE__)}/test_helper"
|
5
|
+
|
6
|
+
class HasWebFallbackTest < ActionMailer::TestCase
|
7
|
+
|
8
|
+
class ExampleMailer < ActionMailer::Base
|
9
|
+
has_web_fallback :welcome
|
10
|
+
has_web_fallback :goodbye
|
11
|
+
|
12
|
+
def welcome
|
13
|
+
recipients "no-one@example.com"
|
14
|
+
body[:name] = "Joe"
|
15
|
+
subject "welcome"
|
16
|
+
end
|
17
|
+
|
18
|
+
def goodbye
|
19
|
+
body[:name] = "Joe"
|
20
|
+
subject "goodbye"
|
21
|
+
end
|
22
|
+
|
23
|
+
def recover_password
|
24
|
+
end
|
25
|
+
end
|
26
|
+
#
|
27
|
+
|
28
|
+
context "When included, ExampleMailer" do
|
29
|
+
|
30
|
+
should "have a class attribute for tracking methods with fallbacks" do
|
31
|
+
assert ExampleMailer.inheritable_attributes.include? :methods_with_web_fallback
|
32
|
+
end
|
33
|
+
|
34
|
+
should "be able to identify if a method has a web fallback" do
|
35
|
+
assert ExampleMailer.has_web_fallback?(:welcome)
|
36
|
+
assert ExampleMailer.has_web_fallback?(:goodbye)
|
37
|
+
end
|
38
|
+
|
39
|
+
should "be able to identify a method without a web fallback" do
|
40
|
+
assert !ExampleMailer.has_web_fallback?(:recover_password)
|
41
|
+
end
|
42
|
+
|
43
|
+
should "be able to generate UUIDs" do # I know this is a lousy test, but how the hell would one test a UUID?
|
44
|
+
uuid_1 = ExampleMailer.web_fallback_uuid_for(:welcome)
|
45
|
+
uuid_2 = ExampleMailer.web_fallback_uuid_for(:welcome)
|
46
|
+
assert uuid_1 != uuid_2, "UUID clash - #{uuid_1} vs #{uuid_2}"
|
47
|
+
end
|
48
|
+
|
49
|
+
should_eventually "be able to list cached files for a given function that should be deleted" do
|
50
|
+
# I have quite literally no idea how to test this without
|
51
|
+
# mocking the living shit out File, or writing out to the file system.
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
context "A method with no fallback" do
|
57
|
+
setup do
|
58
|
+
@recover_password_mail = ExampleMailer.send(:new, 'recover_password')
|
59
|
+
end
|
60
|
+
|
61
|
+
should "know it doesn't have a fallback" do
|
62
|
+
assert !@recover_password_mail.has_web_fallback?
|
63
|
+
end
|
64
|
+
|
65
|
+
should "not write out an html version" do
|
66
|
+
@recover_password_mail.deliver!
|
67
|
+
assert !File.exists?(@recover_password_mail.web_fallback_path)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context "A method with fallback" do
|
72
|
+
setup do
|
73
|
+
@welcome_mail = ExampleMailer.send(:new, 'welcome')
|
74
|
+
end
|
75
|
+
|
76
|
+
should "know if it has a web fallback " do
|
77
|
+
assert @welcome_mail.has_web_fallback?
|
78
|
+
end
|
79
|
+
|
80
|
+
context "and an html part" do
|
81
|
+
should "have a url to the email " do
|
82
|
+
assert_match /^\/system\/web_fallback\/has_web_fallback_test\/example\/welcome\/[a-z0-9\-]+\.html$/, @welcome_mail.web_fallback_url
|
83
|
+
end
|
84
|
+
|
85
|
+
should "have an absolute_path to the file's location" do
|
86
|
+
assert @welcome_mail.web_fallback_path
|
87
|
+
end
|
88
|
+
|
89
|
+
should "know it has an html part" do
|
90
|
+
assert @welcome_mail.has_html_part?
|
91
|
+
end
|
92
|
+
|
93
|
+
should "be able to include a link in any of the body parts." do
|
94
|
+
@welcome_mail.parts.each do |part|
|
95
|
+
assert_match @welcome_mail.web_fallback_url, part.body
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
should "write out an html version" do
|
100
|
+
@welcome_mail.deliver!
|
101
|
+
assert File.exists?(@welcome_mail.web_fallback_path)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "but no html part" do
|
106
|
+
setup do
|
107
|
+
@goodbye_mail = ExampleMailer.send(:new, 'goodbye')
|
108
|
+
end
|
109
|
+
|
110
|
+
should "know it doesn't have an html part" do
|
111
|
+
assert !@goodbye_mail.has_html_part?
|
112
|
+
end
|
113
|
+
|
114
|
+
should "not write out an html version" do
|
115
|
+
@goodbye_mail.deliver!
|
116
|
+
assert !File.exists?(@goodbye_mail.web_fallback_path)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
require 'test/unit'
|
3
|
+
require File.dirname(__FILE__) + '/../lib/has_web_fallback'
|
4
|
+
|
5
|
+
# Reset out template path for AM to work with tests, apologies to any one else running tests....
|
6
|
+
ActionMailer::Base.template_root = File.join(File.dirname(__FILE__), 'app', 'views')
|
metadata
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_web_fallback
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gavin Montague
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-22 00:00:00 +01:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thoughtbot-shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: actionmailer
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: uuidtools
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
description: Adds the commonly required behaviour of caching web accessible versions of HTML emails to ActionMailer
|
46
|
+
email: gavin@leftbrained.co.uk
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README
|
53
|
+
files:
|
54
|
+
- lib/has_web_fallback.rb
|
55
|
+
- lib/leftbrained/has_web_fallback.rb
|
56
|
+
- README
|
57
|
+
has_rdoc: true
|
58
|
+
homepage: http://github.com/Govan/has_web_fallback
|
59
|
+
licenses: []
|
60
|
+
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --charset=UTF-8
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: "0"
|
71
|
+
version:
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: "0"
|
77
|
+
version:
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.5
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Adds the commonly required behaviour of caching web accessible versions of HTML emails to ActionMailer
|
85
|
+
test_files:
|
86
|
+
- test/has_web_fallback_test.rb
|
87
|
+
- test/test_helper.rb
|