operations 0.7.0 → 0.7.2

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: affb68a66ecbc1ee0e687ab13efb202037d10054adb114e684e775224957b65e
4
+ data.tar.gz: 255180d0be3704a73c0bf6e148658df8b662e33a4508dff3ae3fee819e73caba
5
5
  SHA512:
6
- metadata.gz: 186e9327e813bbf7ddd5d5aea52b5bf00cc7a62a9c8b235b7fd25f36585a2468192922df3ff9435d7320b164807e31acf73523eaf9b106c67de6f2f27da19400
7
- data.tar.gz: 43d61db8dbe71a4f9269755a6fc73c46214ba3baddeed80e650a7ae2ced43c08fb361dd19cef005954504970c06bfa573a1a55f760e7ac711c68c19f4e34601b
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,10 +1,27 @@
1
1
  # Changelog
2
2
 
3
- ## [0.7.0](https://github.com/BookingSync/operations/tree/main)
3
+ ## [0.7.2](https://github.com/BookingSync/operations/tree/v0.7.2)
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
+ - 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
+
14
+ ## [0.7.1](https://github.com/BookingSync/operations/tree/v0.7.1)
15
+
16
+ ### Added
17
+
18
+ - Added `persisted:` option to the new forms definition. [\#48](https://github.com/BookingSync/operations/pull/48) ([pyromaniac](https://github.com/pyromaniac))
19
+
20
+ ## [0.7.0](https://github.com/BookingSync/operations/tree/v0.7.0)
21
+
22
+ ### Added
23
+
24
+ - 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
25
 
9
26
  ### Improvements
10
27
 
@@ -15,7 +32,7 @@
15
32
 
16
33
  - 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
34
 
18
- ## [0.6.2]
35
+ ## [0.6.3](https://github.com/BookingSync/operations/tree/v0.6.3)
19
36
 
20
37
  ### Added
21
38
 
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)
@@ -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
@@ -902,7 +904,7 @@ class Post::Update
902
904
  def self.default_form
903
905
  @default_form ||= Operations::Form.new(
904
906
  default,
905
- hydrator: Post::Update::Hydrator.new
907
+ hydrators: [Post::Update::Hydrator.new]
906
908
  )
907
909
  end
908
910
  end
@@ -910,9 +912,7 @@ end
910
912
  class Post::Update::Hydrator
911
913
  def call(form_class, params, post:, **_context)
912
914
  value_attributes = form_class.attributes.keys - %i[post_id]
913
- data = value_attributes.index_with { |name| post.public_send(name) }
914
-
915
- data.merge!(params)
915
+ value_attributes.index_with { |name| post.public_send(name) }
916
916
  end
917
917
  end
918
918
  ```
@@ -927,19 +927,36 @@ class Post::Update
927
927
  @default_form ||= Operations::Form.new(
928
928
  default,
929
929
  model_map: Post::Update::ModelMap.new,
930
- hydrator: Post::Update::Hydrator.new
930
+ hydrators: [Post::Update::Hydrator.new]
931
931
  )
932
932
  end
933
933
  end
934
934
 
935
935
  class Post::Update::ModelMap
936
- def call(_path)
937
- 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
938
944
  end
939
945
  end
940
946
  ```
941
947
 
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`.
948
+ 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:
949
+
950
+ ```ruby
951
+ class Post::Update
952
+ def self.default_form
953
+ @default_form ||= Operations::Form.new(
954
+ default,
955
+ model_name: "custom_post_update_form", # form name can be customized
956
+ )
957
+ end
958
+ end
959
+ ```
943
960
 
944
961
  It is possible to add more params transfomations to the form in cases when operation contract is different from the params structure:
945
962
 
@@ -948,7 +965,6 @@ class Post::Update
948
965
  def self.default_form
949
966
  @default_form ||= Operations::Form.new(
950
967
  default,
951
- model_name: "post_update_form", # form name can be customized
952
968
  params_transformations: [
953
969
  ParamsMap.new(id: :post_id),
954
970
  NestedAttributes.new(:sections)
@@ -994,6 +1010,21 @@ class NestedAttributes
994
1010
  end
995
1011
  ```
