saucy 0.8.5 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ 0.9.0
2
+
3
+ Added billed observer event.
4
+
5
+ Extracted and renamed the observer/notification code.
6
+ If you register any observers, you should update your code from:
7
+
8
+ Saucy::Configuration.observe(my_observer)
9
+
10
+ to:
11
+
12
+ Saucy::Notifications.register_observer(my_observer)
13
+
14
+ 0.8.6
15
+
16
+ For each notification event, only notify observers that respond_to that event.
17
+
18
+ 0.8.5
19
+
20
+ Added plan_downgraded observer event.
21
+
1
22
  0.8.3
2
23
 
3
24
  Added coupon support. In your app, add the following migration:
@@ -9,8 +9,8 @@ class AccountsController < ApplicationController
9
9
  def new
10
10
  @plan = Plan.find(params[:plan_id])
11
11
  @signup = Signup.new
12
- Saucy::Configuration.notify("plan_viewed", :request => request,
13
- :plan => @plan)
12
+ Saucy::Notifications.notify_observers("plan_viewed", :request => request,
13
+ :plan => @plan)
14
14
  end
15
15
 
16
16
  def create
@@ -22,8 +22,8 @@ class AccountsController < ApplicationController
22
22
  @signup.plan = @plan
23
23
 
24
24
  if @signup.save
25
- Saucy::Configuration.notify("account_created", :request => request,
26
- :account => @signup.account)
25
+ Saucy::Notifications.notify_observers("account_created", :request => request,
26
+ :account => @signup.account)
27
27
  flash[:success] = "Account was created."
28
28
  sign_in @signup.user
29
29
  redirect_to new_account_project_path(@signup.account)
@@ -32,10 +32,10 @@ class PlansController < ApplicationController
32
32
  end
33
33
 
34
34
  def notify_observers(event_name, from_plan, to_plan)
35
- Saucy::Configuration.notify(event_name,
36
- :account => @account,
37
- :request => request,
38
- :from_plan => from_plan,
39
- :to_plan => to_plan)
35
+ Saucy::Notifications.notify_observers(event_name,
36
+ :account => @account,
37
+ :request => request,
38
+ :from_plan => from_plan,
39
+ :to_plan => to_plan)
40
40
  end
41
41
  end
@@ -6,24 +6,11 @@ module Saucy
6
6
  cattr_accessor :manager_email_address
7
7
  cattr_accessor :support_email_address
8
8
  cattr_accessor :merchant_account_id
9
- cattr_accessor :observers
10
9
 
11
10
  def initialize
12
11
  @@manager_email_address = 'manager@example.com'
13
12
  @@support_email_address = 'support@example.com'
14
- @@layouts = Layouts.new
15
- @@observers = []
16
- end
17
-
18
- def self.observe(observer)
19
- @@observers << observer
20
- end
21
-
22
- def self.notify(event, data)
23
- @@observers.each do |observer|
24
- observer.send(event, data)
25
- end
13
+ @@layouts = Layouts.new
26
14
  end
27
15
  end
28
16
  end
29
-
@@ -0,0 +1,21 @@
1
+ require 'saucy/layouts'
2
+
3
+ module Saucy
4
+ module Notifications
5
+ @@observers = []
6
+
7
+ def self.clear_observers
8
+ @@observers = []
9
+ end
10
+
11
+ def self.register_observer(observer)
12
+ @@observers << observer
13
+ end
14
+
15
+ def self.notify_observers(event, data)
16
+ @@observers.each do |observer|
17
+ observer.send(event, data) if observer.respond_to?(event)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -229,6 +229,7 @@ module Saucy
229
229
  BillingMailer.problem(account, account.subscription.transactions.last).deliver!
230
230
  else
231
231
  BillingMailer.receipt(account, account.subscription.transactions.last).deliver!
232
+ Saucy::Notifications.notify_observers("billed", :account => account)
232
233
  end
233
234
  end
234
235
  end
data/lib/saucy.rb CHANGED
@@ -5,6 +5,7 @@ require 'saucy/project'
5
5
  require 'saucy/plan'
6
6
  require 'saucy/account_authorization'
7
7
  require 'saucy/configuration'
8
+ require 'saucy/notifications'
8
9
  require 'saucy/engine'
9
10
  require 'saucy/projects_controller'
10
11
  require 'saucy/coupons'
@@ -39,11 +39,11 @@ describe Invitation, "saved" do
39
39
  subject.account_name.should == subject.account.name
