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
data/lib/lotus/model/version.rb
CHANGED
@@ -0,0 +1,620 @@
|
|
1
|
+
require 'lotus/utils/class_attribute'
|
2
|
+
|
3
|
+
module Lotus
|
4
|
+
# Mediates between the entities and the persistence layer, by offering an API
|
5
|
+
# to query and execute commands on a databse.
|
6
|
+
#
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# IMPORTANT: A repository MUST be named after an entity, by appeding the
|
10
|
+
# `Repository` suffix to the entity class name.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# require 'lotus/model'
|
14
|
+
#
|
15
|
+
# class Article
|
16
|
+
# include Lotus::Entity
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# # valid
|
20
|
+
# class ArticleRepository
|
21
|
+
# include Lotus::Repository
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # not valid for Article
|
25
|
+
# class PostRepository
|
26
|
+
# include Lotus::Repository
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
#
|
30
|
+
#
|
31
|
+
# A repository is storage idenpendent.
|
32
|
+
# All the queries and commands are delegated to the current adapter.
|
33
|
+
#
|
34
|
+
# This architecture has several advantages:
|
35
|
+
#
|
36
|
+
# * Applications depends on an abstract API, instead of low level details
|
37
|
+
# (Dependency Inversion principle)
|
38
|
+
#
|
39
|
+
# * Applications depends on a stable API, that doesn't change if the
|
40
|
+
# storage changes
|
41
|
+
#
|
42
|
+
# * Developers can postpone storage decisions
|
43
|
+
#
|
44
|
+
# * Isolates the persistence logic at a low level
|
45
|
+
#
|
46
|
+
# Lotus::Model is shipped with two adapters:
|
47
|
+
#
|
48
|
+
# * SqlAdapter
|
49
|
+
# * MemoryAdapter
|
50
|
+
#
|
51
|
+
#
|
52
|
+
#
|
53
|
+
# All the queries and commands are private.
|
54
|
+
# This decision forces developers to define intention revealing API, instead
|
55
|
+
# leak storage API details outside of a repository.
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
# require 'lotus/model'
|
59
|
+
#
|
60
|
+
# # This is bad for several reasons:
|
61
|
+
# #
|
62
|
+
# # * The caller has an intimate knowledge of the internal mechanisms
|
63
|
+
# # of the Repository.
|
64
|
+
# #
|
65
|
+
# # * The caller works on several levels of abstraction.
|
66
|
+
# #
|
67
|
+
# # * It doesn't express a clear intent, it's just a chain of methods.
|
68
|
+
# #
|
69
|
+
# # * The caller can't be easily tested in isolation.
|
70
|
+
# #
|
71
|
+
# # * If we change the storage, we are forced to change the code of the
|
72
|
+
# # caller(s).
|
73
|
+
#
|
74
|
+
# ArticleRepository.where(author_id: 23).order(:published_at).limit(8)
|
75
|
+
#
|
76
|
+
#
|
77
|
+
#
|
78
|
+
# # This is a huge improvement:
|
79
|
+
# #
|
80
|
+
# # * The caller doesn't know how the repository fetches the entities.
|
81
|
+
# #
|
82
|
+
# # * The caller works on a single level of abstraction.
|
83
|
+
# # It doesn't even know about records, only works with entities.
|
84
|
+
# #
|
85
|
+
# # * It expresses a clear intent.
|
86
|
+
# #
|
87
|
+
# # * The caller can be easily tested in isolation.
|
88
|
+
# # It's just a matter of stub this method.
|
89
|
+
# #
|
90
|
+
# # * If we change the storage, the callers aren't affected.
|
91
|
+
#
|
92
|
+
# ArticleRepository.most_recent_by_author(author)
|
93
|
+
#
|
94
|
+
# class ArticleRepository
|
95
|
+
# include Lotus::Repository
|
96
|
+
#
|
97
|
+
# def self.most_recent_by_author(author, limit = 8)
|
98
|
+
# query do
|
99
|
+
# where(author_id: author.id).
|
100
|
+
# order(:published_at)
|
101
|
+
# end.limit(limit)
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# @since 0.1.0
|
106
|
+
#
|
107
|
+
# @see Lotus::Entity
|
108
|
+
# @see http://martinfowler.com/eaaCatalog/repository.html
|
109
|
+
# @see http://en.wikipedia.org/wiki/Dependency_inversion_principle
|
110
|
+
module Repository
|
111
|
+
# Inject the public API into the hosting class.
|
112
|
+
#
|
113
|
+
# @since 0.1.0
|
114
|
+
#
|
115
|
+
# @example
|
116
|
+
# require 'lotus/model'
|
117
|
+
#
|
118
|
+
# class UserRepository
|
119
|
+
# include Lotus::Repository
|
120
|
+
# end
|
121
|
+
def self.included(base)
|
122
|
+
base.class_eval do
|
123
|
+
extend ClassMethods
|
124
|
+
include Lotus::Utils::ClassAttribute
|
125
|
+
|
126
|
+
class_attribute :collection
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
module ClassMethods
|
131
|
+
# Assigns an adapter.
|
132
|
+
#
|
133
|
+
# Lotus::Model is shipped with two adapters:
|
134
|
+
#
|
135
|
+
# * SqlAdapter
|
136
|
+
# * MemoryAdapter
|
137
|
+
#
|
138
|
+
# @param adapter [Object] an object that implements
|
139
|
+
# `Lotus::Model::Adapters::Abstract` interface
|
140
|
+
#
|
141
|
+
# @since 0.1.0
|
142
|
+
#
|
143
|
+
# @see Lotus::Model::Adapters::SqlAdapter
|
144
|
+
# @see Lotus::Model::Adapters::MemoryAdapter
|
145
|
+
#
|
146
|
+
# @example Memory adapter
|
147
|
+
# require 'lotus/model'
|
148
|
+
# require 'lotus/model/adapters/memory_adapter'
|
149
|
+
#
|
150
|
+
# mapper = Lotus::Model::Mapper.new do
|
151
|
+
# # ...
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# adapter = Lotus::Model::Adapters::MemoryAdapter.new(mapper)
|
155
|
+
#
|
156
|
+
# class UserRepository
|
157
|
+
# include Lotus::Repository
|
158
|
+
# end
|
159
|
+
#
|
160
|
+
# UserRepository.adapter = adapter
|
161
|
+
#
|
162
|
+
#
|
163
|
+
#
|
164
|
+
# @example SQL adapter with a Sqlite database
|
165
|
+
# require 'sqlite3'
|
166
|
+
# require 'lotus/model'
|
167
|
+
# require 'lotus/model/adapters/sql_adapter'
|
168
|
+
#
|
169
|
+
# mapper = Lotus::Model::Mapper.new do
|
170
|
+
# # ...
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# adapter = Lotus::Model::Adapters::SqlAdapter.new(mapper, 'sqlite://path/to/database.db')
|
174
|
+
#
|
175
|
+
# class UserRepository
|
176
|
+
# include Lotus::Repository
|
177
|
+
# end
|
178
|
+
#
|
179
|
+
# UserRepository.adapter = adapter
|
180
|
+
#
|
181
|
+
#
|
182
|
+
#
|
183
|
+
# @example SQL adapter with a Postgres database
|
184
|
+
# require 'pg'
|
185
|
+
# require 'lotus/model'
|
186
|
+
# require 'lotus/model/adapters/sql_adapter'
|
187
|
+
#
|
188
|
+
# mapper = Lotus::Model::Mapper.new do
|
189
|
+
# # ...
|
190
|
+
# end
|
191
|
+
#
|
192
|
+
# adapter = Lotus::Model::Adapters::SqlAdapter.new(mapper, 'postgres://host:port/database')
|
193
|
+
#
|
194
|
+
# class UserRepository
|
195
|
+
# include Lotus::Repository
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# UserRepository.adapter = adapter
|
199
|
+
def adapter=(adapter)
|
200
|
+
@adapter = adapter
|
201
|
+
end
|
202
|
+
|
203
|
+
# Creates or updates a record in the database for the given entity.
|
204
|
+
#
|
205
|
+
# @param entity [#id, #id=] the entity to persist
|
206
|
+
#
|
207
|
+
# @return [Object] the entity
|
208
|
+
#
|
209
|
+
# @since 0.1.0
|
210
|
+
#
|
211
|
+
# @see Lotus::Repository#create
|
212
|
+
# @see Lotus::Repository#update
|
213
|
+
#
|
214
|
+
# @example With a non persisted entity
|
215
|
+
# require 'lotus/model'
|
216
|
+
#
|
217
|
+
# class ArticleRepository
|
218
|
+
# include Lotus::Repository
|
219
|
+
# end
|
220
|
+
#
|
221
|
+
# article = Article.new(title: 'Introducing Lotus::Model')
|
222
|
+
# article.id # => nil
|
223
|
+
#
|
224
|
+
# ArticleRepository.persist(article) # creates a record
|
225
|
+
# article.id # => 23
|
226
|
+
#
|
227
|
+
# @example With a persisted entity
|
228
|
+
# require 'lotus/model'
|
229
|
+
#
|
230
|
+
# class ArticleRepository
|
231
|
+
# include Lotus::Repository
|
232
|
+
# end
|
233
|
+
#
|
234
|
+
# article = ArticleRepository.find(23)
|
235
|
+
# article.id # => 23
|
236
|
+
#
|
237
|
+
# article.title = 'Launching Lotus::Model'
|
238
|
+
# ArticleRepository.persist(article) # updates the record
|
239
|
+
#
|
240
|
+
# article = ArticleRepository.find(23)
|
241
|
+
# article.title # => "Launching Lotus::Model"
|
242
|
+
def persist(entity)
|
243
|
+
@adapter.persist(collection, entity)
|
244
|
+
end
|
245
|
+
|
246
|
+
# Creates a record in the database for the given entity.
|
247
|
+
# It assigns the `id` attribute, in case of success.
|
248
|
+
#
|
249
|
+
# If already persisted (`id` present) it does nothing.
|
250
|
+
#
|
251
|
+
# @param entity [#id,#id=] the entity to create
|
252
|
+
#
|
253
|
+
# @return [Object] the entity
|
254
|
+
#
|
255
|
+
# @since 0.1.0
|
256
|
+
#
|
257
|
+
# @see Lotus::Repository#persist
|
258
|
+
#
|
259
|
+
# @example
|
260
|
+
# require 'lotus/model'
|
261
|
+
#
|
262
|
+
# class ArticleRepository
|
263
|
+
# include Lotus::Repository
|
264
|
+
# end
|
265
|
+
#
|
266
|
+
# article = Article.new(title: 'Introducing Lotus::Model')
|
267
|
+
# article.id # => nil
|
268
|
+
#
|
269
|
+
# ArticleRepository.persist(article) # creates a record
|
270
|
+
# article.id # => 23
|
271
|
+
#
|
272
|
+
# ArticleRepository.persist(article) # no-op
|
273
|
+
def create(entity)
|
274
|
+
unless entity.id
|
275
|
+
@adapter.create(collection, entity)
|
276
|
+
end
|
277
|
+
|
278
|
+
entity
|
279
|
+
end
|
280
|
+
|
281
|
+
# Updates a record in the database corresponding to the given entity.
|
282
|
+
#
|
283
|
+
# If not already persisted (`id` present) it raises an exception.
|
284
|
+
#
|
285
|
+
# @param entity [#id] the entity to update
|
286
|
+
#
|
287
|
+
# @return [Object] the entity
|
288
|
+
#
|
289
|
+
# @raise [Lotus::Model::NonPersistedEntityError] if the given entity
|
290
|
+
# wasn't already persisted.
|
291
|
+
#
|
292
|
+
# @since 0.1.0
|
293
|
+
#
|
294
|
+
# @see Lotus::Repository#persist
|
295
|
+
# @see Lotus::Model::NonPersistedEntityError
|
296
|
+
#
|
297
|
+
# @example With a persisted entity
|
298
|
+
# require 'lotus/model'
|
299
|
+
#
|
300
|
+
# class ArticleRepository
|
301
|
+
# include Lotus::Repository
|
302
|
+
# end
|
303
|
+
#
|
304
|
+
# article = ArticleRepository.find(23)
|
305
|
+
# article.id # => 23
|
306
|
+
# article.title = 'Launching Lotus::Model'
|
307
|
+
#
|
308
|
+
# ArticleRepository.update(article) # updates the record
|
309
|
+
#
|
310
|
+
#
|
311
|
+
#
|
312
|
+
# @example With a non persisted entity
|
313
|
+
# require 'lotus/model'
|
314
|
+
#
|
315
|
+
# class ArticleRepository
|
316
|
+
# include Lotus::Repository
|
317
|
+
# end
|
318
|
+
#
|
319
|
+
# article = Article.new(title: 'Introducing Lotus::Model')
|
320
|
+
# article.id # => nil
|
321
|
+
#
|
322
|
+
# ArticleRepository.update(article) # raises Lotus::Model::NonPersistedEntityError
|
323
|
+
def update(entity)
|
324
|
+
if entity.id
|
325
|
+
@adapter.update(collection, entity)
|
326
|
+
else
|
327
|
+
raise Lotus::Model::NonPersistedEntityError
|
328
|
+
end
|
329
|
+
|
330
|
+
entity
|
331
|
+
end
|
332
|
+
|
333
|
+
# Deletes a record in the database corresponding to the given entity.
|
334
|
+
#
|
335
|
+
# If not already persisted (`id` present) it raises an exception.
|
336
|
+
#
|
337
|
+
# @param entity [#id] the entity to delete
|
338
|
+
#
|
339
|
+
# @return [Object] the entity
|
340
|
+
#
|
341
|
+
# @raise [Lotus::Model::NonPersistedEntityError] if the given entity
|
342
|
+
# wasn't already persisted.
|
343
|
+
#
|
344
|
+
# @since 0.1.0
|
345
|
+
#
|
346
|
+
# @see Lotus::Model::NonPersistedEntityError
|
347
|
+
#
|
348
|
+
# @example With a persisted entity
|
349
|
+
# require 'lotus/model'
|
350
|
+
#
|
351
|
+
# class ArticleRepository
|
352
|
+
# include Lotus::Repository
|
353
|
+
# end
|
354
|
+
#
|
355
|
+
# article = ArticleRepository.find(23)
|
356
|
+
# article.id # => 23
|
357
|
+
#
|
358
|
+
# ArticleRepository.delete(article) # deletes the record
|
359
|
+
#
|
360
|
+
#
|
361
|
+
#
|
362
|
+
# @example With a non persisted entity
|
363
|
+
# require 'lotus/model'
|
364
|
+
#
|
365
|
+
# class ArticleRepository
|
366
|
+
# include Lotus::Repository
|
367
|
+
# end
|
368
|
+
#
|
369
|
+
# article = Article.new(title: 'Introducing Lotus::Model')
|
370
|
+
# article.id # => nil
|
371
|
+
#
|
372
|
+
# ArticleRepository.delete(article) # raises Lotus::Model::NonPersistedEntityError
|
373
|
+
def delete(entity)
|
374
|
+
if entity.id
|
375
|
+
@adapter.delete(collection, entity)
|
376
|
+
else
|
377
|
+
raise Lotus::Model::NonPersistedEntityError
|
378
|
+
end
|
379
|
+
|
380
|
+
entity
|
381
|
+
end
|
382
|
+
|
383
|
+
# Returns all the persisted entities.
|
384
|
+
#
|
385
|
+
# @return [Array<Object>] the result of the query
|
386
|
+
#
|
387
|
+
# @since 0.1.0
|
388
|
+
#
|
389
|
+
# @example
|
390
|
+
# require 'lotus/model'
|
391
|
+
#
|
392
|
+
# class ArticleRepository
|
393
|
+
# include Lotus::Repository
|
394
|
+
# end
|
395
|
+
#
|
396
|
+
# ArticleRepository.all # => [ #<Article:0x007f9b19a60098> ]
|
397
|
+
def all
|
398
|
+
@adapter.all(collection)
|
399
|
+
end
|
400
|
+
|
401
|
+
# Finds an entity by its identity.
|
402
|
+
#
|
403
|
+
# If used with a SQL database, it corresponds to the primary key.
|
404
|
+
#
|
405
|
+
# @param id [Object] the identity of the entity
|
406
|
+
#
|
407
|
+
# @return [Object] the result of the query
|
408
|
+
#
|
409
|
+
# @raise [Lotus::Model::EntityNotFound] if the entity cannot be found.
|
410
|
+
#
|
411
|
+
# @since 0.1.0
|
412
|
+
#
|
413
|
+
# @see Lotus::Model::EntityNotFound
|
414
|
+
#
|
415
|
+
# @example With a persisted entity
|
416
|
+
# require 'lotus/model'
|
417
|
+
#
|
418
|
+
# class ArticleRepository
|
419
|
+
# include Lotus::Repository
|
420
|
+
# end
|
421
|
+
#
|
422
|
+
# ArticleRepository.find(9) # => raises Lotus::Model::EntityNotFound
|
423
|
+
def find(id)
|
424
|
+
@adapter.find(collection, id).tap do |record|
|
425
|
+
raise Lotus::Model::EntityNotFound.new unless record
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Returns the first entity in the database.
|
430
|
+
#
|
431
|
+
# @return [Object,nil] the result of the query
|
432
|
+
#
|
433
|
+
# @since 0.1.0
|
434
|
+
#
|
435
|
+
# @see Lotus::Repository#last
|
436
|
+
#
|
437
|
+
# @example With at least one persisted entity
|
438
|
+
# require 'lotus/model'
|
439
|
+
#
|
440
|
+
# class ArticleRepository
|
441
|
+
# include Lotus::Repository
|
442
|
+
# end
|
443
|
+
#
|
444
|
+
# ArticleRepository.first # => #<Article:0x007f8c71d98a28>
|
445
|
+
#
|
446
|
+
# @example With an empty collection
|
447
|
+
# require 'lotus/model'
|
448
|
+
#
|
449
|
+
# class ArticleRepository
|
450
|
+
# include Lotus::Repository
|
451
|
+
# end
|
452
|
+
#
|
453
|
+
# ArticleRepository.first # => nil
|
454
|
+
def first
|
455
|
+
@adapter.first(collection)
|
456
|
+
end
|
457
|
+
|
458
|
+
# Returns the last entity in the database.
|
459
|
+
#
|
460
|
+
# @return [Object,nil] the result of the query
|
461
|
+
#
|
462
|
+
# @since 0.1.0
|
463
|
+
#
|
464
|
+
# @see Lotus::Repository#last
|
465
|
+
#
|
466
|
+
# @example With at least one persisted entity
|
467
|
+
# require 'lotus/model'
|
468
|
+
#
|
469
|
+
# class ArticleRepository
|
470
|
+
# include Lotus::Repository
|
471
|
+
# end
|
472
|
+
#
|
473
|
+
# ArticleRepository.last # => #<Article:0x007f8c71d98a28>
|
474
|
+
#
|
475
|
+
# @example With an empty collection
|
476
|
+
# require 'lotus/model'
|
477
|
+
#
|
478
|
+
# class ArticleRepository
|
479
|
+
# include Lotus::Repository
|
480
|
+
# end
|
481
|
+
#
|
482
|
+
# ArticleRepository.last # => nil
|
483
|
+
def last
|
484
|
+
@adapter.last(collection)
|
485
|
+
end
|
486
|
+
|
487
|
+
# Deletes all the records from the current collection.
|
488
|
+
#
|
489
|
+
# If used with a SQL database it executes a `DELETE FROM <table>`.
|
490
|
+
#
|
491
|
+
# @since 0.1.0
|
492
|
+
#
|
493
|
+
# @example
|
494
|
+
# require 'lotus/model'
|
495
|
+
#
|
496
|
+
# class ArticleRepository
|
497
|
+
# include Lotus::Repository
|
498
|
+
# end
|
499
|
+
#
|
500
|
+
# ArticleRepository.clear # deletes all the records
|
501
|
+
def clear
|
502
|
+
@adapter.clear(collection)
|
503
|
+
end
|
504
|
+
|
505
|
+
private
|
506
|
+
# Fabricates a query and yields the given block to access the low level
|
507
|
+
# APIs exposed by the query itself.
|
508
|
+
#
|
509
|
+
# This is a Ruby private method, because we wanted to prevent outside
|
510
|
+
# objects to query directly the database. However, this is a public API
|
511
|
+
# method, and this is the only way to filter entities.
|
512
|
+
#
|
513
|
+
# The returned query SHOULD be lazy: the entities should be fetched by
|
514
|
+
# the database only when needed.
|
515
|
+
#
|
516
|
+
# The returned query SHOULD refer to the entire collection by default.
|
517
|
+
#
|
518
|
+
# Queries can be reused and combined together. See the example below.
|
519
|
+
# IMPORTANT: This feature works only with the Sql adapter.
|
520
|
+
#
|
521
|
+
# A repository is storage independent.
|
522
|
+
# All the queries are deletegated to the current adapter, which is
|
523
|
+
# responsible to implement a querying API.
|
524
|
+
#
|
525
|
+
# Lotus::Model is shipped with two adapters:
|
526
|
+
#
|
527
|
+
# * SqlAdapter, which yields a Lotus::Model::Adapters::Sql::Query
|
528
|
+
# * MemoryAdapter, which yields a Lotus::Model::Adapters::Memory::Query
|
529
|
+
#
|
530
|
+
# @param blk [Proc] a block of code that is executed in the context of a
|
531
|
+
# query
|
532
|
+
#
|
533
|
+
# @return a query, the type depends on the current adapter
|
534
|
+
#
|
535
|
+
# @api public
|
536
|
+
# @since 0.1.0
|
537
|
+
#
|
538
|
+
# @see Lotus::Model::Adapters::Sql::Query
|
539
|
+
# @see Lotus::Model::Adapters::Memory::Query
|
540
|
+
#
|
541
|
+
# @example
|
542
|
+
# require 'lotus/model'
|
543
|
+
#
|
544
|
+
# class ArticleRepository
|
545
|
+
# include Lotus::Repository
|
546
|
+
#
|
547
|
+
# def self.most_recent_by_author(author, limit = 8)
|
548
|
+
# query do
|
549
|
+
# where(author_id: author.id).
|
550
|
+
# desc(:published_at).
|
551
|
+
# limit(limit)
|
552
|
+
# end
|
553
|
+
# end
|
554
|
+
#
|
555
|
+
# def self.most_recent_published_by_author(author, limit = 8)
|
556
|
+
# # combine .most_recent_published_by_author and .published queries
|
557
|
+
# most_recent_by_author(author, limit).published
|
558
|
+
# end
|
559
|
+
#
|
560
|
+
# def self.published
|
561
|
+
# query do
|
562
|
+
# where(published: true)
|
563
|
+
# end
|
564
|
+
# end
|
565
|
+
#
|
566
|
+
# def self.rank
|
567
|
+
# # reuse .published, which returns a query that respond to #desc
|
568
|
+
# published.desc(:comments_count)
|
569
|
+
# end
|
570
|
+
#
|
571
|
+
# def self.best_article_ever
|
572
|
+
# # reuse .published, which returns a query that respond to #limit
|
573
|
+
# rank.limit(1)
|
574
|
+
# end
|
575
|
+
#
|
576
|
+
# def self.comments_average
|
577
|
+
# query.average(:comments_count)
|
578
|
+
# end
|
579
|
+
# end
|
580
|
+
def query(&blk)
|
581
|
+
@adapter.query(collection, self, &blk)
|
582
|
+
end
|
583
|
+
|
584
|
+
# Negates the filtering conditions of a the given query with the logical
|
585
|
+
# opposite operator.
|
586
|
+
#
|
587
|
+
# This is only supported by the SqlAdapter.
|
588
|
+
#
|
589
|
+
# @param query [Object] a query
|
590
|
+
#
|
591
|
+
# @return a negated query, the type depends on the current adapter
|
592
|
+
#
|
593
|
+
# @api public
|
594
|
+
# @since 0.1.0
|
595
|
+
#
|
596
|
+
# @see Lotus::Model::Adapters::Sql::Query#negate!
|
597
|
+
#
|
598
|
+
# @example
|
599
|
+
# require 'lotus/model'
|
600
|
+
#
|
601
|
+
# class ProjectRepository
|
602
|
+
# include Lotus::Repository
|
603
|
+
#
|
604
|
+
# def self.cool
|
605
|
+
# query do
|
606
|
+
# where(language: 'ruby')
|
607
|
+
# end
|
608
|
+
# end
|
609
|
+
#
|
610
|
+
# def self.not_cool
|
611
|
+
# exclude cool
|
612
|
+
# end
|
613
|
+
# end
|
614
|
+
def exclude(query)
|
615
|
+
query.negate!
|
616
|
+
query
|
617
|
+
end
|
618
|
+
end
|
619
|
+
end
|
620
|
+
end
|