cuprum-collections 0.1.0 → 0.3.0.rc.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/README.md +321 -15
  4. data/lib/cuprum/collections/basic/collection.rb +13 -0
  5. data/lib/cuprum/collections/basic/commands/destroy_one.rb +4 -3
  6. data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
  7. data/lib/cuprum/collections/basic/commands/insert_one.rb +4 -3
  8. data/lib/cuprum/collections/basic/commands/update_one.rb +4 -3
  9. data/lib/cuprum/collections/basic/query.rb +3 -3
  10. data/lib/cuprum/collections/basic/repository.rb +67 -0
  11. data/lib/cuprum/collections/commands/abstract_find_many.rb +33 -32
  12. data/lib/cuprum/collections/commands/abstract_find_one.rb +4 -3
  13. data/lib/cuprum/collections/commands/create.rb +60 -0
  14. data/lib/cuprum/collections/commands/find_one_matching.rb +134 -0
  15. data/lib/cuprum/collections/commands/update.rb +74 -0
  16. data/lib/cuprum/collections/commands/upsert.rb +162 -0
  17. data/lib/cuprum/collections/commands.rb +7 -2
  18. data/lib/cuprum/collections/errors/abstract_find_error.rb +210 -0
  19. data/lib/cuprum/collections/errors/already_exists.rb +4 -72
  20. data/lib/cuprum/collections/errors/extra_attributes.rb +8 -18
  21. data/lib/cuprum/collections/errors/failed_validation.rb +5 -18
  22. data/lib/cuprum/collections/errors/invalid_parameters.rb +7 -15
  23. data/lib/cuprum/collections/errors/invalid_query.rb +5 -15
  24. data/lib/cuprum/collections/errors/missing_default_contract.rb +5 -17
  25. data/lib/cuprum/collections/errors/not_found.rb +4 -67
  26. data/lib/cuprum/collections/errors/not_unique.rb +18 -0
  27. data/lib/cuprum/collections/errors/unknown_operator.rb +7 -17
  28. data/lib/cuprum/collections/errors.rb +13 -1
  29. data/lib/cuprum/collections/queries/ordering.rb +4 -2
  30. data/lib/cuprum/collections/repository.rb +105 -0
  31. data/lib/cuprum/collections/rspec/assign_one_command_contract.rb +2 -2
  32. data/lib/cuprum/collections/rspec/build_one_command_contract.rb +1 -1
  33. data/lib/cuprum/collections/rspec/collection_contract.rb +140 -103
  34. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +8 -6
  35. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +114 -34
  36. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +12 -9
  37. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +4 -3
  38. data/lib/cuprum/collections/rspec/query_contract.rb +3 -3
  39. data/lib/cuprum/collections/rspec/querying_contract.rb +2 -2
  40. data/lib/cuprum/collections/rspec/repository_contract.rb +235 -0
  41. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +4 -3
  42. data/lib/cuprum/collections/version.rb +3 -3
  43. data/lib/cuprum/collections.rb +1 -0
  44. metadata +25 -91
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/errors'
4
+
5
+ module Cuprum::Collections::Errors
6
+ # Abstract base class for failed query errors.
7
+ class AbstractFindError < Cuprum::Error # rubocop:disable Metrics/ClassLength
8
+ PERMITTED_KEYWORDS = %i[
9
+ attribute_name
10
+ attribute_value
11
+ attributes
12
+ primary_key
13
+ query
14
+ ].freeze
15
+ private_constant :PERMITTED_KEYWORDS
16
+
17
+ # @overload initialize(attribute_name:, attribute_value:, collection_name:, primary_key: false) # rubocop:disable Layout/LineLength
18
+ # @param attribute_name [String] The name of the queried attribute.
19
+ # @param attribute_value [Object] The value of the queried attribute.
20
+ # @param collection_name [String] The name of the collection.
21
+ # @param primary_key [true, false] Indicates that the queried attribute is
22
+ # the primary key for the collection.
23
+ #
24
+ # @overload initialize(attributes:, collection_name:)
25
+ # @param attributes [Hash<String=>Object>] The queried attributes.
26
+ # @param collection_name [String] The name of the collection.
27
+ #
28
+ # @overload initialize(query:, collection_name:)
29
+ # @param collection_name [String] The name of the collection.
30
+ # @param query [Cuprum::Collections::Query] The performed query.
31
+ def initialize(collection_name:, **options) # rubocop:disable Metrics/MethodLength
32
+ @collection_name = collection_name
33
+ @primary_key = false
34
+
35
+ resolve_options(**options)
36
+
37
+ super(
38
+ attribute_name: attribute_name,
39
+ attribute_value: attribute_value,
40
+ attributes: attributes,
41
+ collection_name: collection_name,
42
+ message: generate_message,
43
+ query: query&.criteria
44
+ )
45
+ end
46
+
47
+ # @return [String] the name of the queried attribute, if any.
48
+ attr_reader :attribute_name
49
+
50
+ # @return [Object] the value of the queried attribute, if any.
51
+ attr_reader :attribute_value
52
+
53
+ # @return [Hash<String=>Object>] The queried attributes.
54
+ attr_reader :attributes
55
+
56
+ # @return [String] the name of the collection.
57
+ attr_reader :collection_name
58
+
59
+ # @return [Cuprum::Collections::Query] the performed query, if any.
60
+ attr_reader :query
61
+
62
+ # @return [Array<Array>] the details of the query, in query criteria format.
63
+ def details
64
+ if attribute_name
65
+ [[attribute_name, :equal, attribute_value]]
66
+ elsif attributes
67
+ attributes
68
+ .map { |attr_name, attr_value| [attr_name, :equal, attr_value] }
69
+ elsif query
70
+ query.criteria
71
+ end
72
+ end
73
+
74
+ # @return [true, false] indicates that the queried attribute is the primary
75
+ # key for the collection.
76
+ def primary_key?
77
+ @primary_key
78
+ end
79
+
80
+ private
81
+
82
+ def as_json_data
83
+ {
84
+ 'collection_name' => collection_name,
85
+ 'details' => details
86
+ }.merge(find_data)
87
+ end
88
+
89
+ def entity_name
90
+ titleize(tools.str.singularize(collection_name))
91
+ end
92
+
93
+ def find_data # rubocop:disable Metrics/MethodLength
94
+ if attribute_name
95
+ {
96
+ 'attribute_name' => attribute_name,
97
+ 'attribute_value' => attribute_value,
98
+ 'primary_key' => primary_key?
99
+ }
100
+ elsif attributes
101
+ hsh = tools.hash_tools.convert_keys_to_strings(attributes)
102
+
103
+ { 'attributes' => hsh }
104
+ else
105
+ {}
106
+ end
107
+ end
108
+
109
+ def generate_message
110
+ core_message = "#{entity_name} #{message_fragment}"
111
+
112
+ if attribute_name
113
+ "#{core_message} with #{attribute_name.inspect} " \
114
+ "#{attribute_value.inspect}" \
115
+ "#{primary_key? ? ' (primary key)' : ''}"
116
+ elsif attributes
117
+ "#{core_message} with attributes #{attributes.inspect}"
118
+ elsif query
119
+ "#{core_message} matching the query"
120
+ end
121
+ end
122
+
123
+ def message_fragment
124
+ 'query failed'
125
+ end
126
+
127
+ def resolve_attribute_options(**options)
128
+ options = options.dup
129
+ @attribute_name = options.delete(:attribute_name)
130
+ @attribute_value = options.delete(:attribute_value)
131
+ @primary_key = options.delete(:primary_key) || false
132
+
133
+ validate_keywords(extra_keywords: options.keys)
134
+ end
135
+
136
+ def resolve_attributes_options(**options)
137
+ options = options.dup
138
+ @attributes = options.delete(:attributes)
139
+
140
+ validate_keywords(extra_keywords: options.keys)
141
+ end
142
+
143
+ def resolve_query_options(**options)
144
+ options = options.dup
145
+ @query = options.delete(:query)
146
+
147
+ validate_keywords(extra_keywords: options.keys)
148
+ end
149
+
150
+ def resolve_options(**options) # rubocop:disable Metrics/MethodLength
151
+ if options[:primary_key_name] && options[:primary_key_values]
152
+ resolve_primary_key_options(**options)
153
+ elsif options[:attribute_name] && options.key?(:attribute_value)
154
+ resolve_attribute_options(**options)
155
+ elsif options[:attributes]
156
+ resolve_attributes_options(**options)
157
+ elsif options[:query]
158
+ resolve_query_options(**options)
159
+ else
160
+ raise ArgumentError,
161
+ 'missing keywords :attribute_name, :attribute_value or :attributes ' \
162
+ 'or :query'
163
+ end
164
+ end
165
+
166
+ def resolve_primary_key_options(**options) # rubocop:disable Metrics/MethodLength
167
+ values = Array(options[:primary_key_values])
168
+
169
+ unless values.size == 1
170
+ raise ArgumentError,
171
+ 'deprecated mode does not support empty or multiple attribute values'
172
+ end
173
+
174
+ SleepingKingStudios::Tools::CoreTools
175
+ .instance
176
+ .deprecate(
177
+ 'NotFound.new(primary_key_name:, primary_key_values:)',
178
+ message: 'use NotFound.new(attribute_name:, attribute_value:)'
179
+ )
180
+
181
+ @attribute_name = options[:primary_key_name]
182
+ @attribute_value = values.first
183
+ @primary_key = true
184
+ end
185
+
186
+ def titleize(string)
187
+ tools.str.underscore(string).split('_').map(&:capitalize).join(' ')
188
+ end
189
+
190
+ def tools
191
+ SleepingKingStudios::Tools::Toolbelt.instance
192
+ end
193
+
194
+ def validate_keywords(extra_keywords:) # rubocop:disable Metrics/MethodLength
195
+ return if extra_keywords.empty?
196
+
197
+ ambiguous_keywords = extra_keywords & PERMITTED_KEYWORDS
198
+
199
+ if ambiguous_keywords.empty?
200
+ raise ArgumentError,
201
+ "unknown keyword#{extra_keywords.size == 1 ? '' : 's'} " \
202
+ "#{extra_keywords.map(&:inspect).join(', ')}"
203
+ else
204
+ raise ArgumentError,
205
+ "ambiguous keyword#{extra_keywords.size == 1 ? '' : 's'} " \
206
+ "#{ambiguous_keywords.map(&:inspect).join(', ')}"
207
+ end
208
+ end
209
+ end
210
+ end
@@ -1,86 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cuprum/error'
4
-
5
3
  require 'cuprum/collections/errors'