40
40
  end
41
41
 
42
- it "defauls new user email to invited email" do
42
+ it "defaults new user email to invited email" do
43
43
  subject.new_user_email.should == subject.email
44
44
  end
45
45
 
46
- it "defauls existing user email to invited email" do
46
+ it "defaults existing user email to invited email" do
47
47
  subject.authenticating_user_email.should == subject.email
48
48
  end
49
49
 
@@ -211,11 +211,11 @@ describe Invitation, "saved" do
211
211
  subject.account_name.should == subject.account.name
212
212
  end
213
213
 
214
- it "defauls new user email to invited email" do
214
+ it "defaults new user email to invited email" do
215
215
  subject.new_user_email.should == subject.email
216
216
  end
217
217
 
218
- it "defauls existing user email to invited email" do
218
+ it "defaults existing user email to invited email" do
219
219
  subject.authenticating_user_email.should == subject.email
220
220
  end
221
221
 
@@ -306,78 +306,95 @@ describe Account, "with a paid subscription" do
306
306
  end
307
307
  end
308
308
 
309
- it "gets marked as not past due and updates its next_billing_date when the subscription is active after its billing date" do
310
- subscription = FakeBraintree.subscriptions[subject.subscription_token]
311
- subscription["status"] = Braintree::Subscription::Status::Active
312
- subscription["next_billing_date"] = 2.months.from_now
313
- FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
314
- :subscription_id => subject.subscription_token }
315
- subscription["transactions"] = [FakeBraintree.generated_transaction]
316
-
317
- Timecop.travel(subject.next_billing_date + 1.day) do
318
- Account.update_subscriptions!
319
- subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
320
- subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
309
+ context "when billing didn't work" do
310
+ let(:subscription) { FakeBraintree.subscriptions[subject.subscription_token] }
311
+
312
+ before do
313
+ subscription["status"] = Braintree::Subscription::Status::PastDue
314
+ subscription["next_billing_date"] = 2.months.from_now
315
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
316
+ :subscription_id => subject.subscription_token }
317
+ subscription["transactions"] = [FakeBraintree.generated_transaction]
321
318
  end
322
- end
323
319
 
324
- it "receives a receipt email at it's billing email with transaction details" do
325
- subscription = FakeBraintree.subscriptions[subject.subscription_token]
326
- subscription["status"] = Braintree::Subscription::Status::Active
327
- subscription["next_billing_date"] = 2.months.from_now
328
- FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
329
- :subscription_id => subject.subscription_token }
330
- subscription["transactions"] = [FakeBraintree.generated_transaction]
320
+ it "receives a receipt email at it's billing email with a notice that it failed when billing didn't work" do
321
+ Timecop.travel(subject.next_billing_date + 1.day) do
322
+ ActionMailer::Base.deliveries.clear
331
323
 
332
- Timecop.travel(subject.next_billing_date + 1.day) do
333
- ActionMailer::Base.deliveries.clear
324
+ Account.update_subscriptions!
334
325
 
335
- Account.update_subscriptions!
326
+ ActionMailer::Base.deliveries.any? do |email|
327
+ email.to == [subject.billing_email] &&
328
+ email.subject =~ /problem/i
329
+ end.should be
330
+ end
331
+ end
336
332
 
337
- ActionMailer::Base.deliveries.any? do |email|
338
- email.to == [subject.billing_email] &&
339
- email.subject =~ /receipt/i
340
- end.should be
333
+ it "does not notify observers of billing when billing didn't work" do
334
+ Timecop.travel(subject.next_billing_date + 1.day) do
335
+ Account.update_subscriptions!
336
+ should_not notify_observers("billed", :account => subject)
337
+ end
341
338
  end
342
339
  end
343
340
 
344
- it "doesn't receive a receipt email when it's already been billed" do
345
- subscription = FakeBraintree.subscriptions[subject.subscription_token]
346
- subscription["status"] = Braintree::Subscription::Status::Active
347
- subscription["next_billing_date"] = 2.months.from_now
348
- FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
349
- :subscription_id => subject.subscription_token }
350
- subscription["transactions"] = [FakeBraintree.generated_transaction]
341
+ context "and an active subscription due 2 months from now" do
342
+ let(:subscription) { FakeBraintree.subscriptions[subject.subscription_token] }
351
343
 
