lotus-model 0.0.0 → 0.1.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +6 -0
  4. data/.yardopts +5 -0
  5. data/EXAMPLE.md +217 -0
  6. data/Gemfile +14 -2
  7. data/README.md +303 -3
  8. data/Rakefile +17 -1
  9. data/lib/lotus-model.rb +1 -0
  10. data/lib/lotus/entity.rb +157 -0
  11. data/lib/lotus/model.rb +23 -2
  12. data/lib/lotus/model/adapters/abstract.rb +167 -0
  13. data/lib/lotus/model/adapters/implementation.rb +111 -0
  14. data/lib/lotus/model/adapters/memory/collection.rb +132 -0
  15. data/lib/lotus/model/adapters/memory/command.rb +90 -0
  16. data/lib/lotus/model/adapters/memory/query.rb +457 -0
  17. data/lib/lotus/model/adapters/memory_adapter.rb +149 -0
  18. data/lib/lotus/model/adapters/sql/collection.rb +209 -0
  19. data/lib/lotus/model/adapters/sql/command.rb +67 -0
  20. data/lib/lotus/model/adapters/sql/query.rb +615 -0
  21. data/lib/lotus/model/adapters/sql_adapter.rb +154 -0
  22. data/lib/lotus/model/mapper.rb +101 -0
  23. data/lib/lotus/model/mapping.rb +23 -0
  24. data/lib/lotus/model/mapping/coercer.rb +80 -0
  25. data/lib/lotus/model/mapping/collection.rb +336 -0
  26. data/lib/lotus/model/version.rb +4 -1
  27. data/lib/lotus/repository.rb +620 -0
  28. data/lotus-model.gemspec +15 -11
  29. data/test/entity_test.rb +126 -0
  30. data/test/fixtures.rb +81 -0
  31. data/test/model/adapters/abstract_test.rb +75 -0
  32. data/test/model/adapters/implementation_test.rb +22 -0
  33. data/test/model/adapters/memory/query_test.rb +91 -0
  34. data/test/model/adapters/memory_adapter_test.rb +1044 -0
  35. data/test/model/adapters/sql/query_test.rb +121 -0
  36. data/test/model/adapters/sql_adapter_test.rb +1078 -0
  37. data/test/model/mapper_test.rb +94 -0
  38. data/test/model/mapping/coercer_test.rb +27 -0
  39. data/test/model/mapping/collection_test.rb +82 -0
  40. data/test/repository_test.rb +283 -0
  41. data/test/test_helper.rb +30 -0
  42. data/test/version_test.rb +7 -0
  43. metadata +109 -11
