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.
@@ -36,7 +36,7 @@ module Lotus
36
36
  query(collection).all
37
37
  end
38
38
 
39
- # Returns an unique record from the given collection, with the given
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).
@@ -18,7 +18,8 @@ module Lotus
18
18
  # @api private
19
19
  # @since 0.1.0
20
20
  def initialize(dataset, collection)
21
- @dataset, @collection = dataset, collection
21
+ @dataset = dataset
22
+ @collection = collection
22
23
  end
23
24
 
24
25
  # Creates a record for the given entity.
@@ -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
- alias_method :or, :where
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! {|r| r.fetch(column) == value} })
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).uniq
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
- conditions.push(Proc.new{ sort_by{|r| r.fetch(column)} })
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
- conditions.push(Proc.new{ sort_by{|r| r.fetch(column)}.reverse })
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.last(number)) })
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
- result = conditions.map do |condition|
436
- result.instance_exec(&condition)
437
- end if conditions.any?
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
- @mutex.synchronize do
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
- @mutex.synchronize do
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
- @mutex.synchronize do
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
- @mutex.synchronize do
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
- @mutex.synchronize do
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 collection [Lotus::Model::Mapping::Collection] a mapped
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, collection)
27
+ def initialize(dataset, mapped_collection)
28
28
  super(dataset)
29
- @collection = collection
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, @collection)
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 an `limit` directive.
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, @collection)
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, @collection)
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, @collection)
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, @collection)
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, @collection)
133
+ Collection.new(super, @mapped_collection)
134
134
  end
135
135
 
136
- # Filters the current scope with an `select` directive.
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, @collection)
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)), @collection)
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 an `where` directive.
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, @collection)
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
- @collection.deserialize(self)
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
- @collection.serialize(entity)
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
- def where(condition)
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
- def or(condition)
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
- def exclude(condition)
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