rails_ops 1.7.3 → 1.7.4

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.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.7.3
1
+ 1.7.4
@@ -2,9 +2,11 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'active_type', '< 2.7.0'
5
6
  gem 'appraisal'
6
7
  gem 'bundler'
7
8
  gem 'cancancan'
9
+ gem 'minitest', '~> 5.25'
8
10
  gem 'pry'
9
11
  gem 'rails', '~> 6.0.4'
10
12
  gem 'rake'
@@ -2,9 +2,11 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'active_type', '< 2.7.0'
5
6
  gem 'appraisal'
6
7
  gem 'bundler'
7
8
  gem 'cancancan'
9
+ gem 'minitest', '~> 5.25'
8
10
  gem 'pry'
9
11
  gem 'rails', '~> 6.1.4'
10
12
  gem 'rake'
@@ -2,9 +2,11 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'active_type', '< 2.7.0'
5
6
  gem 'appraisal'
6
7
  gem 'bundler'
7
8
  gem 'cancancan'
9
+ gem 'minitest', '~> 5.25'
8
10
  gem 'pry'
9
11
  gem 'rails', '~> 7.0.1'
10
12
  gem 'rake'
@@ -5,6 +5,7 @@ source 'https://rubygems.org'
5
5
  gem 'appraisal'
6
6
  gem 'bundler'
7
7
  gem 'cancancan'
8
+ gem 'minitest', '~> 5.25'
8
9
  gem 'pry'
9
10
  gem 'rails', '~> 7.1.0'
10
11
  gem 'rake'
@@ -5,6 +5,7 @@ source 'https://rubygems.org'
5
5
  gem 'appraisal'
6
6
  gem 'bundler'
7
7
  gem 'cancancan'
8
+ gem 'minitest', '~> 5.25'
8
9
  gem 'pry'
9
10
  gem 'rails', '~> 7.2.1'
10
11
  gem 'rake'
@@ -5,6 +5,7 @@ source 'https://rubygems.org'
5
5
  gem 'appraisal'
6
6
  gem 'bundler'
7
7
  gem 'cancancan'
8
+ gem 'minitest', '~> 5.25'
8
9
  gem 'pry'
9
10
  gem 'rails', '~> 8.0.1'
10
11
  gem 'rake'
@@ -168,6 +168,11 @@ module RailsOps::Mixins::Model::Nesting
168
168
  def perform_nested_model_ops!
169
169
  fail 'Nested model operations can only be performed once.' if nested_model_ops_performed?
170
170
 
171
+ # Run before_model_validation policies. This is the correct place for
172
+ # attribute cleanup and sanitization as the model attributes are
173
+ # assigned but validation has not yet run.
174
+ run_policies :before_model_validation
175
+
171
176
  # Validate the whole model hierarchy. Since we're calling 'model' here, this
172
177
  # line also makes sure a model is built.
173
178
  model.validate!
@@ -11,6 +11,7 @@ module RailsOps::Mixins::Policies
11
11
  before_perform
12
12
  after_perform
13
13
  before_nested_model_ops
14
+ before_model_validation
14
15
  before_model_save
15
16
  ].freeze
16
17
 
@@ -33,6 +34,13 @@ module RailsOps::Mixins::Policies
33
34
  fail 'Policy :before_attr_assign may not be used unless your operation defines the `assign_attributes` method!'
34
35
  end
35
36
 
37
+ # The `before_model_validation` chain is only allowed if the operation
38
+ # is a model operation, i.e. it needs to implement the `build_model`
39
+ # method.
40
+ if chain == :before_model_validation && !method_defined?(:build_model)
41
+ fail 'Policy :before_model_validation may not be used unless your operation defines the `build_model` method!'
42
+ end
43
+
36
44
  self._policy_chains = _policy_chains.dup
37
45
  if prepend_action
38
46
  _policy_chains[chain] = [block] + _policy_chains[chain]
