rails_ops 1.7.2 → 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.2
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]
@@ -25,7 +25,12 @@ module RailsOps::Mixins::SchemaValidation
25
25
  # @param *args [Array] Parameters to pass at schema initialization
26
26
  # @yield Block to pass at schema initialization
27
27
  def schema2(*args, &block)
28
- self._op_schema = Schemacop::Schema.new(*args, &block)
28
+ if args.any? || block_given?
29
+ self._op_schema = Schemacop::Schema.new(*args, &block)
30
+ else
31
+ # Define empty schema (not possible with V2 schema).
32
+ schema3
33
+ end
29
34
  end
30
35
 
31
36
  def schema3(type = :hash, *args, **kwargs, &block)
data/rails_ops.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: rails_ops 1.7.2 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.2"
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-04-14"
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
@@ -198,6 +198,49 @@ class RailsOps::OperationTest < ActiveSupport::TestCase
198
198
  end
199
199
  end
200
200
 
201
+ def test_no_schema
202
+ op = Class.new(RailsOps::Operation) do
203
+ def perform; end
204
+ end
205
+
206
+ assert_nothing_raised do
207
+ op.run!(foo: :bar)
208
+ end
209
+ end
210
+
211
+ def test_empty_schema
212
+ op = Class.new(RailsOps::Operation) do
213
+ schema
214
+ def perform; end
215
+ end
216
+
217
+ assert_raises Schemacop::Exceptions::ValidationError do
218
+ op.run!(foo: :bar)
219
+ end
220
+ end
221
+
222
+ def test_empty_schema2
223
+ op = Class.new(RailsOps::Operation) do
224
+ schema2
225
+ def perform; end
226
+ end
227
+
228
+ assert_raises Schemacop::Exceptions::ValidationError do
229
+ op.run!(foo: :bar)
230
+ end
231
+ end
232
+
233
+ def test_empty_schema3
234
+ op = Class.new(RailsOps::Operation) do
235
+ schema3
236
+ def perform; end
237
+ end
238
+
239
+ assert_raises Schemacop::Exceptions::ValidationError do
240
+ op.run!(foo: :bar)
241
+ end
242
+ end
243
+
201
244
  def test_op_with_schema3(use_default: false)
202
245
  op = Class.new(RailsOps::Operation) do
203
246
  schema_block = proc do
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_ops
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-04-14 00:00:00.000000000 Z
10
+ date: 2026-02-18 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: active_type
@@ -86,8 +85,6 @@ dependencies:
86
85
  - - "<="
87
86
  - !ruby/object:Gem::Version
88
87
  version: '3.1'
89
- description:
90
- email:
91
88
  executables: []
92
89
  extensions: []
93
90
  extra_rdoc_files: []
@@ -99,6 +96,7 @@ files:
99
96
  - ".rubocop.yml"
100
97
  - Appraisals
101
98
  - CHANGELOG.md
99
+ - CLAUDE.md
102
100
  - Gemfile
103
101
  - Gemfile.lock
104
102
  - LICENSE
@@ -260,11 +258,9 @@ files:
260
258
  - test/unit/rails_ops/operation/update_lazy_auth_test.rb
261
259
  - test/unit/rails_ops/operation_test.rb
262
260
  - test/unit/rails_ops/profiler_test.rb
263
- homepage:
264
261
  licenses:
265
262
  - MIT
266
263
  metadata: {}
267
- post_install_message:
268
264
  rdoc_options: []
269
265
  require_paths:
270
266
  - lib
@@ -279,8 +275,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
279
275
  - !ruby/object:Gem::Version
280
276
  version: '0'
281
277
  requirements: []
282
- rubygems_version: 3.5.23
283
- signing_key:
278
+ rubygems_version: 4.0.2
284
279
  specification_version: 4
285
280
  summary: An operations service layer for rails projects.
286
281
  test_files: