cuprum-collections 0.2.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 (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