cuprum-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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