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 +4 -4
- data/CHANGELOG.md +68 -0
- data/Gemfile.lock +1 -1
- data/README.md +40 -0
- data/VERSION +1 -1
- data/lib/rails_ops/mixins/model/authorization.rb +4 -0
- data/lib/rails_ops/mixins/policies.rb +7 -0
- data/lib/rails_ops/operation/model/destroy.rb +4 -0
- data/lib/rails_ops/operation/model/load.rb +14 -1
- data/lib/rails_ops/operation/model/update.rb +17 -9
- data/lib/rails_ops/operation/model.rb +2 -0
- data/rails_ops.gemspec +3 -3
- data/test/unit/rails_ops/mixins/policies_test.rb +52 -0
- data/test/unit/rails_ops/operation/model/create_test.rb +47 -0
- data/test/unit/rails_ops/operation/model/destroy_test.rb +10 -0
- data/test/unit/rails_ops/operation/model/load_test.rb +9 -0
- data/test/unit/rails_ops/operation/model/update_test.rb +58 -0
- data/test/unit/rails_ops/operation/update_lazy_auth_test.rb +13 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2daa2888b099269858b2d97722dd19316f1547633f26f76c840fa3ceb5188705
|
4
|
+
data.tar.gz: 487627e4cf995b265a2b3b70711c915bad37149982acd805d863208eaf579def
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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.
|
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
|
23
|
-
|
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
|
41
|
-
|
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.
|
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
|
+
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-
|
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
|
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.
|
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-
|
11
|
+
date: 2025-01-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: active_type
|