event_sourced_record 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 092ed18da567347e5ac8f364e1d0f462f53e017d
4
- data.tar.gz: ee8bd1282829f2b7f4f0f67a52415ef5d2d2b433
3
+ metadata.gz: 11467089bb5e302e5ba7458442510f7c635a9665
4
+ data.tar.gz: 6eb6064de3952fc90fc8ddba4aea5cf8b417e220
5
5
  SHA512:
6
- metadata.gz: 5453d5b029fc488d6ac88d910b5c200bc9fe435b30616256c1058aa7dc686024c263ec8f8b66bf3db3427031d2d105d288760bbad91005efdc32096429ac1a68
7
- data.tar.gz: c8a38090c8720b7fd0d5476ccd56dec501bebe5503077eafb63da0a7c91834a5af9d4d7911d73f10228619cff5c531f43ab8f9ff00f9efb24ad060ef8fdbd8e7
6
+ metadata.gz: e6d3a67f2edaf5c8d3907ea7eb2c9423fa39508542673acdcf3b881cabf05301b65893fa6057cfecf393e99f44f07f37afc7a5ab5d07049dea8b57a9ba24992e
7
+ data.tar.gz: d3ede2905cfacc55d72692e469c7d954eec68fa58b0221b542a38c76fdfea23eb6cc412f627941c7bdf15e36b05d97a0af2229b62bb08fba45b9ae8f780da532
data/Appraisals CHANGED
@@ -1,3 +1,7 @@
1
+ appraise "rails-3-2" do
2
+ gem "rails", "3.2.21"
3
+ end
4
+
1
5
  appraise "rails-4-0" do
2
6
  gem "rails", "4.0.13"
3
7
  end
@@ -5,4 +9,3 @@ end
5
9
  appraise "rails-4-2" do
6
10
  gem "rails", "4.2.0"
7
11
  end
