chimpactions 0.0.0 → 0.0.4

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.
Files changed (44) hide show
  1. data/Gemfile +14 -2
  2. data/MIT-LICENSE +20 -0
  3. data/README.textile +134 -0
  4. data/Rakefile +44 -0
  5. data/app/controllers/chimpactions_controller.rb +123 -0
  6. data/app/models/chimpaction.rb +20 -0
  7. data/app/views/chimpactions/_errors.html.erb +10 -0
  8. data/app/views/chimpactions/_form.html.erb +26 -0
  9. data/app/views/chimpactions/_list.html.erb +6 -0
  10. data/app/views/chimpactions/edit.html.erb +4 -0
  11. data/app/views/chimpactions/index.html.erb +22 -0
  12. data/app/views/chimpactions/new.html.erb +4 -0
  13. data/app/views/chimpactions/webhooks.html.erb +26 -0
  14. data/chimpactions.gemspec +9 -4
  15. data/config/deploy/templates/nginx.conf.tpl_ +0 -0
  16. data/config/deploy/templates/unicorn.conf.tpl_ +0 -0
  17. data/config/routes.rb +8 -0
  18. data/lib/chimpactions.rb +210 -2
  19. data/lib/chimpactions/action.rb +73 -0
  20. data/lib/chimpactions/engine.rb +21 -0
  21. data/lib/chimpactions/exception.rb +14 -0
  22. data/lib/chimpactions/list.rb +113 -0
  23. data/lib/chimpactions/notifier.rb +9 -0
  24. data/lib/chimpactions/setup.rb +42 -0
  25. data/lib/chimpactions/subscriber.rb +125 -0
  26. data/lib/chimpactions/utility.rb +13 -0
  27. data/lib/chimpactions/version.rb +1 -1
  28. data/lib/generators/chimpactions/customize/customize_generator.rb +19 -0
  29. data/lib/generators/chimpactions/customize/templates/views/_errors.html.erb +10 -0
  30. data/lib/generators/chimpactions/customize/templates/views/_form.html.erb +26 -0
  31. data/lib/generators/chimpactions/customize/templates/views/_list.html.erb +6 -0
  32. data/lib/generators/chimpactions/customize/templates/views/edit.html.erb +4 -0
  33. data/lib/generators/chimpactions/customize/templates/views/index.html.erb +22 -0
  34. data/lib/generators/chimpactions/customize/templates/views/new.html.erb +4 -0
  35. data/lib/generators/chimpactions/customize/templates/views/webhooks.html.erb +26 -0
  36. data/lib/generators/chimpactions/install/install_generator.rb +19 -0
  37. data/lib/generators/chimpactions/install/templates/README +10 -0
  38. data/lib/generators/chimpactions/install/templates/chimpactions.yml +39 -0
  39. data/lib/generators/chimpactions/install/templates/chimpactions_initializer.rb +1 -0
  40. data/lib/generators/chimpactions/migration/migration_generator.rb +26 -0
  41. data/lib/generators/chimpactions/migration/templates/create_chimpactions.rb +16 -0
  42. data/lib/railtie.rb +16 -0
  43. metadata +82 -38
  44. data/.gitignore +0 -4
@@ -0,0 +1,4 @@
1
+ <section>
2
+ <h1>Create a Chimpaction : </h1>
3
+ <%= render :partial => 'form'%>
4
+ </section>
@@ -0,0 +1,26 @@
1
+ <style>
2
+ .ca_list {list-style:none;padding:0px;}
3
+ </style>
4
+ <div style="font-size:1.2em; color:green;"><%= flash[:notice] if flash[:notice] %></div>
5
+ <ul class="ca_list">
6
+ <% @lists.each do |list|%>
7
+ <li><%= list.name%> ( ID : <%= list.id %> )
8
+ <% if list.webhook? == false %>
9
+ <%=link_to "Add Webhook", {:action => 'add_webhook', :id => list.web_id }%>
10
+ <% else %>
11
+ <%=link_to "REMOVE Webhook", {:action => 'delete_webhook', :id => list.web_id }%>
12
+ <%end%>
13
+ </li>
14
+ <%end%>
15
+ </ul>
16
+
17
+ <div style="margin-top:25px;"> Remember ! : in order to do something here, your <%= Chimpactions.registered_class_name %> class must define :
18
+ <pre>
19
+ ...
20
+ def receive_webhook(post_from_mailchimp)
21
+ // do something with the MailChimp POST data here
22
+ end
23
+ ...
24
+ </pre>
25
+
26
+ </div>
data/chimpactions.gemspec CHANGED
@@ -9,13 +9,18 @@ Gem::Specification.new do |s|
9
9
  s.authors = ["Peter Bonnell"]
