madmimi 1.0.16 → 1.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 +7 -0
- data/.rspec +2 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +93 -0
- data/README.rdoc +88 -37
- data/Rakefile +2 -30
- data/VERSION +1 -1
- data/config/paths.yml +23 -0
- data/lib/madmimi.rb +210 -123
- data/madmimi.gemspec +130 -22
- data/spec/cassettes/add_to_list/success.yml +54 -0
- data/spec/cassettes/add_to_list/user_does_not_exist.yml +165 -0
- data/spec/cassettes/add_to_list/user_existed.yml +54 -0
- data/spec/cassettes/add_to_list/user_exists.yml +171 -0
- data/spec/cassettes/add_to_list/user_updated.yml +174 -0
- data/spec/cassettes/add_user/user_did_not_exist.yml +54 -0
- data/spec/cassettes/add_user/user_exist.yml +303 -0
- data/spec/cassettes/add_user/user_existed.yml +54 -0
- data/spec/cassettes/add_user/user_missing.yml +138 -0
- data/spec/cassettes/add_user/user_updated.yml +153 -0
- data/spec/cassettes/add_users/users_did_not_exist.yml +54 -0
- data/spec/cassettes/add_users/users_exist.yml +326 -0
- data/spec/cassettes/add_users/users_existed.yml +54 -0
- data/spec/cassettes/add_users/users_missing.yml +305 -0
- data/spec/cassettes/add_users/users_updated.yml +327 -0
- data/spec/cassettes/add_users_to_list/gained_membership.yml +107 -0
- data/spec/cassettes/add_users_to_list/missing_membership.yml +174 -0
- data/spec/cassettes/add_users_to_list/users_did_not_exist.yml +54 -0
- data/spec/cassettes/add_users_to_list/users_exist.yml +365 -0
- data/spec/cassettes/add_users_to_list/users_existed.yml +54 -0
- data/spec/cassettes/add_users_to_list/users_missing.yml +341 -0
- data/spec/cassettes/add_users_to_list/users_updated.yml +365 -0
- data/spec/cassettes/audience_search/does_not_include_suppressed_users.yml +76 -0
- data/spec/cassettes/audience_search/includes_suppressed_users.yml +86 -0
- data/spec/cassettes/csv_import/success.yml +54 -0
- data/spec/cassettes/csv_import/users_exist.yml +273 -0
- data/spec/cassettes/csv_import/users_missing.yml +381 -0
- data/spec/cassettes/delete_list/exists.yml +57 -0
- data/spec/cassettes/delete_list/fail.yml +54 -0
- data/spec/cassettes/delete_list/not_exists.yml +56 -0
- data/spec/cassettes/delete_list/success.yml +54 -0
- data/spec/cassettes/delete_list/users_exist.yml +129 -0
- data/spec/cassettes/list_members/multiple.yml +68 -0
- data/spec/cassettes/list_members/single.yml +68 -0
- data/spec/cassettes/lists/multiple.yml +56 -0
- data/spec/cassettes/lists/single.yml +55 -0
- data/spec/cassettes/mailing_stats/mailing_does_not_exist.yml +50 -0
- data/spec/cassettes/mailing_stats/promotion_and_mailing_exist.yml +57 -0
- data/spec/cassettes/mailing_stats/promotion_does_not_exist.yml +50 -0
- data/spec/cassettes/members/multiple.yml +174 -0
- data/spec/cassettes/members/single.yml +61 -0
- data/spec/cassettes/memberships/multiple.yml +56 -0
- data/spec/cassettes/memberships/single.yml +108 -0
- data/spec/cassettes/new_list/exists.yml +57 -0
- data/spec/cassettes/new_list/fail.yml +52 -0
- data/spec/cassettes/new_list/not_exists.yml +56 -0
- data/spec/cassettes/new_list/success.yml +52 -0
- data/spec/cassettes/promotions/multiple.yml +63 -0
- data/spec/cassettes/promotions/single.yml +61 -0
- data/spec/cassettes/remove_from_all_list/user_does_not_have_memberships.yml +60 -0
- data/spec/cassettes/remove_from_all_lists/user_has_memberships.yml +113 -0
- data/spec/cassettes/remove_from_all_lists/user_with_memberships.yml +49 -0
- data/spec/cassettes/remove_from_list/user_does_not_exist.yml +49 -0
- data/spec/cassettes/remove_from_list/user_in_list_does_not_exist.yml +62 -0
- data/spec/cassettes/remove_from_list/user_in_list_exists.yml +68 -0
- data/spec/cassettes/remove_from_list/user_in_the_list.yml +49 -0
- data/spec/cassettes/remove_from_list/user_not_in_the_list.yml +49 -0
- data/spec/cassettes/save_promotion/only_plain_text.yml +56 -0
- data/spec/cassettes/save_promotion/only_raw_html.yml +56 -0
- data/spec/cassettes/save_promotion/promotion_exists.yml +65 -0
- data/spec/cassettes/save_promotion/promotion_missing.yml +63 -0
- data/spec/cassettes/save_promotion/raw_html_and_plain_text.yml +56 -0
- data/spec/cassettes/send_html/promotion_does_not_exist.yml +56 -0
- data/spec/cassettes/send_html/send_single.yml +53 -0
- data/spec/cassettes/send_html/send_to_all.yml +56 -0
- data/spec/cassettes/send_html/send_to_list.yml +56 -0
- data/spec/cassettes/send_mail/promotion_does_not_exist.yml +54 -0
- data/spec/cassettes/send_mail/send_single.yml +53 -0
- data/spec/cassettes/send_mail/send_to_all.yml +56 -0
- data/spec/cassettes/send_mail/send_to_list.yml +56 -0
- data/spec/cassettes/send_plaintext/promotion_does_not_exist.yml +56 -0
- data/spec/cassettes/send_plaintext/send_single.yml +53 -0
- data/spec/cassettes/send_plaintext/send_to_all.yml +56 -0
- data/spec/cassettes/send_plaintext/send_to_list.yml +56 -0
- data/spec/cassettes/send_plaintext/tracking_beacon_missing.yml +56 -0
- data/spec/cassettes/status/transactional_mail_does_not_exist.yml +51 -0
- data/spec/cassettes/status/transactional_mail_exists.yml +53 -0
- data/spec/cassettes/suppress_email/already_suppressed.yml +51 -0
- data/spec/cassettes/suppress_email/not_suppressed.yml +102 -0
- data/spec/cassettes/suppress_email/user_does_not_exist.yml +100 -0
- data/spec/cassettes/suppress_email/user_is_now_suppressed.yml +51 -0
- data/spec/cassettes/suppress_email/user_not_suppressed.yml +51 -0
- data/spec/cassettes/suppressed/member_does_not_exist.yml +49 -0
- data/spec/cassettes/suppressed/member_is_not_suppressed.yml +51 -0
- data/spec/cassettes/suppressed/member_is_suppressed.yml +51 -0
- data/spec/cassettes/suppressed_since/members_exist.yml +56 -0
- data/spec/cassettes/suppressed_since/no_members.yml +56 -0
- data/spec/cassettes/unsuppress_email/already_suppressed.yml +100 -0
- data/spec/cassettes/unsuppress_email/not_suppressed.yml +99 -0
- data/spec/cassettes/unsuppress_email/user_does_not_exist.yml +99 -0
- data/spec/cassettes/unsuppress_email/user_is_now_unsuppressed.yml +49 -0
- data/spec/cassettes/unsuppress_email/user_suppressed.yml +51 -0
- data/spec/cassettes/update_email/user_does_not_have_permission.yml +52 -0
- data/spec/cassettes/update_email/user_existed.yml +54 -0
- data/spec/cassettes/update_email/user_missing.yml +52 -0
- data/spec/cassettes/update_email/user_with_new_email.yml +345 -0
- data/spec/cassettes/update_email/user_with_old_email.yml +345 -0
- data/spec/madmimi_spec.rb +1166 -0
- data/spec/spec_helper.rb +15 -0
- metadata +210 -96
- data/test/fixtures/lists.xml +0 -7
- data/test/fixtures/promotions.xml +0 -10
- data/test/fixtures/search.xml +0 -38
- data/test/helper.rb +0 -43
- data/test/test_madmimi.rb +0 -63
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: be4672c1319bb8ab018016377765b23bf7dd99a5
|
|
4
|
+
data.tar.gz: edcb37c771c1c11a7ebe34342ebd8971d7d26285
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e577ff522f35537b25bea05a7d8aa1a4bf9064b8678f510058413cef9fd94a671a4fa179b29b8f216cf5c160ec1400d065de66e5470c5cbc9067480c102f22bb
|
|
7
|
+
data.tar.gz: 26eac26ebec69e51f232b1913f4238d3a3f041bdb1559e2aacc9dc27e1768606961933026d0d8bf9e664e7b52c862bd264e55dc14536648efc0e4397e55668ae
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
activesupport (4.1.5)
|
|
5
|
+
i18n (~> 0.6, >= 0.6.9)
|
|
6
|
+
json (~> 1.7, >= 1.7.7)
|
|
7
|
+
minitest (~> 5.1)
|
|
8
|
+
thread_safe (~> 0.1)
|
|
9
|
+
tzinfo (~> 1.1)
|
|
10
|
+
addressable (2.3.5)
|
|
11
|
+
builder (3.2.2)
|
|
12
|
+
crack (0.4.2)
|
|
13
|
+
safe_yaml (~> 1.0.0)
|
|
14
|
+
descendants_tracker (0.0.3)
|
|
15
|
+
diff-lcs (1.2.5)
|
|
16
|
+
faraday (0.9.0)
|
|
17
|
+
multipart-post (>= 1.2, < 3)
|
|
18
|
+
git (1.2.6)
|
|
19
|
+
github_api (0.11.2)
|
|
20
|
+
addressable (~> 2.3)
|
|
21
|
+
descendants_tracker (~> 0.0.1)
|
|
22
|
+
faraday (~> 0.8, < 0.10)
|
|
23
|
+
hashie (>= 1.2)
|
|
24
|
+
multi_json (>= 1.7.5, < 2.0)
|
|
25
|
+
nokogiri (~> 1.6.0)
|
|
26
|
+
oauth2
|
|
27
|
+
hashie (2.0.5)
|
|
28
|
+
highline (1.6.20)
|
|
29
|
+
httparty (0.13.1)
|
|
30
|
+
json (~> 1.8)
|
|
31
|
+
multi_xml (>= 0.5.2)
|
|
32
|
+
i18n (0.6.11)
|
|
33
|
+
jeweler (2.0.1)
|
|
34
|
+
builder
|
|
35
|
+
bundler (>= 1.0)
|
|
36
|
+
git (>= 1.2.5)
|
|
37
|
+
github_api
|
|
38
|
+
highline (>= 1.6.15)
|
|
39
|
+
nokogiri (>= 1.5.10)
|
|
40
|
+
rake
|
|
41
|
+
rdoc
|
|
42
|
+
json (1.8.1)
|
|
43
|
+
jwt (0.1.11)
|
|
44
|
+
multi_json (>= 1.5)
|
|
45
|
+
mini_portile (0.5.2)
|
|
46
|
+
minitest (5.4.0)
|
|
47
|
+
multi_json (1.8.4)
|
|
48
|
+
multi_xml (0.5.5)
|
|
49
|
+
multipart-post (2.0.0)
|
|
50
|
+
nokogiri (1.6.1)
|
|
51
|
+
mini_portile (~> 0.5.0)
|
|
52
|
+
oauth2 (0.9.3)
|
|
53
|
+
faraday (>= 0.8, < 0.10)
|
|
54
|
+
jwt (~> 0.1.8)
|
|
55
|
+
multi_json (~> 1.3)
|
|
56
|
+
multi_xml (~> 0.5)
|
|
57
|
+
rack (~> 1.2)
|
|
58
|
+
rack (1.5.2)
|
|
59
|
+
rake (10.1.1)
|
|
60
|
+
rdoc (4.1.1)
|
|
61
|
+
json (~> 1.4)
|
|
62
|
+
rspec (3.0.0)
|
|
63
|
+
rspec-core (~> 3.0.0)
|
|
64
|
+
rspec-expectations (~> 3.0.0)
|
|
65
|
+
rspec-mocks (~> 3.0.0)
|
|
66
|
+
rspec-core (3.0.3)
|
|
67
|
+
rspec-support (~> 3.0.0)
|
|
68
|
+
rspec-expectations (3.0.3)
|
|
69
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
70
|
+
rspec-support (~> 3.0.0)
|
|
71
|
+
rspec-mocks (3.0.3)
|
|
72
|
+
rspec-support (~> 3.0.0)
|
|
73
|
+
rspec-support (3.0.3)
|
|
74
|
+
safe_yaml (1.0.1)
|
|
75
|
+
thread_safe (0.3.4)
|
|
76
|
+
tzinfo (1.2.2)
|
|
77
|
+
thread_safe (~> 0.1)
|
|
78
|
+
vcr (2.9.2)
|
|
79
|
+
webmock (1.11.0)
|
|
80
|
+
addressable (>= 2.2.7)
|
|
81
|
+
crack (>= 0.3.2)
|
|
82
|
+
|
|
83
|
+
PLATFORMS
|
|
84
|
+
ruby
|
|
85
|
+
|
|
86
|
+
DEPENDENCIES
|
|
87
|
+
activesupport (> 3.0.0)
|
|
88
|
+
crack (> 0.1.7)
|
|
89
|
+
httparty (>= 0.13.1)
|
|
90
|
+
jeweler (> 1.4)
|
|
91
|
+
rspec
|
|
92
|
+
vcr
|
|
93
|
+
webmock
|
data/README.rdoc
CHANGED
|
@@ -4,70 +4,124 @@ The power of Mad Mimi in your Ruby application. Deliver emails, track statistics
|
|
|
4
4
|
|
|
5
5
|
== Installation
|
|
6
6
|
|
|
7
|
-
gem install madmimi
|
|
7
|
+
$ gem install madmimi
|
|
8
|
+
|
|
9
|
+
or if you prefer to live on the edge, just clone this repository and build it from scratch.
|
|
10
|
+
|
|
11
|
+
== Dependencies
|
|
12
|
+
|
|
13
|
+
* active_support (I intend to remove this in the not too distant future, and build my own implementation.)
|
|
8
14
|
|
|
9
15
|
== Basic Usage
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
mimi = MadMimi.new('emailaddress', 'api_key')
|
|
18
|
+
mimi.lists # get all of your Mad Mimi lists returned as a hash
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
You can pass +raise_exceptions+ to the initializer, if you wish to receive exceptions for failing requests:
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
MadMimi.new('emailaddress', 'api_key', { :raise_exceptions => true })
|
|
23
|
+
mimi.lists # will raise an Exception if request fails
|
|
24
|
+
|
|
25
|
+
You can set +verify_ssl+ to either true or false in initializer, if you wish verify SSL or not in requests:
|
|
26
|
+
|
|
27
|
+
MadMimi.new('emailaddress', 'api_key', { :verify_ssl => true })
|
|
17
28
|
|
|
18
29
|
=== Audience Members and Lists
|
|
19
30
|
|
|
20
|
-
mimi.memberships('email')
|
|
31
|
+
mimi.memberships('email') # returns a hash of the lists that specific email address is subscribed to
|
|
32
|
+
|
|
33
|
+
mimi.new_list('New list name') # make a new list
|
|
34
|
+
|
|
35
|
+
mimi.delete_list('New list name') # delete the list I just created
|
|
36
|
+
|
|
37
|
+
mimi.csv_import("name,email\ndave,dave@example.com\n") # import from a csv string
|
|
38
|
+
|
|
39
|
+
mimi.add_user({ :email => 'dave@example.com', :first_name => 'Dave' }) # add new audience member
|
|
40
|
+
|
|
41
|
+
mimi.add_users([
|
|
42
|
+
{ :email => 'dave@example.com', :first_name => 'Dave' },
|
|
43
|
+
{ :email => 'custom@example.com', :custom_field_1 => 'Dummy value' },
|
|
44
|
+
{ :email => 'smith@example.com', :last_name => 'Smith' }
|
|
45
|
+
]) # add audience members in bulk
|
|
46
|
+
|
|
47
|
+
mimi.add_to_list('dave@example.com', 'Test List') # add this email address to a specific list
|
|
48
|
+
mimi.add_to_list('dave@example.com', 'Test List', {
|
|
49
|
+
:first_name => 'Dave',
|
|
50
|
+
:last_name => 'Example',
|
|
51
|
+
:custom_field => 'Custom value'
|
|
52
|
+
}) # add additional data with this email
|
|
21
53
|
|
|
22
|
-
mimi.
|
|
54
|
+
mimi.remove_from_list('dave@example.com', 'Test List') # remove this email address from a specific list
|
|
23
55
|
|
|
24
|
-
mimi.
|
|
56
|
+
mimi.remove_from_all_lists('dave@example.com') # remove this email address from all lists
|
|
25
57
|
|
|
26
|
-
|
|
58
|
+
# this API call needs advanced permissions (manually requested)
|
|
59
|
+
mimi.update_email('dave@example.com', 'john@example.com') # changes email address for user 'dave@example.com' to 'john@example.com'
|
|
27
60
|
|
|
28
|
-
mimi.
|
|
61
|
+
mimi.members # get all audience members
|
|
29
62
|
|
|
30
|
-
mimi.
|
|
63
|
+
mimi.list_members('Test List') # get audience members in specific list
|
|
31
64
|
|
|
32
|
-
mimi.
|
|
65
|
+
mimi.suppressed?('dave@example.com') # check if audience member is suppressed
|
|
66
|
+
|
|
67
|
+
mimi.suppress_email('dave@example.com') # move email to suppressed list
|
|
68
|
+
|
|
69
|
+
mimi.unsuppress_email('dave@example.com') # move email from suppressed list
|
|
70
|
+
|
|
71
|
+
mimi.suppressed_since('unix timestamp') # get a TXT of all addresses that were suppressed since this timestamp
|
|
33
72
|
|
|
34
73
|
=== Promotions
|
|
35
74
|
|
|
36
|
-
mimi.promotions
|
|
75
|
+
mimi.promotions # returns a hash of your promotions
|
|
37
76
|
|
|
38
|
-
mimi.save_promotion('promotion_name', 'raw_html', 'plain_text')
|
|
77
|
+
mimi.save_promotion('promotion_name', 'raw_html', 'plain_text') # saves a promotion (creates the promotion if it does not exist)
|
|
39
78
|
|
|
40
|
-
mimi.mailing_stats('promotion_id', 'mailing_id')
|
|
79
|
+
mimi.mailing_stats('promotion_id', 'mailing_id') # get stats on a specific mailing
|
|
41
80
|
|
|
42
81
|
== Sending E-Mail (using the Mailer API)
|
|
43
82
|
|
|
44
83
|
=== Replacing keys in your email body text:
|
|
45
84
|
|
|
46
|
-
options = {
|
|
85
|
+
options = {
|
|
86
|
+
'promotion_name' => 'Test Promotion',
|
|
87
|
+
'recipients' => 'Nicholas Young <nicholas@madmimi.com>',
|
|
88
|
+
'from' => 'MadMimi Ruby <rubygem@madmimi.com>',
|
|
89
|
+
'subject' => 'Test Subject'
|
|
90
|
+
}
|
|
91
|
+
yaml_body = {
|
|
92
|
+
'greeting' => 'Hello',
|
|
93
|
+
'name' => 'Nicholas'
|
|
94
|
+
}
|
|
47
95
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
mimi.send_mail(options, yaml_body)
|
|
96
|
+
mimi.send_mail(options, yaml_body)
|
|
51
97
|
|
|
52
98
|
=== Sending Raw HTML (presumably generated by your app)
|
|
53
99
|
|
|
54
|
-
options = {
|
|
55
|
-
|
|
56
|
-
|
|
100
|
+
options = {
|
|
101
|
+
'promotion_name' => 'Test Promotion',
|
|
102
|
+
'recipients' => 'Nicholas Young <nicholas@madmimi.com>',
|
|
103
|
+
'from' => 'MadMimi Ruby <rubygem@madmimi.com>',
|
|
104
|
+
'subject' => 'Test Subject'
|
|
105
|
+
}
|
|
106
|
+
raw_html = "<html><head><title>My great promotion!</title></head><body>Body stuff[[tracking_beacon]]</body></html>"
|
|
57
107
|
|
|
58
|
-
mimi.send_html(options, raw_html)
|
|
108
|
+
mimi.send_html(options, raw_html)
|
|
59
109
|
|
|
60
110
|
=== Sending Plain Text
|
|
61
111
|
|
|
62
|
-
options = {
|
|
112
|
+
options = {
|
|
113
|
+
'promotion_name' => 'Test Promotion',
|
|
114
|
+
'recipients' => 'Nicholas Young <nicholas@madmimi.com>',
|
|
115
|
+
'from' => 'MadMimi Ruby <rubygem@madmimi.com>',
|
|
116
|
+
'subject' => 'Test Subject'
|
|
117
|
+
}
|
|
118
|
+
plain_text = "Plain text email contents [[unsubscribe]]"
|
|
63
119
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
mimi.send_plaintext(options, plain_text)
|
|
120
|
+
mimi.send_plaintext(options, plain_text)
|
|
67
121
|
|
|
68
122
|
=== Getting the status of a transactional mailing
|
|
69
123
|
|
|
70
|
-
mimi.status('transaction_id')
|
|
124
|
+
mimi.status('transaction_id') # get the status on a specific transactional mailing
|
|
71
125
|
|
|
72
126
|
=== Return values
|
|
73
127
|
|
|
@@ -82,18 +136,14 @@ there is probably an error or uncompleted operation.
|
|
|
82
136
|
|
|
83
137
|
=== Specific options keys
|
|
84
138
|
|
|
85
|
-
'raw_html': Must include at least one of the [[tracking_beacon]] or [[peek_image]] tags.
|
|
86
|
-
|
|
87
|
-
'
|
|
88
|
-
If it exists and you specify raw_html, the promotion body will be replaced.
|
|
89
|
-
|
|
90
|
-
'list_name': For all of the #send methods, if 'list_name' is provided, the recipients
|
|
139
|
+
* 'raw_html': Must include at least one of the [[tracking_beacon]] or [[peek_image]] tags.
|
|
140
|
+
* 'promotion_name': If a promotion doesn't exist under the given name, it will be created. If it exists and you specify raw_html, the promotion body will be replaced.
|
|
141
|
+
* 'list_name': For all of the #send methods, if 'list_name' is provided, the recipients
|
|
91
142
|
will be those for an already-existing "audience."
|
|
92
|
-
|
|
93
|
-
'to_all': Set to true to send a promotion, plain_text or raw_html to all your audience members
|
|
143
|
+
* 'to_all': Set to true to send a promotion, plain_text or raw_html to all your audience members
|
|
94
144
|
|
|
95
145
|
== Note on Patches/Pull Requests
|
|
96
|
-
|
|
146
|
+
|
|
97
147
|
* Fork the project.
|
|
98
148
|
* Make your feature addition or bug fix.
|
|
99
149
|
* Add tests for it. This is important so I don't break it in a
|
|
@@ -105,6 +155,7 @@ will be those for an already-existing "audience."
|
|
|
105
155
|
== Contributors
|
|
106
156
|
tuker
|
|
107
157
|
marcheiligers
|
|
158
|
+
maximgladkov
|
|
108
159
|
|
|
109
160
|
== Copyright
|
|
110
161
|
|
data/Rakefile
CHANGED
|
@@ -9,11 +9,7 @@ begin
|
|
|
9
9
|
gem.description = %Q{Send emails, track statistics, and manage your subscriber base with ease.}
|
|
10
10
|
gem.email = "nicholas@madmimi.com"
|
|
11
11
|
gem.homepage = "http://github.com/madmimi/madmimi-gem"
|
|
12
|
-
gem.authors = ["Nicholas Young", "Marc Heiligers"]
|
|
13
|
-
gem.add_dependency "crack", ">0.1.7"
|
|
14
|
-
gem.add_development_dependency "jeweler", ">1.4"
|
|
15
|
-
gem.add_development_dependency "fakeweb", ">1.2"
|
|
16
|
-
gem.add_development_dependency "shoulda", ">2.10"
|
|
12
|
+
gem.authors = ["Nicholas Young", "Marc Heiligers", "Maxim Gladkov"]
|
|
17
13
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
|
18
14
|
end
|
|
19
15
|
Jeweler::GemcutterTasks.new
|
|
@@ -21,31 +17,7 @@ rescue LoadError
|
|
|
21
17
|
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
|
22
18
|
end
|
|
23
19
|
|
|
24
|
-
require '
|
|
25
|
-
Rake::TestTask.new(:test) do |test|
|
|
26
|
-
test.libs << 'lib' << 'test'
|
|
27
|
-
test.pattern = 'test/**/test_*.rb'
|
|
28
|
-
test.verbose = true
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
begin
|
|
32
|
-
require 'rcov/rcovtask'
|
|
33
|
-
Rcov::RcovTask.new do |test|
|
|
34
|
-
test.libs << 'test'
|
|
35
|
-
test.pattern = 'test/**/test_*.rb'
|
|
36
|
-
test.verbose = true
|
|
37
|
-
end
|
|
38
|
-
rescue LoadError
|
|
39
|
-
task :rcov do
|
|
40
|
-
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
task :test => :check_dependencies
|
|
45
|
-
|
|
46
|
-
task :default => :test
|
|
47
|
-
|
|
48
|
-
require 'rake/rdoctask'
|
|
20
|
+
require 'rdoc/task'
|
|
49
21
|
Rake::RDocTask.new do |rdoc|
|
|
50
22
|
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
|
51
23
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0
|
|
1
|
+
1.1.0
|
data/config/paths.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
:create_list: /audience_lists.json
|
|
2
|
+
:remove_from_all_lists: /audience_lists/remove_all
|
|
3
|
+
:destroy_list: /audience_lists/%{list}
|
|
4
|
+
:add_to_list: /audience_lists/%{list}/add
|
|
5
|
+
:remove_from_list: /audience_lists/%{list}/remove
|
|
6
|
+
:audience_lists: /audience_lists/lists.xml
|
|
7
|
+
:audience_list_members: /audience_lists/%{list}/members.xml
|
|
8
|
+
:audience_members: /audience_members
|
|
9
|
+
:get_audience_members: /audience_members.xml
|
|
10
|
+
:search: /audience_members/search.xml
|
|
11
|
+
:memberships: /audience_members/%{email}/lists.xml
|
|
12
|
+
:is_suppressed: /audience_members/%{email}/is_suppressed
|
|
13
|
+
:update_user_email: /audience_members/%{email}/update_email
|
|
14
|
+
:suppressed_since: /audience_members/suppressed_since/%{timestamp}.txt
|
|
15
|
+
:suppress_user: /suppressed_audience_members
|
|
16
|
+
:unsuppress_user: /suppressed_audience_members/%{email}
|
|
17
|
+
:promotions: /promotions.xml
|
|
18
|
+
:promotion_save: /promotions/save
|
|
19
|
+
:mailing_stats: /promotions/%{promotion_id}/mailings/%{mailing_id}.xml
|
|
20
|
+
:mailer: /mailer
|
|
21
|
+
:mailer_to_list: /mailer/to_list
|
|
22
|
+
:mailer_to_all: /mailer/to_all
|
|
23
|
+
:mailer_status: /mailers/status/%{transaction_id}
|
data/lib/madmimi.rb
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# The above copyright notice and this permission notice shall be included in
|
|
15
15
|
# all copies or substantial portions of the Software.
|
|
16
16
|
|
|
17
|
-
# Except as contained in this notice, the name(s) of the above copyright holder(s)
|
|
17
|
+
# Except as contained in this notice, the name(s) of the above copyright holder(s)
|
|
18
18
|
# shall not be used in advertising or otherwise to promote the sale, use or other
|
|
19
19
|
# dealings in this Software without prior written authorization.
|
|
20
20
|
|
|
@@ -26,38 +26,47 @@
|
|
|
26
26
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
27
27
|
# THE SOFTWARE.
|
|
28
28
|
|
|
29
|
+
require 'active_support/core_ext/string'
|
|
30
|
+
require 'active_support/core_ext/hash'
|
|
29
31
|
require 'uri'
|
|
30
|
-
require '
|
|
31
|
-
require '
|
|
32
|
-
require 'crack'
|
|
32
|
+
require 'rubygems'
|
|
33
|
+
require 'httparty'
|
|
33
34
|
require 'csv'
|
|
35
|
+
require 'yaml'
|
|
36
|
+
require 'crack'
|
|
34
37
|
|
|
35
38
|
class MadMimi
|
|
36
39
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
40
|
+
MadMimiError = Class.new(StandardError)
|
|
41
|
+
|
|
42
|
+
include HTTParty
|
|
43
|
+
|
|
44
|
+
base_uri 'api.madmimi.com'
|
|
45
|
+
|
|
46
|
+
parser(
|
|
47
|
+
Proc.new do |body, format|
|
|
48
|
+
begin
|
|
49
|
+
case format
|
|
50
|
+
when :json
|
|
51
|
+
Crack::JSON.parse(body)
|
|
52
|
+
when :xml
|
|
53
|
+
Crack::XML.parse(body)
|
|
54
|
+
else
|
|
55
|
+
body
|
|
56
|
+
end
|
|
57
|
+
rescue Crack::ParseError, REXML::ParseException
|
|
58
|
+
body
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def initialize(username, api_key, options = {})
|
|
64
|
+
@api_settings = options.reverse_merge({
|
|
65
|
+
:verify_ssl => true
|
|
66
|
+
}).merge({
|
|
67
|
+
:username => username,
|
|
68
|
+
:api_key => api_key
|
|
69
|
+
})
|
|
61
70
|
end
|
|
62
71
|
|
|
63
72
|
def username
|
|
@@ -68,95 +77,143 @@ class MadMimi
|
|
|
68
77
|
@api_settings[:api_key]
|
|
69
78
|
end
|
|
70
79
|
|
|
71
|
-
def
|
|
72
|
-
|
|
80
|
+
def raise_exceptions?
|
|
81
|
+
@api_settings[:raise_exceptions]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def raise_exceptions=(raise_exceptions)
|
|
85
|
+
@api_settings[:raise_exceptions] = raise_exceptions
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def verify_ssl?
|
|
89
|
+
@api_settings[:verify_ssl]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def verify_ssl=(verify_ssl)
|
|
93
|
+
@api_settings[:verify_ssl] = verify_ssl
|
|
73
94
|
end
|
|
74
95
|
|
|
75
96
|
# Audience and lists
|
|
76
97
|
def lists
|
|
77
|
-
|
|
78
|
-
|
|
98
|
+
wrap_with_array('lists', 'list') do
|
|
99
|
+
do_request(path(:audience_lists), :get, :format => :xml)
|
|
100
|
+
end
|
|
79
101
|
end
|
|
80
102
|
|
|
81
103
|
def memberships(email)
|
|
82
|
-
|
|
83
|
-
|
|
104
|
+
wrap_with_array('lists', 'list') do
|
|
105
|
+
do_request(path(:memberships, :email => email), :get)
|
|
106
|
+
end
|
|
84
107
|
end
|
|
85
108
|
|
|
86
109
|
def new_list(list_name)
|
|
87
|
-
do_request(
|
|
110
|
+
do_request(path(:create_list), :post, :name => list_name)
|
|
88
111
|
end
|
|
89
112
|
|
|
90
113
|
def delete_list(list_name)
|
|
91
|
-
do_request(
|
|
114
|
+
do_request(path(:destroy_list, :list => list_name), :delete)
|
|
92
115
|
end
|
|
93
116
|
|
|
94
117
|
def csv_import(csv_string)
|
|
95
|
-
do_request(
|
|
118
|
+
do_request(path(:audience_members), :post, :csv_file => csv_string)
|
|
96
119
|
end
|
|
97
120
|
|
|
98
|
-
def add_user(
|
|
99
|
-
|
|
100
|
-
do_request(AUDIENCE_MEMBERS_PATH, :post, :csv_file => csv_data)
|
|
121
|
+
def add_user(hash_or_array)
|
|
122
|
+
csv_import(build_csv(hash_or_array))
|
|
101
123
|
end
|
|
102
124
|
|
|
103
|
-
|
|
104
|
-
|
|
125
|
+
alias :add_users :add_user
|
|
126
|
+
|
|
127
|
+
def add_to_list(email, list_name, options={})
|
|
128
|
+
do_request(path(:add_to_list, :list => list_name), :post, options.merge(:email => email))
|
|
105
129
|
end
|
|
106
130
|
|
|
107
131
|
def remove_from_list(email, list_name)
|
|
108
|
-
do_request(
|
|
132
|
+
do_request(path(:remove_from_list, :list => list_name), :post, :email => email)
|
|
109
133
|
end
|
|
110
134
|
|
|
111
|
-
def
|
|
112
|
-
do_request(
|
|
135
|
+
def remove_from_all_lists(email)
|
|
136
|
+
do_request(path(:remove_from_all_lists), :post, :email => email)
|
|
113
137
|
end
|
|
114
|
-
|
|
138
|
+
|
|
139
|
+
def update_email(existing_email, new_email)
|
|
140
|
+
do_request(path(:update_user_email, :email => existing_email), :post, :email => existing_email, :new_email => new_email)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def members
|
|
144
|
+
wrap_with_array('audience', 'member') do
|
|
145
|
+
do_request(path(:get_audience_members), :get)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def list_members(list_name)
|
|
150
|
+
wrap_with_array('audience', 'member') do
|
|
151
|
+
do_request(path(:audience_list_members, :list => list_name), :get)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def suppressed_since(timestamp, show_suppression_reason = false)
|
|
156
|
+
do_request(path(:suppressed_since, :timestamp => timestamp), :get, {
|
|
157
|
+
:show_suppression_reason => show_suppression_reason
|
|
158
|
+
})
|
|
159
|
+
end
|
|
160
|
+
|
|
115
161
|
def suppress_email(email)
|
|
116
|
-
|
|
162
|
+
return '' if suppressed?(email)
|
|
163
|
+
|
|
164
|
+
process_json_response do
|
|
165
|
+
do_request(path(:suppress_user), :post, :audience_member_id => email, :format => :json)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def unsuppress_email(email)
|
|
170
|
+
return '' unless suppressed?(email)
|
|
171
|
+
|
|
172
|
+
process_json_response do
|
|
173
|
+
do_request(path(:unsuppress_user, :email => email), :delete, :format => :json)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def suppressed?(email)
|
|
178
|
+
response = do_request(path(:is_suppressed, :email => email), :get)
|
|
179
|
+
response == 'true'
|
|
117
180
|
end
|
|
118
181
|
|
|
119
182
|
def audience_search(query_string, raw = false)
|
|
120
|
-
|
|
121
|
-
Crack::XML.parse(request)
|
|
183
|
+
do_request(path(:search), :get, :raw => raw, :query => query_string)
|
|
122
184
|
end
|
|
123
185
|
|
|
124
|
-
# Not the most elegant, but it works for now. :)
|
|
125
186
|
def add_users_to_list(list_name, arr)
|
|
126
|
-
arr.
|
|
127
|
-
a[:add_list] = list_name
|
|
128
|
-
add_user(a)
|
|
129
|
-
end
|
|
187
|
+
add_users(arr.map{ |a| a[:add_list] = list_name; a })
|
|
130
188
|
end
|
|
131
|
-
|
|
189
|
+
|
|
132
190
|
# Promotions
|
|
133
191
|
def promotions
|
|
134
|
-
|
|
135
|
-
|
|
192
|
+
wrap_with_array('promotions', 'promotion') do
|
|
193
|
+
do_request(path(:promotions), :get)
|
|
194
|
+
end
|
|
136
195
|
end
|
|
137
|
-
|
|
196
|
+
|
|
138
197
|
def save_promotion(promotion_name, raw_html, plain_text = nil)
|
|
139
198
|
options = { :promotion_name => promotion_name }
|
|
140
|
-
|
|
199
|
+
|
|
141
200
|
unless raw_html.nil?
|
|
142
201
|
check_for_tracking_beacon raw_html
|
|
143
202
|
check_for_opt_out raw_html
|
|
144
203
|
options[:raw_html] = raw_html
|
|
145
204
|
end
|
|
146
|
-
|
|
205
|
+
|
|
147
206
|
unless plain_text.nil?
|
|
148
207
|
check_for_opt_out plain_text
|
|
149
208
|
options[:raw_plain_text] = plain_text
|
|
150
209
|
end
|
|
151
|
-
|
|
152
|
-
do_request
|
|
210
|
+
|
|
211
|
+
do_request(path(:promotion_save), :post, options)
|
|
153
212
|
end
|
|
154
|
-
|
|
213
|
+
|
|
155
214
|
# Stats
|
|
156
215
|
def mailing_stats(promotion_id, mailing_id)
|
|
157
|
-
path
|
|
158
|
-
request = do_request(path, :get)
|
|
159
|
-
Crack::XML.parse(request)
|
|
216
|
+
do_request(path(:mailing_stats, :promotion_id => promotion_id, :mailing_id => mailing_id), :get)
|
|
160
217
|
end
|
|
161
218
|
|
|
162
219
|
# Mailer API
|
|
@@ -164,12 +221,12 @@ class MadMimi
|
|
|
164
221
|
options = opt.dup
|
|
165
222
|
options[:body] = yaml_body.to_yaml
|
|
166
223
|
if !options[:list_name].nil? || options[:to_all]
|
|
167
|
-
do_request(options[:to_all] ?
|
|
224
|
+
do_request(path(options[:to_all] ? :mailer_to_all : :mailer_to_list), :post, options, true)
|
|
168
225
|
else
|
|
169
|
-
do_request(
|
|
226
|
+
do_request(path(:mailer), :post, options, true)
|
|
170
227
|
end
|
|
171
228
|
end
|
|
172
|
-
|
|
229
|
+
|
|
173
230
|
def send_html(opt, html)
|
|
174
231
|
options = opt.dup
|
|
175
232
|
if html.include?('[[tracking_beacon]]') || html.include?('[[peek_image]]')
|
|
@@ -178,9 +235,9 @@ class MadMimi
|
|
|
178
235
|
unless html.include?('[[unsubscribe]]') || html.include?('[[opt_out]]')
|
|
179
236
|
raise MadMimiError, "When specifying list_name, include the [[unsubscribe]] or [[opt_out]] macro in your HTML before sending."
|
|
180
237
|
end
|
|
181
|
-
do_request(options[:to_all] ?
|
|
238
|
+
do_request(path(options[:to_all] ? :mailer_to_all : :mailer_to_list), :post, options, true)
|
|
182
239
|
else
|
|
183
|
-
do_request(
|
|
240
|
+
do_request(path(:mailer), :post, options, true)
|
|
184
241
|
end
|
|
185
242
|
else
|
|
186
243
|
raise MadMimiError, "You'll need to include either the [[tracking_beacon]] or [[peek_image]] macro in your HTML before sending."
|
|
@@ -192,89 +249,119 @@ class MadMimi
|
|
|
192
249
|
options[:raw_plain_text] = plaintext
|
|
193
250
|
if !options[:list_name].nil? || options[:to_all]
|
|
194
251
|
if plaintext.include?('[[unsubscribe]]') || plaintext.include?('[[opt_out]]')
|
|
195
|
-
do_request(options[:to_all] ?
|
|
252
|
+
do_request(path(options[:to_all] ? :mailer_to_all : :mailer_to_list), :post, options, true)
|
|
196
253
|
else
|
|
197
254
|
raise MadMimiError, "You'll need to include either the [[unsubscribe]] or [[opt_out]] macro in your text before sending."
|
|
198
255
|
end
|
|
199
256
|
else
|
|
200
|
-
do_request(
|
|
257
|
+
do_request(path(:mailer), :post, options, true)
|
|
201
258
|
end
|
|
202
259
|
end
|
|
203
|
-
|
|
260
|
+
|
|
204
261
|
def status(transaction_id)
|
|
205
|
-
do_request
|
|
262
|
+
do_request(path(:mailer_status, :transaction_id => transaction_id), :get, {}, true)
|
|
206
263
|
end
|
|
207
264
|
|
|
208
265
|
private
|
|
209
266
|
|
|
210
267
|
# Refactor this method asap
|
|
211
|
-
def do_request(path,
|
|
212
|
-
options =
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
case req_type
|
|
226
|
-
|
|
227
|
-
when :get then
|
|
228
|
-
begin
|
|
229
|
-
http.start do |http|
|
|
230
|
-
req = Net::HTTP::Get.new(path)
|
|
231
|
-
req.set_form_data(form_data)
|
|
232
|
-
response = http.request(req)
|
|
233
|
-
resp = response.body.strip
|
|
234
|
-
end
|
|
235
|
-
resp
|
|
236
|
-
rescue SocketError
|
|
237
|
-
raise "Host unreachable."
|
|
238
|
-
end
|
|
239
|
-
when :post then
|
|
240
|
-
begin
|
|
241
|
-
http.start do |http|
|
|
242
|
-
req = Net::HTTP::Post.new(path)
|
|
243
|
-
req.set_form_data(form_data)
|
|
244
|
-
response = http.request(req)
|
|
245
|
-
resp = response.body.strip
|
|
246
|
-
end
|
|
247
|
-
rescue SocketError
|
|
248
|
-
raise "Host unreachable."
|
|
249
|
-
end
|
|
250
|
-
end
|
|
268
|
+
def do_request(path, method = :get, options = {}, transactional = false)
|
|
269
|
+
options = default_options.deep_merge({
|
|
270
|
+
:format => options.delete(:format) || extract_format(path),
|
|
271
|
+
:body => options
|
|
272
|
+
})
|
|
273
|
+
|
|
274
|
+
path = convert_to_secure(path) if transactional
|
|
275
|
+
|
|
276
|
+
response = self.class.send(method, path, options)
|
|
277
|
+
|
|
278
|
+
response.value if raise_exceptions?
|
|
279
|
+
response.parsed_response
|
|
251
280
|
end
|
|
252
281
|
|
|
253
|
-
def build_csv(
|
|
282
|
+
def build_csv(hash_or_array)
|
|
283
|
+
hashes = Array.wrap(hash_or_array)
|
|
284
|
+
columns = hashes.map(&:keys).flatten.uniq
|
|
285
|
+
|
|
254
286
|
if CSV.respond_to?(:generate_row) # before Ruby 1.9
|
|
255
287
|
buffer = ''
|
|
256
|
-
CSV.generate_row(
|
|
257
|
-
|
|
288
|
+
CSV.generate_row(columns, columns.size, buffer)
|
|
289
|
+
hashes.each do |hash|
|
|
290
|
+
values = columns.map{ |c| hash[c] }
|
|
291
|
+
CSV.generate_row(values, values.size, buffer)
|
|
292
|
+
end
|
|
258
293
|
buffer
|
|
259
294
|
else # Ruby 1.9 and after
|
|
260
295
|
CSV.generate do |csv|
|
|
261
|
-
csv <<
|
|
262
|
-
|
|
296
|
+
csv << columns
|
|
297
|
+
hashes.each do |hash|
|
|
298
|
+
csv << columns.map{ |c| hash[c] }
|
|
299
|
+
end
|
|
263
300
|
end
|
|
264
301
|
end
|
|
265
302
|
end
|
|
266
|
-
|
|
303
|
+
|
|
267
304
|
def check_for_tracking_beacon(content)
|
|
268
305
|
unless content.include?('[[tracking_beacon]]') || content.include?('[[peek_image]]')
|
|
269
306
|
raise MadMimiError, "You'll need to include either the [[tracking_beacon]] or [[peek_image]] macro in your HTML before sending."
|
|
270
307
|
end
|
|
271
308
|
true
|
|
272
309
|
end
|
|
273
|
-
|
|
310
|
+
|
|
274
311
|
def check_for_opt_out(content)
|
|
275
312
|
unless content.include?('[[opt_out]]') || content.include?('[[unsubscribe]]')
|
|
276
313
|
raise MadMimiError, "When specifying list_name or sending to all, include the [[unsubscribe]] or [[opt_out]] macro in your HTML before sending."
|
|
277
314
|
end
|
|
278
315
|
true
|
|
279
316
|
end
|
|
280
|
-
|
|
317
|
+
|
|
318
|
+
def process_json_response
|
|
319
|
+
json_response = yield
|
|
320
|
+
begin
|
|
321
|
+
json_response["success"] ? '' : json_response["error"]
|
|
322
|
+
rescue JSON::ParserError
|
|
323
|
+
json_response
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def default_options
|
|
328
|
+
{
|
|
329
|
+
:body => {
|
|
330
|
+
:username => username,
|
|
331
|
+
:api_key => api_key
|
|
332
|
+
},
|
|
333
|
+
:verify => verify_ssl?
|
|
334
|
+
}
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def extract_format(path)
|
|
338
|
+
File.extname(path)[1..-1].try(:to_sym)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def convert_to_secure(path)
|
|
342
|
+
"#{ self.class.base_uri.gsub('http://', 'https://') }#{ path }"
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def path(key, arguments={})
|
|
346
|
+
escaped_arguments = arguments.inject({}){ |h, (k, v)| h[k] = URI.escape(v.to_s); h }
|
|
347
|
+
paths[key] % escaped_arguments
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def paths
|
|
351
|
+
@paths ||= YAML.load(File.read(paths_config_file))
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def paths_config_file
|
|
355
|
+
@paths_config_file ||= File.join(File.dirname(File.expand_path(__FILE__)), '../config/paths.yml')
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def wrap_with_array(*args)
|
|
359
|
+
yield.tap do |response|
|
|
360
|
+
obj = args[0..-2].inject(response){ |r, arg| r.try(:[], arg) }
|
|
361
|
+
|
|
362
|
+
if obj && obj[args.last] && obj[args.last].is_a?(Hash)
|
|
363
|
+
obj[args.last] = Array.wrap(obj[args.last])
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|