8
-
@@ -0,0 +1,284 @@
1
+ # Getting started with Event Sourced Record
2
+
3
+ This document is intended to teach you how to use Event Sourced Record, and explains some of the concepts behind event sourcing in general. It assumes you're already familiar with Rails.
4
+
5
+ Say you're starting a company that sells a shampoo subscription through the mail, and you want to use Event Sourcing to handle your subscription model.
6
+
7
+ ## Requirements
8
+
9
+ Event Sourced Record supports Rails 3.2 and higher.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'event_sourced_record'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install event_sourced_record
24
+
25
+ Event Sourced Record uses observers, so you'll need to add them to your Gemfile:
26
+
27
+ gem 'rails-observers'
28
+
29
+ ## Generate your classes
30
+
31
+ You can use `rails generate event_sourced_record` to get started:
32
+
33
+ $ rails generate event_sourced_record Subscription \
34
+ user_id:integer bottles_per_shipment:integer \
35
+ bottles_left:integer
36
+
37
+ This generates two migrations, which you might as well run now:
38
+
39
+ $ rake db:migrate
40
+
41
+ This takes the same attribute list as `rails generate model`, but generates a number of different types of files. Let's look at them in turn:
42
+
43
+ ### Subscription
44
+
45
+ This is the model that you'd create in a typical Rails application, but here you'll find that we don't do much with it directly.
46
+
47
+ class Subscription < ActiveRecord::Base
48
+ has_many :subscription_events
49
+
50
+ validates :uuid, uniqueness: true
51
+ end
52
+
53
+ `Subscription` holds data in a convenient form, but it's not responsible for changing its own state. That's the responsiblity of the calculator, using the associated events.
54
+
55
+ In Event Sourcing parlance, `Subscription` is a "projection", meaning that everything in it can be derived from data and logic that lives elsewhere.
56
+
57
+ ### SubscriptionEvent
58
+
59
+ You might never end up showing this model to end-users, but in fact it's the authoritative data in this system. With all the events, you can rebuild the projections, but not the other way around.
60
+
61
+ `SubscriptionEvent` represents a timestamped event associated with a particular `Subscription`. Each event should be treated as read-only; it's meant to be written once and then never modified.
62
+
63
+ class SubscriptionEvent < ActiveRecord::Base
64
+ include EventSourcedRecord::Event
65
+
66
+ event_type :creation do
67
+ # attributes :user_id
68
+ #
69
+ # validates :user_id, presence: true
70
+ end
71
+ end
72
+
73
+ ### SubscriptionCalculator
74
+
75
+ This service class replays the event sequence to build a Subscription record that reflects current state. You'll flesh it out by adding methods that advance the state of the Subscription for each individual type of event.
76
+
77
+ class SubscriptionCalculator < EventSourcedRecord::Calculator
78
+ events :subscription_events
79
+
80
+ def advance_creation(event)
81
+
82
+ end
83
+ end
84
+
85
+
86
+ ### SubscriptionEventObserver
87
+
88
+ You can run `SubscriptionCalculator` yourself whenever you like, but `SubscriptionEventObserver` takes care of a core use-case. It monitors `SubscriptionEvent` (and other event classes, as we'll see) and tells `SubscriptionCalculator` to build or rebuild the `Subscription` every time there's a new event class saved.
89
+
90
+ class SubscriptionEventObserver < ActiveRecord::Observer
91
+ observe :subscription_event
92
+
93
+ def after_create(event)
94
+ SubscriptionCalculator.new(event).run.save!
95
+ end
96
+ end
97
+
98
+ The generator registers this observer in `config/application.rb`:
99
+
100
+ config.active_record.observers = :subscription_event_observer
101
+
102
+ ## Creation
103
+
104
+ The generated code starts you out with a `creation` event, but we'll want to define it to get some use out of it.
105
+
106
+ Above, we specified that subscriptions will have `user_id`, `bottles_per_shipment`, and `bottles_left`. During the creation, we assume we can get `user_id` from `current_user` in the controller, and `bottles_per_shipment` is something that the user will specify during the initial signup. Let's say, also, that sign up requires that you buy some bottles up-front, which we'll define on the initial event but not on `Subscription`.
107
+
108
+ ### Define the creation event type
109
+
110
+ Fill out the `event_type` block in `subscription_event.rb`:
111
+
112
+ class SubscriptionEvent < ActiveRecord::Base
113
+ include EventSourcedRecord::Event
114
+
115
+ event_type :creation do
116
+ attributes :bottles_per_shipment, :bottles_purchased, :user_id
117
+
118
+ validates :bottles_per_shipment, presence: true, numericality: true
119
+ validates :bottles_purchased, presence: true, numericality: true
120
+ validates :user_id, presence: true
121
+ end
122
+ end
123
+
124
+ This lets you build and save events with the attributes `bottles_per_shipment`,
125
+ `bottles_purchased`, and `user_id`, and validates those attributes -- as long as the event type is set by using the auto-generated scope:
126
+
127
+ event = SubscriptionEvent.creation.new(
128
+ bottles_purchased: 6,
129
+ user_id: current_user.id
130
+ )
131
+ puts "Trying to purchase #{event.bottles_purchased} bottles"
132
+ event.valid? # false
133
+ event.errors[:bottles_per_shipment] # ["can't be blank", "is not a number"]
134
+
135
+ ### Handle the creation in the calculator
136
+
137
+ Fill out the `advance_creation` method in `subscription_calculator.rb`:
138
+
139
+ class SubscriptionCalculator < EventSourcedRecord::Calculator
140
+ events :subscription_events
141
+
142
+ def advance_creation(event)
143
+ @subscription.user_id = event.user_id
144
+ @subscription.bottles_per_shipment = event.bottles_per_shipment
145
+ @subscription.bottles_left = event.bottles_purchased
146
+ end
147
+ end
148
+
149
+ Note that for `user_id` and `bottles_per_shipment` we simply copy the field from the event to the subscription, but in the case of `bottles_purchased`, that is translated to `Subscription#bottles_left`. This field will go up and down over time.
150
+
151
+ ### Create a subscription (indirectly)
152
+
153
+ Creating a subscription is a matter of creating the event itself:
154
+
155
+ event = SubscriptionEvent.creation.new(
156
+ bottles_per_shipment: 1,
157
+ bottles_purchased: 6,
158
+ user_id: current_user.id
159
+ )
160
+ event.save!
161
+ subscription = Subscription.last
162
+ puts "Created subscription #{subscription.id}"
163
+
164
+ ### What's happening here?
165
+
166
+ There's a lot going on here. If you're curious, here's what's happening under the hood:
167
+
168
+ 1. `SubscriptionEvent` saves, provided its event type validations are satisfied.
169
+ 1. `SubscriptionObserver` is notified that a `SubscriptionEvent` was saved, so it runs the calculator.
170
+ 1. `SubscriptionCalculator` collects all associated events around the `Subscription`, orders them by `created_at`, and runs them in order.
171
+ 1. In the case of creation, we don't actually have a `Subscription` at the time we run the calculator for the first time. So `SubscriptionCalculator` makes use of an auto-generated `subscription_uuid` attribute to tell subscriptions apart even when some of them have yet to be created in the database.
172
+ 1. For each event, `SubscriptionCalculator` calls `advance_[event_type]`, which is responsible for updating the attributes on `@subscription` accordingly.
173
+ 1. `SubscriptionObserver` takes the record returned by `SubscriptionCalculator` and saves it to the database.
174
+
175
+ This is a lot of indirection, which you don't have to understand right away. When you hit more complex situations later, you'll find this indirection will come in handy.
176
+
177
+ ## Change the settings for a subscription
178
+
179
+ Occasionally, subscribers will want to change their settings. Let's say in this case we'll only allow them to change `bottles_per_shipment`. So we add a new event type in `SubscriptionEvent`:
180
+
181
+ class SubscriptionEvent < ActiveRecord::Base
182
+ event_type :change_settings do
183
+ attributes :bottles_per_shipment
184
+
185
+ validates :bottles_per_shipment, numericality: true
186
+ end
187
+ end
188
+
189
+ And we add a method to handle this new event type in the calculator:
190
+
191
+ class SubscriptionCalculator < EventSourcedRecord::Calculator
192
+ def advance_change_settings(event)
193
+ @subscription.bottles_per_shipment = event.bottles_per_shipment
194
+ end
195
+ end
196
+
197
+ If we want to change the subscription from the last example from 1 bottle per shipment to 2 bottles per shipment, this looks like this:
198
+
199
+ subscription.bottles_per_shipment # 1
200
+ SubscriptionEvent.change_settings.create!(
201
+ subscription_uuid: subscription.uuid, bottles_per_shipment: 2
202
+ )
203
+ subscription.reload
204
+ subscription.bottles_per_shipment # 2
205
+
206
+ ## Shipment
207
+
208
+ We created `SubscriptionEvent` to store events about `Subscription`, but you may find you have other kinds of classes that are like events in that they are time-based and relatively immutable. Let's say that shipments of shampoo function this way in our system: As soon as they are inserted in the database they are applied against the associated subscription.
209
+
210
+ So let's create a `Shipment` class, using the standard Rails generator:
211
+
212
+ rails generate model Shipment subscription_id:integer num_bottles:integer
213
+
214
+ And let's say that everytime a `Shipment` goes out the door, we deduct `Shipment#num_bottles` from `bottles_left` on the associated `Subscription`. To do that we'll need to change the calculator, and the observer.
215
+
216
+ In `subscription_calculator.rb` we make two changes. First, we add `:shipments` to `events`, which tells the calculator to include `Shipment` as an event that needs to be considered. Then we add an `advance_shipment` method to handle each associated `Shipment`.
217
+
218
+ class SubscriptionCalculator < EventSourcedRecord::Calculator
219
+ events :subscription_events, :shipments
220
+
221
+ def advance_shipment(shipment)
222
+ @subscription.bottles_left -= shipment.num_bottles
223
+ end
224
+ end
225
+
226
+ In `subscription_event_observer.rb`, we add `:shipment` to `observer`, so the observer will know to fire when we create a shipment.
227
+
228
+ class SubscriptionEventObserver < ActiveRecord::Observer
229
+ observe :subscription_event, :shipment
230
+
231
+ def after_create(event)
232
+ SubscriptionCalculator.new(event).run.save!
233
+ end
234
+ end
235
+
236
+ Note that `SubscriptionEventObserver#after_create` didn't change. When a `Shipment` is created, `after_create` will treat that `Shipment` like another type of event.
237
+
238
+ ## About calculators
239
+
240
+ To get the most benefit out of the calculator, you should make its work idempotent: That is, you should be to run it twice and have no side effects. And don't forget that every time you call `SubscriptionCalculator#run`, it runs through all events in order, even events that have been considered before. So things like sending emails, charging a credit card for a recurring order, or firing off analytic events should not happen anywhere inside `SubscriptionCalculator`.
241
+
242
+ The best way to think of it is that `Subscription` is a cache, and `SubscriptionCalculator` contains the logic that fills that cache. Just as you wouldn't want to send an email every time you saved data in Redis, you wouldn't want to send an email every time you called `SubscriptionCalculator#run`.
243
+
244
+ For this reason, you'll find that event sourcing fits nicely with architectural styles that move these sorts of side effects out of the model, such as [service classes](https://blog.engineyard.com/2014/keeping-your-rails-controllers-dry-with-services) or [Data Context Interaction](http://dci-in-ruby.info/).
245
+
246
+ ## Extending Subscription in production
247
+
248
+ Over time, your application will change, and `Subscription` will change accordingly. It will handle new concepts which you will need to add to both pre-existing and future subscriptions.
249
+
250
+ Because `Subscription` is just a cache, event sourcing gives us a consistent way to handle both pre-existing and future records:
251
+
252
+ 1. Write a migration if needed to add or change fields on the `subscriptions` table.
253
+ 1. Modify `SubscriptionCalculator` to take the new fields into account.
254
+ 1. Test, merge, and deploy to production.
255
+ 1. Rebuild every `Subscription`, with code such as:
256
+
257
+
258
+ Subscription.all.each do |subscription|
259
+ SubscriptionCalculator.new(subscription).run.save!
260
+ end
261
+
262
+ Because you have designed `SubscriptionCalculator` to be idempotent, it is safe to re-run at any time you want -- for example, if you want to fill in a database column that didn't exist before.
263
+
264
+ ## Debugging Subscription in production
265
+
266
+ The same process can work for fixing many bugs in production. Events themselves are usually a simple act of recording the user's intention. Errors usually emerge in `SubscriptionCalculator`, which is responsible for the tougher work of interpreting a sequence of events.
267
+
268
+ So when you find a bug, you can often fix it via this process:
269
+
270
+ 1. Write a test to reproduce the bug.
271
+ 1. Fix `SubscriptionCalculator` to fix the bug.
272
+ 1. Merge and deploy to production.
273
+ 1. Rebuild every `Subscription` with the code above.
274
+
275
+ Remember, `SubscriptionCalculator` is idempotent, so this process will fix all the records affected by the bug will leaving the others unchanged. It will also spare you the agonizing effort of picking through data in `rails console` to guess which records are broken. Why go to the trouble? Just re-run the whole thing and move on.
276
+
277
+ ## Reporting
278
+
279
+ Because `SubscriptionCalculator` rebuilds a record in sequence, it's a piece of cake to only partially rebuild up to a certain time, which can save a lot of time in generating retrospective reports:
280
+
281
+ calculator = SubscriptionCalculator.new(subscription)
282
+ sub_at_end_of_year = calculator.run(last_event_time: Date.new(2014,12,31))
283
+
284
+ Note that the returned instance is un-saved, and shouldn't be saved to `subscriptions`, but it's now a piece of cake to take those values and send them to whatever reporting system you have in place.
data/README.md CHANGED
@@ -6,6 +6,10 @@ With Event Sourcing, every change to the state of an object is recorded as an im
6
6
 
7
7
  For more, see Martin Fowler's writeup of the pattern: http://martinfowler.com/eaaDev/EventSourcing.html
8
8
 
9
+ ## Requirements
10
+
11
+ Event Sourced Record supports Rails 3.2 and higher.
12
+
9
13
  ## Installation
10
14
 
11
15
  Add this line to your application's Gemfile:
@@ -24,10 +28,10 @@ Event Sourced Record uses observers, so you'll need to add them to your Gemfile:
24
28
 
25
29
  gem 'rails-observers'
26
30
 
27
- Note that only Rails 4 is supported as of this writing. Rails 3 support is coming soon.
28
-
29
31
  ## Usage
30
32
 
33
+ See `Getting_Started.md` for a detailed example.
34
+
31
35
  Generate the required classes with `rails generate event_sourced_record`:
32
36
 
33
37
  rails generate event_sourced_record Subscription \
@@ -23,6 +23,16 @@ module EventSourcedRecord::Event
23
23
  end
24
24
  end
25
25
 
26
+ def respond_to?(meth, include_all = false)
27
+ if event_type_config && event_type_config.attributes.include?(meth)
28
+ true
29
+ elsif event_type_config && event_type_config.attributes.any? { |a| "#{a}=" == meth }
30
+ true
31
+ else
32
+ super
33
+ end
34
+ end
35
+
26
36
  private
27
37
 
28
38
  def ensure_data
@@ -1,3 +1,3 @@
1
1
  module EventSourcedRecord
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -1,18 +1,27 @@
1
- class EventSourcedRecord::EventGenerator < Rails::Generators::NamedBase
1
+ require 'generators/event_sourced_record'
2
+
3
+ class EventSourcedRecord::EventGenerator < ActiveRecord::Generators::Base
2
4
  source_root File.expand_path('../templates', __FILE__)
3
5
  argument :attributes,
4
6
  :type => :array, :default => []
5
7
 
6
8
  def create_migration_file
7
- attributes_str = attributes.map { |attr|
8
- attr_banner = attr.name
9
- attr_banner << ":#{attr.type}" if attr.type
10
- attr_banner << ':index' if attr.has_index?
11
- attr_banner
12
- }.join(' ')
13
- generate(
14
- "migration", "create_#{event_table_name} #{attributes_str}"
15
- )
9
+ ar_major_version = ActiveRecord::VERSION::MAJOR
10
+ if ar_major_version >= 4
11
+ attributes_str = attributes.map { |attr|
12
+ attr_banner = attr.name
13
+ attr_banner << ":#{attr.type}" if attr.type
14
+ attr_banner << ':index' if attr.has_index?
15
+ attr_banner
16
+ }.join(' ')
17
+ generate(
18
+ "migration", "create_#{event_table_name} #{attributes_str}"
19
+ )
20
+ else
21
+ migration_template(
22
+ "event_migration.ar3.rb", "db/migrate/create_#{event_table_name}.rb"
23
+ )
24
+ end
16
25
  end
17
26
 
18
27
  def create_model_file
@@ -22,10 +31,18 @@ class EventSourcedRecord::EventGenerator < Rails::Generators::NamedBase
22
31
  )
