chimp_sync 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f370c639ff0268c115d070640aeca13cde8d3424
4
+ data.tar.gz: 7db796dba59c8a71b78905b90846d645ab854a69
5
+ SHA512:
6
+ metadata.gz: 8b8fdb8e644e548af516b831fe11b11999beb580a6f1c6ed34fabc00af29faf9cb77b258bb95bd6772c56f14bb5c629b07606a509d15797746f3ee591c22d74a
7
+ data.tar.gz: 397dd376d3aa07036b47e8ca2a065e05b9fe616815318c2a0cc21e94dd0e046d2ef060ce6f0e2d4771f0c1827cd340f31491025010f42c5bc30f36689aa2439e
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Pat Allan and Inspire9
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,50 @@
1
+ # ChimpSync
2
+
3
+ ChimpSync keeps MailChimp list subscription details synced with your own data.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'chimp_sync', '~> 0.0.1'
11
+ ```
12
+
13
+ This gem uses Panthoot for receiving MailChimp webhooks. If you're using Rails, the endpoint is automatically connected to `/panthoot/hooks`. If you're not using Rails, then you'll want to mount `Panthoot::App.new` into your app somewhere.
14
+
15
+ ## Usage
16
+
17
+ In an initializer or similar, to ensure any changes made from MailChimp's side of things are reflected in your app:
18
+
19
+ ```ruby
20
+ # Repeat as needed for as many lists as required - the label argument must be
21
+ # unique for each, though.
22
+ ChimpSync.sync :my_label do |list|
23
+ list.api_key = ENV['MAILCHIMP_API_KEY']
24
+ list.id = ENV['MAILCHIMP_LIST_ID']
25
+
26
+ list.update_local = lambda { |email, subscribed|
27
+ # email is the email address as a string, subscribed is a boolean
28
+ # indicating the new subscription status via MailChimp.
29
+ User.find_by(email: email).update_attributes(subscribed: subscribed)
30
+ }
31
+ end
32
+ ```
33
+
34
+ You'll also want to pass back local changes to MailChimp:
35
+
36
+ ```ruby
37
+ ChimpSync::Update.subscribed :my_label, user.email, user.subscribed
38
+ ```
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it ( https://github.com/inspire9/chimp_sync/fork )
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create a new Pull Request
47
+
48
+ ## Credits
49
+
50
+ Copyright (c) 2014, ChimpSync is developed and maintained by Pat Allan and "Inspire9":http://inspire9.com, and is released under the open MIT Licence.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler/gem_tasks'
2
+
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ Gem::Specification.new do |spec|
3
+ spec.name = 'chimp_sync'
4
+ spec.version = '0.0.1'
5
+ spec.authors = ['Pat Allan']
6
+ spec.email = ['pat@freelancing-gods.com']
7
+ spec.summary = %q{Keep MailChimp list subscriptions synchronised.}
8
+ spec.description = %q{Synchronise your MailChimp list subscription details with your own data.}
9
+ spec.homepage = 'https://github.com/inspire9/chimp_sync'
10
+ spec.license = 'MIT'
11
+
12
+ spec.files = `git ls-files -z`.split("\x0")
13
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ['lib']
16
+
17
+ spec.add_runtime_dependency 'gibbon', '~> 1.1'
18
+ spec.add_runtime_dependency 'panthoot', '>= 0.2.1'
19
+
20
+ spec.add_development_dependency 'combustion', '~> 0.5.1'
21
+ spec.add_development_dependency 'rails', '~> 4.0'
22
+ spec.add_development_dependency 'rspec-rails', '~> 3.0.1'
23
+ spec.add_development_dependency 'sqlite3', '~> 1.3.9'
24
+ spec.add_development_dependency 'webmock', '~> 1.18.0'
25
+ end
data/config.ru ADDED
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require :default, :development
5
+
6
+ Combustion.initialize!
7
+ run Combustion::Application
data/lib/chimp_sync.rb ADDED
@@ -0,0 +1,24 @@
1
+ require 'gibbon'
2
+ require 'panthoot'
3
+
4
+ module ChimpSync
5
+ def self.lists_for(list_id)
6
+ lists.values.select { |list| list.id == list_id }
7
+ end
8
+
9
+ def self.lists
10
+ @lists ||= {}
11
+ end
12
+
13
+ def self.reset
14
+ @lists = {}
15
+ end
16
+
17
+ def self.sync(label, &block)
18
+ lists[label] = ChimpSync::List.new.tap { |list| block.call list }
19
+ end
20
+ end
21
+
22
+ require 'chimp_sync/list'
23
+ require 'chimp_sync/subscriber'
24
+ require 'chimp_sync/update'
@@ -0,0 +1,3 @@
1
+ class ChimpSync::List
2
+ attr_accessor :id, :api_key, :update_local
3
+ end
@@ -0,0 +1,37 @@
1
+ class ChimpSync::Subscriber
2
+ def self.add(*types, &block)
3
+ types.each do |type|
4
+ ActiveSupport::Notifications.subscribe("#{type}.panthoot") do |*args|
5
+ new(type, &block).call *args
6
+ end
7
+ end
8
+ end
9
+
10
+ def initialize(type, &block)
11
+ @type, @block = type, block
12
+ end
13
+
14
+ def call(*arguments)
15
+ data = ActiveSupport::Notifications::Event.new(*arguments).payload[type]
16
+
17
+ ChimpSync.lists_for(data.list_id).each do |list|
18
+ block.call list, data
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :type, :block
25
+ end
26
+
27
+ ChimpSync::Subscriber.add :subscribe do |list, data|
28
+ list.update_local.call data.email, true
29
+ end
30
+
31
+ ChimpSync::Subscriber.add :unsubscribe, :email_cleaned do |list, data|
32
+ list.update_local.call data.email, false
33
+ end
34
+
35
+ ChimpSync::Subscriber.add :email_address_change do |list, data|
36
+ list.update_local.call data.old_email, false
37
+ end
@@ -0,0 +1,21 @@
1
+ class ChimpSync::Update
2
+ def self.subscribed(label, email, subscribed)
3
+ list = ChimpSync.lists[label]
4
+ gibbon = Gibbon::API.new list.api_key
5
+
6
+ if subscribed
7
+ gibbon.lists.subscribe id: list.id, email: {email: email},
8
+ double_optin: false, send_welcome: true
9
+ else
10
+ gibbon.lists.unsubscribe id: list.id, email: {email: email}
11
+ end
12
+ end
13
+
14
+ def self.email(label, old_email, new_email)
15
+ raise 'Not yet implemented.'
16
+ list = ChimpSync.lists[label]
17
+ gibbon = Gibbon::API.new list.api_key
18
+
19
+ gibbon.lists.update_member id: list.id, email: {email: new_email}
20
+ end
21
+ end
@@ -0,0 +1,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -0,0 +1,3 @@
1
+ test:
2
+ adapter: sqlite3
3
+ database: db/combustion_test.sqlite
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ #
3
+ end
@@ -0,0 +1,6 @@
1
+ ActiveRecord::Schema.define do
2
+ create_table :users, force: true do |table|
3
+ table.string :email, null: false
4
+ table.boolean :subscribed, null: false
5
+ end
6
+ end
@@ -0,0 +1 @@
1
+ *.log
File without changes
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Sync from Webhooks', type: :request do
4
+ let(:list_id) { 'a6b5da1054' }
5
+ let(:email) { 'pat@test.com' }
6
+
7
+ before :each do
8
+ ChimpSync.reset
9
+ ChimpSync.sync :test_list do |list|
10
+ list.api_key = 'my-api-key'
11
+ list.id = list_id
12
+
13
+ list.update_local = lambda { |email, subscribed|
14
+ User.find_by(email: email).update_attributes(subscribed: subscribed)
15
+ }
16
+ end
17
+ end
18
+
19
+ it 'marks users as subscribed when MailChimp sends a subscribe hook' do
20
+ user = User.create! email: email, subscribed: false
21
+
22
+ post '/panthoot/hooks', 'type' => 'subscribe',
23
+ 'fired_at' => Time.zone.now.to_s(:db), 'data[id]' => '8a25ff1d98',
24
+ 'data[list_id]' => list_id, 'data[email]' => email,
25
+ 'data[email_type]' => 'html', 'data[merges][EMAIL]' => 'email',
26
+ 'data[merges][FNAME]' => 'MailChimp', 'data[merges][LNAME]' => 'API',
27
+ 'data[ip_opt]' => '10.20.10.30', 'data[ip_signup]' => '10.20.10.30'
28
+
29
+ expect(user.reload).to be_subscribed
30
+ end
31
+
32
+ it 'marks users as unsubscribed when MailChimp sends an unsubscribe hook' do
33
+ user = User.create! email: email, subscribed: true
34
+
35
+ post '/panthoot/hooks', 'type' => 'unsubscribe',
36
+ 'fired_at' => Time.zone.now.to_s(:db), 'data[id]' => '8a25ff1d98',
37
+ 'data[list_id]' => list_id, 'data[email]' => email,
38
+ 'data[email_type]' => 'html', 'data[merges][EMAIL]' => 'email',
39
+ 'data[merges][FNAME]' => 'MailChimp', 'data[merges][LNAME]' => 'API',
40
+ 'data[ip_opt]' => '10.20.10.30', 'data[ip_signup]' => '10.20.10.30'
41
+
42
+ expect(user.reload).to_not be_subscribed
43
+ end
44
+
45
+ it 'marks users as unsubscribed when MailChimp sends a clean hook' do
46
+ user = User.create! email: email, subscribed: true
47
+
48
+ post '/panthoot/hooks', 'type' => 'cleaned',
49
+ 'fired_at' => Time.zone.now.to_s(:db), 'data[list_id]' => list_id,
50
+ 'data[campaign_id]' => '4fjk2ma9xd', 'data[reason]' => 'hard',
51
+ 'data[email]' => email
52
+
53
+ expect(user.reload).to_not be_subscribed
54
+ end
55
+
56
+ it 'marks users as unsubscribed when MailChimp sends a change hook' do
57
+ user = User.create! email: email, subscribed: true
58
+
59
+ post '/panthoot/hooks', 'type' => 'upemail',
60
+ 'fired_at' => Time.zone.now.to_s(:db), 'data[list_id]' => list_id,
61
+ 'data[new_id]' => '51da8c3259',
62
+ 'data[new_email]' => 'api+new@mailchimp.com', 'data[old_email]' => email
63
+
64
+ expect(user.reload).to_not be_subscribed
65
+ end
66
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Update subscriptions via API' do
4
+ let(:list_id) { 'a6b5da1054' }
5
+ let(:email) { 'pat@test.com' }
6
+
7
+ before :each do
8
+ ChimpSync.reset
9
+ ChimpSync.sync :test_list do |list|
10
+ list.api_key = 'my-api-key'
11
+ list.id = list_id
12
+
13
+ list.update_local = lambda { |email, subscribed|
14
+ User.find_by(email: email).update_attributes(subscribed: subscribed)
15
+ }
16
+ end
17
+
18
+ stub_request(:post, %r{\Ahttps://key\.api\.mailchimp\.com/2\.0/lists/}).
19
+ to_return(body: '')
20
+ end
21
+
22
+ it 'can mark an email address as subscribed' do
23
+ ChimpSync::Update.subscribed :test_list, 'pat@test.com', true
24
+
25
+ expect(
26
+ a_request(:post, 'https://key.api.mailchimp.com/2.0/lists/subscribe').
27
+ with(body: "{\"apikey\":\"my-api-key\",\"id\":\"a6b5da1054\",\"email\":{\"email\":\"pat@test.com\"},\"double_optin\":false,\"send_welcome\":true}")
28
+ ).to have_been_made
29
+ end
30
+
31
+ it 'can mark an email address as unsubscribed' do
32
+ ChimpSync::Update.subscribed :test_list, 'pat@test.com', false
33
+
34
+ expect(
35
+ a_request(:post, 'https://key.api.mailchimp.com/2.0/lists/unsubscribe').
36
+ with(body: "{\"apikey\":\"my-api-key\",\"id\":\"a6b5da1054\",\"email\":{\"email\":\"pat@test.com\"}}")
37
+ ).to have_been_made
38
+ end
39
+
40
+ it 'can update an email address' do
41
+ pending 'Need to identify the current email when sending through the new one'
42
+ ChimpSync::Update.email :test_list, 'pat@test.com', 'pat+new@test.com'
43
+
44
+ expect(a_request(
45
+ :post, 'https://key.api.mailchimp.com/2.0/lists/update-member'
46
+ ).with(
47
+ body: "{\"apikey\":\"my-api-key\",\"id\":\"a6b5da1054\",\"email\":{\"email\":\"pat+new@test.com\"}}"
48
+ )).to have_been_made
49
+ end
50
+ end
@@ -0,0 +1,18 @@
1
+ require 'bundler'
2
+
3
+ Bundler.setup :default, :development
4
+
5
+ require 'rails'
6
+ require 'combustion'
7
+ require 'chimp_sync'
8
+
9
+ Combustion.initialize! :action_controller, :active_record
10
+
11
+ require 'rspec/rails'
12
+ require 'webmock/rspec'
13
+
14
+ WebMock.disable_net_connect!
15
+
16
+ RSpec.configure do |config|
17
+ config.use_transactional_fixtures = true
18
+ end
metadata ADDED
@@ -0,0 +1,173 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chimp_sync
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Pat Allan
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-07-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gibbon
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: panthoot
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: combustion
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.1
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 3.0.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 3.0.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.3.9
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.3.9
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 1.18.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 1.18.0
111
+ description: Synchronise your MailChimp list subscription details with your own data.
112
+ email:
113
+ - pat@freelancing-gods.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - Gemfile
120
+ - LICENSE.txt
121
+ - README.md
122
+ - Rakefile
123
+ - chimp_sync.gemspec
124
+ - config.ru
125
+ - lib/chimp_sync.rb
126
+ - lib/chimp_sync/list.rb
127
+ - lib/chimp_sync/subscriber.rb
128
+ - lib/chimp_sync/update.rb
129
+ - spec/internal/app/models/user.rb
130
+ - spec/internal/config/database.yml
131
+ - spec/internal/config/routes.rb
132
+ - spec/internal/db/combustion_test.sqlite
133
+ - spec/internal/db/schema.rb
134
+ - spec/internal/log/.gitignore
135
+ - spec/internal/public/favicon.ico
136
+ - spec/requests/sync_from_webhooks_spec.rb
137
+ - spec/requests/update_via_api_spec.rb
138
+ - spec/spec_helper.rb
139
+ homepage: https://github.com/inspire9/chimp_sync
140
+ licenses:
141
+ - MIT
142
+ metadata: {}
143
+ post_install_message:
144
+ rdoc_options: []
145
+ require_paths:
146
+ - lib
147
+ required_ruby_version: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - ">="
150
+ - !ruby/object:Gem::Version
151
+ version: '0'
152
+ required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ requirements: []
158
+ rubyforge_project:
159
+ rubygems_version: 2.3.0
160
+ signing_key:
161
+ specification_version: 4
162
+ summary: Keep MailChimp list subscriptions synchronised.
163
+ test_files:
164
+ - spec/internal/app/models/user.rb
165
+ - spec/internal/config/database.yml
166
+ - spec/internal/config/routes.rb
167
+ - spec/internal/db/combustion_test.sqlite
168
+ - spec/internal/db/schema.rb
169
+ - spec/internal/log/.gitignore
170
+ - spec/internal/public/favicon.ico
171
+ - spec/requests/sync_from_webhooks_spec.rb
172
+ - spec/requests/update_via_api_spec.rb
173
+ - spec/spec_helper.rb