lotus-model 0.4.1 → 0.5.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 +15 -1
- data/README.md +3 -1
- data/lib/lotus/model.rb +10 -2
- data/lib/lotus/model/adapters/abstract.rb +63 -2
- data/lib/lotus/model/adapters/file_system_adapter.rb +11 -0
- data/lib/lotus/model/adapters/memory/query.rb +13 -0
- data/lib/lotus/model/adapters/memory_adapter.rb +9 -0
- data/lib/lotus/model/adapters/sql/collection.rb +63 -11
- data/lib/lotus/model/adapters/sql/query.rb +93 -1
- data/lib/lotus/model/adapters/sql_adapter.rb +35 -5
- data/lib/lotus/model/coercer.rb +74 -0
- data/lib/lotus/model/configuration.rb +1 -1
- data/lib/lotus/model/mapper.rb +2 -2
- data/lib/lotus/model/mapping.rb +2 -2
- data/lib/lotus/model/mapping/attribute.rb +85 -0
- data/lib/lotus/model/mapping/coercers.rb +314 -0
- data/lib/lotus/model/mapping/collection.rb +61 -7
- data/lib/lotus/model/mapping/{coercer.rb → collection_coercer.rb} +7 -9
- data/lib/lotus/model/migrator.rb +2 -1
- data/lib/lotus/model/migrator/adapter.rb +28 -27
- data/lib/lotus/model/migrator/connection.rb +133 -0
- data/lib/lotus/model/migrator/mysql_adapter.rb +2 -2
- data/lib/lotus/model/migrator/postgres_adapter.rb +20 -17
- data/lib/lotus/model/migrator/sqlite_adapter.rb +2 -2
- data/lib/lotus/model/version.rb +1 -1
- data/lib/lotus/repository.rb +134 -18
- data/lotus-model.gemspec +1 -1
- metadata +9 -6
- data/lib/lotus/model/mapping/coercions.rb +0 -192
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de9019e132ab36bb7b30223c52e247df84e3cf3e
|
4
|
+
data.tar.gz: d56eeb3a71e31193b1509527f24ac8f0822df5f0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8449498885c9eda612f978aed61dfbe362e213b8c392ce86cb37d9586f5d0865b010f9900246b3621d63627e0900f43a52a0dfe954e9b507306987ab1ef9755b
|
7
|
+
data.tar.gz: 478a4d4bf0323970ee7adf7733123418745aa135db6dc79142e9f203962793b039e2e017fca8a3d9f1bee01dd3f7511068220c465a44e29399a57c950eea9e61
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,22 @@
|
|
1
1
|
# Lotus::Model
|
2
2
|
A persistence layer for Lotus
|
3
3
|
|
4
|
-
## v0.
|
4
|
+
## v0.5.0 - 2015-09-30
|
5
5
|
### Added
|
6
|
+
- [Brenno Costa] Official support for JRuby 9k+
|
7
|
+
- [Luca Guidi] Command/Query separation via `Repository.execute` and `Repository.fetch`
|
8
|
+
- [Luca Guidi] Custom attribute coercers for data mapper
|
9
|
+
- [Alfonso Uceda] Added `#join` and `#left_join` and `#group` to SQL adapter
|
10
|
+
|
11
|
+
### Changed
|
12
|
+
- [Luca Guidi] `Repository.execute` no longer returns a result from the database.
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
- [Manuel Corrales] Use `dropdb` to drop PostgreSQL database.
|
16
|
+
- [Luca Guidi & Bohdan V.] Ignore dotfiles while running migrations.
|
17
|
+
|
18
|
+
## v0.4.1 - 2015-07-10
|
19
|
+
### Fixed
|
6
20
|
- [Nick Coyne] Fixed database creation for PostgreSQL (now it uses `createdb`).
|
7
21
|
|
8
22
|
## v0.4.0 - 2015-06-23
|
data/README.md
CHANGED
@@ -35,7 +35,7 @@ Like all the other Lotus components, it can be used as a standalone framework or
|
|
35
35
|
|
36
36
|
## Rubies
|
37
37
|
|
38
|
-
__Lotus::Model__ supports Ruby (MRI) 2+
|
38
|
+
__Lotus::Model__ supports Ruby (MRI) 2+ and JRuby 9000+
|
39
39
|
|
40
40
|
## Installation
|
41
41
|
|
@@ -463,6 +463,8 @@ Here is common interface for existing class:
|
|
463
463
|
* `.range` - Returns a range of values between the MAX and the MIN for the given column
|
464
464
|
* `.exist?` - Checks if at least one record exists for the current conditions
|
465
465
|
* `.count` - Returns a count of the records for the current conditions
|
466
|
+
* `.join` - Adds an inner join with a table (only SQL)
|
467
|
+
* `.left_join` - Adds a left join with a table (only SQL)
|
466
468
|
|
467
469
|
If you need more information regarding those methods, you can use comments from [memory](https://github.com/lotus/model/blob/master/lib/lotus/model/adapters/memory/query.rb#L29) or [sql](https://github.com/lotus/model/blob/master/lib/lotus/model/adapters/sql/query.rb#L28) adapters interface.
|
468
470
|
|
data/lib/lotus/model.rb
CHANGED
@@ -28,8 +28,16 @@ module Lotus
|
|
28
28
|
class InvalidMappingError < ::StandardError
|
29
29
|
end
|
30
30
|
|
31
|
-
# Error for invalid
|
32
|
-
#
|
31
|
+
# Error for invalid raw command syntax
|
32
|
+
#
|
33
|
+
# @since 0.5.0
|
34
|
+
class InvalidCommandError < ::StandardError
|
35
|
+
def initialize(message = "Invalid command")
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Error for invalid raw query syntax
|
33
41
|
#
|
34
42
|
# @since 0.3.1
|
35
43
|
class InvalidQueryError < ::StandardError
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'lotus/utils/basic_object'
|
2
|
+
|
1
3
|
module Lotus
|
2
4
|
module Model
|
3
5
|
module Adapters
|
@@ -23,6 +25,45 @@ module Lotus
|
|
23
25
|
class NotSupportedError < ::StandardError
|
24
26
|
end
|
25
27
|
|
28
|
+
# It's raised when an operation is requested to an adapter after it was
|
29
|
+
# disconnected.
|
30
|
+
#
|
31
|
+
# @since 0.5.0
|
32
|
+
class DisconnectedAdapterError < ::StandardError
|
33
|
+
def initialize
|
34
|
+
super "You have tried to perform an operation on a disconnected adapter"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Represents a disconnected resource.
|
39
|
+
#
|
40
|
+
# When we use <tt>#disconnect</tt> for <tt>MemoryAdapter</tt> and
|
41
|
+
# </tt>FileSystemAdapter</tt>, we want to free underlying resources such
|
42
|
+
# as a mutex or a file descriptor.
|
43
|
+
#
|
44
|
+
# These adapters use to use anonymous descriptors that are destroyed by
|
45
|
+
# Ruby VM after each operation. Sometimes we need to clean the state and
|
46
|
+
# start fresh (eg. during a test suite or a deploy).
|
47
|
+
#
|
48
|
+
# Instead of assign <tt>nil</tt> to these instance variables, we assign this
|
49
|
+
# special type: <tt>DisconnectedResource</tt>.
|
50
|
+
#
|
51
|
+
# In case an operation is still performed after the adapter was disconnected,
|
52
|
+
# instead of see a generic <tt>NoMethodError</tt> for <tt>nil</tt>, a developer
|
53
|
+
# will face a specific message relative to the state of the adapter.
|
54
|
+
#
|
55
|
+
# @api private
|
56
|
+
# @since 0.5.0
|
57
|
+
#
|
58
|
+
# @see Lotus::Model::Adapters::Abstract#disconnect
|
59
|
+
# @see Lotus::Model::Adapters::MemoryAdapter#disconnect
|
60
|
+
# @see Lotus::Model::Adapters::FileSystemAdapter#disconnect
|
61
|
+
class DisconnectedResource < Utils::BasicObject
|
62
|
+
def method_missing(method_name, *)
|
63
|
+
::Kernel.raise DisconnectedAdapterError.new
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
26
67
|
# Abstract adapter.
|
27
68
|
#
|
28
69
|
# An adapter is a concrete implementation that allows a repository to
|
@@ -202,15 +243,35 @@ module Lotus
|
|
202
243
|
raise NotSupportedError
|
203
244
|
end
|
204
245
|
|
205
|
-
# Executes a raw
|
246
|
+
# Executes a raw command
|
206
247
|
#
|
207
248
|
# @param raw [String] the raw statement to execute on the connection
|
208
|
-
#
|
249
|
+
#
|
250
|
+
# @return [NilClass]
|
209
251
|
#
|
210
252
|
# @since 0.3.1
|
211
253
|
def execute(raw)
|
212
254
|
raise NotImplementedError
|
213
255
|
end
|
256
|
+
|
257
|
+
# Fetches raw records from
|
258
|
+
#
|
259
|
+
# @param raw [String] the raw query
|
260
|
+
# @param blk [Proc] an optional block that is yielded for each record
|
261
|
+
#
|
262
|
+
# @return [Enumerable<Hash>, Array<Hash>]
|
263
|
+
#
|
264
|
+
# @since 0.5.0
|
265
|
+
def fetch(raw, &blk)
|
266
|
+
raise NotImplementedError
|
267
|
+
end
|
268
|
+
|
269
|
+
# Disconnects the connection by freeing low level resources
|
270
|
+
#
|
271
|
+
# @since 0.5.0
|
272
|
+
def disconnect
|
273
|
+
raise NotImplementedError
|
274
|
+
end
|
214
275
|
end
|
215
276
|
end
|
216
277
|
end
|
@@ -225,6 +225,17 @@ module Lotus
|
|
225
225
|
end
|
226
226
|
end
|
227
227
|
|
228
|
+
# @api private
|
229
|
+
# @since 0.5.0
|
230
|
+
#
|
231
|
+
# @see Lotus::Model::Adapters::Abstract#disconnect
|
232
|
+
def disconnect
|
233
|
+
super
|
234
|
+
|
235
|
+
@_mutex = DisconnectedResource.new
|
236
|
+
@root = DisconnectedResource.new
|
237
|
+
end
|
238
|
+
|
228
239
|
private
|
229
240
|
# @api private
|
230
241
|
# @since 0.2.0
|
@@ -541,6 +541,19 @@ module Lotus
|
|
541
541
|
raise NotImplementedError
|
542
542
|
end
|
543
543
|
|
544
|
+
# This method is defined in order to make the interface of
|
545
|
+
# `Memory::Query` identical to `Sql::Query`, but this feature is NOT
|
546
|
+
# implemented
|
547
|
+
#
|
548
|
+
# @raise [NotImplementedError]
|
549
|
+
#
|
550
|
+
# @since 0.5.0
|
551
|
+
#
|
552
|
+
# @see Lotus::Model::Adapters::Sql::Query#group!
|
553
|
+
def group
|
554
|
+
raise NotImplementedError
|
555
|
+
end
|
556
|
+
|
544
557
|
protected
|
545
558
|
def method_missing(m, *args, &blk)
|
546
559
|
if @context.respond_to?(m)
|
@@ -140,6 +140,15 @@ module Lotus
|
|
140
140
|
yield
|
141
141
|
end
|
142
142
|
|
143
|
+
# @api private
|
144
|
+
# @since 0.5.0
|
145
|
+
#
|
146
|
+
# @see Lotus::Model::Adapters::Abstract#disconnect
|
147
|
+
def disconnect
|
148
|
+
@collections = DisconnectedResource.new
|
149
|
+
@mutex = DisconnectedResource.new
|
150
|
+
end
|
151
|
+
|
143
152
|
private
|
144
153
|
|
145
154
|
# Returns a collection from the given name.
|
@@ -56,7 +56,7 @@ module Lotus
|
|
56
56
|
# @since 0.1.0
|
57
57
|
def insert(entity)
|
58
58
|
serialized_entity = _serialize(entity)
|
59
|
-
serialized_entity[
|
59
|
+
serialized_entity[identity] = super(serialized_entity)
|
60
60
|
|
61
61
|
_deserialize(serialized_entity)
|
62
62
|
end
|
@@ -157,6 +157,22 @@ module Lotus
|
|
157
157
|
end
|
158
158
|
end
|
159
159
|
|
160
|
+
|
161
|
+
# Filters the current scope with a `group` directive.
|
162
|
+
#
|
163
|
+
# @param args [Array] the array of arguments
|
164
|
+
#
|
165
|
+
# @see Lotus::Model::Adapters::Sql::Query#group
|
166
|
+
#
|
167
|
+
# @return [Lotus::Model::Adapters::Sql::Collection] the filtered
|
168
|
+
# collection
|
169
|
+
#
|
170
|
+
# @api private
|
171
|
+
# @since 0.5.0
|
172
|
+
def group(*args)
|
173
|
+
Collection.new(super, @mapped_collection)
|
174
|
+
end
|
175
|
+
|
160
176
|
# Filters the current scope with a `where` directive.
|
161
177
|
#
|
162
178
|
# @param args [Array] the array of arguments
|
@@ -198,6 +214,52 @@ module Lotus
|
|
198
214
|
@mapped_collection.deserialize(self)
|
199
215
|
end
|
200
216
|
|
217
|
+
# Select all attributes for current scope
|
218
|
+
#
|
219
|
+
# @return [Lotus::Model::Adapters::Sql::Collection] the filtered
|
220
|
+
# collection
|
221
|
+
#
|
222
|
+
# @api private
|
223
|
+
# @since 0.5.0
|
224
|
+
#
|
225
|
+
# @see http://www.rubydoc.info/github/jeremyevans/sequel/Sequel%2FDataset%3Aselect_all
|
226
|
+
def select_all
|
227
|
+
Collection.new(super(table_name), @mapped_collection)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Use join table for current scope
|
231
|
+
#
|
232
|
+
# @return [Lotus::Model::Adapters::Sql::Collection] the filtered
|
233
|
+
# collection
|
234
|
+
#
|
235
|
+
# @api private
|
236
|
+
# @since 0.5.0
|
237
|
+
#
|
238
|
+
# @see http://www.rubydoc.info/github/jeremyevans/sequel/Sequel%2FDataset%3Ajoin_table
|
239
|
+
def join_table(*args)
|
240
|
+
Collection.new(super, @mapped_collection)
|
241
|
+
end
|
242
|
+
|
243
|
+
# Return table name mapped collection
|
244
|
+
#
|
245
|
+
# @return [String] table name
|
246
|
+
#
|
247
|
+
# @api private
|
248
|
+
# @since 0.5.0
|
249
|
+
def table_name
|
250
|
+
@mapped_collection.name
|
251
|
+
end
|
252
|
+
|
253
|
+
# Name of the identity column in database
|
254
|
+
#
|
255
|
+
# @return [Symbol] the identity name
|
256
|
+
#
|
257
|
+
# @api private
|
258
|
+
# @since 0.5.0
|
259
|
+
def identity
|
260
|
+
@mapped_collection.identity
|
261
|
+
end
|
262
|
+
|
201
263
|
private
|
202
264
|
# Serialize the given entity before to persist in the database.
|
203
265
|
#
|
@@ -218,16 +280,6 @@ module Lotus
|
|
218
280
|
def _deserialize(entity)
|
219
281
|
@mapped_collection.deserialize([entity]).first
|
220
282
|
end
|
221
|
-
|
222
|
-
# Name of the identity column in database
|
223
|
-
#
|
224
|
-
# @return [Symbol] the identity name
|
225
|
-
#
|
226
|
-
# @api private
|
227
|
-
# @since 0.2.2
|
228
|
-
def _identity
|
229
|
-
@mapped_collection.identity
|
230
|
-
end
|
231
283
|
end
|
232
284
|
end
|
233
285
|
end
|
@@ -75,7 +75,7 @@ module Lotus
|
|
75
75
|
#
|
76
76
|
# @since 0.1.0
|
77
77
|
def all
|
78
|
-
|
78
|
+
run.to_a
|
79
79
|
rescue Sequel::DatabaseError => e
|
80
80
|
raise Lotus::Model::InvalidQueryError.new(e.message)
|
81
81
|
end
|
@@ -401,6 +401,30 @@ module Lotus
|
|
401
401
|
# query.desc(:name).desc(:year)
|
402
402
|
alias_method :desc, :reverse_order
|
403
403
|
|
404
|
+
# Group by the specified columns.
|
405
|
+
#
|
406
|
+
# @param columns [Array<Symbol>]
|
407
|
+
#
|
408
|
+
# @return self
|
409
|
+
#
|
410
|
+
# @since 0.5.0
|
411
|
+
#
|
412
|
+
# @example Single column
|
413
|
+
#
|
414
|
+
# query.group(:name)
|
415
|
+
#
|
416
|
+
# # => SELECT * FROM `people` GROUP BY `name`
|
417
|
+
#
|
418
|
+
# @example Multiple columns
|
419
|
+
#
|
420
|
+
# query.group(:name, :year)
|
421
|
+
#
|
422
|
+
# # => SELECT * FROM `people` GROUP BY `name`, `year`
|
423
|
+
def group(*columns)
|
424
|
+
conditions.push([:group, *columns])
|
425
|
+
self
|
426
|
+
end
|
427
|
+
|
404
428
|
# Returns the sum of the values for the given column.
|
405
429
|
#
|
406
430
|
# @param column [Symbol] the column name
|
@@ -592,6 +616,50 @@ module Lotus
|
|
592
616
|
|
593
617
|
alias_method :run, :scoped
|
594
618
|
|
619
|
+
# Specify an `INNER JOIN` clause.
|
620
|
+
#
|
621
|
+
# @param collection [String]
|
622
|
+
# @param options [Hash]
|
623
|
+
# @option key [Symbol] the key
|
624
|
+
# @option foreign_key [Symbol] the foreign key
|
625
|
+
#
|
626
|
+
# @return self
|
627
|
+
#
|
628
|
+
# @since 0.5.0
|
629
|
+
#
|
630
|
+
# @example
|
631
|
+
#
|
632
|
+
# query.join(:users)
|
633
|
+
#
|
634
|
+
# # => SELECT * FROM `posts` INNER JOIN `users` ON `posts`.`user_id` = `users`.`id`
|
635
|
+
def join(collection, options = {})
|
636
|
+
_join(collection, options.merge(join: :inner))
|
637
|
+
end
|
638
|
+
|
639
|
+
alias_method :inner_join, :join
|
640
|
+
|
641
|
+
# Specify a `LEFT JOIN` clause.
|
642
|
+
#
|
643
|
+
# @param collection [String]
|
644
|
+
# @param options [Hash]
|
645
|
+
# @option key [Symbol] the key
|
646
|
+
# @option foreign_key [Symbol] the foreign key
|
647
|
+
#
|
648
|
+
# @return self
|
649
|
+
#
|
650
|
+
# @since 0.5.0
|
651
|
+
#
|
652
|
+
# @example
|
653
|
+
#
|
654
|
+
# query.left_join(:users)
|
655
|
+
#
|
656
|
+
# # => SELECT * FROM `posts` LEFT JOIN `users` ON `posts`.`user_id` = `users`.`id`
|
657
|
+
def left_join(collection, options = {})
|
658
|
+
_join(collection, options.merge(join: :left))
|
659
|
+
end
|
660
|
+
|
661
|
+
alias_method :left_outer_join, :left_join
|
662
|
+
|
595
663
|
protected
|
596
664
|
# Handles missing methods for query combinations
|
597
665
|
#
|
@@ -609,6 +677,30 @@ module Lotus
|
|
609
677
|
|
610
678
|
private
|
611
679
|
|
680
|
+
# Specify a JOIN clause. (inner or left)
|
681
|
+
#
|
682
|
+
# @param collection [String]
|
683
|
+
# @param options [Hash]
|
684
|
+
# @option key [Symbol] the key
|
685
|
+
# @option foreign_key [Symbol] the foreign key
|
686
|
+
# @option join [Symbol] the join type
|
687
|
+
#
|
688
|
+
# @return self
|
689
|
+
#
|
690
|
+
# @api private
|
691
|
+
# @since 0.5.0
|
692
|
+
def _join(collection, options = {})
|
693
|
+
collection_name = Utils::String.new(collection).singularize
|
694
|
+
|
695
|
+
foreign_key = options.fetch(:foreign_key) { "#{ @collection.table_name }__#{ collection_name }_id".to_sym }
|
696
|
+
key = options.fetch(:key) { @collection.identity.to_sym }
|
697
|
+
|
698
|
+
conditions.push([:select_all])
|
699
|
+
conditions.push([:join_table, options.fetch(:join, :inner), collection, key => foreign_key])
|
700
|
+
|
701
|
+
self
|
702
|
+
end
|
703
|
+
|
612
704
|
# Returns a new query that is the result of the merge of the current
|
613
705
|
# conditions with the ones of the given query.
|
614
706
|
#
|
@@ -227,23 +227,53 @@ module Lotus
|
|
227
227
|
Sql::Console.new(@uri).connection_string
|
228
228
|
end
|
229
229
|
|
230
|
-
# Executes raw
|
230
|
+
# Executes a raw SQL command
|
231
231
|
#
|
232
|
-
# @param raw [String] the raw
|
232
|
+
# @param raw [String] the raw SQL statement to execute on the connection
|
233
233
|
#
|
234
|
-
# @
|
234
|
+
# @raise [Lotus::Model::InvalidCommandError] if the raw SQL statement is invalid
|
235
235
|
#
|
236
|
-
# @
|
236
|
+
# @return [NilClass]
|
237
237
|
#
|
238
238
|
# @since 0.3.1
|
239
239
|
def execute(raw)
|
240
240
|
begin
|
241
241
|
@connection.execute(raw)
|
242
|
+
nil
|
242
243
|
rescue Sequel::DatabaseError => e
|
243
|
-
raise Lotus::Model::
|
244
|
+
raise Lotus::Model::InvalidCommandError.new(e.message)
|
244
245
|
end
|
245
246
|
end
|
246
247
|
|
248
|
+
# Fetches raw result sets for the given SQL query
|
249
|
+
#
|
250
|
+
# @param raw [String] the raw SQL query
|
251
|
+
# @param blk [Proc] optional block that is yielded for each record
|
252
|
+
#
|
253
|
+
# @return [Array]
|
254
|
+
#
|
255
|
+
# @raise [Lotus::Model::InvalidQueryError] if the raw SQL statement is invalid
|
256
|
+
#
|
257
|
+
# @since 0.5.0
|
258
|
+
def fetch(raw, &blk)
|
259
|
+
if block_given?
|
260
|
+
@connection.fetch(raw, &blk)
|
261
|
+
else
|
262
|
+
@connection.fetch(raw).to_a
|
263
|
+
end
|
264
|
+
rescue Sequel::DatabaseError => e
|
265
|
+
raise Lotus::Model::InvalidQueryError.new(e.message)
|
266
|
+
end
|
267
|
+
|
268
|
+
# @api private
|
269
|
+
# @since 0.5.0
|
270
|
+
#
|
271
|
+
# @see Lotus::Model::Adapters::Abstract#disconnect
|
272
|
+
def disconnect
|
273
|
+
@connection.disconnect
|
274
|
+
@connection = DisconnectedResource.new
|
275
|
+
end
|
276
|
+
|
247
277
|
private
|
248
278
|
|
249
279
|
# Returns a collection from the given name.
|