operations 0.7.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 37feed93ae3f90b8e08f2ab960faf4195c8891e6bc4d60a78d03666c5c035de3
4
- data.tar.gz: 29b2bad869d32e4bf22f188eb4c57a199300a315431f373d5cf988f15f63ab8b
3
+ metadata.gz: 41842fbe905d259c1c39bd2922caa0dc50583cdfceac0c0b78ce7e4e8e1d26f4
4
+ data.tar.gz: 37e703dca9a9db80167e2750e77af20e59494a3fb1c0f208720e08ae2bb83a8e
5
5
  SHA512:
6
- metadata.gz: 186e9327e813bbf7ddd5d5aea52b5bf00cc7a62a9c8b235b7fd25f36585a2468192922df3ff9435d7320b164807e31acf73523eaf9b106c67de6f2f27da19400
7
- data.tar.gz: 43d61db8dbe71a4f9269755a6fc73c46214ba3baddeed80e650a7ae2ced43c08fb361dd19cef005954504970c06bfa573a1a55f760e7ac711c68c19f4e34601b
6
+ metadata.gz: 33ebaee09aff4d2cd32742566e1c3959c5f43558b6e3c57760936a31f8431af2806a8b6b8807da10588fbf2916de5e34857cc9ce5378b93d70099dd09b049d78
7
+ data.tar.gz: 1f733f0567064105528b480d47f766aa6acd9b2dd2766d011e11edeb542f3eea6233f2e30cb810d10f4e9412ab9548a186643072dd80414cf07b9aecc7189570
data/CHANGELOG.md CHANGED
@@ -1,10 +1,16 @@
1
1
  # Changelog
2
2
 
3
- ## [0.7.0](https://github.com/BookingSync/operations/tree/main)
3
+ ## [0.7.1](https://github.com/BookingSync/operations/tree/v0.7.1)
4
4
 
5
5
  ### Added
6
6
 
