cuprum-rails 0.1.0 → 0.2.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 +145 -0
- data/DEVELOPMENT.md +20 -0
- data/README.md +356 -63
- data/lib/cuprum/rails/action.rb +32 -16
- data/lib/cuprum/rails/actions/create.rb +62 -15
- data/lib/cuprum/rails/actions/destroy.rb +23 -7
- data/lib/cuprum/rails/actions/edit.rb +23 -7
- data/lib/cuprum/rails/actions/index.rb +30 -10
- data/lib/cuprum/rails/actions/middleware/associations/cache.rb +112 -0
- data/lib/cuprum/rails/actions/middleware/associations/find.rb +23 -0
- data/lib/cuprum/rails/actions/middleware/associations/parent.rb +70 -0
- data/lib/cuprum/rails/actions/middleware/associations/query.rb +140 -0
- data/lib/cuprum/rails/actions/middleware/associations.rb +12 -0
- data/lib/cuprum/rails/actions/middleware/log_request.rb +126 -0
- data/lib/cuprum/rails/actions/middleware/log_result.rb +51 -0
- data/lib/cuprum/rails/actions/middleware/resources/find.rb +44 -0
- data/lib/cuprum/rails/actions/middleware/resources/query.rb +91 -0
- data/lib/cuprum/rails/actions/middleware/resources.rb +11 -0
- data/lib/cuprum/rails/actions/middleware.rb +13 -0
- data/lib/cuprum/rails/actions/new.rb +16 -4
- data/lib/cuprum/rails/actions/parameter_validation.rb +60 -0
- data/lib/cuprum/rails/actions/resource_action.rb +119 -42
- data/lib/cuprum/rails/actions/show.rb +23 -7
- data/lib/cuprum/rails/actions/update.rb +70 -22
- data/lib/cuprum/rails/actions.rb +11 -7
- data/lib/cuprum/rails/collection.rb +27 -47
- data/lib/cuprum/rails/command.rb +3 -1
- data/lib/cuprum/rails/commands/destroy_one.rb +10 -6
- data/lib/cuprum/rails/commands/find_many.rb +8 -1
- data/lib/cuprum/rails/commands/find_matching.rb +1 -1
- data/lib/cuprum/rails/commands/find_one.rb +8 -0
- data/lib/cuprum/rails/commands/insert_one.rb +17 -6
- data/lib/cuprum/rails/commands/update_one.rb +16 -5
- data/lib/cuprum/rails/constraints/parameters_contract.rb +14 -0
- data/lib/cuprum/rails/constraints.rb +10 -0
- data/lib/cuprum/rails/controller.rb +12 -2
- data/lib/cuprum/rails/controllers/action.rb +100 -0
- data/lib/cuprum/rails/controllers/class_methods/actions.rb +33 -7
- data/lib/cuprum/rails/controllers/class_methods/configuration.rb +36 -0
- data/lib/cuprum/rails/controllers/class_methods/middleware.rb +88 -0
- data/lib/cuprum/rails/controllers/class_methods/validations.rb +2 -2
- data/lib/cuprum/rails/controllers/configuration.rb +41 -1
- data/lib/cuprum/rails/controllers/middleware.rb +59 -0
- data/lib/cuprum/rails/controllers.rb +2 -0
- data/lib/cuprum/rails/errors/invalid_parameters.rb +55 -0
- data/lib/cuprum/rails/errors/invalid_statement.rb +11 -0
- data/lib/cuprum/rails/errors/missing_parameter.rb +42 -0
- data/lib/cuprum/rails/errors/resource_error.rb +46 -0
- data/lib/cuprum/rails/errors.rb +6 -1
- data/lib/cuprum/rails/map_errors.rb +29 -1
- data/lib/cuprum/rails/query.rb +1 -1
- data/lib/cuprum/rails/repository.rb +12 -25
- data/lib/cuprum/rails/request.rb +149 -60
- data/lib/cuprum/rails/resource.rb +119 -85
- data/lib/cuprum/rails/responders/base_responder.rb +78 -0
- data/lib/cuprum/rails/responders/html/plural_resource.rb +9 -39
- data/lib/cuprum/rails/responders/html/rendering.rb +81 -0
- data/lib/cuprum/rails/responders/html/resource.rb +107 -0
- data/lib/cuprum/rails/responders/html/singular_resource.rb +9 -38
- data/lib/cuprum/rails/responders/html.rb +2 -0
- data/lib/cuprum/rails/responders/html_responder.rb +8 -52
- data/lib/cuprum/rails/responders/json/resource.rb +3 -3
- data/lib/cuprum/rails/responders/json_responder.rb +31 -16
- data/lib/cuprum/rails/responders/matching.rb +29 -27
- data/lib/cuprum/rails/responders/serialization.rb +11 -9
- data/lib/cuprum/rails/responders.rb +1 -0
- data/lib/cuprum/rails/responses/head_response.rb +24 -0
- data/lib/cuprum/rails/responses/html/redirect_back_response.rb +55 -0
- data/lib/cuprum/rails/responses/html/redirect_response.rb +19 -4
- data/lib/cuprum/rails/responses/html/render_response.rb +17 -5
- data/lib/cuprum/rails/responses/html.rb +6 -2
- data/lib/cuprum/rails/responses.rb +1 -0
- data/lib/cuprum/rails/result.rb +36 -0
- data/lib/cuprum/rails/routes.rb +36 -23
- data/lib/cuprum/rails/rspec/contract_helpers.rb +57 -0
- data/lib/cuprum/rails/rspec/contracts/action_contracts.rb +754 -0
- data/lib/cuprum/rails/rspec/contracts/actions/create_contracts.rb +289 -0
- data/lib/cuprum/rails/rspec/contracts/actions/destroy_contracts.rb +164 -0
- data/lib/cuprum/rails/rspec/contracts/actions/edit_contracts.rb +73 -0
- data/lib/cuprum/rails/rspec/contracts/actions/index_contracts.rb +108 -0
- data/lib/cuprum/rails/rspec/contracts/actions/new_contracts.rb +111 -0
- data/lib/cuprum/rails/rspec/contracts/actions/show_contracts.rb +72 -0
- data/lib/cuprum/rails/rspec/contracts/actions/update_contracts.rb +263 -0
- data/lib/cuprum/rails/rspec/contracts/actions.rb +8 -0
- data/lib/cuprum/rails/rspec/contracts/command_contracts.rb +479 -0
- data/lib/cuprum/rails/rspec/contracts/responder_contracts.rb +232 -0
- data/lib/cuprum/rails/rspec/contracts/routes_contracts.rb +363 -0
- data/lib/cuprum/rails/rspec/contracts/serializers_contracts.rb +70 -0
- data/lib/cuprum/rails/rspec/contracts.rb +8 -0
- data/lib/cuprum/rails/rspec/matchers/be_a_result_matcher.rb +64 -0
- data/lib/cuprum/rails/rspec/matchers.rb +41 -0
- data/lib/cuprum/rails/serializers/base_serializer.rb +60 -0
- data/lib/cuprum/rails/serializers/context.rb +84 -0
- data/lib/cuprum/rails/serializers/json/active_record_serializer.rb +2 -2
- data/lib/cuprum/rails/serializers/json/array_serializer.rb +9 -8
- data/lib/cuprum/rails/serializers/json/attributes_serializer.rb +95 -172
- data/lib/cuprum/rails/serializers/json/error_serializer.rb +2 -2
- data/lib/cuprum/rails/serializers/json/hash_serializer.rb +9 -8
- data/lib/cuprum/rails/serializers/json/identity_serializer.rb +3 -3
- data/lib/cuprum/rails/serializers/json/properties_serializer.rb +252 -0
- data/lib/cuprum/rails/serializers/json.rb +2 -1
- data/lib/cuprum/rails/serializers.rb +3 -1
- data/lib/cuprum/rails/version.rb +1 -1
- data/lib/cuprum/rails.rb +19 -16
- metadata +73 -131
- data/lib/cuprum/rails/controller_action.rb +0 -121
- data/lib/cuprum/rails/errors/missing_parameters.rb +0 -33
- data/lib/cuprum/rails/errors/missing_primary_key.rb +0 -46
- data/lib/cuprum/rails/errors/undefined_permitted_attributes.rb +0 -34
- data/lib/cuprum/rails/rspec/command_contract.rb +0 -460
- data/lib/cuprum/rails/rspec/define_route_contract.rb +0 -84
- data/lib/cuprum/rails/serializers/json/serializer.rb +0 -66
data/lib/cuprum/rails/action.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'forwardable'
|
4
|
+
|
3
5
|
require 'cuprum/command'
|
4
6
|
|
5
7
|
require 'cuprum/rails'
|
@@ -7,37 +9,51 @@ require 'cuprum/rails'
|
|
7
9
|
module Cuprum::Rails
|
8
10
|
# Abstract command that implement a controller action.
|
9
11
|
class Action < Cuprum::Command
|
10
|
-
|
11
|
-
def initialize(resource:)
|
12
|
-
super()
|
13
|
-
|
14
|
-
@resource = resource
|
15
|
-
end
|
12
|
+
extend Forwardable
|
16
13
|
|
17
|
-
# @!method call(request:)
|
14
|
+
# @!method call(request:, repository: nil, **options)
|
18
15
|
# Performs the controller action.
|
19
16
|
#
|
20
17
|
# Subclasses should implement a #process method with the :request keyword,
|
21
18
|
# which accepts an ActionDispatch::Request instance.
|
22
19
|
#
|
23
|
-
# @param request [ActionDispatch::Request]
|
20
|
+
# @param request [ActionDispatch::Request] the Rails request.
|
21
|
+
# @param repository [Cuprum::Collections::Repository] the repository
|
22
|
+
# containing the data collections for the application or scope.
|
23
|
+
# @param options [Hash<Symbol, Object>] additional options for the action.
|
24
24
|
#
|
25
25
|
# @return [Cuprum::Result] the result of the action.
|
26
26
|
|
27
|
-
#
|
28
|
-
|
27
|
+
# @!method params
|
28
|
+
# @return [Hash<String, Object>] the request parameters.
|
29
|
+
def_delegators :@request, :params
|
29
30
|
|
30
|
-
|
31
|
+
# @return [Hash<Symbol, Object>] additional options for the action.
|
32
|
+
attr_reader :options
|
31
33
|
|
34
|
+
# @return [Cuprum::Collections::Repository] the repository containing the
|
35
|
+
# data collections for the application or scope.
|
36
|
+
attr_reader :repository
|
37
|
+
|
38
|
+
# @return [Cuprum::Rails::Request] the formatted request.
|
32
39
|
attr_reader :request
|
33
40
|
|
34
|
-
|
35
|
-
|
41
|
+
private
|
42
|
+
|
43
|
+
def build_result(error: nil, metadata: nil, status: nil, value: nil)
|
44
|
+
Cuprum::Rails::Result.new(
|
45
|
+
error: error,
|
46
|
+
metadata: metadata,
|
47
|
+
status: status,
|
48
|
+
value: value
|
49
|
+
)
|
36
50
|
end
|
37
51
|
|
38
|
-
def process(request:)
|
39
|
-
@params
|
40
|
-
@
|
52
|
+
def process(request:, repository: nil, **options)
|
53
|
+
@params = nil
|
54
|
+
@repository = repository
|
55
|
+
@request = request
|
56
|
+
@options = options
|
41
57
|
|
42
58
|
nil
|
43
59
|
end
|
@@ -10,21 +10,20 @@ module Cuprum::Rails::Actions
|
|
10
10
|
class Create < Cuprum::Rails::Actions::ResourceAction
|
11
11
|
private
|
12
12
|
|
13
|
-
|
14
|
-
entity = nil
|
13
|
+
attr_reader :entity
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def build_response
|
16
|
+
{ resource.singular_name => entity }
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_entity(attributes:)
|
20
|
+
steps do
|
21
|
+
@entity = step { collection.build_one.call(attributes: attributes) }
|
19
22
|
|
20
23
|
step { collection.validate_one.call(entity: entity) }
|
21
24
|
|
22
25
|
step { collection.insert_one.call(entity: entity) }
|
23
|
-
|
24
|
-
{ singular_resource_name => entity }
|
25
26
|
end
|
26
|
-
|
27
|
-
[entity, result]
|
28
27
|
end
|
29
28
|
|
30
29
|
def failed_validation?(result)
|
@@ -32,17 +31,65 @@ module Cuprum::Rails::Actions
|
|
32
31
|
result.error.is_a?(Cuprum::Collections::Errors::FailedValidation)
|
33
32
|
end
|
34
33
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
entity, result = create_resource
|
34
|
+
def handle_failed_validation
|
35
|
+
result = yield
|
39
36
|
|
40
37
|
return result unless failed_validation?(result)
|
41
38
|
|
42
39
|
Cuprum::Result.new(
|
43
|
-
error: result.error,
|
40
|
+
error: scope_validation_errors(result.error),
|
44
41
|
status: :failure,
|
45
|
-
value: {
|
42
|
+
value: { resource.singular_name => entity }
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def parameters_contract
|
47
|
+
return @parameters_contract if @parameters_contract
|
48
|
+
|
49
|
+
resource_name = resource.singular_name
|
50
|
+
parameters_constraint = require_parameters_constraint
|
51
|
+
|
52
|
+
@parameters_contract =
|
53
|
+
Cuprum::Rails::Constraints::ParametersContract.new do
|
54
|
+
key resource_name, parameters_constraint
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def perform_action
|
59
|
+
handle_failed_validation do
|
60
|
+
create_entity(attributes: resource_params)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def process(**)
|
65
|
+
@entity = nil
|
66
|
+
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
def require_parameters_constraint
|
71
|
+
Stannum::Contract.new do
|
72
|
+
constraint Stannum::Constraints::Presence.new, sanity: true
|
73
|
+
constraint Stannum::Constraints::Types::HashType.new
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def require_permitted_attributes?
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
def scope_validation_errors(error)
|
82
|
+
mapped_errors = Stannum::Errors.new
|
83
|
+
|
84
|
+
error.errors.each do |err|
|
85
|
+
mapped_errors
|
86
|
+
.dig(resource.singular_name, *err[:path].map(&:to_s))
|
87
|
+
.add(err[:type], message: err[:message], **err[:data])
|
88
|
+
end
|
89
|
+
|
90
|
+
Cuprum::Collections::Errors::FailedValidation.new(
|
91
|
+
entity_class: error.entity_class,
|
92
|
+
errors: mapped_errors
|
46
93
|
)
|
47
94
|
end
|
48
95
|
end
|
@@ -8,15 +8,31 @@ module Cuprum::Rails::Actions
|
|
8
8
|
class Destroy < Cuprum::Rails::Actions::ResourceAction
|
9
9
|
private
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
attr_reader :entity
|
12
|
+
|
13
|
+
def build_response
|
14
|
+
{ resource.singular_name => entity }
|
15
|
+
end
|
16
|
+
|
17
|
+
def destroy_entity(primary_key:)
|
18
|
+
collection.destroy_one.call(primary_key: primary_key)
|
19
|
+
end
|
13
20
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
21
|
+
def parameters_contract
|
22
|
+
@parameters_contract ||=
|
23
|
+
Cuprum::Rails::Constraints::ParametersContract.new do
|
24
|
+
key 'id', Stannum::Constraints::Presence.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform_action
|
29
|
+
@entity = step { destroy_entity(primary_key: resource_id) }
|
30
|
+
end
|
18
31
|
|
19
|
-
|
32
|
+
def process(**)
|
33
|
+
@entity = nil
|
34
|
+
|
35
|
+
super
|
20
36
|
end
|
21
37
|
end
|
22
38
|
end
|
@@ -8,15 +8,31 @@ module Cuprum::Rails::Actions
|
|
8
8
|
class Edit < Cuprum::Rails::Actions::ResourceAction
|
9
9
|
private
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
attr_reader :entity
|
12
|
+
|
13
|
+
def build_response
|
14
|
+
{ resource.singular_name => entity }
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_entity(primary_key:)
|
18
|
+
collection.find_one.call(primary_key: primary_key)
|
19
|
+
end
|
13
20
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
21
|
+
def parameters_contract
|
22
|
+
@parameters_contract ||=
|
23
|
+
Cuprum::Rails::Constraints::ParametersContract.new do
|
24
|
+
key 'id', Stannum::Constraints::Presence.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def perform_action
|
29
|
+
@entity = step { find_entity(primary_key: resource_id) }
|
30
|
+
end
|
18
31
|
|
19
|
-
|
32
|
+
def process(**)
|
33
|
+
@entity = nil
|
34
|
+
|
35
|
+
super
|
20
36
|
end
|
21
37
|
end
|
22
38
|
end
|
@@ -11,12 +11,27 @@ module Cuprum::Rails::Actions
|
|
11
11
|
|
12
12
|
private
|
13
13
|
|
14
|
+
attr_reader :entities
|
15
|
+
|
16
|
+
def build_response
|
17
|
+
{ resource.name => entities.to_a }
|
18
|
+
end
|
19
|
+
|
14
20
|
# @note Overload this method to change how the filtering params are defined,
|
15
21
|
# or override the #limit, #offset, #order, #where methods directly.
|
16
22
|
def filter_params
|
17
23
|
tools.hash_tools.convert_keys_to_strings(request.params)
|
18
24
|
end
|
19
25
|
|
26
|
+
def find_entities(limit:, offset:, order:, &block)
|
27
|
+
collection.find_matching.call(
|
28
|
+
limit: limit,
|
29
|
+
offset: offset,
|
30
|
+
order: order,
|
31
|
+
&block
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
20
35
|
def limit
|
21
36
|
filter_params['limit']
|
22
37
|
end
|
@@ -29,19 +44,24 @@ module Cuprum::Rails::Actions
|
|
29
44
|
filter_params.fetch('order', default_order.presence)
|
30
45
|
end
|
31
46
|
|
32
|
-
def
|
33
|
-
super
|
34
|
-
|
47
|
+
def perform_action
|
35
48
|
filters = where
|
36
49
|
block = where.present? ? -> { filters } : nil
|
37
50
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
51
|
+
@entities = step do
|
52
|
+
find_entities(
|
53
|
+
limit: limit,
|
54
|
+
offset: offset,
|
55
|
+
order: order,
|
56
|
+
&block
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def process(**)
|
62
|
+
@entities = nil
|
63
|
+
|
64
|
+
super
|
45
65
|
end
|
46
66
|
|
47
67
|
def tools
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/actions/middleware/associations'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Actions::Middleware::Associations
|
6
|
+
# Pre-warm the association cache for a resource.
|
7
|
+
class Cache < Cuprum::Command
|
8
|
+
# Strategy for caching an association value on an ActiveRecord model.
|
9
|
+
ACTIVE_RECORD_STRATEGY = lambda do |entity:, name:, value:|
|
10
|
+
entity.send(:association_instance_set, name, value)
|
11
|
+
|
12
|
+
entity
|
13
|
+
end
|
14
|
+
|
15
|
+
# Generic strategy for caching an association value.
|
16
|
+
DEFAULT_STRATEGY = lambda do |entity:, name:, value:|
|
17
|
+
entity[name] = value
|
18
|
+
|
19
|
+
entity
|
20
|
+
end
|
21
|
+
|
22
|
+
STRATEGIES = {
|
23
|
+
Object => DEFAULT_STRATEGY,
|
24
|
+
ActiveRecord::Base => ACTIVE_RECORD_STRATEGY
|
25
|
+
}.freeze
|
26
|
+
private_constant :STRATEGIES
|
27
|
+
|
28
|
+
class << self
|
29
|
+
# Defines a strategy for caching an association value.
|
30
|
+
#
|
31
|
+
# @param klass [Class] the base class or module for matching entities.
|
32
|
+
#
|
33
|
+
# @yield the strategy for caching the association value.
|
34
|
+
#
|
35
|
+
# @yieldparam entity [Object] the base entity.
|
36
|
+
# @yieldparam name [String] the name of the association.
|
37
|
+
# @yieldparam value [Object] the associated entity to cache.
|
38
|
+
#
|
39
|
+
# @yieldreturn [Object] the entity with cached assocation value.
|
40
|
+
def define_strategy(klass, &block)
|
41
|
+
(@strategies ||= STRATEGIES.dup)[klass] = block
|
42
|
+
end
|
43
|
+
|
44
|
+
# @return [Proc] the defined strategies for caching association values.
|
45
|
+
def strategies
|
46
|
+
(@strategies ||= STRATEGIES.dup).reverse_each
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param association [Cuprum::Associations::Association] the association
|
51
|
+
# to cache.
|
52
|
+
# @param resource [Cuprum::Rails::Resource] the resource to cache.
|
53
|
+
def initialize(association:, resource:)
|
54
|
+
super()
|
55
|
+
|
56
|
+
@association = association
|
57
|
+
@resource = resource
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Cuprum::Associations::Association] the association to cache.
|
61
|
+
attr_reader :association
|
62
|
+
|
63
|
+
# @return [Cuprum::Rails::Resource] the resource to cache.
|
64
|
+
attr_reader :resource
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def cache_association(entity:, value:)
|
69
|
+
value = value.first if association.singular?
|
70
|
+
strategy =
|
71
|
+
self.class.strategies.find { |klass, _| entity.is_a?(klass) }.last
|
72
|
+
|
73
|
+
strategy.call(
|
74
|
+
entity: entity,
|
75
|
+
name: association.name,
|
76
|
+
value: value
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def convert_to_array(value)
|
81
|
+
return [] if value.nil?
|
82
|
+
|
83
|
+
return value if value.is_a?(Array)
|
84
|
+
|
85
|
+
[value]
|
86
|
+
end
|
87
|
+
|
88
|
+
def index_values(values:)
|
89
|
+
key_name = association.with_inverse(resource).query_key_name
|
90
|
+
|
91
|
+
values
|
92
|
+
.each
|
93
|
+
.with_object(Hash.new { |hsh, key| hsh[key] = [] }) do |value, hsh|
|
94
|
+
hsh[value[key_name]] << value
|
95
|
+
|
96
|
+
hsh
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def process(entities:, values:)
|
101
|
+
indexed = index_values(values: convert_to_array(values))
|
102
|
+
cached = convert_to_array(entities).map do |entity|
|
103
|
+
cache_association(
|
104
|
+
entity: entity,
|
105
|
+
value: indexed[entity[association.inverse_key_name]]
|
106
|
+
)
|
107
|
+
end
|
108
|
+
|
109
|
+
entities.is_a?(Array) ? cached : cached.first
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/actions/middleware/associations'
|
4
|
+
require 'cuprum/rails/actions/middleware/associations/query'
|
5
|
+
|
6
|
+
module Cuprum::Rails::Actions::Middleware::Associations
|
7
|
+
# Middleware for querying an association from the action results.
|
8
|
+
class Find < Cuprum::Rails::Actions::Middleware::Associations::Query
|
9
|
+
private
|
10
|
+
|
11
|
+
def process(next_command, repository:, request:, resource:, **rest)
|
12
|
+
super
|
13
|
+
|
14
|
+
return result unless result.success?
|
15
|
+
|
16
|
+
values = step { perform_query }
|
17
|
+
entities = entities_from(result: result)
|
18
|
+
entities = step { cache_association(entities: entities, values: values) }
|
19
|
+
|
20
|
+
merge_result(entities: entities, result: result, values: values)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/commands/associations/require_many'
|
4
|
+
|
5
|
+
require 'cuprum/rails/actions/middleware/associations'
|
6
|
+
require 'cuprum/rails/actions/middleware/associations/query'
|
7
|
+
require 'cuprum/rails/errors/missing_parameter'
|
8
|
+
|
9
|
+
module Cuprum::Rails::Actions::Middleware::Associations
|
10
|
+
# Middleware for querying a parent association from a parameter.
|
11
|
+
class Parent < Cuprum::Rails::Actions::Middleware::Associations::Query
|
12
|
+
# @param association_params [Hash] parameters to pass to the association.
|
13
|
+
def initialize(**association_params)
|
14
|
+
super(
|
15
|
+
association_type: :belongs_to,
|
16
|
+
**association_params
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def cache_entities(result:, values:)
|
23
|
+
return unless result.success?
|
24
|
+
|
25
|
+
entities = entities_from(result: result)
|
26
|
+
|
27
|
+
cache_association(entities: entities, values: values)
|
28
|
+
end
|
29
|
+
|
30
|
+
def process(next_command, repository:, request:, resource:, **rest)
|
31
|
+
@repository = repository
|
32
|
+
@request = request
|
33
|
+
@resource = resource
|
34
|
+
|
35
|
+
primary_key = step { query_keys }
|
36
|
+
values = step { require_parent(primary_key: primary_key) }
|
37
|
+
result = super
|
38
|
+
entities = step { cache_entities(result: result, values: values) }
|
39
|
+
|
40
|
+
merge_result(entities: entities, result: result, values: values)
|
41
|
+
end
|
42
|
+
|
43
|
+
def query_command
|
44
|
+
Cuprum::Collections::Commands::Associations::RequireMany.new(
|
45
|
+
association: association,
|
46
|
+
repository: repository,
|
47
|
+
resource: resource
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def query_keys
|
52
|
+
key = "#{association.singular_name}_id"
|
53
|
+
value = request.params[key]
|
54
|
+
|
55
|
+
return value if value.present?
|
56
|
+
|
57
|
+
error = Cuprum::Rails::Errors::MissingParameter.new(
|
58
|
+
parameter_name: key,
|
59
|
+
parameters: request.params
|
60
|
+
)
|
61
|
+
failure(error)
|
62
|
+
end
|
63
|
+
|
64
|
+
def require_parent(primary_key:)
|
65
|
+
values = step { perform_query(keys: primary_key) }
|
66
|
+
|
67
|
+
values.is_a?(Array) ? values.first : values
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/collections/associations/belongs_to'
|
4
|
+
require 'cuprum/collections/commands/associations/find_many'
|
5
|
+
|
6
|
+
require 'cuprum/rails/actions/middleware/associations'
|
7
|
+
require 'cuprum/rails/actions/middleware/associations/cache'
|
8
|
+
require 'cuprum/rails/result'
|
9
|
+
|
10
|
+
module Cuprum::Rails::Actions::Middleware::Associations
|
11
|
+
# Abstract middleware for performing an association query.
|
12
|
+
class Query < Cuprum::Rails::Action
|
13
|
+
include Cuprum::Middleware
|
14
|
+
|
15
|
+
# @param association_type [String, Symbol] the type of association.
|
16
|
+
# @param association_params [Hash] parameters to pass to the association.
|
17
|
+
def initialize(association_type: nil, **association_params)
|
18
|
+
super()
|
19
|
+
|
20
|
+
@association_type = association_type&.intern
|
21
|
+
@association = build_association(**association_params)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Cuprum::Collections::Association] the association.
|
25
|
+
attr_reader :association
|
26
|
+
|
27
|
+
# @return [String, Symbol] the type of association.
|
28
|
+
attr_reader :association_type
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_reader :repository
|
33
|
+
|
34
|
+
attr_reader :request
|
35
|
+
|
36
|
+
attr_reader :resource
|
37
|
+
|
38
|
+
attr_reader :result
|
39
|
+
|
40
|
+
def association_class
|
41
|
+
case association_type
|
42
|
+
when :belongs_to
|
43
|
+
Cuprum::Collections::Associations::BelongsTo
|
44
|
+
else
|
45
|
+
Cuprum::Collections::Association
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_association(**params)
|
50
|
+
association_class.new(**params)
|
51
|
+
end
|
52
|
+
|
53
|
+
def cache_association(entities:, values:)
|
54
|
+
Cuprum::Rails::Actions::Middleware::Associations::Cache
|
55
|
+
.new(association: association, resource: resource)
|
56
|
+
.call(entities: entities, values: values)
|
57
|
+
end
|
58
|
+
|
59
|
+
def entities_from(result:)
|
60
|
+
return unless result.value.is_a?(Hash)
|
61
|
+
|
62
|
+
if result.value.key?(resource.singular_name)
|
63
|
+
return result.value[resource.singular_name]
|
64
|
+
end
|
65
|
+
|
66
|
+
result.value[resource.name]
|
67
|
+
end
|
68
|
+
|
69
|
+
def merge_result(entities:, result:, values:)
|
70
|
+
return result unless result.value.is_a?(Hash)
|
71
|
+
|
72
|
+
Cuprum::Rails::Result.new(
|
73
|
+
**result.properties,
|
74
|
+
value: merge_values(
|
75
|
+
entities: entities,
|
76
|
+
value: result.value,
|
77
|
+
values: values
|
78
|
+
)
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
def merge_values(entities:, value:, values:)
|
83
|
+
if entities.present?
|
84
|
+
key = pluralize_name(resource: resource, values: entities)
|
85
|
+
value = value.merge(key => entities)
|
86
|
+
end
|
87
|
+
|
88
|
+
if values.present?
|
89
|
+
key = pluralize_name(resource: association, values: values)
|
90
|
+
value = value.merge(key => values)
|
91
|
+
end
|
92
|
+
|
93
|
+
value
|
94
|
+
end
|
95
|
+
|
96
|
+
def pluralize_name(resource:, values:)
|
97
|
+
values.is_a?(Array) ? resource.plural_name : resource.singular_name
|
98
|
+
end
|
99
|
+
|
100
|
+
def process(next_command, repository:, request:, resource:, **rest)
|
101
|
+
@repository = repository
|
102
|
+
@request = request
|
103
|
+
@resource = resource
|
104
|
+
@result = next_command.call(
|
105
|
+
repository: repository,
|
106
|
+
request: request,
|
107
|
+
resource: resource,
|
108
|
+
**rest
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def perform_query(keys: nil)
|
113
|
+
keys ||= step { query_keys }
|
114
|
+
|
115
|
+
if keys.is_a?(Array)
|
116
|
+
query_command.call(keys: keys)
|
117
|
+
else
|
118
|
+
query_command.call(key: keys)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def query_command
|
123
|
+
Cuprum::Collections::Commands::Associations::FindMany.new(
|
124
|
+
association: association,
|
125
|
+
repository: repository,
|
126
|
+
resource: resource
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def query_keys
|
131
|
+
entities = entities_from(result: result)
|
132
|
+
|
133
|
+
if entities.is_a?(Array)
|
134
|
+
association.map_entities_to_keys(*entities)
|
135
|
+
else
|
136
|
+
association.map_entities_to_keys(entities).first
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuprum/rails/actions/middleware'
|
4
|
+
|
5
|
+
module Cuprum::Rails::Actions::Middleware
|
6
|
+
# Namespace for association middleware.
|
7
|
+
module Associations
|
8
|
+
autoload :Cache, 'cuprum/rails/actions/middleware/associations/cache'
|
9
|
+
autoload :Parent, 'cuprum/rails/actions/middleware/associations/parent'
|
10
|
+
autoload :Query, 'cuprum/rails/actions/middleware/associations/query'
|
11
|
+
end
|
12
|
+
end
|