23
32
  end
24
33
 
34
+ def attributes_with_index
35
+ attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) }
36
+ end
37
+
25
38
  hook_for :test_framework, as: :model
26
39
 
27
40
  private
28
41
 
42
+ def event_migration_class_name
43
+ "create_#{event_table_name}".camelize
44
+ end
45
+
29
46
  def event_class_name
30
47
  class_name
31
48
  end
@@ -1,21 +1,36 @@
1
- class EventSourcedRecord::ProjectionGenerator < Rails::Generators::NamedBase
1
+ require 'generators/event_sourced_record'
2
+
3
+ class EventSourcedRecord::ProjectionGenerator < ActiveRecord::Generators::Base
2
4
  source_root File.expand_path('../templates', __FILE__)
3
5
  argument :attributes,
4
6
  :type => :array, :default => []
5
7
 
6
8
  def create_migration_file
7
- generate(
8
- "migration", "create_#{projection_table_name} #{migration_attributes}"
9
- )
9
+ ar_major_version = ActiveRecord::VERSION::MAJOR
10
+ if ar_major_version >= 4
11
+ generate(
12
+ "migration", "create_#{projection_table_name} #{migration_attributes}"
13
+ )
14
+ else
15
+ migration_template(
16
+ "projection_migration.ar3.rb",
17
+ "db/migrate/create_#{projection_table_name}.rb"
18
+ )
19
+ end
10
20
  end
