entity_store 0.0.5 → 0.0.7

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.
@@ -1,8 +1,42 @@
1
1
  module EntityStore
2
2
  module Entity
3
3
  attr_accessor :id
4
+
5
+ # Holds a reference to the store used to load this entity so the same store
6
+ # can be used for related entities
7
+ attr_accessor :related_entity_loader
8
+
4
9
  attr_writer :version
5
10
 
11
+ def self.included(klass)
12
+ klass.class_eval do
13
+ extend ClassMethods
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+
19
+ def related_entities(*names)
20
+ names.each do |name|
21
+ # attr accessor for the id
22
+ define_method("#{name}_id") { instance_variable_get("@#{name}_id")}
23
+ define_method("#{name}_id=") do |value| instance_variable_set("@#{name}_id", value) end
24
+
25
+ # lazy loader for related entity
26
+ define_method(name) {
27
+ if instance_variable_get("@#{name}_id") && related_entity_loader
28
+ instance_variable_get("@_#{name}") || instance_variable_set("@_#{name}", related_entity_loader.get(instance_variable_get("@#{name}_id")))
29
+ end
30
+ }
31
+ end
32
+
33
+ define_method(:loaded_related_entities) {
34
+ names.collect{ |name| instance_variable_get("@_#{name}") }.select{|entity| !entity.nil? }
35
+ }
36
+ end
37
+
38
+ end
39
+
6
40
  def initialize(attr={})
7
41
  attr.each_pair { |k,v| self.send("#{k}=", v) }
8
42
  end
@@ -11,12 +11,12 @@ module EntityStore
11
11
 
12
12
  def open_store
13
13
  uri = URI.parse(EntityStore.connection_profile)
