operations 0.7.1 → 0.7.2

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: 41842fbe905d259c1c39bd2922caa0dc50583cdfceac0c0b78ce7e4e8e1d26f4
4
- data.tar.gz: 37e703dca9a9db80167e2750e77af20e59494a3fb1c0f208720e08ae2bb83a8e
3
+ metadata.gz: affb68a66ecbc1ee0e687ab13efb202037d10054adb114e684e775224957b65e
4
+ data.tar.gz: 255180d0be3704a73c0bf6e148658df8b662e33a4508dff3ae3fee819e73caba
5
5
  SHA512:
6
- metadata.gz: 33ebaee09aff4d2cd32742566e1c3959c5f43558b6e3c57760936a31f8431af2806a8b6b8807da10588fbf2916de5e34857cc9ce5378b93d70099dd09b049d78
7
- data.tar.gz: 1f733f0567064105528b480d47f766aa6acd9b2dd2766d011e11edeb542f3eea6233f2e30cb810d10f4e9412ab9548a186643072dd80414cf07b9aecc7189570
6
+ metadata.gz: 9f639386ccb6d003909737a4606b44f21cf49dc90b36fe0c854ea1e8c824049b2de7a70c480b44b2f4713558f21417c755361b6fccad4ed2f2af1068bc7cd135
7
+ data.tar.gz: a24d378627511c1198728a9aebe38c7f39cb057756e957906df29bf059804eef94d1188146b845842be42d94c559ebccd84b3d4d6a613b95a2a31d427771fe09
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2024-05-05 08:51:48 UTC using RuboCop version 1.59.0.
3
+ # on 2024-08-07 02:24:58 UTC using RuboCop version 1.65.0.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -22,14 +22,14 @@ Metrics/AbcSize:
22
22
  # Offense count: 1
23
23
  # Configuration parameters: CountComments, CountAsOne.
24
24
  Metrics/ClassLength:
25
- Max: 145
25
+ Max: 149
26
26
 
27
27
  # Offense count: 1
28
28
  # Configuration parameters: CountComments, CountAsOne.
29
29
  Metrics/ModuleLength:
30
- Max: 142
30
+ Max: 141
31
31
 
32
- # Offense count: 2
32
+ # Offense count: 3
33
33
  # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
34
34
  Metrics/ParameterLists:
35
35
  Max: 7
