typed_operation 1.0.0.beta1 → 1.0.0.beta3
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.md +20 -52
- data/lib/generators/typed_operation/install/USAGE +1 -0
- data/lib/generators/typed_operation/install/install_generator.rb +5 -0
- data/lib/generators/typed_operation/install/templates/application_operation.rb +24 -2
- data/lib/typed_operation/action_policy_auth.rb +1 -1
- data/lib/typed_operation/base.rb +0 -12
- data/lib/typed_operation/immutable_base.rb +0 -1
- data/lib/typed_operation/operations/introspection.rb +5 -7
- data/lib/typed_operation/operations/lifecycle.rb +1 -1
- data/lib/typed_operation/operations/parameters.rb +10 -1
- data/lib/typed_operation/operations/{attribute_builder.rb → property_builder.rb} +6 -6
- data/lib/typed_operation/version.rb +1 -1
- data/lib/typed_operation.rb +1 -6
- metadata +28 -12
- data/lib/typed_operation/operations/deconstruct.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d6d2e6d373445f7a0c186b88963fb6cadb3947f32731f4411fbeb71588e65d63
|
4
|
+
data.tar.gz: 1329be34dbc54ba576c97dae12d82b513696b420ef34885842b6d8b0bb11b643
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d18d142eddd2506739786b89028a952322b0bf0baded051519107e07ed7db4c2444f9134ec1e6471254e7b86549d98a1766379e139bef50d2577c73d634f3c47
|
7
|
+
data.tar.gz: bfa38ed9df98fc442e92b9f09ebf2aaf29b5fd3c28e9ccb016c64150c28bb3d42b21409b291258137b2efc2ad8b4e904181877a1e92c7eed1661b29d3f2cdd4d
|
data/README.md
CHANGED
@@ -4,14 +4,7 @@ An implementation of a Command pattern, which is callable, and can be partially
|
|
4
4
|
|
5
5
|
Inputs to the operation are specified as typed attributes (uses [`literal`](https://github.com/joeldrapper/literal)).
|
6
6
|
|
7
|
-
Type of result of the operation is up to you, eg you could use [`
|
8
|
-
|
9
|
-
**Note the version described here (~ 1.0.0) is pre-release on Rubygems (v1.0.0 is waiting for a release of `literal`). To use it now you can simply require `literal` from github in your Gemfile:**
|
10
|
-
|
11
|
-
```ruby
|
12
|
-
gem "literal", github: "joeldrapper/literal", branch: "main"
|
13
|
-
gem "typed_operation", "~> 1.0.0.beta1"
|
14
|
-
```
|
7
|
+
Type of result of the operation is up to you, eg you could use [`Dry::Monads`](https://dry-rb.org/gems/dry-monads/1.3/).
|
15
8
|
|
16
9
|
## Features
|
17
10
|
|
@@ -44,15 +37,16 @@ class ShelveBookOperation < ::TypedOperation::Base
|
|
44
37
|
# Or if you prefer:
|
45
38
|
# `param :description, String`
|
46
39
|
|
47
|
-
|
48
|
-
|
40
|
+
# `param` creates named parameters by default
|
41
|
+
param :author_id, Integer, &:to_i
|
42
|
+
param :isbn, String
|
49
43
|
|
50
44
|
# Optional parameters are specified by wrapping the type constraint in the `optional` method, or using the `optional:` option
|
51
|
-
|
45
|
+
param :shelf_code, optional(Integer)
|
52
46
|
# Or if you prefer:
|
53
47
|
# `named_param :shelf_code, Integer, optional: true`
|
54
48
|
|
55
|
-
|
49
|
+
param :category, String, default: "unknown".freeze
|
56
50
|
|
57
51
|
# optional hook called when the operation is initialized, and after the parameters have been set
|
58
52
|
def prepare
|
@@ -176,8 +170,10 @@ Create an operation by subclassing `TypedOperation::Base` or `TypedOperation::Im
|
|
176
170
|
|
177
171
|
- `TypedOperation::Base` (uses `Literal::Struct`) is the parent class for an operation where the arguments are potentially mutable (ie not frozen).
|
178
172
|
No attribute writer methods are defined, so the arguments can not be changed after initialization, but the values passed in are not guaranteed to be frozen.
|
179
|
-
- `TypedOperation::ImmutableBase` (uses `Literal::Data`) is the parent class for an operation where the
|
180
|
-
thus giving a somewhat stronger immutability guarantee
|
173
|
+
- `TypedOperation::ImmutableBase` (uses `Literal::Data`) is the parent class for an operation where the operation instance is frozen on initialization,
|
174
|
+
thus giving a somewhat stronger immutability guarantee.
|
175
|
+
|
176
|
+
> Note: you cannot include `TypedOperation::ActionPolicyAuth` into a `TypedOperation::ImmutableBase`.
|
181
177
|
|
182
178
|
The subclass must implement the `#perform` method which is where the operations main work is done.
|
183
179
|
|
@@ -521,7 +517,8 @@ This is an example of a `ApplicationOperation` in a Rails app that uses `Dry::Mo
|
|
521
517
|
|
522
518
|
class ApplicationOperation < ::TypedOperation::Base
|
523
519
|
# We choose to use dry-monads for our operations, so include the required modules
|
524
|
-
include Dry::Monads[:result
|
520
|
+
include Dry::Monads[:result]
|
521
|
+
include Dry::Monads::Do.for(:perform)
|
525
522
|
|
526
523
|
class << self
|
527
524
|
# Setup our own preferred names for the DSL methods
|
@@ -564,6 +561,8 @@ end
|
|
564
561
|
|
565
562
|
### Using with Action Policy (`action_policy` gem)
|
566
563
|
|
564
|
+
> Note, this optional feature requires the `action_policy` gem to be installed and does not yet work with `ImmutableBase`.
|
565
|
+
|
567
566
|
Add `TypedOperation::ActionPolicyAuth` to your `ApplicationOperation` (first `require` the module):
|
568
567
|
|
569
568
|
```ruby
|
@@ -709,40 +708,6 @@ end
|
|
709
708
|
|
710
709
|
Note you are provided the ActionPolicy error object, but you cannot stop the error from being re-raised.
|
711
710
|
|
712
|
-
### Using with `literal` monads
|
713
|
-
|
714
|
-
You can use the `literal` gem to provide a `Result` type for your operations.
|
715
|
-
|
716
|
-
```ruby
|
717
|
-
class MyOperation < ::TypedOperation::Base
|
718
|
-
param :account_name, String
|
719
|
-
param :owner, String
|
720
|
-
|
721
|
-
def perform
|
722
|
-
create_account.bind do |account|
|
723
|
-
associate_owner(account).map { account }
|
724
|
-
end
|
725
|
-
end
|
726
|
-
|
727
|
-
private
|
728
|
-
|
729
|
-
def create_account
|
730
|
-
# ...
|
731
|
-
# Literal::Failure.new(:cant_create_account)
|
732
|
-
Literal::Success.new(account_name)
|
733
|
-
end
|
734
|
-
|
735
|
-
def associate_owner(account)
|
736
|
-
# ...
|
737
|
-
Literal::Failure.new(:cant_associate_owner)
|
738
|
-
# Literal::Success.new("ok")
|
739
|
-
end
|
740
|
-
end
|
741
|
-
|
742
|
-
MyOperation.new(account_name: "foo", owner: "bar").call
|
743
|
-
# => Literal::Failure(:cant_associate_owner)
|
744
|
-
```
|
745
|
-
|
746
711
|
### Using with `Dry::Monads`
|
747
712
|
|
748
713
|
As per the example in [`Dry::Monads` documentation](https://dry-rb.org/gems/dry-monads/1.0/do-notation/)
|
@@ -750,14 +715,14 @@ As per the example in [`Dry::Monads` documentation](https://dry-rb.org/gems/dry-
|
|
750
715
|
```ruby
|
751
716
|
class MyOperation < ::TypedOperation::Base
|
752
717
|
include Dry::Monads[:result]
|
753
|
-
include Dry::Monads::Do.for(:
|
718
|
+
include Dry::Monads::Do.for(:perform, :create_account)
|
754
719
|
|
755
720
|
param :account_name, String
|
756
721
|
param :owner, ::Owner
|
757
722
|
|
758
723
|
def perform
|
759
724
|
account = yield create_account(account_name)
|
760
|
-
yield
|
725
|
+
yield AnotherOperation.call(account, owner)
|
761
726
|
|
762
727
|
Success(account)
|
763
728
|
end
|
@@ -797,8 +762,11 @@ bin/rails g typed_operation:install
|
|
797
762
|
Use the `--dry_monads` switch to `include Dry::Monads[:result]` into your `ApplicationOperation` (don't forget to also
|
798
763
|
add `gem "dry-monads"` to your Gemfile)
|
799
764
|
|
765
|
+
Use the `--action_policy` switch to add the `TypedOperation::ActionPolicyAuth` module to your `ApplicationOperation`
|
766
|
+
(and you will also need to add `gem "action_policy"` to your Gemfile).
|
767
|
+
|
800
768
|
```ruby
|
801
|
-
bin/rails g typed_operation:install --dry_monads
|
769
|
+
bin/rails g typed_operation:install --dry_monads --action_policy
|
802
770
|
```
|
803
771
|
|
804
772
|
## Generate a new Operation
|
@@ -12,6 +12,7 @@ rails generate typed_operation:install
|
|
12
12
|
Options:
|
13
13
|
--------
|
14
14
|
--dry_monads: if specified the ApplicationOperation will include dry-monads Result and Do notation.
|
15
|
+
--action_policy: if specified the ApplicationOperation will include action_policy authorization integration.
|
15
16
|
|
16
17
|
Example:
|
17
18
|
--------
|
@@ -6,6 +6,7 @@ module TypedOperation
|
|
6
6
|
module Install
|
7
7
|
class InstallGenerator < Rails::Generators::Base
|
8
8
|
class_option :dry_monads, type: :boolean, default: false
|
9
|
+
class_option :action_policy, type: :boolean, default: false
|
9
10
|
|
10
11
|
source_root File.expand_path("templates", __dir__)
|
11
12
|
|
@@ -18,6 +19,10 @@ module TypedOperation
|
|
18
19
|
def include_dry_monads?
|
19
20
|
options[:dry_monads]
|
20
21
|
end
|
22
|
+
|
23
|
+
def include_action_policy?
|
24
|
+
options[:action_policy]
|
25
|
+
end
|
21
26
|
end
|
22
27
|
end
|
23
28
|
end
|
@@ -1,14 +1,36 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class ApplicationOperation < ::TypedOperation::Base
|
4
|
+
<% if include_action_policy? -%>
|
5
|
+
include TypedOperation::ActionPolicyAuth
|
6
|
+
|
7
|
+
<% end -%>
|
4
8
|
<% if include_dry_monads? -%>
|
5
|
-
include Dry::Monads[:result
|
9
|
+
include Dry::Monads[:result]
|
10
|
+
include Dry::Monads::Do.for(:perform)
|
6
11
|
|
12
|
+
# Helper to execute then unwrap a successful result or raise an exception
|
7
13
|
def call!
|
8
14
|
call.value!
|
9
15
|
end
|
10
16
|
|
11
17
|
<% end -%>
|
12
18
|
# Other common parameters & methods for Operations of this application...
|
13
|
-
#
|
19
|
+
# Some examples:
|
20
|
+
#
|
21
|
+
# def self.operation_key
|
22
|
+
# name.underscore.to_sym
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# def operation_key
|
26
|
+
# self.class.operation_key
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# # Translation and localization
|
30
|
+
#
|
31
|
+
# def translate(key, **)
|
32
|
+
# key = "operations.#{operation_key}.#{key}" if key.start_with?(".")
|
33
|
+
# I18n.t(key, **)
|
34
|
+
# end
|
35
|
+
# alias_method :t, :translate
|
14
36
|
end
|
@@ -34,7 +34,7 @@ module TypedOperation
|
|
34
34
|
raise ArgumentError, "authorize_via must be called with a valid param name" unless via.all? { |param| parameters.include?(param) }
|
35
35
|
@_authorized_via_param = via
|
36
36
|
|
37
|
-
action_type_method = "#{action_type}?"
|
37
|
+
action_type_method = :"#{action_type}?" if action_type
|
38
38
|
# If an method name is provided, use it
|
39
39
|
policy_method = to || action_type_method || raise(::TypedOperation::InvalidOperationError, "You must provide an action type or policy method name")
|
40
40
|
@_policy_method = policy_method
|
data/lib/typed_operation/base.rb
CHANGED
@@ -8,18 +8,6 @@ module TypedOperation
|
|
8
8
|
|
9
9
|
include Operations::Lifecycle
|
10
10
|
include Operations::Callable
|
11
|
-
include Operations::Deconstruct
|
12
11
|
include Operations::Executable
|
13
|
-
|
14
|
-
class << self
|
15
|
-
def attribute(name, type, special = nil, reader: :public, writer: :public, positional: false, default: nil)
|
16
|
-
super(name, type, special, reader:, writer: false, positional:, default:)
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
def with(...)
|
21
|
-
# copy to new operation with new attrs
|
22
|
-
self.class.new(**attributes.merge(...))
|
23
|
-
end
|
24
12
|
end
|
25
13
|
end
|
@@ -5,25 +5,23 @@ module TypedOperation
|
|
5
5
|
# Introspection methods
|
6
6
|
module Introspection
|
7
7
|
def positional_parameters
|
8
|
-
|
8
|
+
literal_properties.filter_map { |property| property.name if property.positional? }
|
9
9
|
end
|
10
10
|
|
11
11
|
def keyword_parameters
|
12
|
-
|
12
|
+
literal_properties.filter_map { |property| property.name if property.keyword? }
|
13
13
|
end
|
14
14
|
|
15
15
|
def required_parameters
|
16
|
-
|
17
|
-
attribute.default.nil? # Any optional parameters will have a default value/proc in their Literal::Attribute
|
18
|
-
end
|
16
|
+
literal_properties.filter { |property| property.required? }
|
19
17
|
end
|
20
18
|
|
21
19
|
def required_positional_parameters
|
22
|
-
required_parameters.filter_map { |
|
20
|
+
required_parameters.filter_map { |property| property.name if property.positional? }
|
23
21
|
end
|
24
22
|
|
25
23
|
def required_keyword_parameters
|
26
|
-
required_parameters.filter_map { |
|
24
|
+
required_parameters.filter_map { |property| property.name if property.keyword? }
|
27
25
|
end
|
28
26
|
|
29
27
|
def optional_positional_parameters
|
@@ -4,10 +4,19 @@ module TypedOperation
|
|
4
4
|
module Operations
|
5
5
|
# Method to define parameters for your operation.
|
6
6
|
module Parameters
|
7
|
+
# Override literal `prop` to prevent creating writers (Literal::Data does this by default)
|
8
|
+
def prop(name, type, kind = :keyword, reader: :public, writer: :public, default: nil)
|
9
|
+
if self < ImmutableBase
|
10
|
+
super(name, type, kind, reader:, default:)
|
11
|
+
else
|
12
|
+
super(name, type, kind, reader:, writer: false, default:)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
7
16
|
# Parameter for keyword argument, or a positional argument if you use positional: true
|
8
17
|
# Required, but you can set a default or use optional: true if you want optional
|
9
18
|
def param(name, signature = :any, **options, &converter)
|
10
|
-
|
19
|
+
PropertyBuilder.new(self, name, signature, options).define(&converter)
|
11
20
|
end
|
12
21
|
|
13
22
|
# Alternative DSL
|
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
module TypedOperation
|
4
4
|
module Operations
|
5
|
-
class
|
5
|
+
class PropertyBuilder
|
6
6
|
def initialize(typed_operation, parameter_name, type_signature, options)
|
7
7
|
@typed_operation = typed_operation
|
8
8
|
@name = parameter_name
|
9
9
|
@signature = type_signature
|
10
|
-
@optional = options[:optional]
|
11
|
-
@positional = options[:positional]
|
10
|
+
@optional = options[:optional] # Wraps signature in NilableType
|
11
|
+
@positional = options[:positional] # Changes kind to positional
|
12
12
|
@reader = options[:reader] || :public
|
13
13
|
@default_key = options.key?(:default)
|
14
14
|
@default = options[:default]
|
@@ -23,11 +23,11 @@ module TypedOperation
|
|
23
23
|
else
|
24
24
|
converter
|
25
25
|
end
|
26
|
-
@typed_operation.
|
26
|
+
@typed_operation.prop(
|
27
27
|
@name,
|
28
28
|
@signature,
|
29
|
+
@positional ? :positional : :keyword,
|
29
30
|
default: default_value_for_literal,
|
30
|
-
positional: @positional,
|
31
31
|
reader: @reader,
|
32
32
|
&coerce_by
|
33
33
|
)
|
@@ -51,7 +51,7 @@ module TypedOperation
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def union_with_nil_to_support_nil_default
|
54
|
-
@signature = Literal::
|
54
|
+
@signature = Literal::Types::UnionType.new(@signature, NilClass) if has_default_value_nil?
|
55
55
|
end
|
56
56
|
|
57
57
|
def has_default_value_nil?
|
data/lib/typed_operation.rb
CHANGED
@@ -1,7 +1,3 @@
|
|
1
|
-
if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("3.2.0")
|
2
|
-
require "polyfill-data"
|
3
|
-
end
|
4
|
-
|
5
1
|
require "literal"
|
6
2
|
|
7
3
|
require "typed_operation/version"
|
@@ -11,8 +7,7 @@ require "typed_operation/operations/parameters"
|
|
11
7
|
require "typed_operation/operations/partial_application"
|
12
8
|
require "typed_operation/operations/callable"
|
13
9
|
require "typed_operation/operations/lifecycle"
|
14
|
-
require "typed_operation/operations/
|
15
|
-
require "typed_operation/operations/attribute_builder"
|
10
|
+
require "typed_operation/operations/property_builder"
|
16
11
|
require "typed_operation/operations/executable"
|
17
12
|
require "typed_operation/curried"
|
18
13
|
require "typed_operation/immutable_base"
|
metadata
CHANGED
@@ -1,15 +1,34 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: typed_operation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.beta3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Stephen Ierodiaconou
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
10
|
+
date: 2025-04-07 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: literal
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: 1.0.0
|
19
|
+
- - "<"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 2.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 1.0.0
|
29
|
+
- - "<"
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 2.0.0
|
13
32
|
description: Command pattern, which is callable, and can be partially applied, curried
|
14
33
|
and has typed parameters. Authorization to execute via action_policy if desired.
|
15
34
|
email:
|
@@ -32,14 +51,13 @@ files:
|
|
32
51
|
- lib/typed_operation/base.rb
|
33
52
|
- lib/typed_operation/curried.rb
|
34
53
|
- lib/typed_operation/immutable_base.rb
|
35
|
-
- lib/typed_operation/operations/attribute_builder.rb
|
36
54
|
- lib/typed_operation/operations/callable.rb
|
37
|
-
- lib/typed_operation/operations/deconstruct.rb
|
38
55
|
- lib/typed_operation/operations/executable.rb
|
39
56
|
- lib/typed_operation/operations/introspection.rb
|
40
57
|
- lib/typed_operation/operations/lifecycle.rb
|
41
58
|
- lib/typed_operation/operations/parameters.rb
|
42
59
|
- lib/typed_operation/operations/partial_application.rb
|
60
|
+
- lib/typed_operation/operations/property_builder.rb
|
43
61
|
- lib/typed_operation/partially_applied.rb
|
44
62
|
- lib/typed_operation/prepared.rb
|
45
63
|
- lib/typed_operation/railtie.rb
|
@@ -50,7 +68,6 @@ licenses:
|
|
50
68
|
metadata:
|
51
69
|
homepage_uri: https://github.com/stevegeek/typed_operation
|
52
70
|
source_code_uri: https://github.com/stevegeek/typed_operation
|
53
|
-
post_install_message:
|
54
71
|
rdoc_options: []
|
55
72
|
require_paths:
|
56
73
|
- lib
|
@@ -58,15 +75,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
75
|
requirements:
|
59
76
|
- - ">="
|
60
77
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
78
|
+
version: '3.2'
|
62
79
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
80
|
requirements:
|
64
|
-
- - "
|
81
|
+
- - ">="
|
65
82
|
- !ruby/object:Gem::Version
|
66
|
-
version:
|
83
|
+
version: '0'
|
67
84
|
requirements: []
|
68
|
-
rubygems_version: 3.
|
69
|
-
signing_key:
|
85
|
+
rubygems_version: 3.6.2
|
70
86
|
specification_version: 4
|
71
87
|
summary: TypedOperation is a command pattern with typed parameters, which is callable,
|
72
88
|
and can be partially applied.
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module TypedOperation
|
4
|
-
module Operations
|
5
|
-
module Deconstruct
|
6
|
-
def deconstruct
|
7
|
-
attributes.values
|
8
|
-
end
|
9
|
-
|
10
|
-
def deconstruct_keys(keys)
|
11
|
-
h = attributes.to_h
|
12
|
-
keys ? h.slice(*keys) : h
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|