newsletterable 0.0.1
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 +7 -0
- data/Gemfile +8 -0
- data/Guardfile +14 -0
- data/LICENSE.txt +22 -0
- data/README.md +101 -0
- data/Rakefile +2 -0
- data/lib/newsletterable.rb +58 -0
- data/lib/newsletterable/configuration.rb +12 -0
- data/lib/newsletterable/errors.rb +3 -0
- data/lib/newsletterable/model.rb +67 -0
- data/lib/newsletterable/orm_adapters/active_model.rb +21 -0
- data/lib/newsletterable/orm_adapters/active_record.rb +8 -0
- data/lib/newsletterable/orm_adapters/adapter.rb +20 -0
- data/lib/newsletterable/orm_adapters/mongoid.rb +22 -0
- data/lib/newsletterable/railtie.rb +17 -0
- data/lib/newsletterable/service.rb +61 -0
- data/lib/newsletterable/subscriber.rb +84 -0
- data/lib/newsletterable/version.rb +3 -0
- data/lib/newsletterable/worker.rb +74 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 25e624e8412c36225fbf5abeca8dc9719a9f862b
|
4
|
+
data.tar.gz: b189d86b3c90404fcb0a23598ab2deb8b7192589
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: adb51010471ea00c312979fcee7b589973110ce232922b1873fef2dcb826b6f4eb3e1c8c057a40ddb1459f864517c7b582ae3dfccbbef80736363ef211ef586c
|
7
|
+
data.tar.gz: b46d19892e07d937806632b92d6b54fe827f285b82774f3ddff756a89bc765cdced395e7362ad96e49925c8ab92424e9930508309272edba23527791f3e1b1a8
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
2
|
+
require "guard/rspec/dsl"
|
3
|
+
dsl = Guard::RSpec::Dsl.new(self)
|
4
|
+
|
5
|
+
# RSpec files
|
6
|
+
rspec = dsl.rspec
|
7
|
+
watch(rspec.spec_helper) { rspec.spec_dir }
|
8
|
+
watch(rspec.spec_support) { rspec.spec_dir }
|
9
|
+
watch(rspec.spec_files)
|
10
|
+
|
11
|
+
# Ruby files
|
12
|
+
ruby = dsl.ruby
|
13
|
+
dsl.watch_spec_files_for(ruby.lib_files)
|
14
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Stan Bondi
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
# Newsletterable
|
2
|
+
|
3
|
+
Keep mailing list subscriptions in sync with your
|
4
|
+
ActiveRecord or Mongoid (ActiveModel) models.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'newsletterable'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install newsletterable
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
Your initializer:
|
25
|
+
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
Newsletterable.configure do |config|
|
29
|
+
# Mailchimp API key
|
30
|
+
config.api_key = ENV['api_key']
|
31
|
+
|
32
|
+
# Default lists
|
33
|
+
config.default_lists = {
|
34
|
+
newsletter: '123abc',
|
35
|
+
everyone: 'cba321'
|
36
|
+
}
|
37
|
+
|
38
|
+
# Specify worker class or custom callback for enqueuing to your worker.
|
39
|
+
# Workers usually include Newsletterable::Worker (defines the perform method)
|
40
|
+
config.worker = MyWorker
|
41
|
+
# or define a callable which will enqueue anyway you like, or synchonously
|
42
|
+
config.worker = lambda { |id| SleepyWorker.perform_async('NewsletterTask', id.to_s) }
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
Your subscriber model e.g User class
|
47
|
+
Must respond_to `email` (TODO: make this configurable)
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class User < ActiveRecord::Base
|
51
|
+
include Newsletterable::Subscriber
|
52
|
+
|
53
|
+
has_many :newsletter_subscriptions, as: :subscribable
|
54
|
+
|
55
|
+
# Boolean fields `newsletter` and `everyone`
|
56
|
+
subscribe_on :newsletter
|
57
|
+
# will unsubscribe if the subscriber class is destroyed
|
58
|
+
subscribe_on :everyone, unsubscribe_on_destroy: true
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
Your Subscription model (each row is a subscription to a mailchimp list)
|
63
|
+
Here using mongoid so that we can easier see the required methods/fields.
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
class NewsletterSubscription
|
67
|
+
include Mongoid::Document
|
68
|
+
include Newsletterable::Model
|
69
|
+
|
70
|
+
# This must be an association to the subscribable object
|
71
|
+
belongs_to :subscribable, polymorphic: true
|
72
|
+
|
73
|
+
# Used to store old email addresses when object is destroyed or email updated
|
74
|
+
field :old_email, type: String
|
75
|
+
# List id of suibscription
|
76
|
+
field :list, type: String
|
77
|
+
# Either pending subscribed unsubscribed out_of_date or error
|
78
|
+
field :state, type: String
|
79
|
+
end
|
80
|
+
|
81
|
+
```
|
82
|
+
|
83
|
+
Add a worker (using anything you like)
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
|
87
|
+
class MyWorker
|
88
|
+
include Sidekiq::Worker
|
89
|
+
include Newsletterable::Worker
|
90
|
+
|
91
|
+
sidekiq_options ....
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
## Contributing
|
96
|
+
|
97
|
+
1. Fork it ( https://github.com/fixate/newsletterable/fork )
|
98
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
99
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
100
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
101
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
require "active_model"
|
2
|
+
require "active_support/concern"
|
3
|
+
require "active_support/core_ext"
|
4
|
+
|
5
|
+
require "newsletterable/version"
|
6
|
+
require "newsletterable/configuration"
|
7
|
+
require "newsletterable/errors"
|
8
|
+
require "newsletterable/model"
|
9
|
+
require "newsletterable/service"
|
10
|
+
require "newsletterable/subscriber"
|
11
|
+
require "newsletterable/worker"
|
12
|
+
require "newsletterable/orm_adapters/adapter"
|
13
|
+
require "newsletterable/railtie" if defined?(Rails)
|
14
|
+
|
15
|
+
module Newsletterable
|
16
|
+
extend self
|
17
|
+
@@lock = Mutex.new
|
18
|
+
|
19
|
+
def configuration
|
20
|
+
@@lock.synchronize do
|
21
|
+
@@configuration ||= Configuration.new(defaults)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def configure
|
26
|
+
yield configuration
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.eager_load!
|
30
|
+
# No autoloads
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def defaults
|
36
|
+
{
|
37
|
+
worker: Newsletterable::Worker,
|
38
|
+
list_resolver: default_list_resolver
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def default_list_resolver
|
43
|
+
proc do |subscriber, list_name, lists|
|
44
|
+
result = case lists
|
45
|
+
when String, Array
|
46
|
+
lists
|
47
|
+
when Hash
|
48
|
+
lists[list_name]
|
49
|
+
else
|
50
|
+
if lists.respond_to?(:call)
|
51
|
+
lists.call(subscriber)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
Array.wrap(result)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Newsletterable
|
2
|
+
module Model
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
Newsletterable.configuration.subscriptions_model = self
|
7
|
+
|
8
|
+
define_newsletterable_state_methods(self)
|
9
|
+
define_newsletterable_callbacks(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def email
|
13
|
+
subscribable.nil? ? self.old_email : subscribable.email
|
14
|
+
end
|
15
|
+
|
16
|
+
def enqueue_subscription
|
17
|
+
worker = Newsletterable.configuration.worker
|
18
|
+
case worker
|
19
|
+
when Symbol, String
|
20
|
+
# Get worker from method in model
|
21
|
+
self.send(worker)
|
22
|
+
else
|
23
|
+
# Get worker from lambda function
|
24
|
+
if worker.respond_to?(:call)
|
25
|
+
worker.call(self.id)
|
26
|
+
# worker is already a Worker class, call perform_async
|
27
|
+
# TODO: should we require perform_async, make it configurable or make this an adapter
|
28
|
+
elsif worker.respond_to?(:perform_async)
|
29
|
+
worker.perform_async(self.id)
|
30
|
+
else
|
31
|
+
raise ConfigurationError, "config.worker is nil or invalid."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
def define_newsletterable_callbacks(klass)
|
38
|
+
klass.class_eval do
|
39
|
+
opts =[:enqueue_subscription, {
|
40
|
+
if: proc { |n| !n.subscribed? },
|
41
|
+
on: [:create, :update]
|
42
|
+
}]
|
43
|
+
|
44
|
+
if respond_to?(:after_commit)
|
45
|
+
after_commit(*opts)
|
46
|
+
else
|
47
|
+
after_save(*opts)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def define_newsletterable_state_methods(klass)
|
53
|
+
%i{ pending subscribed unsubscribed out_of_date error }.each do |state|
|
54
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
55
|
+
def #{state}!
|
56
|
+
self.state = '#{state}'
|
57
|
+
end
|
58
|
+
|
59
|
+
def #{state}?
|
60
|
+
self.state.to_s == '#{state}'
|
61
|
+
end
|
62
|
+
RUBY
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Newsletterable
|
2
|
+
module OrmAdapters
|
3
|
+
class ActiveModel < Adapter
|
4
|
+
def query_subscription(query, initialize = false)
|
5
|
+
scope = subscriptions_model.where(query)
|
6
|
+
|
7
|
+
if initialize
|
8
|
+
scope.first_or_initialize do |s|
|
9
|
+
s.pending!
|
10
|
+
end
|
11
|
+
else
|
12
|
+
scope.first
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def save(record)
|
17
|
+
record.save!
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Newsletterable
|
2
|
+
module OrmAdapters
|
3
|
+
class Adapter
|
4
|
+
|
5
|
+
def self.factory(name)
|
6
|
+
klass = "Newsletterable::OrmAdapters::#{name.to_s.camelize}"
|
7
|
+
unless (Module.const_get(klass).is_a?(Class) rescue false)
|
8
|
+
require "newsletterable/orm_adapters/#{name}"
|
9
|
+
end
|
10
|
+
klass.constantize.new
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def subscriptions_model
|
16
|
+
Newsletterable.configuration.subscriptions_model
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'newsletterable/orm_adapters/active_model'
|
2
|
+
|
3
|
+
module Newsletterable
|
4
|
+
module OrmAdapters
|
5
|
+
class Mongoid < ActiveModel
|
6
|
+
def query_subscription(query, initialize = true)
|
7
|
+
# Work around for https://github.com/mongoid/mongoid/issues/4101
|
8
|
+
if initialize && query[:subscribable]
|
9
|
+
subscribable = query.delete(:subscribable)
|
10
|
+
query[:subscribable_id] = subscribable.id
|
11
|
+
query[:subscribable_type] = subscribable.class.to_s
|
12
|
+
end
|
13
|
+
|
14
|
+
super(query, initialize)
|
15
|
+
end
|
16
|
+
|
17
|
+
def save(record)
|
18
|
+
record.with(safe: true).save!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "rails"
|
2
|
+
|
3
|
+
module MandrillQueue
|
4
|
+
class Railtie < Rails::Railtie # :nodoc:
|
5
|
+
config.eager_load_namespaces << Newsletterable
|
6
|
+
|
7
|
+
initializer "newsletterable.initialize" do |app|
|
8
|
+
Newsletterable.configure do |config|
|
9
|
+
if defined?(ActiveRecord)
|
10
|
+
config.orm_adapter = :active_record
|
11
|
+
elsif defined?(Mongoid)
|
12
|
+
config.orm_adapter = :mongoid
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Newsletterable
|
2
|
+
class Service
|
3
|
+
attr_reader :subscribable
|
4
|
+
|
5
|
+
def initialize(subscribable)
|
6
|
+
@subscribable = subscribable
|
7
|
+
end
|
8
|
+
|
9
|
+
def subscribe(lists)
|
10
|
+
lists.each do |list|
|
11
|
+
subscription = orm_adapter.query_subscription({
|
12
|
+
subscribable: subscribable,
|
13
|
+
list: list
|
14
|
+
}, true)
|
15
|
+
|
16
|
+
if subscription.unsubscribed? || subscription.error?
|
17
|
+
subscription.pending!
|
18
|
+
end
|
19
|
+
|
20
|
+
orm_adapter.save(subscription) unless subscription.subscribed?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def unsubscribe(lists)
|
25
|
+
lists.each do |list|
|
26
|
+
subscription = orm_adapter.query_subscription(
|
27
|
+
subscribable: subscribable,
|
28
|
+
list: list
|
29
|
+
)
|
30
|
+
unless subscription.nil?
|
31
|
+
subscription.old_email = subscribable.email
|
32
|
+
subscription.unsubscribed!
|
33
|
+
orm_adapter.save(subscription)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(lists, old_email)
|
39
|
+
lists.each do |list|
|
40
|
+
subscription = orm_adapter.query_subscription(
|
41
|
+
subscribable: subscribable,
|
42
|
+
list: list
|
43
|
+
)
|
44
|
+
|
45
|
+
if subscription.nil?
|
46
|
+
raise RuntimeError, "No subscription to update for subscribable #{subscribable.id}."
|
47
|
+
end
|
48
|
+
|
49
|
+
subscription.out_of_date!
|
50
|
+
subscription.old_email = old_email
|
51
|
+
orm_adapter.save(subscription)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def orm_adapter
|
58
|
+
@orm_adapter ||= OrmAdapters::Adapter.factory(Newsletterable.configuration.orm_adapter)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module Newsletterable
|
2
|
+
module Subscriber
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
class_eval %(
|
7
|
+
class << self; attr_accessor :__newsletterable_options; end
|
8
|
+
)
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscription_service
|
12
|
+
@subscription_service ||= Service.new(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def manage_subscription(list_name)
|
16
|
+
field = field_for(list_name)
|
17
|
+
list_ids = resolve_list(list_name)
|
18
|
+
|
19
|
+
if send(field)
|
20
|
+
subscription_service.subscribe(list_ids)
|
21
|
+
else
|
22
|
+
subscription_service.unsubscribe(list_ids)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve_list(list_name)
|
27
|
+
options = options_for(list_name)
|
28
|
+
lists = options[:lists] || Newsletterable.configuration.default_lists
|
29
|
+
|
30
|
+
resolver = options[:resolver] || Newsletterable.configuration.list_resolver
|
31
|
+
|
32
|
+
if resolver.is_a?(Symbol)
|
33
|
+
self.send(resolver, list_name, lists)
|
34
|
+
elsif resolver.respond_to?(:call)
|
35
|
+
resolver.call(self, list_name, lists)
|
36
|
+
else
|
37
|
+
raise ConfigurationError, 'Newsletterable resolver should be a Symbol or callable.'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_subscription(list_name)
|
42
|
+
old_email = email_was
|
43
|
+
|
44
|
+
yield
|
45
|
+
|
46
|
+
list_ids = resolve_list(list_name)
|
47
|
+
subscription_service.update(list_ids, old_email)
|
48
|
+
end
|
49
|
+
|
50
|
+
def remove_subscription(list_name)
|
51
|
+
list_ids = resolve_list(list_name)
|
52
|
+
subscription_service.unsubscribe(list_ids)
|
53
|
+
end
|
54
|
+
|
55
|
+
def options_for(list_name)
|
56
|
+
self.class.__newsletterable_options[list_name]
|
57
|
+
end
|
58
|
+
|
59
|
+
def field_for(list_name)
|
60
|
+
if options = options_for(list_name)
|
61
|
+
options[:field]
|
62
|
+
else
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module ClassMethods
|
68
|
+
|
69
|
+
def subscribe_on(list_name, options = {})
|
70
|
+
field = options[:field] || list_name
|
71
|
+
|
72
|
+
self.__newsletterable_options ||= {}
|
73
|
+
self.__newsletterable_options[list_name] = options.merge(field: field)
|
74
|
+
|
75
|
+
after_save -> { manage_subscription(list_name) }, if: :"#{field}_changed?"
|
76
|
+
around_update -> { update_subscription(list_name) }, if: -> { send(:"#{field}?") && email_changed? }
|
77
|
+
unless options[:unsubscribe_on_destroy]
|
78
|
+
before_destroy -> { remove_subscription(list_name) }, if: :"#{field}?"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'mailchimp'
|
2
|
+
|
3
|
+
module Newsletterable
|
4
|
+
module Worker
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def perform(subscription_id)
|
8
|
+
@subscription = orm_adapter.query_subscription(id: subscription_id)
|
9
|
+
|
10
|
+
if @subscription.nil?
|
11
|
+
logger.warn "No subscription with id #{subscription_id} exists. Ignoring."
|
12
|
+
else
|
13
|
+
process
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def process
|
18
|
+
logger.info "Processing mailing list subscription for #{@subscription.email}..."
|
19
|
+
logger.debug "Status: [#{@subscription.state}]"
|
20
|
+
|
21
|
+
case @subscription.state.to_sym
|
22
|
+
when :pending || :error
|
23
|
+
logger.info "Subscribing #{@subscription.email} to '#{@subscription.list}' list"
|
24
|
+
logger.info "Trying to subscribe after previous error!" if @subscription.error?
|
25
|
+
add(@subscription)
|
26
|
+
@subscription.subscribed!
|
27
|
+
@subscription.save!
|
28
|
+
when :unsubscribed
|
29
|
+
logger.info "Unsubscribing #{@subscription.email} from '#{@subscription.list}' list"
|
30
|
+
remove(@subscription)
|
31
|
+
@subscription.destroy!
|
32
|
+
when :out_of_date
|
33
|
+
logger.info "Updating subscription for #{@subscription.email}"
|
34
|
+
update(@subscription)
|
35
|
+
@subscription.old_email = nil
|
36
|
+
@subscription.subscribed!
|
37
|
+
@subscription.save!
|
38
|
+
end
|
39
|
+
|
40
|
+
rescue Mailchimp::Error => ex
|
41
|
+
logger.error "#{@subscription.email}: #{ex.message || 'Unknown error.'}"
|
42
|
+
@subscription.error!
|
43
|
+
@subscription.save
|
44
|
+
raise
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def orm_adapter
|
50
|
+
@orm_adapter ||= OrmAdapters::Adapter.factory(Newsletterable.configuration.orm_adapter)
|
51
|
+
end
|
52
|
+
|
53
|
+
def mailchimp
|
54
|
+
@mailchimp_api ||= Mailchimp::API.new(Newsletterable.configuration.api_key)
|
55
|
+
end
|
56
|
+
|
57
|
+
def add(subscription)
|
58
|
+
mailchimp.lists.subscribe(subscription.list, { email: subscription.email }, {}, 'html', false)
|
59
|
+
rescue Mailchimp::ListAlreadySubscribedError
|
60
|
+
logger.warn "#{subscription.email} already subscribed to '#{subscription.list}'."
|
61
|
+
end
|
62
|
+
|
63
|
+
def remove(subscription)
|
64
|
+
mailchimp.lists.unsubscribe(subscription.list, { email: subscription.email })
|
65
|
+
rescue Mailchimp::ListNotSubscribedError
|
66
|
+
logger.warn "#{subscription.email} is not subscribed to '#{subscription.list}'."
|
67
|
+
end
|
68
|
+
|
69
|
+
def update(subscription)
|
70
|
+
raise UpdateError, "old_email required to update subscription for subscription #{subscription.id}." if subscription.old_email.nil?
|
71
|
+
mailchimp.lists.subscribe(subscription.list, { email: subscription.old_email }, { 'new-email' => subscription.email }, 'html', true, true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: newsletterable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Stan Bondi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-06-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activemodel
|
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: mailchimp-api
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.7'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.7'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: guard-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: temping
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- stan@fixate.it
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- Gemfile
|
119
|
+
- Guardfile
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- lib/newsletterable.rb
|
124
|
+
- lib/newsletterable/configuration.rb
|
125
|
+
- lib/newsletterable/errors.rb
|
126
|
+
- lib/newsletterable/model.rb
|
127
|
+
- lib/newsletterable/orm_adapters/active_model.rb
|
128
|
+
- lib/newsletterable/orm_adapters/active_record.rb
|
129
|
+
- lib/newsletterable/orm_adapters/adapter.rb
|
130
|
+
- lib/newsletterable/orm_adapters/mongoid.rb
|
131
|
+
- lib/newsletterable/railtie.rb
|
132
|
+
- lib/newsletterable/service.rb
|
133
|
+
- lib/newsletterable/subscriber.rb
|
134
|
+
- lib/newsletterable/version.rb
|
135
|
+
- lib/newsletterable/worker.rb
|
136
|
+
homepage: ''
|
137
|
+
licenses:
|
138
|
+
- MIT
|
139
|
+
metadata: {}
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 2.4.5
|
157
|
+
signing_key:
|
158
|
+
specification_version: 4
|
159
|
+
summary: Keep mailing list subscriptions in sync with your models
|
160
|
+
test_files: []
|