11
21
 
12
22
  def create_model_file
23
+ ar_major_version = ActiveRecord::VERSION::MAJOR
13
24
  template(
14
- 'projection_model.rb',
25
+ "projection_model.ar#{ar_major_version}.rb",
15
26
  File.join('app/models', class_path, "#{projection_file_name}.rb")
16
27
  )
17
28
  end
18
29
 
30
+ def attributes_with_index
31
+ attributes.select { |a| a.has_index? || (a.reference? && options[:indexes]) }
32
+ end
33
+
19
34
  hook_for :test_framework, as: :model
20
35
 
21
36
  private
@@ -31,6 +46,9 @@ class EventSourcedRecord::ProjectionGenerator < Rails::Generators::NamedBase
31
46
  attr_strings.join(' ')
32
47
  end
33
48
 
49
+ def projection_migration_class_name
50
+ "create_#{projection_table_name}".camelize
51
+ end
34
52
 
35
53
  def projection_class_name
36
54
  class_name
@@ -0,0 +1,16 @@
1
+ class <%= event_migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ create_table :<%= event_table_name %> do |t|
4
+ <% attributes.each do |attribute| -%>
5
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
6
+ <% end -%>
7
+ <% if options[:timestamps] %>
8
+ t.timestamps
9
+ <% end -%>
10
+ end
11
+ <% attributes_with_index.each do |attribute| -%>
12
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
13
+ <% end -%>
14
+ end
15
+ end
16
+
@@ -0,0 +1,16 @@
1
+ class <%= projection_migration_class_name %> < ActiveRecord::Migration
2
+ def change
3
+ create_table :<%= projection_table_name %> do |t|
4
+ <% attributes.each do |attribute| -%>
5
+ t.<%= attribute.type %> :<%= attribute.name %><%= attribute.inject_options %>
6
+ <% end -%>
7
+ <% if options[:timestamps] %>
8
+ t.timestamps
9
+ <% end -%>
10
+ end
11
+ <% attributes_with_index.each do |attribute| -%>
12
+ add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
13
+ <% end -%>
14
+ end
15
+ end
16
+
@@ -0,0 +1,11 @@
1
+ <% module_namespacing do -%>
2
+ class <%= projection_class_name %> < <%= projection_parent_class_name.classify %>
3
+ <% attributes.select {|attr| attr.reference? }.each do |attribute| -%>
4
+ belongs_to :<%= attribute.name %>
5
+ <% end -%>
6
+ has_many :<%= file_name %>_events
7
+
8
+ validates :uuid, uniqueness: true
9
+ end
10
+ <% end -%>
11
+
@@ -0,0 +1,6 @@
1
+ require 'rails/generators/active_record'
2
+ require 'generators/event_sourced_record/event_sourced_record_generator'
3
+ require 'generators/event_sourced_record/calculator_generator'
4
+ require 'generators/event_sourced_record/event_generator'
5
+ require 'generators/event_sourced_record/observer_generator'
6
+ require 'generators/event_sourced_record/projection_generator'
@@ -20,9 +20,16 @@ class EventSourcedRecord::EventGeneratorTest < Rails::Generators::TestCase
20
20
  end
