pigeons 0.0.1pre

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.
@@ -0,0 +1,429 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+
6
+ # To test:
7
+ # 1) Bases (check)
8
+ # 2) Conditions
9
+ # 3) Flights (Cohorts)
10
+ # 4) Bit more complexity?
11
+ # 5) Send
12
+ # 6) "signup", aye!
13
+
14
+ class PigeonExtensions < Pigeons::Extension
15
+
16
+ # Match an ownership
17
+ condition /^own \s+ (?<property>\w+)/ix do |scope, match|
18
+ # p [ "Pigeon Extension::Condition", "#{item} matched conditional" ]
19
+
20
+ scope.where([ "property LIKE ?", "%#{match[:property].singularize}%" ])
21
+ end
22
+
23
+ condition /eaten/i do |scope, match|
24
+ scope.where(eaten: true)
25
+ end
26
+
27
+ condition /slept/i do |scope, match|
28
+ scope.where(slept: true)
29
+ end
30
+
31
+ base /^(?<color>\w+) \s+ dragon[s]?$/ix do |match|
32
+ Dragon.scoped.where(color: match[:color])
33
+ end
34
+
35
+ base /^the pixies$/i do |match|
36
+ Pixie.scoped
37
+ end
38
+
39
+ event /^hatching$/ do |scope, time, match|
40
+ scope.where("hatched_at < ?", time)
41
+ end
42
+
43
+ event /^defeating (?<orc_name>.*)$/ do |scope, time, match|
44
+
45
+ source_arel = scope.arel_table
46
+ battle_arel = Battle.arel_table
47
+ orc_arel = Orc.arel_table
48
+
49
+ scope.where(
50
+ Battle.joins(:orc).where(
51
+ # match to dragon
52
+ battle_arel[:dragon_id].eq(source_arel[:id]).and(
53
+ orc_arel[:name].eq(match[:orc_name])
54
+ ).and(
55
+ battle_arel[:created_at].lt(time)
56
+ ).and(
57
+ battle_arel[:is_dragon_victor].eq(true)
58
+ )
59
+ ).exists
60
+ )
61
+ end
62
+
63
+ event /^leveling up$/ do |scope, time, match|
64
+
65
+ source_arel = scope.arel_table
66
+ level_arel = Level.arel_table
67
+
68
+ scope.where(
69
+ Level.where(
70
+ level_arel[:pixie_id].eq(source_arel[:id]).and(
71
+ level_arel[:created_at].lt(time)
72
+ )
73
+ ).exists
74
+ )
75
+ end
76
+
77
+ end
78
+
79
+ class TestPigeons < Test::Unit::TestCase
80
+ include Mocha
81
+
82
+ context "when the config has issues" do
83
+ should "raise unknown letter type" do
84
+ Pigeons::Settings.pigeon_config_file = "ignored"
85
+ File.stubs(:exists? => true)
86
+
87
+ # Nonexistant
88
+ PigeonMailer.stubs(respond_to?: false)
89
+ File.stubs(:read => { flights: { aflight: [ "dragon gets a nonexistent letter" ] } }.to_json)
90
+
91
+ assert_raise Pigeons::PigeonError::PigeonFlightConfigError do
92
+ Pigeons.assemble
93
+ end
94
+
95
+ # No idea what class
96
+ PigeonMailer.stubs(respond_to?: true)
97
+ File.stubs(:read => { flights: { aflight: [ "rhinos gets a welcome letter" ] } }.to_json)
98
+ end
99
+ end
100
+
101
+ # These test flights will get run and we'll check the resultant SQL
102
+ # These are, obstensibly end-to-end tests
103
+ test_flights = [
104
+ # Test basic letter
105
+ {
106
+ name: "all dragons get welcome",
107
+ config: { flights: { aflight: [ "dragons gets a welcome letter" ] } },
108
+ # Simply a check to make sure we didn't send this letter type
109
+ expected: [ letter_not_exists(simple_scope(Dragon.scoped), "welcome") ]
110
+ },
111
+ # Test a different base
112
+ {
113
+ name: "all orcs get goodbye",
114
+ config: { flights: { aflight: [ "orcs get a goodbye letter" ] } },
115
+ expected: [ letter_not_exists(simple_scope(Orc.scoped), "goodbye") ]
116
+ },
117
+ # Test relative time (after hours)
118
+ {
119
+ name: "all dragons get welcome after signup, hours",
120
+ config: { flights: { aflight: [ "dragons gets a welcome letter 24 hours after signup" ] } },
121
+ expected: [ letter_not_exists(simple_scope(Dragon.scoped), "welcome").where("created_at < ?", 24.hours.ago) ]
122
+ },
123
+ # Test relative time (after days)
124
+ {
125
+ name: "all dragons get welcome after signup, days",
126
+ config: { flights: { aflight: [ "dragons gets a welcome letter 2 days after signup" ] } },
127
+ expected: [ letter_not_exists(simple_scope(Dragon.scoped), "welcome").where("created_at < ?", 2.days.ago) ]
128
+ },
129
+ # Test recurring (every)
130
+ {
131
+ name: "all dragons get welcome after signup every weeks",
132
+ config: { flights: { aflight: [ "dragons get a welcome letter every 3 weeks after signup" ] } },
133
+ expected: [ simple_scope(Dragon.scoped).where(
134
+ PigeonLetter.where(
135
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
136
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
137
+ ).and(
138
+ PigeonLetter.arel_table[:created_at].gt(3.weeks.ago)
139
+ ).and(
140
+ PigeonLetter.arel_table[:letter_type].eq("welcome")
141
+ )
142
+ ).exists.not
143
+ ).where("created_at < ?", 3.weeks.ago) ]
144
+ },
145
+ # Now, let's try an after
146
+ {
147
+ name: "all dragons get welcomed then fired",
148
+ config: { flights: { aflight: [ "dragons get a welcome letter 2 seconds after signup",
149
+ "then get a fired letter 2 hours after that" ] } },
150
+ expected: [ letter_not_exists(simple_scope(Dragon.scoped), "welcome").where("created_at < ?", 2.seconds.ago),
151
+ letter_not_exists(simple_scope(Dragon.scoped), "fired").where(
152
+ PigeonLetter.where(
153
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
154
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
155
+ ).and(
156
+ PigeonLetter.arel_table[:letter_type].in(["welcome"])
157
+ )
158
+ ).exists
159
+ ).where(
160
+ PigeonLetter.where(
161
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
162
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
163
+ ).and(
164
+ PigeonLetter.arel_table[:letter_type].in(["welcome"])
165
+ ).and(
166
+ PigeonLetter.arel_table[:created_at].gt(2.hours.ago)
167
+ )
168
+ ).exists.not
169
+ )
170
+ ]
171
+ },
172
+ # Let's add a condition
173
+ {
174
+ name: "all dragons who own lairs get taxed",
175
+ config: { flights: { aflight: [ "dragons who own lairs get a tax letter every year" ] } },
176
+ expected: [ simple_scope(Dragon.scoped).where(
177
+ PigeonLetter.where(
178
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
179
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
180
+ ).and(
181
+ PigeonLetter.arel_table[:created_at].gt(1.year.ago)
182
+ ).and(
183
+ PigeonLetter.arel_table[:letter_type].eq("tax")
184
+ )
185
+ ).exists.not
186
+ ).where("property LIKE '%lair%'") ]
187
+ },
188
+ # Let's add a simple base
189
+ {
190
+ name: "all pixies",
191
+ config: { flights: { aflight: [ "the pixies get a punk rock letter" ] } },
192
+ expected: [ letter_not_exists(simple_scope(Pixie.scoped), "punk_rock") ]
193
+ },
194
+ # Let's add a complex base
195
+ {
196
+ name: "red dragon sadness",
197
+ config: { flights: { aflight: [ "red dragons get a hate letter every day after signup" ] } },
198
+ expected: [ simple_scope(Dragon.scoped.where(color: 'red')).where(
199
+ PigeonLetter.where(
200
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
201
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
202
+ ).and(
203
+ PigeonLetter.arel_table[:created_at].gt(1.day.ago)
204
+ ).and(
205
+ PigeonLetter.arel_table[:letter_type].eq("hate")
206
+ )
207
+ ).exists.not
208
+ ).where(["created_at < ?", 1.day.ago]) ]
209
+ },
210
+ # Now let's test complex running conditions
211
+ {
212
+ name: "running conditions",
213
+ config: { flights: { aflight: [ "dragons get a hello letter after signup",
214
+ "then who've eaten get a food letter 1 hour after that",
215
+ "then who've slept get a sleep letter after that",
216
+ "then get a goodnight letter 30 minutes after that",
217
+ "then gets a nightcap letter" ] } }, # Note, these running conditions are complicated
218
+ expected: [ letter_not_exists(simple_scope(Dragon.scoped), "hello").where("created_at < ?", Time.now),
219
+ letter_not_exists(simple_scope(Dragon.scoped).where(eaten: true), "food").where(
220
+ PigeonLetter.where(
221
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
222
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
223
+ ).and(
224
+ PigeonLetter.arel_table[:letter_type].in(["hello"])
225
+ )
226
+ ).exists
227
+ ).where(
228
+ PigeonLetter.where(
229
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
230
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
231
+ ).and(
232
+ PigeonLetter.arel_table[:letter_type].in(["hello"])
233
+ ).and(
234
+ PigeonLetter.arel_table[:created_at].gt(1.hour.ago)
235
+ )
236
+ ).exists.not
237
+ ),
238
+ letter_not_exists(simple_scope(Dragon.scoped).where(slept: true), "sleep").where(
239
+ PigeonLetter.where(
240
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
241
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
242
+ ).and(
243
+ PigeonLetter.arel_table[:letter_type].in(["hello","food"])
244
+ )
245
+ ).exists
246
+ ).where(
247
+ PigeonLetter.where(
248
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
249
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
250
+ ).and(
251
+ PigeonLetter.arel_table[:letter_type].in(["hello","food"])
252
+ ).and(
253
+ PigeonLetter.arel_table[:created_at].gt(Time.now)
254
+ )
255
+ ).exists.not
256
+ ),
257
+ letter_not_exists(simple_scope(Dragon.scoped), "goodnight").where(
258
+ PigeonLetter.where(
259
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
260
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
261
+ ).and(
262
+ PigeonLetter.arel_table[:letter_type].in(["hello","food","sleep"])
263
+ )
264
+ ).exists
265
+ ).where(
266
+ PigeonLetter.where(
267
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
268
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
269
+ ).and(
270
+ PigeonLetter.arel_table[:letter_type].in(["hello","food","sleep"])
271
+ ).and(
272
+ PigeonLetter.arel_table[:created_at].gt(30.minutes.ago)
273
+ )
274
+ ).exists.not
275
+ ),
276
+ letter_not_exists(simple_scope(Dragon.scoped), "nightcap").where(
277
+ PigeonLetter.where(
278
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
279
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
280
+ ).and(
281
+ PigeonLetter.arel_table[:letter_type].in(["goodnight"])
282
+ )
283
+ ).exists
284
+ ).where(
285
+ PigeonLetter.where(
286
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
287
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
288
+ ).and(
289
+ PigeonLetter.arel_table[:letter_type].in(["goodnight"])
290
+ ).and(
291
+ PigeonLetter.arel_table[:created_at].gt(Time.now)
292
+ )
293
+ ).exists.not
294
+ ) ]
295
+ },
296
+ # Finally, we're going to test events
297
+ # First, simply
298
+ {
299
+ name: "all dragons get a birth certificate letter after hatching",
300
+ config: { flights: { aflight: [ "all dragons get a birth certificate letter after hatching" ] } },
301
+ expected: [ letter_not_exists(simple_scope(Dragon.scoped), "birth_certificate").where("hatched_at < ?", Time.now) ]
302
+ },
303
+ # Now, we're test a more complex event
304
+ {
305
+ name: "all dragons get a congratulations letter after defeating Hodor.",
306
+ config: { flights: { aflight: [ "all dragons get a congratulations letter after defeating Hodor." ] } },
307
+ expected: [ letter_not_exists(simple_scope(Dragon.scoped), "congratulations").where(
308
+ Battle.joins(:orc).where(
309
+ Battle.arel_table[:dragon_id].eq(Dragon.arel_table[:id]).and(
310
+ Orc.arel_table[:name].eq("Hodor")
311
+ ).and(
312
+ Battle.arel_table[:created_at].lt(Time.now)
313
+ ).and(
314
+ Battle.arel_table[:is_dragon_victor].eq(true)
315
+ )
316
+ ).exists
317
+ ) ]
318
+ },
319
+ # A recurring event
320
+ {
321
+ name: "pixies getting a level up email",
322
+ config: { flights: { aflight: [ "pixies get a level up letter every time after leveling up" ] } },
323
+ expected: [ simple_scope(Pixie.scoped).where(
324
+ PigeonLetter.where(
325
+ PigeonLetter.arel_table[:cargo_id].eq(Pixie.arel_table[:id]).and(
326
+ PigeonLetter.arel_table[:cargo_type].eq(Pixie.arel_table.name.classify)
327
+ ).and(
328
+ PigeonLetter.arel_table[:created_at].gt(Time.now)
329
+ ).and(
330
+ PigeonLetter.arel_table[:letter_type].eq("level_up")
331
+ )
332
+ ).exists.not
333
+ ).where(
334
+ Level.where(
335
+ Level.arel_table[:pixie_id].eq(Pixie.arel_table[:id]).and(
336
+ Level.arel_table[:created_at].lt(Time.now)
337
+ )
338
+ ).exists
339
+ ) ]
340
+ },
341
+ # Finally, we're going to test base changes
342
+ {
343
+ name: "some dragons, but then other dragons",
344
+ config: { flights: { aflight: [ "red dragons get a red letter",
345
+ "then get a redder letter 1 hour after that",
346
+ "blue dragons get a blue letter",
347
+ "then get a bluer letter 1 fortnight" ] } },
348
+ expected: [ letter_not_exists(simple_scope(Dragon.where(color: 'red')), "red"),
349
+ letter_not_exists(simple_scope(Dragon.where(color: 'red')), "redder").where(
350
+ PigeonLetter.where(
351
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
352
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
353
+ ).and(
354
+ PigeonLetter.arel_table[:letter_type].in(["red"])
355
+ )
356
+ ).exists
357
+ ).where(
358
+ PigeonLetter.where(
359
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
360
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
361
+ ).and(
362
+ PigeonLetter.arel_table[:letter_type].in(["red"])
363
+ ).and(
364
+ PigeonLetter.arel_table[:created_at].gt(1.hour.ago)
365
+ )
366
+ ).exists.not
367
+ ),
368
+ letter_not_exists(simple_scope(Dragon.where(color: 'blue')), "blue"),
369
+ letter_not_exists(simple_scope(Dragon.where(color: 'blue')), "bluer").where(
370
+ PigeonLetter.where(
371
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
372
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
373
+ ).and(
374
+ PigeonLetter.arel_table[:letter_type].in(["blue"])
375
+ )
376
+ ).exists
377
+ ).where(
378
+ PigeonLetter.where(
379
+ PigeonLetter.arel_table[:cargo_id].eq(Dragon.arel_table[:id]).and(
380
+ PigeonLetter.arel_table[:cargo_type].eq(Dragon.arel_table.name.classify)
381
+ ).and(
382
+ PigeonLetter.arel_table[:letter_type].in(["blue"])
383
+ ).and(
384
+ PigeonLetter.arel_table[:created_at].gt(1.fortnight.ago)
385
+ )
386
+ ).exists.not
387
+ ) ]
388
+ },
389
+ # Next, we'll test two flights
390
+ {
391
+ name: "two flights - some dragons, but then other dragons",
392
+ config: { flights: { aflight: [ "red dragons get a red letter" ], bflight: [ "red dragons get a blue letter" ] } },
393
+ expected: {
394
+ aflight: [ letter_not_exists(simple_scope(Dragon.where(color: 'red'),2,0), "red") ],
395
+ bflight: [ letter_not_exists(simple_scope(Dragon.where(color: 'red'),2,1), "blue") ]
396
+ }
397
+ }
398
+ ]
399
+
400
+ context "in end-to-end test flights" do
401
+ setup do
402
+ Pigeons::Settings.pigeon_config_file = "ignored"
403
+ File.stubs(:exists? => true)
404
+
405
+ PigeonMailer.stubs(respond_to?: true)
406
+ ::Time.stubs(now: ::NOW) # Make this static for a test
407
+ ::Time.stubs(current: ::CURRENT)
408
+ end
409
+ puts test_flights.map { |flight| flight[:config][:flights][:aflight].join("\n") }.join("\n")
410
+
411
+ test_flights.each do |flight_test|
412
+ config = flight_test[:config]
413
+ expected = flight_test[:expected]
414
+
415
+ should "for #{flight_test[:name]}" do
416
+ File.stubs(:read => config.to_json)
417
+ flights = Pigeons.assemble
418
+ if flights['bflight'].nil? # Test just one
419
+ assert_same_elements expected.map { |e| e.to_sql }, flights['aflight'].map { |l| l[:scope].to_sql }
420
+ else
421
+ assert_same_elements expected[:aflight].map { |e| e.to_sql }, flights['aflight'].map { |l| l[:scope].to_sql }
422
+ assert_same_elements expected[:bflight].map { |e| e.to_sql }, flights['bflight'].map { |l| l[:scope].to_sql }
423
+ end
424
+ end
425
+ end
426
+
427
+ end
428
+
429
+ end
metadata ADDED
@@ -0,0 +1,200 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pigeons
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1pre
5
+ prerelease: 5
6
+ platform: ruby
7
+ authors:
8
+ - Geoff Hayes
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activerecord
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: actionmailer
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: mocha
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: shoulda
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: test-unit
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rake
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: sqlite3
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
142
+ description: Pigeons makes it a breeze to send your users lifecycle e-mails.
143
+ email:
144
+ - geoff@safeshepherd.com
145
+ executables: []
146
+ extensions: []
147
+ extra_rdoc_files: []
148
+ files:
149
+ - .gitignore
150
+ - Gemfile
151
+ - LICENSE.txt
152
+ - README.md
153
+ - Rakefile
154
+ - lib/generators/pigeons/install_generator.rb
155
+ - lib/generators/pigeons/templates/pigeon_letter.rb
156
+ - lib/generators/pigeons/templates/pigeon_letter_migration.rb
157
+ - lib/generators/pigeons/templates/pigeon_mailer.rb
158
+ - lib/generators/pigeons/templates/pigeons.json
159
+ - lib/pigeons.rb
160
+ - lib/pigeons/checks.rb
161
+ - lib/pigeons/elements.rb
162
+ - lib/pigeons/errors.rb
163
+ - lib/pigeons/extensions.rb
164
+ - lib/pigeons/logger.rb
165
+ - lib/pigeons/pigeons.rb
166
+ - lib/pigeons/pigeons_tasks.rb
167
+ - lib/pigeons/scope.rb
168
+ - lib/pigeons/version.rb
169
+ - pigeons.gemspec
170
+ - tasks/pigeons.rake
171
+ - test/test_helper.rb
172
+ - test/test_pigeons.rb
173
+ homepage: https://github.com/hayesgm/pigeons
174
+ licenses: []
175
+ post_install_message:
176
+ rdoc_options: []
177
+ require_paths:
178
+ - lib
179
+ required_ruby_version: !ruby/object:Gem::Requirement
180
+ none: false
181
+ requirements:
182
+ - - ! '>='
183
+ - !ruby/object:Gem::Version
184
+ version: '0'
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ! '>'
189
+ - !ruby/object:Gem::Version
190
+ version: 1.3.1
191
+ requirements: []
192
+ rubyforge_project:
193
+ rubygems_version: 1.8.25
194
+ signing_key:
195
+ specification_version: 3
196
+ summary: Pigeons provides an extensible way to send our lifecycle e-mails through
197
+ simple human-readable syntax
198
+ test_files:
199
+ - test/test_helper.rb
200
+ - test/test_pigeons.rb