rails_ops 1.6.0 → 1.7.0

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: 010b1029e9dfdeef808a17c61fb3b5b6f47749b363962234a472d7b59f2830c3
4
+ data.tar.gz: f8ba45974bd863da77b7c5d27530bd94d6b4a9b04962848d948799d2fa5e37c4
5
5
  SHA512:
6
- metadata.gz: '084ab4ade5ecb7545d0e89258f5ce586310e73c2fa9979a2d4365a870ccf9e50c43edba63d8c137220d955e78ef22e83b8ca8415d758d50ecc472a2dd1812a3a'
7
- data.tar.gz: 37cb59996c9577937971ccc6a56dbd6e12ac8f2eb57a68943bad660432003c2fbe70dd15a59cd33919a52d50000e09f8505a92eec512f08ba0a693d32c6a1d70
6
+ metadata.gz: 7bef9d33ac6d02a9a6634d8a4fcabce53bfad4abc4d589cef63f51e2aceebb751bba4a3e65c7462b7fe670d0bed0b94a334b3d0c9b119c43b26ac62dd2ae90e7
7
+ data.tar.gz: c382da348b07a9b4ed75d184defcd15bc6d26470158d0ab717ef919de7fd2fa8c4e3443be235be42a01eb63fcf760baf71365c851ce2e02fa50f07dbf52b21fc
data/CHANGELOG.md CHANGED
@@ -1,5 +1,64 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.7.0 (2025-01-30)
4
+
5
+ * Introduce new `:before_attr_assign` policy chain which allows to
6
+ access the `model` instance before the parameters are assigned.
7
+
8
+ * Introduce new `model_includes` DSL method which can be used to eager load
9
+ associations in Model operations. See the [corresponding section in the README](README.md#including-associated-records)
10
+ for more information.
11
+
12
+ * Fix bug with lazy authorization in `RailsOps::Operation::Model::Update`
13
+ operation which used a version of the `model` which was missing some
14
+ attributes.
15
+
16
+ * Deprecate lazy authorization in `RailsOps::Operation::Model::Update`
17
+ operations.
18
+
19
+ ### Migrating from earlier versions
20
+
21
+ * Make sure you use the correct policy chains, depending on the state you
22
+ need the `model` to be in. If you need the model before the attributes are
23
+ assigned to the passed-in params, use the `:before_attr_assign` chain.
24
+ In all other chains, the `model` instance has its attributes assigned to the
25
+ params you supplied to the operation.
26
+
27
+ * If you use `lazy` authorizaion in any of your `Update` operations, you are
28
+ advised to remove them and replace the lazy authorization by a custom functionality.
29
+ For example, this is the operation before:
30
+
31
+ ```ruby
32
+ class Operations::User::Update < RailsOps::Operation::Model::Update
33
+ model User
34
+
35
+ model_authorization_action :update, lazy: true
36
+ end
37
+ ```
38
+
39
+ and this is the operation afterwards:
40
+
41
+ ```ruby
42
+ class Operations::User::Update < RailsOps::Operation::Model::Update
43
+ model User
44
+
45
+ # Disable automatically authorizing against the `:update` action
46
+ model_authorization_action nil
47
+
48
+ policy :before_perform do
49
+ # Using "find_model" to retrieve the model from the database with
50
+ # the attributes before assigning the params to the model instance.
51
+ authorize_model! :update, find_model
52
+ end
53
+ end
54
+ ```
55
+
56
+ ## 1.6.1 (2025-01-24)
57
+
58
+ * Fix lazy authorization in `RailsOps::Operation::Model::Update` operation
59
+
60
+ Internal reference: `#133962`.
61
+
3
62
  ## 1.6.0 (2025-01-23)
4
63
 
5
64
  * 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.0)
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.0
@@ -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
@@ -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]
@@ -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
@@ -64,6 +65,10 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
64
65
  :id
65
66
  end
66
67
 
68
+ def self.model_includes(includes)
69
+ self._model_includes = includes
70
+ end
71
+
67
72
  def find_model
68
73
  unless params[model_id_field]
69
74
  fail "Param #{model_id_field.inspect} must be given."
@@ -75,6 +80,9 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
75
80
  # Express intention to lock if required
76
81
  relation = lock_relation(relation)
77
82
 
83
+ # Apply includes if given in the operation
84
+ relation = relation.includes(self.class._model_includes) if self.class._model_includes.present?
85
+
78
86
  # Fetch (and possibly lock) model
79
87
  model = relation.find_by!(model_id_field => params[model_id_field])
80
88
 
@@ -116,7 +124,7 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
116
124
  adapter_type = ActiveRecord::Base.connection.adapter_name.downcase.to_sym
117
125
 
118
126
  case adapter_type
119
- when :mysql, :mysql2, :oracleenhanced
127
+ when :mysql, :mysql2, :oracleenhanced, :trilogy
120
128
  return 'LOCK IN SHARE MODE'
121
129
  when :postgresql
122
130
  return 'FOR SHARE'
@@ -19,8 +19,17 @@ class RailsOps::Operation::Model::Update < RailsOps::Operation::Model::Load
19
19
 
20
20
  policy :before_perform do
21
21
  # 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
22
+ # on a fresh copy of the model, before assigning the attributes. We simply use the `find_model`
23
+ # method from our parent class and then run the authorization on this instance.
24
+ if self.class._model_authorization_lazy
25
+ model_from_database = find_model
26
+
27
+ if model_from_database.respond_to?(:parent_op=)
28
+ model_from_database.parent_op = self
29
+ end
30
+
31
+ authorize_model! model_authorization_action, model_from_database
32
+ end
24
33
  end
25
34
 
26
35
  def model_authorization
@@ -37,13 +46,8 @@ class RailsOps::Operation::Model::Update < RailsOps::Operation::Model::Load
37
46
  build_nested_model_ops :update
38
47
 
39
48
  # 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
49
+ # we'll call the authorization later on in the `before_perform` block.
50
+ model_authorization unless self.class._model_authorization_lazy
47
51
 
48
52
  # Assign attributes
49
53
  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.0 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.0"
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-30"
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
@@ -163,4 +163,52 @@ 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
166
214
  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.0
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-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_type