lotus-model 0.1.2 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|