7
- - Implement new forms system detaching it from operations [\#47](https://github.com/BookingSync/operations/pull/47) ([pyromaniac](https://github.com/pyromaniac))
7
+ - Added `persisted:` option to the new forms definition. [\#48](https://github.com/BookingSync/operations/pull/48) ([pyromaniac](https://github.com/pyromaniac))
8
+
9
+ ## [0.7.0](https://github.com/BookingSync/operations/tree/v0.7.0)
10
+
11
+ ### Added
12
+
13
+ - Implement new forms system detaching it from operations. Please check [UPGRADING_FORMS.md](UPGRADING_FORMS.md) for more details. [\#47](https://github.com/BookingSync/operations/pull/47) ([pyromaniac](https://github.com/pyromaniac))
8
14
 
9
15
  ### Improvements
10
16
 
@@ -15,7 +21,7 @@
15
21
 
16
22
  - In some cases, `Operation::Command#form_class` was evaluated before `form_base` was evaluated [\#41](https://github.com/BookingSync/operations/pull/41) ([pyromaniac](https://github.com/pyromaniac))
17
23
 
18
- ## [0.6.2]
24
+ ## [0.6.3](https://github.com/BookingSync/operations/tree/v0.6.3)
19
25
 
20
26
  ### Added
21
27
 
data/README.md CHANGED
@@ -853,6 +853,8 @@ In this case, `Order::MarkAsCompleted.system.call(...)` will be used in, say, co
853
853
 
854
854
  ### Form objects
855
855
 
856
+ Form objects were refactored to be separate from Command. Please check [UPGRADING_FORMS.md](UPGRADING_FORMS.md) for more details.
857
+
856
858
  While we normally recommend using frontend-backend separation, it is still possible to use this framework with Rails view helpers:
857
859
 
858
860
  ```ruby
@@ -885,7 +887,7 @@ class Post::Update
885
887
  end
886
888
  ```
887
889
 
888
- Then, the form can be used as any other form object:
890
+ Then, the form can be used as any other form object. Unfortunately, there is no way to figure out the correct route for the operation form object, so it have to be provided manually:
889
891
 
890
892
  ```erb
891
893
  # views/posts/edit.html.erb
@@ -939,7 +941,18 @@ class Post::Update::ModelMap
939
941
  end
940
942
  ```
941
943
 
942
- In forms, params input is already transformed to extract the nested data with the form name. `form_for @post_update_form` will generate the form that send params nested under the `params[:post_update_form]` key. By default operation forms extract this form data and send it to the operation at the top level, so `{ id: 42, post_update_form: { title: "Post Title" } }` params will be sent to the operation as `{ id: 42, title: "Post Title" }`. Strong params are also accepted by the form, though they are being converted with `to_unsafe_hash`.
944
+ In forms, params input is already transformed to extract the nested data with the form name. `form_for @post_update_form` will generate the form that send params nested under the `params[:post_update_form]` key. By default operation forms extract this form data and send it to the operation at the top level, so `{ id: 42, post_update_form: { title: "Post Title" } }` params will be sent to the operation as `{ id: 42, title: "Post Title" }`. Strong params are also accepted by the form, though they are being converted with `to_unsafe_hash`. Though the form name can be customized if necessary:
945
+
946
+ ```ruby
947
+ class Post::Update
948
+ def self.default_form
949
+ @default_form ||= Operations::Form.new(
950
+ default,
951
+ model_name: "custom_post_update_form", # form name can be customized
952
+ )
953
+ end
954
+ end
955
+ ```
943
956
 
944
957
  It is possible to add more params transfomations to the form in cases when operation contract is different from the params structure:
945
958
 
@@ -948,7 +961,6 @@ class Post::Update
948
961
  def self.default_form
949
962
  @default_form ||= Operations::Form.new(
950
963
  default,
951
- model_name: "post_update_form", # form name can be customized
952
964
  params_transformations: [
953
965
  ParamsMap.new(id: :post_id),
954
966
  NestedAttributes.new(:sections)
@@ -994,6 +1006,21 @@ class NestedAttributes
994
1006
  end
995
1007
  ```
996
1008
 
1009
+ By default, the top-level form objects instantiated from the form will have `persisted?` flag set to `true`. This will result the form to use the `PATCH` werb like for any persisted AR object. If it is required to generate a form with the `POST` verb in case of operation, say, creating some objects, this default behavior can be customised:
1010
+
1011
+ ```ruby
1012
+ class Post::Create
1013
+ def self.default_form
1014
+ @default_form ||= Operations::Form.new(
1015
+ default,
1016
+ persisted: false
1017
+ )
1018
+ end
1019
+ end
1020
+ ```
1021
+
1022
+ Note that operation itself is agnostic to the persistence layer, so there is no way for it to figure out this semanticsa automatically.
1023
+
997
1024
  ## Development
998
1025
 
999
1026
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,101 @@
1
+ # New form objects
2
+
3
+ In version 0.7.0, a new form objects system were introduced. The old way of using form objects is deprecated and will be removed in 1.0.0. In order to upgrade your code to start using the new way please follow the guide:
4
+
5
+ ## Replace `Operations::Form` with `Operations::Form::Base`
6
+
7
+ If you have any classes that were inherited from `Operations::Form`, please change them to inherit from `Operations::Form::Base`
8
+
9
+ ## Define forms as separate objects on top of the existing operations:
10
+
11
+ ```ruby
12
+ # Before
13
+ class Post::Update
14
+ def self.default
15
+ @default ||= Operations::Command.new(
16
+ ...,
17
+ form_hydrator: Post::Update::Hydrator.new,
18
+ form_model_map: {
19
+ [%r{.+}] => "Post"
20
+ }
21
+ )
22
+ end
23
+ end
24
+
25
+ # After
26
+ class Post::Update
27
+ def self.default
28
+ @default ||= Operations::Command.new(...)
29
+ end
30
+
31
+ def self.default_form
32
+ @default_form ||= Operations::Form.new(
33
+ default,
34
+ hydrator: Post::Update::Hydrator.new,
35
+ model_map: Post::Update::ModelMap.new,
36
+ params_transformations: [
37
+ ParamsMap.new(id: :post_id)
38
+ ]
39
+ )
40
+ end
41
+ end
42
+ ```
43
+
44
+ Where `Post::Update::ModelMap` can be a copy of [Operations::Form::DeprecatedLegacyModelMapImplementation](https://github.com/BookingSync/operations/blob/main/lib/operations/form/deprecated_legacy_model_map_implementation.rb) or your own implementation.
45
+
46
+ And `ParamsMap` can be as simple as:
47
+
48
+ ```ruby
49
+ class ParamsMap
50
+ extend Dry::Initializer
51
+
52
+ param :params_map
53
+
54
+ def call(_form_class, params, **_context)
55
+ params.transform_keys { |key| params_map[key] || key }
56
+ end
57
+ end
58
+ ```
59
+
60
+ ## Change the way you use forms in you controllers and views:
61
+
62
+ ```ruby
63
+ # Before
64
+ class PostsController < ApplicationController
65
+ def edit
66
+ @post_update = Post::Update.default.callable(
67
+ { post_id: params[:id] },
68
+ current_user: current_user
69
+ )
70
+
71
+ respond_with @post_update
72
+ end
73
+
74
+ def update
75
+ # With operations we don't need strong parameters as the operation contract takes care of this.
76
+ @post_update = Post::Update.default.call(
77
+ { **params[:post_update_default_form], post_id: params[:id] },
78
+ current_user: current_user
79
+ )
80
+
81
+ respond_with @post_update, location: edit_post_url(@post_update.context[:post])
82
+ end
83
+ end
84
+
85
+ # After
86
+ class PostsController < ApplicationController
87
+ def edit
88
+ @post_update_form = Post::Update.default_form.build(params, current_user: current_user)
89
+
90
+ respond_with @post_update_form
91
+ end
92
+
93
+ def update
94
+ @post_update_form = Post::Update.default_form.persist(params, current_user: current_user)
95
+
96
+ respond_with @post_update_form, location: edit_post_url(@post_update_form.operation_result.context[:post])
97
+ end
98
+ end
99
+ ```
100
+
101
+ Notice that `callable` and `call` methond are replaced with `build` and `persist` respectively.
@@ -387,7 +387,8 @@ class Operations::Command
387
387
  key_map: contract.class.schema.key_map,
388
388
  model_map: Operations::Form::DeprecatedLegacyModelMapImplementation.new(form_model_map),
389
389
  namespace: operation.class,
390
- class_name: form_class_name
390
+ class_name: form_class_name,
391
+ persisted: true
391
392
  )
392
393
  end
393
394
 
@@ -47,6 +47,7 @@ class Operations::Form::Base
47
47
 
48
48
  base.class_attribute :attributes, instance_accessor: false, default: {}
49
49
  base.class_attribute :primary_key, instance_accessor: false, default: :id
50
+ base.class_attribute :persisted, instance_accessor: false, default: nil
50
51
 
51
52
  base.define_method :initialize do |*args, **kwargs|
52
53
  args.empty? && kwargs.present? ? super(kwargs, **{}) : super(*args, **kwargs)
@@ -132,7 +133,7 @@ class Operations::Form::Base
132
133
  end
133
134
 
134
135
  def persisted?
135
- !has_attribute?(self.class.primary_key) || read_attribute(self.class.primary_key).present?
136
+ self.class.persisted.nil? ? read_attribute(self.class.primary_key).present? : self.class.persisted
136
137
  end
137
138
 
138
139
  def new_record?
@@ -11,18 +11,19 @@ class Operations::Form::Builder
11
11
 
12
12
  option :base_class, Operations::Types::Instance(Class)
13
13
 
14
- def build(key_map:, model_map:, namespace: nil, class_name: nil, model_name: nil)
14
+ def build(key_map:, model_map:, namespace: nil, class_name: nil, model_name: nil, persisted: nil)
15
15
  return namespace.const_get(class_name) if namespace && class_name && namespace.const_defined?(class_name)
16
16
 
17
- traverse(key_map, model_map, namespace, class_name, model_name, [])
17
+ traverse(key_map, model_map, namespace, class_name, model_name, [], persisted: persisted)
18
18
  end
19
19
 
20
20
  private
21
21
 
22
- def traverse(key_map, model_map, namespace, class_name, model_name, path)
22
+ def traverse(key_map, model_map, namespace, class_name, model_name, path, persisted: nil)
23
23
  form = Class.new(base_class)
24
24
  namespace.const_set(class_name, form) if namespace&.name && class_name
25
25
  define_model_name(form, model_name) if model_name && !form.name
26
+ form.persisted = persisted
26
27
 
27
28
  key_map.each { |key| define_attribute(form, model_map, key, path) }
28
29
  form
@@ -21,8 +21,8 @@
21
21
  #
22
22
  class Operations::Form
23
23
  include Dry::Core::Constants
24
- include Dry::Equalizer(:command, :model_map, :params_transformations, :hydrator, :form_class)
25
- include Operations::Inspect.new(:model_name, :model_map, :params_transformations, :hydrator, :form_class)
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)
26
26
 
27
27
  # We need to make deprecated inheritance from Operations::Form act exactly the
28
28
  # same way as from Operations::Form::Base. In order to do this, we are encapsulating all the
@@ -48,6 +48,7 @@ class Operations::Form
48
48
  param :command, type: Operations::Types.Interface(:operation, :contract, :call)
49
49
  option :model_name, type: Operations::Types::String.optional, default: proc {}, reader: false
50
50
  option :model_map, type: Operations::Types.Interface(:call).optional, default: proc {}
51
+ option :persisted, type: Operations::Types::Bool, default: proc { true }
51
52
  option :params_transformations, type: Operations::Types::Coercible::Array.of(Operations::Types.Interface(:call)),
52
53
  default: proc { [] }
53
54
  option :hydrator, type: Operations::Types.Interface(:call).optional, default: proc {}
@@ -64,7 +65,7 @@ class Operations::Form
64
65
 
65
66
  def form_class
66
67
  @form_class ||= Operations::Form::Builder.new(base_class: base_class)
67
- .build(key_map: key_map, model_map: model_map, model_name: model_name)
68
+ .build(key_map: key_map, model_map: model_map, model_name: model_name, persisted: persisted)
68
69
  end
69
70
 
70
71
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Operations
4
- VERSION = "0.7.0"
4
+ VERSION = "0.7.1"
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.0
4
+ version: 0.7.1
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-28 00:00:00.000000000 Z
11
+ date: 2024-07-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: appraisal
@@ -154,6 +154,7 @@ files:
154
154
  - LICENSE.txt
155
155
  - README.md
156
156
  - Rakefile
157
+ - UPGRADING_FORMS.md
157
158
  - bin/console
158
159
  - bin/setup
159
160
  - gemfiles/rails.5.2.gemfile