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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +6 -0
- data/.yardopts +5 -0
- data/EXAMPLE.md +217 -0
- data/Gemfile +14 -2
- data/README.md +303 -3
- data/Rakefile +17 -1
- data/lib/lotus-model.rb +1 -0
- data/lib/lotus/entity.rb +157 -0
- data/lib/lotus/model.rb +23 -2
- data/lib/lotus/model/adapters/abstract.rb +167 -0
- data/lib/lotus/model/adapters/implementation.rb +111 -0
- data/lib/lotus/model/adapters/memory/collection.rb +132 -0
- data/lib/lotus/model/adapters/memory/command.rb +90 -0
- data/lib/lotus/model/adapters/memory/query.rb +457 -0
- data/lib/lotus/model/adapters/memory_adapter.rb +149 -0
- data/lib/lotus/model/adapters/sql/collection.rb +209 -0
- data/lib/lotus/model/adapters/sql/command.rb +67 -0
- data/lib/lotus/model/adapters/sql/query.rb +615 -0
- data/lib/lotus/model/adapters/sql_adapter.rb +154 -0
- data/lib/lotus/model/mapper.rb +101 -0
- data/lib/lotus/model/mapping.rb +23 -0
- data/lib/lotus/model/mapping/coercer.rb +80 -0
- data/lib/lotus/model/mapping/collection.rb +336 -0
- data/lib/lotus/model/version.rb +4 -1
- data/lib/lotus/repository.rb +620 -0
- data/lotus-model.gemspec +15 -11
- data/test/entity_test.rb +126 -0
- data/test/fixtures.rb +81 -0
- data/test/model/adapters/abstract_test.rb +75 -0
- data/test/model/adapters/implementation_test.rb +22 -0
- data/test/model/adapters/memory/query_test.rb +91 -0
- data/test/model/adapters/memory_adapter_test.rb +1044 -0
- data/test/model/adapters/sql/query_test.rb +121 -0
- data/test/model/adapters/sql_adapter_test.rb +1078 -0
- data/test/model/mapper_test.rb +94 -0
- data/test/model/mapping/coercer_test.rb +27 -0
- data/test/model/mapping/collection_test.rb +82 -0
- data/test/repository_test.rb +283 -0
- data/test/test_helper.rb +30 -0
- data/test/version_test.rb +7 -0
- 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
|