352
- Timecop.travel(subject.next_billing_date - 1.day) do
353
- ActionMailer::Base.deliveries.clear
344
+ before do
345
+ subscription["status"] = Braintree::Subscription::Status::Active
346
+ subscription["next_billing_date"] = 2.months.from_now
347
+ FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Settled,
348
+ :subscription_id => subject.subscription_token }
349
+ subscription["transactions"] = [FakeBraintree.generated_transaction]
350
+ end
354
351
 
355
- Account.update_subscriptions!
352
+ it "gets marked as not past due and updates its next_billing_date when the subscription is active after its billing date" do
353
+ Timecop.travel(subject.next_billing_date + 1.day) do
354
+ Account.update_subscriptions!
355
+ subject.reload.subscription_status.should == Braintree::Subscription::Status::Active
356
+ subject.next_billing_date.to_s.should == subscription["next_billing_date"].to_s
357
+ end
358
+ end
359
+
360
+ it "receives a receipt email at it's billing email with transaction details" do
361
+ Timecop.travel(subject.next_billing_date + 1.day) do
362
+ ActionMailer::Base.deliveries.clear
363
+
364
+ Account.update_subscriptions!
356
365
 
357
- ActionMailer::Base.deliveries.select do |email|
358
- email.to == [subject.billing_email] &&
359
- email.subject =~ /receipt/i
360
- end.should be_empty
366
+ ActionMailer::Base.deliveries.any? do |email|
367
+ email.to == [subject.billing_email] &&
368
+ email.subject =~ /receipt/i
369
+ end.should be
370
+ end
361
371
  end
362
- end
363
372
 
364
- it "receives a receipt email at it's billing email with a notice that it failed when billing didn't work" do
365
- subscription = FakeBraintree.subscriptions[subject.subscription_token]
366
- subscription["status"] = Braintree::Subscription::Status::PastDue
367
- subscription["next_billing_date"] = 2.months.from_now
368
- FakeBraintree.transaction = { :status => Braintree::Transaction::Status::Failed,
369
- :subscription_id => subject.subscription_token }
370
- subscription["transactions"] = [FakeBraintree.generated_transaction]
373
+ it "notifies observers of billing" do
374
+ Timecop.travel(subject.next_billing_date + 1.day) do
375
+ Account.update_subscriptions!
376
+ should notify_observers("billed", :account => subject)
377
+ end
378
+ end
371
379
 
372
- Timecop.travel(subject.next_billing_date + 1.day) do
373
- ActionMailer::Base.deliveries.clear
380
+ it "doesn't receive a receipt email when it's already been billed" do
381
+ Timecop.travel(subject.next_billing_date - 1.day) do
382
+ ActionMailer::Base.deliveries.clear
374
383
 
375
- Account.update_subscriptions!
384
+ Account.update_subscriptions!
385
+
386
+ ActionMailer::Base.deliveries.select do |email|
387
+ email.to == [subject.billing_email] &&
388
+ email.subject =~ /receipt/i
389
+ end.should be_empty
390
+ end
391
+ end
376
392
 
377
- ActionMailer::Base.deliveries.any? do |email|
378
- email.to == [subject.billing_email] &&
379
- email.subject =~ /problem/i
380
- end.should be
393
+ it "does not notify observers of billing when it's already been billed" do
394
+ Timecop.travel(subject.next_billing_date - 1.day) do
395
+ Account.update_subscriptions!
396
+ should_not notify_observers("billed", :account => subject)
397
+ end
381
398
  end
382
399
  end
383
400
  end
@@ -17,23 +17,6 @@ describe Saucy::Configuration do
17
17
  subject.merchant_account_id.should be_nil
18
18
  end
19
19
 
20
- it "can listen for events" do
21
- observer = stub("an observer")
22
- cleanup_observers do
23
- Saucy::Configuration.observe(observer)
24
- Saucy::Configuration.observers.should include(observer)
25
- end
26
- end
27
-
28
- it "can notify observers" do
29
- observer = stub("an observer", :some_event => nil)
30
- cleanup_observers do
31
- Saucy::Configuration.observe(observer)
32
- Saucy::Configuration.notify("some_event", "some_data")
33
- observer.should have_received("some_event").with("some_data")
34
- end
35
- end
36
-
37
20
  it "can assign a manager email address" do
38
21
  old_address = subject.manager_email_address
39
22
  begin
@@ -53,10 +36,4 @@ describe Saucy::Configuration do
53
36
  subject.support_email_address = old_address
54
37
  end
