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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 464b0e645a604caaee0ab2b83f21b56cb0ca7a6b6b69e3475dcce17366ed2c80
4
- data.tar.gz: c59c8522a9c8c04be5a589649596b85bd42cb8f4faeae4073230fd42470556ce
3
+ metadata.gz: d6d2e6d373445f7a0c186b88963fb6cadb3947f32731f4411fbeb71588e65d63
4
+ data.tar.gz: 1329be34dbc54ba576c97dae12d82b513696b420ef34885842b6d8b0bb11b643
5
5
  SHA512:
6
- metadata.gz: 7c42242a9df90fd0751b3e5fcd77a6b71ded4e9bc5cfc7725082e2d1ff200643dce87c10e7f80d2ae2eececb2d00231083396ed1d2aaf10a4fb88e1eaa287aa3
7
- data.tar.gz: bfad02c10284d3895956b3390ac6529f1b815286f19e2cb5644ed5b687d01e2248f664cc166925d9db4a521ebb20956143fb9a7c98592c193915cbb812ffd90b
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 [`literal` monads](https://github.com/joeldrapper/literal) or [`Dry::Monads`](https://dry-rb.org/gems/dry-monads/1.3/).
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
- named_param :author_id, Integer, &:to_i
48
- named_param :isbn, String
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
- named_param :shelf_code, optional(Integer)
45
+ param :shelf_code, optional(Integer)
52
46
  # Or if you prefer:
53
47
  # `named_param :shelf_code, Integer, optional: true`
54
48
 
55
- named_param :category, String, default: "unknown".freeze
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 arguments are immutable (frozen on initialization),
180
- thus giving a somewhat stronger immutability guarantee (ie that the operation does not mutate its arguments).
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, :do]
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(:call)
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 associate_owner(account, owner)
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, :do]
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}?".to_sym if 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
@@ -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
@@ -8,7 +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
12
  end
14
13
  end
@@ -5,25 +5,23 @@ module TypedOperation
5
5
  # Introspection methods
6
6
  module Introspection
7
7
  def positional_parameters
8
- literal_attributes.filter_map { |name, attribute| name if attribute.positional? }
8
+ literal_properties.filter_map { |property| property.name if property.positional? }
9
9
  end
10
10
 
11
11
  def keyword_parameters
12
- literal_attributes.filter_map { |name, attribute| name unless attribute.positional? }
12
+ literal_properties.filter_map { |property| property.name if property.keyword? }
13
13
  end
14
14
 
15
15
  def required_parameters
16
- literal_attributes.filter do |name, attribute|
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 { |name, attribute| name if attribute.positional? }
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 { |name, attribute| name unless attribute.positional? }
24
+ required_parameters.filter_map { |property| property.name if property.keyword? }
27
25
  end
28
26
 
29
27
  def optional_positional_parameters
@@ -4,7 +4,7 @@ module TypedOperation
4
4
  module Operations
5
5
  module Lifecycle
6
6
  # This is called by Literal on initialization of underlying Struct/Data
7
- def after_initialization
7
+ def after_initialize
8
8
  prepare if respond_to?(:prepare)
9
9
  end
10
10
  end
@@ -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
- AttributeBuilder.new(self, name, signature, options).define(&converter)
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 AttributeBuilder
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.attribute(
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::Union.new(@signature, NilClass) if has_default_value_nil?
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?
@@ -1,3 +1,3 @@
1
1
  module TypedOperation
2
- VERSION = "1.0.0.beta1"
2
+ VERSION = "1.0.0.beta3"
3
3
  end
@@ -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/deconstruct"
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.beta1
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: 2023-08-26 00:00:00.000000000 Z
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.1'
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: 1.3.1
83
+ version: '0'
67
84
  requirements: []
68
- rubygems_version: 3.4.19
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