cuprum-collections 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -0
- data/README.md +255 -8
- data/lib/cuprum/collections/basic/collection.rb +13 -0
- data/lib/cuprum/collections/basic/commands/destroy_one.rb +4 -3
- data/lib/cuprum/collections/basic/commands/find_many.rb +1 -1
- data/lib/cuprum/collections/basic/commands/insert_one.rb +4 -3
- data/lib/cuprum/collections/basic/commands/update_one.rb +4 -3
- data/lib/cuprum/collections/basic/query.rb +3 -3
- data/lib/cuprum/collections/commands/abstract_find_many.rb +33 -32
- data/lib/cuprum/collections/commands/abstract_find_one.rb +4 -3
- data/lib/cuprum/collections/commands/create.rb +60 -0
- data/lib/cuprum/collections/commands/find_one_matching.rb +134 -0
- data/lib/cuprum/collections/commands/update.rb +74 -0
- data/lib/cuprum/collections/commands/upsert.rb +162 -0
- data/lib/cuprum/collections/commands.rb +7 -2
- data/lib/cuprum/collections/errors/abstract_find_error.rb +210 -0
- data/lib/cuprum/collections/errors/already_exists.rb +4 -72
- data/lib/cuprum/collections/errors/extra_attributes.rb +8 -18
- data/lib/cuprum/collections/errors/failed_validation.rb +5 -18
- data/lib/cuprum/collections/errors/invalid_parameters.rb +7 -15
- data/lib/cuprum/collections/errors/invalid_query.rb +5 -15
- data/lib/cuprum/collections/errors/missing_default_contract.rb +5 -17
- data/lib/cuprum/collections/errors/not_found.rb +4 -67
- data/lib/cuprum/collections/errors/not_unique.rb +18 -0
- data/lib/cuprum/collections/errors/unknown_operator.rb +7 -17
- data/lib/cuprum/collections/errors.rb +13 -1
- data/lib/cuprum/collections/queries/ordering.rb +2 -2
- data/lib/cuprum/collections/repository.rb +23 -10
- data/lib/cuprum/collections/rspec/collection_contract.rb +140 -103
- data/lib/cuprum/collections/rspec/destroy_one_command_contract.rb +8 -6
- data/lib/cuprum/collections/rspec/find_many_command_contract.rb +114 -34
- data/lib/cuprum/collections/rspec/find_one_command_contract.rb +12 -9
- data/lib/cuprum/collections/rspec/insert_one_command_contract.rb +4 -3
- data/lib/cuprum/collections/rspec/query_contract.rb +3 -3
- data/lib/cuprum/collections/rspec/querying_contract.rb +2 -2
- data/lib/cuprum/collections/rspec/repository_contract.rb +167 -93
- data/lib/cuprum/collections/rspec/update_one_command_contract.rb +4 -3
- data/lib/cuprum/collections/version.rb +1 -1
- data/lib/cuprum/collections.rb +1 -0
- metadata +20 -89
@@ -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
|
7
|
-
module Commands
|
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::
|
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
|
61
|
-
|
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
|