10
10
  s.email = ["gems@circuitllc.com"]
11
11
  s.homepage = ""
12
- s.summary = %q{Chimpactions allows for simple management of subscribers on MailChimp lists}
13
- s.description = %q{coming soon....IN DEVELOPMENT AS OF 4/21/2011}
12
+ s.summary = %q{chimpactions provides a quick way to move subscribers from one MailChimp list to another.}
13
+ s.description = %q{chimpactions }
14
14
 
15
15
  s.rubyforge_project = "chimpactions"
16
16
 
17
- s.files = `git ls-files`.split("\n")
18
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.files = Dir["{app,lib,config}/**/*"] + ["MIT-LICENSE", "Rakefile", "Gemfile", "README.textile","chimpactions.gemspec"]
18
+ s.test_files = [] #`git ls-files -- {test,spec,features}/*`.split("\n")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
+ s.add_dependency('gibbon', '>= 0.1.5')
22
+
23
+ s.post_install_message = %[
24
+ chimpactions has been installed.
25
+ ]
21
26
  end
File without changes
File without changes
data/config/routes.rb ADDED
@@ -0,0 +1,8 @@
1
+ Rails.application.routes.draw do |map|
2
+ resources :chimpactions, :except => [:show]
3
+ match 'chimpactions/webhooks', :to => 'chimpactions#webhooks'
4
+ match 'chimpactions/delete_webhook/:id', :to => 'chimpactions#delete_webhook'
5
+ match 'chimpactions/add_webhook/:id', :to => 'chimpactions#add_webhook'
6
+ match 'chimpactions/receive', :to => 'chimpacitons#receive', :as => :webhook
7
+ match 'chimpactions/index', :to => 'chimpactions#index'
8
+ end
data/lib/chimpactions.rb CHANGED
@@ -1,3 +1,211 @@
1
+ # Holds MailChimp account level information
2
+ # Should set up wit an initializer.
3
+ #require 'active_support'
4
+ require "railtie" if defined?(Rails)
1
5
  module Chimpactions