14
- connection = Connection.from_uri(EntityStore.connection_profile)
14
+ connection = Connection.from_uri(EntityStore.connection_profile, :connect_timeout => connect_timeout)
15
15
  connection.db(uri.path.gsub(/^\//, ''))
16
16
  end
17
17
 
18
18
  def connect_timeout
19
- ENV['ENTITY_STORE_CONNECT_TIMEOUT'] || '2000'
19
+ (ENV['ENTITY_STORE_CONNECT_TIMEOUT'] || '2').to_i
20
20
  end
21
21
 
22
22
  def entities
@@ -7,20 +7,32 @@ module EntityStore
7
7
  def add(entity)
8
8
  entity.id = storage_client.add_entity(entity)
9
9
  add_events(entity)
10
- return entity
10
+ entity
11
11
  rescue => e
12
12
  EntityStore.logger.error { "Store#add error: #{e.inspect} - #{entity.inspect}" }
13
13
  raise e
14
14
  end
15
15
 
16
16
  def save(entity)
17
+ do_save entity
18
+ entity.loaded_related_entities.each do |e| do_save e end if entity.respond_to?(:loaded_related_entities)
19
+ entity
20
+ end
21
+
22
+ def do_save(entity)
17
23
  # need to look at concurrency if we start storing version on client
18
- entity.version += 1
19
- storage_client.save_entity(entity)
20
- add_events(entity)
21
- return entity
24
+ unless entity.pending_events.empty?
25
+ entity.version += 1
26
+ if entity.id
27
+ storage_client.save_entity(entity)
28
+ else
29
+ entity.id = storage_client.add_entity(entity)
30
+ end
31
+ add_events(entity)
32
+ end
33
+ entity
22
34
  rescue => e
23
- EntityStore.logger.error { "Store#save error: #{e.inspect} - #{entity.inspect}" }
35
+ EntityStore.logger.error { "Store#do_save error: #{e.inspect} - #{entity.inspect}" }
24
36
  raise e
25
37
  end
26
38
 
@@ -40,8 +52,10 @@ module EntityStore
40
52
  def get(id, raise_exception=false)
41
53
  if entity = storage_client.get_entity(id, raise_exception)
42
54
  storage_client.get_events(id).each { |e| e.apply(entity) }
55
+ # assign this entity loader to allow lazy loading of related entities
56
+ entity.related_entity_loader = self
43
57
  end
44
- return entity
58
+ entity
45
59
  end
46
60
 
47
61
  # Public : USE AT YOUR PERIL this clears the ENTIRE data store
@@ -1,3 +1,3 @@
1
1
  module EntityStore
2
- VERSION = "0.0.5".freeze
2
+ VERSION = "0.0.7".freeze
3
3
  end
@@ -0,0 +1,69 @@
1
+ require "spec_helper"
2
+
3
+ class DummyEntity
4
+ include Entity
5
+
6
+ related_entities :club, :user
7
+
8
+ end
9
+
10
+ describe Entity do
11
+ describe ".related_entities" do
12
+ before(:each) do
13
+ @entity_loader = mock(Store)
14
+ @club = mock('Entity', :id => random_string)
15
+ @user = mock('Entity', :id => random_string)
16
+ @entity = DummyEntity.new(:related_entity_loader => @entity_loader, :club_id => @club.id, :user_id => @user.id)
17
+ @entity_loader.stub(:get) { |id|
18
+ case id
19
+ when @club.id
20
+ @club
21
+ when @user.id
22
+ @user
23
+ end
24
+ }
25
+ end
26
+
27
+ it "should have the club_id set" do
28
+ @entity.club_id.should eq(@club.id)
29
+ end
30
+ it "should load club" do
31
+ @entity.club.should eq(@club)
32
+ end
33
+ it "should call entity_loader with club id" do
34
+ @entity_loader.should_receive(:get).with(@club.id)
35
+ @entity.club
36
+ end
37
+ it "should have the user_id set" do
38
+ @entity.user_id.should eq(@user.id)
39
+ end
40
+ it "should load user" do
41
+ @entity.user.should eq(@user)
42
+ end
43
+ it "should call entity_loader with user id" do
44
+ @entity_loader.should_receive(:get).with(@user.id)
45
+ @entity.user
46
+ end
47
+
48
+ context "when only user loaded" do
49
+ before(:each) do
50
+ @entity.user
51
+ end
52
+
53
+ it "should only have user in the loaded related entities collection" do
54
+ @entity.loaded_related_entities.should eq([@user])
55
+ end
56
+ end
57
+
58
+ context "when both user and club loaded" do
59
+ before(:each) do
60
+ @entity.club
61
+ @entity.user
62
+ end
63
+
64
+ it "should only have user in the loaded related entities collection" do
65
+ @entity.loaded_related_entities.should eq([@club, @user])
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,20 +1,17 @@
1
1
  require 'spec_helper'
2
2
 
3
- class DummyEntity
3
+ class DummyEntityForStore
4
4
  include Entity
5
5
 
6
6
  attr_accessor :name
7
7
 
8
- def initialize(name)
9
- @name = name
10
- end
11
8
  end
12
9
 
13
10
  describe Store do
14
11
  describe "#add" do
15
12
  before(:each) do
16
13
  @new_id = random_string
17
- @entity = DummyEntity.new(random_string)
14
+ @entity = DummyEntityForStore.new(:name => random_string)
18
15
  @storage_client = mock("StorageClient", :add_entity => @new_id)
19
16
  @store = Store.new
20
17
  @store.stub(:add_events)
@@ -38,7 +35,7 @@ describe Store do
38
35
 
39
36
  describe "#add_events" do
40
37
  before(:each) do
41
- @entity = DummyEntity.new(random_string)
38
+ @entity = DummyEntityForStore.new(:name => random_string)
42
39
  @entity.id = random_string
43
40
  @entity.version = random_integer
44
41
  @entity.pending_events << mock(Event, :entity_id= => true, :entity_version= => true)
@@ -79,16 +76,41 @@ describe Store do
79
76
  end
80
77
 
81
78
  describe "#save" do
79
+ context "when entity has related entities loaded" do
80
+ before(:each) do
81
+ @entity = DummyEntityForStore.new(:id => random_string)
82
+ @store = Store.new
83
+ @related_entity = mock('Entity')
84
+ @entity.stub(:loaded_related_entities) { [ @related_entity ] }
85
+ @store.stub(:do_save)
86
+ end
87
+
88
+ subject { @store.save(@entity) }
89
+
90
+ it "should save the entity" do
91
+ @store.should_receive(:do_save).with(@entity)
92
+ subject
93
+ end
94
+ it "should save them as well" do
95
+ @store.should_receive(:do_save).with(@related_entity)
96
+ subject
97
+ end
98
+ end
99
+
100
+ end
101
+
102
+ describe "#do_save" do
82
103
  before(:each) do
83
104
  @new_id = random_string
84
- @entity = DummyEntity.new(random_string)
105
+ @entity = DummyEntityForStore.new(:id => random_string)
85
106
  @storage_client = mock("StorageClient", :save_entity => true)
86
107
  @store = Store.new
87
108
  @store.stub(:add_events)
88
109
  @store.stub(:storage_client) { @storage_client }
110
+ @entity.stub(:pending_events) { [ mock('Event') ] }
89
111
  end
90
112
 
91
- subject { @store.save(@entity) }
113
+ subject { @store.do_save(@entity) }
92
114
 
93
115
  it "increments the entity version number" do
94
116
  @entity.should_receive(:version=).with(@entity.version + 1)
@@ -102,16 +124,40 @@ describe Store do
102
124
  @store.should_receive(:add_events).with(@entity)
103
125
  subject
104
126
  end
105
- it "returns a reference to the ride" do
127
+ it "returns a reference to the entity" do
106
128
  subject.should eq(@entity)
107
129
  end
130
+ context "when no pending events" do
131
+ before(:each) do
132
+ @entity.stub(:pending_events) { [] }
133
+ end
134
+ it "should not save the entity" do
135
+ @storage_client.should_not_receive(:save_entity)
136
+ subject
137
+ end
138
+ it "should not add the events" do
139
+ @storage_client.should_not_receive(:add_events)
140
+ subject
141
+ end
142
+ end
143
+ context "when entity doesn't have an id" do
144
+ before(:each) do
145
+ @entity.id = nil
146
+ @id = random_string
147
+ @storage_client.stub(:add_entity) { @id }
148
+ end
149
+ it "should add the entity" do
150
+ @storage_client.should_receive(:add_entity).with(@entity)
151
+ subject
152
+ end
153
+ end
108
154
  end
109
155
 
110
156
  describe "#get" do
111
157
  before(:each) do
112
158
  @id = random_integer
113
- @entity = DummyEntity.new(random_string)
114
- DummyEntity.stub(:new).and_return(@ride)
159
+ @entity = DummyEntityForStore.new
160
+ DummyEntityForStore.stub(:new).and_return(@ride)
115
161
  @events = [mock("Event", :apply => true), mock("Event", :apply => true)]
116
162
 
117
163
  @storage_client = mock("StorageClient", :get_entity => @entity, :get_events => @events)
@@ -135,6 +181,9 @@ describe Store do
135
181
  end
136
182
  subject
137
183
  end
184
+ it "should assign itself as the related_entity_loader" do
185
+ subject.related_entity_loader.should eq(@store)
186
+ end
138
187
  it "should return a ride" do
139
188
  subject.should eq(@entity)
140
189
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: entity_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-08-31 00:00:00.000000000 Z
12
+ date: 2012-10-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: mongo
@@ -62,6 +62,7 @@ files:
62
62
  - lib/entity_store/version.rb
63
63
  - lib/entity_store.rb
64
64
  - lib/tasks/entity_store.rake
65
+ - spec/entity_store/entity_spec.rb
65
66
  - spec/entity_store/entity_value_spec.rb
66
67
  - spec/entity_store/event_bus_spec.rb
67
68
  - spec/entity_store/event_spec.rb
@@ -95,6 +96,7 @@ signing_key:
95
96
  specification_version: 3
96
97
  summary: Event sourced entity store with a Mongo body
97
98
  test_files:
99
+ - spec/entity_store/entity_spec.rb
98
100
  - spec/entity_store/entity_value_spec.rb
99
101
  - spec/entity_store/event_bus_spec.rb
100
102
  - spec/entity_store/event_spec.rb