4
+ require 'cuprum/collections/errors/abstract_find_error'
6
5
 
7
6
  module Cuprum::Collections::Errors
8
7
  # Returned when an insert command is called with an existing record.
9
- class AlreadyExists < Cuprum::Error
8
+ class AlreadyExists < Cuprum::Collections::Errors::AbstractFindError
10
9
  # Short string used to identify the type of error.
11
10
  TYPE = 'cuprum.collections.errors.already_exists'
12
11
 
13
- # @param collection_name [String, Symbol] The name of the collection.
14
- # @param primary_key_name [String, Symbol] The name of the primary key
15
- # attribute.
16
- # @param primary_key_values [Object, Array] The expected values of the
17
- # primary key attribute.
18
- def initialize(collection_name:, primary_key_name:, primary_key_values:)
19
- @collection_name = collection_name
20
- @primary_key_name = primary_key_name
21
- @primary_key_values = Array(primary_key_values)
22
-
23
- super(
24
- collection_name: collection_name,
25
- message: default_message,
26
- primary_key_name: primary_key_name,
27
- primary_key_values: primary_key_values
28
- )
29
- end
30
-
31
- # @return [String, Symbol] the name of the collection.
32
- attr_reader :collection_name
33
-
34
- # @return [String, Symbol] the name of the primary key attribute.
35
- attr_reader :primary_key_name
36
-
37
- # @return [Array] The expected values of the primary key attribute.
38
- attr_reader :primary_key_values
39
-
40
- # @return [Hash] a serializable hash representation of the error.
41
- def as_json
42
- {
43
- 'data' => {
44
- 'collection_name' => collection_name,
45
- 'primary_key_name' => primary_key_name,
46
- 'primary_key_values' => primary_key_values
47
- },
48
- 'message' => message,
49
- 'type' => type
50
- }
51
- end
52
-
53
- # @return [String] short string used to identify the type of error.
54
- def type
55
- TYPE
56
- end
57
-
58
12
  private