21
21
 
22
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
- )
23
+ ar_major_version = ActiveRecord::VERSION::MAJOR
24
+ if ar_major_version >= 4
25
+ assert @generate_calls['migration'].include?(
26
+ "create_subscription_events subscription_uuid:string:index event_type:string data:text created_at:datetime"
27
+ )
28
+ else
29
+ assert_migration("db/migrate/create_subscription_events.rb") do |contents|
30
+ assert_match(/t.string :event_type/, contents)
31
+ end
32
+ end
26
33
  end
27
34
 
28
35
  test "creates a model for the event class" do
@@ -19,18 +19,26 @@ class EventSourcedRecord::ProjectionGeneratorTest < Rails::Generators::TestCase
19
19
  end
20
20
 
21
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
- )
22
+ ar_major_version = ActiveRecord::VERSION::MAJOR
23
+ if ar_major_version >= 4
24
+ assert(
25
+ @generate_calls['migration'].include?(
26
+ "create_subscriptions user_id:integer bottles_per_shipment:integer bottles_left:integer uuid:string:uniq"
27
+ ),
28
+ @generate_calls.inspect
29
+ )
30
+ else
31
+ assert_migration("db/migrate/create_subscriptions.rb") do |contents|
32
+ assert_match(/t.integer :user_id/, contents)
33
+ end
34
+ end
28
35
  end
