event_sourced_record 0.0.1

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.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +25 -0
  3. data/Appraisals +8 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +131 -0
  7. data/Rakefile +10 -0
  8. data/event_sourced_record.gemspec +37 -0
  9. data/lib/event_sourced_record/calculator.rb +101 -0
  10. data/lib/event_sourced_record/event/event_type_config.rb +30 -0
  11. data/lib/event_sourced_record/event.rb +94 -0
  12. data/lib/event_sourced_record/version.rb +3 -0
  13. data/lib/event_sourced_record.rb +7 -0
  14. data/lib/generators/event_sourced_record/USAGE +8 -0
  15. data/lib/generators/event_sourced_record/calculator_generator.rb +19 -0
  16. data/lib/generators/event_sourced_record/event_generator.rb +40 -0
  17. data/lib/generators/event_sourced_record/event_sourced_record_generator.rb +41 -0
  18. data/lib/generators/event_sourced_record/observer_generator.rb +34 -0
  19. data/lib/generators/event_sourced_record/projection_generator.rb +50 -0
  20. data/lib/generators/event_sourced_record/templates/calculator.rb +7 -0
  21. data/lib/generators/event_sourced_record/templates/event_model.rb +13 -0
  22. data/lib/generators/event_sourced_record/templates/observer.rb +8 -0
  23. data/lib/generators/event_sourced_record/templates/projection_model.rb +15 -0
  24. data/lib/generators/rspec/service_generator.rb +15 -0
  25. data/lib/generators/rspec/templates/service_spec.rb +6 -0
  26. data/lib/generators/test_unit/service_generator.rb +15 -0
  27. data/lib/generators/test_unit/templates/service_test.rb +9 -0
  28. data/test/event_sourced_record/calculator_test.rb +111 -0
  29. data/test/event_sourced_record/event_test.rb +112 -0
  30. data/test/generators/calculator_generator_test.rb +25 -0
  31. data/test/generators/event_generator_test.rb +35 -0
  32. data/test/generators/event_sourced_record_generator_test.rb +38 -0
  33. data/test/generators/observer_generator_test.rb +19 -0
  34. data/test/generators/projection_generator_test.rb +36 -0
  35. data/test/generators/templates/application.rb +12 -0
  36. data/test/test_helper.rb +82 -0
  37. metadata +217 -0