59
13
 
60
- def default_message
61
- primary_keys = primary_key_values.map(&:inspect).join(', ')
62
-
63
- "#{entity_name} already exist#{singular? ? 's' : ''} with" \
64
- " #{primary_key_name} #{primary_keys}"
65
- end
66
-
67
- def entity_name
68
- entity_name = collection_name
69
- entity_name = tools.str.singularize(entity_name) if singular?
70
-
71
- titleize(entity_name)
72
- end
73
-
74
- def singular?
75
- primary_key_values.size == 1
76
- end
77
-
78
- def titleize(string)
79
- tools.str.underscore(string).split('_').map(&:capitalize).join(' ')
80
- end
81
-
82
- def tools
83
- SleepingKingStudios::Tools::Toolbelt.instance
14
+ def message_fragment
15
+ 'already exists'
84
16
  end
85
17
  end
86
18
  end
@@ -38,29 +38,19 @@ module Cuprum::Collections::Errors
38
38
  # @return [Array<String>] The names of valid attributes for the entity.
39
39
  attr_reader :valid_attributes
40
40
 
41
- # @return [Hash] a serializable hash representation of the error.
42
- def as_json
41
+ private
42
+
43
+ def as_json_data
43
44
  {
44
- 'data' => {
45
- 'entity_class' => entity_class.name,
46
- 'extra_attributes' => extra_attributes,
47
- 'valid_attributes' => valid_attributes
48
- },
49
- 'message' => message,
50
- 'type' => type
45
+ 'entity_class' => entity_class.name,
46
+ 'extra_attributes' => extra_attributes,
47
+ 'valid_attributes' => valid_attributes
51
48
  }