29
36
 
30
37
  test "creates a model for the projection class" do
31
38
  assert_file("app/models/subscription.rb") do |contents|
32
39
  assert_match(/class Subscription < ActiveRecord::Base/, contents)
33
40
  assert_match(/validates :uuid, uniqueness: true/, contents)
41
+ assert_no_match(/attr_accessible :bottles_left/, contents)
34
42
  end
35
43
  end
36
44
  end
data/test/test_helper.rb CHANGED
@@ -3,13 +3,15 @@ require 'rails/test_help'
3
3
  require 'rails/generators/test_case'
4
4
  require 'pry'
5
5
  require 'mocha/test_unit'
6
+ #require 'rails/generators/active_record'
6
7
  $: << 'lib'
7
8
  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'
9
+ require 'generators/event_sourced_record'
10
+ #require 'generators/event_sourced_record/event_sourced_record_generator'
11
+ #require 'generators/event_sourced_record/calculator_generator'
12
+ #require 'generators/event_sourced_record/event_generator'
13
+ #require 'generators/event_sourced_record/observer_generator'
14
+ #require 'generators/event_sourced_record/projection_generator'
13
15
  require 'active_record'
14
16
 
15
17
  ActiveRecord::Base.establish_connection(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: event_sourced_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Francis Hwang
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-01-13 00:00:00.000000000 Z
11
+ date: 2015-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -146,6 +146,7 @@ files:
146
146
  - ".gitignore"
147
147
  - Appraisals
148
148
  - Gemfile
149
+ - Getting_Started.md
149
150
  - LICENSE.txt
150
151
  - README.md
151
152
  - Rakefile
@@ -155,6 +156,7 @@ files:
155
156
  - lib/event_sourced_record/event.rb
156
157
  - lib/event_sourced_record/event/event_type_config.rb
157
158
  - lib/event_sourced_record/version.rb
159
+ - lib/generators/event_sourced_record.rb
158
160
  - lib/generators/event_sourced_record/USAGE
159
161
  - lib/generators/event_sourced_record/calculator_generator.rb
160
162
  - lib/generators/event_sourced_record/event_generator.rb
@@ -162,9 +164,12 @@ files:
162
164
  - lib/generators/event_sourced_record/observer_generator.rb
163
165
  - lib/generators/event_sourced_record/projection_generator.rb
164
166
  - lib/generators/event_sourced_record/templates/calculator.rb
167
+ - lib/generators/event_sourced_record/templates/event_migration.ar3.rb
165
168
  - lib/generators/event_sourced_record/templates/event_model.rb
166
169
  - lib/generators/event_sourced_record/templates/observer.rb
167
- - lib/generators/event_sourced_record/templates/projection_model.rb
170
+ - lib/generators/event_sourced_record/templates/projection_migration.ar3.rb
171
+ - lib/generators/event_sourced_record/templates/projection_model.ar3.rb
172
+ - lib/generators/event_sourced_record/templates/projection_model.ar4.rb
168
173
  - lib/generators/rspec/service_generator.rb
169
174
  - lib/generators/rspec/templates/service_spec.rb
170
175
  - lib/generators/test_unit/service_generator.rb