2
- # Your code goes here...
3
- end
6
+ # ruby wrapper gem for the MailChimp API
7
+ # https://github.com/amro/gibbon
8
+ #extend ActiveSupport::Autoload
9
+ require 'gibbon'
10
+ require 'chimpactions/engine'# if defined?(Rails)
11
+ autoload :List, 'chimpactions/list'
12
+ autoload :Subscriber, 'chimpactions/subscriber'
13
+ autoload :Utility, 'chimpactions/utility'
14
+ autoload :Action, 'chimpactions/action'
15
+ require 'chimpactions/exception'
16
+ autoload :Setup, 'chimpactions/setup'
17
+ autoload :ListNotifier, 'chimpactions/notifier'
18
+
19
+ # Obersver pattern untility class attribute.
20
+ mattr_accessor :observers
21
+ @@observers = []
22
+
23
+ # Observer pattern untility.
24
+ def self.add_observer(observer)
25
+ @@observers << observer
26
+ end
27
+
28
+ # Observer pattern notify untility.
29
+ def self.notify_observers(mod)
30
+ @@observers.each do |o|
31
+ o.update(mod)
32
+ end
33
+ end
34
+
35
+ # Ensure the module has an observer for communicating with List objects.
36
+ add_observer Chimpactions::ListNotifier.new
37
+
38
+ # BELOW ATTRIBUTES SET IN INSTALLED INITIALIZER
39
+ # => try rake chimpactions:install for full option initilaizer
40
+
41
+ # ALL merge variables from anywhere in your MailChimp account
42
+ mattr_accessor :merge_map
43
+ @@merge_map = Hash.new
44
+
45
+ # Your MailChimp API KEY
46
+ mattr_accessor :mailchimp_api_key # your MailChimp API key
47
+ @@mailchimp_api_key = "your_mailchimp_api_key"
48
+
49
+ # Your MailChimp SES key
50
+ mattr_accessor :mailchimp_ses_key # your MailChimp ses key
51
+ @@mailchimp_ses_key = "your_mailchimp_ses_key"
52
+
53
+ # When a class includes Chimpactions::Subscriber, it is registered here
54
+ mattr_accessor :registered_classes
55
+ @@registered_classes = Array.new
56
+
57
+ # Require the user to reply to the MailChimp system before adding to list.
58
+ mattr_accessor :default_double_optin
59
+ @@default_double_optin = false
60
+
61
+ #Have MailChimp send the "Welcome" email for the list immediately on subscribing.
62
+ mattr_accessor :default_send_welcome
63
+ @@default_send_welcome = true
64
+
65
+ # The default email type when subscribing
66
+ mattr_accessor :default_email_type
67
+ @@default_email_type = 'html'
68
+
69
+ # Udpate existing subscriber (to prevent already subscribed error)
70
+ mattr_accessor :default_update_existing
71
+ @@default_update_existing = true
72
+ # END INITIALIZER
73
+
74
+ mattr_accessor :actions
75
+ @@actions = Array.new
76
+
77
+ # the gibbon API wrapper object for communication with MailChimp servers
78
+ mattr_accessor :socket
79
+
80
+ def initialize(*atts)
81
+ super(*atts)
82
+ puts "**** CA INITIALIZED! ************"
83
+ end
84
+
85
+ # Assign a local class to the Chimpactions module.
86
+ # @param [Object] The local object to inherit Chimpactions::Subscriber methods.
87
+ # - Must respond_to? 'email'
88
+ # - For methods defined in the Chimpactions merge_map
89
+ # -- can to respond_to? each method
90
+ # -- if it does not, Chimpactions will not send the merge variable or value.
91
+ def self.for(klass_name)
92
+ klass = Kernel.const_get(klass_name.to_s.capitalize).new
93
+ raise Chimpactions::SetupError.new("The #{klass.name} class MUST at least respond to 'email' !") if !klass.respond_to?(:email)
94
+ Kernel.const_get(klass_name.to_s.capitalize).send(:include, Chimpactions::Subscriber)
95
+ @@registered_classes << klass if !@@registered_classes.include? klass
96
+ end
97
+
98
+ # # Change the MailChimp used by the system.
99
+ # Notifies all list objects and reloads the available lists from the new account.
100
+ # @param [String] A new API key
101
+ def self.change_account(new_api_key)
102
+ self.mailchimp_api_key = new_api_key
103
+ self.socket = Gibbon::API.new(self.mailchimp_api_key)
104
+ notify_observers self
105
+ self.available_lists(true)
106
+ end
107
+
108
+ # The registered class
109
+ # @return [ClassName]
110
+ def self.registered_class
111
+ self.registered_classes[0]
112
+ end
113
+
114
+
115
+ def self.registered_class_name
116
+ self.registered_classes[0].class.name
117
+ end
118
+ # Default setup for Chimpactions. Run rails generate chimpactions_install to create
119
+ # a fresh initializer with configuration options.
120
+ def self.setup(config)
121
+ # puts config
122
+ if config['mailchimp_api_key'] == 'your_mailchimp_api_key' || config['local_model'] == 'YourLocalModel'
123
+ raise Chimpactions::SetupError.new("You must customize initializers/chimpactions.yml.")
124
+ end
125
+ # klass = Kernel.cont_get(config['local_model'].to_s.capitalize)
126
+ # @@registered_classes << klass if !@@registered_classes.include? klass
127
+ self.for(config['local_model'])
128
+ self.mailchimp_api_key = config['mailchimp_api_key']
129
+ self.merge_map = config['merge_map']
130
+ self.mailchimp_ses_key = config['mailchimp_ses_key']
131
+ self.socket = Gibbon::API.new(self.mailchimp_api_key)
132
+ case config['action_store']
133
+ when :yml
134
+ if !config['actions'].blank?
135
+ config['actions'].each do |action|
136
+ self.actions << Chimpactions::Action.new(action)
137
+ end
138
+ end
139
+ when :active_record
140
+ Chimpaction.all.each do |action|
141
+ self.actions << Chimpactions::Action.new(action)
142
+ end
143
+ end
144
+ end
145
+
146
+ # Queries your MailChimp account for available lists.
147
+ # @return [Hash] ChimpActions::List objects available in your account.
148
+ #TODO: What if there are an unmanageable (50+) number of lists? Paginate?
149
+ def self.available_lists(force=false)
150
+ if force
151
+ @available_lists = load_lists.map{|raw_list| Chimpactions::List.new(raw_list)}
152
+ else
153
+ @available_lists ||= load_lists.map{|raw_list| Chimpactions::List.new(raw_list)}
154
+ end
155
+ end
156
+
157
+ # Query the Mailchimp server for lists in this account
158
+ # # @return [Hash] Data for the query
159
+ def self.load_lists
160
+ # send the query
161
+ response = socket.lists
162
+ if !response['error']
163
+ response['data']
164
+ else
165
+ raise MailChimpError.new("#{response['error']} (#{response['code']})")
166
+ end
167
+ end
168
+
169
+ # Searches the MailChimp list hash by id, web_id, name
170
+ # @param [String, Fixnum, Chimpactions::List] list
171
+ # @return [Chimpactions::List] The Chimpactions List object
172
+ def self.list(list)
173
+ case list.class.name
174
+ when "Chimpactions::List"
175
+ #if it's a List, just send it back...
176
+ return_list = [list]
177
+ when "String"
178
+ # The List.name , id, or web_id ?
179
+ return_list = Chimpactions.available_lists.select{|l| l.id == list}
180
+ return_list = Chimpactions.available_lists.select{|l| l.web_id == list.to_i} if return_list.empty?
181
+ return_list = Chimpactions.available_lists.select{|l| l.name == list} if return_list.empty?
182
+ when "Fixnum"
183
+ return_list = Chimpactions.available_lists.select{|l| l.web_id == list.to_i}
184
+ else
185
+ return_list = []
186
+ end
187
+ raise Chimpactions::NotFoundError.new("Could not locate #{list} in your account. ") if return_list.empty?
188
+ return_list[0]
189
+ end
190
+
191
+ # Find a list by any list attribute
192
+ # @param [Hash] :attribute => 'value'
193
+ # @return Chimpactions::List object
194
+ def self.find_list(params)
195
+ available_lists.find{|list| list.method(:params[0].to_s).call == params[1]}
196
+ end
197
+
198
+ # Utility for a hash of lists keyed by name in your MailChimp account.
199
+ # @return [Hash] Lists available in your MailChimp account, keyed by name.
200
+ def self.lists_by_name
201
+ available_lists.sort{|a,b| a.name <=> b.name}
202
+ end
203
+
204
+ # Utility for a hash of lists keyed by ID.
205
+ # @return [Hash] Lists available in your MailChimp account, keyed by id
206
+ def self.list_ids
207
+ available_lists.sort{|a,b| a.id <=> b.id}
208
+ end
209
+
210
+
211
+ end
@@ -0,0 +1,73 @@
1
+ module Chimpactions
2
+ # Defines a single step to take given a set of conditions.
3
+ class Action
4
+
5
+ #The Chimpaction::Subscriber method to execute for this action.
6
+ attr_accessor :action
7
+ #The Chimpactions::List object to excute the action on.
8
+ attr_accessor :list
9
+ attr_accessor :whenn
10
+ attr_accessor :is
11
+ attr_accessor :value
12
+
13
+ # @params [String,String,Subscriber,String] {:action, :list, :method, :comparitor, :value}Subscriber method name, = || != || < || >, Subscriber Object, value to test against
14
+ def initialize(params)
15
+ if params.class.name == "Hash"
16
+ params.each_pair do |key,val|
17
+ self.send(key.to_s.dup << "=", val)
18
+ end
19
+ elsif params.class.name == "Chimpaction"
20
+ ini_ar(params)
21
+ end
22
+ end
23
+
24
+ def ini_ar(ar)
25
+ attribs = %W[action list whenn is value]
26
+ attribs.each do |attrib|
27
+ val = ar.send(attrib)
28
+ self.send(attrib.to_s.dup << "=", val)
29
+ end
30
+ end
31
+
32
+ def execute(subscriber)
33
+ if perform?(subscriber)
34
+ puts "subscriber.#{action}(\"#{list}\")"
35
+ eval "subscriber.#{action}(\"#{list}\")"
36
+ end
37
+ end
38
+
39
+ # Check the Object attributes against the specified logic.
40
+ # @param [String,String,Subscriber,String] Subscriber method name, = || != || < || >, Subscriber Object, value to test against
41
+ def perform?(subscriber)
42
+ # puts "#{is}"
43
+ if is.eql?("=")
44
+ is = '=='
45
+ end
46
+ # puts "#{is}"
47
+ value = cast_value(self.value)
48
+ value = "\"#{value}\"" if value.class.name == "String"
49
+ # puts "*** CHECKING : subscriber.#{whenn} #{is} #{value}"
50
+ eval "subscriber.#{whenn} #{is} #{value}"
51
+ end
52
+
53
+ # Utility for casting a String to a 'best guess' Type
54
+ # @param [Object]
55
+ # @return [String, Integer, TrueClass, FalseClass]
56
+ def cast_value(val)
57
+ raise ArgumentError.new("Your value is not a String! (it's a #{val.class})") if val.class != String && val.class != TrueClass && val.class != FalseClass
58
+
59
+ if (Float val rescue false)
60
+ Float val
61
+ elsif (Integer val rescue false)
62
+ Integer val
63
+ elsif val =~ /^true$/i || val == true
64
+ true
65
+ elsif val =~ /^false$/i || val == false
66
+ false
67
+ else
68
+ val
69
+ end
70
+ end
71
+
72
+ end #Action
73
+ end # module
@@ -0,0 +1,21 @@
1
+ require "chimpactions"
2
+ require "rails"
3
+
4
+ module Chimpactions
5
+ class Engine < Rails::Engine
6
+ initializer "engine.configure_rails_initialization" do
7
+ if File.exists?("#{Rails.root}/config/chimpactions.yml")
8
+ setup_hash = YAML.load_file("#{Rails.root}/config/chimpactions.yml")
9
+ model_test = Kernel.const_get(setup_hash['local_model'].to_s.capitalize) rescue false
10
+ if model_test != false
11
+ Chimpactions.setup(setup_hash)
12
+ Kernel.const_get(Chimpactions.registered_class_name.to_s.capitalize).send(:include, Chimpactions::Subscriber)
13
+ else
14
+ raise Chimpactions::SetupError.new("The #{setup_hash['local_model'].to_s.capitalize} class was not found!")
15
+ end
16
+ else
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,14 @@
1
+ module Chimpactions
2
+ # A Chimpactions specific ArgumentError.
3
+ class ArgumentError < ArgumentError; end
4
+ # For an empty result from a MailChimp request.
5
+ class NotFoundError < StandardError; end
6
+ # A setting Chimpactions needs is missing or malformed.
7
+ class SetupError < RuntimeError; end
8
+ # Low level error in the MailChimp connection (gibbon).
9
+ class ConnectionError < IOError; end
10
+ # Bad API key.
11
+ class AuthError < ConnectionError; end
12
+ # An error from the MAilChimp API
13
+ class MailChimpError < ConnectionError; end
14
+ end
@@ -0,0 +1,113 @@
1
+ module Chimpactions
2
+ # The List class does all kinds of things with your MailChimp lists.
3
+ class List
4
+ include Chimpactions::Utility
5
+
6
+ cattr_reader :socket
7
+ # The system-wide single socket to the MailChimp mothership.
8
+ @@socket = Chimpactions.socket
9
+
10
+ # Receiver for Observer pattern.
11
+ # Sets the class level socket to the current Chimpactions module socket.
12
+ def self.new_socket
13
+ @@socket = Chimpactions.socket
14
+ end
15
+
16
+ # @return [List::Stats]
17
+ attr_reader :stats
18
+
19
+ # Create a new List object
20
+ # @param [Hash] raw MailChimp API data return see Chimpactions::Subscriber
21
+ def initialize(raw)
22
+ @raw = raw
23
+ @stats = Stats.new(@raw.delete('stats'))
24
+ self
25
+ end
26
+
27
+ #The raw list data.
28
+ #@return [Hash]
29
+ def data
30
+ @raw
31
+ end
32
+
33
+ # MC API v1.3
34
+ # def listMergeVarAdd(string apikey, string id, string tag, string name, array options)
35
+ # #Add a new merge tag to a given list
36
+ # end
37
+ #
38
+ # def listMergeVarDel(string apikey, string id, string tag)
39
+ # end
40
+ #
41
+ # def listMergeVars(string apikey, string id)
42
+ # #Get the list of merge tags for a given list, including their name, tag, and required setting
43
+ # end
44
+ #
45
+ # def listWebhookAdd(string apikey, string id, string url, array actions, array sources)
46
+ # actions :
47
+ #subscribe optional - as subscribes occur, defaults to true
48
+ #boolean unsubscribe optional - as subscribes occur, defaults to true
49
+ #boolean profile optional - as profile updates occur, defaults to true
50
+ #boolean cleaned optional - as emails are cleaned from the list, defaults to true
51
+ #boolean upemail optiona
52
+ # #Add a new Webhook URL for the given list
53
+ # end
54
+ #
55
+ # def listWebhookDel(string apikey, string id, string url)
56
+ # #Delete an existing Webhook URL from a given list
57
+ # end
58
+ #
59
+ # def listWebhooks(string apikey, string id)
60
+ # #Return the Webhooks configured for the given list
61
+ # end
62
+ # END MC API
63
+
64
+ #The available merge varaibles for the List.
65
+ def merge_vars
66
+ @merge_vars ||= @@socket.listMergeVars(:id => id)
67
+ end
68
+
69
+ # Checks if there are ANY webhooks for this list
70
+ # @return [boolean]
71
+ def webhook?
72
+ @@socket.listWebhooks(:id => id).empty? ? false : true
73
+ end
74
+
75
+ # Retrieves webhooks for this List
76
+ # @return [Hash, false]
77
+ def webhooks
78
+ @@socket.listWebhooks(:id => id)
79
+ end
80
+
81
+ # Sets a webhook for this List
82
+ # @param [Hash] _REQUIRED_ : :url => 'a valid url'
83
+ # @return [true, Hash] true if ok, otherwise an error hash
84
+ def set_webhook(opts)
85
+ @@socket.listWebhookAdd({:id => id}.merge(opts) ) == "true"
86
+ end
87
+
88
+ # removes the specified url webhook for this List
89
+ # @param [Hash] _REQUIRED_ : :url => 'a valid, currently used url'
90
+ # @return [true, Hash] true if ok, otherwise an error hash
91
+ def remove_webhook(opts)
92
+ @@socket.listWebhookDel({:id => id}.merge(opts) ) == "true"
93
+ end
94
+
95
+ # The Stats class is a simple wrapper for statistics about the List.
96
+ # <code> my_list.stats.subscriber_total #=> 243 </code>
97
+ class Stats
98
+ include Chimpactions::Utility
99
+
100
+ def initialize(raw)
101
+ @raw = raw
102
+ self
103
+ end
104
+
105
+ #The raw data array of statistic info.
106
+ def data
107
+ @raw
108
+ end
109
+ end #Stats
110
+
111
+ end #List
112
+
113
+ end #Chimpactions