lotus-model 0.4.1 → 0.5.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 +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.
|