rails_ops 1.6.0 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 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