data/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.7.2](https://github.com/BookingSync/operations/tree/v0.7.2)
4
+
5
+ ### Added
6
+
7
+ - Allow referencing and arbitrary model attrbiute from form object attribute with `model_name: "Post#title"` [\#50](https://github.com/BookingSync/operations/pull/50) ([pyromaniac](https://github.com/pyromaniac))
8
+ - Allow passing multiple `hydrators:` to Operations::Form [\#49](https://github.com/BookingSync/operations/pull/49) ([pyromaniac](https://github.com/pyromaniac))
9
+
10
+ ### Improvements
11
+
12
+ - Change default form hydration behavior - it is now deep merging params after hydration, so no need to do it in the hydrator. Controlled with `hydration_merge_params:` option. [\#49](https://github.com/BookingSync/operations/pull/49) ([pyromaniac](https://github.com/pyromaniac))
13
+
3
14
  ## [0.7.1](https://github.com/BookingSync/operations/tree/v0.7.1)
4
15
 
5
16
  ### Added
data/README.md CHANGED
@@ -312,7 +312,7 @@ class ActiveRecordRepository
312
312
  include Dry::Monads[:result]
313
313
  extend Dry::Initializer
314
314
 
315
- param :model, type: Types.Instance(Class).constrained(lt: ActiveRecord::Base)
315
+ param :model, type: Types::Class.constrained(lt: ActiveRecord::Base)
316
316
 
317
317
  def create(**attributes)
318
318
  record = model.new(**attributes)
@@ -904,7 +904,7 @@ class Post::Update
904
904
  def self.default_form
905
905
  @default_form ||= Operations::Form.new(
906
906
  default,
907
- hydrator: Post::Update::Hydrator.new
907
+ hydrators: [Post::Update::Hydrator.new]
908
908
  )
909
909
  end
910
910
  end
@@ -912,9 +912,7 @@ end
912
912
  class Post::Update::Hydrator
913
913
  def call(form_class, params, post:, **_context)
914
914
  value_attributes = form_class.attributes.keys - %i[post_id]
915
- data = value_attributes.index_with { |name| post.public_send(name) }
916
-
917
- data.merge!(params)
915
+ value_attributes.index_with { |name| post.public_send(name) }
918
916
  end
919
917
  end
920
918
  ```
@@ -929,14 +927,20 @@ class Post::Update
929
927
  @default_form ||= Operations::Form.new(
930
928
  default,
931
929
  model_map: Post::Update::ModelMap.new,
932
- hydrator: Post::Update::Hydrator.new
930
+ hydrators: [Post::Update::Hydrator.new]
933
931
  )
934
932
  end
935
933
  end
936
934
 
937
935
  class Post::Update::ModelMap
938
- def call(_path)
939
- Post
936
+ MAPPING = {
937
+ %w[published_at] => Post, # a model can be passed but beware of circular dependencies, better use strings
938
+ %w[title] => "Post", # or a model name - safer option
939
+ %w[content] => "Post#body" # referencing different attribute is possible, useful for naming migration or translations
940
+ }.freeze
941
+
942
+ def call(path)
943
+ MAPPING[path] # returns the mapping for a single path
940
944
  end
941
945
  end
942
946
  ```
data/UPGRADING_FORMS.md CHANGED
@@ -31,7 +31,7 @@ class Post::Update
31
31
  def self.default_form
32
32
  @default_form ||= Operations::Form.new(
33
33
  default,
34
- hydrator: Post::Update::Hydrator.new,
34
+ hydrators: [Post::Update::Hydrator.new],
35
35
  model_map: Post::Update::ModelMap.new,
36
36
  params_transformations: [
37
37
  ParamsMap.new(id: :post_id)
@@ -197,6 +197,11 @@ class Operations::Command
197
197
  result_policies = policies_sum - [Undefined] unless policies_sum == [Undefined, Undefined]
198
198
  options[:policies] = result_policies if result_policies
199
199
 
200
+ if after.present?
201
+ ActiveSupport::Deprecation.new.warn("Operations::Command `after:` option is deprecated and will be " \
202
+ "removed in 1.0.0. Please use `on_success:` instead")
203
+ end
204
+
200
205
  preconditions.push(precondition) if precondition.present?
201
206
  super(operation, preconditions: preconditions, on_success: after, **options)
202
207
  end
@@ -5,39 +5,35 @@
5
5
  # legacy UI.
6
6
  class Operations::Form::Attribute
7
7
  extend Dry::Initializer
8
- include Dry::Equalizer(:name, :collection, :model_name, :form)
9
- include Operations::Inspect.new(:name, :collection, :model_name, :form)
8
+ include Dry::Equalizer(:name, :collection, :model_class, :model_attribute, :form)
9
+ include Operations::Inspect.new(:name, :collection, :model_class, :model_attribute, :form)
10
10
 
11
11
  param :name, type: Operations::Types::Coercible::Symbol
12
12
  option :collection, type: Operations::Types::Bool, default: proc { false }
13
- option :model_name,
14
- type: (Operations::Types::String | Operations::Types.Instance(Class).constrained(lt: ActiveRecord::Base)).optional,
15
- default: proc {}
13
+ option :model_name, type: (Operations::Types::String | Operations::Types::Class).optional, default: proc {}
16
14
  option :form, type: Operations::Types::Class.optional, default: proc {}
17
15
 
18
- def model_type
19
- @model_type ||= owning_model.type_for_attribute(string_name) if model_name
20
- end
16
+ def model_class
17
+ return @model_class if defined?(@model_class)
21
18
 
22
- def model_human_name(options = {})
23
- owning_model.human_attribute_name(string_name, options) if model_name
19
+ @model_class = model_name.is_a?(String) ? model_name.split("#").first.constantize : model_name
24
20
  end
25
21
 
26
- def model_validators
27
- @model_validators ||= model_name ? owning_model.validators_on(string_name) : []
28
- end
22
+ def model_attribute
23
+ return @model_attribute if defined?(@model_attribute)
29
24
 
30
- def model_localized_attr_name(locale)
31
- owning_model.localized_attr_name_for(string_name, locale) if model_name
25
+ @model_attribute = model_class && (model_name.to_s.split("#").second.presence || name.to_s)
32
26
  end
33
27
 
34
- private
28
+ def model_type
29
+ model_class.type_for_attribute(model_attribute) if model_name
30
+ end
35
31
 
36
- def owning_model
37
- @owning_model ||= model_name.is_a?(String) ? model_name.constantize : model_name
32
+ def model_human_name(options = {})
33
+ model_class.human_attribute_name(model_attribute, options) if model_name
38
34
  end
39
35
 
40
- def string_name
41
- @string_name ||= name.to_s
36
+ def model_validators
37
+ model_name ? model_class.validators_on(model_attribute) : []
42
38
  end
43
39
  end
@@ -50,7 +50,13 @@ class Operations::Form::Base
50
50
  base.class_attribute :persisted, instance_accessor: false, default: nil
51
51
 
52
52
  base.define_method :initialize do |*args, **kwargs|
53
- args.empty? && kwargs.present? ? super(kwargs, **{}) : super(*args, **kwargs)
53
+ if args.empty?
54
+ # Initializing Operations::Form::Base instance
55
+ super(kwargs, **{})
56
+ else
57
+ # Initializing Operations::Form instance as form object (deprecated)
58
+ super(*args, **kwargs)
59
+ end
54
60
  end
55
61
  end
56
62
 
@@ -83,12 +89,13 @@ class Operations::Form::Base
83
89
 
84
90
  # :nodoc:
85
91
  module InstanceMethods
86
- def type_for_attribute(name)
87
- self.class.attributes[name.to_sym].model_type
92
+ # Copied from globalize-accessors, should be deprecated and removed as it is not a core method
93
+ def localized_attr_name_for(attr_name, locale)
94
+ "#{attr_name}_#{locale.to_s.underscore}"
88
95
  end
89
96
 
90
- def localized_attr_name_for(name, locale)
91
- self.class.attributes[name.to_sym].model_localized_attr_name(locale)
97
+ def type_for_attribute(name)
98
+ self.class.attributes[name.to_sym].model_type
92
99
  end
93
100
 
94
101
  def has_attribute?(name) # rubocop:disable Naming/PredicateName
@@ -21,8 +21,10 @@
21
21
  #
22
22
  class Operations::Form
23
23
  include Dry::Core::Constants
24
- include Dry::Equalizer(:command, :model_map, :persisted, :params_transformations, :hydrator, :form_class)
25
- include Operations::Inspect.new(:model_name, :model_map, :persisted, :params_transformations, :hydrator, :form_class)
24
+ include Dry::Equalizer(:command, :model_map, :persisted,
25
+ :params_transformations, :hydrators, :hydration_merge_params, :form_class)
26
+ include Operations::Inspect.new(:model_name, :model_map, :persisted,
27
+ :params_transformations, :hydrators, :hydration_merge_params, :form_class)
26
28
 
27
29
  # We need to make deprecated inheritance from Operations::Form act exactly the
28
30
  # same way as from Operations::Form::Base. In order to do this, we are encapsulating all the
@@ -51,10 +53,17 @@ class Operations::Form
51
53
  option :persisted, type: Operations::Types::Bool, default: proc { true }
52
54
  option :params_transformations, type: Operations::Types::Coercible::Array.of(Operations::Types.Interface(:call)),
53
55
  default: proc { [] }
54
- option :hydrator, type: Operations::Types.Interface(:call).optional, default: proc {}
56
+ option :hydrators, type: Operations::Types::Array.of(Operations::Types.Interface(:call)), default: proc { [] }
57
+ option :hydration_merge_params, type: Operations::Types::Bool, default: proc { true }
55
58
  option :base_class, type: Operations::Types::Class, default: proc { ::Operations::Form::Base }
56
59
  end)
57
60
 
61
+ def initialize(command, hydrator: nil, hydrators: [], **options)
62
+ hydrators.push(hydrator) if hydrator.present?
63
+
64
+ super(command, hydrators: hydrators, **options)
65
+ end
66
+
58
67
  def build(params = EMPTY_HASH, **context)
59
68
  instantiate_form(command.callable(transform_params(params, **context), **context))
60
69
  end
@@ -81,12 +90,20 @@ class Operations::Form
81
90
 
82
91
  def instantiate_form(operation_result)
83
92
  form_class.new(
84
- hydrator&.call(form_class, operation_result.params, **operation_result.context) || {},
93
+ hydrate_params(form_class, operation_result.params, **operation_result.context),
85
94
  messages: operation_result.errors.to_h,
86
95
  operation_result: operation_result
87
96
  )
88
97
  end
89
98
 
99
+ def hydrate_params(form_class, params, **context)
100
+ hydrated_params = hydrators.inject({}) do |value, hydrator|
101
+ value.merge(hydrator.call(form_class, params, **context).deep_symbolize_keys)
102
+ end
103
+ hydrated_params.deep_merge!(params) if hydration_merge_params
104
+ hydrated_params
105
+ end
106
+
90
107
  def key_map
91
108
  @key_map ||= command.contract.schema.key_map
92
109
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Operations
4
- VERSION = "0.7.1"
4
+ VERSION = "0.7.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: operations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.7.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arkadiy Zabazhanov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-07-29 00:00:00.000000000 Z
11
+ date: 2024-08-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal