lotus-model 0.3.0 → 0.3.1
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/CHANGELOG.md +10 -0
- data/README.md +105 -3
- data/lib/lotus/entity.rb +14 -4
- data/lib/lotus/entity/dirty_tracking.rb +126 -0
- data/lib/lotus/model.rb +11 -0
- data/lib/lotus/model/adapters/abstract.rb +10 -0
- data/lib/lotus/model/adapters/memory/query.rb +163 -19
- data/lib/lotus/model/adapters/sql/query.rb +77 -17
- data/lib/lotus/model/adapters/sql_adapter.rb +14 -0
- data/lib/lotus/model/mapping/coercions.rb +2 -2
- data/lib/lotus/model/mapping/collection.rb +1 -0
- data/lib/lotus/model/version.rb +1 -1
- data/lib/lotus/repository.rb +91 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b91d99745af6c3080d3e83ff84b369517df2dd77
|
4
|
+
data.tar.gz: cda6f8e7128dbb6db125054d0092a6f4fc83b413
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1ad445fbbf969ebd9dccc1997bd92a10ccc3e4bd3f679d8691b94f9c66940d72afda02d3fcad063001a1905fcae862b5eeb2b0768bc0d85016f10fc3e5e1ef5e
|
7
|
+
data.tar.gz: ed8fafcd2491178a6b9646e1a28c07110208c7d8d1dfa15420aeca8482fccc38952058b95d3f86c28e0bc8066a1a9503c11b487348853b442a2bd491e9cf8314
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
# Lotus::Model
|
2
2
|
A persistence layer for Lotus
|
3
3
|
|
4
|
+
## v0.3.1 - 2015-05-15
|
5
|
+
### Added
|
6
|
+
- [Dmitry Tymchuk] Dirty tracking for entities (via `Lotus::Entity::DirtyTracking` module to include)
|
7
|
+
- [My Mai] Automatic update of timestamps when an entity is persisted.
|
8
|
+
- [Peter Berkenbosch] Introduced `Lotus::Repository#execute`, to execute raw query/commands against database (eg. `BookRepository.execute "SELECT * FROM users"` or `BookRepository.execute "UPDATE users SET admin = 'f'"`)
|
9
|
+
- [Guilherme Franco] Memory and File System adapters now accept a block for `where`, `or`, `and` conditions (eg `where { age > 33 }`).
|
10
|
+
|
11
|
+
### Fixed
|
12
|
+
- [Luca Guidi] Ensure Array coercion to preserve original data structure
|
13
|
+
|
4
14
|
## v0.3.0 - 2015-03-23
|
5
15
|
### Added
|
6
16
|
- [Linus Pettersson] Database console
|
data/README.md
CHANGED
@@ -150,7 +150,7 @@ However, we suggest to implement this interface by including `Lotus::Entity`, in
|
|
150
150
|
|
151
151
|
See [Dependency Inversion Principle](http://en.wikipedia.org/wiki/Dependency_inversion_principle) for more on interfaces.
|
152
152
|
|
153
|
-
When a class
|
153
|
+
When a class extends a `Lotus::Entity` class, it will also *inherit* its mother's attributes.
|
154
154
|
|
155
155
|
```ruby
|
156
156
|
require 'lotus/model'
|
@@ -349,7 +349,7 @@ For advanced mapping and legacy databases, please have a look at the API doc.
|
|
349
349
|
|
350
350
|
**Known limitations**
|
351
351
|
|
352
|
-
|
352
|
+
Note there are limitations with inherited entities:
|
353
353
|
|
354
354
|
```ruby
|
355
355
|
require 'lotus/model'
|
@@ -374,7 +374,7 @@ mapper = Lotus::Model::Mapper.new do
|
|
374
374
|
end
|
375
375
|
```
|
376
376
|
|
377
|
-
In the example above, there are few problems:
|
377
|
+
In the example above, there are a few problems:
|
378
378
|
|
379
379
|
* `Article` could not be fetched because mapping could not map `price`.
|
380
380
|
* Finding a persisted `RareArticle` record, for eg. `ArticleRepository.find(123)`,
|
@@ -447,6 +447,108 @@ end
|
|
447
447
|
|
448
448
|
**This is not necessary, when Lotus::Model is used within a Lotus application.**
|
449
449
|
|
450
|
+
## Features
|
451
|
+
|
452
|
+
### Timestamps
|
453
|
+
|
454
|
+
If an entity has the following accessors: `:created_at` and `:updated_at`, they will be automatically updated when the entity is persisted.
|
455
|
+
|
456
|
+
```ruby
|
457
|
+
require 'lotus/model'
|
458
|
+
|
459
|
+
class User
|
460
|
+
include Lotus::Entity
|
461
|
+
attributes :name, :created_at, :updated_at
|
462
|
+
end
|
463
|
+
|
464
|
+
class UserRepository
|
465
|
+
include Lotus::Repository
|
466
|
+
end
|
467
|
+
|
468
|
+
Lotus::Model.configure do
|
469
|
+
adapter type: :memory, uri: 'memory://localhost/timestamps'
|
470
|
+
|
471
|
+
mapping do
|
472
|
+
collection :users do
|
473
|
+
entity User
|
474
|
+
repository UserRepository
|
475
|
+
|
476
|
+
attribute :id, Integer
|
477
|
+
attribute :name, String
|
478
|
+
attribute :created_at, DateTime
|
479
|
+
attribute :updated_at, DateTime
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end.load!
|
483
|
+
|
484
|
+
user = User.new(name: 'L')
|
485
|
+
puts user.created_at # => nil
|
486
|
+
puts user.updated_at # => nil
|
487
|
+
|
488
|
+
user = UserRepository.create(user)
|
489
|
+
puts user.created_at.to_s # => "2015-05-15T10:12:20+00:00"
|
490
|
+
puts user.updated_at.to_s # => "2015-05-15T10:12:20+00:00"
|
491
|
+
|
492
|
+
sleep 3
|
493
|
+
user.name = "Luca"
|
494
|
+
user = UserRepository.update(user)
|
495
|
+
puts user.created_at.to_s # => "2015-05-15T10:12:20+00:00"
|
496
|
+
puts user.updated_at.to_s # => "2015-05-15T10:12:23+00:00"
|
497
|
+
```
|
498
|
+
|
499
|
+
### Dirty Tracking
|
500
|
+
|
501
|
+
Entities are able to track changes of their data, if `Lotus::Entity::DirtyTracking` is included.
|
502
|
+
|
503
|
+
```ruby
|
504
|
+
require 'lotus/model'
|
505
|
+
|
506
|
+
class User
|
507
|
+
include Lotus::Entity
|
508
|
+
include Lotus::Entity::DirtyTracking
|
509
|
+
attributes :name, :age
|
510
|
+
end
|
511
|
+
|
512
|
+
class UserRepository
|
513
|
+
include Lotus::Repository
|
514
|
+
end
|
515
|
+
|
516
|
+
Lotus::Model.configure do
|
517
|
+
adapter type: :memory, uri: 'memory://localhost/dirty_tracking'
|
518
|
+
|
519
|
+
mapping do
|
520
|
+
collection :users do
|
521
|
+
entity User
|
522
|
+
repository UserRepository
|
523
|
+
|
524
|
+
attribute :id, Integer
|
525
|
+
attribute :name, String
|
526
|
+
attribute :age, String
|
527
|
+
end
|
528
|
+
end
|
529
|
+
end.load!
|
530
|
+
|
531
|
+
user = User.new(name: 'L')
|
532
|
+
user.changed? # => false
|
533
|
+
|
534
|
+
user.age = 33
|
535
|
+
user.changed? # => true
|
536
|
+
user.changed_attributes # => {:age=>33}
|
537
|
+
|
538
|
+
user = UserRepository.create(user)
|
539
|
+
user.changed? # => false
|
540
|
+
|
541
|
+
user.update(name: 'Luca')
|
542
|
+
user.changed? # => true
|
543
|
+
user.changed_attributes # => {:name=>"Luca"}
|
544
|
+
|
545
|
+
user = UserRepository.update(user)
|
546
|
+
user.changed? # => false
|
547
|
+
|
548
|
+
result = UserRepository.find(user.id)
|
549
|
+
result.changed? # => false
|
550
|
+
```
|
551
|
+
|
450
552
|
## Example
|
451
553
|
|
452
554
|
For a full working example, have a look at [EXAMPLE.md](https://github.com/lotus/model/blob/master/EXAMPLE.md).
|
data/lib/lotus/entity.rb
CHANGED
@@ -138,24 +138,35 @@ module Lotus
|
|
138
138
|
#
|
139
139
|
# User.attributes => #<Set: {:id, :name}>
|
140
140
|
# DeletedUser.attributes => #<Set: {:id, :name, :deleted_at}>
|
141
|
+
#
|
141
142
|
def attributes(*attrs)
|
142
143
|
if attrs.any?
|
143
144
|
attrs = Lotus::Utils::Kernel.Array(attrs)
|
144
145
|
self.attributes.merge attrs
|
145
146
|
|
146
147
|
attrs.each do |attr|
|
147
|
-
|
148
|
+
define_attr_accessor(attr) if defined_attribute?(attr)
|
148
149
|
end
|
149
150
|
else
|
150
151
|
@attributes ||= Set.new
|
151
152
|
end
|
152
153
|
end
|
153
154
|
|
155
|
+
# Define setter/getter methods for attributes.
|
156
|
+
#
|
157
|
+
# @params attr [Symbol] an attribute name
|
158
|
+
#
|
159
|
+
# @since 0.3.1
|
160
|
+
# @api private
|
161
|
+
def define_attr_accessor(attr)
|
162
|
+
attr_accessor(attr)
|
163
|
+
end
|
164
|
+
|
154
165
|
# Check if attr_reader define the given attribute
|
155
166
|
#
|
156
|
-
# @since 0.
|
167
|
+
# @since 0.3.1
|
157
168
|
# @api private
|
158
|
-
def
|
169
|
+
def defined_attribute?(name)
|
159
170
|
name == :id ||
|
160
171
|
!instance_methods.include?(name)
|
161
172
|
end
|
@@ -237,4 +248,3 @@ module Lotus
|
|
237
248
|
|
238
249
|
end
|
239
250
|
end
|
240
|
-
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Lotus
|
2
|
+
module Entity
|
3
|
+
# Dirty tracking for entities
|
4
|
+
#
|
5
|
+
# @since 0.3.1
|
6
|
+
module DirtyTracking
|
7
|
+
# Override setters for attributes to support dirty tracking
|
8
|
+
#
|
9
|
+
# @since 0.3.1
|
10
|
+
#
|
11
|
+
# @example Dirty tracking
|
12
|
+
# require 'lotus/model'
|
13
|
+
#
|
14
|
+
# class User
|
15
|
+
# include Lotus::Entity
|
16
|
+
# include Lotus::Entity::DirtyTracking
|
17
|
+
#
|
18
|
+
# attributes :name
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# article = Article.new(title: 'Generation P')
|
22
|
+
# article.changed? # => false
|
23
|
+
#
|
24
|
+
# article.title = 'Master and Margarita'
|
25
|
+
# article.changed? # => true
|
26
|
+
#
|
27
|
+
# article.changed_attributes # => {:title => "Generation P"}
|
28
|
+
def self.included(base)
|
29
|
+
base.class_eval do
|
30
|
+
extend ClassMethods
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ClassMethods
|
35
|
+
# Override attribute accessors function.
|
36
|
+
# Create setter methods with attribute values checking.
|
37
|
+
# If the new value or a changed, added to @changed_attributes.
|
38
|
+
#
|
39
|
+
# @params attr [Symbol] an attribute name
|
40
|
+
#
|
41
|
+
# @since 0.3.1
|
42
|
+
# @api private
|
43
|
+
#
|
44
|
+
# @see Lotus::Entity::ClassMethods#define_attr_accessor
|
45
|
+
def define_attr_accessor(attr)
|
46
|
+
attr_reader(attr)
|
47
|
+
|
48
|
+
class_eval %{
|
49
|
+
def #{ attr }=(value)
|
50
|
+
_attribute_changed(:#{ attr }, @#{ attr }, value)
|
51
|
+
@#{ attr } = value
|
52
|
+
end
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Override initialize process.
|
58
|
+
#
|
59
|
+
# @param attributes [Hash] a set of attribute names and values
|
60
|
+
#
|
61
|
+
# @since 0.3.1
|
62
|
+
#
|
63
|
+
# @see Lotus::Entity#initialize
|
64
|
+
def initialize(attributes = {})
|
65
|
+
_clear_changes_information
|
66
|
+
super
|
67
|
+
_clear_changes_information
|
68
|
+
end
|
69
|
+
|
70
|
+
# Getter for hash of changed attributes.
|
71
|
+
# Return empty hash, if there is no changes
|
72
|
+
# Getter for hash of changed attributes. Value in it is the previous one.
|
73
|
+
#
|
74
|
+
# @return [::Hash] the changed attributes
|
75
|
+
#
|
76
|
+
# @since 0.3.1
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# require 'lotus/model'
|
80
|
+
#
|
81
|
+
# class User
|
82
|
+
# include Lotus::Entity
|
83
|
+
# include Lotus::Entity::DirtyTracking
|
84
|
+
#
|
85
|
+
# attributes :title
|
86
|
+
# end
|
87
|
+
#
|
88
|
+
# article = Article.new(title: 'The crime and punishment')
|
89
|
+
# article.changed_attributes # => {}
|
90
|
+
#
|
91
|
+
# article.title = 'Master and Margarita'
|
92
|
+
# article.changed_attributes # => {:title => "The crime and punishment"}
|
93
|
+
def changed_attributes
|
94
|
+
@changed_attributes.dup
|
95
|
+
end
|
96
|
+
|
97
|
+
# Checks if the attributes were changed
|
98
|
+
#
|
99
|
+
# @return [TrueClass, FalseClass] the result of the check
|
100
|
+
#
|
101
|
+
# @since 0.3.1
|
102
|
+
def changed?
|
103
|
+
@changed_attributes.size > 0
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
# Set changed attributes in Hash with their old values.
|
109
|
+
#
|
110
|
+
# @params attrs [Symbol] an attribute name
|
111
|
+
#
|
112
|
+
# @since 0.3.1
|
113
|
+
# @api private
|
114
|
+
def _attribute_changed(attr, current_value, new_value)
|
115
|
+
@changed_attributes[attr] = new_value if current_value != new_value
|
116
|
+
end
|
117
|
+
|
118
|
+
# Clear all information about dirty data
|
119
|
+
#
|
120
|
+
# @since 0.3.1
|
121
|
+
def _clear_changes_information
|
122
|
+
@changed_attributes = {}
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
data/lib/lotus/model.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'lotus/model/version'
|
2
2
|
require 'lotus/entity'
|
3
|
+
require 'lotus/entity/dirty_tracking'
|
3
4
|
require 'lotus/repository'
|
4
5
|
require 'lotus/model/mapper'
|
5
6
|
require 'lotus/model/configuration'
|
@@ -27,6 +28,16 @@ module Lotus
|
|
27
28
|
class InvalidMappingError < ::StandardError
|
28
29
|
end
|
29
30
|
|
31
|
+
# Error for invalid query
|
32
|
+
# It's raised when a query is malformed
|
33
|
+
#
|
34
|
+
# @since 0.3.1
|
35
|
+
class InvalidQueryError < ::StandardError
|
36
|
+
def initialize(message = "Invalid query")
|
37
|
+
super
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
30
41
|
include Utils::ClassAttribute
|
31
42
|
|
32
43
|
# Framework configuration
|
@@ -201,6 +201,16 @@ module Lotus
|
|
201
201
|
def connection_string
|
202
202
|
raise NotSupportedError
|
203
203
|
end
|
204
|
+
|
205
|
+
# Executes a raw statement directly on the connection
|
206
|
+
#
|
207
|
+
# @param raw [String] the raw statement to execute on the connection
|
208
|
+
# @return [Object]
|
209
|
+
#
|
210
|
+
# @since 0.3.1
|
211
|
+
def execute(raw)
|
212
|
+
raise NotImplementedError
|
213
|
+
end
|
204
214
|
end
|
205
215
|
end
|
206
216
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'forwardable'
|
2
|
+
require 'ostruct'
|
2
3
|
require 'lotus/utils/kernel'
|
3
4
|
|
4
5
|
module Lotus
|
@@ -17,7 +18,7 @@ module Lotus
|
|
17
18
|
#
|
18
19
|
# query.where(language: 'ruby')
|
19
20
|
# .and(framework: 'lotus')
|
20
|
-
# .
|
21
|
+
# .reverse_order(:users_count).all
|
21
22
|
#
|
22
23
|
# # the records are fetched only when we invoke #all
|
23
24
|
#
|
@@ -94,13 +95,42 @@ module Lotus
|
|
94
95
|
#
|
95
96
|
# query.where(year: 1900..1982)
|
96
97
|
#
|
98
|
+
# @example Using block
|
99
|
+
#
|
100
|
+
# query.where { age > 31 }
|
101
|
+
#
|
97
102
|
# @example Multiple conditions
|
98
103
|
#
|
99
104
|
# query.where(language: 'ruby')
|
100
105
|
# .where(framework: 'lotus')
|
101
|
-
|
102
|
-
|
103
|
-
|
106
|
+
#
|
107
|
+
# @example Multiple conditions with blocks
|
108
|
+
#
|
109
|
+
# query.where { language == 'ruby' }
|
110
|
+
# .where { framework == 'lotus' }
|
111
|
+
#
|
112
|
+
# @example Mixed hash and block conditions
|
113
|
+
#
|
114
|
+
# query.where(language: 'ruby')
|
115
|
+
# .where { framework == 'lotus' }
|
116
|
+
def where(condition = nil, &blk)
|
117
|
+
if blk
|
118
|
+
_push_evaluated_block_condition(:where, blk, :find_all)
|
119
|
+
elsif condition
|
120
|
+
_push_to_expanded_condition(:where, condition) do |column, value|
|
121
|
+
Proc.new {
|
122
|
+
find_all { |r|
|
123
|
+
case value
|
124
|
+
when Array,Set,Range
|
125
|
+
value.include?(r.fetch(column, nil))
|
126
|
+
else
|
127
|
+
r.fetch(column, nil) == value
|
128
|
+
end
|
129
|
+
}
|
130
|
+
}
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
104
134
|
self
|
105
135
|
end
|
106
136
|
|
@@ -131,9 +161,24 @@ module Lotus
|
|
131
161
|
# @example Range
|
132
162
|
#
|
133
163
|
# query.where(country: 'italy').or(year: 1900..1982)
|
134
|
-
|
135
|
-
|
136
|
-
|
164
|
+
#
|
165
|
+
# @example Using block
|
166
|
+
#
|
167
|
+
# query.where { age == 31 }.or { age == 32 }
|
168
|
+
#
|
169
|
+
# @example Mixed hash and block conditions
|
170
|
+
#
|
171
|
+
# query.where(language: 'ruby')
|
172
|
+
# .or { framework == 'lotus' }
|
173
|
+
def or(condition = nil, &blk)
|
174
|
+
if blk
|
175
|
+
_push_evaluated_block_condition(:or, blk, :find_all)
|
176
|
+
elsif condition
|
177
|
+
_push_to_expanded_condition(:or, condition) do |column, value|
|
178
|
+
Proc.new { find_all { |r| r.fetch(column) == value} }
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
137
182
|
self
|
138
183
|
end
|
139
184
|
|
@@ -165,9 +210,29 @@ module Lotus
|
|
165
210
|
#
|
166
211
|
# query.exclude(language: 'java')
|
167
212
|
# .exclude(company: 'enterprise')
|
168
|
-
|
169
|
-
|
170
|
-
|
213
|
+
#
|
214
|
+
# @example Using block
|
215
|
+
#
|
216
|
+
# query.exclude { age > 31 }
|
217
|
+
#
|
218
|
+
# @example Multiple conditions with blocks
|
219
|
+
#
|
220
|
+
# query.exclude { language == 'java' }
|
221
|
+
# .exclude { framework == 'spring' }
|
222
|
+
#
|
223
|
+
# @example Mixed hash and block conditions
|
224
|
+
#
|
225
|
+
# query.exclude(language: 'java')
|
226
|
+
# .exclude { framework == 'spring' }
|
227
|
+
def exclude(condition = nil, &blk)
|
228
|
+
if blk
|
229
|
+
_push_evaluated_block_condition(:where, blk, :reject)
|
230
|
+
elsif condition
|
231
|
+
_push_to_expanded_condition(:where, condition) do |column, value|
|
232
|
+
Proc.new { reject { |r| r.fetch(column) == value} }
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
171
236
|
self
|
172
237
|
end
|
173
238
|
|
@@ -204,7 +269,7 @@ module Lotus
|
|
204
269
|
#
|
205
270
|
# @since 0.1.0
|
206
271
|
#
|
207
|
-
# @see Lotus::Model::Adapters::
|
272
|
+
# @see Lotus::Model::Adapters::Memory::Query#reverse_order
|
208
273
|
#
|
209
274
|
# @example Single column
|
210
275
|
#
|
@@ -225,6 +290,23 @@ module Lotus
|
|
225
290
|
self
|
226
291
|
end
|
227
292
|
|
293
|
+
# Alias for order
|
294
|
+
#
|
295
|
+
# @since 0.1.0
|
296
|
+
#
|
297
|
+
# @see Lotus::Model::Adapters::Memory::Query#order
|
298
|
+
#
|
299
|
+
# @example Single column
|
300
|
+
#
|
301
|
+
# query.asc(:name)
|
302
|
+
#
|
303
|
+
# @example Multiple columns
|
304
|
+
#
|
305
|
+
# query.asc(:name, :year)
|
306
|
+
#
|
307
|
+
# @example Multiple invokations
|
308
|
+
#
|
309
|
+
# query.asc(:name).asc(:year)
|
228
310
|
alias_method :asc, :order
|
229
311
|
|
230
312
|
# Specify the descending order of the records, sorted by the given
|
@@ -234,22 +316,22 @@ module Lotus
|
|
234
316
|
#
|
235
317
|
# @return self
|
236
318
|
#
|
237
|
-
# @since 0.1
|
319
|
+
# @since 0.3.1
|
238
320
|
#
|
239
|
-
# @see Lotus::Model::Adapters::
|
321
|
+
# @see Lotus::Model::Adapters::Memory::Query#order
|
240
322
|
#
|
241
323
|
# @example Single column
|
242
324
|
#
|
243
|
-
# query.
|
325
|
+
# query.reverse_order(:name)
|
244
326
|
#
|
245
327
|
# @example Multiple columns
|
246
328
|
#
|
247
|
-
# query.
|
329
|
+
# query.reverse_order(:name, :year)
|
248
330
|
#
|
249
331
|
# @example Multiple invokations
|
250
332
|
#
|
251
|
-
# query.
|
252
|
-
def
|
333
|
+
# query.reverse_order(:name).reverse_order(:year)
|
334
|
+
def reverse_order(*columns)
|
253
335
|
Lotus::Utils::Kernel.Array(columns).each do |column|
|
254
336
|
modifiers.push(Proc.new{ sort_by!{|r| r.fetch(column)}.reverse! })
|
255
337
|
end
|
@@ -257,6 +339,25 @@ module Lotus
|
|
257
339
|
self
|
258
340
|
end
|
259
341
|
|
342
|
+
# Alias for reverse_order
|
343
|
+
#
|
344
|
+
# @since 0.1.0
|
345
|
+
#
|
346
|
+
# @see Lotus::Model::Adapters::Memory::Query#reverse_order
|
347
|
+
#
|
348
|
+
# @example Single column
|
349
|
+
#
|
350
|
+
# query.desc(:name)
|
351
|
+
#
|
352
|
+
# @example Multiple columns
|
353
|
+
#
|
354
|
+
# query.desc(:name, :year)
|
355
|
+
#
|
356
|
+
# @example Multiple invokations
|
357
|
+
#
|
358
|
+
# query.desc(:name).desc(:year)
|
359
|
+
alias_method :desc, :reverse_order
|
360
|
+
|
260
361
|
# Limit the number of records to return.
|
261
362
|
#
|
262
363
|
# @param number [Fixnum]
|
@@ -486,8 +587,51 @@ module Lotus
|
|
486
587
|
all.map {|record| record.public_send(column) }.compact
|
487
588
|
end
|
488
589
|
|
489
|
-
|
490
|
-
|
590
|
+
# Expands and yields keys and values of a query hash condition and
|
591
|
+
# stores the result and condition type in the conditions array.
|
592
|
+
#
|
593
|
+
# It yields condition's keys and values to allow the caller to create a proc
|
594
|
+
# object to be stored and executed later performing the actual query.
|
595
|
+
#
|
596
|
+
# @param condition_type [Symbol] the condition type. (eg. `:where`, `:or`)
|
597
|
+
# @param condition [Hash] the query condition to be expanded.
|
598
|
+
#
|
599
|
+
# @return [Array<Array>] the conditions array itself.
|
600
|
+
#
|
601
|
+
# @api private
|
602
|
+
# @since 0.3.1
|
603
|
+
def _push_to_expanded_condition(condition_type, condition)
|
604
|
+
proc = yield Array(condition).flatten(1)
|
605
|
+
conditions.push([condition_type, proc])
|
606
|
+
end
|
607
|
+
|
608
|
+
# Evaluates a block condition of a specified type and stores it in the
|
609
|
+
# conditions array.
|
610
|
+
#
|
611
|
+
# @param condition_type [Symbol] the condition type. (eg. `:where`, `:or`)
|
612
|
+
# @param condition [Proc] the query condition to be evaluated and stored.
|
613
|
+
# @param strategy [Symbol] the iterator method to be executed.
|
614
|
+
# (eg. `:find_all`, `:reject`)
|
615
|
+
#
|
616
|
+
# @return [Array<Array>] the conditions array itself.
|
617
|
+
#
|
618
|
+
# @raise [Lotus::Model::InvalidQueryError] if block raises error when
|
619
|
+
# evaluated.
|
620
|
+
#
|
621
|
+
# @api private
|
622
|
+
# @since 0.3.1
|
623
|
+
def _push_evaluated_block_condition(condition_type, condition, strategy)
|
624
|
+
conditions.push([condition_type, Proc.new {
|
625
|
+
send(strategy) { |r|
|
626
|
+
begin
|
627
|
+
OpenStruct.new(r).instance_eval(&condition)
|
628
|
+
rescue NoMethodError
|
629
|
+
# TODO improve the error message, informing which
|
630
|
+
# attributes are invalid
|
631
|
+
raise Lotus::Model::InvalidQueryError.new
|
632
|
+
end
|
633
|
+
}
|
634
|
+
}])
|
491
635
|
end
|
492
636
|
end
|
493
637
|
end
|
@@ -17,7 +17,7 @@ module Lotus
|
|
17
17
|
#
|
18
18
|
# query.where(language: 'ruby')
|
19
19
|
# .and(framework: 'lotus')
|
20
|
-
# .
|
20
|
+
# .reverse_order(:users_count).all
|
21
21
|
#
|
22
22
|
# # the records are fetched only when we invoke #all
|
23
23
|
#
|
@@ -70,9 +70,14 @@ module Lotus
|
|
70
70
|
#
|
71
71
|
# @return [Array] a collection of entities
|
72
72
|
#
|
73
|
+
# @raise [Lotus::Model::InvalidQueryError] if there is some issue when
|
74
|
+
# hitting the database for fetching records
|
75
|
+
#
|
73
76
|
# @since 0.1.0
|
74
77
|
def all
|
75
78
|
Lotus::Utils::Kernel.Array(run)
|
79
|
+
rescue Sequel::DatabaseError => e
|
80
|
+
raise Lotus::Model::InvalidQueryError.new(e.message)
|
76
81
|
end
|
77
82
|
|
78
83
|
# Adds a SQL `WHERE` condition.
|
@@ -117,9 +122,8 @@ module Lotus
|
|
117
122
|
# query.where{ age > 10 }
|
118
123
|
#
|
119
124
|
# # => SELECT * FROM `users` WHERE (`age` > 31)
|
120
|
-
def where(condition=nil, &blk)
|
121
|
-
|
122
|
-
conditions.push([:where, condition])
|
125
|
+
def where(condition = nil, &blk)
|
126
|
+
_push_to_conditions(:where, condition || blk)
|
123
127
|
self
|
124
128
|
end
|
125
129
|
|
@@ -162,9 +166,8 @@ module Lotus
|
|
162
166
|
# query.where(name: 'John').or{ age > 31 }
|
163
167
|
#
|
164
168
|
# # => SELECT * FROM `users` WHERE ((`name` = 'John') OR (`age` < 32))
|
165
|
-
def or(condition=nil, &blk)
|
166
|
-
|
167
|
-
conditions.push([:or, condition])
|
169
|
+
def or(condition = nil, &blk)
|
170
|
+
_push_to_conditions(:or, condition || blk)
|
168
171
|
self
|
169
172
|
end
|
170
173
|
|
@@ -209,9 +212,8 @@ module Lotus
|
|
209
212
|
# query.exclude{ age > 31 }
|
210
213
|
#
|
211
214
|
# # => SELECT * FROM `users` WHERE (`age` <= 31)
|
212
|
-
def exclude(condition=nil, &blk)
|
213
|
-
|
214
|
-
conditions.push([:exclude, condition])
|
215
|
+
def exclude(condition = nil, &blk)
|
216
|
+
_push_to_conditions(:exclude, condition || blk).inspect
|
215
217
|
self
|
216
218
|
end
|
217
219
|
|
@@ -294,7 +296,7 @@ module Lotus
|
|
294
296
|
#
|
295
297
|
# @since 0.1.0
|
296
298
|
#
|
297
|
-
# @see Lotus::Model::Adapters::Sql::Query#
|
299
|
+
# @see Lotus::Model::Adapters::Sql::Query#reverse_order
|
298
300
|
#
|
299
301
|
# @example Single column
|
300
302
|
#
|
@@ -318,6 +320,29 @@ module Lotus
|
|
318
320
|
self
|
319
321
|
end
|
320
322
|
|
323
|
+
# Alias for order
|
324
|
+
#
|
325
|
+
# @since 0.1.0
|
326
|
+
#
|
327
|
+
# @see Lotus::Model::Adapters::Sql::Query#order
|
328
|
+
#
|
329
|
+
# @example Single column
|
330
|
+
#
|
331
|
+
# query.asc(:name)
|
332
|
+
#
|
333
|
+
# # => SELECT * FROM `people` ORDER BY (`name`)
|
334
|
+
#
|
335
|
+
# @example Multiple columns
|
336
|
+
#
|
337
|
+
# query.asc(:name, :year)
|
338
|
+
#
|
339
|
+
# # => SELECT * FROM `people` ORDER BY `name`, `year`
|
340
|
+
#
|
341
|
+
# @example Multiple invokations
|
342
|
+
#
|
343
|
+
# query.asc(:name).asc(:year)
|
344
|
+
#
|
345
|
+
# # => SELECT * FROM `people` ORDER BY `name`, `year`
|
321
346
|
alias_method :asc, :order
|
322
347
|
|
323
348
|
# Specify the descending order of the records, sorted by the given
|
@@ -327,28 +352,28 @@ module Lotus
|
|
327
352
|
#
|
328
353
|
# @return self
|
329
354
|
#
|
330
|
-
# @since 0.1
|
355
|
+
# @since 0.3.1
|
331
356
|
#
|
332
357
|
# @see Lotus::Model::Adapters::Sql::Query#order
|
333
358
|
#
|
334
359
|
# @example Single column
|
335
360
|
#
|
336
|
-
# query.
|
361
|
+
# query.reverse_order(:name)
|
337
362
|
#
|
338
363
|
# # => SELECT * FROM `people` ORDER BY (`name`) DESC
|
339
364
|
#
|
340
365
|
# @example Multiple columns
|
341
366
|
#
|
342
|
-
# query.
|
367
|
+
# query.reverse_order(:name, :year)
|
343
368
|
#
|
344
369
|
# # => SELECT * FROM `people` ORDER BY `name`, `year` DESC
|
345
370
|
#
|
346
371
|
# @example Multiple invokations
|
347
372
|
#
|
348
|
-
# query.
|
373
|
+
# query.reverse_order(:name).reverse_order(:year)
|
349
374
|
#
|
350
375
|
# # => SELECT * FROM `people` ORDER BY `name`, `year` DESC
|
351
|
-
def
|
376
|
+
def reverse_order(*columns)
|
352
377
|
Array(columns).each do |column|
|
353
378
|
conditions.push([_order_operator, Sequel.desc(column)])
|
354
379
|
end
|
@@ -356,6 +381,25 @@ module Lotus
|
|
356
381
|
self
|
357
382
|
end
|
358
383
|
|
384
|
+
# Alias for reverse_order
|
385
|
+
#
|
386
|
+
# @since 0.1.0
|
387
|
+
#
|
388
|
+
# @see Lotus::Model::Adapters::Sql::Query#reverse_order
|
389
|
+
#
|
390
|
+
# @example Single column
|
391
|
+
#
|
392
|
+
# query.desc(:name)
|
393
|
+
#
|
394
|
+
# @example Multiple columns
|
395
|
+
#
|
396
|
+
# query.desc(:name, :year)
|
397
|
+
#
|
398
|
+
# @example Multiple invokations
|
399
|
+
#
|
400
|
+
# query.desc(:name).desc(:year)
|
401
|
+
alias_method :desc, :reverse_order
|
402
|
+
|
359
403
|
# Returns the sum of the values for the given column.
|
360
404
|
#
|
361
405
|
# @param column [Symbol] the column name
|
@@ -590,7 +634,7 @@ module Lotus
|
|
590
634
|
# end
|
591
635
|
#
|
592
636
|
# def self.rank
|
593
|
-
# query.
|
637
|
+
# query.reverse_order(:comments_count)
|
594
638
|
# end
|
595
639
|
#
|
596
640
|
# def self.rank_by_author(author)
|
@@ -621,6 +665,22 @@ module Lotus
|
|
621
665
|
end
|
622
666
|
end
|
623
667
|
|
668
|
+
# Stores a query condition of a specified type in the conditions array.
|
669
|
+
#
|
670
|
+
# @param condition_type [Symbol] the condition type. (eg. `:where`, `:or`)
|
671
|
+
# @param condition [Hash, Proc] the query condition to be stored.
|
672
|
+
#
|
673
|
+
# @return [Array<Array>] the conditions array itself.
|
674
|
+
#
|
675
|
+
# @raise [ArgumentError] if condition is not specified.
|
676
|
+
#
|
677
|
+
# @api private
|
678
|
+
# @since 0.3.1
|
679
|
+
def _push_to_conditions(condition_type, condition)
|
680
|
+
raise ArgumentError.new('You need to specify a condition.') if condition.nil?
|
681
|
+
conditions.push([condition_type, condition])
|
682
|
+
end
|
683
|
+
|
624
684
|
def _order_operator
|
625
685
|
if conditions.any? {|c, _| c == :order }
|
626
686
|
:order_more
|
@@ -227,6 +227,20 @@ module Lotus
|
|
227
227
|
Sql::Console.new(@uri).connection_string
|
228
228
|
end
|
229
229
|
|
230
|
+
# Executes raw sql directly on the connection
|
231
|
+
#
|
232
|
+
# @param raw [String] the raw sql statement to execute on the connection
|
233
|
+
# @return [Object]
|
234
|
+
#
|
235
|
+
# @since 0.3.1
|
236
|
+
def execute(raw)
|
237
|
+
begin
|
238
|
+
@connection.execute(raw)
|
239
|
+
rescue Sequel::DatabaseError => e
|
240
|
+
raise Lotus::Model::InvalidQueryError.new(e.message)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
230
244
|
private
|
231
245
|
|
232
246
|
# Returns a collection from the given name.
|
@@ -17,9 +17,9 @@ module Lotus
|
|
17
17
|
#
|
18
18
|
# @since 0.1.1
|
19
19
|
#
|
20
|
-
# @see http://
|
20
|
+
# @see http://ruby-doc.org/core/Kernel.html#method-i-Array
|
21
21
|
def self.Array(arg)
|
22
|
-
|
22
|
+
::Kernel.Array(arg) unless arg.nil?
|
23
23
|
end
|
24
24
|
|
25
25
|
# Coerce into a Boolean, unless the argument is nil
|
data/lib/lotus/model/version.rb
CHANGED
data/lib/lotus/repository.rb
CHANGED
@@ -252,6 +252,7 @@ module Lotus
|
|
252
252
|
# article = ArticleRepository.find(23)
|
253
253
|
# article.title # => "Launching Lotus::Model"
|
254
254
|
def persist(entity)
|
255
|
+
_touch(entity)
|
255
256
|
@adapter.persist(collection, entity)
|
256
257
|
end
|
257
258
|
|
@@ -283,7 +284,8 @@ module Lotus
|
|
283
284
|
#
|
284
285
|
# ArticleRepository.create(article) # no-op
|
285
286
|
def create(entity)
|
286
|
-
unless entity
|
287
|
+
unless _persisted?(entity)
|
288
|
+
_touch(entity)
|
287
289
|
@adapter.create(collection, entity)
|
288
290
|
end
|
289
291
|
end
|
@@ -331,7 +333,8 @@ module Lotus
|
|
331
333
|
#
|
332
334
|
# ArticleRepository.update(article) # raises Lotus::Model::NonPersistedEntityError
|
333
335
|
def update(entity)
|
334
|
-
if entity
|
336
|
+
if _persisted?(entity)
|
337
|
+
_touch(entity)
|
335
338
|
@adapter.update(collection, entity)
|
336
339
|
else
|
337
340
|
raise Lotus::Model::NonPersistedEntityError
|
@@ -379,7 +382,7 @@ module Lotus
|
|
379
382
|
#
|
380
383
|
# ArticleRepository.delete(article) # raises Lotus::Model::NonPersistedEntityError
|
381
384
|
def delete(entity)
|
382
|
-
if entity
|
385
|
+
if _persisted?(entity)
|
383
386
|
@adapter.delete(collection, entity)
|
384
387
|
else
|
385
388
|
raise Lotus::Model::NonPersistedEntityError
|
@@ -548,6 +551,50 @@ module Lotus
|
|
548
551
|
end
|
549
552
|
end
|
550
553
|
|
554
|
+
# Executes the given raw statement on the adapter.
|
555
|
+
#
|
556
|
+
# Please note that it's only supported by some databases,
|
557
|
+
# a `NotImplementedError` will be raised when the adapter does not
|
558
|
+
# responds to the `execute` method.
|
559
|
+
#
|
560
|
+
# For advanced scenarios, please check the documentation of each adapter.
|
561
|
+
#
|
562
|
+
# @param raw [String] the raw statement to execute on the connection
|
563
|
+
# @return [Object] the raw result set from SQL adapter
|
564
|
+
#
|
565
|
+
# @raise [NotImplementedError] if current Lotus::Model adapter doesn't
|
566
|
+
# implement `execute`.
|
567
|
+
#
|
568
|
+
# @see Lotus::Model::Adapters::Abstract#execute
|
569
|
+
# @see Lotus::Model::Adapters::SqlAdapter#execute
|
570
|
+
#
|
571
|
+
# @since 0.3.1
|
572
|
+
#
|
573
|
+
# @example Basic usage with SQL adapter
|
574
|
+
# require 'lotus/model'
|
575
|
+
#
|
576
|
+
# class Article
|
577
|
+
# include Lotus::Entity
|
578
|
+
# attributes :title, :body
|
579
|
+
# end
|
580
|
+
#
|
581
|
+
# class ArticleRepository
|
582
|
+
# include Lotus::Repository
|
583
|
+
# end
|
584
|
+
#
|
585
|
+
# article = Article.new(title: 'Introducing transactions',
|
586
|
+
# body: 'lorem ipsum')
|
587
|
+
#
|
588
|
+
# result_set = ArticleRepository.execute("SELECT * FROM articles")
|
589
|
+
# result_set.each_hash do |result|
|
590
|
+
# result # -> { id: 123, title: "Introducing transactions", body: "lorem ipsum"}
|
591
|
+
# end
|
592
|
+
#
|
593
|
+
# puts result_set.class # => SQLite3::ResultSet
|
594
|
+
def execute(raw)
|
595
|
+
@adapter.execute(raw)
|
596
|
+
end
|
597
|
+
|
551
598
|
private
|
552
599
|
# Fabricates a query and yields the given block to access the low level
|
553
600
|
# APIs exposed by the query itself.
|
@@ -661,6 +708,47 @@ module Lotus
|
|
661
708
|
query.negate!
|
662
709
|
query
|
663
710
|
end
|
711
|
+
|
712
|
+
# This is a method to check entity persited or not
|
713
|
+
#
|
714
|
+
# @param entity
|
715
|
+
# @return a boolean value
|
716
|
+
# @since 0.3.1
|
717
|
+
def _persisted?(entity)
|
718
|
+
!!entity.id
|
719
|
+
end
|
720
|
+
|
721
|
+
# Update timestamps
|
722
|
+
#
|
723
|
+
# @param entity [Object, Lotus::Entity] the entity
|
724
|
+
#
|
725
|
+
# @api private
|
726
|
+
# @since 0.3.1
|
727
|
+
def _touch(entity)
|
728
|
+
now = Time.now.utc
|
729
|
+
|
730
|
+
if _has_timestamp?(entity, :created_at)
|
731
|
+
entity.created_at ||= now
|
732
|
+
end
|
733
|
+
|
734
|
+
if _has_timestamp?(entity, :updated_at)
|
735
|
+
entity.updated_at = now
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
739
|
+
# Check if the given entity has the given timestamp
|
740
|
+
#
|
741
|
+
# @param entity [Object, Lotus::Entity] the entity
|
742
|
+
# @param timestamp [Symbol] the timestamp name
|
743
|
+
#
|
744
|
+
# @return [TrueClass,FalseClass]
|
745
|
+
#
|
746
|
+
# @api private
|
747
|
+
# @since 0.3.1
|
748
|
+
def _has_timestamp?(entity, timestamp)
|
749
|
+
entity.respond_to?(timestamp) &&
|
750
|
+
entity.respond_to?("#{ timestamp }=")
|
751
|
+
end
|
664
752
|
end
|
665
753
|
end
|
666
754
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lotus-model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Luca Guidi
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-05-15 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: lotus-utils
|
@@ -96,6 +96,7 @@ files:
|
|
96
96
|
- README.md
|
97
97
|
- lib/lotus-model.rb
|
98
98
|
- lib/lotus/entity.rb
|
99
|
+
- lib/lotus/entity/dirty_tracking.rb
|
99
100
|
- lib/lotus/model.rb
|
100
101
|
- lib/lotus/model/adapters/abstract.rb
|
101
102
|
- lib/lotus/model/adapters/file_system_adapter.rb
|