rails_ops 1.6.0 → 1.7.1

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: d3115a85d7a5312b46e27d0f722639fb27eb4e8ff1bfff18c3243bad537ac7ec
4
- data.tar.gz: e916bfcc1f675d24ace13fd7d26fd5790f9e1e78f12c6de7a25c8e75be974a68
3
+ metadata.gz: 2daa2888b099269858b2d97722dd19316f1547633f26f76c840fa3ceb5188705
4
+ data.tar.gz: 487627e4cf995b265a2b3b70711c915bad37149982acd805d863208eaf579def
5
5
  SHA512:
6
- metadata.gz: '084ab4ade5ecb7545d0e89258f5ce586310e73c2fa9979a2d4365a870ccf9e50c43edba63d8c137220d955e78ef22e83b8ca8415d758d50ecc472a2dd1812a3a'
7
- data.tar.gz: 37cb59996c9577937971ccc6a56dbd6e12ac8f2eb57a68943bad660432003c2fbe70dd15a59cd33919a52d50000e09f8505a92eec512f08ba0a693d32c6a1d70
6
+ metadata.gz: fe5df67fda43b85dd4bd069fc764b0850b78caf03d5610b889bbe2566ca083eb67b9237ae0cb3bca78dee66b6b671c0ef07a2b673f9b5507ce4ac61a0d40af55
7
+ data.tar.gz: 118a98a329619d2c829f5ae89f7d9347d492bcfd4972b5b93832ca6b611f12f10febba81a41dc2cde56d9c8043846f18ec4d3a8ad20582c22486895f771324dd
data/CHANGELOG.md CHANGED
@@ -1,5 +1,73 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.1 (2025-01-31)
4
+
5
+ * Raise exception when using `model_authorization_action` in operations inheriting
6
+ directly from `RailsOps::Operation::Model::Load`. In `1.6.0`, this method was
7
+ renamed to `load_model_authorization_action` for these operations. Using
8
+ `model_authorization_action` was still possible, but RailsOps silently ignored
9
+ this. This release ensures that Load operations use the correct DSL method to
10
+ change authorization actions.
11
+
12
+ ## 1.7.0 (2025-01-30)
13
+
14
+ * Introduce new `:before_attr_assign` policy chain which allows to
15
+ access the `model` instance before the parameters are assigned.
16
+
17
+ * Introduce new `model_includes` DSL method which can be used to eager load
18
+ associations in Model operations. See the [corresponding section in the README](README.md#including-associated-records)
19
+ for more information.
20
+
21
+ * Fix bug with lazy authorization in `RailsOps::Operation::Model::Update`
22
+ operation which used a version of the `model` which was missing some
23
+ attributes.
24
+
25
+ * Deprecate lazy authorization in `RailsOps::Operation::Model::Update`
26
+ operations.
27
+
28
+ ### Migrating from earlier versions
29
+
30
+ * Make sure you use the correct policy chains, depending on the state you
31
+ need the `model` to be in. If you need the model before the attributes are
32
+ assigned to the passed-in params, use the `:before_attr_assign` chain.
33
+ In all other chains, the `model` instance has its attributes assigned to the
34
+ params you supplied to the operation.
35
+
36
+ * If you use `lazy` authorizaion in any of your `Update` operations, you are
37
+ advised to remove them and replace the lazy authorization by a custom functionality.
38
+ For example, this is the operation before:
39
+
40
+ ```ruby
41
+ class Operations::User::Update < RailsOps::Operation::Model::Update
42
+ model User
43
+
44
+ model_authorization_action :update, lazy: true
45
+ end
46
+ ```
47
+
48
+ and this is the operation afterwards:
49
+
50
+ ```ruby
51
+ class Operations::User::Update < RailsOps::Operation::Model::Update
52
+ model User
53
+
54
+ # Disable automatically authorizing against the `:update` action
55
+ model_authorization_action nil
56
+
57
+ policy :before_perform do
58
+ # Using "find_model" to retrieve the model from the database with
59
+ # the attributes before assigning the params to the model instance.
60
+ authorize_model! :update, find_model
61
+ end
62
+ end
63
+ ```
64
+
65
+ ## 1.6.1 (2025-01-24)
66
+
67
+ * Fix lazy authorization in `RailsOps::Operation::Model::Update` operation
68
+
69
+ Internal reference: `#133962`.
70
+
3
71
  ## 1.6.0 (2025-01-23)
4
72
 
5
73
  * Stable release based on previous RC releases
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_ops (1.6.0)
4
+ rails_ops (1.7.1)
5
5
  active_type (>= 1.3.0)
6
6
  minitest
7
7
  rails (> 4)
data/README.md CHANGED
@@ -454,6 +454,14 @@ an appropriate exception.
454
454
  As mentioned above, policies can be executed at various points in your
455
455
  operation's lifecycle. This is possible using *policy chains*:
456
456
 
457
+ - `:before_attr_assign`
458
+
459
+ Policies in this chain run before assigning the attributes to the model. This chain is only run
460
+ in `Model` operations, which at some point call the `assign_attributes` method. This chain is
461
+ the only chain in which the model is in the state *before* the passed in params are assigned.
462
+ If you need to run any code which needs the state of the model from the database (e.g. to run
463
+ custom authentications), this is the correct place.
464
+
457
465
  - `:on_init`
458
466
 
459
467
  Policies in this chain run after the operation class is instantiated.
@@ -1318,6 +1326,34 @@ end
1318
1326
  As this base class is very minimalistic, it is recommended to fully read and
1319
1327
  comprehend its source code.
1320
1328
 
1329
+ ### Including associated records
1330
+
1331
+ Normaly, when inheriting from `RailsOps::Operation::Model::Load` (as well as from the
1332
+ `Update` and the `Destroy` operations respectively), RailsOps only loads the instance
1333
+ of the model specified by the `id` parameter. In some cases, you'd want to eagerly load
1334
+ associations of the model, e.g. when you need to access associated records.
1335
+
1336
+ For this, RailsOps provides the `model_includes` DSL method, with which you can
1337
+ pass-in associations to eager load (the value will simply be passed on to an `includes`
1338
+ call). See the following code snipped for an example:
1339
+
1340
+ ```ruby
1341
+ class Operations::User::Load < RailsOps::Operation::Model::Load
1342
+ schema3 do
1343
+ int! :id, cast_str: true
1344
+ end
1345
+
1346
+ model ::User
1347
+
1348
+ # This will result in RailsOps eagerly loading the `posts`
1349
+ # association, as well as the comments and authors of the
1350
+ # comments.
1351
+ # The call that RailsOps will create is:
1352
+ # User.includes(posts: { comments: :author }).find_by(id: params[:id])
1353
+ model_includes posts: { comments: :author }
1354
+ end
1355
+ ```
1356
+
1321
1357
  ### Parameter extraction for create and update
1322
1358
 
1323
1359
  As mentioned before, the `Create` and `Update` base classes provide an
@@ -1392,6 +1428,10 @@ sensible default. See the respective class' source code for details.
1392
1428
 
1393
1429
  #### Lazy model update authorization
1394
1430
 
1431
+ *Please note that using lazy model update authorization is deprecated any may
1432
+ be removed in a future release. See the changelog for instructions on how to
1433
+ adapt your application.*
1434
+
1395
1435
  In case of operations inheriting from `RailsOps::Operation::Model::Update`, you
1396
1436
  can specify the `model_authorization_action` to be `lazy`, meaning that it will
1397
1437
  only be checked when *performing* the operation, but not on initialization. This
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.6.0
1
+ 1.7.1
@@ -12,6 +12,8 @@ module RailsOps::Mixins::Model::Authorization
12
12
  module ClassMethods
13
13
  # Gets or sets the action verb used for authorizing models.
14
14
  def model_authorization_action(*action, lazy: false)
15
+ RailsOps.deprecator.warn('Using `lazy` model authorization is deprecated and will be removed in a future version.') if lazy
16
+
15
17
  if action.size == 1
16
18
  self._model_authorization_action = action.first
17
19
  self._model_authorization_lazy = lazy
@@ -22,6 +24,8 @@ module RailsOps::Mixins::Model::Authorization
22
24
  return _model_authorization_action
23
25
  end
24
26
 
27
+ alias original_authorization_action model_authorization_action
28
+
25
29
  # This wraps the original method
26
30
  # {RailsOps::Mixins::ParamAuthorization::ClassClassMethods.authorize_param}
27
31
  # to automatically use `authorize_model_with_authorize_only` and pass the
@@ -6,6 +6,7 @@ module RailsOps::Mixins::Policies
6
6
  extend ActiveSupport::Concern
7
7
 
8
8
  POLICY_CHAIN_KEYS = %i[
9
+ before_attr_assign
9
10
  on_init
10
11
  before_perform
11
12
  after_perform
@@ -26,6 +27,12 @@ module RailsOps::Mixins::Policies
26
27
  fail "Unknown policy chain #{chain.inspect}, available are #{POLICY_CHAIN_KEYS.inspect}."
27
28
  end
28
29
 
30
+ # The `before_attr_assign` chain is only allowed if the operation is a model
31
+ # operation, i.e. it needs to implement the `build_model` method.
32
+ if chain == :before_attr_assign && !method_defined?(:assign_attributes)
33
+ fail 'Policy :before_attr_assign may not be used unless your operation defines the `assign_attributes` method!'
34
+ end
35
+
29
36
  self._policy_chains = _policy_chains.dup
30
37
  if prepend_action
31
38
  _policy_chains[chain] = [block] + _policy_chains[chain]
@@ -1,4 +1,8 @@
1
1
  class RailsOps::Operation::Model::Destroy < RailsOps::Operation::Model::Load
2
+ def self.model_authorization_action(*args, **kwargs, &block)
3
+ original_authorization_action(*args, **kwargs, &block)
4
+ end
5
+
2
6
  model_authorization_action :destroy
3
7
  lock_mode :exclusive
4
8
 
@@ -2,6 +2,7 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
2
2
  class_attribute :_lock_model_at_build
3
3
  class_attribute :_load_model_authorization_action
4
4
  class_attribute :_lock_mode
5
+ class_attribute :_model_includes
5
6
 
6
7
  policy :on_init do
7
8
  model
@@ -30,6 +31,11 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
30
31
 
31
32
  load_model_authorization_action :read
32
33
 
34
+ def self.model_authorization_action(*args, **kwargs, &block)
35
+ fail 'Using `model_authorization_action` in operations inheriting from RailsOps::Operation::Model::Load ' \
36
+ 'is not allowed, as this action will not be checked in `Load` operations. Use `load_model_authorization_action` instead.'
37
+ end
38
+
33
39
  def self.lock_model_at_build(enabled = true)
34
40
  self._lock_model_at_build = enabled
35
41
  end
@@ -64,6 +70,10 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
64
70
  :id
65
71
  end
66
72
 
73
+ def self.model_includes(includes)
74
+ self._model_includes = includes
75
+ end
76
+
67
77
  def find_model
68
78
  unless params[model_id_field]
69
79
  fail "Param #{model_id_field.inspect} must be given."
@@ -75,6 +85,9 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
75
85
  # Express intention to lock if required
76
86
  relation = lock_relation(relation)
77
87
 
88
+ # Apply includes if given in the operation
89
+ relation = relation.includes(self.class._model_includes) if self.class._model_includes.present?
90
+
78
91
  # Fetch (and possibly lock) model
79
92
  model = relation.find_by!(model_id_field => params[model_id_field])
80
93
 
@@ -116,7 +129,7 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
116
129
  adapter_type = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
117
130
 
118
131
  case adapter_type
119
- when :mysql, :mysql2, :oracleenhanced
132
+ when :mysql, :mysql2, :oracleenhanced, :trilogy
120
133
  return 'LOCK IN SHARE MODE'
121
134
  when :postgresql
122
135
  return 'FOR SHARE'
@@ -1,4 +1,8 @@
1
1
  class RailsOps::Operation::Model::Update < RailsOps::Operation::Model::Load
2
+ def self.model_authorization_action(*args, **kwargs, &block)
3
+ original_authorization_action(*args, **kwargs, &block)
4
+ end
5
+
2
6
  model_authorization_action :update
3
7
  lock_mode :exclusive
4
8
 
@@ -19,8 +23,17 @@ class RailsOps::Operation::Model::Update < RailsOps::Operation::Model::Load
19
23
 
20
24
  policy :before_perform do
21
25
  # If the authorization is configured to be lazy, we need to call the authorization
22
- # on the copy of the model that we made before assigning the new attributes.
23
- authorize_model! model_authorization_action, @model_before_assigning_attributes if self.class._model_authorization_lazy
26
+ # on a fresh copy of the model, before assigning the attributes. We simply use the `find_model`
27
+ # method from our parent class and then run the authorization on this instance.
28
+ if self.class._model_authorization_lazy
29
+ model_from_database = find_model
30
+
31
+ if model_from_database.respond_to?(:parent_op=)
32
+ model_from_database.parent_op = self
33
+ end
34
+
35
+ authorize_model! model_authorization_action, model_from_database
36
+ end
24
37
  end
25
38
 
26
39
  def model_authorization
@@ -37,13 +50,8 @@ class RailsOps::Operation::Model::Update < RailsOps::Operation::Model::Load
37
50
  build_nested_model_ops :update
38
51
 
39
52
  # Perform update authorization BEFORE assigning attributes. If the authorization is lazy,
40
- # we copy the model before assigning the attributes, such that we can call the authorization
41
- # later on.
42
- if self.class._model_authorization_lazy
43
- @model_before_assigning_attributes = @model.dup
44
- else
45
- model_authorization
46
- end
53
+ # we'll call the authorization later on in the `before_perform` block.
54
+ model_authorization unless self.class._model_authorization_lazy
47
55
 
48
56
  # Assign attributes
49
57
  assign_attributes
@@ -131,6 +131,8 @@ class RailsOps::Operation::Model < RailsOps::Operation
131
131
  # are meant for nested models (registered via `nested_model_op`) will not
132
132
  # be assigned. You can turn this filtering off by passing `false`.
133
133
  def assign_attributes(attributes = nil, model: nil, without_protection: false, without_nested_models: true)
134
+ run_policies :before_attr_assign
135
+
134
136
  model ||= self.model
135
137
 
136
138
  attributes ||= extract_attributes_from_params(model)
data/rails_ops.gemspec CHANGED
@@ -1,14 +1,14 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: rails_ops 1.6.0 ruby lib
2
+ # stub: rails_ops 1.7.1 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "rails_ops".freeze
6
- s.version = "1.6.0"
6
+ s.version = "1.7.1"
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-01-23"
11
+ s.date = "2025-01-31"
12
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]
13
13
  s.licenses = ["MIT".freeze]
14
14
  s.rubygems_version = "3.4.6".freeze
@@ -33,6 +33,58 @@ class RailsOps::Mixins::PoliciesTest < ActiveSupport::TestCase
33
33
  op.run!.sequence
34
34
  end
35
35
 
36
+ def test_basic_policies_with_model
37
+ group = Group.create!
38
+
39
+ op = Class.new(RailsOps::Operation::Model::Update) do
40
+ attr_reader :sequence
41
+
42
+ model Group
43
+
44
+ policy do
45
+ @sequence << :default
46
+ end
47
+
48
+ policy :before_attr_assign do
49
+ @sequence = []
50
+ @sequence << :before_attr_assign
51
+ end
52
+
53
+ policy :on_init do
54
+ @sequence << :on_init
55
+ end
56
+
57
+ policy :before_perform do
58
+ @sequence << :before_perform
59
+ end
60
+
61
+ policy :after_perform do
62
+ @sequence << :after_perform
63
+ end
64
+
65
+ def perform
66
+ @sequence << :perform
67
+ end
68
+ end
69
+
70
+ assert_equal %i[before_attr_assign on_init default before_perform perform after_perform],
71
+ op.run!(id: group.id).sequence
72
+ end
73
+
74
+ def test_before_attr_assign_needs_build_model
75
+ # When trying to use the `:before_attr_assign` chain, we need
76
+ # to have the `assign_attributes` method implemented, which usually
77
+ # is implemented in the `RailsOps::Operation::Model` base class
78
+ # and runs the `before_attr_assign` policy chain.
79
+ assert_raises RuntimeError, match: /Policy :before_attr_assign may not be used unless your operation defines the `assign_attributes` method!/ do
80
+ Class.new(RailsOps::Operation) do
81
+ policy :before_attr_assign do
82
+ # Nothing needed here
83
+ end
84
+ end
85
+ end
86
+ end
87
+
36
88
  def test_prepend_action
37
89
  op = Class.new(RailsOps::Operation) do
38
90
  attr_reader :sequence
@@ -65,4 +65,51 @@ class RailsOps::Operation::Model::CreateTest < ActiveSupport::TestCase
65
65
  op.build_model
66
66
  end
67
67
  end
68
+
69
+ def test_policies
70
+ op_klass = Class.new(RailsOps::Operation::Model::Create) do
71
+ model ::Group
72
+
73
+ policy do
74
+ # Here, we need the model to have the new name assigned
75
+ fail 'Attribute should be assigned to new value' unless model.name == 'new_name'
76
+
77
+ # However, the model should not be persisted yet
78
+ fail 'Model should not be persisted to the database yet' if model.persisted?
79
+ end
80
+
81
+ policy :before_attr_assign do
82
+ # The name of the model itself should still be nil
83
+ fail 'Attribute should not be assigned to a value yet' if model.name.present?
84
+ end
85
+
86
+ policy :on_init do
87
+ # Here, we need the model to have the new name assigned
88
+ fail 'Attribute should be assigned to new value' unless model.name == 'new_name'
89
+
90
+ # However, the model should not be persisted yet
91
+ fail 'Model should not be persisted to the database yet' if model.persisted?
92
+ end
93
+
94
+ policy :before_perform do
95
+ # Here, we need the model to have the new name assigned
96
+ fail 'Attribute should be assigned to new value' unless model.name == 'new_name'
97
+
98
+ # However, the model should not be persisted yet
99
+ fail 'Model should not be persisted to the database yet' if model.persisted?
100
+ end
101
+
102
+ policy :after_perform do
103
+ # Here, we need the model to have the new name assigned
104
+ fail 'Attribute should be assigned to new value' unless model.name == 'new_name'
105
+
106
+ # Now, the model should be persisted to the database
107
+ fail 'Model should not be persisted to the database yet' unless model.persisted?
108
+ end
109
+ end
110
+
111
+ assert_nothing_raised do
112
+ op_klass.run!(group: { name: 'new_name' })
113
+ end
114
+ end
68
115
  end
@@ -28,4 +28,14 @@ class RailsOps::Operation::Model::DestroyTest < ActiveSupport::TestCase
28
28
  op.run!
29
29
  end
30
30
  end
31
+
32
+ def test_model_authorization_action_permitted
33
+ assert_nothing_raised do
34
+ Class.new(RailsOps::Operation::Model::Destroy) do
35
+ model Group
36
+ load_model_authorization_action :foobar
37
+ model_authorization_action :barfoo
38
+ end
39
+ end
40
+ end
31
41
  end
@@ -52,6 +52,15 @@ class RailsOps::Operation::Model::LoadTest < ActiveSupport::TestCase
52
52
 
53
53
  def test_too_many_authorization_actions
54
54
  assert_raises_with_message ArgumentError, 'Too many arguments' do
55
+ Class.new(RailsOps::Operation::Model::Load) do
56
+ model Group
57
+ load_model_authorization_action :read, :update
58
+ end
59
+ end
60
+ end
61
+
62
+ def test_model_authorization_action_not_permitted
63
+ assert_raises_with_message RuntimeError, /Use `load_model_authorization_action` instead/ do
55
64
  Class.new(RailsOps::Operation::Model::Load) do
56
65
  model Group
57
66
  model_authorization_action :read, :update
@@ -163,4 +163,62 @@ class RailsOps::Operation::Model::UpdateTest < ActiveSupport::TestCase
163
163
  ensure
164
164
  RailsOps.config.authorization_backend = nil
165
165
  end
166
+
167
+ def test_policies
168
+ op_klass = Class.new(RailsOps::Operation::Model::Update) do
169
+ model ::Group
170
+
171
+ policy do
172
+ # Here, we need the model to have the new name assigned
173
+ fail 'Attribute should be assigned to new value' unless model.name == 'new_name'
174
+
175
+ # However, the new name should not be persisted to the database yet
176
+ fail 'Attribute change should not be persisted yet' unless Group.find(model.id).name == 'foobar'
177
+ end
178
+
179
+ policy :before_attr_assign do
180
+ # The name of the model itself should still be the initial value
181
+ fail 'Attribute should not be assigned to new value yet' unless model.name == 'foobar'
182
+ end
183
+
184
+ policy :on_init do
185
+ # Here, we need the model to have the new name assigned
186
+ fail 'Attribute should be assigned to new value' unless model.name == 'new_name'
187
+
188
+ # However, the new name should not be persisted to the database yet
189
+ fail 'Attribute change should not be persisted yet' unless Group.find(model.id).name == 'foobar'
190
+ end
191
+
192
+ policy :before_perform do
193
+ # Here, we need the model to have the new name assigned
194
+ fail 'Attribute should be assigned to new value' unless model.name == 'new_name'
195
+
196
+ # However, the new name should not be persisted to the database yet
197
+ fail 'Attribute change should not be persisted yet' unless Group.find(model.id).name == 'foobar'
198
+ end
199
+
200
+ policy :after_perform do
201
+ # Here, we need the model to have the new name assigned
202
+ fail 'Attribute should be assigned to new value' unless model.name == 'new_name'
203
+
204
+ # Also, the new name should be persisted to the database
205
+ fail 'Attribute change should not be persisted yet' unless Group.find(model.id).name == 'new_name'
206
+ end
207
+ end
208
+
209
+ model = Group.create!(name: 'foobar')
210
+ assert_nothing_raised do
211
+ op_klass.run!(id: model.id, group: { name: 'new_name' })
212
+ end
213
+ end
214
+
215
+ def test_model_authorization_action_permitted
216
+ assert_nothing_raised do
217
+ Class.new(RailsOps::Operation::Model::Update) do
218
+ model Group
219
+ load_model_authorization_action :foobar
220
+ model_authorization_action :barfoo
221
+ end
222
+ end
223
+ end
166
224
  end
@@ -89,9 +89,21 @@ class RailsOps::Operation::UpdateLazyAuthTest < ActiveSupport::TestCase
89
89
 
90
90
  def test_permitted_update_unpermitted_color
91
91
  ctx = RailsOps::Context.new(ability: ABILITY.new(read: true, update: true))
92
- op = BASIC_OP.new(ctx, id: 3, group: { color: 'red' })
92
+ op = BASIC_OP.new(ctx, id: 3)
93
93
  assert_raises CanCan::AccessDenied do
94
94
  op.run!
95
95
  end
96
96
  end
97
+
98
+ def test_permitted_update_permitted_color_other_color_target_state
99
+ # Here, we test that we can update a record where the ability
100
+ # allows us the `:update` action on the current state of the model,
101
+ # despite bringing the object to a state where we won't have the
102
+ # ability to update the object anymore afterwards.
103
+ ctx = RailsOps::Context.new(ability: ABILITY.new(read: true, update: true))
104
+ op = BASIC_OP.new(ctx, id: 1, group: { color: 'blue' })
105
+ assert_nothing_raised do
106
+ op.run!
107
+ end
108
+ end
97
109
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_ops
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.0
4
+ version: 1.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-23 00:00:00.000000000 Z
11
+ date: 2025-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_type