mail-control 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: caaf2a07f4cecd59194d9daf3e6d0e04ee9dedf7
4
+ data.tar.gz: 4c943c088a90bd54ad7e7f3ae62eaf943325ebce
5
+ SHA512:
6
+ metadata.gz: 776a0118e140c9692d61cb15b728a04b932697ac4091606fa537ae61eb097792140ebc12c11a6044dc990ecbc80d26d5b37b7d67d2a102ec4b4798bcd2f79663
7
+ data.tar.gz: 219ff9a4a205aea11a8144ea63164b575405dae75347dfb41275d7dae6a41a123dcd5889ce71446405b97063834500ac9e3710285964a9cfae137c399d1f6344
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ services: SQLite3
3
+ gemfile:
4
+ - Gemfile
5
+ rvm:
6
+ - 1.9.3
7
+ - 2.0.0
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ ## 0.1.0
4
+
5
+ First version of gem
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development, :test do
4
+ gem "sqlite3", :platform => [:ruby, :mswin, :mingw]
5
+ gem "jdbc-sqlite3", :platform => :jruby
6
+ end
7
+
8
+ # Specify your gem's dependencies in mail-control.gemspec
9
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2013 Andreas Saebjoernsen
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
@@ -0,0 +1,145 @@
1
+ # MailControl
2
+
3
+ MailControl is a simple gem for giving both your app and users control over when, if and how emails are sent out.
4
+
5
+ This gem is inspired by Streama by Christopher Pappas.
6
+
7
+ [![Build Status](https://secure.travis-ci.org/digitalplaywright/mail-control.png)](http://travis-ci.org/digitalplaywright/mail-control) [![Dependency Status](https://gemnasium.com/digitalplaywright/mail-control.png)](https://gemnasium.com/digitalplaywright/mail-control) [![Code Climate](https://codeclimate.com/github/digitalplaywright/mail-control.png)](https://codeclimate.com/github/digitalplaywright/mail-control)
8
+
9
+
10
+ ## Install
11
+
12
+ gem install mail-control
13
+
14
+ ## Usage
15
+
16
+ ### Create migration for logged_emails and migrate the database (in your Rails project):
17
+
18
+ ```ruby
19
+ rails g mail-control:migration
20
+ rake db:migrate
21
+ ```
22
+
23
+ ### Define Queued Task
24
+
25
+ Create an LoggedEmail model and define the logged_emails and the fields you would like to cache within the queued_task.
26
+
27
+ An queued_task consists of an actor, a verb, an act_object, and a target.
28
+
29
+ ``` ruby
30
+ class LoggedEmail < ActiveRecord::Base
31
+ include MailControl::LoggedEmail
32
+
33
+ queued_task :new_enquiry do
34
+ actor :User
35
+ act_object :Article
36
+ act_target :Volume
37
+ end
38
+ end
39
+ ```
40
+
41
+ The queued_task verb is implied from the queued_task name, in the above example the verb is :new_enquiry
42
+
43
+ The act_object may be the entity performing the queued_task, or the entity on which the queued_task was performed.
44
+ e.g John(actor) shared a video(act_object)
45
+
46
+ The target is the act_object that the verb is enacted on.
47
+ e.g. Geraldine(actor) posted a photo(act_object) to her album(target)
48
+
49
+ This is based on the LoggedEmail Streams 1.0 specification (http://logged_emailstrea.ms)
50
+
51
+ ### Setup Actors
52
+
53
+ Include the Actor module in a class and override the default followers method.
54
+
55
+ ``` ruby
56
+ class User < ActiveRecord::Base
57
+ include MailControl::Actor
58
+
59
+ end
60
+ ```
61
+
62
+
63
+
64
+ ### Publishing LoggedEmail
65
+
66
+ In your controller or background worker:
67
+
68
+ ``` ruby
69
+ current_user.send_email(:new_enquiry, :act_object => @enquiry, :target => @listing)
70
+ ```
71
+
72
+ This will publish the queued_task to the mongoid act_objects returned by the #followers method in the Actor.
73
+
74
+
75
+ ## Retrieving LoggedEmail
76
+
77
+ To retrieve all queued_task for an actor
78
+
79
+ ``` ruby
80
+ current_user.logged_emails
81
+ ```
82
+
83
+ To retrieve and filter to a particular queued_task type
84
+
85
+ ``` ruby
86
+ current_user.logged_emails(:verb => 'new_enquiry')
87
+ ```
88
+
89
+ #### Options
90
+
91
+ Additional options can be required:
92
+
93
+ ``` ruby
94
+ class LoggedEmail < ActiveRecord::Base
95
+ include MailControl::LoggedEmail
96
+
97
+ queued_task :new_enquiry do
98
+ actor :User
99
+ act_object :Article
100
+ act_target :Volume
101
+ option :country
102
+ option :city
103
+ end
104
+ end
105
+ ```
106
+
107
+ The option fields are stored using the ActiveRecord 'store' feature.
108
+
109
+
110
+ #### Bond type
111
+
112
+ A verb can have one bond type. This bond type can be used to classify and quickly retrieve
113
+ queued_task feed items that belong to a particular aggregate feed, like e.g the global feed.
114
+
115
+ ``` ruby
116
+ class LoggedEmail < ActiveRecord::Base
117
+ include MailControl::LoggedEmail
118
+
119
+ queued_task :new_enquiry do
120
+ actor :User
121
+ act_object :Article
122
+ act_target :Volume
123
+ bond_type :global
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Poll for changes and empty queue
129
+
130
+ There is a poll changes interface that will group tasks by first actor and then verb.
131
+
132
+ ```ruby
133
+ LoggedEmail.poll_for_changes() do |verb, hash|
134
+ #verb is the verb the changes are group by
135
+ #hash has format: {:actor => _actor /* actor is the */,
136
+ # :act_objects => act_objects /* all objects from matching tasks */,
137
+ # :act_object_ids => act_object_ids /* all object ids from matching tasks */,
138
+ # :act_targets => act_targets /* all targets from matching tasks */ ,
139
+ # :act_target_ids => act_target_ids /* all target ids from matching tasks}
140
+ end
141
+ ```
142
+
143
+
144
+
145
+
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ task :default => :test
6
+
7
+ Rake::TestTask.new do |t|
8
+ t.libs << "test"
9
+ t.test_files = FileList['test/test*.rb', 'test/unit/*_test.rb']
10
+ end
11
+
@@ -0,0 +1,14 @@
1
+ require 'rails/generators/named_base'
2
+
3
+ module MailControl
4
+ # A generator module with LoggedEmail table schema.
5
+ module Generators
6
+ # A base module
7
+ module Base
8
+ # Get path for migration template
9
+ def source_root
10
+ @_mail_control_source_root ||= File.expand_path(File.join('../mail-control', generator_name, 'templates'), __FILE__)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ require 'generators/mail-control'
2
+ require 'rails/generators/active_record'
3
+
4
+ module MailControl
5
+ module Generators
6
+ # LoggedEmail generator that creates queued_task model file from template
7
+ class LoggedEmailGenerator < ActiveRecord::Generators::Base
8
+ extend Base
9
+
10
+ argument :name, :type => :string, :default => 'logged_email'
11
+ # Create model in project's folder
12
+ def generate_files
13
+ copy_file 'logged_email.rb', "app/models/#{name}.rb"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,5 @@
1
+ # LoggedEmail model for customisation & custom methods
2
+ class LoggedEmail < ActiveRecord::Base
3
+ include MailControl::LoggedEmail
4
+
5
+ end
@@ -0,0 +1,17 @@
1
+ require 'generators/mail-control'
2
+ require 'rails/generators/active_record'
3
+
4
+ module LiveStream
5
+ module Generators
6
+ # Migration generator that creates migration file from template
7
+ class MigrationGenerator < ActiveRecord::Generators::Base
8
+ extend Base
9
+
10
+ argument :name, :type => :string, :default => 'create_logged_emails'
11
+ # Create migration in project's folder
12
+ def generate_files
13
+ migration_template 'migration.rb', "db/migrate/#{name}"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ # Migration responsible for creating a table with logged_emails
2
+ class CreateLoggedEmails < ActiveRecord::Migration
3
+ # Create table
4
+ def self.up
5
+ create_table :logged_emails do |t|
6
+ t.belongs_to :actor, :polymorphic => true
7
+ t.belongs_to :act_object, :polymorphic => true
8
+ t.belongs_to :act_target, :polymorphic => true
9
+
10
+ t.string :verb
11
+ t.string :description
12
+ t.string :options
13
+ t.string :bond_type
14
+
15
+ t.time :send_after
16
+ t.time :send_before
17
+ t.string :state
18
+
19
+ t.string :unsubscribe_by
20
+
21
+ t.timestamps
22
+ end
23
+
24
+ add_index :logged_emails, [:verb]
25
+ add_index :logged_emails, [:actor_id, :actor_type]
26
+ add_index :logged_emails, [:act_object_id, :act_object_type]
27
+ add_index :logged_emails, [:act_target_id, :act_target_type]
28
+ end
29
+ # Drop table
30
+ def self.down
31
+ drop_table :logged_emails
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ require 'active_record'
2
+ require 'active_support'
3
+ require 'active_support/core_ext/module/delegation.rb'
4
+ require "mail-control/version"
5
+ require "mail-control/actor"
6
+ require "mail-control/logged_email"
7
+ require "mail-control/definition"
8
+ require "mail-control/definition_dsl"
9
+ require "mail-control/errors"
@@ -0,0 +1,58 @@
1
+ module MailControl
2
+
3
+ module Actor
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ cattr_accessor :queued_task_klass
8
+
9
+ has_many :logged_emails, :class_name => "LoggedEmail", :as => :actor
10
+ has_many :act_object_logged_emails, :class_name => "LoggedEmail", :as => :act_object
11
+ has_many :act_target_logged_emails, :class_name => "LoggedEmail", :as => :act_target
12
+
13
+
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def mail_control_class(klass)
19
+ self.queued_task_klass = klass.to_s
20
+ end
21
+
22
+ end
23
+
24
+
25
+ # Publishes the queued_task to the receivers
26
+ #
27
+ # @param [ Hash ] options The options to publish with.
28
+ #
29
+ # @example publish an queued_task with a act_object and act_target
30
+ # current_user.send_email(:enquiry, :act_object => @enquiry, :act_target => @listing)
31
+ #
32
+ def send_email(name, options={})
33
+ options[:send_after] = Time.now + options[:send_after] if options[:send_after].kind_of?(Fixnum)
34
+ options[:send_before] = Time.now + options[:send_before] if options[:send_before].kind_of?(Fixnum)
35
+
36
+ raise "Expected Time type. Got:" + options[:send_after].class.name unless options[:send_after].kind_of?(Time)
37
+ raise "Expected Time type. Got:" + options[:send_before].class.name unless options[:send_before].kind_of?(Time)
38
+
39
+ mail_control_class.send_email(name, {:actor => self}.merge(options))
40
+ end
41
+
42
+ def mail_control_class
43
+ @queued_task_klass ||= queued_task_klass ? queued_task_klass.classify.constantize : ::LoggedEmail
44
+ end
45
+
46
+ def actor_logged_emails(options = {})
47
+
48
+ if options.empty?
49
+ logged_emails
50
+ else
51
+ logged_emails.where(options)
52
+ end
53
+
54
+ end
55
+
56
+ end
57
+
58
+ end
@@ -0,0 +1,52 @@
1
+ module MailControl
2
+
3
+ class Definition
4
+
5
+ attr_reader :name, :actor, :act_object, :act_target, :grouped_actor, :bond_type, :options
6
+ attr_reader :unsubscribe_by
7
+
8
+
9
+ # @param dsl [MailControl::DefinitionDSL] A DSL act_object
10
+ def initialize(definition)
11
+ @name = definition[:name]
12
+ @actor = definition[:actor] || nil
13
+ @act_object = definition[:act_object] || nil
14
+ @act_target = definition[:act_target] || nil
15
+ @grouped_actor = definition[:grouped_actor] || nil
16
+ @bond_type = definition[:bond_type] || nil
17
+ @options = definition[:options] || []
18
+ @unsubscribe_by = definition[:unsubscribe_by] || nil
19
+
20
+ end
21
+
22
+ #
23
+ # Registers a new definition
24
+ #
25
+ # @param definition [Definition] The definition to register
26
+ # @return [Definition] Returns the registered definition
27
+ def self.register(definition)
28
+ return false unless definition.is_a? DefinitionDSL
29
+ definition = new(definition)
30
+ self.registered << definition
31
+ return definition || false
32
+ end
33
+
34
+ # List of registered definitions
35
+ # @return [Array<MailControl::Definition>]
36
+ def self.registered
37
+ @definitions ||= []
38
+ end
39
+
40
+ def self.find(name)
41
+ unless definition = registered.find{|definition| definition.name == name.to_sym}
42
+ raise MailControl::InvalidLoggedEmail, "Could not find a definition for `#{name}`"
43
+ else
44
+ definition
45
+ end
46
+ end
47
+
48
+
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_support'
2
+
3
+ module MailControl
4
+
5
+ class DefinitionDSL
6
+
7
+ attr_reader :attributes
8
+
9
+ def initialize(name)
10
+ @attributes = {
11
+ :name => name.to_sym,
12
+ :actor => nil,
13
+ :act_object => nil,
14
+ :act_target => nil,
15
+ :grouped_actor => nil,
16
+ :reverses => nil,
17
+ :bond_type => nil,
18
+ :unsubscribe_by => nil,
19
+ :options => nil
20
+ }
21
+ end
22
+
23
+ def add_option(option)
24
+ @attributes[:options] ||= []
25
+
26
+ @attributes[:options] << option
27
+ end
28
+
29
+ def option(text)
30
+ add_option( text )
31
+ end
32
+
33
+ delegate :[], :to => :@attributes
34
+
35
+ def self.data_methods(*args)
36
+ args.each do |method|
37
+ define_method method do |*args|
38
+
39
+ @attributes[method] = args[0]
40
+
41
+ end
42
+ end
43
+ end
44
+
45
+ data_methods :actor, :act_object, :act_target, :grouped_actor, :bond_type, :unsubscribe_by
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,44 @@
1
+ module MailControl
2
+
3
+ class MailControlError < StandardError
4
+ end
5
+
6
+ class InvalidLoggedEmail < MailControlError
7
+ end
8
+
9
+ # This error is raised when an act_object isn't defined
10
+ # as an actor, act_object or act_target
11
+ #
12
+ # Example:
13
+ #
14
+ # <tt>InvalidField.new('field_name')</tt>
15
+ class InvalidData < MailControlError
16
+ attr_reader :message
17
+
18
+ def initialize message
19
+ @message = "Invalid Data: #{message}"
20
+ end
21
+
22
+ end
23
+
24
+ # This error is raised when trying to store a field that doesn't exist
25
+ #
26
+ # Example:
27
+ #
28
+ # <tt>InvalidField.new('field_name')</tt>
29
+ class InvalidField < MailControlError
30
+ attr_reader :message
31
+
32
+ def initialize message
33
+ @message = "Invalid Field: #{message}"
34
+ end
35
+
36
+ end
37
+
38
+ class LoggedEmailNotSaved < MailControlError
39
+ end
40
+
41
+ class NoFollowersDefined < MailControlError
42
+ end
43
+
44
+ end
@@ -0,0 +1,216 @@
1
+ module MailControl
2
+ module LoggedEmail
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+
7
+ store :options
8
+
9
+ belongs_to :actor, :polymorphic => true
10
+ belongs_to :act_object, :polymorphic => true
11
+ belongs_to :act_target, :polymorphic => true
12
+
13
+ validates_presence_of :actor_id, :actor_type, :verb
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ def poll_for_changes()
19
+ #if block_given?
20
+
21
+ ::LoggedEmail.where('state = ? AND send_after <= ?', 'initial', Time.now).select(:act_target_id).uniq.each do |cur_target|
22
+ target_id = cur_target['act_target_id']
23
+
24
+ ::LoggedEmail.where('state = ? AND act_target_id = ? AND send_after <= ?', 'initial', target_id, Time.now).select(:verb).uniq.each do |verb|
25
+
26
+ changes_for_target = ::LoggedEmail.where(:act_target_id => target_id, :state => 'initial', :verb => verb['verb'].to_s)
27
+
28
+ yield verb, changes_for_target
29
+
30
+ changes_for_target.each do |cft|
31
+ cft.update_attributes(:state => 'complete')
32
+ end
33
+
34
+
35
+
36
+ end
37
+
38
+
39
+ end
40
+ end
41
+
42
+
43
+ # Defines a new LoggedEmail2 type and registers a definition
44
+ #
45
+ # @param [ String ] name The name of the queued_task
46
+ #
47
+ # @example Define a new queued_task
48
+ # queued_task(:enquiry) do
49
+ # actor :user, :cache => [:full_name]
50
+ # act_object :enquiry, :cache => [:subject]
51
+ # act_target :listing, :cache => [:title]
52
+ # end
53
+ #
54
+ # @return [Definition] Returns the registered definition
55
+ def queued_task(name, &block)
56
+ definition = MailControl::DefinitionDSL.new(name)
57
+ definition.instance_eval(&block)
58
+ MailControl::Definition.register(definition)
59
+ end
60
+
61
+ # Publishes an queued_task using an queued_task name and data
62
+ #
63
+ # @param [ String ] verb The verb of the queued_task
64
+ # @param [ Hash ] data The data to initialize the queued_task with.
65
+ #
66
+ # @return [MailControl::LoggedEmail2] An LoggedEmail instance with data
67
+ def send_email(verb, data)
68
+ new.send_email({:verb => verb}.merge(data))
69
+ end
70
+
71
+ end
72
+
73
+
74
+
75
+ # Publishes the queued_task to the receivers
76
+ #
77
+ # @param [ Hash ] options The options to send_email with.
78
+ #
79
+ def send_email(data = {})
80
+ assign_properties(data)
81
+
82
+ self
83
+ end
84
+
85
+
86
+ def refresh_data
87
+ save(:validate => false)
88
+ end
89
+
90
+ protected
91
+
92
+ def assign_properties(data = {})
93
+
94
+ self.verb = data.delete(:verb)
95
+
96
+
97
+ write_attribute(:send_after, data[:send_after])
98
+ data.delete(:send_after)
99
+
100
+ write_attribute(:send_before, data[:send_before])
101
+ data.delete(:send_before)
102
+
103
+ self.state = 'initial'
104
+
105
+ [:actor, :act_object, :act_target].each do |type|
106
+
107
+ cur_object = data[type]
108
+
109
+ unless cur_object
110
+ if definition.send(type.to_sym)
111
+ raise verb.to_json
112
+ #raise MailControl::InvalidData.new(type)
113
+ else
114
+ next
115
+
116
+ end
117
+ end
118
+
119
+ class_sym = cur_object.class.name.to_sym
120
+
121
+ raise MailControl::InvalidData.new(class_sym) unless definition.send(type) == class_sym
122
+
123
+ case type
124
+ when :actor
125
+ self.actor = cur_object
126
+ when :act_object
127
+ self.act_object = cur_object
128
+ when :act_target
129
+ self.act_target = cur_object
130
+ else
131
+ raise "unknown type"
132
+ end
133
+
134
+ data.delete(type)
135
+
136
+ end
137
+
138
+ [:grouped_actor].each do |group|
139
+
140
+
141
+ grp_object = data[group]
142
+
143
+ if grp_object == nil
144
+ if definition.send(group.to_sym)
145
+ raise verb.to_json
146
+ #raise MailControl::InvalidData.new(group)
147
+ else
148
+ next
149
+
150
+ end
151
+ end
152
+
153
+ grp_object.each do |cur_obj|
154
+ raise MailControl::InvalidData.new(class_sym) unless definition.send(group) == cur_obj.class.name.to_sym
155
+
156
+ self.grouped_actors << cur_obj
157
+
158
+ end
159
+
160
+ data.delete(group)
161
+
162
+ end
163
+
164
+ cur_bond_type = definition.send(:bond_type)
165
+
166
+ if cur_bond_type
167
+ write_attribute( :bond_type, cur_bond_type.to_s )
168
+ end
169
+
170
+ if definition.send(:unsubscribe_by)
171
+ write_attribute(:unsubscribe_by, definition.send(:unsubscribe_by))
172
+ else
173
+ raise "definition must define unsubscribe_by option"
174
+ end
175
+
176
+ if self.act_target.is_unsubscribed_to?(self)
177
+ self.state = 'unsubscribed'
178
+ end
179
+
180
+ def_options = definition.send(:options)
181
+ def_options.each do |cur_option|
182
+ cur_object = data[cur_option]
183
+
184
+ if cur_object
185
+
186
+ if cur_option == :description
187
+ self.description = cur_object
188
+ else
189
+ options[cur_option] = cur_object
190
+ end
191
+ data.delete(cur_option)
192
+
193
+ else
194
+ #all options defined must be used
195
+ raise Streama::InvalidData.new(cur_object[0])
196
+ end
197
+ end
198
+
199
+ if data.size > 0
200
+ raise "unexpected arguments: " + data.to_json
201
+ end
202
+
203
+
204
+
205
+ self.save
206
+
207
+
208
+ end
209
+
210
+ def definition
211
+ @definition ||= MailControl::Definition.find(verb)
212
+ end
213
+
214
+
215
+ end
216
+ end
@@ -0,0 +1,3 @@
1
+ module MailControl
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "mail-control/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "mail-control"
7
+ s.version = MailControl::VERSION
8
+ s.authors = ["Andreas Saebjoernsen"]
9
+ s.email = ["andreas.saebjoernsen@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{LoggedEmail Streams for rails}
12
+ s.description = %q{MailControl is a simple gem for giving both your app and users control over when, if and how emails are sent out.}
13
+ s.homepage = 'https://github.com/digitalplaywright/mail-control'
14
+
15
+ s.rubyforge_project = "mail-control"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.required_ruby_version = '>= 1.9.3'
23
+
24
+ s.add_dependency 'activerecord', '>= 3'
25
+ s.add_dependency 'activesupport', '>= 3'
26
+ s.add_dependency 'actionpack', '>= 3'
27
+ s.add_development_dependency 'rake'
28
+
29
+ end
@@ -0,0 +1,33 @@
1
+ # Migration responsible for creating a table with logged_emails
2
+ class CreateLoggedEmails < ActiveRecord::Migration
3
+ # Create table
4
+ def self.up
5
+ create_table :logged_emails do |t|
6
+ t.belongs_to :actor, :polymorphic => true
7
+ t.belongs_to :act_object, :polymorphic => true
8
+ t.belongs_to :act_target, :polymorphic => true
9
+
10
+ t.string :verb
11
+ t.string :description
12
+ t.string :options
13
+ t.string :bond_type
14
+
15
+ t.time :send_after
16
+ t.time :send_before
17
+ t.string :state
18
+
19
+ t.string :unsubscribe_by
20
+
21
+ t.timestamps
22
+ end
23
+
24
+ add_index :logged_emails, [:verb]
25
+ add_index :logged_emails, [:actor_id, :actor_type]
26
+ add_index :logged_emails, [:act_object_id, :act_object_type]
27
+ add_index :logged_emails, [:act_target_id, :act_target_type]
28
+ end
29
+ # Drop table
30
+ def self.down
31
+ drop_table :logged_emails
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ class CreateArticles < ActiveRecord::Migration
2
+ def self.up
3
+ puts "creating"
4
+ create_table :articles do |t|
5
+ t.string :name
6
+ t.boolean :published
7
+ t.belongs_to :user
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :users do |t|
4
+ t.string :name
5
+ t.timestamps
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class AddNonstandardToActivities < ActiveRecord::Migration
2
+ def change
3
+ change_table :logged_emails do |t|
4
+ t.string :nonstandard
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ class CreateVolumes < ActiveRecord::Migration
2
+ def self.up
3
+ puts "creating"
4
+ create_table :volumes do |t|
5
+ t.belongs_to :user
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,81 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup(:default, :test)
4
+
5
+ $:.unshift File.expand_path('../../lib/', __FILE__)
6
+ require 'active_support/testing/setup_and_teardown'
7
+ require 'mail-control'
8
+ require 'minitest/autorun'
9
+
10
+ #LiveStream.config # touch config to load ORM, needed in some separate tests
11
+
12
+ require 'active_record'
13
+ require 'active_record/connection_adapters/sqlite3_adapter'
14
+
15
+
16
+ class ActiveSupport::TestCase
17
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
18
+ #
19
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
20
+ # -- they do not yet inherit this setting
21
+ #fixtures :all
22
+
23
+ # Add more helper methods to be used by all tests here...
24
+ end
25
+
26
+ class LoggedEmail < ActiveRecord::Base
27
+ include MailControl::LoggedEmail
28
+
29
+ queued_task :new_enquiry do
30
+ actor :User
31
+ act_object :Article
32
+ act_target :User
33
+ #option :description
34
+ unsubscribe_by :option_1
35
+ end
36
+
37
+ queued_task :test_description do
38
+ actor :User
39
+ act_object :Article
40
+ act_target :User
41
+ option :description
42
+ unsubscribe_by :option_1
43
+ end
44
+
45
+ queued_task :test_option do
46
+ actor :User
47
+ act_object :Article
48
+ act_target :User
49
+ option :country
50
+ unsubscribe_by :option_1
51
+ end
52
+
53
+ queued_task :test_bond_type do
54
+ actor :User
55
+ act_object :Article
56
+ act_target :User
57
+ bond_type :global
58
+ unsubscribe_by :option_1
59
+ end
60
+
61
+ end
62
+
63
+ class User < ActiveRecord::Base
64
+ include MailControl::Actor
65
+
66
+ def is_unsubscribed_to?(_logged_email)
67
+ _logged_email.unsubscribe_by == 'i_am_unsubscribed'
68
+ end
69
+
70
+ end
71
+
72
+ class Article < ActiveRecord::Base
73
+
74
+ end
75
+
76
+ class Volume < ActiveRecord::Base
77
+
78
+ end
79
+
80
+ ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
81
+ ActiveRecord::Migrator.migrate(File.expand_path('../migrations', __FILE__))
@@ -0,0 +1,92 @@
1
+ class LoggedEmailTest < ActiveSupport::TestCase
2
+
3
+ def test_truth
4
+ assert true
5
+ end
6
+
7
+ def test_register_definition
8
+
9
+ @definition = LoggedEmail.queued_task(:test_queued_task) do
10
+ actor :user, :cache => [:full_name]
11
+ act_object :listing, :cache => [:title, :full_address]
12
+ act_target :listing, :cache => [:title]
13
+ end
14
+
15
+ assert @definition.is_a?(MailControl::Definition)
16
+
17
+ end
18
+
19
+ def test_publish_new_queued_task
20
+ _user = User.create()
21
+ _article = Article.create()
22
+ _user_t = User.create()
23
+
24
+ _queued_task = LoggedEmail.send_email(:new_enquiry, :send_after => Time.now, :send_before => Time.now + 1.hour, :actor => _user, :act_object => _article, :act_target => _user_t )
25
+
26
+ assert _queued_task.persisted?
27
+ #_queued_task.should be_an_instance_of LoggedEmail
28
+
29
+ end
30
+
31
+ def test_description
32
+ _user = User.create()
33
+ _article = Article.create()
34
+ _user_t = User.create()
35
+
36
+ _description = "this is a test"
37
+ _queued_task = LoggedEmail.send_email(:test_description, :send_after => Time.now, :send_before => Time.now + 1.hour, :actor => _user, :act_object => _article, :act_target => _user_t ,
38
+ :description => _description )
39
+
40
+ assert _queued_task.description == _description
41
+
42
+ end
43
+
44
+ def test_options
45
+ _user = User.create()
46
+ _article = Article.create()
47
+ _user_t = User.create()
48
+
49
+ _country = "denmark"
50
+ _queued_task = LoggedEmail.send_email(:test_option, :send_after => Time.now, :send_before => Time.now + 1.hour, :actor => _user, :act_object => _article, :act_target => _user_t ,
51
+ :country => _country )
52
+
53
+ assert _queued_task.options[:country] == _country
54
+
55
+ end
56
+
57
+ def test_bond_type
58
+ _user = User.create()
59
+ _article = Article.create()
60
+ _user_t = User.create()
61
+
62
+ _queued_task = LoggedEmail.send_email(:test_bond_type, :send_after => Time.now, :send_before => Time.now + 1.hour, :actor => _user, :act_object => _article, :act_target => _user_t )
63
+
64
+ assert _queued_task.bond_type == 'global'
65
+
66
+ end
67
+
68
+ def test_poll_changes
69
+
70
+ _user = User.create()
71
+ _article = Article.create()
72
+ _user_t = User.create()
73
+
74
+ _activity = LoggedEmail.send_email(:test_bond_type, :send_after => Time.now - 1.hour, :send_before => Time.now + 1.hour, :actor => _user, :act_object => _article, :act_target => _user_t )
75
+
76
+ assert LoggedEmail.all.size > 0
77
+
78
+
79
+ LoggedEmail.poll_for_changes() do |verb, hash|
80
+ #assert hash[:actor].id == _user.id
81
+ end
82
+
83
+ assert LoggedEmail.where('state = ? AND act_target_id = ?', 'initial', _user_t.id).size == 0
84
+
85
+ end
86
+
87
+
88
+
89
+
90
+
91
+
92
+ end
@@ -0,0 +1,46 @@
1
+ class ActorTest < ActiveSupport::TestCase
2
+
3
+
4
+ def test_send_email
5
+
6
+ _user = User.create()
7
+ _article = Article.create()
8
+ _user_t = User.create()
9
+
10
+ queued_task = _user.send_email(:new_enquiry, :send_after => Time.now, :send_before => Time.now + 1.hour, :act_object => _article, :act_target => _user_t )
11
+
12
+ assert queued_task.persisted?
13
+
14
+ end
15
+
16
+ def test_retrieves_the_stream_for_an_actor
17
+ _user = User.create()
18
+ _article = Article.create()
19
+ _user_t = User.create()
20
+
21
+ _user.send_email(:new_enquiry, :send_after => Time.now, :send_before => Time.now + 1.hour, :act_object => _article, :act_target => _user_t )
22
+ _user.send_email(:new_enquiry, :send_after => Time.now, :send_before => Time.now + 1.hour, :act_object => _article, :act_target => _user_t )
23
+
24
+ assert _user.logged_emails.size == 2
25
+
26
+ end
27
+
28
+
29
+ def test_retrieves_the_stream_for_a_particular_queued_task_type
30
+ _user = User.create()
31
+ _article = Article.create()
32
+ _user_t = User.create()
33
+
34
+ _user.send_email(:new_enquiry, :send_after => Time.now, :send_before => Time.now + 1.hour, :act_object => _article, :act_target => _user_t )
35
+ _user.send_email(:new_enquiry, :send_after => Time.now, :send_before => Time.now + 1.hour, :act_object => _article, :act_target => _user_t )
36
+ _user.send_email(:test_bond_type, :send_after => Time.now, :send_before => Time.now + 1.hour, :act_object => _article, :act_target => _user_t )
37
+
38
+ assert _user.actor_logged_emails(:verb => 'new_enquiry').size == 2
39
+ assert _user.actor_logged_emails(:bond_type => 'global').size == 1
40
+
41
+ end
42
+
43
+
44
+
45
+
46
+ end
@@ -0,0 +1,29 @@
1
+ class DefinitionDslTest < ActiveSupport::TestCase
2
+
3
+ def definition_dsl
4
+ MailControl::DefinitionDSL.new(:new_enquiry)
5
+ end
6
+
7
+ def test_initializes_with_name
8
+ assert definition_dsl.attributes[:name] == :new_enquiry
9
+ end
10
+
11
+ def test_adds_an_actor_to_the_definition
12
+ dsl = definition_dsl
13
+ dsl.actor(:User)
14
+ assert dsl.attributes[:actor] == :User
15
+ end
16
+
17
+ def test_adds_an_act_object_to_the_definition
18
+ dsl = definition_dsl
19
+ dsl.act_object(:Article)
20
+ assert dsl.attributes[:act_object] == :Article
21
+ end
22
+
23
+ def test_adds_a_act_target_to_the_definition
24
+ dsl = definition_dsl
25
+ dsl.act_target(:Volume)
26
+ assert dsl.attributes[:act_target] == :Volume
27
+ end
28
+
29
+ end
@@ -0,0 +1,51 @@
1
+ class DefinitionTest < ActiveSupport::TestCase
2
+
3
+ def definition_dsl
4
+ dsl = MailControl::DefinitionDSL.new(:new_enquiry)
5
+ dsl.actor(:User)
6
+ dsl.act_object(:Article)
7
+ dsl.act_target(:Volume)
8
+ dsl
9
+ end
10
+
11
+ def test_initialization
12
+ _definition_dsl = definition_dsl
13
+ _definition = MailControl::Definition.new(_definition_dsl)
14
+
15
+ assert _definition.actor == :User
16
+ assert _definition.act_object == :Article
17
+ assert _definition.act_target == :Volume
18
+
19
+ end
20
+
21
+ def test_register_definition_and_return_new_definition
22
+
23
+ assert MailControl::Definition.register(definition_dsl).is_a?(MailControl::Definition)
24
+
25
+ end
26
+
27
+ def test_register_invalid_definition
28
+
29
+ assert MailControl::Definition.register(false) == false
30
+
31
+ end
32
+
33
+ def test_return_registered_definitions
34
+
35
+ MailControl::Definition.register(definition_dsl)
36
+ assert MailControl::Definition.registered.size > 0
37
+
38
+ end
39
+
40
+ def test_return_definition_by_name
41
+ assert MailControl::Definition.find(:new_enquiry).name == :new_enquiry
42
+
43
+ end
44
+
45
+ def test_raise_exception_if_invalid_queued_task
46
+
47
+ assert_raises(MailControl::InvalidLoggedEmail){ MailControl::Definition.find(:unknown_queued_task) }
48
+
49
+ end
50
+
51
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mail-control
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Andreas Saebjoernsen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: actionpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: MailControl is a simple gem for giving both your app and users control
70
+ over when, if and how emails are sent out.
71
+ email:
72
+ - andreas.saebjoernsen@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .document
78
+ - .gitignore
79
+ - .rspec
80
+ - .travis.yml
81
+ - CHANGELOG.md
82
+ - Gemfile
83
+ - LICENSE.txt
84
+ - README.md
85
+ - Rakefile
86
+ - lib/generators/mail-control.rb
87
+ - lib/generators/mail-control/activity/activity_generator.rb
88
+ - lib/generators/mail-control/activity/templates/logged_email.rb
89
+ - lib/generators/mail-control/migration/migration_generator.rb
90
+ - lib/generators/mail-control/migration/templates/migration.rb
91
+ - lib/mail-control.rb
92
+ - lib/mail-control/actor.rb
93
+ - lib/mail-control/definition.rb
94
+ - lib/mail-control/definition_dsl.rb
95
+ - lib/mail-control/errors.rb
96
+ - lib/mail-control/logged_email.rb
97
+ - lib/mail-control/version.rb
98
+ - mail-control.gemspec
99
+ - test/migrations/001_create_logged_emails.rb
100
+ - test/migrations/002_create_articles.rb
101
+ - test/migrations/003_create_users.rb
102
+ - test/migrations/004_add_nonstandard_to_activities.rb
103
+ - test/migrations/005_create_volumes.rb
104
+ - test/test_helper.rb
105
+ - test/unit/activity_test.rb
106
+ - test/unit/actor_test.rb
107
+ - test/unit/definition_dsl_test.rb
108
+ - test/unit/definition_test.rb
109
+ homepage: https://github.com/digitalplaywright/mail-control
110
+ licenses: []
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - '>='
119
+ - !ruby/object:Gem::Version
120
+ version: 1.9.3
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project: mail-control
128
+ rubygems_version: 2.0.4
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: LoggedEmail Streams for rails
132
+ test_files:
133
+ - test/migrations/001_create_logged_emails.rb
134
+ - test/migrations/002_create_articles.rb
135
+ - test/migrations/003_create_users.rb
136
+ - test/migrations/004_add_nonstandard_to_activities.rb
137
+ - test/migrations/005_create_volumes.rb
138
+ - test/test_helper.rb
139
+ - test/unit/activity_test.rb
140
+ - test/unit/actor_test.rb
141
+ - test/unit/definition_dsl_test.rb
142
+ - test/unit/definition_test.rb
143
+ has_rdoc: