cuprum-rails 0.1.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 +7 -0
- data/CHANGELOG.md +98 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/DEVELOPMENT.md +28 -0
- data/LICENSE +22 -0
- data/README.md +1045 -0
- data/lib/cuprum/rails/action.rb +45 -0
- data/lib/cuprum/rails/actions/create.rb +49 -0
- data/lib/cuprum/rails/actions/destroy.rb +22 -0
- data/lib/cuprum/rails/actions/edit.rb +22 -0
- data/lib/cuprum/rails/actions/index.rb +55 -0
- data/lib/cuprum/rails/actions/new.rb +19 -0
- data/lib/cuprum/rails/actions/resource_action.rb +75 -0
- data/lib/cuprum/rails/actions/show.rb +22 -0
- data/lib/cuprum/rails/actions/update.rb +59 -0
- data/lib/cuprum/rails/actions.rb +16 -0
- data/lib/cuprum/rails/collection.rb +115 -0
- data/lib/cuprum/rails/command.rb +137 -0
- data/lib/cuprum/rails/commands/assign_one.rb +66 -0
- data/lib/cuprum/rails/commands/build_one.rb +55 -0
- data/lib/cuprum/rails/commands/destroy_one.rb +43 -0
- data/lib/cuprum/rails/commands/find_many.rb +60 -0
- data/lib/cuprum/rails/commands/find_matching.rb +121 -0
- data/lib/cuprum/rails/commands/find_one.rb +50 -0
- data/lib/cuprum/rails/commands/insert_one.rb +41 -0
- data/lib/cuprum/rails/commands/update_one.rb +49 -0
- data/lib/cuprum/rails/commands/validate_one.rb +68 -0
- data/lib/cuprum/rails/commands.rb +18 -0
- data/lib/cuprum/rails/controller.rb +50 -0
- data/lib/cuprum/rails/controller_action.rb +121 -0
- data/lib/cuprum/rails/controllers/class_methods/actions.rb +57 -0
- data/lib/cuprum/rails/controllers/class_methods/configuration.rb +64 -0
- data/lib/cuprum/rails/controllers/class_methods/validations.rb +30 -0
- data/lib/cuprum/rails/controllers/class_methods.rb +15 -0
- data/lib/cuprum/rails/controllers/configuration.rb +53 -0
- data/lib/cuprum/rails/controllers.rb +10 -0
- data/lib/cuprum/rails/errors/missing_parameters.rb +33 -0
- data/lib/cuprum/rails/errors/missing_primary_key.rb +46 -0
- data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +34 -0
- data/lib/cuprum/rails/errors.rb +8 -0
- data/lib/cuprum/rails/map_errors.rb +44 -0
- data/lib/cuprum/rails/query.rb +77 -0
- data/lib/cuprum/rails/query_builder.rb +78 -0
- data/lib/cuprum/rails/repository.rb +44 -0
- data/lib/cuprum/rails/request.rb +105 -0
- data/lib/cuprum/rails/resource.rb +145 -0
- data/lib/cuprum/rails/responders/actions.rb +73 -0
- data/lib/cuprum/rails/responders/html/plural_resource.rb +62 -0
- data/lib/cuprum/rails/responders/html/singular_resource.rb +59 -0
- data/lib/cuprum/rails/responders/html.rb +11 -0
- data/lib/cuprum/rails/responders/html_responder.rb +129 -0
- data/lib/cuprum/rails/responders/json/resource.rb +60 -0
- data/lib/cuprum/rails/responders/json.rb +10 -0
- data/lib/cuprum/rails/responders/json_responder.rb +122 -0
- data/lib/cuprum/rails/responders/matching.rb +145 -0
- data/lib/cuprum/rails/responders/serialization.rb +36 -0
- data/lib/cuprum/rails/responders.rb +15 -0
- data/lib/cuprum/rails/responses/html/redirect_response.rb +29 -0
- data/lib/cuprum/rails/responses/html/render_response.rb +52 -0
- data/lib/cuprum/rails/responses/html.rb +11 -0
- data/lib/cuprum/rails/responses/json_response.rb +29 -0
- data/lib/cuprum/rails/responses.rb +11 -0
- data/lib/cuprum/rails/routes.rb +166 -0
- data/lib/cuprum/rails/routing/plural_routes.rb +26 -0
- data/lib/cuprum/rails/routing/singular_routes.rb +24 -0
- data/lib/cuprum/rails/routing.rb +11 -0
- data/lib/cuprum/rails/rspec/command_contract.rb +460 -0
- data/lib/cuprum/rails/rspec/define_route_contract.rb +84 -0
- data/lib/cuprum/rails/rspec.rb +8 -0
- data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +24 -0
- data/lib/cuprum/rails/serializers/json/array_serializer.rb +40 -0
- data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +217 -0
- data/lib/cuprum/rails/serializers/json/error_serializer.rb +24 -0
- data/lib/cuprum/rails/serializers/json/hash_serializer.rb +44 -0
- data/lib/cuprum/rails/serializers/json/identity_serializer.rb +21 -0
- data/lib/cuprum/rails/serializers/json/serializer.rb +66 -0
- data/lib/cuprum/rails/serializers/json.rb +40 -0
- data/lib/cuprum/rails/serializers.rb +10 -0
- data/lib/cuprum/rails/version.rb +59 -0
- data/lib/cuprum/rails.rb +31 -0
- metadata +286 -0
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types/hash_with_indifferent_keys'
|
4
|
+
|
5
|
+
require 'cuprum/collections/errors/extra_attributes'
|
6
|
+
|
7
|
+
require 'cuprum/rails/command'
|
8
|
+
require 'cuprum/rails/commands'
|
9
|
+
|
10
|
+
module Cuprum::Rails::Commands
|
11
|
+
# Command for assigning attributes to an ActiveRecord model.
|
12
|
+
class AssignOne < Cuprum::Rails::Command
|
13
|
+
# @!method call(attributes:, entity:)
|
14
|
+
# Assigns the given attributes to the record.
|
15
|
+
#
|
16
|
+
# Any attributes on the record that are not part of the given attributes
|
17
|
+
# hash are unchanged.
|
18
|
+
#
|
19
|
+
# @param attributes [Hash] The attributes and values to update.
|
20
|
+
# @param entity [ActiveRecord::Base] The record to update.
|
21
|
+
#
|
22
|
+
# @return [ActiveRecord::Base] a copy of the record, merged with the given
|
23
|
+
# attributes.
|
24
|
+
#
|
25
|
+
# @example Assigning attributes
|
26
|
+
# entity = Book.new(
|
27
|
+
# 'title' => 'The Hobbit',
|
28
|
+
# 'author' => 'J.R.R. Tolkien',
|
29
|
+
# 'series' => nil,
|
30
|
+
# 'category' => 'Science Fiction and Fantasy'
|
31
|
+
# )
|
32
|
+
# attributes = { title: 'The Silmarillion' }
|
33
|
+
# command = Assign.new(record_class: Book)
|
34
|
+
# result = command.call(attributes: attributes, entity: entity)
|
35
|
+
# result.value.attributes
|
36
|
+
# #=> {
|
37
|
+
# 'id' => nil,
|
38
|
+
# 'title' => 'The Silmarillion',
|
39
|
+
# 'author' => 'J.R.R. Tolkien',
|
40
|
+
# 'series' => nil,
|
41
|
+
# 'category' => 'Science Fiction and Fantasy'
|
42
|
+
# }
|
43
|
+
validate_parameters :call do
|
44
|
+
keyword :attributes,
|
45
|
+
Stannum::Constraints::Types::HashWithIndifferentKeys.new
|
46
|
+
keyword :entity, Object
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def process(attributes:, entity:)
|
52
|
+
step { validate_entity(entity) }
|
53
|
+
|
54
|
+
entity.assign_attributes(attributes)
|
55
|
+
|
56
|
+
entity
|
57
|
+
rescue ActiveModel::UnknownAttributeError => exception
|
58
|
+
error = Cuprum::Collections::Errors::ExtraAttributes.new(
|
59
|
+
entity_class: record_class,
|
60
|
+
extra_attributes: [exception.attribute],
|
61
|
+
valid_attributes: record_class.attribute_names
|
62
|
+
)
|
63
|
+
failure(error)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/types/hash_with_indifferent_keys'
|
4
|
+
|
5
|
+
require 'cuprum/collections/errors/extra_attributes'
|
6
|
+
|
7
|
+
require 'cuprum/rails/command'
|
8
|
+
require 'cuprum/rails/commands'
|
9
|
+
|
10
|
+
module Cuprum::Rails::Commands
|
11
|
+
# Command for generating an ActiveRecord model from an attributes hash.
|
12
|
+
class BuildOne < Cuprum::Rails::Command
|
13
|
+
# @!method call(attributes:)
|
14
|
+
# Builds a new record with the given attributes.
|
15
|
+
#
|
16
|
+
# @param attributes [Hash] The attributes and values to assign.
|
17
|
+
#
|
18
|
+
# @return [ActiveRecord::Base] the newly built record.
|
19
|
+
#
|
20
|
+
# @example Building a record
|
21
|
+
# attributes = {
|
22
|
+
# 'title' => 'The Hobbit',
|
23
|
+
# 'author' => 'J.R.R. Tolkien',
|
24
|
+
# 'series' => nil,
|
25
|
+
# 'category' => 'Science Fiction and Fantasy'
|
26
|
+
# }
|
27
|
+
# command = Build.new(record_class: Book)
|
28
|
+
# result = command.call(attributes: attributes)
|
29
|
+
# result.value.attributes
|
30
|
+
# #=> {
|
31
|
+
# 'id' => nil,
|
32
|
+
# 'title' => 'The Silmarillion',
|
33
|
+
# 'author' => 'J.R.R. Tolkien',
|
34
|
+
# 'series' => nil,
|
35
|
+
# 'category' => 'Science Fiction and Fantasy'
|
36
|
+
# }
|
37
|
+
validate_parameters :call do
|
38
|
+
keyword :attributes,
|
39
|
+
Stannum::Constraints::Types::HashWithIndifferentKeys.new
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def process(attributes:)
|
45
|
+
record_class.new(attributes)
|
46
|
+
rescue ActiveModel::UnknownAttributeError => exception
|
47
|
+
error = Cuprum::Collections::Errors::ExtraAttributes.new(
|
48
|
+
entity_class: record_class,
|
49
|
+
extra_attributes: [exception.attribute],
|
50
|
+
valid_attributes: record_class.attribute_names
|
51
|
+
)
|
52
|
+
failure(error)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/errors/not_found'
|
4
|
+
|
5
|
+
require 'cuprum/rails/command'
|
6
|
+
require 'cuprum/rails/commands'
|
7
|
+
|
8
|
+
module Cuprum::Rails::Commands
|
9
|
+
# Command for destroying an ActiveRecord record by primary key.
|
10
|
+
class DestroyOne < Cuprum::Rails::Command
|
11
|
+
# @!method call(primary_key:)
|
12
|
+
# Finds and destroys the record with the given primary key.
|
13
|
+
#
|
14
|
+
# The command will find the record with the given primary key and remove
|
15
|
+
# it from the collection. If the record is not found, the command will
|
16
|
+
# fail and return a NotFound error.
|
17
|
+
#
|
18
|
+
# @param primary_key [Object] The primary key of the requested record.
|
19
|
+
#
|
20
|
+
# @return [Cuprum::Result<Hash{String, Object}>] a result with the
|
21
|
+
# destroyed record.
|
22
|
+
validate_parameters :call do
|
23
|
+
keyword :primary_key, Object
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def process(primary_key:)
|
29
|
+
step { validate_primary_key(primary_key) }
|
30
|
+
|
31
|
+
entity = record_class.find(primary_key)
|
32
|
+
|
33
|
+
entity.destroy
|
34
|
+
rescue ActiveRecord::RecordNotFound
|
35
|
+
error = Cuprum::Collections::Errors::NotFound.new(
|
36
|
+
collection_name: collection_name,
|
37
|
+
primary_key_name: primary_key_name,
|
38
|
+
primary_key_values: [primary_key]
|
39
|
+
)
|
40
|
+
Cuprum::Result.new(error: error)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/boolean'
|
4
|
+
|
5
|
+
require 'cuprum/collections/commands/abstract_find_many'
|
6
|
+
|
7
|
+
require 'cuprum/rails/command'
|
8
|
+
require 'cuprum/rails/commands'
|
9
|
+
|
10
|
+
module Cuprum::Rails::Commands
|
11
|
+
# Command for finding multiple ActiveRecord records by primary key.
|
12
|
+
class FindMany < Cuprum::Rails::Command
|
13
|
+
include Cuprum::Collections::Commands::AbstractFindMany
|
14
|
+
|
15
|
+
# @!method call(primary_keys:, allow_partial: false, envelope: false, scope: nil) # rubocop:disable Layout/LineLength
|
16
|
+
# Queries the collection for the records with the given primary keys.
|
17
|
+
#
|
18
|
+
# The command will find and return the entities with the given primary
|
19
|
+
# keys. If any of the records are not found, the command will fail and
|
20
|
+
# return a NotFound error. If the :allow_partial option is set, the
|
21
|
+
# command will return a partial result unless none of the requested
|
22
|
+
# records are found.
|
23
|
+
#
|
24
|
+
# When the :envelope option is true, the command wraps the records in a
|
25
|
+
# Hash, using the name of the collection as the key.
|
26
|
+
#
|
27
|
+
# @param allow_partial [Boolean] If true, passes if any of the records are
|
28
|
+
# found.
|
29
|
+
# @param envelope [Boolean] If true, wraps the result value in a Hash.
|
30
|
+
# @param primary_keys [Array] The primary keys of the requested records.
|
31
|
+
# @param scope [Cuprum::Collections::Basic::Query, nil] Optional scope for
|
32
|
+
# the query. Records must match the scope as well as the primary keys.
|
33
|
+
#
|
34
|
+
# @return [Cuprum::Result<Array<ActiveRecord>>] a result with the
|
35
|
+
# requested records.
|
36
|
+
validate_parameters :call do
|
37
|
+
keyword :allow_partial, Stannum::Constraints::Boolean.new, default: true
|
38
|
+
keyword :envelope, Stannum::Constraints::Boolean.new, default: true
|
39
|
+
keyword :primary_keys, Array
|
40
|
+
keyword :scope, Cuprum::Rails::Query, optional: true
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def build_query
|
46
|
+
Cuprum::Rails::Query.new(record_class)
|
47
|
+
end
|
48
|
+
|
49
|
+
def process(
|
50
|
+
primary_keys:,
|
51
|
+
allow_partial: false,
|
52
|
+
envelope: false,
|
53
|
+
scope: nil
|
54
|
+
)
|
55
|
+
step { validate_primary_keys(primary_keys) }
|
56
|
+
|
57
|
+
super
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/commands/abstract_find_matching'
|
4
|
+
require 'cuprum/collections/constraints/ordering'
|
5
|
+
|
6
|
+
require 'cuprum/rails/command'
|
7
|
+
require 'cuprum/rails/commands'
|
8
|
+
require 'cuprum/rails/query'
|
9
|
+
|
10
|
+
module Cuprum::Rails::Commands
|
11
|
+
# Command for querying filtered, ordered data from a Rails collection.
|
12
|
+
class FindMatching < Cuprum::Rails::Command
|
13
|
+
include Cuprum::Collections::Commands::AbstractFindMatching
|
14
|
+
|
15
|
+
# @!method call(envelope: false, limit: nil, offset: nil, order: nil, scope: nil, where: nil, &block) # rubocop:disable Layout/LineLength
|
16
|
+
# Queries the collection for records matching the given conditions.
|
17
|
+
#
|
18
|
+
# @param envelope [Boolean] If true, wraps the result value in a Hash.
|
19
|
+
# @param limit [Integer] The maximum number of results to return.
|
20
|
+
# @param offset [Integer] The initial ordered items to skip.
|
21
|
+
# @param order [Array<String, Symbol>, Hash<{String, Symbol => Symbol}>]
|
22
|
+
# The sort order of the returned items. Should be either an array of
|
23
|
+
# attribute names or a hash of attribute names and directions.
|
24
|
+
# @param scope [Cuprum::Collections::Basic::Query, nil] Optional scope for
|
25
|
+
# the query. Records must match the scope as well as the :where filters.
|
26
|
+
# @param where [Object] Additional filters for selecting data. The command
|
27
|
+
# will only return data matching these filters.
|
28
|
+
# @yield The given block is passed to a QueryBuilder, which converts the
|
29
|
+
# block to query criteria and generates a new query using those
|
30
|
+
# criteria.
|
31
|
+
# @yieldreturn [Hash] The filters to apply to the query. The hash keys
|
32
|
+
# should be the names of attributes or columns, and the corresponding
|
33
|
+
# values should be either the literal value for that attribute or a
|
34
|
+
# method call for a valid operation defined for the query.
|
35
|
+
#
|
36
|
+
# @example Querying all records in the collection.
|
37
|
+
# command = FindMatching.new(collection_name: 'books', data: books)
|
38
|
+
# command.call
|
39
|
+
# #=> an enumerable iterating all records in the collection
|
40
|
+
#
|
41
|
+
# @example Querying all records matching some critera:
|
42
|
+
# command.call { { author: 'Nnedi Okorafor' } }
|
43
|
+
# #=> an enumerable iterating all records in the collection whose author
|
44
|
+
# is 'Nnedi Okorafor'
|
45
|
+
#
|
46
|
+
# @example Ordering query results
|
47
|
+
# command.call(order: :title) { { author: 'Nnedi Okorafor' } }
|
48
|
+
# #=> an enumerable iterating all records in the collection whose author
|
49
|
+
# # is 'Nnedi Okorafor', sorted by :title in ascending order
|
50
|
+
#
|
51
|
+
# @example Advanced filtering
|
52
|
+
# command.call do
|
53
|
+
# {
|
54
|
+
# category: eq('Science Fiction and Fantasy'),
|
55
|
+
# author: ne('J.R.R. Tolkien')
|
56
|
+
# }
|
57
|
+
# end
|
58
|
+
# #=> an enumerable iterating all records in the collection whose
|
59
|
+
# # category is 'Science Fiction and Fantasy', and whose author is not
|
60
|
+
# # 'J.R.R. Tolkien'.
|
61
|
+
#
|
62
|
+
# @example Advanced ordering
|
63
|
+
# order = { author: :asc, genre: :desc }
|
64
|
+
# command.call(order: order) { { author: 'Nnedi Okorafor' } }
|
65
|
+
# #=> an enumerable iterating all records in the collection whose author
|
66
|
+
# # is 'Nnedi Okorafor', sorted first by :author in ascending order
|
67
|
+
# # and within the same author by genre in descending order
|
68
|
+
#
|
69
|
+
# @example Filtering, ordering, and subsets
|
70
|
+
# command.call(offset: 50, limit: 10, order: :author) do
|
71
|
+
# { category: 'Science Fiction and Fantasy' }
|
72
|
+
# end
|
73
|
+
# #=> an enumerable iterating the 51st through 60th records in the
|
74
|
+
# # collection whose category is 'Science Fiction and Fantasy', sorted
|
75
|
+
# # by :author in ascending order.
|
76
|
+
#
|
77
|
+
# @example Wrapping the result in an envelope
|
78
|
+
# command =
|
79
|
+
# Filter.new(collection_name: 'books', data: books)
|
80
|
+
# command.call(envelope: true)
|
81
|
+
# #=> {
|
82
|
+
# 'books' => [] # an array containing the matching records
|
83
|
+
# }
|
84
|
+
#
|
85
|
+
# @overload call(limit: nil, offset: nil, order: nil, &block)
|
86
|
+
# When the :envelope option is false (default), the command returns an
|
87
|
+
# Enumerator which can be iterated to return the matching records.
|
88
|
+
#
|
89
|
+
# @return [Cuprum::Result<Enumerator>] the matching records in the
|
90
|
+
# specified order as an Enumerator.
|
91
|
+
#
|
92
|
+
# @overload call(limit: nil, offset: nil, order: nil, &block)
|
93
|
+
# When the :envelope option is true, the command immediately evaluates
|
94
|
+
# the query and wraps the resulting array in a Hash, using the name of
|
95
|
+
# the collection as the key.
|
96
|
+
#
|
97
|
+
# @return [Cuprum::Result<Hash{String, Array<ActiveRecord::Base>}>] a
|
98
|
+
# hash with the collection name as key and the matching records as
|
99
|
+
# value.
|
100
|
+
validate_parameters :call do
|
101
|
+
keyword :envelope,
|
102
|
+
Stannum::Constraints::Boolean.new,
|
103
|
+
default: true
|
104
|
+
keyword :limit, Integer, optional: true
|
105
|
+
keyword :offset, Integer, optional: true
|
106
|
+
keyword :order,
|
107
|
+
Cuprum::Collections::Constraints::Ordering.new,
|
108
|
+
optional: true
|
109
|
+
keyword :scope,
|
110
|
+
Cuprum::Rails::Query,
|
111
|
+
optional: true
|
112
|
+
keyword :where, Object, optional: true
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def build_query
|
118
|
+
Cuprum::Rails::Query.new(record_class)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'stannum/constraints/boolean'
|
4
|
+
|
5
|
+
require 'cuprum/collections/commands/abstract_find_one'
|
6
|
+
|
7
|
+
require 'cuprum/rails/command'
|
8
|
+
require 'cuprum/rails/commands'
|
9
|
+
|
10
|
+
module Cuprum::Rails::Commands
|
11
|
+
# Command for finding one ActiveRecord record by primary key.
|
12
|
+
class FindOne < Cuprum::Rails::Command
|
13
|
+
include Cuprum::Collections::Commands::AbstractFindOne
|
14
|
+
|
15
|
+
# @!method call(primary_key:, envelope: false, scope: nil)
|
16
|
+
# Queries the collection for the record with the given primary key.
|
17
|
+
#
|
18
|
+
# The command will find and return the entity with the given primary key.
|
19
|
+
# If the entity is not found, the command will fail and return a NotFound
|
20
|
+
# error.
|
21
|
+
#
|
22
|
+
# When the :envelope option is true, the command wraps the record in a
|
23
|
+
# Hash, using the singular name of the collection as the key.
|
24
|
+
#
|
25
|
+
# @param envelope [Boolean] If true, wraps the result value in a Hash.
|
26
|
+
# @param primary_key [Object] The primary key of the requested record.
|
27
|
+
# @param scope [Cuprum::Collections::Basic::Query, nil] Optional scope for
|
28
|
+
# the query. The record must match the scope as well as the primary key.
|
29
|
+
#
|
30
|
+
# @return [Cuprum::Result<Hash{String, Object}>] a result with the
|
31
|
+
# requested record.
|
32
|
+
validate_parameters :call do
|
33
|
+
keyword :envelope, Stannum::Constraints::Boolean.new, default: true
|
34
|
+
keyword :primary_key, Object
|
35
|
+
keyword :scope, Cuprum::Rails::Query, optional: true
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def build_query
|
41
|
+
Cuprum::Rails::Query.new(record_class)
|
42
|
+
end
|
43
|
+
|
44
|
+
def process(primary_key:, envelope: false, scope: nil)
|
45
|
+
step { validate_primary_key(primary_key) }
|
46
|
+
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/errors/already_exists'
|
4
|
+
|
5
|
+
require 'cuprum/rails/command'
|
6
|
+
require 'cuprum/rails/commands'
|
7
|
+
|
8
|
+
module Cuprum::Rails::Commands
|
9
|
+
# Command for inserting an ActiveRecord record into the collection.
|
10
|
+
class InsertOne < Cuprum::Rails::Command
|
11
|
+
# @!method call(entity:)
|
12
|
+
# Inserts the record into the collection.
|
13
|
+
#
|
14
|
+
# If the collection already includes a record with the same primary key,
|
15
|
+
# #call will fail and the collection will not be updated.
|
16
|
+
#
|
17
|
+
# @param entity [ActiveRecord::Base] The record to persist.
|
18
|
+
#
|
19
|
+
# @return [Cuprum::Result<ActiveRecord::Base>] the persisted record.
|
20
|
+
validate_parameters :call do
|
21
|
+
keyword :entity, Object
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def process(entity:)
|
27
|
+
step { validate_entity(entity) }
|
28
|
+
|
29
|
+
entity.save
|
30
|
+
|
31
|
+
entity
|
32
|
+
rescue ActiveRecord::RecordNotUnique
|
33
|
+
error = Cuprum::Collections::Errors::AlreadyExists.new(
|
34
|
+
collection_name: collection_name,
|
35
|
+
primary_key_name: primary_key_name,
|
36
|
+
primary_key_values: entity[primary_key_name]
|
37
|
+
)
|
38
|
+
failure(error)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/errors/not_found'
|
4
|
+
|
5
|
+
require 'cuprum/rails/command'
|
6
|
+
require 'cuprum/rails/commands'
|
7
|
+
|
8
|
+
module Cuprum::Rails::Commands
|
9
|
+
# Command for updating an ActiveRecord record in the collection.
|
10
|
+
class UpdateOne < Cuprum::Rails::Command
|
11
|
+
# @!method call(entity:)
|
12
|
+
# Updates the record in the collection.
|
13
|
+
#
|
14
|
+
# If the collection does not already have a record with the same primary
|
15
|
+
# key, #call will fail and the collection will not be updated.
|
16
|
+
#
|
17
|
+
# @param entity [ActiveRecord::Base] The collection record to persist.
|
18
|
+
#
|
19
|
+
# @return [Cuprum::Result<ActiveRecord::Base>] the persisted record.
|
20
|
+
validate_parameters :call do
|
21
|
+
keyword :entity, Object
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def handle_missing_record(primary_key:)
|
27
|
+
query = record_class.where(primary_key_name => primary_key)
|
28
|
+
|
29
|
+
return if query.exists?
|
30
|
+
|
31
|
+
error = Cuprum::Collections::Errors::NotFound.new(
|
32
|
+
collection_name: collection_name,
|
33
|
+
primary_key_name: primary_key_name,
|
34
|
+
primary_key_values: primary_key
|
35
|
+
)
|
36
|
+
failure(error)
|
37
|
+
end
|
38
|
+
|
39
|
+
def process(entity:)
|
40
|
+
step { validate_entity(entity) }
|
41
|
+
|
42
|
+
step { handle_missing_record(primary_key: entity[primary_key_name]) }
|
43
|
+
|
44
|
+
entity.save
|
45
|
+
|
46
|
+
entity
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/errors/failed_validation'
|
4
|
+
|
5
|
+
require 'cuprum/rails/command'
|
6
|
+
require 'cuprum/rails/commands'
|
7
|
+
require 'cuprum/rails/map_errors'
|
8
|
+
|
9
|
+
module Cuprum::Rails::Commands
|
10
|
+
# Command for validating an ActiveRecord record.
|
11
|
+
class ValidateOne < Cuprum::Rails::Command
|
12
|
+
# @!method call(entity:, contract: nil)
|
13
|
+
# Validates the record against the given or default contract.
|
14
|
+
#
|
15
|
+
# If the record matches the contract, #call will return a passing result
|
16
|
+
# with the record as the result value. If the record does not match the
|
17
|
+
# contract, #call will return a failing result with a FailedValidation
|
18
|
+
# error and the validation errors.
|
19
|
+
#
|
20
|
+
# @param contract [Stannum::Constraints:Base] The contract with which to
|
21
|
+
# validate the record. If not given, the record will be validated using
|
22
|
+
# the collection's default contract.
|
23
|
+
# @param entity [ActiveRecord::Base] The collection record to validate.
|
24
|
+
#
|
25
|
+
# @return [Cuprum::Result<ActiveRecord::Base>] the validated record.
|
26
|
+
validate_parameters :call do
|
27
|
+
keyword :contract,
|
28
|
+
Stannum::Constraints::Base,
|
29
|
+
optional: true
|
30
|
+
keyword :entity, Object
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def map_errors(native_errors:)
|
36
|
+
Cuprum::Rails::MapErrors.instance.call(native_errors: native_errors)
|
37
|
+
end
|
38
|
+
|
39
|
+
def match_default(entity:)
|
40
|
+
return true if entity.valid?
|
41
|
+
|
42
|
+
errors = map_errors(native_errors: entity.errors)
|
43
|
+
|
44
|
+
[false, errors]
|
45
|
+
end
|
46
|
+
|
47
|
+
def process(entity:, contract: nil)
|
48
|
+
step { validate_entity(entity) }
|
49
|
+
|
50
|
+
step { validate_record(contract: contract, entity: entity) }
|
51
|
+
|
52
|
+
entity
|
53
|
+
end
|
54
|
+
|
55
|
+
def validate_record(contract:, entity:)
|
56
|
+
valid, errors =
|
57
|
+
contract ? contract.match(entity) : match_default(entity: entity)
|
58
|
+
|
59
|
+
return if valid
|
60
|
+
|
61
|
+
error = Cuprum::Collections::Errors::FailedValidation.new(
|
62
|
+
entity_class: entity.class,
|
63
|
+
errors: errors
|
64
|
+
)
|
65
|
+
failure(error)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails'
|
4
|
+
|
5
|
+
module Cuprum::Rails
|
6
|
+
# Namespace for commands implementing Rails collection functionality.
|
7
|
+
module Commands
|
8
|
+
autoload :AssignOne, 'cuprum/rails/commands/assign_one'
|
9
|
+
autoload :BuildOne, 'cuprum/rails/commands/build_one'
|
10
|
+
autoload :DestroyOne, 'cuprum/rails/commands/destroy_one'
|
11
|
+
autoload :FindMany, 'cuprum/rails/commands/find_many'
|
12
|
+
autoload :FindMatching, 'cuprum/rails/commands/find_matching'
|
13
|
+
autoload :FindOne, 'cuprum/rails/commands/find_one'
|
14
|
+
autoload :InsertOne, 'cuprum/rails/commands/insert_one'
|
15
|
+
autoload :UpdateOne, 'cuprum/rails/commands/update_one'
|
16
|
+
autoload :ValidateOne, 'cuprum/rails/commands/validate_one'
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails'
|
4
|
+
require 'cuprum/rails/controller_action'
|
5
|
+
require 'cuprum/rails/controllers/class_methods/configuration'
|
6
|
+
require 'cuprum/rails/controllers/class_methods/validations'
|
7
|
+
|
8
|
+
module Cuprum::Rails
|
9
|
+
# Provides a DSL for defining actions and responses.
|
10
|
+
#
|
11
|
+
# @example Defining A Controller
|
12
|
+
# class ExampleController < ApplicationController
|
13
|
+
# include Cuprum::Rails::Controller
|
14
|
+
#
|
15
|
+
# responder :html, CustomHtmlResponder
|
16
|
+
#
|
17
|
+
# action :process, ExampleProcessAction
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# @example Defining A RESTful Controller
|
21
|
+
# class BooksController
|
22
|
+
# include Cuprum::Rails::Controller
|
23
|
+
#
|
24
|
+
# responder :html, Cuprum::Rails::Responders::Html::PluralResource
|
25
|
+
#
|
26
|
+
# action :index, Cuprum::Rails::Actions::Index
|
27
|
+
# action :show, Cuprum::Rails::Actions::Show, member: true
|
28
|
+
# action :published, Books::Published
|
29
|
+
# action :publish, Books::Publish, member: true
|
30
|
+
# end
|
31
|
+
module Controller
|
32
|
+
# Exception when the controller does not define a resource.
|
33
|
+
class UndefinedResourceError < StandardError; end
|
34
|
+
|
35
|
+
# Exception when the controller does not have a responder for a format.
|
36
|
+
class UnknownFormatError < StandardError; end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
private
|
40
|
+
|
41
|
+
def included(other)
|
42
|
+
super
|
43
|
+
|
44
|
+
other.extend(Cuprum::Rails::Controllers::ClassMethods::Actions)
|
45
|
+
other.extend(Cuprum::Rails::Controllers::ClassMethods::Configuration)
|
46
|
+
other.extend(Cuprum::Rails::Controllers::ClassMethods::Validations)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|