@@ -0,0 +1,94 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Model::Mapper do
4
+ before do
5
+ @mapper = Lotus::Model::Mapper.new
6
+ end
7
+
8
+ describe '#initialize' do
9
+ before do
10
+ class FakeCoercer
11
+ end
12
+ end
13
+
14
+ after do
15
+ Object.send(:remove_const, :FakeCoercer)
16
+ end
17
+
18
+ it 'uses the given coercer' do
19
+ mapper = Lotus::Model::Mapper.new(FakeCoercer) do
20
+ collection :articles do
21
+ end
22
+ end
23
+
24
+ mapper.collection(:articles).coercer_class.must_equal(FakeCoercer)
25
+ end
26
+
27
+ it 'executes the given block' do
28
+ mapper = Lotus::Model::Mapper.new do
29
+ collection :articles do
30
+ entity Article
31
+ end
32
+ end.load!
33
+
34
+ mapper.collection(:articles).must_be_kind_of Lotus::Model::Mapping::Collection
35
+ end
36
+ end
37
+
38
+ describe '#collections' do
39
+ before do
40
+ @mapper = Lotus::Model::Mapper.new do
41
+ collection :teas do
42
+ end
43
+ end
44
+ end
45
+
46
+ it 'returns the mapped collections' do
47
+ name, collection = @mapper.collections.first
48
+
49
+ name.must_equal :teas
50
+ collection.must_be_kind_of Lotus::Model::Mapping::Collection
51
+ end
52
+ end
53
+
54
+ describe '#collection' do
55
+ describe 'when a block is given' do
56
+ it 'register a collection' do
57
+ @mapper.collection :users do
58
+ entity User
59
+ end
60
+
61
+ @mapper.load!
62
+
63
+ collection = @mapper.collection(:users)
64
+ collection.must_be_kind_of Lotus::Model::Mapping::Collection
65
+ collection.name.must_equal :users
66
+ end
67
+ end
68
+
69
+ describe 'when only the name is passed' do
70
+ describe 'and the collection is present' do
71
+ before do
72
+ @mapper.collection :users do
73
+ entity User
74
+ end
75
+
76
+ @mapper.load!
77
+ end
78
+
79
+ it 'returns the collection' do
80
+ collection = @mapper.collection(:users)
81
+
82
+ collection.must_be_kind_of(Lotus::Model::Mapping::Collection)
83
+ collection.name.must_equal :users
84
+ end
85
+ end
86
+
87
+ describe 'and the collection is missing' do
88
+ it 'raises an error' do
89
+ -> { @mapper.collection(:unknown) }.must_raise(Lotus::Model::Mapping::UnmappedCollectionError)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,27 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Model::Mapping::Coercer do
4
+ let(:entity) { User.new(name: 'Tyrion Lannister') }
5
+ let(:collection) { Lotus::Model::Mapping::Collection.new(:users, Lotus::Model::Mapping::Coercer) }
6
+ let(:coercer) { Lotus::Model::Mapping::Coercer.new(collection) }
7
+
8
+ before do
9
+ collection.entity(User)
10
+ collection.attribute :id, Integer
11
+ collection.attribute :name, String
12
+ end
13
+
14
+ describe '#to_record' do
15
+ it 'should not return identity column' do
16
+ coercer.to_record(entity).must_equal(name: 'Tyrion Lannister')
17
+ end
18
+
19
+ describe 'when identity is set' do
20
+ let(:entity) { User.new(id: 3, name: 'Tyrion Lannister') }
21
+
22
+ it 'should return identity as well' do
23
+ coercer.to_record(entity).must_equal(id: 3, name: 'Tyrion Lannister')
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,82 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Model::Mapping::Collection do
4
+ before do
5
+ @collection = Lotus::Model::Mapping::Collection.new(:users, Lotus::Model::Mapping::Coercer)
6
+ end
7
+
8
+ describe '::Boolean' do
9
+ it 'defines top level constant' do
10
+ assert defined?(::Boolean)
11
+ end
12
+ end
13
+
14
+ describe '#initialize' do
15
+ it 'assigns the name' do
16
+ @collection.name.must_equal :users
17
+ end
18
+
19
+ it 'assigns the coercer class' do
20
+ @collection.coercer_class.must_equal Lotus::Model::Mapping::Coercer
21
+ end
22
+
23
+ it 'executes the given block' do
24
+ collection = Lotus::Model::Mapping::Collection.new(:users, Lotus::Model::Mapping::Coercer) do
25
+ entity User
26
+ end
27
+
28
+ collection.entity.must_equal User
29
+ end
30
+ end
31
+
32
+ describe '#entity' do
33
+ describe 'when a value is given' do
34
+ before do
35
+ @collection.entity(User)
36
+ end
37
+
38
+ it 'sets the value' do
39
+ @collection.entity.must_equal User
40
+ end
41
+ end
42
+
43
+ describe 'when a value is not given' do
44
+ it 'retuns the value' do
45
+ @collection.entity.must_be_nil
46
+ end
47
+ end
48
+ end
49
+
50
+ describe '#identity' do
51
+ describe 'when a value is given' do
52
+ before do
53
+ @collection.identity(:_id)
54
+ end
55
+
56
+ it 'sets the value' do
57
+ @collection.identity.must_equal :_id
58
+ end
59
+ end
60
+
61
+ describe 'when a value is not given' do
62
+ it 'retuns the value' do
63
+ @collection.identity.must_equal(:id)
64
+ end
65
+ end
66
+ end
67
+
68
+ describe '#attribute' do
69
+ before do
70
+ @collection.attribute :id, Integer
71
+ @collection.attribute :name, String, as: 't_name'
72
+ end
73
+
74
+ it 'defines an attribute' do
75
+ @collection.attributes[:id].must_equal [Integer, :id]
76
+ end
77
+
78
+ it 'defines a mapped attribute' do
79
+ @collection.attributes[:name].must_equal [String, :t_name]
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,283 @@
1
+ require 'test_helper'
2
+
3
+ describe Lotus::Repository do
4
+ let(:user1) { User.new(name: 'L') }
5
+ let(:user2) { User.new(name: 'MG') }
6
+ let(:users) { [user1, user2] }
7
+
8
+ let(:article1) { Article.new(user_id: user1.id, title: 'Introducing Lotus::Model', comments_count: '23') }
9
+ let(:article2) { Article.new(user_id: user1.id, title: 'Thread safety', comments_count: '42') }
10
+ let(:article3) { Article.new(user_id: user2.id, title: 'Love Relationships', comments_count: '4') }
11
+
12
+ { memory: [Lotus::Model::Adapters::MemoryAdapter, nil, MAPPER],
13
+ sql: [Lotus::Model::Adapters::SqlAdapter, SQLITE_CONNECTION_STRING, MAPPER] }.each do |adapter_name, (adapter,uri,mapper)|
14
+ describe "with #{ adapter_name } adapter" do
15
+ before do
16
+ UserRepository.adapter = adapter.new(mapper, uri)
17
+ ArticleRepository.adapter = adapter.new(mapper, uri)
18
+
19
+ UserRepository.collection = :users
20
+ ArticleRepository.collection = :articles
21
+
22
+ UserRepository.clear
23
+ ArticleRepository.clear
24
+ end
25
+
26
+ describe '.collection' do
27
+ it 'returns the collection name' do
28
+ UserRepository.collection.must_equal :users
29
+ ArticleRepository.collection.must_equal :articles
30
+ end
31
+ end
32
+
33
+ describe '.persist' do
34
+ describe 'when non persisted' do
35
+ before do
36
+ UserRepository.persist(user)
37
+ end
38
+
39
+ let(:user) { User.new(name: 'S') }
40
+
41
+ it 'is created' do
42
+ id = UserRepository.last.id
43
+ UserRepository.find(id).must_equal(user)
44
+ end
45
+ end
46
+
47
+ describe 'when already persisted' do
48
+ before do
49
+ UserRepository.create(user1)
50
+
51
+ user1.name = 'Luke'
52
+ UserRepository.persist(user1)
53
+ end
54
+
55
+ let(:id) { user1.id }
56
+
57
+ it 'is updated' do
58
+ UserRepository.find(id).must_equal(user1)
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '.create' do
64
+ before do
65
+ UserRepository.create(user1)
66
+ UserRepository.create(user2)
67
+ end
68
+
69
+ it 'persist entities' do
70
+ UserRepository.all.must_equal(users)
71
+ end
72
+
73
+ it 'creates different kind of entities' do
74
+ ArticleRepository.create(article1)
75
+ ArticleRepository.all.must_equal([article1])
76
+ end
77
+
78
+ it 'does nothing when already persisted' do
79
+ id = user1.id
80
+
81
+ UserRepository.create(user1)
82
+ user1.id.must_equal id
83
+ end
84
+ end
85
+
86
+ describe '.update' do
87
+ before do
88
+ UserRepository.create(user1)
89
+ end
90
+
91
+ let(:id) { user1.id }
92
+
93
+ it 'updates entities' do
94
+ user = User.new(name: 'Luca')
95
+ user.id = id
96
+
97
+ UserRepository.update(user)
98
+
99
+ u = UserRepository.find(id)
100
+ u.name.must_equal('Luca')
101
+ end
102
+
103
+ it 'raises an error when not persisted' do
104
+ -> { UserRepository.update(user2) }.must_raise(Lotus::Model::NonPersistedEntityError)
105
+ end
106
+ end
107
+
108
+ describe '.delete' do
109
+ before do
110
+ UserRepository.create(user)
111
+ UserRepository.delete(user)
112
+ end
113
+
114
+ let(:user) { User.new(name: 'D') }
115
+
116
+ it 'delete entity' do
117
+ UserRepository.all.wont_include(user)
118
+ end
119
+
120
+ it 'raises error when the given entity is not persisted' do
121
+ -> { UserRepository.delete(user2) }.must_raise(Lotus::Model::NonPersistedEntityError)
122
+ end
123
+ end
124
+
125
+ describe '.all' do
126
+ describe 'without data' do
127
+ it 'returns an empty collection' do
128
+ UserRepository.all.must_be_empty
129
+ end
130
+ end
131
+
132
+ describe 'with data' do
133
+ before do
134
+ UserRepository.create(user1)
135
+ UserRepository.create(user2)
136
+ end
137
+
138
+ it 'returns all the entities' do
139
+ UserRepository.all.must_equal(users)
140
+ end
141
+ end
142
+ end
143
+
144
+ describe '.find' do
145
+ describe 'without data' do
146
+ it 'raises error' do
147
+ -> { UserRepository.find(1) }.must_raise(Lotus::Model::EntityNotFound)
148
+ end
149
+ end
150
+
151
+ describe 'with data' do
152
+ before do
153
+ TestPrimaryKey = Struct.new(:id) do
154
+ def to_int
155
+ id
156
+ end
157
+ end
158
+
159
+ UserRepository.create(user1)
160
+ UserRepository.create(user2)
161
+
162
+ ArticleRepository.create(article1)
163
+ end
164
+
165
+ after do
166
+ Object.send(:remove_const, :TestPrimaryKey)
167
+ end
168
+
169
+ it 'returns the entity associated with the given id' do
170
+ UserRepository.find(user1.id).must_equal(user1)
171
+ end
172
+
173
+ it 'accepts a string as argument' do
174
+ UserRepository.find(user2.id.to_s).must_equal(user2)
175
+ end
176
+
177
+ it 'accepts an object that can be coerced to integer' do
178
+ id = TestPrimaryKey.new(user2.id)
179
+ UserRepository.find(id).must_equal(user2)
180
+ end
181
+
182
+ it 'coerces attributes as indicated by the mapper' do
183
+ result = ArticleRepository.find(article1.id)
184
+ result.comments_count.must_be_kind_of(Integer)
185
+ end
186
+
187
+ it "doesn't assign a value to unmapped attributes" do
188
+ ArticleRepository.find(article1.id).unmapped_attribute.must_be_nil
189
+ end
190
+
191
+ it "raises error when the given id isn't associated with any entity" do
192
+ -> { UserRepository.find(1_000_000) }.must_raise(Lotus::Model::EntityNotFound)
193
+ end
194
+ end
195
+ end
196
+
197
+ describe '.first' do
198
+ describe 'without data' do
199
+ it 'returns nil' do
200
+ UserRepository.first.must_be_nil
201
+ end
202
+ end
203
+
204
+ describe 'with data' do
205
+ before do
206
+ UserRepository.create(user1)
207
+ UserRepository.create(user2)
208
+ end
209
+
210
+ it 'returns first record' do
211
+ UserRepository.first.must_equal(user1)
212
+ end
213
+ end
214
+ end
215
+
216
+ describe '.last' do
217
+ describe 'without data' do
218
+ it 'returns nil' do
219
+ UserRepository.last.must_be_nil
220
+ end
221
+ end
222
+
223
+ describe 'with data' do
224
+ before do
225
+ UserRepository.create(user1)
226
+ UserRepository.create(user2)
227
+ end
228
+
229
+ it 'returns last record' do
230
+ UserRepository.last.must_equal(user2)
231
+ end
232
+ end
233
+ end
234
+
235
+ describe '.clear' do
236
+ describe 'without data' do
237
+ it 'removes all the records' do
238
+ UserRepository.clear
239
+ UserRepository.all.must_be_empty
240
+ end
241
+ end
242
+
243
+ describe 'with data' do
244
+ before do
245
+ UserRepository.create(user1)
246
+ UserRepository.create(user2)
247
+ end
248
+
249
+ it 'removes all the records' do
250
+ UserRepository.clear
251
+ UserRepository.all.must_be_empty
252
+ end
253
+ end
254
+ end
255
+
256
+ describe 'querying' do
257
+ before do
258
+ UserRepository.create(user1)
259
+ ArticleRepository.create(article1)
260
+ ArticleRepository.create(article2)
261
+ ArticleRepository.create(article3)
262
+ end
263
+
264
+ it 'defines custom finders' do
265
+ actual = ArticleRepository.by_user(user1)
266
+ actual.all.must_equal [article1, article2]
267
+ end
268
+
269
+ if adapter_name == :sql
270
+ it 'combines queries' do
271
+ actual = ArticleRepository.rank_by_user(user1)
272
+ actual.all.must_equal [article2, article1]
273
+ end
274
+
275
+ it 'negates a query' do
276
+ actual = ArticleRepository.not_by_user(user1)
277
+ actual.all.must_equal []
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end