996
1012
 
1013
+ 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:
1014
+
1015
+ ```ruby
1016
+ class Post::Create
1017
+ def self.default_form
1018
+ @default_form ||= Operations::Form.new(
1019
+ default,
1020
+ persisted: false
1021
+ )
1022
+ end
1023
+ end
1024
+ ```
1025
+
1026
+ Note that operation itself is agnostic to the persistence layer, so there is no way for it to figure out this semanticsa automatically.
1027
+
997
1028
  ## Development
998
1029
 
999
1030
  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
+ hydrators: [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.
@@ -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
@@ -387,7 +392,8 @@ class Operations::Command
387
392
  key_map: contract.class.schema.key_map,
388
393
  model_map: Operations::Form::DeprecatedLegacyModelMapImplementation.new(form_model_map),
389
394
  namespace: operation.class,
390
- class_name: form_class_name
395
+ class_name: form_class_name,
396
+ persisted: true
391
397
  )
392
398
  end
393
399
 
@@ -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
@@ -47,9 +47,16 @@ 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
- 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
53
60
  end
54
61
  end
55
62
 
@@ -82,12 +89,13 @@ class Operations::Form::Base
82
89
 
83
90
  # :nodoc:
84
91
  module InstanceMethods
85
- def type_for_attribute(name)
86
- 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}"
87
95
  end
88
96
 
89
- def localized_attr_name_for(name, locale)
90
- 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
91
99
  end
92
100
 
93
101
  def has_attribute?(name) # rubocop:disable Naming/PredicateName
@@ -132,7 +140,7 @@ class Operations::Form::Base
132
140
  end
133
141
 
134
142
  def persisted?
135
- !has_attribute?(self.class.primary_key) || read_attribute(self.class.primary_key).present?
143
+ self.class.persisted.nil? ? read_attribute(self.class.primary_key).present? : self.class.persisted
136
144
  end
137
145
 
138
146
  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,10 @@
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,
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
@@ -48,12 +50,20 @@ class Operations::Form
48
50
  param :command, type: Operations::Types.Interface(:operation, :contract, :call)
49
51
  option :model_name, type: Operations::Types::String.optional, default: proc {}, reader: false
50
52
  option :model_map, type: Operations::Types.Interface(:call).optional, default: proc {}
53
+ option :persisted, type: Operations::Types::Bool, default: proc { true }
51
54
  option :params_transformations, type: Operations::Types::Coercible::Array.of(Operations::Types.Interface(:call)),
52
55
  default: proc { [] }
53
- 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 }
54
58
  option :base_class, type: Operations::Types::Class, default: proc { ::Operations::Form::Base }
55
59
  end)
56
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
+
57
67
  def build(params = EMPTY_HASH, **context)
58
68
  instantiate_form(command.callable(transform_params(params, **context), **context))
59
69
  end
@@ -64,7 +74,7 @@ class Operations::Form
64
74
 
65
75
  def form_class
66
76
  @form_class ||= Operations::Form::Builder.new(base_class: base_class)
67
- .build(key_map: key_map, model_map: model_map, model_name: model_name)
77
+ .build(key_map: key_map, model_map: model_map, model_name: model_name, persisted: persisted)
68
78
  end
69
79
 
70
80
  private
@@ -80,12 +90,20 @@ class Operations::Form
80
90
 
81
91
  def instantiate_form(operation_result)
82
92
  form_class.new(
83
- hydrator&.call(form_class, operation_result.params, **operation_result.context) || {},
93
+ hydrate_params(form_class, operation_result.params, **operation_result.context),
84
94
  messages: operation_result.errors.to_h,
85
95
  operation_result: operation_result
86
96
  )
87
97
  end
88
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
+
89
107
  def key_map
90
108
  @key_map ||= command.contract.schema.key_map
91
109
  end
@@ -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.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.0
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-28 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
@@ -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