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