52
49
  end
53
50
 
54
- # @return [String] short string used to identify the type of error.
55
- def type
56
- TYPE
57
- end
58
-
59
- private
60
-
61
51
  def default_message
62
- "invalid attributes for #{entity_class.name}:" \
63
- " #{extra_attributes.join(', ')}"
52
+ "invalid attributes for #{entity_class.name}: " \
53
+ "#{extra_attributes.join(', ')}"
64
54
  end
65
55
  end
66
56
  end
@@ -7,9 +7,6 @@ require 'cuprum/collections/errors'
7
7
  module Cuprum::Collections::Errors
8
8
  # Error returned when a collection item fails validation.
9
9
  class FailedValidation < Cuprum::Error
10
- COMPARABLE_PROPERTIES = %i[entity_class errors message].freeze
11
- private_constant :COMPARABLE_PROPERTIES
12
-
13
10
  # Short string used to identify the type of error.
14
11
  TYPE = 'cuprum.collections.errors.failed_validation'
15
12
 
@@ -33,25 +30,15 @@ module Cuprum::Collections::Errors
33
30
  # @return [Stannum::Errors] The errors generated when validating the entity.
34
31
  attr_reader :errors
35
32
 
36
- # @return [Hash] a serializable hash representation of the error.
37
- def as_json
33
+ private
34
+
35
+ def as_json_data
38
36
  {
39
- 'data' => {
40
- 'entity_class' => entity_class.name,
41
- 'errors' => format_errors
42
- },
43
- 'message' => message,
44
- 'type' => type
37
+ 'entity_class' => entity_class.name,
38
+ 'errors' => format_errors
45
39
  }
46
40
  end
47
41
 
48
- # @return [String] short string used to identify the type of error.
49
- def type
50
- TYPE
51
- end
52
-
53
- private
54
-
55
42
  def default_message
56
43
  "#{entity_class.name} failed validation"
57
44
  end
@@ -24,27 +24,19 @@ module Cuprum::Collections::Errors
24
24
  )
25
25
  end
26
26
 
27
- # @return [Hash] a serializable hash representation of the error.
28
- def as_json
29
- {
30
- 'data' => {
31
- 'command_class' => command.class.name,
32
- 'errors' => errors.to_a
33
- },
34
- 'message' => message,
35
- 'type' => type
36
- }
37
- end
38
-
39
27
  # @return [Cuprum::Command] the called command.
40
28
  attr_reader :command
41
29
 
42
30
  # @return [Stannum::Errors] the errors returned by the parameters contract.
43
31
  attr_reader :errors
44
32
 
45
- # @return [String] short string used to identify the type of error.
46
- def type
47
- TYPE
33
+ private
34
+
35
+ def as_json_data
36
+ {
37
+ 'command_class' => command.class.name,
38
+ 'errors' => errors.to_a
39
+ }
48
40
  end
