plutonium 0.14.0 → 0.15.0.pre.rc1
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/README copy.md +1 -1
- data/README.md +1 -1
- data/app/assets/plutonium.css +1 -1
- data/app/views/{application → plutonium}/_resource_header.html copy.erb +1 -1
- data/app/views/{application → plutonium}/_resource_header.html.erb +1 -1
- data/app/views/{application → plutonium}/_resource_sidebar.html.erb +2 -0
- data/app/views/resource/_resource_details.html.erb +1 -36
- data/app/views/resource/_resource_form.html.erb +1 -5
- data/app/views/resource/_resource_table.html.erb +315 -85
- data/app/views/resource/edit.html.erb +1 -5
- data/app/views/resource/index.html.erb +1 -5
- data/app/views/resource/new.html.erb +1 -5
- data/app/views/resource/show.html.erb +1 -5
- data/config/initializers/pagy.rb +1 -0
- data/config/initializers/rabl.rb +27 -20
- data/gemfiles/rails_7.gemfile.lock +5 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +2 -2
- data/lib/generators/pu/core/install/install_generator.rb +0 -3
- data/lib/generators/pu/core/install/templates/app/controllers/plutonium_controller.rb.tt +2 -0
- data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +21 -1
- data/lib/generators/pu/core/install/templates/app/definitions/resource_definition.rb.tt +2 -0
- data/lib/generators/pu/core/install/templates/app/models/resource_record.rb.tt +0 -2
- data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +5 -2
- data/lib/generators/pu/eject/shell/shell_generator.rb +2 -2
- data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +19 -0
- data/lib/generators/pu/lib/plutonium_generators/concerns/logger.rb +1 -1
- data/lib/generators/pu/lib/plutonium_generators/generator.rb +5 -3
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +26 -2
- data/lib/generators/pu/pkg/{feature/feature_generator.rb → package/package_generator.rb} +4 -4
- data/lib/generators/pu/pkg/{feature → package}/templates/app/controllers/resource_controller.rb.tt +0 -2
- data/lib/generators/pu/pkg/package/templates/app/definitions/resource_definition.rb.tt +4 -0
- data/lib/generators/pu/pkg/package/templates/app/query_objects/resource_query_object.rb.tt +4 -0
- data/lib/generators/pu/pkg/{app/app_generator.rb → portal/portal_generator.rb} +10 -8
- data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/concerns/controller.rb.tt +3 -7
- data/lib/generators/pu/pkg/{app → portal}/templates/app/controllers/dashboard_controller.rb.tt +1 -1
- data/lib/generators/pu/pkg/portal/templates/app/controllers/plutonium_controller.rb.tt +5 -0
- data/lib/generators/pu/pkg/{app/templates/app/controllers/controller.rb.tt → portal/templates/app/controllers/resource_controller.rb.tt} +1 -1
- data/lib/generators/pu/pkg/portal/templates/app/definitions/resource_definition.rb.tt +4 -0
- data/lib/generators/pu/pkg/{app → portal}/templates/app/views/package/dashboard/index.html.erb +2 -1
- data/lib/generators/pu/res/conn/conn_generator.rb +78 -3
- data/lib/generators/pu/res/conn/templates/app/controllers/resource_controller.rb.tt +1 -1
- data/lib/generators/pu/res/conn/templates/app/definitions/resource_definition.rb.tt +3 -0
- data/lib/generators/pu/res/conn/templates/app/policies/resource_policy.rb.tt +29 -1
- data/lib/generators/pu/res/conn/templates/app/presenters/resource_presenter.rb.tt +1 -1
- data/lib/generators/pu/res/conn/templates/app/query_objects/resource_query_object.rb.tt +1 -1
- data/lib/generators/pu/res/model/model_generator.rb +0 -7
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -1
- data/lib/generators/pu/res/scaffold/scaffold_generator.rb +22 -4
- data/lib/generators/pu/res/scaffold/templates/controller.rb.tt +0 -1
- data/lib/generators/pu/res/scaffold/templates/definition.rb.tt +4 -0
- data/lib/generators/pu/res/scaffold/templates/policy.rb.tt +2 -2
- data/lib/generators/pu/rodauth/templates/app/controllers/rodauth_controller.rb.tt +1 -1
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +270 -0
- data/lib/plutonium/action/README.md +0 -0
- data/lib/plutonium/action/base.rb +103 -0
- data/lib/plutonium/action/interactive.rb +117 -0
- data/lib/plutonium/action/route_options.rb +65 -0
- data/lib/plutonium/action/simple.rb +8 -0
- data/lib/plutonium/auth.rb +1 -1
- data/lib/plutonium/configuration.rb +130 -0
- data/lib/plutonium/core/actions/collection.rb +1 -1
- data/lib/plutonium/core/associations/renderers/factory.rb +3 -1
- data/lib/plutonium/core/autodiscovery/association_renderer_discoverer.rb +1 -1
- data/lib/plutonium/core/autodiscovery/input_discoverer.rb +1 -1
- data/lib/plutonium/core/autodiscovery/renderer_discoverer.rb +1 -1
- data/lib/plutonium/core/controller.rb +110 -0
- data/lib/plutonium/core/controllers/authorizable.rb +12 -35
- data/lib/plutonium/core/controllers/bootable.rb +38 -7
- data/lib/plutonium/core/controllers/entity_scoping.rb +6 -2
- data/lib/plutonium/core/fields/renderers/association_renderer.rb +1 -1
- data/lib/plutonium/core/ui/collection.rb +1 -1
- data/lib/plutonium/core/ui/detail.rb +1 -1
- data/lib/plutonium/core/ui/form.rb +1 -1
- data/lib/plutonium/definition/actions.rb +50 -0
- data/lib/plutonium/definition/base.rb +92 -0
- data/lib/plutonium/definition/config_attr.rb +30 -0
- data/lib/plutonium/definition/defineable_props.rb +96 -0
- data/lib/plutonium/definition/search.rb +21 -0
- data/lib/plutonium/engine/validator.rb +30 -0
- data/lib/plutonium/engine.rb +25 -0
- data/lib/plutonium/helpers/assets_helper.rb +73 -20
- data/lib/plutonium/helpers/form_helper.rb +1 -3
- data/lib/plutonium/interaction/README.md +369 -0
- data/lib/plutonium/interaction/base.rb +75 -0
- data/lib/plutonium/interaction/concerns/presentable.rb +61 -0
- data/lib/plutonium/interaction/concerns/workflow_dsl.rb +82 -0
- data/lib/plutonium/interaction/outcome.rb +129 -0
- data/lib/plutonium/interaction/response/base.rb +63 -0
- data/lib/plutonium/interaction/response/null.rb +33 -0
- data/lib/plutonium/interaction/response/redirect.rb +30 -0
- data/lib/plutonium/interaction/response/render.rb +28 -0
- data/lib/plutonium/lib/bit_flags.rb +70 -9
- data/lib/plutonium/lib/overlayed_hash.rb +86 -0
- data/lib/plutonium/lib/smart_cache.rb +171 -0
- data/lib/plutonium/models/has_cents.rb +170 -0
- data/lib/plutonium/{pkg/base.rb → package/engine.rb} +10 -2
- data/lib/plutonium/{application → portal}/controller.rb +3 -11
- data/lib/plutonium/{application → portal}/dynamic_controllers.rb +4 -4
- data/lib/plutonium/portal/engine.rb +15 -0
- data/lib/plutonium/railtie.rb +35 -15
- data/lib/plutonium/reloader.rb +71 -29
- data/lib/plutonium/resource/controller.rb +51 -34
- data/lib/plutonium/resource/controllers/authorizable.rb +128 -0
- data/lib/plutonium/{core → resource}/controllers/crud_actions.rb +23 -22
- data/lib/plutonium/resource/controllers/defineable.rb +26 -0
- data/lib/plutonium/{core → resource}/controllers/interactive_actions.rb +12 -12
- data/lib/plutonium/resource/controllers/presentable.rb +41 -0
- data/lib/plutonium/resource/controllers/queryable.rb +44 -0
- data/lib/plutonium/resource/definition.rb +6 -0
- data/lib/plutonium/resource/policy.rb +25 -13
- data/lib/plutonium/resource/query_object.rb +50 -51
- data/lib/plutonium/resource/record.rb +6 -89
- data/lib/plutonium/resource/register.rb +82 -0
- data/lib/plutonium/routing/mapper_extensions.rb +1 -1
- data/lib/plutonium/routing/resource_registration.rb +1 -1
- data/lib/plutonium/routing/route_set_extensions.rb +6 -18
- data/lib/plutonium/ui/action_button.rb +125 -0
- data/lib/plutonium/ui/breadcrumbs.rb +163 -0
- data/lib/plutonium/ui/component/base.rb +13 -0
- data/lib/plutonium/ui/component/behaviour.rb +38 -0
- data/lib/plutonium/ui/component/kit.rb +31 -0
- data/lib/plutonium/ui/component/methods.rb +54 -0
- data/lib/plutonium/ui/display/base.rb +25 -0
- data/lib/plutonium/ui/display/component/association.rb +26 -0
- data/lib/plutonium/ui/display/resource.rb +77 -0
- data/lib/plutonium/ui/display/theme.rb +27 -0
- data/lib/plutonium/ui/dyna_frame/content.rb +20 -0
- data/lib/plutonium/ui/empty_card.rb +20 -0
- data/lib/plutonium/ui/form/base.rb +37 -0
- data/lib/plutonium/ui/form/resource.rb +75 -0
- data/lib/plutonium/ui/form/theme.rb +42 -0
- data/lib/plutonium/ui/page/base.rb +112 -0
- data/lib/plutonium/ui/page/edit.rb +23 -0
- data/lib/plutonium/ui/page/index.rb +27 -0
- data/lib/plutonium/ui/page/new.rb +23 -0
- data/lib/plutonium/ui/page/show.rb +27 -0
- data/lib/plutonium/ui/page_header.rb +49 -0
- data/lib/plutonium/ui/table/base.rb +13 -0
- data/lib/plutonium/ui/table/components/pagy_info.rb +70 -0
- data/lib/plutonium/ui/table/components/pagy_page_info.rb +70 -0
- data/lib/plutonium/ui/table/components/pagy_pagination.rb +105 -0
- data/lib/plutonium/ui/table/components/scopes_bar.rb +136 -0
- data/lib/plutonium/ui/table/components/search_bar.rb +158 -0
- data/lib/plutonium/ui/table/display_theme.rb +21 -0
- data/lib/plutonium/ui/table/resource.rb +98 -0
- data/lib/plutonium/ui/table/theme.rb +35 -0
- data/lib/plutonium/ui.rb +9 -0
- data/lib/plutonium/version.rb +5 -1
- data/lib/plutonium.rb +53 -26
- data/package-lock.json +19 -22
- data/package.json +4 -4
- data/sig/.keep +0 -0
- data/src/css/plutonium.css +15 -0
- data/tailwind.options.js +11 -3
- metadata +220 -81
- data/lib/generators/pu/core/install/templates/app/presenters/resource_presenter.rb.tt +0 -2
- data/lib/generators/pu/core/install/templates/app/query_objects/resource_query_object.rb.tt +0 -2
- data/lib/generators/pu/pkg/feature/templates/app/query_objects/resource_query_object.rb.tt +0 -4
- data/lib/plutonium/concerns/resource_validatable.rb +0 -34
- data/lib/plutonium/config.rb +0 -9
- data/lib/plutonium/core/controllers/base.rb +0 -101
- data/lib/plutonium/core/controllers/presentable.rb +0 -65
- data/lib/plutonium/core/controllers/queryable.rb +0 -28
- data/lib/plutonium/pkg/app.rb +0 -35
- data/lib/plutonium/pkg/concerns/resource_validatable.rb +0 -36
- data/lib/plutonium/pkg/feature.rb +0 -18
- data/lib/plutonium/policy/initializer.rb +0 -22
- data/lib/plutonium/policy/scope.rb +0 -19
- data/lib/plutonium/pundit/context.rb +0 -18
- data/lib/plutonium/pundit/policy_finder.rb +0 -25
- data/lib/plutonium/resource/policy_context.rb +0 -5
- data/lib/plutonium/resource_register.rb +0 -83
- data/lib/plutonium/smart_cache.rb +0 -151
- data/sig/plutonium.rbs +0 -12
- /data/app/views/{application → plutonium}/_flash.html.erb +0 -0
- /data/app/views/{application → plutonium}/_flash_alerts.html.erb +0 -0
- /data/app/views/{application → plutonium}/_flash_toasts.html.erb +0 -0
- /data/lib/generators/pu/pkg/{app/templates/app/views/package → package/templates}/.keep +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/app/interactions/resource_interaction.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/app/models/resource_record.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/app/policies/resource_policy.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/app/presenters/resource_presenter.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature → package}/templates/lib/engine.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/app/policies/resource_policy.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/app/presenters/resource_presenter.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/app/query_objects/resource_query_object.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{feature/templates → portal/templates/app/views/package}/.keep +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/config/routes.rb.tt +0 -0
- /data/lib/generators/pu/pkg/{app → portal}/templates/lib/engine.rb.tt +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
require "active_model"
|
|
2
|
+
|
|
3
|
+
module Plutonium
|
|
4
|
+
module Interaction
|
|
5
|
+
# Base class for all interactions.
|
|
6
|
+
# Provides core functionality for validations, execution, and result handling.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# class MyInteraction < Plutonium::Interaction::Base
|
|
10
|
+
# attribute :user_id, :integer
|
|
11
|
+
# validates :user_id, presence: true
|
|
12
|
+
#
|
|
13
|
+
# private
|
|
14
|
+
#
|
|
15
|
+
# def execute
|
|
16
|
+
# user = User.find(user_id)
|
|
17
|
+
# success(user)
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# @note Subclasses must implement the #execute method.
|
|
22
|
+
class Base
|
|
23
|
+
include ActiveModel::Model
|
|
24
|
+
include ActiveModel::Attributes
|
|
25
|
+
# include Concerns::Presentable
|
|
26
|
+
# include Concerns::WorkflowDSL
|
|
27
|
+
|
|
28
|
+
# Executes the interaction with the given arguments.
|
|
29
|
+
#
|
|
30
|
+
# @param args [Hash] The arguments to initialize the interaction.
|
|
31
|
+
# @return [Plutonium::Interaction::Outcome] The result of the interaction.
|
|
32
|
+
def self.call(**args)
|
|
33
|
+
new(**args).call
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Executes the interaction.
|
|
37
|
+
#
|
|
38
|
+
# @return [Plutonium::Interaction::Outcome] The result of the interaction.
|
|
39
|
+
def call
|
|
40
|
+
if valid?
|
|
41
|
+
execute
|
|
42
|
+
else
|
|
43
|
+
failure(errors)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
# Implement the main logic of the interaction.
|
|
50
|
+
#
|
|
51
|
+
# @abstract Subclass and override {#execute} to implement the interaction logic.
|
|
52
|
+
# @return [Plutonium::Interaction::Outcome] The result of the interaction.
|
|
53
|
+
# @raise [NotImplementedError] If the subclass doesn't implement this method.
|
|
54
|
+
def execute
|
|
55
|
+
raise NotImplementedError, "#{self.class} must implement #execute"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Creates a successful outcome.
|
|
59
|
+
#
|
|
60
|
+
# @param value [Object] The value to be wrapped in the successful outcome.
|
|
61
|
+
# @return [Plutonium::Interaction::Success] A successful outcome.
|
|
62
|
+
def success(value)
|
|
63
|
+
Success.new(value)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Creates a failure outcome.
|
|
67
|
+
#
|
|
68
|
+
# @param errors [ActiveModel::Errors, Array<String>] The errors to be wrapped in the failure outcome.
|
|
69
|
+
# @return [Plutonium::Interaction::Failure] A failure outcome.
|
|
70
|
+
def failure(errors)
|
|
71
|
+
Failure.new(errors)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Plutonium
|
|
2
|
+
module Interaction
|
|
3
|
+
module Concerns
|
|
4
|
+
# Provides presentation-related functionality for interactions.
|
|
5
|
+
#
|
|
6
|
+
# This module allows interactions to define metadata such as labels, icons,
|
|
7
|
+
# and descriptions, which can be used for UI generation or documentation.
|
|
8
|
+
#
|
|
9
|
+
# @example
|
|
10
|
+
# class MyInteraction < Plutonium::Interaction::Base
|
|
11
|
+
# include Plutonium::Interaction::Concerns::Presentable
|
|
12
|
+
#
|
|
13
|
+
# presents label: "My Interaction",
|
|
14
|
+
# icon: "star",
|
|
15
|
+
# description: "Does something awesome"
|
|
16
|
+
#
|
|
17
|
+
# # ... rest of the interaction
|
|
18
|
+
# end
|
|
19
|
+
module Presentable
|
|
20
|
+
extend ActiveSupport::Concern
|
|
21
|
+
|
|
22
|
+
included do
|
|
23
|
+
class_attribute :presentation_metadata, default: {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class_methods do
|
|
27
|
+
# Defines presentation metadata for the interaction.
|
|
28
|
+
#
|
|
29
|
+
# @param options [Hash] The presentation options.
|
|
30
|
+
# @option options [String] :label The label for the interaction.
|
|
31
|
+
# @option options [String] :icon The icon for the interaction.
|
|
32
|
+
# @option options [String] :description The description of the interaction.
|
|
33
|
+
def presents(**options)
|
|
34
|
+
self.presentation_metadata = options
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns the label for the interaction.
|
|
39
|
+
#
|
|
40
|
+
# @return [String] The label defined in the presentation metadata or a default generated from the class name.
|
|
41
|
+
def label
|
|
42
|
+
self.class.presentation_metadata[:label] || self.class.name.demodulize.titleize
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns the icon for the interaction.
|
|
46
|
+
#
|
|
47
|
+
# @return [String, nil] The icon defined in the presentation metadata.
|
|
48
|
+
def icon
|
|
49
|
+
self.class.presentation_metadata[:icon]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Returns the description for the interaction.
|
|
53
|
+
#
|
|
54
|
+
# @return [String, nil] The description defined in the presentation metadata.
|
|
55
|
+
def description
|
|
56
|
+
self.class.presentation_metadata[:description]
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
module Plutonium
|
|
2
|
+
module Interaction
|
|
3
|
+
module Concerns
|
|
4
|
+
# DO NOT USE
|
|
5
|
+
#
|
|
6
|
+
# Provides a Domain Specific Language (DSL) for defining workflows in interactions.
|
|
7
|
+
#
|
|
8
|
+
# This module allows interactions to define a series of steps that can be executed
|
|
9
|
+
# in sequence, with optional conditions for each step.
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# class MyWorkflow < Plutonium::Interaction::Base
|
|
13
|
+
# include Plutonium::Interaction::Concerns::WorkflowDSL
|
|
14
|
+
#
|
|
15
|
+
# workflow do
|
|
16
|
+
# step :validate_input, ValidateInputInteraction
|
|
17
|
+
# step :process_data, ProcessDataInteraction, if: ->(ctx) { ctx[:data_valid] }
|
|
18
|
+
# step :send_notification, SendNotificationInteraction
|
|
19
|
+
# end
|
|
20
|
+
#
|
|
21
|
+
# private
|
|
22
|
+
#
|
|
23
|
+
# def execute
|
|
24
|
+
# execute_workflow(attributes.to_h)
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
27
|
+
module WorkflowDSL
|
|
28
|
+
extend ActiveSupport::Concern
|
|
29
|
+
|
|
30
|
+
included do
|
|
31
|
+
class_attribute :workflow_steps, default: []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class_methods do
|
|
35
|
+
# Defines the workflow for the interaction.
|
|
36
|
+
#
|
|
37
|
+
# @yield The block where workflow steps are defined.
|
|
38
|
+
def workflow(&block)
|
|
39
|
+
WorkflowBuilder.new(self).instance_eval(&block)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Helper class for building workflows.
|
|
44
|
+
class WorkflowBuilder
|
|
45
|
+
# @param use_case_class [Class] The interaction class where the workflow is being defined.
|
|
46
|
+
def initialize(use_case_class)
|
|
47
|
+
@use_case_class = use_case_class
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Adds a step to the workflow.
|
|
51
|
+
#
|
|
52
|
+
# @param name [Symbol] The name of the step.
|
|
53
|
+
# @param use_case [Class] The interaction class to be executed for this step.
|
|
54
|
+
# @param if [Proc, nil] An optional condition for executing the step.
|
|
55
|
+
def step(name, use_case, if: nil)
|
|
56
|
+
@use_case_class.workflow_steps << {
|
|
57
|
+
name:,
|
|
58
|
+
use_case:,
|
|
59
|
+
condition: binding.local_variable_get(:if)
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Executes the defined workflow.
|
|
65
|
+
#
|
|
66
|
+
# @param context [Hash] The initial context for the workflow.
|
|
67
|
+
# @return [Plutonium::Interaction::Outcome] The outcome of the last executed step.
|
|
68
|
+
def execute_workflow(context = {})
|
|
69
|
+
self.class.workflow_steps.reduce(Success.new(context)) do |result, step|
|
|
70
|
+
result.and_then do |ctx|
|
|
71
|
+
if step[:condition].nil? || instance_exec(ctx, &step[:condition])
|
|
72
|
+
step[:use_case].call(context: ctx).map { |outcome| ctx[step[:name]] = outcome }
|
|
73
|
+
else
|
|
74
|
+
Success.new(ctx)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module Plutonium
|
|
2
|
+
module Interaction
|
|
3
|
+
# Base class for interaction outcomes.
|
|
4
|
+
#
|
|
5
|
+
# This class provides a common interface for both successful and failed outcomes
|
|
6
|
+
# of an interaction.
|
|
7
|
+
#
|
|
8
|
+
# @abstract Subclass and override {#and_then}, {#map}, and {#to_response} to implement
|
|
9
|
+
class Outcome
|
|
10
|
+
# @return [Array<Array(String, Symbol)>] Messages associated with the outcome.
|
|
11
|
+
attr_reader :messages
|
|
12
|
+
|
|
13
|
+
# Checks if the outcome is successful.
|
|
14
|
+
#
|
|
15
|
+
# @return [Boolean] true if the outcome is a Success, false otherwise.
|
|
16
|
+
def success?
|
|
17
|
+
is_a?(Success)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Checks if the outcome is a failure.
|
|
21
|
+
#
|
|
22
|
+
# @return [Boolean] true if the outcome is a Failure, false otherwise.
|
|
23
|
+
def failure?
|
|
24
|
+
is_a?(Failure)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Adds a message to the outcome.
|
|
28
|
+
#
|
|
29
|
+
# @param msg [String] The message to add.
|
|
30
|
+
# @param type [Symbol] The type of the message (e.g., :notice, :error).
|
|
31
|
+
# @return [self]
|
|
32
|
+
def with_message(msg, type = :notice)
|
|
33
|
+
@messages ||= []
|
|
34
|
+
@messages << [msg, type]
|
|
35
|
+
self
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Sets the response for the outcome.
|
|
39
|
+
#
|
|
40
|
+
# @param response [Plutonium::Interaction::Response::Base] The response to set.
|
|
41
|
+
#
|
|
42
|
+
# @abstract
|
|
43
|
+
# @raise [NotImplementedError] if not implemented in subclass.
|
|
44
|
+
def with_response(response)
|
|
45
|
+
raise NotImplementedError, "#{self.class} must implement #with_response"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Chains another operation to be executed if this outcome is successful.
|
|
49
|
+
#
|
|
50
|
+
# @abstract
|
|
51
|
+
# @raise [NotImplementedError] if not implemented in subclass.
|
|
52
|
+
def and_then
|
|
53
|
+
raise NotImplementedError, "#{self.class} must implement #and_then"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Converts the outcome to a response object.
|
|
57
|
+
#
|
|
58
|
+
# @abstract
|
|
59
|
+
# @raise [NotImplementedError] if not implemented in subclass.
|
|
60
|
+
def to_response
|
|
61
|
+
raise NotImplementedError, "#{self.class} must implement #to_response"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Represents a successful outcome of an interaction.
|
|
66
|
+
class Success < Outcome
|
|
67
|
+
# @return [Object] The value wrapped by this successful outcome.
|
|
68
|
+
attr_reader :value
|
|
69
|
+
|
|
70
|
+
# @param value [Object] The value to be wrapped in this successful outcome.
|
|
71
|
+
def initialize(value)
|
|
72
|
+
@value = value
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Chains another operation to be executed with the value of this outcome.
|
|
76
|
+
#
|
|
77
|
+
# @yield [Object] The value wrapped by this outcome.
|
|
78
|
+
# @return [Outcome] The result of the yielded block.
|
|
79
|
+
def and_then
|
|
80
|
+
yield value
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Sets the response for this successful outcome.
|
|
84
|
+
#
|
|
85
|
+
# @param response [Plutonium::Interaction::Response::Base] The response to set.
|
|
86
|
+
# @return [self]
|
|
87
|
+
def with_response(response)
|
|
88
|
+
@to_response = nil
|
|
89
|
+
@response = response
|
|
90
|
+
self
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Converts this successful outcome to a response object.
|
|
94
|
+
#
|
|
95
|
+
# @return [Plutonium::Interaction::Response::Base] The response object.
|
|
96
|
+
def to_response
|
|
97
|
+
@to_response ||= begin
|
|
98
|
+
@response ||= Response::Null.new(value)
|
|
99
|
+
@response.with_flash(messages)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Represents a failed outcome of an interaction.
|
|
105
|
+
class Failure < Outcome
|
|
106
|
+
# @return [ActiveModel::Errors, Array<String>] The errors associated with this failure.
|
|
107
|
+
attr_reader :errors
|
|
108
|
+
|
|
109
|
+
# @param errors [ActiveModel::Errors, Array<String>] The errors to be wrapped in this failure.
|
|
110
|
+
def initialize(errors)
|
|
111
|
+
@errors = errors
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Returns self without executing the given block, propagating the failure.
|
|
115
|
+
#
|
|
116
|
+
# @return [self]
|
|
117
|
+
def and_then
|
|
118
|
+
self
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns self without setting a response.
|
|
122
|
+
#
|
|
123
|
+
# @return [self]
|
|
124
|
+
def with_response(response)
|
|
125
|
+
self
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Plutonium
|
|
2
|
+
module Interaction
|
|
3
|
+
module Response
|
|
4
|
+
# Base class for interaction responses.
|
|
5
|
+
#
|
|
6
|
+
# This class provides common functionality for handling flash messages
|
|
7
|
+
# and processing responses in controllers.
|
|
8
|
+
#
|
|
9
|
+
# @abstract Subclass and override {#execute} to implement
|
|
10
|
+
# specific response behavior.
|
|
11
|
+
class Base
|
|
12
|
+
# @return [Array<Array(String, Symbol)>] Flash messages associated with the response.
|
|
13
|
+
attr_reader :flash
|
|
14
|
+
|
|
15
|
+
# Initializes a new Response::Base instance.
|
|
16
|
+
def initialize
|
|
17
|
+
@flash = []
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Processes the response in the context of a controller.
|
|
21
|
+
#
|
|
22
|
+
# @param controller [ActionController::Base] The controller instance.
|
|
23
|
+
# @yield [Object] Executed if the response doesn't handle its own rendering.
|
|
24
|
+
# @return [void]
|
|
25
|
+
def process(controller, &)
|
|
26
|
+
set_flash(controller)
|
|
27
|
+
execute(controller, &)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Adds flash messages to the response.
|
|
31
|
+
#
|
|
32
|
+
# @param messages [Array<Array(String, Symbol)>] The messages to add.
|
|
33
|
+
# @return [self]
|
|
34
|
+
def with_flash(messages)
|
|
35
|
+
@flash.concat(messages) unless messages.blank?
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
# Sets flash messages in the controller.
|
|
42
|
+
#
|
|
43
|
+
# @param controller [ActionController::Base] The controller instance.
|
|
44
|
+
# @return [void]
|
|
45
|
+
def set_flash(controller)
|
|
46
|
+
@flash.each do |message, type|
|
|
47
|
+
controller.flash[type] = message
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Executes the response logic.
|
|
52
|
+
#
|
|
53
|
+
# @abstract
|
|
54
|
+
# @param controller [ActionController::Base] The controller instance.
|
|
55
|
+
# @yield [Object] Executed if the response doesn't handle its own rendering.
|
|
56
|
+
# @raise [NotImplementedError] if not implemented in subclass.
|
|
57
|
+
def execute(controller, &)
|
|
58
|
+
raise NotImplementedError, "#{self.class} must implement #execute"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Plutonium
|
|
2
|
+
module Interaction
|
|
3
|
+
module Response
|
|
4
|
+
# Represents a null response, which doesn't perform any specific action.
|
|
5
|
+
#
|
|
6
|
+
# This class is used when an interaction doesn't produce a specific response
|
|
7
|
+
# type but still needs to wrap a result value.
|
|
8
|
+
class Null < Base
|
|
9
|
+
# @return [Object] The result value wrapped by this null response.
|
|
10
|
+
attr_reader :result
|
|
11
|
+
|
|
12
|
+
# Initializes a new Null response.
|
|
13
|
+
#
|
|
14
|
+
# @param result [Object] The result value to be wrapped.
|
|
15
|
+
def initialize(result)
|
|
16
|
+
super()
|
|
17
|
+
@result = result
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
# Executes the null response by yielding the result value.
|
|
23
|
+
#
|
|
24
|
+
# @param controller [ActionController::Base] The controller instance (unused).
|
|
25
|
+
# @yield [Object] The result value wrapped by this response.
|
|
26
|
+
# @return [void]
|
|
27
|
+
def execute(controller, &)
|
|
28
|
+
yield @result
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Plutonium
|
|
2
|
+
module Interaction
|
|
3
|
+
module Response
|
|
4
|
+
# Represents a redirect response.
|
|
5
|
+
#
|
|
6
|
+
# This class is used to perform redirects as a result of an interaction.
|
|
7
|
+
class Redirect < Base
|
|
8
|
+
# Initializes a new Redirect response.
|
|
9
|
+
#
|
|
10
|
+
# @param path [String, Symbol] The path or named route to redirect to.
|
|
11
|
+
# @param options [Hash] Additional options to pass to the redirect_to method.
|
|
12
|
+
def initialize(path, options = {})
|
|
13
|
+
super()
|
|
14
|
+
@path = path
|
|
15
|
+
@options = options
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# Executes the redirect response.
|
|
21
|
+
#
|
|
22
|
+
# @param controller [ActionController::Base] The controller instance.
|
|
23
|
+
# @return [void]
|
|
24
|
+
def execute(controller)
|
|
25
|
+
controller.redirect_to @path, @options
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Plutonium
|
|
2
|
+
module Interaction
|
|
3
|
+
module Response
|
|
4
|
+
# Represents a render response.
|
|
5
|
+
#
|
|
6
|
+
# This class is used to render views as a result of an interaction.
|
|
7
|
+
class Render < Base
|
|
8
|
+
# Initializes a new Render response.
|
|
9
|
+
#
|
|
10
|
+
# @param options [Hash] Options to pass to the render method.
|
|
11
|
+
def initialize(options = {})
|
|
12
|
+
super()
|
|
13
|
+
@options = options
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# Executes the render response.
|
|
19
|
+
#
|
|
20
|
+
# @param controller [ActionController::Base] The controller instance.
|
|
21
|
+
# @return [void]
|
|
22
|
+
def execute(controller)
|
|
23
|
+
controller.render @options
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,35 +1,96 @@
|
|
|
1
1
|
module Plutonium
|
|
2
2
|
module Lib
|
|
3
|
+
# The BitFlags class provides a convenient way to work with bit flags.
|
|
4
|
+
# It allows setting, checking, and extracting flags using a more
|
|
5
|
+
# readable interface. It supports both symbols and strings as flags.
|
|
6
|
+
#
|
|
7
|
+
# @example Usage
|
|
8
|
+
# flags = BitFlags.new(:read, 'write', :execute)
|
|
9
|
+
# value = flags[:read, 'write'] # => 3
|
|
10
|
+
# flags.set?(value, :read) # => true
|
|
11
|
+
# flags.set?(value, 'execute') # => false
|
|
12
|
+
# flags.extract(value) # => [:read, :write]
|
|
13
|
+
#
|
|
3
14
|
class BitFlags
|
|
4
|
-
|
|
15
|
+
# @return [Array<Symbol>] An array of all defined flags.
|
|
16
|
+
attr_reader :flags
|
|
5
17
|
|
|
18
|
+
# @return [Array<Integer>] An array of all defined bit values.
|
|
19
|
+
attr_reader :indices
|
|
20
|
+
|
|
21
|
+
# Initializes a new BitFlags object with the given flags.
|
|
22
|
+
#
|
|
23
|
+
# @param flags [Array<Symbol, String>] The flags to be used in this BitFlags object.
|
|
24
|
+
# @example
|
|
25
|
+
# BitFlags.new(:read, 'write', :execute)
|
|
6
26
|
def initialize(*flags)
|
|
7
|
-
@
|
|
27
|
+
@flags = normalize_flags(flags).uniq.freeze
|
|
28
|
+
@indices = @flags.each_index.map { |index| 1 << index }.freeze
|
|
29
|
+
@map = @flags.zip(@indices).to_h.freeze
|
|
30
|
+
@all_bits = calculate_all_bits
|
|
8
31
|
end
|
|
9
32
|
|
|
33
|
+
# Checks if the given value has all the specified flags set.
|
|
34
|
+
#
|
|
35
|
+
# @param value [Integer] The value to check against.
|
|
36
|
+
# @param flags [Array<Symbol, String>] The flags to check for.
|
|
37
|
+
# @return [Boolean] True if all specified flags are set and valid, false otherwise.
|
|
38
|
+
# @example
|
|
39
|
+
# flags.set?(3, :read, 'write') # => true
|
|
10
40
|
def set?(value, *flags)
|
|
11
|
-
|
|
41
|
+
normalized_flags = normalize_flags(flags)
|
|
42
|
+
return false if normalized_flags.any? { |flag| !@map.key?(flag) }
|
|
43
|
+
check = bits(*normalized_flags)
|
|
12
44
|
value & check == check
|
|
13
45
|
end
|
|
14
46
|
|
|
47
|
+
# Extracts the flags that are set in the given value.
|
|
48
|
+
#
|
|
49
|
+
# @param value [Integer] The value to extract flags from.
|
|
50
|
+
# @return [Array<Symbol>] An array of flags that are set in the value.
|
|
51
|
+
# @example
|
|
52
|
+
# flags.extract(3) # => [:read, :write]
|
|
15
53
|
def extract(value)
|
|
16
|
-
|
|
54
|
+
value &= @all_bits
|
|
55
|
+
@map.select { |_, bit| value & bit != 0 }.keys
|
|
17
56
|
end
|
|
18
57
|
|
|
58
|
+
# Returns the bit value for the given flags.
|
|
59
|
+
#
|
|
60
|
+
# @param flags [Array<Symbol, String>] The flags to get the bit value for.
|
|
61
|
+
# @return [Integer] The combined bit value of the given flags.
|
|
62
|
+
# @example
|
|
63
|
+
# flags[:read, 'write'] # => 3
|
|
19
64
|
def [](*flags)
|
|
20
65
|
bits(*flags)
|
|
21
66
|
end
|
|
22
67
|
|
|
68
|
+
# Calculates the combined bit value for the given flags.
|
|
69
|
+
#
|
|
70
|
+
# @param flags [Array<Symbol, String>] The flags to calculate the bit value for.
|
|
71
|
+
# @return [Integer] The combined bit value of the given flags.
|
|
72
|
+
# @example
|
|
73
|
+
# flags.bits(:read, 'write') # => 3
|
|
23
74
|
def bits(*flags)
|
|
24
|
-
|
|
75
|
+
normalized_flags = normalize_flags(flags)
|
|
76
|
+
normalized_flags.sum { |flag| @map[flag] || 0 }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Calculates the sum of all bit values.
|
|
80
|
+
#
|
|
81
|
+
# @return [Integer] The sum of all bit values.
|
|
82
|
+
def sum
|
|
83
|
+
@all_bits
|
|
25
84
|
end
|
|
26
85
|
|
|
27
|
-
|
|
28
|
-
|
|
86
|
+
private
|
|
87
|
+
|
|
88
|
+
def calculate_all_bits
|
|
89
|
+
@indices.inject(:|) || 0
|
|
29
90
|
end
|
|
30
91
|
|
|
31
|
-
def
|
|
32
|
-
|
|
92
|
+
def normalize_flags(flags)
|
|
93
|
+
flags.map(&:to_sym)
|
|
33
94
|
end
|
|
34
95
|
end
|
|
35
96
|
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Plutonium
|
|
4
|
+
module Lib
|
|
5
|
+
# OverlayedHash provides a hash-like structure that overlays values on top of a base hash.
|
|
6
|
+
#
|
|
7
|
+
# This class allows you to create a new hash-like object that uses a base hash for default values,
|
|
8
|
+
# but can be modified without affecting the original base hash. When a key is accessed, it first
|
|
9
|
+
# checks if the key exists in the overlay; if not, it falls back to the base hash.
|
|
10
|
+
#
|
|
11
|
+
# @example Usage
|
|
12
|
+
# base = { a: 1, b: 2 }
|
|
13
|
+
# overlay = OverlayedHash.new(base)
|
|
14
|
+
# overlay[:b] = 3
|
|
15
|
+
# overlay[:c] = 4
|
|
16
|
+
#
|
|
17
|
+
# overlay[:a] # => 1 (from base)
|
|
18
|
+
# overlay[:b] # => 3 (from overlay)
|
|
19
|
+
# overlay[:c] # => 4 (from overlay)
|
|
20
|
+
# base[:b] # => 2 (unchanged)
|
|
21
|
+
class OverlayedHash
|
|
22
|
+
# Initialize a new OverlayedHash with a base hash.
|
|
23
|
+
#
|
|
24
|
+
# @param base [Hash] The base hash to use for fallback values.
|
|
25
|
+
def initialize(base)
|
|
26
|
+
@base = base
|
|
27
|
+
@overlay = {}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Retrieve a value from the overlay hash or the base hash.
|
|
31
|
+
#
|
|
32
|
+
# @param key The key to look up.
|
|
33
|
+
# @return The value associated with the key, or nil if not found.
|
|
34
|
+
def [](key)
|
|
35
|
+
@overlay.key?(key) ? @overlay[key] : @base[key]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Set a value in the overlay hash.
|
|
39
|
+
#
|
|
40
|
+
# @param key The key to set.
|
|
41
|
+
# @param value The value to associate with the key.
|
|
42
|
+
def []=(key, value)
|
|
43
|
+
@overlay[key] = value
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Check if a key exists in either the overlay or base hash.
|
|
47
|
+
#
|
|
48
|
+
# @param key The key to check for.
|
|
49
|
+
# @return [Boolean] true if the key exists, false otherwise.
|
|
50
|
+
def key?(key)
|
|
51
|
+
@overlay.key?(key) || @base.key?(key)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Enumerate over all keys in both the overlay and base hash.
|
|
55
|
+
#
|
|
56
|
+
# @yield [key] Gives each key to the block.
|
|
57
|
+
# @return [Enumerator] If no block is given.
|
|
58
|
+
def each_key
|
|
59
|
+
return to_enum(:each_key) unless block_given?
|
|
60
|
+
|
|
61
|
+
keys.each { |key| yield key }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Retrieve all keys from both the overlay and base hash.
|
|
65
|
+
#
|
|
66
|
+
# @return [Array] An array of all unique keys.
|
|
67
|
+
def keys
|
|
68
|
+
(@overlay.keys + @base.keys).uniq
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Retrieve all values, prioritizing the overlay over the base.
|
|
72
|
+
#
|
|
73
|
+
# @return [Array] An array of values corresponding to all keys.
|
|
74
|
+
def values
|
|
75
|
+
keys.map { |key| self[key] }
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Convert the OverlayedHash to a regular Hash.
|
|
79
|
+
#
|
|
80
|
+
# @return [Hash] A new Hash with all keys and values from the OverlayedHash.
|
|
81
|
+
def to_h
|
|
82
|
+
keys.each_with_object({}) { |key, hash| hash[key] = self[key] }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|