@@ -0,0 +1,7 @@
1
+ class <%= class_name %> < EventSourcedRecord::Calculator
2
+ events :<%= event_name.pluralize %>
3
+
4
+ def advance_creation(event)
5
+
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ <% module_namespacing do -%>
2
+ class <%= event_class_name %> < ActiveRecord::Base
3
+ include EventSourcedRecord::Event
4
+
5
+ event_type :creation do
6
+ # attributes :user_id
7
+ #
8
+ # validates :user_id, presence: true
9
+ end
10
+ end
11
+ <% end -%>
12
+
13
+
@@ -0,0 +1,8 @@
1
+ class <%= class_name %> < ActiveRecord::Observer
2
+ observe :<%= event_name %>
3
+
4
+ def after_create(event)
5
+ <%= calculator_class_name %>.new(event).run.save!
6
+ end
7
+ end
8
+
@@ -0,0 +1,15 @@
1
+ <% module_namespacing do -%>
2
+ class <%= projection_class_name %> < <%= projection_parent_class_name.classify %>
3
+ <% attributes.select(&:reference?).each do |attribute| -%>
4
+ belongs_to :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %>
5
+ <% end -%>
6
+ has_many :<%= file_name %>_events
7
+ <% if attributes.any?(&:password_digest?) -%>
8
+ has_secure_password
9
+ <% end -%>
10
+
11
+ validates :uuid, uniqueness: true
12
+ end
13
+ <% end -%>
14
+
15
+
@@ -0,0 +1,15 @@
1
+ require 'generators/rspec'
2
+
3
+ module Rspec
4
+ module Generators # :nodoc:
5
+ class ServiceGenerator < Base # :nodoc:
6
+ source_root File.expand_path('../templates', __FILE__)
7
+ check_class_collision suffix: "ServiceTest"
8
+
9
+ def create_service_files
10
+ template 'service_spec.rb', File.join('spec/services', class_path, "#{file_name}_spec.rb")
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,6 @@
1
+ <% module_namespacing do -%>
2
+ RSpec.describe <%= class_name %> do
3
+ pending "add some examples to (or delete) #{__FILE__}"
4
+ end
5
+ <% end -%>
6
+
@@ -0,0 +1,15 @@
1
+ require 'rails/generators/test_unit'
2
+
3
+ module TestUnit # :nodoc:
4
+ module Generators # :nodoc:
5
+ class ServiceGenerator < Base # :nodoc:
6
+ source_root File.expand_path('../templates', __FILE__)
7
+ check_class_collision suffix: "ServiceTest"
8
+
9
+ def create_service_files
10
+ template 'service_test.rb', File.join('test/services', class_path, "#{file_name}_test.rb")
11
+ end
12
+ end
13
+ end
14
+ end
15
+
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+
3
+ <% module_namespacing do -%>
4
+ class <%= class_name %>Test < ActiveSupport::TestCase
5
+ # test "the truth" do
6
+ # assert true
7
+ # end
8
+ end
9
+ <% end -%>
@@ -0,0 +1,111 @@
1
+ require 'test_helper'
2
+
3
+ class EventSourcedRecord::CalculatorTest < MiniTest::Unit::TestCase
4
+ def setup
5
+ @event = SubscriptionEvent.creation.create!(
6
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 12345
7
+ )
8
+ assert @event.subscription_uuid
9
+ end
10
+
11
+ def test_run_when_the_sourced_record_doesnt_exist
12
+ calculator = SubscriptionCalculator.new(@event.subscription_uuid)
13
+ subscription = calculator.run
14
+ assert_equal(1, subscription.bottles_per_shipment)
15
+ assert_equal(6, subscription.bottles_left)
16
+ assert_equal(@event.subscription_uuid, subscription.uuid)
17
+ assert !subscription.persisted?
18
+ end
19
+
20
+ def test_run_when_the_sourced_record_already_exists
21
+ subscription = Subscription.create!(uuid: @event.subscription_uuid)
22
+ calculator = SubscriptionCalculator.new(@event.subscription_uuid)
23
+ subscription_prime = calculator.run
24
+ assert_equal(1, subscription_prime.bottles_per_shipment)
25
+ assert_equal(6, subscription_prime.bottles_left)
26
+ assert_equal(@event.subscription_uuid, subscription.uuid)
27
+ assert_equal(subscription.id, subscription_prime.id)
28
+ end
29
+
30
+ def test_run_and_save_when_the_sourced_record_doesnt_exist
31
+ calculator = SubscriptionCalculator.new(@event.subscription_uuid)
32
+ subscription = calculator.run.tap(&:save!)
33
+ assert_equal(1, subscription.bottles_per_shipment)
34
+ assert_equal(6, subscription.bottles_left)
35
+ assert_equal(@event.subscription_uuid, subscription.uuid)
36
+ assert subscription.persisted?
37
+ end
38
+
39
+ def test_run_and_save_when_the_sourced_record_already_exists
40
+ subscription = Subscription.create!(uuid: @event.subscription_uuid)
41
+ subscription_count = Subscription.count
42
+ calculator = SubscriptionCalculator.new(@event.subscription_uuid)
43
+ subscription_prime = calculator.run.tap(&:save!)
44
+ assert_equal(subscription_count, Subscription.count)
45
+ assert_equal(1, subscription_prime.bottles_per_shipment)
46
+ assert_equal(6, subscription_prime.bottles_left)
47
+ assert_equal(@event.subscription_uuid, subscription.uuid)
48
+ assert_equal(subscription.id, subscription_prime.id)
49
+ end
50
+
51
+ def test_run_with_a_different_event_class
52
+ event = SubscriptionEvent.creation.create!(
53
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 99999
54
+ )
55
+ subscription = SubscriptionCalculator.new(event.subscription_uuid).run.tap(&:save!)
56
+ assert_equal(6, subscription.bottles_left)
57
+ Shipment.create!(subscription_id: subscription.id, num_bottles: 3)
58
+ SubscriptionCalculator.new(event.subscription_uuid).run.tap(&:save!)
59
+ subscription.reload
60
+ assert_equal(3, subscription.bottles_left)
61
+ end
62
+
63
+ def test_initialize_by_projection_or_id_or_event_or_associated_event
64
+ event = SubscriptionEvent.creation.create!(
65
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 99999
66
+ )
67
+ subscription = SubscriptionCalculator.new(event).run.tap(&:save!)
68
+ shipment = Shipment.create!(
69
+ subscription_id: subscription.id, num_bottles: 3
70
+ )
71
+ lookups = [subscription, subscription.id, event, shipment]
72
+ lookups.each do |lookup|
73
+ calculator = SubscriptionCalculator.new(lookup)
74
+ assert_equal(
75
+ subscription, calculator.run,
76
+ "Can't find subscription with lookup #{lookup.inspect}"
77
+ )
78
+ end
79
+ end
80
+
81
+ def test_run_modifies_a_different_instance
82
+ event = SubscriptionEvent.creation.create!(
83
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 99999
84
+ )
85
+ subscription = SubscriptionCalculator.new(event).run.tap(&:save!)
86
+ subscription.bottles_left = 999
87
+ SubscriptionCalculator.new(subscription).run
88
+ assert_equal(999, subscription.bottles_left)
89
+ end
90
+
91
+ def test_last_event_time
92
+ event = SubscriptionEvent.creation.create!(
93
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 99999,
94
+ created_at: Time.utc(2015,1,1)
95
+ )
96
+ subscription = SubscriptionCalculator.new(event).run.tap(&:save!)
97
+ Shipment.create!(
98
+ subscription_id: subscription.id, num_bottles: 3,
99
+ created_at: Time.utc(2015,1,4,12)
100
+ )
101
+ calculator = SubscriptionCalculator.new(subscription)
102
+ result1 = calculator.run(last_event_time: Date.new(2015,1,1))
103
+ assert_equal(6, result1.bottles_left)
104
+ result2 = calculator.run(last_event_time: Time.utc(2015,1,4))
105
+ assert_equal(6, result2.bottles_left)
106
+ result3 = calculator.run(last_event_time: Time.utc(2015,1,5))
107
+ assert_equal(3, result3.bottles_left)
108
+ result4 = calculator.run(last_event_time: Date.new(2015,1,5))
109
+ assert_equal(3, result4.bottles_left)
110
+ end
111
+ end
@@ -0,0 +1,112 @@
1
+ require 'test_helper'
2
+
3
+ class EventSourcedRecord::EventTest < MiniTest::Unit::TestCase
4
+ def test_creation_auto_generates_uuid
5
+ event = SubscriptionEvent.creation.new
6
+ assert event.subscription_uuid
7
+ end
8
+
9
+ def test_creation_data_assignment
10
+ event = SubscriptionEvent.creation.new(
11
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 999
12
+ )
13
+ assert_equal(999, event.data['user_id'])
14
+ assert_equal(999, event.user_id)
15
+ end
16
+
17
+ def test_creation_accessors_on_reload
18
+ event = SubscriptionEvent.creation.new(
19
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 999
20
+ )
21
+ event.save!
22
+ event_prime = SubscriptionEvent.find(event.id)
23
+ assert_equal(999, event_prime.user_id)
24
+ end
25
+
26
+ def test_creation_reserved_attributes
27
+ event = SubscriptionEvent.creation.new(
28
+ subscription_uuid: 'asdf'
29
+ )
30
+ assert_equal('asdf', event.subscription_uuid)
31
+ assert_nil event.data['subscription_uuid']
32
+ end
33
+
34
+ def test_creation_dont_accept_random_attribute
35
+ assert_raises(ActiveRecord::UnknownAttributeError) do
36
+ SubscriptionEvent.creation.new(foo: 'bar')
37
+ end
38
+ assert_raises(ActiveRecord::UnknownAttributeError) do
39
+ SubscriptionEvent.change_settings.new(user_id: 123)
40
+ end
41
+ end
42
+
43
+ def test_creation_validation_errors
44
+ event = SubscriptionEvent.creation.new
45
+ assert !event.valid?
46
+ assert event.errors[:bottles_per_shipment].include?("can't be blank")
47
+ assert event.errors[:bottles_per_shipment].include?('is not a number')
48
+ assert event.errors[:bottles_purchased].include?("can't be blank")
49
+ assert event.errors[:bottles_purchased].include?('is not a number')
50
+ assert event.errors[:user_id].include?("can't be blank")
51
+ end
52
+
53
+ def test_creation_valid
54
+ event = SubscriptionEvent.creation.new(
55
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 999
56
+ )
57
+ assert_equal(6, event.bottles_purchased)
58
+ assert event.valid?
59
+ end
60
+
61
+ def test_creation_create
62
+ events_before = SubscriptionEvent.count
63
+ SubscriptionEvent.creation.create!(
64
+ bottles_per_shipment: 1, bottles_purchased: 6, user_id: 999
65
+ )
66
+ assert_equal(events_before + 1, SubscriptionEvent.count)
67
+ end
68
+
69
+ def test_creation_settable_attributes
70
+ event = SubscriptionEvent.creation.new
71
+ event.bottles_per_shipment = 1
72
+ assert_equal(1, event.bottles_per_shipment)
73
+ end
74
+
75
+ def test_change_settings_validation_errors
76
+ event = SubscriptionEvent.change_settings.new
77
+ assert !event.valid?
78
+ assert event.errors[:bottles_per_shipment].include?('is not a number')
79
+ end
80
+
81
+ def test_change_settings_valid
82
+ event = SubscriptionEvent.change_settings.new(:bottles_per_shipment => 2)
83
+ assert event.valid?
84
+ end
85
+
86
+ def test_new_without_scope
87
+ event = SubscriptionEvent.new(
88
+ event_type: 'creation', bottles_per_shipment: 1, bottles_purchased: 6,
89
+ user_id: 999
90
+ )
91
+ assert event.valid?
92
+ end
93
+
94
+ def test_event_type_required
95
+ event = SubscriptionEvent.new
96
+ assert !event.valid?
97
+ assert event.errors[:event_type].include?("can't be blank")
98
+ end
99
+
100
+ def test_event_type_inclusion
101
+ event = SubscriptionEvent.new(event_type: 'wrong')
102
+ assert !event.valid?
103
+ assert event.errors[:event_type].include?("is not a valid event type")
104
+ end
105
+
106
+ def test_event_type_unsettable
107
+ event = SubscriptionEvent.creation.new
108
+ assert_raises(EventSourcedRecord::Event::EventTypeImmutableError) do
109
+ event.event_type = 'change_settings'
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class EventSourcedRecord::CalculatorGeneratorTest < Rails::Generators::TestCase
4
+ destination 'tmp/calculator_generator_test'
5
+ setup :prepare_destination
6
+ tests EventSourcedRecord::CalculatorGenerator
7
+
8
+ setup do
9
+ @generate_calls = Hash.new { |h,k| h[k] = [] }
10
+ EventSourcedRecord::CalculatorGenerator.any_instance.stubs(:generate).with { |name, arg_string|
11
+ @generate_calls[name] << arg_string
12
+ }
13
+ run_generator %w(subscription_calculator)
14
+ end
15
+
16
+ test "creates a calculator" do
17
+ assert_file("app/services/subscription_calculator.rb") do |contents|
18
+ assert_match(
19
+ /class SubscriptionCalculator < EventSourcedRecord::Calculator/,
20
+ contents
21
+ )
22
+ assert_match(/def advance_creation\(event\)/, contents)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ require 'test_helper'
2
+
3
+ class EventSourcedRecord::EventGeneratorTest < Rails::Generators::TestCase
4
+ destination 'tmp/event_generator_test'
5
+ setup :prepare_destination
6
+ tests EventSourcedRecord::EventGenerator
7
+
8
+ setup do
9
+ @generate_calls = Hash.new { |h,k| h[k] = [] }
10
+ EventSourcedRecord::EventGenerator.any_instance.stubs(:generate).with { |name, arg_string|
11
+ @generate_calls[name] << arg_string
12
+ }
13
+ run_generator %w(
14
+ subscription_event
15
+ subscription_uuid:string:index
16
+ event_type:string
17
+ data:text
18
+ created_at:datetime
19
+ )
20
+ end
21
+
22
+ test "creates a migration for the event class" do
23
+ assert @generate_calls['migration'].include?(
24
+ "create_subscription_events subscription_uuid:string:index event_type:string data:text created_at:datetime"
25
+ )
26
+ end
27
+
28
+ test "creates a model for the event class" do
29
+ assert_file("app/models/subscription_event.rb") do |contents|
30
+ assert_match(/class SubscriptionEvent < ActiveRecord::Base/, contents)
31
+ assert_match(/include EventSourcedRecord::Event/, contents)
32
+ assert_match(/event_type :creation do/, contents)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,38 @@
1
+ require 'test_helper'
2
+
3
+ class EventSourcedRecord::EventSourcedRecordGeneratorTest < Rails::Generators::TestCase
4
+ destination 'tmp/event_sourced_record_generator_test'
5
+ setup :prepare_destination
6
+ tests EventSourcedRecord::EventSourcedRecordGenerator
7
+
8
+ setup do
9
+ @generate_calls = Hash.new { |h,k| h[k] = [] }
10
+ EventSourcedRecord::EventSourcedRecordGenerator.any_instance.stubs(:generate).with { |name, arg_string|
11
+ @generate_calls[name] << arg_string
12
+ }
13
+ run_generator %w(
14
+ ShampooSubscription
15
+ user_id:integer
16
+ bottles_per_shipment:integer
17
+ bottles_left:integer
18
+ )
19
+ end
20
+
21
+ test "calls the event generator" do
22
+ assert @generate_calls['event_sourced_record:event'].include?(
23
+ "shampoo_subscription_event shampoo_subscription_uuid:string:index event_type:string data:text created_at:datetime"
24
+ )
25
+ end
26
+
27
+ test "calls the projection generator" do
28
+ assert @generate_calls['event_sourced_record:projection'].include?(
29
+ "shampoo_subscription user_id:integer bottles_per_shipment:integer bottles_left:integer"
30
+ )
31
+ end
32
+
33
+ test "calls the calculator generator" do
34
+ assert @generate_calls['event_sourced_record:calculator'].include?(
35
+ "shampoo_subscription_calculator"
36
+ )
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ require 'test_helper'
2
+
3
+ class EventSourcedRecord::ObserverGeneratorTest < Rails::Generators::TestCase
4
+ Destination = 'tmp/observer_generator_test'
5
+ destination Destination
6
+ setup :prepare_destination
7
+ tests EventSourcedRecord::ObserverGenerator
8
+
9
+ setup do
10
+ config_path = File.join(Destination, 'config')
11
+ FileUtils.mkdir(config_path)
12
+ FileUtils.cp("test/generators/templates/application.rb", config_path)
13
+ run_generator %w(subscription_event_observer)
14
+ end
15
+
16
+ test "creates a file for the observer" do
17
+ assert_file("app/observers/subscription_event_observer.rb")
18
+ end
19
+ end
@@ -0,0 +1,36 @@
1
+ require 'test_helper'
2
+
3
+ class EventSourcedRecord::ProjectionGeneratorTest < Rails::Generators::TestCase
4
+ destination 'tmp/projection_generator_test'
5
+ setup :prepare_destination
6
+ tests EventSourcedRecord::ProjectionGenerator
7
+
8
+ setup do
9
+ @generate_calls = Hash.new { |h,k| h[k] = [] }
10
+ EventSourcedRecord::ProjectionGenerator.any_instance.stubs(:generate).with { |name, arg_string|
11
+ @generate_calls[name] << arg_string
12
+ }
13
+ run_generator %w(
14
+ subscription
15
+ user_id:integer
16
+ bottles_per_shipment:integer
17
+ bottles_left:integer
18
+ )
19
+ end
20
+
21
+ test "creates a migration for the projection class" do
22
+ assert(
23
+ @generate_calls['migration'].include?(
24
+ "create_subscriptions user_id:integer bottles_per_shipment:integer bottles_left:integer uuid:string:uniq"
25
+ ),
26
+ @generate_calls.inspect
27
+ )
28
+ end
29
+
30
+ test "creates a model for the projection class" do
31
+ assert_file("app/models/subscription.rb") do |contents|
32
+ assert_match(/class Subscription < ActiveRecord::Base/, contents)
33
+ assert_match(/validates :uuid, uniqueness: true/, contents)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,12 @@
1
+ require File.expand_path('../boot', __FILE__)
2
+
3
+ require 'rails/all'
4
+
5
+ # Require the gems listed in Gemfile, including any gems
6
+ # you've limited to :test, :development, or :production.
7
+ Bundler.require(*Rails.groups)
8
+
9
+ module ShampooExpress
10
+ class Application < Rails::Application
11
+ end
12
+ end
@@ -0,0 +1,82 @@
1
+ require 'rails'
2
+ require 'rails/test_help'
3
+ require 'rails/generators/test_case'
4
+ require 'pry'
5
+ require 'mocha/test_unit'
6
+ $: << 'lib'
7
+ require 'event_sourced_record'
8
+ require 'generators/event_sourced_record/event_sourced_record_generator'
9
+ require 'generators/event_sourced_record/calculator_generator'
10
+ require 'generators/event_sourced_record/event_generator'
11
+ require 'generators/event_sourced_record/observer_generator'
12
+ require 'generators/event_sourced_record/projection_generator'
13
+ require 'active_record'
14
+
15
+ ActiveRecord::Base.establish_connection(
16
+ :adapter => 'sqlite3', :database => 'tmp/test.sqlite3'
17
+ )
18
+
19
+ silence_stream(STDOUT) do
20
+ ActiveRecord::Schema.define do
21
+ create_table "shipments", force: true do |t|
22
+ t.integer "subscription_id"
23
+ t.integer "num_bottles"
24
+ t.datetime "created_at"
25
+ t.datetime "updated_at"
26
+ end
27
+
28
+ create_table 'subscription_events', :force => true do |t|
29
+ t.string "subscription_uuid"
30
+ t.string "event_type"
31
+ t.text "data"
32
+ t.datetime "created_at"
33
+ end
34
+
35
+ create_table "subscriptions", force: true do |t|
36
+ t.integer "user_id"
37
+ t.integer "bottles_per_shipment"
38
+ t.integer "bottles_left"
39
+ t.string "uuid"
40
+ end
41
+ end
42
+ end
43
+
44
+ class Shipment < ActiveRecord::Base
45
+ end
46
+
47
+ class SubscriptionEvent < ActiveRecord::Base
48
+ include EventSourcedRecord::Event
49
+
50
+ event_type :creation do
51
+ attributes :bottles_per_shipment, :bottles_purchased, :user_id
52
+
53
+ validates :bottles_per_shipment, presence: true, numericality: true
54
+ validates :bottles_purchased, presence: true, numericality: true
55
+ validates :user_id, presence: true
56
+ end
57
+
58
+ event_type :change_settings do
59
+ attributes :bottles_per_shipment
60
+
61
+ validates :bottles_per_shipment, numericality: true
62
+ end
63
+ end
64
+
65
+ class Subscription < ActiveRecord::Base
66
+ has_many :subscription_events
67
+
68
+ validates :uuid, uniqueness: true
69
+ end
70
+
71
+ class SubscriptionCalculator < EventSourcedRecord::Calculator
72
+ events :subscription_events, :shipments
73
+
74
+ def advance_creation(event)
75
+ @subscription.bottles_per_shipment = event.bottles_per_shipment
76
+ @subscription.bottles_left = event.bottles_purchased
77
+ end
78
+
79
+ def advance_shipment(shipment)
80
+ @subscription.bottles_left -= shipment.num_bottles
81
+ end
82
+ end