cuprum-collections 0.1.0 → 0.3.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
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