better_service 2.0.0 → 2.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 +4 -4
- data/LICENSE +2 -0
- data/README.md +98 -45
- data/Rakefile +7 -209
- data/config/locales/better_service.en.yml +15 -0
- data/lib/better_service/cache_service.rb +4 -4
- data/lib/better_service/concerns/instrumentation.rb +59 -14
- data/lib/better_service/concerns/serviceable/authorizable.rb +1 -1
- data/lib/better_service/concerns/serviceable/messageable.rb +70 -1
- data/lib/better_service/concerns/serviceable/repository_aware.rb +8 -3
- data/lib/better_service/concerns/workflowable/callbacks.rb +27 -27
- data/lib/better_service/concerns/workflowable/step.rb +39 -5
- data/lib/better_service/errors/better_service_error.rb +4 -0
- data/lib/better_service/errors/runtime/authorization_error.rb +4 -1
- data/lib/better_service/errors/runtime/database_error.rb +4 -1
- data/lib/better_service/errors/runtime/execution_error.rb +4 -1
- data/lib/better_service/errors/runtime/invalid_result_error.rb +28 -0
- data/lib/better_service/errors/runtime/resource_not_found_error.rb +4 -1
- data/lib/better_service/errors/runtime/validation_error.rb +4 -1
- data/lib/better_service/repository/base_repository.rb +1 -1
- data/lib/better_service/result.rb +110 -0
- data/lib/better_service/services/base.rb +216 -57
- data/lib/better_service/version.rb +1 -1
- data/lib/better_service/workflows/branch_group.rb +1 -1
- data/lib/better_service.rb +1 -6
- data/lib/generators/serviceable/action_generator.rb +11 -0
- data/lib/generators/serviceable/base_generator.rb +109 -0
- data/lib/generators/serviceable/create_generator.rb +11 -0
- data/lib/generators/serviceable/destroy_generator.rb +11 -0
- data/lib/generators/serviceable/index_generator.rb +11 -0
- data/lib/generators/serviceable/scaffold_generator.rb +29 -7
- data/lib/generators/serviceable/show_generator.rb +11 -0
- data/lib/generators/serviceable/templates/action_service.rb.tt +8 -3
- data/lib/generators/serviceable/templates/base_locale.en.yml.tt +53 -0
- data/lib/generators/serviceable/templates/base_service.rb.tt +78 -0
- data/lib/generators/serviceable/templates/base_service_test.rb.tt +64 -0
- data/lib/generators/serviceable/templates/create_service.rb.tt +29 -18
- data/lib/generators/serviceable/templates/destroy_service.rb.tt +16 -29
- data/lib/generators/serviceable/templates/index_service.rb.tt +16 -34
- data/lib/generators/serviceable/templates/repository.rb.tt +76 -0
- data/lib/generators/serviceable/templates/repository_test.rb.tt +124 -0
- data/lib/generators/serviceable/templates/show_service.rb.tt +10 -38
- data/lib/generators/serviceable/templates/update_service.rb.tt +24 -38
- data/lib/generators/serviceable/update_generator.rb +11 -0
- metadata +13 -12
- data/lib/better_service/concerns/serviceable/viewable.rb +0 -33
- data/lib/better_service/services/action_service.rb +0 -60
- data/lib/better_service/services/create_service.rb +0 -63
- data/lib/better_service/services/destroy_service.rb +0 -60
- data/lib/better_service/services/index_service.rb +0 -56
- data/lib/better_service/services/show_service.rb +0 -44
- data/lib/better_service/services/update_service.rb +0 -61
|
@@ -43,7 +43,7 @@ module BetterService
|
|
|
43
43
|
fallback_key = "better_service.services.default.#{action}"
|
|
44
44
|
|
|
45
45
|
# I18n supports array of fallback keys: try each in order
|
|
46
|
-
I18n.t(full_key, default: [fallback_key.to_sym, key_path], **interpolations)
|
|
46
|
+
I18n.t(full_key, default: [ fallback_key.to_sym, key_path ], **interpolations)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
# Extract action name from key path for fallback lookup
|
|
@@ -67,6 +67,75 @@ module BetterService
|
|
|
67
67
|
else "action_completed"
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
|
+
|
|
71
|
+
# ============================================
|
|
72
|
+
# RESPONSE HELPERS FOR TUPLE FORMAT
|
|
73
|
+
# ============================================
|
|
74
|
+
|
|
75
|
+
# Build a failure response hash for validation failures
|
|
76
|
+
# Use this when using save (not save!) to handle AR validation gracefully
|
|
77
|
+
#
|
|
78
|
+
# @param record [ActiveRecord::Base] The record with validation errors
|
|
79
|
+
# @param custom_message [String, nil] Optional custom message
|
|
80
|
+
# @return [Hash] Failure response hash (for use in process_with/respond_with)
|
|
81
|
+
#
|
|
82
|
+
# @example In process_with block
|
|
83
|
+
# process_with do |data|
|
|
84
|
+
# product = user.products.build(params)
|
|
85
|
+
#
|
|
86
|
+
# if product.save
|
|
87
|
+
# { object: product }
|
|
88
|
+
# else
|
|
89
|
+
# failure_for(product)
|
|
90
|
+
# end
|
|
91
|
+
# end
|
|
92
|
+
def failure_for(record, custom_message = nil)
|
|
93
|
+
{
|
|
94
|
+
object: record,
|
|
95
|
+
success: false,
|
|
96
|
+
message: custom_message || default_failure_message(record)
|
|
97
|
+
}
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Build a success response hash
|
|
101
|
+
#
|
|
102
|
+
# @param object [Object] The object to return (AR model, array, etc.)
|
|
103
|
+
# @param custom_message [String, nil] Optional custom message
|
|
104
|
+
# @return [Hash] Success response hash (for use in respond_with)
|
|
105
|
+
#
|
|
106
|
+
# @example In respond_with block
|
|
107
|
+
# respond_with do |data|
|
|
108
|
+
# return data if data[:success] == false
|
|
109
|
+
# success_for(data[:object], "Product created!")
|
|
110
|
+
# end
|
|
111
|
+
def success_for(object, custom_message = nil)
|
|
112
|
+
{
|
|
113
|
+
object: object,
|
|
114
|
+
success: true,
|
|
115
|
+
message: custom_message || default_success_message
|
|
116
|
+
}
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Generate default failure message based on record state
|
|
120
|
+
#
|
|
121
|
+
# @param record [ActiveRecord::Base] The failed record
|
|
122
|
+
# @return [String] Failure message
|
|
123
|
+
def default_failure_message(record)
|
|
124
|
+
model_name = record.class.name.underscore.humanize.downcase
|
|
125
|
+
if record.new_record?
|
|
126
|
+
message("create.failure", default: "Failed to create #{model_name}")
|
|
127
|
+
else
|
|
128
|
+
message("update.failure", default: "Failed to update #{model_name}")
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Generate default success message based on action
|
|
133
|
+
#
|
|
134
|
+
# @return [String] Success message
|
|
135
|
+
def default_success_message
|
|
136
|
+
action = self.class._action_name || :action
|
|
137
|
+
message("#{action}.success", default: "Operation completed successfully")
|
|
138
|
+
end
|
|
70
139
|
end
|
|
71
140
|
end
|
|
72
141
|
end
|
|
@@ -10,9 +10,12 @@ module BetterService
|
|
|
10
10
|
# separation between business logic and data access.
|
|
11
11
|
#
|
|
12
12
|
# @example Basic usage
|
|
13
|
-
# class Products::CreateService <
|
|
13
|
+
# class Products::CreateService < Products::BaseService
|
|
14
14
|
# include BetterService::Concerns::Serviceable::RepositoryAware
|
|
15
15
|
#
|
|
16
|
+
# performed_action :created
|
|
17
|
+
# with_transaction true
|
|
18
|
+
#
|
|
16
19
|
# repository :product
|
|
17
20
|
#
|
|
18
21
|
# process_with do |data|
|
|
@@ -21,7 +24,7 @@ module BetterService
|
|
|
21
24
|
# end
|
|
22
25
|
#
|
|
23
26
|
# @example With custom class name
|
|
24
|
-
# class Bookings::AcceptService <
|
|
27
|
+
# class Bookings::AcceptService < Bookings::BaseService
|
|
25
28
|
# include BetterService::Concerns::Serviceable::RepositoryAware
|
|
26
29
|
#
|
|
27
30
|
# repository :booking, class_name: "Bookings::BookingRepository"
|
|
@@ -33,9 +36,11 @@ module BetterService
|
|
|
33
36
|
# end
|
|
34
37
|
#
|
|
35
38
|
# @example Multiple repositories shorthand
|
|
36
|
-
# class Dashboard::IndexService <
|
|
39
|
+
# class Dashboard::IndexService < Dashboard::BaseService
|
|
37
40
|
# include BetterService::Concerns::Serviceable::RepositoryAware
|
|
38
41
|
#
|
|
42
|
+
# performed_action :listed
|
|
43
|
+
#
|
|
39
44
|
# repositories :user, :booking, :payment
|
|
40
45
|
# end
|
|
41
46
|
#
|
|
@@ -4,33 +4,33 @@ module BetterService
|
|
|
4
4
|
module Concerns
|
|
5
5
|
module Workflowable
|
|
6
6
|
# Callbacks - Adds lifecycle callbacks to workflows
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
7
|
+
#
|
|
8
|
+
# Provides before_workflow, after_workflow, and around_step hooks
|
|
9
|
+
# that allow executing custom logic at different stages of the workflow.
|
|
10
|
+
#
|
|
11
|
+
# Example:
|
|
12
|
+
# class OrderWorkflow < BetterService::Workflow
|
|
13
|
+
# before_workflow :validate_prerequisites
|
|
14
|
+
# after_workflow :cleanup_resources
|
|
15
|
+
# around_step :log_step_execution
|
|
16
|
+
#
|
|
17
|
+
# private
|
|
18
|
+
#
|
|
19
|
+
# def validate_prerequisites(context)
|
|
20
|
+
# context.fail!("Cart is empty") if context.cart_items.empty?
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# def cleanup_resources(context)
|
|
24
|
+
# context.user.clear_cart! if context.success?
|
|
25
|
+
# end
|
|
26
|
+
#
|
|
27
|
+
# def log_step_execution(step, context)
|
|
28
|
+
# start_time = Time.current
|
|
29
|
+
# yield # Execute the step
|
|
30
|
+
# duration = Time.current - start_time
|
|
31
|
+
# Rails.logger.info "Step #{step.name} completed in #{duration}s"
|
|
32
|
+
# end
|
|
33
|
+
# end
|
|
34
34
|
module Callbacks
|
|
35
35
|
extend ActiveSupport::Concern
|
|
36
36
|
|
|
@@ -35,7 +35,7 @@ module BetterService
|
|
|
35
35
|
# @param context [Context] The workflow context
|
|
36
36
|
# @param user [Object] The current user
|
|
37
37
|
# @param params [Hash] Base params for the workflow
|
|
38
|
-
# @return [Hash] Service result
|
|
38
|
+
# @return [Hash] Service result (normalized to hash format for workflow compatibility)
|
|
39
39
|
def call(context, user, base_params = {})
|
|
40
40
|
# Check if step should be skipped due to condition
|
|
41
41
|
if should_skip?(context)
|
|
@@ -49,20 +49,23 @@ module BetterService
|
|
|
49
49
|
# Build input params for the service
|
|
50
50
|
service_params = build_params(context, base_params)
|
|
51
51
|
|
|
52
|
-
# Call the service
|
|
53
|
-
|
|
52
|
+
# Call the service - returns [object, metadata] tuple
|
|
53
|
+
service_result = service_class.new(user, params: service_params).call
|
|
54
|
+
|
|
55
|
+
# Normalize result to hash format (services now return [object, metadata] tuple)
|
|
56
|
+
result = normalize_service_result(service_result)
|
|
54
57
|
|
|
55
58
|
# Store result in context if successful
|
|
56
59
|
if result[:success]
|
|
57
60
|
store_result_in_context(context, result)
|
|
58
61
|
elsif optional
|
|
59
62
|
# If step is optional and failed, continue but log the failure
|
|
60
|
-
context.add(:"#{name}_error", result[:errors])
|
|
63
|
+
context.add(:"#{name}_error", result[:errors] || result[:validation_errors])
|
|
61
64
|
return {
|
|
62
65
|
success: true,
|
|
63
66
|
optional_failure: true,
|
|
64
67
|
message: "Optional step #{name} failed but continuing",
|
|
65
|
-
errors: result[:errors]
|
|
68
|
+
errors: result[:errors] || result[:validation_errors]
|
|
66
69
|
}
|
|
67
70
|
end
|
|
68
71
|
|
|
@@ -123,6 +126,37 @@ module BetterService
|
|
|
123
126
|
end
|
|
124
127
|
end
|
|
125
128
|
|
|
129
|
+
# Normalize service result from Result format to hash format
|
|
130
|
+
# Services return BetterService::Result but workflows expect hash with :success, :resource, etc.
|
|
131
|
+
#
|
|
132
|
+
# @param service_result [BetterService::Result] The service result
|
|
133
|
+
# @return [Hash] Normalized result hash
|
|
134
|
+
# @raise [BetterService::Errors::Runtime::InvalidResultError] If result is not a BetterService::Result
|
|
135
|
+
def normalize_service_result(service_result)
|
|
136
|
+
unless service_result.is_a?(BetterService::Result)
|
|
137
|
+
raise BetterService::Errors::Runtime::InvalidResultError.new(
|
|
138
|
+
"Step #{name} service must return BetterService::Result, got #{service_result.class}",
|
|
139
|
+
context: { step: name, service: service_class.name, result_class: service_result.class.name }
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
object = service_result.resource
|
|
144
|
+
metadata = service_result.meta
|
|
145
|
+
|
|
146
|
+
# Build normalized hash result
|
|
147
|
+
result = metadata.dup
|
|
148
|
+
result[:success] = metadata[:success] if metadata.key?(:success)
|
|
149
|
+
|
|
150
|
+
# Store object appropriately based on type
|
|
151
|
+
if object.is_a?(Array)
|
|
152
|
+
result[:items] = object
|
|
153
|
+
elsif object.present?
|
|
154
|
+
result[:resource] = object
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
result
|
|
158
|
+
end
|
|
159
|
+
|
|
126
160
|
# Store successful result data in context
|
|
127
161
|
def store_result_in_context(context, result)
|
|
128
162
|
# Store resource if present
|
|
@@ -218,6 +218,9 @@ module BetterService
|
|
|
218
218
|
|
|
219
219
|
# Workflow rollback failed
|
|
220
220
|
ROLLBACK_FAILED = :rollback_failed
|
|
221
|
+
|
|
222
|
+
# Service returned invalid result type (not BetterService::Result)
|
|
223
|
+
INVALID_RESULT = :invalid_result
|
|
221
224
|
end
|
|
222
225
|
end
|
|
223
226
|
|
|
@@ -235,6 +238,7 @@ require_relative "runtime/resource_not_found_error"
|
|
|
235
238
|
require_relative "runtime/database_error"
|
|
236
239
|
require_relative "runtime/validation_error"
|
|
237
240
|
require_relative "runtime/authorization_error"
|
|
241
|
+
require_relative "runtime/invalid_result_error"
|
|
238
242
|
|
|
239
243
|
require_relative "workflowable/configuration/workflow_configuration_error"
|
|
240
244
|
require_relative "workflowable/configuration/step_not_found_error"
|
|
@@ -31,7 +31,10 @@ module BetterService
|
|
|
31
31
|
# rescue BetterService::Errors::Runtime::AuthorizationError => e
|
|
32
32
|
# render json: { error: e.message }, status: :forbidden
|
|
33
33
|
# end
|
|
34
|
-
class AuthorizationError < RuntimeError
|
|
34
|
+
class AuthorizationError < BetterService::Errors::Runtime::RuntimeError
|
|
35
|
+
def initialize(message = "Not authorized", code: :unauthorized, context: {}, original_error: nil)
|
|
36
|
+
super(message, code: code, context: context, original_error: original_error)
|
|
37
|
+
end
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
end
|
|
@@ -31,7 +31,10 @@ module BetterService
|
|
|
31
31
|
#
|
|
32
32
|
# MyService.new(user, params: { user_id: 1 }).call
|
|
33
33
|
# # => raises DatabaseError
|
|
34
|
-
class DatabaseError < RuntimeError
|
|
34
|
+
class DatabaseError < BetterService::Errors::Runtime::RuntimeError
|
|
35
|
+
def initialize(message = "Database error", code: :database_error, context: {}, original_error: nil)
|
|
36
|
+
super(message, code: code, context: context, original_error: original_error)
|
|
37
|
+
end
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
end
|
|
@@ -20,7 +20,10 @@ module BetterService
|
|
|
20
20
|
#
|
|
21
21
|
# MyService.new(user, params: {}).call
|
|
22
22
|
# # => raises ExecutionError wrapping SocketError
|
|
23
|
-
class ExecutionError < RuntimeError
|
|
23
|
+
class ExecutionError < BetterService::Errors::Runtime::RuntimeError
|
|
24
|
+
def initialize(message = "Execution failed", code: :execution_error, context: {}, original_error: nil)
|
|
25
|
+
super(message, code: code, context: context, original_error: original_error)
|
|
26
|
+
end
|
|
24
27
|
end
|
|
25
28
|
end
|
|
26
29
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
module Errors
|
|
5
|
+
module Runtime
|
|
6
|
+
# InvalidResultError - Raised when a service does not return BetterService::Result
|
|
7
|
+
#
|
|
8
|
+
# All services MUST return a BetterService::Result object. This error is raised
|
|
9
|
+
# when a service returns a Hash, Array (tuple), or any other type instead.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# raise BetterService::Errors::Runtime::InvalidResultError.new(
|
|
13
|
+
# "Service MyService must return BetterService::Result, got Hash",
|
|
14
|
+
# context: { service: "MyService", result_class: "Hash" }
|
|
15
|
+
# )
|
|
16
|
+
class InvalidResultError < RuntimeError
|
|
17
|
+
def initialize(message = nil, code: :invalid_result, context: {}, original_error: nil)
|
|
18
|
+
super(
|
|
19
|
+
message || "Service must return BetterService::Result",
|
|
20
|
+
code: code,
|
|
21
|
+
context: context,
|
|
22
|
+
original_error: original_error
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -31,7 +31,10 @@ module BetterService
|
|
|
31
31
|
#
|
|
32
32
|
# MyService.new(user, params: { user_id: 99999 }).call
|
|
33
33
|
# # => raises ResourceNotFoundError
|
|
34
|
-
class ResourceNotFoundError < RuntimeError
|
|
34
|
+
class ResourceNotFoundError < BetterService::Errors::Runtime::RuntimeError
|
|
35
|
+
def initialize(message = "Resource not found", code: :resource_not_found, context: {}, original_error: nil)
|
|
36
|
+
super(message, code: code, context: context, original_error: original_error)
|
|
37
|
+
end
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
end
|
|
@@ -35,7 +35,10 @@ module BetterService
|
|
|
35
35
|
# validation_errors: e.context[:validation_errors]
|
|
36
36
|
# }, status: :unprocessable_entity
|
|
37
37
|
# end
|
|
38
|
-
class ValidationError < RuntimeError
|
|
38
|
+
class ValidationError < BetterService::Errors::Runtime::RuntimeError
|
|
39
|
+
def initialize(message = "Validation failed", code: :validation_failed, context: {}, original_error: nil)
|
|
40
|
+
super(message, code: code, context: context, original_error: original_error)
|
|
41
|
+
end
|
|
39
42
|
end
|
|
40
43
|
end
|
|
41
44
|
end
|
|
@@ -249,7 +249,7 @@ module BetterService
|
|
|
249
249
|
# @param per_page [Integer] Records per page
|
|
250
250
|
# @return [ActiveRecord::Relation] Paginated scope
|
|
251
251
|
def paginate(scope, page:, per_page:)
|
|
252
|
-
offset_value = ([page.to_i, 1].max - 1) * per_page.to_i
|
|
252
|
+
offset_value = ([ page.to_i, 1 ].max - 1) * per_page.to_i
|
|
253
253
|
scope.offset(offset_value).limit(per_page)
|
|
254
254
|
end
|
|
255
255
|
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module BetterService
|
|
4
|
+
# Result wrapper per le risposte dei service
|
|
5
|
+
#
|
|
6
|
+
# Fornisce un modo standardizzato per restituire sia la risorsa che i metadata.
|
|
7
|
+
# BetterController può automaticamente unwrappare gli oggetti Result.
|
|
8
|
+
#
|
|
9
|
+
# @example Caso di successo
|
|
10
|
+
# BetterService::Result.new(user, meta: { message: "Created" })
|
|
11
|
+
#
|
|
12
|
+
# @example Caso di fallimento
|
|
13
|
+
# BetterService::Result.new(user, meta: { success: false, message: "Validation failed" })
|
|
14
|
+
#
|
|
15
|
+
class Result
|
|
16
|
+
attr_reader :resource, :meta
|
|
17
|
+
|
|
18
|
+
# @param resource [Object] L'oggetto risorsa (model, collection, etc.)
|
|
19
|
+
# @param meta [Hash] Hash di metadata, deve contenere :success key (default: true)
|
|
20
|
+
def initialize(resource, meta: {})
|
|
21
|
+
@resource = resource
|
|
22
|
+
@meta = meta.is_a?(Hash) ? meta.reverse_merge(success: true) : { success: true }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @return [Boolean] true se l'operazione è riuscita
|
|
26
|
+
def success?
|
|
27
|
+
meta[:success] == true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @return [Boolean] true se l'operazione è fallita
|
|
31
|
+
def failure?
|
|
32
|
+
!success?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [String, nil] Il messaggio dal meta
|
|
36
|
+
def message
|
|
37
|
+
meta[:message]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Symbol, nil] L'azione eseguita
|
|
41
|
+
def action
|
|
42
|
+
meta[:action]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Hash, nil] Gli errori di validazione
|
|
46
|
+
def validation_errors
|
|
47
|
+
meta[:validation_errors]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @return [Array<String>, nil] I messaggi di errore completi
|
|
51
|
+
def full_messages
|
|
52
|
+
meta[:full_messages]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @return [ActiveModel::Errors, nil] Errori dalla risorsa se disponibili
|
|
56
|
+
def errors
|
|
57
|
+
resource.respond_to?(:errors) ? resource.errors : nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Supporta destructuring: resource, meta = result
|
|
61
|
+
# @return [Array] [resource, meta]
|
|
62
|
+
def to_ary
|
|
63
|
+
[ resource, meta ]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Alias per compatibilità con destructuring
|
|
67
|
+
alias_method :deconstruct, :to_ary
|
|
68
|
+
|
|
69
|
+
# @return [Hash] Rappresentazione completa
|
|
70
|
+
def to_h
|
|
71
|
+
{ resource: resource, meta: meta }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Accesso Hash-like per compatibilità con BetterController
|
|
75
|
+
# @param key [Symbol] La chiave da accedere
|
|
76
|
+
# @return [Object, nil] Il valore associato alla chiave
|
|
77
|
+
def [](key)
|
|
78
|
+
case key
|
|
79
|
+
when :resource then resource
|
|
80
|
+
when :meta then meta
|
|
81
|
+
when :success then success?
|
|
82
|
+
when :message then message
|
|
83
|
+
when :action then action
|
|
84
|
+
else
|
|
85
|
+
meta[key]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Accesso nested Hash-like (dig)
|
|
90
|
+
# @param keys [Array<Symbol>] Le chiavi per l'accesso nested
|
|
91
|
+
# @return [Object, nil] Il valore nested
|
|
92
|
+
def dig(*keys)
|
|
93
|
+
return nil if keys.empty?
|
|
94
|
+
|
|
95
|
+
value = self[keys.first]
|
|
96
|
+
return value if keys.size == 1
|
|
97
|
+
return nil unless value.respond_to?(:dig)
|
|
98
|
+
|
|
99
|
+
value.dig(*keys[1..])
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Verifica esistenza chiave
|
|
103
|
+
# @param key [Symbol] La chiave da verificare
|
|
104
|
+
# @return [Boolean]
|
|
105
|
+
def key?(key)
|
|
106
|
+
%i[resource meta success message action].include?(key) || meta.key?(key)
|
|
107
|
+
end
|
|
108
|
+
alias_method :has_key?, :key?
|
|
109
|
+
end
|
|
110
|
+
end
|