chimpactions 0.0.0 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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