49
41
  end
50
42
  end
@@ -29,25 +29,15 @@ module Cuprum::Collections::Errors
29
29
  # @return [Symbol] the strategy used to construct the query.
30
30
  attr_reader :strategy
31
31
 
32
- # @return [Hash] a serializable hash representation of the error.
33
- def as_json
32
+ private
33
+
34
+ def as_json_data
34
35
  {
35
- 'data' => {
36
- 'errors' => errors.to_a,
37
- 'strategy' => strategy
38
- },
39
- 'message' => message,
40
- 'type' => type
36
+ 'errors' => errors.to_a,
37
+ 'strategy' => strategy
41
38
  }
42
39
  end
43
40
 
44
- # @return [String] short string used to identify the type of error.
45
- def type
46
- TYPE
47
- end
48
-
49
- private
50
-
51
41
  def default_message
52
42
  "unable to parse query with strategy #{strategy.inspect}"
53
43
  end
@@ -23,27 +23,15 @@ module Cuprum::Collections::Errors
23
23
  # @return [Class] the class of the assigned entity.
24
24
  attr_reader :entity_class
25
25
 
26
- # @return [Hash] a serializable hash representation of the error.
27
- def as_json
28
- {
29
- 'data' => {
30
- 'entity_class' => entity_class.name
31
- },
32
- 'message' => message,
33
- 'type' => type
34
- }
35
- end
26
+ private
36
27
 
37
- # @return [String] short string used to identify the type of error.
38
- def type
39
- TYPE
28
+ def as_json_data
29
+ { 'entity_class' => entity_class.name }
40
30
  end
41
31
 
42
- private
43
-
44
32
  def default_message
45
- "attempted to validate a #{entity_class.name}, but #{entity_class.name}" \
46
- ' does not define a default contract'
33
+ "attempted to validate a #{entity_class.name}, but " \
34
+ "#{entity_class.name} does not define a default contract"
47
35
  end
48
36
  end
49
37
  end
@@ -1,81 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'cuprum/error'
4
- require 'sleeping_king_studios/tools/toolbelt'
5
-
6
3
  require 'cuprum/collections/errors'
4
+ require 'cuprum/collections/errors/abstract_find_error'
7
5
 
8
6
  module Cuprum::Collections::Errors
9
7
  # Returned when a find command does not find the requested items.
10
- class NotFound < Cuprum::Error
8
+ class NotFound < Cuprum::Collections::Errors::AbstractFindError
11
9
  # Short string used to identify the type of error.
12
10
  TYPE = 'cuprum.collections.errors.not_found'
13
11
 
14
- # @param collection_name [String, Symbol] The name of the collection.
15
- # @param primary_key_name [String, Symbol] The name of the primary key
16
- # attribute.
17
- # @param primary_key_values [Object, Array] The expected values of the
18
- # primary key attribute.
19
- def initialize(collection_name:, primary_key_name:, primary_key_values:)
20
- @collection_name = collection_name
21
- @primary_key_name = primary_key_name
22
- @primary_key_values = Array(primary_key_values)
23
-
24
- super(message: default_message)
25
- end
26
-
27
- # @return [String, Symbol] the name of the collection.
28
- attr_reader :collection_name
29
-
30
- # @return [String, Symbol] the name of the primary key attribute.
31
- attr_reader :primary_key_name
32
-
33
- # @return [Array] The expected values of the primary key attribute.
34
- attr_reader :primary_key_values
35
-
36
- # @return [Hash] a serializable hash representation of the error.
37
- def as_json
38
- {
39
- 'data' => {
40
- 'collection_name' => collection_name,
41
- 'primary_key_name' => primary_key_name,
42
- 'primary_key_values' => primary_key_values
43
- },
44
- 'message' => message,
45
- 'type' => type
46
- }
47
- end
48
-
49
- # @return [String] short string used to identify the type of error.
50
- def type
51
- TYPE
52
- end
53
-
54
12
  private
55
13
 