55
38
  end
56
-
57
- def cleanup_observers
58
- yield
59
- ensure
60
- subject.observers.clear
61
- end
62
39
  end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Saucy::Notifications do
4
+ let(:observer_class) do
5
+ Class.new do
6
+ def some_event(*args)
7
+ end
8
+ end
9
+ end
10
+
11
+ it "can notify observers" do
12
+ observer = observer_class.new
13
+ observer.stubs(:some_event => true)
14
+
15
+ cleanup_observers do
16
+ Saucy::Notifications.register_observer(observer)
17
+ Saucy::Notifications.notify_observers("some_event", "some_data")
18
+ observer.should have_received("some_event").with("some_data")
19
+ end
20
+ end
21
+
22
+ it "only notifies observers that respond to a given event" do
23
+ observer = Object.new
24
+
25
+ cleanup_observers do
26
+ expect do
27
+ Saucy::Notifications.register_observer(observer)
28
+ Saucy::Notifications.notify_observers("some_event", "some_data")
29
+ end.should_not raise_error
30
+ end
31
+ end
32
+
33
+ it "can clear its observers" do
34
+ observer = observer_class.new
35
+ observer.stubs(:some_event => true)
36
+
37
+ cleanup_observers do
38
+ Saucy::Notifications.register_observer(observer)
39
+ Saucy::Notifications.clear_observers
40
+ Saucy::Notifications.notify_observers("some_event", "some_data")
41
+ observer.should_not have_received("some_event").with("some_data")
42
+ end
43
+ end
44
+
45
+ def cleanup_observers
46
+ yield
47
+ ensure
48
+ subject.clear_observers
49
+ end
50
+ end
@@ -24,12 +24,20 @@ class RecordedEvent
24
24
  end
25
25
 
26
26
  class RecordingObserver
27
- attr_reader :events
27
+ def self.instance
28
+ @@instance ||= self.new
29
+ end
30
+
31
+ attr_accessor :events
28
32
 
29
33
  def initialize
30
34
  @events = []
31
35
  end
32
36
 
37
+ def respond_to?(message)
38
+ true
39
+ end
40
+
33
41
  def method_missing(name, data)
34
42
  @events << RecordedEvent.new(name, data)
35
43
  end
@@ -46,12 +54,14 @@ RSpec::Matchers.define :notify_observers do |event_name, data|
46
54
  end
47
55
 
48
56
  def recorder
49
- Saucy::Configuration.observers.last
57
+ RecordingObserver.instance
50
58
  end
51
59
  end
52
60
 
53
61
  RSpec.configure do |config|
54
62
  config.before do
55
- Saucy::Configuration.observers = [RecordingObserver.new]
63
+ Saucy::Notifications.clear_observers
64
+ Saucy::Notifications.register_observer(RecordingObserver.instance)
65
+ RecordingObserver.instance.events = []
56
66
  end
57
67
  end
metadata CHANGED
@@ -1,19 +1,21 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saucy
3
3
  version: !ruby/object:Gem::Version
4
- hash: 53
4
+ hash: 59
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 8
9
- - 5
10
- version: 0.8.5
8
+ - 9
9
+ - 0
10
+ version: 0.9.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - thoughtbot, inc.
14
14
  - Joe Ferris
15
15
  - Mike Burns
16
16
  - Chad Pytel
17
+ - Jason Morrison
18
+ - Ben Orenstein
17
19
  autorequire:
18
20
  bindir: bin
19
21
  cert_chain: []
@@ -22,9 +24,7 @@ date: 2011-06-28 00:00:00 -04:00
22
24
  default_executable:
23
25
  dependencies:
24
26
  - !ruby/object:Gem::Dependency
25
- prerelease: false
26
- type: :runtime
27
- requirement: &id001 !ruby/object:Gem::Requirement
27
+ version_requirements: &id001 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -35,12 +35,12 @@ dependencies:
35
35
  - 11
36
36
  - 0
37
37
  version: 0.11.0
38
+ requirement: *id001
38
39
  name: clearance
39
- version_requirements: *id001
40
- - !ruby/object:Gem::Dependency
41
40
  prerelease: false
42
41
  type: :runtime
43
- requirement: &id002 !ruby/object:Gem::Requirement
42
+ - !ruby/object:Gem::Dependency
43
+ version_requirements: &id002 !ruby/object:Gem::Requirement
44
44
  none: false
45
45
  requirements:
46
46
  - - ">="
@@ -50,12 +50,12 @@ dependencies:
50
50
  - 1
51
51
  - 2
52
52
  version: "1.2"
53
+ requirement: *id002
53
54
  name: formtastic
54
- version_requirements: *id002
55
- - !ruby/object:Gem::Dependency
56
55
  prerelease: false
57
56
  type: :runtime
58
- requirement: &id003 !ruby/object:Gem::Requirement
57
+ - !ruby/object:Gem::Dependency
58
+ version_requirements: &id003 !ruby/object:Gem::Requirement
59
59
  none: false
60
60
  requirements:
61
61
  - - ">="
@@ -66,12 +66,12 @@ dependencies:
66
66
  - 0
67
67
  - 3
68
68
  version: 3.0.3
69
+ requirement: *id003
69
70
  name: railties
70
- version_requirements: *id003
71
- - !ruby/object:Gem::Dependency
72
71
  prerelease: false
73
72
  type: :runtime
74
- requirement: &id004 !ruby/object:Gem::Requirement
73
+ - !ruby/object:Gem::Dependency
74
+ version_requirements: &id004 !ruby/object:Gem::Requirement
75
75
  none: false
76
76
  requirements:
77
77
  - - ">="
@@ -82,12 +82,12 @@ dependencies:
82
82
  - 6
83
83
  - 2
84
84
  version: 2.6.2
85
+ requirement: *id004
85
86
  name: braintree
86
- version_requirements: *id004
87
- - !ruby/object:Gem::Dependency
88
87
  prerelease: false
89
88
  type: :runtime
90
- requirement: &id005 !ruby/object:Gem::Requirement
89
+ - !ruby/object:Gem::Dependency
90
+ version_requirements: &id005 !ruby/object:Gem::Requirement
91
91
  none: false
92
92
  requirements:
93
93
  - - "="
@@ -98,12 +98,12 @@ dependencies:
98
98
  - 3
99
99
  - 3
100
100
  version: 1.3.3
101
+ requirement: *id005
101
102
  name: sham_rack
102
- version_requirements: *id005
103
- - !ruby/object:Gem::Dependency
104
103
  prerelease: false
105
104
  type: :runtime
106
- requirement: &id006 !ruby/object:Gem::Requirement
105
+ - !ruby/object:Gem::Dependency
106
+ version_requirements: &id006 !ruby/object:Gem::Requirement
107
107
  none: false
108
108
  requirements:
109
109
  - - ">="
@@ -114,12 +114,12 @@ dependencies:
114
114
  - 1
115
115
  - 2
116
116
  version: 1.1.2
117
+ requirement: *id006
117
118
  name: sinatra
118
- version_requirements: *id006
119
- - !ruby/object:Gem::Dependency
120
119
  prerelease: false
121
- type: :development
122
- requirement: &id007 !ruby/object:Gem::Requirement
120
+ type: :runtime
121
+ - !ruby/object:Gem::Dependency
122
+ version_requirements: &id007 !ruby/object:Gem::Requirement
123
123
  none: false
124
124
  requirements:
125
125
  - - "="
@@ -130,8 +130,10 @@ dependencies:
130
130
  - 2
131
131
  - 6
132
132
  version: 0.2.6
133
+ requirement: *id007
133
134
  name: aruba
134
- version_requirements: *id007
135
+ prerelease: false
136
+ type: :development
135
137
  description: Clearance-based Rails engine for Software as a Service (Saas) that provides account and project management
136
138
  email: support@thoughtbot.com
137
139
  executables: []
@@ -231,6 +233,7 @@ files:
231
233
  - lib/saucy/engine.rb
232
234
  - lib/saucy/fake_braintree.rb
233
235
  - lib/saucy/layouts.rb
236
+ - lib/saucy/notifications.rb
234
237
  - lib/saucy/plan.rb
235
238
  - lib/saucy/project.rb
236
239
  - lib/saucy/projects_controller.rb
@@ -283,7 +286,8 @@ files:
283
286
  - spec/models/subscription_spec.rb
284
287
  - spec/models/user_spec.rb
285
288
  - spec/route_extensions_spec.rb
286
- - spec/saucy_spec.rb
289
+ - spec/saucy/configuration_spec.rb
290
+ - spec/saucy/notifications_spec.rb
287
291
  - spec/scaffold/config/routes.rb
288
292
  - spec/spec_helper.rb
289
293
  - spec/support/authentication_helpers.rb