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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +98 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/DEVELOPMENT.md +28 -0
  5. data/LICENSE +22 -0
  6. data/README.md +1045 -0
  7. data/lib/cuprum/rails/action.rb +45 -0
  8. data/lib/cuprum/rails/actions/create.rb +49 -0
  9. data/lib/cuprum/rails/actions/destroy.rb +22 -0
  10. data/lib/cuprum/rails/actions/edit.rb +22 -0
  11. data/lib/cuprum/rails/actions/index.rb +55 -0
  12. data/lib/cuprum/rails/actions/new.rb +19 -0
  13. data/lib/cuprum/rails/actions/resource_action.rb +75 -0
  14. data/lib/cuprum/rails/actions/show.rb +22 -0
  15. data/lib/cuprum/rails/actions/update.rb +59 -0
  16. data/lib/cuprum/rails/actions.rb +16 -0
  17. data/lib/cuprum/rails/collection.rb +115 -0
  18. data/lib/cuprum/rails/command.rb +137 -0
  19. data/lib/cuprum/rails/commands/assign_one.rb +66 -0
  20. data/lib/cuprum/rails/commands/build_one.rb +55 -0
  21. data/lib/cuprum/rails/commands/destroy_one.rb +43 -0
  22. data/lib/cuprum/rails/commands/find_many.rb +60 -0
  23. data/lib/cuprum/rails/commands/find_matching.rb +121 -0
  24. data/lib/cuprum/rails/commands/find_one.rb +50 -0
  25. data/lib/cuprum/rails/commands/insert_one.rb +41 -0
  26. data/lib/cuprum/rails/commands/update_one.rb +49 -0
  27. data/lib/cuprum/rails/commands/validate_one.rb +68 -0
  28. data/lib/cuprum/rails/commands.rb +18 -0
  29. data/lib/cuprum/rails/controller.rb +50 -0
  30. data/lib/cuprum/rails/controller_action.rb +121 -0
  31. data/lib/cuprum/rails/controllers/class_methods/actions.rb +57 -0
  32. data/lib/cuprum/rails/controllers/class_methods/configuration.rb +64 -0
  33. data/lib/cuprum/rails/controllers/class_methods/validations.rb +30 -0
  34. data/lib/cuprum/rails/controllers/class_methods.rb +15 -0
  35. data/lib/cuprum/rails/controllers/configuration.rb +53 -0
  36. data/lib/cuprum/rails/controllers.rb +10 -0
  37. data/lib/cuprum/rails/errors/missing_parameters.rb +33 -0
  38. data/lib/cuprum/rails/errors/missing_primary_key.rb +46 -0
  39. data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +34 -0
  40. data/lib/cuprum/rails/errors.rb +8 -0
  41. data/lib/cuprum/rails/map_errors.rb +44 -0
  42. data/lib/cuprum/rails/query.rb +77 -0
  43. data/lib/cuprum/rails/query_builder.rb +78 -0
  44. data/lib/cuprum/rails/repository.rb +44 -0
  45. data/lib/cuprum/rails/request.rb +105 -0
  46. data/lib/cuprum/rails/resource.rb +145 -0
  47. data/lib/cuprum/rails/responders/actions.rb +73 -0
  48. data/lib/cuprum/rails/responders/html/plural_resource.rb +62 -0
  49. data/lib/cuprum/rails/responders/html/singular_resource.rb +59 -0
  50. data/lib/cuprum/rails/responders/html.rb +11 -0
  51. data/lib/cuprum/rails/responders/html_responder.rb +129 -0
  52. data/lib/cuprum/rails/responders/json/resource.rb +60 -0
  53. data/lib/cuprum/rails/responders/json.rb +10 -0
  54. data/lib/cuprum/rails/responders/json_responder.rb +122 -0
  55. data/lib/cuprum/rails/responders/matching.rb +145 -0
  56. data/lib/cuprum/rails/responders/serialization.rb +36 -0
  57. data/lib/cuprum/rails/responders.rb +15 -0
  58. data/lib/cuprum/rails/responses/html/redirect_response.rb +29 -0
  59. data/lib/cuprum/rails/responses/html/render_response.rb +52 -0
  60. data/lib/cuprum/rails/responses/html.rb +11 -0
  61. data/lib/cuprum/rails/responses/json_response.rb +29 -0
  62. data/lib/cuprum/rails/responses.rb +11 -0
  63. data/lib/cuprum/rails/routes.rb +166 -0
  64. data/lib/cuprum/rails/routing/plural_routes.rb +26 -0
  65. data/lib/cuprum/rails/routing/singular_routes.rb +24 -0
  66. data/lib/cuprum/rails/routing.rb +11 -0
  67. data/lib/cuprum/rails/rspec/command_contract.rb +460 -0
  68. data/lib/cuprum/rails/rspec/define_route_contract.rb +84 -0
  69. data/lib/cuprum/rails/rspec.rb +8 -0
  70. data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +24 -0
  71. data/lib/cuprum/rails/serializers/json/array_serializer.rb +40 -0
  72. data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +217 -0
  73. data/lib/cuprum/rails/serializers/json/error_serializer.rb +24 -0
  74. data/lib/cuprum/rails/serializers/json/hash_serializer.rb +44 -0
  75. data/lib/cuprum/rails/serializers/json/identity_serializer.rb +21 -0
  76. data/lib/cuprum/rails/serializers/json/serializer.rb +66 -0
  77. data/lib/cuprum/rails/serializers/json.rb +40 -0
  78. data/lib/cuprum/rails/serializers.rb +10 -0
  79. data/lib/cuprum/rails/version.rb +59 -0
  80. data/lib/cuprum/rails.rb +31 -0
  81. 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