56
- def default_message
57
- primary_keys = primary_key_values.map(&:inspect).join(', ')
58
-
59
- "#{entity_name} not found with #{primary_key_name} #{primary_keys}"
60
- end
61
-
62
- def entity_name
63
- entity_name = collection_name
64
- entity_name = tools.str.singularize(entity_name) if singular?
65
-
66
- titleize(entity_name)
67
- end
68
-
69
- def singular?
70
- primary_key_values.size == 1
71
- end
72
-
73
- def titleize(string)
74
- tools.str.underscore(string).split('_').map(&:capitalize).join(' ')
75
- end
76
-
77
- def tools
78
- SleepingKingStudios::Tools::Toolbelt.instance
14
+ def message_fragment
15
+ 'not found'
79
16
  end
80
17
  end
81
18
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/errors'
4
+ require 'cuprum/collections/errors/abstract_find_error'
5
+
6
+ module Cuprum::Collections::Errors
7
+ # Returned when a unique find command does finds multiple items.
8
+ class NotUnique < Cuprum::Collections::Errors::AbstractFindError
9
+ # Short string used to identify the type of error.
10
+ TYPE = 'cuprum.collections.errors.not_unique'
11
+
12
+ private
13
+
14
+ def message_fragment
15
+ 'not unique'
16
+ end
17
+ end
18
+ end
@@ -22,18 +22,6 @@ module Cuprum::Collections::Errors
22
22
  # @return [String, Symbol] the unknown operator.
23
23
  attr_reader :operator
24
24
 
25
- # @return [Hash] a serializable hash representation of the error.
26
- def as_json
27
- {
28
- 'data' => {
29
- 'corrections' => corrections,
30
- 'operator' => operator
31
- },
32
- 'message' => message,
33
- 'type' => type
34
- }
35
- end
36
-
37
25
  # @return [Array<String>] Suggested possible values for the operator.
38
26
  def corrections
39
27
  @corrections ||=
@@ -42,13 +30,15 @@ module Cuprum::Collections::Errors
42
30
  .correct(operator)
43
31
  end
44
32
 
45
- # @return [String] short string used to identify the type of error.
46
- def type
47
- TYPE
48
- end
49
-
50
33
  private
51
34
 
35
+ def as_json_data
36
+ {
37
+ 'corrections' => corrections,
38
+ 'operator' => operator
39
+ }
40
+ end
41
+
52
42
  def generate_message
53
43
  message = "unknown operator #{operator.inspect}"
54
44
 
@@ -4,5 +4,17 @@ require 'cuprum/collections'
4
4
 
5
5
  module Cuprum::Collections
6
6
  # Namespace for errors, which represent failure states of commands.
7
- module Errors; end
7
+ module Errors
8
+ autoload :AbstractFindError, 'cuprum/collections/errors/abstract_find_error'
9
+ autoload :AlreadyExists, 'cuprum/collections/errors/already_exists'
10
+ autoload :ExtraAttributes, 'cuprum/collections/errors/extra_attributes'
11
+ autoload :FailedValidation, 'cuprum/collections/errors/failed_validation'
12
+ autoload :InvalidParameters, 'cuprum/collections/errors/invalid_parameters'
13
+ autoload :InvalidQuery, 'cuprum/collections/errors/invalid_query'
14
+ autoload :MissingDefaultContract,
15
+ 'cuprum/collections/errors/missing_default_contract'
16
+ autoload :NotFound, 'cuprum/collections/errors/not_found'
17
+ autoload :NotUnique, 'cuprum/collections/errors/not_unique'
18
+ autoload :UnknownOperator, 'cuprum/collections/errors/unknown_operator'
19
+ end
8
20
  end
@@ -33,6 +33,8 @@ module Cuprum::Collections::Queries
33
33
  # @raise InvalidOrderError if any of the hash keys are invalid attribute
34
34
  # names, or any of the hash values are invalid sort directions.
35
35
  def normalize(*attributes)
36
+ attributes = attributes.flatten if attributes.first.is_a?(Array)
37
+
36
38
  validate_ordering!(attributes)
37
39
 
38
40
  qualified = attributes.last.is_a?(Hash) ? attributes.pop : {}
@@ -66,8 +68,8 @@ module Cuprum::Collections::Queries
66
68
  return if ordering_constraint.matches?(attributes)
67
69
 
68
70
  raise InvalidOrderError,
69
- 'order must be a list of attribute names and/or a hash of attribute' \
70
- ' names with values :asc or :desc'
71
+ 'order must be a list of attribute names and/or a hash of ' \
72
+ 'attribute names with values :asc or :desc'
71
73
  end
72
74
  end
73
75
  end