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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0e790b1ae96cb5efe8df6cc1cb73d9f2a552ff07
4
- data.tar.gz: 05962ff67790a0184e371b47fa3146a18fd88343
3
+ metadata.gz: de9019e132ab36bb7b30223c52e247df84e3cf3e
4
+ data.tar.gz: d56eeb3a71e31193b1509527f24ac8f0822df5f0
5
5
  SHA512:
6
- metadata.gz: 72200690f749ccf4f728554bb92de8ebc5215619afcda4672f87a8850ea6eca503a8f08ab5e2358fcae8fe7550dfaf3f51cd108857ce0e3567f93b0eb02c0514
7
- data.tar.gz: 25b0f721e617e62ef9a705d2fcca14112d08bf2254b9936c13e4245c191b342acd2003306574393fb4810687afa19f634ab3c4b4456288ce58d9d81fa3f11199
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.1 - 2015-07-10
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 query
32
- # It's raised when a query is malformed
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 statement directly on the connection
246
+ # Executes a raw command
206
247
  #
207
248
  # @param raw [String] the raw statement to execute on the connection
208
- # @return [Object]
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[_identity] = super(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
- Lotus::Utils::Kernel.Array(run)
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 sql directly on the connection
230
+ # Executes a raw SQL command
231
231
  #
232
- # @param raw [String] the raw sql statement to execute on the connection
232
+ # @param raw [String] the raw SQL statement to execute on the connection
233
233
  #
234
- # @return [Object]
234
+ # @raise [Lotus::Model::InvalidCommandError] if the raw SQL statement is invalid
235
235
  #
236
- # @raise [Lotus::Model::InvalidQueryError] if raw statement is invalid
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::InvalidQueryError.new(e.message)
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.