lotus-model 0.1.2 → 0.2.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/CHANGELOG.md +52 -168
- data/EXAMPLE.md +35 -40
- data/README.md +152 -12
- data/lib/lotus/entity.rb +107 -24
- data/lib/lotus/model.rb +169 -8
- data/lib/lotus/model/adapters/abstract.rb +3 -2
- data/lib/lotus/model/adapters/file_system_adapter.rb +272 -0
- data/lib/lotus/model/adapters/implementation.rb +1 -1
- data/lib/lotus/model/adapters/memory/command.rb +2 -1
- data/lib/lotus/model/adapters/memory/query.rb +49 -10
- data/lib/lotus/model/adapters/memory_adapter.rb +13 -5
- data/lib/lotus/model/adapters/null_adapter.rb +20 -0
- data/lib/lotus/model/adapters/sql/collection.rb +18 -18
- data/lib/lotus/model/adapters/sql/query.rb +23 -3
- data/lib/lotus/model/config/adapter.rb +108 -0
- data/lib/lotus/model/config/mapper.rb +45 -0
- data/lib/lotus/model/configuration.rb +187 -0
- data/lib/lotus/model/mapper.rb +26 -3
- data/lib/lotus/model/mapping.rb +24 -0
- data/lib/lotus/model/mapping/coercer.rb +3 -3
- data/lib/lotus/model/mapping/coercions.rb +30 -0
- data/lib/lotus/model/mapping/collection.rb +127 -9
- data/lib/lotus/model/version.rb +1 -1
- data/lib/lotus/repository.rb +19 -11
- data/lotus-model.gemspec +2 -1
- metadata +16 -5
@@ -36,7 +36,7 @@ module Lotus
|
|
36
36
|
query(collection).all
|
37
37
|
end
|
38
38
|
|
39
|
-
# Returns
|
39
|
+
# Returns a unique record from the given collection, with the given
|
40
40
|
# id.
|
41
41
|
#
|
42
42
|
# @param collection [Symbol] the target collection (it must be mapped).
|
@@ -100,12 +100,42 @@ module Lotus
|
|
100
100
|
# .where(framework: 'lotus')
|
101
101
|
def where(condition)
|
102
102
|
column, value = _expand_condition(condition)
|
103
|
-
conditions.push(Proc.new{ find_all{|r| r.fetch(column) == value} })
|
103
|
+
conditions.push([:where, Proc.new{ find_all{|r| r.fetch(column) == value} }])
|
104
104
|
self
|
105
105
|
end
|
106
106
|
|
107
107
|
alias_method :and, :where
|
108
|
-
|
108
|
+
|
109
|
+
# Adds a condition that behaves like SQL `OR`.
|
110
|
+
#
|
111
|
+
# It accepts a `Hash` with only one pair.
|
112
|
+
# The key must be the name of the column expressed as a `Symbol`.
|
113
|
+
# The value is the one used by the SQL query
|
114
|
+
#
|
115
|
+
# This condition will be ignored if not used with WHERE.
|
116
|
+
#
|
117
|
+
# @param condition [Hash]
|
118
|
+
#
|
119
|
+
# @return self
|
120
|
+
#
|
121
|
+
# @since 0.1.0
|
122
|
+
#
|
123
|
+
# @example Fixed value
|
124
|
+
#
|
125
|
+
# query.where(language: 'ruby').or(framework: 'lotus')
|
126
|
+
#
|
127
|
+
# @example Array
|
128
|
+
#
|
129
|
+
# query.where(id: 1).or(author_id: [15, 23])
|
130
|
+
#
|
131
|
+
# @example Range
|
132
|
+
#
|
133
|
+
# query.where(country: 'italy').or(year: 1900..1982)
|
134
|
+
def or(condition=nil, &blk)
|
135
|
+
column, value = _expand_condition(condition)
|
136
|
+
conditions.push([:or, Proc.new{ find_all{|r| r.fetch(column) == value} }])
|
137
|
+
self
|
138
|
+
end
|
109
139
|
|
110
140
|
# Logical negation of a #where condition.
|
111
141
|
#
|
@@ -137,7 +167,7 @@ module Lotus
|
|
137
167
|
# .exclude(company: 'enterprise')
|
138
168
|
def exclude(condition)
|
139
169
|
column, value = _expand_condition(condition)
|
140
|
-
conditions.push(Proc.new{ reject
|
170
|
+
conditions.push([:where, Proc.new{ reject {|r| r.fetch(column) == value} }])
|
141
171
|
self
|
142
172
|
end
|
143
173
|
|
@@ -161,7 +191,7 @@ module Lotus
|
|
161
191
|
#
|
162
192
|
# query.select(:name, :year)
|
163
193
|
def select(*columns)
|
164
|
-
columns = Lotus::Utils::Kernel.Array(columns)
|
194
|
+
columns = Lotus::Utils::Kernel.Array(columns)
|
165
195
|
modifiers.push(Proc.new{ flatten!; each {|r| r.delete_if {|k,_| !columns.include?(k)} } })
|
166
196
|
end
|
167
197
|
|
@@ -189,7 +219,7 @@ module Lotus
|
|
189
219
|
# query.order(:name).order(:year)
|
190
220
|
def order(*columns)
|
191
221
|
Lotus::Utils::Kernel.Array(columns).each do |column|
|
192
|
-
|
222
|
+
modifiers.push(Proc.new{ sort_by!{|r| r.fetch(column)} })
|
193
223
|
end
|
194
224
|
|
195
225
|
self
|
@@ -221,7 +251,7 @@ module Lotus
|
|
221
251
|
# query.desc(:name).desc(:year)
|
222
252
|
def desc(*columns)
|
223
253
|
Lotus::Utils::Kernel.Array(columns).each do |column|
|
224
|
-
|
254
|
+
modifiers.push(Proc.new{ sort_by!{|r| r.fetch(column)}.reverse! })
|
225
255
|
end
|
226
256
|
|
227
257
|
self
|
@@ -255,7 +285,7 @@ module Lotus
|
|
255
285
|
#
|
256
286
|
# query.offset(10)
|
257
287
|
def offset(number)
|
258
|
-
modifiers.unshift(Proc.new{ replace(flatten.
|
288
|
+
modifiers.unshift(Proc.new{ replace(flatten.drop(number)) })
|
259
289
|
self
|
260
290
|
end
|
261
291
|
|
@@ -432,9 +462,18 @@ module Lotus
|
|
432
462
|
def run
|
433
463
|
result = @dataset.all.dup
|
434
464
|
|
435
|
-
|
436
|
-
|
437
|
-
|
465
|
+
if conditions.any?
|
466
|
+
prev_result = nil
|
467
|
+
conditions.each do |(type, condition)|
|
468
|
+
case type
|
469
|
+
when :where
|
470
|
+
prev_result = result
|
471
|
+
result = prev_result.instance_exec(&condition)
|
472
|
+
when :or
|
473
|
+
result |= prev_result.instance_exec(&condition)
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
438
477
|
|
439
478
|
modifiers.map do |modifier|
|
440
479
|
result.instance_exec(&modifier)
|
@@ -49,7 +49,7 @@ module Lotus
|
|
49
49
|
# @api private
|
50
50
|
# @since 0.1.0
|
51
51
|
def create(collection, entity)
|
52
|
-
|
52
|
+
synchronize do
|
53
53
|
entity.id = command(collection).create(entity)
|
54
54
|
entity
|
55
55
|
end
|
@@ -65,7 +65,7 @@ module Lotus
|
|
65
65
|
# @api private
|
66
66
|
# @since 0.1.0
|
67
67
|
def update(collection, entity)
|
68
|
-
|
68
|
+
synchronize do
|
69
69
|
command(collection).update(entity)
|
70
70
|
end
|
71
71
|
end
|
@@ -78,7 +78,7 @@ module Lotus
|
|
78
78
|
# @api private
|
79
79
|
# @since 0.1.0
|
80
80
|
def delete(collection, entity)
|
81
|
-
|
81
|
+
synchronize do
|
82
82
|
command(collection).delete(entity)
|
83
83
|
end
|
84
84
|
end
|
@@ -91,7 +91,7 @@ module Lotus
|
|
91
91
|
# @api private
|
92
92
|
# @since 0.1.0
|
93
93
|
def clear(collection)
|
94
|
-
|
94
|
+
synchronize do
|
95
95
|
command(collection).clear
|
96
96
|
end
|
97
97
|
end
|
@@ -123,7 +123,7 @@ module Lotus
|
|
123
123
|
# @api private
|
124
124
|
# @since 0.1.0
|
125
125
|
def query(collection, context = nil, &blk)
|
126
|
-
|
126
|
+
synchronize do
|
127
127
|
Memory::Query.new(_collection(collection), _mapped_collection(collection), &blk)
|
128
128
|
end
|
129
129
|
end
|
@@ -143,6 +143,14 @@ module Lotus
|
|
143
143
|
def _collection(name)
|
144
144
|
@collections[name] ||= Memory::Collection.new(name, _identity(name))
|
145
145
|
end
|
146
|
+
|
147
|
+
# Executes the given block within a critical section.
|
148
|
+
#
|
149
|
+
# @api private
|
150
|
+
# @since 0.2.0
|
151
|
+
def synchronize
|
152
|
+
@mutex.synchronize { yield }
|
153
|
+
end
|
146
154
|
end
|
147
155
|
end
|
148
156
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Lotus
|
2
|
+
module Model
|
3
|
+
module Adapters
|
4
|
+
# @since 0.2.0
|
5
|
+
class NoAdapterError < ::StandardError
|
6
|
+
def initialize(method_name)
|
7
|
+
super("Cannot invoke `#{ method_name }' without selecting an adapter. Please check your framework configuration.")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# @since 0.2.0
|
12
|
+
# @api private
|
13
|
+
class NullAdapter
|
14
|
+
def method_missing(m, *args)
|
15
|
+
raise NoAdapterError.new(m)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -17,16 +17,16 @@ module Lotus
|
|
17
17
|
#
|
18
18
|
# @param dataset [Sequel::Dataset] the dataset that maps a table or a
|
19
19
|
# subset of it.
|
20
|
-
# @param
|
21
|
-
# collection
|
20
|
+
# @param mapped_collection [Lotus::Model::Mapping::Collection] a
|
21
|
+
# mapped collection
|
22
22
|
#
|
23
23
|
# @return [Lotus::Model::Adapters::Sql::Collection]
|
24
24
|
#
|
25
25
|
# @api private
|
26
26
|
# @since 0.1.0
|
27
|
-
def initialize(dataset,
|
27
|
+
def initialize(dataset, mapped_collection)
|
28
28
|
super(dataset)
|
29
|
-
@
|
29
|
+
@mapped_collection = mapped_collection
|
30
30
|
end
|
31
31
|
|
32
32
|
# Filters the current scope with an `exclude` directive.
|
@@ -41,7 +41,7 @@ module Lotus
|
|
41
41
|
# @api private
|
42
42
|
# @since 0.1.0
|
43
43
|
def exclude(*args)
|
44
|
-
Collection.new(super, @
|
44
|
+
Collection.new(super, @mapped_collection)
|
45
45
|
end
|
46
46
|
|
47
47
|
# Creates a record for the given entity and assigns an id.
|
@@ -58,7 +58,7 @@ module Lotus
|
|
58
58
|
super _serialize(entity)
|
59
59
|
end
|
60
60
|
|
61
|
-
# Filters the current scope with
|
61
|
+
# Filters the current scope with a `limit` directive.
|
62
62
|
#
|
63
63
|
# @param args [Array] the array of arguments
|
64
64
|
#
|
@@ -70,7 +70,7 @@ module Lotus
|
|
70
70
|
# @api private
|
71
71
|
# @since 0.1.0
|
72
72
|
def limit(*args)
|
73
|
-
Collection.new(super, @
|
73
|
+
Collection.new(super, @mapped_collection)
|
74
74
|
end
|
75
75
|
|
76
76
|
# Filters the current scope with an `offset` directive.
|
@@ -85,7 +85,7 @@ module Lotus
|
|
85
85
|
# @api private
|
86
86
|
# @since 0.1.0
|
87
87
|
def offset(*args)
|
88
|
-
Collection.new(super, @
|
88
|
+
Collection.new(super, @mapped_collection)
|
89
89
|
end
|
90
90
|
|
91
91
|
# Filters the current scope with an `or` directive.
|
@@ -100,7 +100,7 @@ module Lotus
|
|
100
100
|
# @api private
|
101
101
|
# @since 0.1.0
|
102
102
|
def or(*args)
|
103
|
-
Collection.new(super, @
|
103
|
+
Collection.new(super, @mapped_collection)
|
104
104
|
end
|
105
105
|
|
106
106
|
# Filters the current scope with an `order` directive.
|
@@ -115,7 +115,7 @@ module Lotus
|
|
115
115
|
# @api private
|
116
116
|
# @since 0.1.0
|
117
117
|
def order(*args)
|
118
|
-
Collection.new(super, @
|
118
|
+
Collection.new(super, @mapped_collection)
|
119
119
|
end
|
120
120
|
|
121
121
|
# Filters the current scope with an `order` directive.
|
@@ -130,10 +130,10 @@ module Lotus
|
|
130
130
|
# @api private
|
131
131
|
# @since 0.1.0
|
132
132
|
def order_more(*args)
|
133
|
-
Collection.new(super, @
|
133
|
+
Collection.new(super, @mapped_collection)
|
134
134
|
end
|
135
135
|
|
136
|
-
# Filters the current scope with
|
136
|
+
# Filters the current scope with a `select` directive.
|
137
137
|
#
|
138
138
|
# @param args [Array] the array of arguments
|
139
139
|
#
|
@@ -146,15 +146,15 @@ module Lotus
|
|
146
146
|
# @since 0.1.0
|
147
147
|
if RUBY_VERSION >= '2.1'
|
148
148
|
def select(*args)
|
149
|
-
Collection.new(super, @
|
149
|
+
Collection.new(super, @mapped_collection)
|
150
150
|
end
|
151
151
|
else
|
152
152
|
def select(*args)
|
153
|
-
Collection.new(__getobj__.select(*Lotus::Utils::Kernel.Array(args)), @
|
153
|
+
Collection.new(__getobj__.select(*Lotus::Utils::Kernel.Array(args)), @mapped_collection)
|
154
154
|
end
|
155
155
|
end
|
156
156
|
|
157
|
-
# Filters the current scope with
|
157
|
+
# Filters the current scope with a `where` directive.
|
158
158
|
#
|
159
159
|
# @param args [Array] the array of arguments
|
160
160
|
#
|
@@ -166,7 +166,7 @@ module Lotus
|
|
166
166
|
# @api private
|
167
167
|
# @since 0.1.0
|
168
168
|
def where(*args)
|
169
|
-
Collection.new(super, @
|
169
|
+
Collection.new(super, @mapped_collection)
|
170
170
|
end
|
171
171
|
|
172
172
|
# Updates the record corresponding to the given entity.
|
@@ -189,7 +189,7 @@ module Lotus
|
|
189
189
|
# @api private
|
190
190
|
# @since 0.1.0
|
191
191
|
def to_a
|
192
|
-
@
|
192
|
+
@mapped_collection.deserialize(self)
|
193
193
|
end
|
194
194
|
|
195
195
|
private
|
@@ -200,7 +200,7 @@ module Lotus
|
|
200
200
|
# @api private
|
201
201
|
# @since 0.1.0
|
202
202
|
def _serialize(entity)
|
203
|
-
@
|
203
|
+
@mapped_collection.serialize(entity)
|
204
204
|
end
|
205
205
|
end
|
206
206
|
end
|
@@ -111,7 +111,14 @@ module Lotus
|
|
111
111
|
# .where(framework: 'lotus')
|
112
112
|
#
|
113
113
|
# # => SELECT * FROM `projects` WHERE (`language` = 'ruby') AND (`framework` = 'lotus')
|
114
|
-
|
114
|
+
#
|
115
|
+
# @example Expressions
|
116
|
+
#
|
117
|
+
# query.where{ age > 10 }
|
118
|
+
#
|
119
|
+
# # => SELECT * FROM `users` WHERE (`age` > 31)
|
120
|
+
def where(condition=nil, &blk)
|
121
|
+
condition = (condition or blk or raise ArgumentError.new('You need to specify a condition.'))
|
115
122
|
conditions.push([:where, condition])
|
116
123
|
self
|
117
124
|
end
|
@@ -149,7 +156,14 @@ module Lotus
|
|
149
156
|
# query.where(country: 'italy').or(year: 1900..1982)
|
150
157
|
#
|
151
158
|
# # => SELECT * FROM `people` WHERE ((`country` = 'italy') OR ((`year` >= 1900) AND (`year` <= 1982)))
|
152
|
-
|
159
|
+
#
|
160
|
+
# @example Expressions
|
161
|
+
#
|
162
|
+
# query.where(name: 'John').or{ age > 31 }
|
163
|
+
#
|
164
|
+
# # => SELECT * FROM `users` WHERE ((`name` = 'John') OR (`age` < 32))
|
165
|
+
def or(condition=nil, &blk)
|
166
|
+
condition = (condition or blk or raise ArgumentError.new('You need to specify a condition.'))
|
153
167
|
conditions.push([:or, condition])
|
154
168
|
self
|
155
169
|
end
|
@@ -190,7 +204,13 @@ module Lotus
|
|
190
204
|
# .exclude(company: 'enterprise')
|
191
205
|
#
|
192
206
|
# # => SELECT * FROM `projects` WHERE (`language` != 'java') AND (`company` != 'enterprise')
|
193
|
-
|
207
|
+
# @example Expressions
|
208
|
+
#
|
209
|
+
# query.exclude{ age > 31 }
|
210
|
+
#
|
211
|
+
# # => SELECT * FROM `users` WHERE (`age` <= 31)
|
212
|
+
def exclude(condition=nil, &blk)
|
213
|
+
condition = (condition or blk or raise ArgumentError.new('You need to specify a condition.'))
|
194
214
|
conditions.push([:exclude, condition])
|
195
215
|
self
|
196
216
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require 'lotus/utils/class'
|
2
|
+
|
3
|
+
module Lotus
|
4
|
+
module Model
|
5
|
+
module Config
|
6
|
+
# Raised when an adapter class does not exist
|
7
|
+
#
|
8
|
+
# @since 0.2.0
|
9
|
+
class AdapterNotFound < ::StandardError
|
10
|
+
def initialize(adapter_name)
|
11
|
+
super "Cannot find Lotus::Model adapter #{adapter_name}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Configuration for the adapter
|
16
|
+
#
|
17
|
+
# Lotus::Model has its own global configuration that can be manipulated
|
18
|
+
# via `Lotus::Model.configure`.
|
19
|
+
#
|
20
|
+
# New adapter configuration can be registered via `Lotus::Model.adapter`.
|
21
|
+
#
|
22
|
+
# @see Lotus::Model.adapter
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# require 'lotus/model'
|
26
|
+
#
|
27
|
+
# Lotus::Model.configure do
|
28
|
+
# adapter type: :sql, uri: 'postgres://localhost/database'
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# Lotus::Model.adapter_config
|
32
|
+
# # => Lotus::Model::Config::Adapter(type: :sql, uri: 'postgres://localhost/database')
|
33
|
+
#
|
34
|
+
# By convention, Lotus inflects type to find the adapter class
|
35
|
+
# For example, if type is :sql, derived class will be `Lotus::Model::Adapters::SqlAdapter`
|
36
|
+
#
|
37
|
+
# @since 0.2.0
|
38
|
+
class Adapter
|
39
|
+
# @return [Symbol] the adapter name
|
40
|
+
#
|
41
|
+
# @since 0.2.0
|
42
|
+
attr_reader :type
|
43
|
+
|
44
|
+
# @return [String] the adapter URI
|
45
|
+
#
|
46
|
+
# @since 0.2.0
|
47
|
+
attr_reader :uri
|
48
|
+
|
49
|
+
# @return [String] the adapter class name
|
50
|
+
#
|
51
|
+
# @since 0.2.0
|
52
|
+
attr_reader :class_name
|
53
|
+
|
54
|
+
# Initialize an adapter configuration instance
|
55
|
+
#
|
56
|
+
# @param options [Hash] configuration options
|
57
|
+
# @option options [Symbol] :type adapter type name
|
58
|
+
# @option options [String] :uri adapter URI
|
59
|
+
#
|
60
|
+
# @return [Lotus::Model::Config::Adapter] a new apdapter configuration's
|
61
|
+
# instance
|
62
|
+
#
|
63
|
+
# @since 0.2.0
|
64
|
+
def initialize(**options)
|
65
|
+
@type = options[:type]
|
66
|
+
@uri = options[:uri]
|
67
|
+
@class_name ||= Lotus::Utils::String.new("#{@type}_adapter").classify
|
68
|
+
end
|
69
|
+
|
70
|
+
# Initialize the adapter
|
71
|
+
#
|
72
|
+
# @param mapper [Lotus::Model::Mapper] the mapper instance
|
73
|
+
#
|
74
|
+
# @return [Lotus::Model::Adapters::SqlAdapter, Lotus::Model::Adapters::MemoryAdapter] an adapter instance
|
75
|
+
#
|
76
|
+
# @see Lotus::Model::Adapters
|
77
|
+
#
|
78
|
+
# @since 0.2.0
|
79
|
+
def build(mapper)
|
80
|
+
load_adapter
|
81
|
+
instantiate_adapter(mapper)
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def load_adapter
|
87
|
+
begin
|
88
|
+
require "lotus/model/adapters/#{type}_adapter"
|
89
|
+
rescue LoadError => e
|
90
|
+
raise LoadError.new("Cannot find Lotus::Model adapter '#{type}' (#{e.message})")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def instantiate_adapter(mapper)
|
95
|
+
begin
|
96
|
+
klass = Lotus::Utils::Class.load!(class_name, Lotus::Model::Adapters)
|
97
|
+
klass.new(mapper, uri)
|
98
|
+
rescue NameError
|
99
|
+
raise AdapterNotFound.new(class_name)
|
100
|
+
rescue => e
|
101
|
+
raise "Cannot instantiate adapter of #{klass} (#{e.message})"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|