cuprum-collections 0.2.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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +255 -8
  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/commands/abstract_find_many.rb +33 -32
  11. data/lib/cuprum/collections/commands/abstract_find_one.rb +4 -3
  12. data/lib/cuprum/collections/commands/create.rb +60 -0
  13. data/lib/cuprum/collections/commands/find_one_matching.rb +134 -0
  14. data/lib/cuprum/collections/commands/update.rb +74 -0
  15. data/lib/cuprum/collections/commands/upsert.rb +162 -0
  16. data/lib/cuprum/collections/commands.rb +7 -2
  17. data/lib/cuprum/collections/errors/abstract_find_error.rb +210 -0
  18. data/lib/cuprum/collections/errors/already_exists.rb +4 -72
  19. data/lib/cuprum/collections/errors/extra_attributes.rb +8 -18
  20. data/lib/cuprum/collections/errors/failed_validation.rb +5 -18
  21. data/lib/cuprum/collections/errors/invalid_parameters.rb +7 -15
  22. data/lib/cuprum/collections/errors/invalid_query.rb +5 -15
  23. data/lib/cuprum/collections/errors/missing_default_contract.rb +5 -17
  24. data/lib/cuprum/collections/errors/not_found.rb +4 -67
  25. data/lib/cuprum/collections/errors/not_unique.rb +18 -0
  26. data/lib/cuprum/collections/errors/unknown_operator.rb +7 -17
  27. data/lib/cuprum/collections/errors.rb +13 -1
  28. data/lib/cuprum/collections/queries/ordering.rb +2 -2
  29. data/lib/cuprum/collections/repository.rb +23 -10
  30. data/lib/cuprum/collections/rspec/collection_contract.rb +140 -103
  31. data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +8 -6
  32. data/lib/cuprum/collections/rspec/find_many_command_contract.rb +114 -34
  33. data/lib/cuprum/collections/rspec/find_one_command_contract.rb +12 -9
  34. data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +4 -3
  35. data/lib/cuprum/collections/rspec/query_contract.rb +3 -3
  36. data/lib/cuprum/collections/rspec/querying_contract.rb +2 -2
  37. data/lib/cuprum/collections/rspec/repository_contract.rb +167 -93
  38. data/lib/cuprum/collections/rspec/update_one_command_contract.rb +4 -3
  39. data/lib/cuprum/collections/version.rb +3 -3
  40. data/lib/cuprum/collections.rb +1 -0
  41. metadata +22 -91
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/commands'
4
+ require 'cuprum/collections/errors/not_found'
5
+ require 'cuprum/collections/errors/not_unique'
6
+
7
+ module Cuprum::Collections::Commands
8
+ # Command for finding a unique entity by a query or set of attributes.
9
+ #
10
+ # @example Finding An Entity By Attributes
11
+ # command =
12
+ # Cuprum::Collections::Commands::FindOneMatching
13
+ # .new(collection: books_collection)
14
+ #
15
+ # # With an attributes Hash that matches one entity.
16
+ # result = command.call(attributes: { 'title' => 'Gideon the Ninth' })
17
+ # result.success?
18
+ # #=> true
19
+ # result.value
20
+ # #=> a Book with title 'Gideon the Ninth'
21
+ #
22
+ # # With an attributes Hash that matches multiple entities.
23
+ # result = command.call(attributes: { 'author' => 'Tamsyn Muir' })
24
+ # result.success?
25
+ # #=> false
26
+ # result.error
27
+ # #=> an instance of Cuprum::Collections::NotUnique
28
+ #
29
+ # # With an attributes Hash that does not match any entities.
30
+ # result = command.call(
31
+ # attributes: {
32
+ # 'author' => 'Becky Chambers',
33
+ # 'series' => 'The Locked Tomb'
34
+ # }
35
+ # )
36
+ # result.success?
37
+ # #=> false
38
+ # result.error
39
+ # #=> an instance of Cuprum::Collections::NotFound
40
+ #
41
+ # @example Finding An Entity By Query
42
+ # command =
43
+ # Cuprum::Collections::Commands::FindOneMatching
44
+ # .new(collection: collection)
45
+ #
46
+ # # With a query that matches one entity.
47
+ # result = command.call do
48
+ # {
49
+ # 'series' => 'The Lord of the Rings',
50
+ # 'published_at' => greater_than('1955-01-01')
51
+ # }
52
+ # end
53
+ # result.success?
54
+ # #=> true
55
+ # result.value
56
+ # #=> a Book matching the query
57
+ #
58
+ # # With a query that matches multiple entities.
59
+ # result = command.call do
60
+ # {
61
+ # 'series' => 'The Lord of the Rings',
62
+ # 'published_at' => less_than('1955-01-01')
63
+ # }
64
+ # end
65
+ # result.success?
66
+ # #=> false
67
+ # result.error
68
+ # #=> an instance of Cuprum::Collections::NotUnique
69
+ #
70
+ # # With an attributes Hash that does not match any entities.
71
+ # result = command.call do
72
+ # {
73
+ # 'series' => 'The Lord of the Rings',
74
+ # 'published_at' => less_than('1954-01-01')
75
+ # }
76
+ # end
77
+ # result.success?
78
+ # #=> false
79
+ # result.error
80
+ # #=> an instance of Cuprum::Collections::NotFound
81
+ class FindOneMatching < Cuprum::Command
82
+ # @param collection [#find_matching] The collection to query.
83
+ def initialize(collection:)
84
+ super()
85
+
86
+ @collection = collection
87
+ end
88
+
89
+ # @return [#find_matching] the collection to query.
90
+ attr_reader :collection
91
+
92
+ private
93
+
94
+ def error_params_for(attributes: nil, &block)
95
+ { collection_name: collection.collection_name }.merge(
96
+ if block_given?
97
+ { query: collection.query.where(&block) }
98
+ else
99
+ { attributes: attributes }
100
+ end
101
+ )
102
+ end
103
+
104
+ def not_found_error(attributes: nil, &block)
105
+ Cuprum::Collections::Errors::NotFound.new(
106
+ **error_params_for(attributes: attributes, &block)
107
+ )
108
+ end
109
+
110
+ def not_unique_error(attributes: nil, &block)
111
+ Cuprum::Collections::Errors::NotUnique.new(
112
+ **error_params_for(attributes: attributes, &block)
113
+ )
114
+ end
115
+
116
+ def process(attributes: nil, &block)
117
+ query = block || -> { attributes }
118
+ entities = step { collection.find_matching.call(limit: 2, &query) }
119
+
120
+ require_one_entity(attributes: attributes, entities: entities, &block)
121
+ end
122
+
123
+ def require_one_entity(attributes:, entities:, &block)
124
+ case entities.count
125
+ when 0
126
+ failure(not_found_error(attributes: attributes, &block))
127
+ when 1
128
+ entities.first
129
+ when 2
130
+ failure(not_unique_error(attributes: attributes, &block))
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cuprum/collections/commands'
4
+
5
+ module Cuprum::Collections::Commands
6
+ # Command for assigning, validating and updating an entity in a collection.
7
+ #
8
+ # @example Updating An Entity
9
+ # command =
10
+ # Cuprum::Collections::Commands::Create.new(collection:)
11
+ # .new(collection: books_collection)
12
+ # entity =
13
+ # books_collection
14
+ # .find_matching { { 'title' => 'Gideon the Ninth' } }
15
+ # .value
16
+ # .first
17
+ #
18
+ # # With Invalid Attributes
19
+ # attributes = { 'author' => '' }
20
+ # result = command.call(attributes: attributes)
21
+ # result.success?
22
+ # #=> false
23
+ # result.error
24
+ # #=> an instance of Cuprum::Collections::Errors::FailedValidation
25
+ # books_collection
26
+ # .find_matching { { 'title' => 'Gideon the Ninth' } }
27
+ # .value
28
+ # .first['author']
29
+ # #=> 'Tamsyn Muir'
30
+ #
31
+ # # With Valid Attributes
32
+ # attributes = { 'series' => 'The Locked Tomb' }
33
+ # result = command.call(attributes: attributes)
34
+ # result.success?
35
+ # #=> true
36
+ # result.value
37
+ # #=> an instance of Book with title 'Gideon the Ninth' and series
38
+ # 'The Locked Tomb'
39
+ # books_collection
40
+ # .find_matching { { 'title' => 'Gideon the Ninth' } }
41
+ # .value
42
+ # .first['series']
43
+ # #=> 'The Locked Tomb'
44
+ class Update < Cuprum::Command
45
+ # @param collection [Object] The collection used to store the entity.
46
+ # @param contract [Stannum::Constraint] The constraint used to validate the
47
+ # entity. If not given, defaults to the default contract for the
48
+ # collection.
49
+ def initialize(collection:, contract: nil)
50
+ super()
51
+
52
+ @collection = collection
53
+ @contract = contract
54
+ end
55
+
56
+ # @return [Object] the collection used to store the entity.
57
+ attr_reader :collection
58
+
59
+ # @return [Stannum::Constraint] the constraint used to validate the entity.
60
+ attr_reader :contract
61
+
62
+ private
63
+
64
+ def process(attributes:, entity:)
65
+ entity = step do
66
+ collection.assign_one.call(attributes: attributes, entity: entity)
67
+ end
68
+
69
+ step { collection.validate_one.call(entity: entity, contract: contract) }
70
+
71
+ step { collection.update_one.call(entity: entity) }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ require 'cuprum/collections/commands'
6
+ require 'cuprum/collections/commands/create'
7
+ require 'cuprum/collections/commands/find_one_matching'
8
+ require 'cuprum/collections/commands/update'
9
+
10
+ module Cuprum::Collections::Commands
11
+ # Command for creating or updating an entity from an attributes Hash.
12
+ #
13
+ # @example Creating Or Updating An Entity By Primary Key
14
+ # command =
15
+ # Cuprum::Collections::Commands::Upsert
16
+ # .new(collection: books_collection)
17
+ #
18
+ # # Creating A New Entity
19
+ # books_collection.query.count
20
+ # #=> 0
21
+ # attributes = {
22
+ # 'id' => 0
23
+ # 'title' => 'Gideon the Ninth',
24
+ # 'author' => 'Tamsyn Muir'
25
+ # }
26
+ # result = command.call(attributes: attributes)
27
+ # result.value
28
+ # #=> a Book with id 0, title 'Gideon the Ninth', and author 'Tamsyn Muir'
29
+ # books_collection.query.count
30
+ # #=> 1
31
+ #
32
+ # # Updating An Existing Entity
33
+ # attributes = {
34
+ # 'id' => 0
35
+ # 'series' => 'The Locked Tomb'
36
+ # }
37
+ # result = command.call(attributes: attributes)
38
+ # result.value
39
+ # #=> a Book with id 0, title 'Gideon the Ninth', author 'Tamsyn Muir', and
40
+ # series 'The Locked Tomb'
41
+ # books_collection.query.count
42
+ # #=> 1
43
+ #
44
+ # @example Creating Or Updating An Entity By Attributes
45
+ # command =
46
+ # Cuprum::Collections::Commands::Upsert
47
+ # .new(attribute_names: %w[title], collection: books_collection)
48
+ #
49
+ # # Creating A New Entity
50
+ # books_collection.query.count
51
+ # #=> 0
52
+ # attributes = {
53
+ # 'id' => 0
54
+ # 'title' => 'Gideon the Ninth',
55
+ # 'author' => 'Tamsyn Muir'
56
+ # }
57
+ # result = command.call(attributes: attributes)
58
+ # result.value
59
+ # #=> a Book with id 0, title 'Gideon the Ninth', and author 'Tamsyn Muir'
60
+ # books_collection.query.count
61
+ # #=> 1
62
+ #
63
+ # # Updating An Existing Entity
64
+ # attributes = {
65
+ # 'title' => 'Gideon the Ninth',
66
+ # 'series' => 'The Locked Tomb'
67
+ # }
68
+ # result = command.call(attributes: attributes)
69
+ # result.value
70
+ # #=> a Book with id 0, title 'Gideon the Ninth', author 'Tamsyn Muir', and
71
+ # series 'The Locked Tomb'
72
+ # books_collection.query.count
73
+ # #=> 1
74
+ class Upsert < Cuprum::Command
75
+ # @param attribute_names [String, Symbol, Array<String, Symbol>] The names
76
+ # of the attributes used to find the unique entity.
77
+ # @param collection [Object] The collection used to store the entity.
78
+ # @param contract [Stannum::Constraint] The constraint used to validate the
79
+ # entity. If not given, defaults to the default contract for the
80
+ # collection.
81
+ def initialize(collection:, attribute_names: 'id', contract: nil)
82
+ super()
83
+
84
+ @attribute_names = normalize_attribute_names(attribute_names)
85
+ @collection = collection
86
+ @contract = contract
87
+ end
88
+
89
+ # @return [Array<String>] the names of the attributes used to find the
90
+ # unique entity.
91
+ attr_reader :attribute_names
92
+
93
+ # @return [Object] the collection used to store the entity.
94
+ attr_reader :collection
95
+
96
+ # @return [Stannum::Constraint] the constraint used to validate the entity.
97
+ attr_reader :contract
98
+
99
+ private
100
+
101
+ def create_entity(attributes:)
102
+ Cuprum::Collections::Commands::Create
103
+ .new(collection: collection, contract: contract)
104
+ .call(attributes: attributes)
105
+ end
106
+
107
+ def filter_attributes(attributes:)
108
+ tools
109
+ .hash_tools
110
+ .convert_keys_to_strings(attributes)
111
+ .select { |key, _| attribute_names.include?(key) }
112
+ end
113
+
114
+ def find_entity(attributes:)
115
+ filtered = filter_attributes(attributes: attributes)
116
+ result =
117
+ Cuprum::Collections::Commands::FindOneMatching
118
+ .new(collection: collection)
119
+ .call(attributes: filtered)
120
+
121
+ return if result.error.is_a?(Cuprum::Collections::Errors::NotFound)
122
+
123
+ result
124
+ end
125
+
126
+ def normalize_attribute_names(attribute_names)
127
+ names = Array(attribute_names)
128
+
129
+ raise ArgumentError, "attribute names can't be blank" if names.empty?
130
+
131
+ names = names.map do |name|
132
+ unless name.is_a?(String) || name.is_a?(Symbol)
133
+ raise ArgumentError, "invalid attribute name #{name.inspect}"
134
+ end
135
+
136
+ name.to_s
137
+ end
138
+
139
+ Set.new(names)
140
+ end
141
+
142
+ def process(attributes:)
143
+ entity = step { find_entity(attributes: attributes) }
144
+
145
+ if entity
146
+ update_entity(attributes: attributes, entity: entity)
147
+ else
148
+ create_entity(attributes: attributes)
149
+ end
150
+ end
151
+
152
+ def tools
153
+ SleepingKingStudios::Tools::Toolbelt.instance
154
+ end
155
+
156
+ def update_entity(attributes:, entity:)
157
+ Cuprum::Collections::Commands::Update
158
+ .new(collection: collection, contract: contract)
159
+ .call(attributes: attributes, entity: entity)
160
+ end
161
+ end
162
+ end
@@ -3,6 +3,11 @@
3
3
  require 'cuprum/collections'
4
4
 
5
5
  module Cuprum::Collections
6
- # Namespace for abstract commands implementing collection functionality.
7
- module Commands; end
6
+ # Namespace for abstract commands and collection-independent commands.
7
+ module Commands
8
+ autoload :Create, 'cuprum/collections/commands/create'
9
+ autoload :FindOneMatching, 'cuprum/collections/commands/find_one_matching'
10
+ autoload :Update, 'cuprum/collections/commands/update'
11
+ autoload :Upsert, 'cuprum/collections/commands/upsert'
12
+ end
8
13
  end
@@ -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