data/rails_ops.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: rails_ops 1.7.3 ruby lib
2
+ # stub: rails_ops 1.7.4 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "rails_ops".freeze
6
- s.version = "1.7.3"
6
+ s.version = "1.7.4"
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Sitrox".freeze]
11
- s.date = "2025-05-22"
12
- s.files = [".github/workflows/rubocop.yml".freeze, ".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, "Appraisals".freeze, "CHANGELOG.md".freeze, "Gemfile".freeze, "Gemfile.lock".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "gemfiles/rails_6.0.gemfile".freeze, "gemfiles/rails_6.1.gemfile".freeze, "gemfiles/rails_7.0.gemfile".freeze, "gemfiles/rails_7.1.gemfile".freeze, "gemfiles/rails_7.2.gemfile".freeze, "gemfiles/rails_8.0.gemfile".freeze, "lib/generators/operation/USAGE".freeze, "lib/generators/operation/operation_generator.rb".freeze, "lib/generators/operation/templates/controller.erb".freeze, "lib/generators/operation/templates/controller_wrapper.erb".freeze, "lib/generators/operation/templates/create.erb".freeze, "lib/generators/operation/templates/destroy.erb".freeze, "lib/generators/operation/templates/load.erb".freeze, "lib/generators/operation/templates/update.erb".freeze, "lib/generators/operation/templates/view.erb".freeze, "lib/rails_ops.rb".freeze, "lib/rails_ops/authorization_backend/abstract.rb".freeze, "lib/rails_ops/authorization_backend/can_can_can.rb".freeze, "lib/rails_ops/configuration.rb".freeze, "lib/rails_ops/context.rb".freeze, "lib/rails_ops/controller_mixin.rb".freeze, "lib/rails_ops/exceptions.rb".freeze, "lib/rails_ops/hooked_job.rb".freeze, "lib/rails_ops/hookup.rb".freeze, "lib/rails_ops/hookup/dsl.rb".freeze, "lib/rails_ops/hookup/dsl_validator.rb".freeze, "lib/rails_ops/hookup/hook.rb".freeze, "lib/rails_ops/log_subscriber.rb".freeze, "lib/rails_ops/mixins.rb".freeze, "lib/rails_ops/mixins/authorization.rb".freeze, "lib/rails_ops/mixins/log_settings.rb".freeze, "lib/rails_ops/mixins/model.rb".freeze, "lib/rails_ops/mixins/model/authorization.rb".freeze, "lib/rails_ops/mixins/model/nesting.rb".freeze, "lib/rails_ops/mixins/param_authorization.rb".freeze, "lib/rails_ops/mixins/policies.rb".freeze, "lib/rails_ops/mixins/require_context.rb".freeze, "lib/rails_ops/mixins/routes.rb".freeze, "lib/rails_ops/mixins/schema_validation.rb".freeze, "lib/rails_ops/mixins/sub_ops.rb".freeze, "lib/rails_ops/model_mixins.rb".freeze, "lib/rails_ops/model_mixins/ar_extension.rb".freeze, "lib/rails_ops/model_mixins/marshalling.rb".freeze, "lib/rails_ops/model_mixins/parent_op.rb".freeze, "lib/rails_ops/model_mixins/sti_fixes.rb".freeze, "lib/rails_ops/model_mixins/virtual_attributes.rb".freeze, "lib/rails_ops/model_mixins/virtual_attributes/virtual_column_wrapper.rb".freeze, "lib/rails_ops/model_mixins/virtual_has_one.rb".freeze, "lib/rails_ops/model_mixins/virtual_model_name.rb".freeze, "lib/rails_ops/operation.rb".freeze, "lib/rails_ops/operation/model.rb".freeze, "lib/rails_ops/operation/model/create.rb".freeze, "lib/rails_ops/operation/model/destroy.rb".freeze, "lib/rails_ops/operation/model/load.rb".freeze, "lib/rails_ops/operation/model/update.rb".freeze, "lib/rails_ops/profiler.rb".freeze, "lib/rails_ops/profiler/node.rb".freeze, "lib/rails_ops/railtie.rb".freeze, "lib/rails_ops/scoped_env.rb".freeze, "lib/rails_ops/virtual_model.rb".freeze, "rails_ops.gemspec".freeze, "test/db/models.rb".freeze, "test/db/schema.rb".freeze, "test/dummy/Rakefile".freeze, "test/dummy/app/assets/config/manifest.js".freeze, "test/dummy/app/assets/images/.keep".freeze, "test/dummy/app/assets/javascripts/application.js".freeze, "test/dummy/app/assets/javascripts/cable.js".freeze, "test/dummy/app/assets/javascripts/channels/.keep".freeze, "test/dummy/app/assets/stylesheets/application.css".freeze, "test/dummy/app/channels/application_cable/channel.rb".freeze, "test/dummy/app/channels/application_cable/connection.rb".freeze, "test/dummy/app/controllers/application_controller.rb".freeze, "test/dummy/app/controllers/concerns/.keep".freeze, "test/dummy/app/controllers/group_controller.rb".freeze, "test/dummy/app/helpers/application_helper.rb".freeze, "test/dummy/app/jobs/application_job.rb".freeze, "test/dummy/app/mailers/application_mailer.rb".freeze, "test/dummy/app/models/ability.rb".freeze, "test/dummy/app/models/animal.rb".freeze, "test/dummy/app/models/application_record.rb".freeze, "test/dummy/app/models/bird.rb".freeze, "test/dummy/app/models/cat.rb".freeze, "test/dummy/app/models/computer.rb".freeze, "test/dummy/app/models/concerns/.keep".freeze, "test/dummy/app/models/cpu.rb".freeze, "test/dummy/app/models/dog.rb".freeze, "test/dummy/app/models/flower.rb".freeze, "test/dummy/app/models/group.rb".freeze, "test/dummy/app/models/mainboard.rb".freeze, "test/dummy/app/models/nightingale.rb".freeze, "test/dummy/app/models/phoenix.rb".freeze, "test/dummy/app/models/user.rb".freeze, "test/dummy/app/views/layouts/application.html.erb".freeze, "test/dummy/app/views/layouts/mailer.html.erb".freeze, "test/dummy/app/views/layouts/mailer.text.erb".freeze, "test/dummy/bin/bundle".freeze, "test/dummy/bin/rails".freeze, "test/dummy/bin/rake".freeze, "test/dummy/bin/setup".freeze, "test/dummy/bin/update".freeze, "test/dummy/bin/yarn".freeze, "test/dummy/config.ru".freeze, "test/dummy/config/application.rb".freeze, "test/dummy/config/boot.rb".freeze, "test/dummy/config/cable.yml".freeze, "test/dummy/config/database.yml".freeze, "test/dummy/config/environment.rb".freeze, "test/dummy/config/environments/development.rb".freeze, "test/dummy/config/environments/production.rb".freeze, "test/dummy/config/environments/test.rb".freeze, "test/dummy/config/hookup.rb".freeze, "test/dummy/config/initializers/application_controller_renderer.rb".freeze, "test/dummy/config/initializers/assets.rb".freeze, "test/dummy/config/initializers/backtrace_silencers.rb".freeze, "test/dummy/config/initializers/cookies_serializer.rb".freeze, "test/dummy/config/initializers/filter_parameter_logging.rb".freeze, "test/dummy/config/initializers/inflections.rb".freeze, "test/dummy/config/initializers/mime_types.rb".freeze, "test/dummy/config/initializers/rails_ops.rb".freeze, "test/dummy/config/initializers/wrap_parameters.rb".freeze, "test/dummy/config/locales/en.yml".freeze, "test/dummy/config/puma.rb".freeze, "test/dummy/config/routes.rb".freeze, "test/dummy/config/secrets.yml".freeze, "test/dummy/config/spring.rb".freeze, "test/dummy/db/schema.rb".freeze, "test/dummy/lib/assets/.keep".freeze, "test/dummy/log/.keep".freeze, "test/dummy/package.json".freeze, "test/dummy/public/404.html".freeze, "test/dummy/public/422.html".freeze, "test/dummy/public/500.html".freeze, "test/dummy/public/apple-touch-icon-precomposed.png".freeze, "test/dummy/public/apple-touch-icon.png".freeze, "test/dummy/public/favicon.ico".freeze, "test/dummy/tmp/.keep".freeze, "test/test_helper.rb".freeze, "test/unit/rails_ops/generators/operation_generator_test.rb".freeze, "test/unit/rails_ops/hookup_test.rb".freeze, "test/unit/rails_ops/mixins/controller_test.rb".freeze, "test/unit/rails_ops/mixins/model/deep_nesting_test.rb".freeze, "test/unit/rails_ops/mixins/model/marshalling_test.rb".freeze, "test/unit/rails_ops/mixins/param_authorization_test.rb".freeze, "test/unit/rails_ops/mixins/policies_test.rb".freeze, "test/unit/rails_ops/operation/auth_test.rb".freeze, "test/unit/rails_ops/operation/model/create_test.rb".freeze, "test/unit/rails_ops/operation/model/destroy_test.rb".freeze, "test/unit/rails_ops/operation/model/load_test.rb".freeze, "test/unit/rails_ops/operation/model/sti_test.rb".freeze, "test/unit/rails_ops/operation/model/update_test.rb".freeze, "test/unit/rails_ops/operation/model_test.rb".freeze, "test/unit/rails_ops/operation/update_lazy_auth_test.rb".freeze, "test/unit/rails_ops/operation_test.rb".freeze, "test/unit/rails_ops/profiler_test.rb".freeze]
11
+ s.date = "2026-02-18"
12
+ s.files = [".github/workflows/rubocop.yml".freeze, ".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, "Appraisals".freeze, "CHANGELOG.md".freeze, "CLAUDE.md".freeze, "Gemfile".freeze, "Gemfile.lock".freeze, "LICENSE".freeze, "README.md".freeze, "Rakefile".freeze, "VERSION".freeze, "gemfiles/rails_6.0.gemfile".freeze, "gemfiles/rails_6.1.gemfile".freeze, "gemfiles/rails_7.0.gemfile".freeze, "gemfiles/rails_7.1.gemfile".freeze, "gemfiles/rails_7.2.gemfile".freeze, "gemfiles/rails_8.0.gemfile".freeze, "lib/generators/operation/USAGE".freeze, "lib/generators/operation/operation_generator.rb".freeze, "lib/generators/operation/templates/controller.erb".freeze, "lib/generators/operation/templates/controller_wrapper.erb".freeze, "lib/generators/operation/templates/create.erb".freeze, "lib/generators/operation/templates/destroy.erb".freeze, "lib/generators/operation/templates/load.erb".freeze, "lib/generators/operation/templates/update.erb".freeze, "lib/generators/operation/templates/view.erb".freeze, "lib/rails_ops.rb".freeze, "lib/rails_ops/authorization_backend/abstract.rb".freeze, "lib/rails_ops/authorization_backend/can_can_can.rb".freeze, "lib/rails_ops/configuration.rb".freeze, "lib/rails_ops/context.rb".freeze, "lib/rails_ops/controller_mixin.rb".freeze, "lib/rails_ops/exceptions.rb".freeze, "lib/rails_ops/hooked_job.rb".freeze, "lib/rails_ops/hookup.rb".freeze, "lib/rails_ops/hookup/dsl.rb".freeze, "lib/rails_ops/hookup/dsl_validator.rb".freeze, "lib/rails_ops/hookup/hook.rb".freeze, "lib/rails_ops/log_subscriber.rb".freeze, "lib/rails_ops/mixins.rb".freeze, "lib/rails_ops/mixins/authorization.rb".freeze, "lib/rails_ops/mixins/log_settings.rb".freeze, "lib/rails_ops/mixins/model.rb".freeze, "lib/rails_ops/mixins/model/authorization.rb".freeze, "lib/rails_ops/mixins/model/nesting.rb".freeze, "lib/rails_ops/mixins/param_authorization.rb".freeze, "lib/rails_ops/mixins/policies.rb".freeze, "lib/rails_ops/mixins/require_context.rb".freeze, "lib/rails_ops/mixins/routes.rb".freeze, "lib/rails_ops/mixins/schema_validation.rb".freeze, "lib/rails_ops/mixins/sub_ops.rb".freeze, "lib/rails_ops/model_mixins.rb".freeze, "lib/rails_ops/model_mixins/ar_extension.rb".freeze, "lib/rails_ops/model_mixins/marshalling.rb".freeze, "lib/rails_ops/model_mixins/parent_op.rb".freeze, "lib/rails_ops/model_mixins/sti_fixes.rb".freeze, "lib/rails_ops/model_mixins/virtual_attributes.rb".freeze, "lib/rails_ops/model_mixins/virtual_attributes/virtual_column_wrapper.rb".freeze, "lib/rails_ops/model_mixins/virtual_has_one.rb".freeze, "lib/rails_ops/model_mixins/virtual_model_name.rb".freeze, "lib/rails_ops/operation.rb".freeze, "lib/rails_ops/operation/model.rb".freeze, "lib/rails_ops/operation/model/create.rb".freeze, "lib/rails_ops/operation/model/destroy.rb".freeze, "lib/rails_ops/operation/model/load.rb".freeze, "lib/rails_ops/operation/model/update.rb".freeze, "lib/rails_ops/profiler.rb".freeze, "lib/rails_ops/profiler/node.rb".freeze, "lib/rails_ops/railtie.rb".freeze, "lib/rails_ops/scoped_env.rb".freeze, "lib/rails_ops/virtual_model.rb".freeze, "rails_ops.gemspec".freeze, "test/db/models.rb".freeze, "test/db/schema.rb".freeze, "test/dummy/Rakefile".freeze, "test/dummy/app/assets/config/manifest.js".freeze, "test/dummy/app/assets/images/.keep".freeze, "test/dummy/app/assets/javascripts/application.js".freeze, "test/dummy/app/assets/javascripts/cable.js".freeze, "test/dummy/app/assets/javascripts/channels/.keep".freeze, "test/dummy/app/assets/stylesheets/application.css".freeze, "test/dummy/app/channels/application_cable/channel.rb".freeze, "test/dummy/app/channels/application_cable/connection.rb".freeze, "test/dummy/app/controllers/application_controller.rb".freeze, "test/dummy/app/controllers/concerns/.keep".freeze, "test/dummy/app/controllers/group_controller.rb".freeze, "test/dummy/app/helpers/application_helper.rb".freeze, "test/dummy/app/jobs/application_job.rb".freeze, "test/dummy/app/mailers/application_mailer.rb".freeze, "test/dummy/app/models/ability.rb".freeze, "test/dummy/app/models/animal.rb".freeze, "test/dummy/app/models/application_record.rb".freeze, "test/dummy/app/models/bird.rb".freeze, "test/dummy/app/models/cat.rb".freeze, "test/dummy/app/models/computer.rb".freeze, "test/dummy/app/models/concerns/.keep".freeze, "test/dummy/app/models/cpu.rb".freeze, "test/dummy/app/models/dog.rb".freeze, "test/dummy/app/models/flower.rb".freeze, "test/dummy/app/models/group.rb".freeze, "test/dummy/app/models/mainboard.rb".freeze, "test/dummy/app/models/nightingale.rb".freeze, "test/dummy/app/models/phoenix.rb".freeze, "test/dummy/app/models/user.rb".freeze, "test/dummy/app/views/layouts/application.html.erb".freeze, "test/dummy/app/views/layouts/mailer.html.erb".freeze, "test/dummy/app/views/layouts/mailer.text.erb".freeze, "test/dummy/bin/bundle".freeze, "test/dummy/bin/rails".freeze, "test/dummy/bin/rake".freeze, "test/dummy/bin/setup".freeze, "test/dummy/bin/update".freeze, "test/dummy/bin/yarn".freeze, "test/dummy/config.ru".freeze, "test/dummy/config/application.rb".freeze, "test/dummy/config/boot.rb".freeze, "test/dummy/config/cable.yml".freeze, "test/dummy/config/database.yml".freeze, "test/dummy/config/environment.rb".freeze, "test/dummy/config/environments/development.rb".freeze, "test/dummy/config/environments/production.rb".freeze, "test/dummy/config/environments/test.rb".freeze, "test/dummy/config/hookup.rb".freeze, "test/dummy/config/initializers/application_controller_renderer.rb".freeze, "test/dummy/config/initializers/assets.rb".freeze, "test/dummy/config/initializers/backtrace_silencers.rb".freeze, "test/dummy/config/initializers/cookies_serializer.rb".freeze, "test/dummy/config/initializers/filter_parameter_logging.rb".freeze, "test/dummy/config/initializers/inflections.rb".freeze, "test/dummy/config/initializers/mime_types.rb".freeze, "test/dummy/config/initializers/rails_ops.rb".freeze, "test/dummy/config/initializers/wrap_parameters.rb".freeze, "test/dummy/config/locales/en.yml".freeze, "test/dummy/config/puma.rb".freeze, "test/dummy/config/routes.rb".freeze, "test/dummy/config/secrets.yml".freeze, "test/dummy/config/spring.rb".freeze, "test/dummy/db/schema.rb".freeze, "test/dummy/lib/assets/.keep".freeze, "test/dummy/log/.keep".freeze, "test/dummy/package.json".freeze, "test/dummy/public/404.html".freeze, "test/dummy/public/422.html".freeze, "test/dummy/public/500.html".freeze, "test/dummy/public/apple-touch-icon-precomposed.png".freeze, "test/dummy/public/apple-touch-icon.png".freeze, "test/dummy/public/favicon.ico".freeze, "test/dummy/tmp/.keep".freeze, "test/test_helper.rb".freeze, "test/unit/rails_ops/generators/operation_generator_test.rb".freeze, "test/unit/rails_ops/hookup_test.rb".freeze, "test/unit/rails_ops/mixins/controller_test.rb".freeze, "test/unit/rails_ops/mixins/model/deep_nesting_test.rb".freeze, "test/unit/rails_ops/mixins/model/marshalling_test.rb".freeze, "test/unit/rails_ops/mixins/param_authorization_test.rb".freeze, "test/unit/rails_ops/mixins/policies_test.rb".freeze, "test/unit/rails_ops/operation/auth_test.rb".freeze, "test/unit/rails_ops/operation/model/create_test.rb".freeze, "test/unit/rails_ops/operation/model/destroy_test.rb".freeze, "test/unit/rails_ops/operation/model/load_test.rb".freeze, "test/unit/rails_ops/operation/model/sti_test.rb".freeze, "test/unit/rails_ops/operation/model/update_test.rb".freeze, "test/unit/rails_ops/operation/model_test.rb".freeze, "test/unit/rails_ops/operation/update_lazy_auth_test.rb".freeze, "test/unit/rails_ops/operation_test.rb".freeze, "test/unit/rails_ops/profiler_test.rb".freeze]
13
13
  s.licenses = ["MIT".freeze]
14
14
  s.rubygems_version = "3.4.6".freeze
15
15
  s.summary = "An operations service layer for rails projects.".freeze
@@ -85,6 +85,99 @@ class RailsOps::Mixins::PoliciesTest < ActiveSupport::TestCase
85
85
  end
86
86
  end
87
87
 
88
+ def test_before_model_validation_sequence
89
+ group = Group.create!
90
+
91
+ op = Class.new(RailsOps::Operation::Model::Update) do
92
+ attr_reader :sequence
93
+
94
+ model Group
95
+
96
+ policy :before_attr_assign do
97
+ @sequence = []
98
+ end
99
+
100
+ policy :before_nested_model_ops do
101
+ @sequence << :before_nested_model_ops
102
+ end
103
+
104
+ policy :before_model_validation do
105
+ @sequence << :before_model_validation
106
+ end
107
+
108
+ policy :before_model_save do
109
+ @sequence << :before_model_save
110
+ end
111
+
112
+ def perform
113
+ @sequence << :before_save
114
+ save!
115
+ @sequence << :after_save
116
+ end
117
+ end
118
+
119
+ result = op.run!(id: group.id)
120
+ assert_equal %i[
121
+ before_save
122
+ before_nested_model_ops
123
+ before_model_validation
124
+ before_model_save
125
+ after_save
126
+ ], result.sequence
127
+ end
128
+
129
+ def test_before_model_validation_needs_build_model
130
+ assert_raises RuntimeError, match: /Policy :before_model_validation may not be used unless your operation defines the `build_model` method!/ do
131
+ Class.new(RailsOps::Operation) do
132
+ policy :before_model_validation do
133
+ # Nothing needed here
134
+ end
135
+ end
136
+ end
137
+ end
138
+
139
+ def test_before_model_validation_attribute_sanitization
140
+ op = Class.new(RailsOps::Operation::Model::Create) do
141
+ model Group
142
+
143
+ policy :before_model_validation do
144
+ # Simulate role-dependent attribute cleanup: nil out color
145
+ # when name is "restricted".
146
+ model.color = nil if model.name == 'restricted'
147
+ end
148
+ end
149
+
150
+ # With name "restricted", the policy should nil out color before
151
+ # validation runs.
152
+ result = op.run!(group: { name: 'restricted', color: 'red' })
153
+ assert_equal 'restricted', result.model.name
154
+ assert_nil result.model.color
155
+
156
+ # With a different name, color should be preserved.
157
+ result = op.run!(group: { name: 'normal', color: 'blue' })
158
+ assert_equal 'normal', result.model.name
159
+ assert_equal 'blue', result.model.color
160
+ end
161
+
162
+ def test_before_model_validation_runs_before_validation
163
+ # Add a validation to Group that rejects color "invalid", then
164
+ # use a before_model_validation policy to fix it before validate!
165
+ # runs. If the policy runs at the right time, the operation
166
+ # succeeds.
167
+ op = Class.new(RailsOps::Operation::Model::Create) do
168
+ model Group do
169
+ validates :color, exclusion: { in: ['invalid'] }
170
+ end
171
+
172
+ policy :before_model_validation do
173
+ model.color = 'fixed' if model.color == 'invalid'
174
+ end
175
+ end
176
+
177
+ result = op.run!(group: { name: 'test', color: 'invalid' })
178
+ assert_equal 'fixed', result.model.color
179
+ end
180
+
88
181
  def test_prepend_action
89
182
  op = Class.new(RailsOps::Operation) do
90
183
  attr_reader :sequence
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_ops
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.3
4
+ version: 1.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-05-22 00:00:00.000000000 Z
10
+ date: 2026-02-18 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: active_type
@@ -96,6 +96,7 @@ files:
96
96
  - ".rubocop.yml"
97
97
  - Appraisals
98
98
  - CHANGELOG.md
99
+ - CLAUDE.md
99
100
  - Gemfile
100
101
  - Gemfile.lock
101
102
  - LICENSE
@@ -274,7 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
274
275
  - !ruby/object:Gem::Version
275
276
  version: '0'
276
277
  requirements: []
277
- rubygems_version: 3.6.8
278
+ rubygems_version: 4.0.2
278
279
  specification_version: 4
279
280
  summary: An operations service layer for rails projects.
280
281
  test_files: