saucy 0.8.5 → 0.9.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.
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