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.
@@ -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