rails_ops 1.5.8 → 1.6.0.rc0

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: d447da1ff197bd043c6831fd1fd4136541a064faf398ad78f376f45d6c98bda3
4
- data.tar.gz: 351fb718726616969c07e48bc3967d7f3cd4e6bdda07512b1b5e45a878cd66fc
3
+ metadata.gz: cc1e32fdbfeebc90a622cb5394dcf1d4d7b41e2d8286a45eadfec4db6f0894e8
4
+ data.tar.gz: 5fcf1149cb2b0a348a6ecdb81e7c874056aa26bac979b436c17144ee48d7094b
5
5
  SHA512:
6
- metadata.gz: 2086a657d14d7addab4f5e995afa457853f574e0c74bfa074043f28d22cb6f2acc9367cf2d29c9a457218897baf9fb15430452025e4c8fdbcd5f29c9616f7663
7
- data.tar.gz: e3ead99ce7cdddc297535f11ad37acdf36244468bc686114d2342bbb0b13289a1f669ca150c4af3b2d1100482336dc697de38f6a78b2e2fac0923d9f21f6bf1b
6
+ metadata.gz: c0f97ccdac3919747d2c952805fc0e605e357e27ad8c3500f3e0e384e8063683a8031f9f8271ac3b492e1f9dca2633b57cdfad2cf81ec231b70cbeae8e54c797
7
+ data.tar.gz: f6a1e8a2eafc54e21a89f1b30b6c5d931ee90c4f352d8eed1df9f17e5f921b4da7115b2cc1d3bac35beaf0ebddfdf1cadffaaa7cd7ac7e935efb7ffcb742582d
@@ -11,7 +11,7 @@ jobs:
11
11
  - name: Set up Ruby
12
12
  uses: ruby/setup-ruby@v1
13
13
  with:
14
- ruby-version: 3.0.1
14
+ ruby-version: 3.1.0
15
15
  bundler-cache: true
16
16
  - name: Run rubocop
17
17
  run: bundle exec rubocop
@@ -37,6 +37,12 @@ jobs:
37
37
  ruby-version: '3.2.0'
38
38
  - rails-version: '7.1'
39
39
  ruby-version: '3.3.0'
40
+ - rails-version: '8.0'
41
+ ruby-version: '3.2.0'
42
+ - rails-version: '8.0'
43
+ ruby-version: '3.3.0'
44
+ - rails-version: '8.0'
45
+ ruby-version: '3.4.0'
40
46
  name: Test against Ruby ${{ matrix.ruby-version }} / Rails ${{ matrix.rails-version }}
41
47
  env:
42
48
  BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/rails_${{ matrix.rails-version }}.gemfile
data/Appraisals CHANGED
@@ -1,3 +1,8 @@
1
+ appraise 'rails-8.0' do
2
+ gem 'rails', '~> 8.0.1'
3
+ gem 'sqlite3', '~> 2.1'
4
+ end
5
+
1
6
  appraise 'rails-7.2' do
2
7
  gem 'rails', '~> 7.2.1'
3
8
  end
data/CHANGELOG.md CHANGED
@@ -1,5 +1,77 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.0.rc0 (2025-01-22)
4
+
5
+ * Adapt the way model authorization works for an additional layer of security:
6
+
7
+ * Update-Operations (operations inheriting from `RailsOps::Operation::Model::Update`)
8
+ now perform their model authorization immediately after the model is loaded (in `build_model`).
9
+
10
+ Previously, the authorization was only performed after the attributes have
11
+ already been assigned, never checking authorization against the "pristine"
12
+ model.
13
+
14
+ * Load-Operations (operations inheriting from
15
+ `RailsOps::Operation::Model::Load`) now always load their associated model
16
+ **directly on OP instantiation**. Previously, this was only the case if load
17
+ model authorization was enabled.
18
+
19
+ In addition, the method `model_authorization` has been renamed to
20
+ `load_model_authorization` in order to separate it from the method
21
+ `model_authorization` in `RailsOps::Operation::Model::Update`.
22
+
23
+ Internal reference: `#133622`.
24
+
25
+ ### Migrating from earlier versions
26
+
27
+ Check that operations using model authorization still work as expected, especially
28
+ operations inheriting from `RailsOps::Operation::Model::Update` or from
29
+ `RailsOps::Operation::Model::Load`:
30
+
31
+ * For operations inheriting from `RailsOps::Operation::Model::Load`: Rename all
32
+ uses of `model_authorization` to `load_model_authorization`.
33
+
34
+ * For operations inheriting from `RailsOps::Operation::Model::Update`, you need
35
+ to make sure that running the model authorization on the "pristine" model (before
36
+ assigning the new attributes) is still applying your authorization logic in
37
+ the correct way (i.e. authorizing on the model *before* assigning the attributes
38
+ applies authorization correctly).
39
+ If you need to authorize the state *after* assigning the params to the model,
40
+ you'll need add that check manually in your operation.
41
+
42
+
43
+ One example of how this behaviour was changed: A user may only update
44
+ a `Group` object with a `color` of `'red'`:
45
+
46
+ ```ruby
47
+ class Ability
48
+ can :update, Group, color: 'red'
49
+ end
50
+ ```
51
+
52
+ Before, this was the way RailsOps handled it:
53
+
54
+ ```ruby
55
+ model = find_record(params[:id]) # => model = Group(color: 'blue')
56
+ model.assign_attributes(params) # params = { color: 'red' }
57
+ authorize! :update, model
58
+ ```
59
+
60
+ This works, because RailsOps already assigned `'red'` to the `color` attribute of the model. This means
61
+ that the `authorize!` call will succeed, even though the original model in the database is not permissible
62
+ to be updated by the user.
63
+
64
+ Afterwards, the behaviour is as follows:
65
+
66
+ ```ruby
67
+ model = find_record(params[:id]) # => model = Group(color: 'blue')
68
+ authorize! :update, model # => Fails with CanCan::AccessDenied, as the user may not update the group
69
+ # [...]
70
+ ```
71
+
72
+ After applying these changes, carefully test your application, run unit tests etc.
73
+ to ensure all operations still behave as expected in regards to authorization.
74
+
3
75
  ## 1.5.8 (2024-09-11)
4
76
 
5
77
  * Also allow single path segments as symbols instead of array for
data/Gemfile CHANGED
@@ -2,3 +2,14 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in rails_ops.gemspec
4
4
  gemspec
5
+
6
+ # Development dependencies should be specified in here
7
+ gem 'appraisal'
8
+ gem 'bundler'
9
+ gem 'cancancan'
10
+ gem 'pry'
11
+ gem 'rake'
12
+ gem 'rubocop', '1.70.0'
13
+ gem 'simplecov'
14
+ gem 'sprockets-rails'
15
+ gem 'sqlite3', '<2.0.0'
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_ops (1.5.8)
4
+ rails_ops (1.6.0.rc0)
5
5
  active_type (>= 1.3.0)
6
6
  minitest
7
7
  rails
@@ -97,7 +97,6 @@ GEM
97
97
  builder (3.2.4)
98
98
  cancancan (3.5.0)
99
99
  coderay (1.1.3)
100
- colorize (0.8.1)
101
100
  concurrent-ruby (1.3.1)
102
101
  connection_pool (2.4.1)
103
102
  crass (1.0.6)
@@ -113,7 +112,8 @@ GEM
113
112
  irb (1.11.2)
114
113
  rdoc
115
114
  reline (>= 0.4.2)
116
- json (2.6.3)
115
+ json (2.9.1)
116
+ language_server-protocol (3.17.0.3)
117
117
  loofah (2.22.0)
118
118
  crass (~> 1.0.2)
119
119
  nokogiri (>= 1.12.0)
@@ -141,9 +141,10 @@ GEM
141
141
  racc (~> 1.4)
142
142
  nokogiri (1.16.5-x86_64-linux)
143
143
  racc (~> 1.4)
144
- parallel (1.22.1)
145
- parser (3.2.1.1)
144
+ parallel (1.26.3)
145
+ parser (3.3.7.0)
146
146
  ast (~> 2.4.1)
147
+ racc
147
148
  pry (0.14.2)
148
149
  coderay (~> 1.1)
149
150
  method_source (~> 1.0)
@@ -191,25 +192,23 @@ GEM
191
192
  rake (13.1.0)
192
193
  rdoc (6.6.3.1)
193
194
  psych (>= 4.0.0)
194
- regexp_parser (2.7.0)
195
+ regexp_parser (2.10.0)
195
196
  reline (0.4.3)
196
197
  io-console (~> 0.5)
197
198
  request_store (1.5.1)
198
199
  rack (>= 1.4)
199
- rexml (3.3.6)
200
- strscan
201
- rubocop (1.45.1)
200
+ rubocop (1.70.0)
202
201
  json (~> 2.3)
202
+ language_server-protocol (>= 3.17.0)
203
203
  parallel (~> 1.10)
204
- parser (>= 3.2.0.0)
204
+ parser (>= 3.3.0.2)
205
205
  rainbow (>= 2.2.2, < 4.0)
206
- regexp_parser (>= 1.8, < 3.0)
207
- rexml (>= 3.2.5, < 4.0)
208
- rubocop-ast (>= 1.24.1, < 2.0)
206
+ regexp_parser (>= 2.9.3, < 3.0)
207
+ rubocop-ast (>= 1.36.2, < 2.0)
209
208
  ruby-progressbar (~> 1.7)
210
- unicode-display_width (>= 2.4.0, < 3.0)
211
- rubocop-ast (1.28.0)
212
- parser (>= 3.2.1.0)
209
+ unicode-display_width (>= 2.4.0, < 4.0)
210
+ rubocop-ast (1.37.0)
211
+ parser (>= 3.3.1.0)
213
212
  ruby-progressbar (1.13.0)
214
213
  ruby2_keywords (0.0.4)
215
214
  schemacop (3.0.22)
@@ -231,12 +230,13 @@ GEM
231
230
  sqlite3 (1.6.2-x86_64-darwin)
232
231
  sqlite3 (1.6.2-x86_64-linux)
233
232
  stringio (3.1.0)
234
- strscan (3.1.0)
235
233
  thor (1.3.1)
236
234
  timeout (0.4.1)
237
235
  tzinfo (2.0.6)
238
236
  concurrent-ruby (~> 1.0)
239
- unicode-display_width (2.4.2)
237
+ unicode-display_width (3.1.4)
238
+ unicode-emoji (~> 4.0, >= 4.0.4)
239
+ unicode-emoji (4.0.4)
240
240
  webrick (1.8.1)
241
241
  websocket-driver (0.7.6)
242
242
  websocket-extensions (>= 0.1.0)
@@ -254,11 +254,10 @@ DEPENDENCIES
254
254
  appraisal
255
255
  bundler
256
256
  cancancan
257
- colorize
258
257
  pry
259
258
  rails_ops!
260
259
  rake
261
- rubocop (= 1.45.1)
260
+ rubocop (= 1.70.0)
262
261
  simplecov
263
262
  sprockets-rails
264
263
  sqlite3 (< 2.0.0)
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright © 2017-2024 Sitrox
3
+ Copyright © 2017-2025 Sitrox
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -29,12 +29,14 @@ Requirements & Installation
29
29
  * Rails 7.0.x
30
30
  * Rails 7.1.x
31
31
  * Rails 7.2.x
32
+ * Rails 8.0.x
32
33
  - Additionally, the following Ruby versions are covered by our unit tests:
33
34
  * 2.7.8
34
35
  * 3.0.1
35
36
  * 3.1.0
36
37
  * 3.2.0
37
38
  * 3.3.0
39
+ * 3.4.0
38
40
  - Please see the [unit test workflow](https://github.com/sitrox/rails_ops/actions/workflows/ruby.yml) for the combinations of the Rails & Ruby versions, as only compatible versions are tested with each other.
39
41
  - Prior Rails and Ruby versions may be supported but they are not tested in the CI.
40
42
  - Rails Ops' model operations require ActiveRecord but are database / adapter
@@ -1353,8 +1355,9 @@ authorization check based on this model.
1353
1355
 
1354
1356
  While you can override this method to perform custom authorization, RailsOps
1355
1357
  provides a base implementation. Using the class method
1356
- `model_authorization_action`, you can specify an action verb that is used for
1357
- authorizing your model.
1358
+ `model_authorization_action` (or `load_model_authorization` for operations
1359
+ inheriting from `RailsOps::Operation::Model::Load`), you can specify an action
1360
+ verb that is used for authorizing your model.
1358
1361
 
1359
1362
  ```ruby
1360
1363
  class Operations::User::Load < RailsOps::Operation::Model::Load
@@ -1362,7 +1365,23 @@ class Operations::User::Load < RailsOps::Operation::Model::Load
1362
1365
 
1363
1366
  # This automatically calls `authorize_model! :read` after operation
1364
1367
  # instantiation.
1365
- model_authorization_action :read
1368
+ load_model_authorization :read
1369
+ end
1370
+ ```
1371
+
1372
+ Another example for an update operation:
1373
+
1374
+ ```ruby
1375
+ class Operations::User::Update < RailsOps::Operation::Model::Update
1376
+ model User
1377
+
1378
+ # This automatically calls `authorize_model! :read` after operation
1379
+ # instantiation.
1380
+ load_model_authorization :read
1381
+
1382
+ # This automatically calls `authorize_model! :update` after operation
1383
+ # instantiation.
1384
+ model_authorization :update
1366
1385
  end
1367
1386
  ```
1368
1387
 
@@ -1793,4 +1812,4 @@ Rails architecture.
1793
1812
 
1794
1813
  ## Copyright
1795
1814
 
1796
- Copyright © 2017 - 2024 Sitrox. See `LICENSE` for further details.
1815
+ Copyright © 2017 - 2025 Sitrox. See `LICENSE` for further details.
data/Rakefile CHANGED
@@ -16,16 +16,6 @@ task :gemspec do
16
16
  spec.require_paths = ['lib']
17
17
  spec.licenses = ['MIT']
18
18
 
19
- spec.add_development_dependency 'appraisal'
20
- spec.add_development_dependency 'bundler'
21
- spec.add_development_dependency 'rake'
22
- spec.add_development_dependency 'sqlite3', '<2.0.0'
23
- spec.add_development_dependency 'cancancan'
24
- spec.add_development_dependency 'pry'
25
- spec.add_development_dependency 'colorize'
26
- spec.add_development_dependency 'rubocop', '1.45.1'
27
- spec.add_development_dependency 'sprockets-rails'
28
- spec.add_development_dependency 'simplecov'
29
19
  spec.add_dependency 'active_type', '>= 1.3.0'
30
20
  spec.add_dependency 'minitest'
31
21
  spec.add_dependency 'rails'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.8
1
+ 1.6.0.rc0
@@ -2,6 +2,15 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'appraisal'
6
+ gem 'bundler'
7
+ gem 'cancancan'
8
+ gem 'pry'
5
9
  gem 'rails', '~> 6.0.4'
10
+ gem 'rake'
11
+ gem 'rubocop', '1.70.0'
12
+ gem 'simplecov'
13
+ gem 'sprockets-rails'
14
+ gem 'sqlite3', '<2.0.0'
6
15
 
7
16
  gemspec path: '../'
@@ -2,6 +2,15 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'appraisal'
6
+ gem 'bundler'
7
+ gem 'cancancan'
8
+ gem 'pry'
5
9
  gem 'rails', '~> 6.1.4'
10
+ gem 'rake'
11
+ gem 'rubocop', '1.70.0'
12
+ gem 'simplecov'
13
+ gem 'sprockets-rails'
14
+ gem 'sqlite3', '<2.0.0'
6
15
 
7
16
  gemspec path: '../'
@@ -2,6 +2,15 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'appraisal'
6
+ gem 'bundler'
7
+ gem 'cancancan'
8
+ gem 'pry'
5
9
  gem 'rails', '~> 7.0.1'
10
+ gem 'rake'
11
+ gem 'rubocop', '1.70.0'
12
+ gem 'simplecov'
13
+ gem 'sprockets-rails'
14
+ gem 'sqlite3', '<2.0.0'
6
15
 
7
16
  gemspec path: '../'
@@ -2,6 +2,15 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'appraisal'
6
+ gem 'bundler'
7
+ gem 'cancancan'
8
+ gem 'pry'
5
9
  gem 'rails', '~> 7.1.0'
10
+ gem 'rake'
11
+ gem 'rubocop', '1.70.0'
12
+ gem 'simplecov'
13
+ gem 'sprockets-rails'
14
+ gem 'sqlite3', '<2.0.0'
6
15
 
7
16
  gemspec path: '../'
@@ -2,6 +2,15 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
+ gem 'appraisal'
6
+ gem 'bundler'
7
+ gem 'cancancan'
8
+ gem 'pry'
5
9
  gem 'rails', '~> 7.2.1'
10
+ gem 'rake'
11
+ gem 'rubocop', '1.70.0'
12
+ gem 'simplecov'
13
+ gem 'sprockets-rails'
14
+ gem 'sqlite3', '<2.0.0'
6
15
 
7
16
  gemspec path: '../'
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'appraisal'
6
+ gem 'bundler'
7
+ gem 'cancancan'
8
+ gem 'pry'
9
+ gem 'rails', '~> 8.0.1'
10
+ gem 'rake'
11
+ gem 'rubocop', '1.70.0'
12
+ gem 'simplecov'
13
+ gem 'sprockets-rails'
14
+ gem 'sqlite3', '~> 2.1'
15
+
16
+ gemspec path: '../'
@@ -13,7 +13,7 @@ module RailsOps::Exceptions
13
13
 
14
14
  def initialize(original_exception)
15
15
  @original_exception = original_exception
16
- super original_exception.message
16
+ super(original_exception.message)
17
17
  end
18
18
  end
19
19
 
@@ -33,7 +33,7 @@ module RailsOps
33
33
  if Rails.gem_version >= Gem::Version.new('7.1')
34
34
  super(message, color, bold: bold)
35
35
  else
36
- super(message, color, bold)
36
+ super
37
37
  end
38
38
  end
39
39
  end
@@ -20,6 +20,11 @@ class RailsOps::Operation::Model::Destroy < RailsOps::Operation::Model::Load
20
20
  end
21
21
  end
22
22
 
23
+ def build_model
24
+ super
25
+ model_authorization
26
+ end
27
+
23
28
  def perform
24
29
  trigger :before_destroy, model: model
25
30
  model.destroy!
@@ -4,7 +4,7 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
4
4
  class_attribute :_lock_mode
5
5
 
6
6
  policy :on_init do
7
- model_authorization
7
+ model
8
8
  end
9
9
 
10
10
  # Gets or sets the action verb used for authorizing models on load.
@@ -18,10 +18,8 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
18
18
  return _load_model_authorization_action
19
19
  end
20
20
 
21
- def model_authorization
22
- return unless authorization_enabled?
23
-
24
- unless load_model_authorization_action.nil?
21
+ def load_model_authorization
22
+ if authorization_enabled? && load_model_authorization_action.present?
25
23
  authorize_model! load_model_authorization_action, model
26
24
  end
27
25
  end
@@ -78,12 +76,18 @@ class RailsOps::Operation::Model::Load < RailsOps::Operation::Model
78
76
  relation = lock_relation(relation)
79
77
 
80
78
  # Fetch (and possibly lock) model
81
- return relation.find_by!(model_id_field => params[model_id_field])
79
+ model = relation.find_by!(model_id_field => params[model_id_field])
80
+
81
+ # Return model
82
+ return model
82
83
  end
83
84
 
84
85
  def build_model
85
86
  @model = find_model
86
87
 
88
+ # Perform load model authorization
89
+ load_model_authorization
90
+
87
91
  if @model.respond_to?(:parent_op=)
88
92
  @model.parent_op = self
89
93
  end
@@ -8,36 +8,44 @@ class RailsOps::Operation::Model::Update < RailsOps::Operation::Model::Load
8
8
  true
9
9
  end
10
10
 
11
- policy :before_perform do
12
- if model_authorization_action && self.class._model_authorization_lazy
13
- authorize_model! model_authorization_action, model
11
+ policy :on_init do
12
+ if self.class._model_authorization_lazy && load_model_authorization_action.nil?
13
+ fail RailsOps::Exceptions::NoAuthorizationPerformed,
14
+ "Operation #{self.class.name} must specify a " \
15
+ 'load_model_authorization_action because model ' \
16
+ 'authorization is configured to be lazy.'
14
17
  end
15
18
  end
16
19
 
17
- def model_authorization
18
- return unless authorization_enabled?
19
-
20
- if self.class._model_authorization_lazy
21
- if load_model_authorization_action.nil?
22
- fail RailsOps::Exceptions::NoAuthorizationPerformed,
23
- "Operation #{self.class.name} must specify a " \
24
- 'load_model_authorization_action because model ' \
25
- 'authorization is configured to be lazy.'
26
- else
27
- authorize_model! load_model_authorization_action, model
28
- end
29
- elsif !load_model_authorization_action.nil?
30
- authorize_model_with_authorize_only! load_model_authorization_action, model
31
- end
20
+ policy :before_perform do
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
24
+ end
32
25
 
33
- unless model_authorization_action.nil? || self.class._model_authorization_lazy
26
+ def model_authorization
27
+ if authorization_enabled? && model_authorization_action.present?
34
28
  authorize_model! model_authorization_action, model
35
29
  end
36
30
  end
37
31
 
38
32
  def build_model
33
+ # Load model via parent class
39
34
  super
35
+
36
+ # Build nested model operations
40
37
  build_nested_model_ops :update
38
+
39
+ # 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
47
+
48
+ # Assign attributes
41
49
  assign_attributes
42
50
  end
43
51
 
@@ -169,9 +169,8 @@ class RailsOps::Operation::Model < RailsOps::Operation
169
169
 
170
170
  protected
171
171
 
172
- # Performs nested model operations and then saves the model. If you have
173
- # nested model operations, you should call this method instead of calling
174
- # `model.save!` directly.
172
+ # Performs nested model operations and then saves the model. You should always
173
+ # call this method instead of invoking "model.save!" directly.
175
174
  def save!
176
175
  run_policies :before_nested_model_ops
177
176
  perform_nested_model_ops!
@@ -19,7 +19,7 @@ module RailsOps
19
19
  descr += ' - ' if descr
20
20
  start = Time.now
21
21
  res = yield
22
- puts "#{descr}#{((Time.now - start).to_f * 1000).round(1)}ms elapsed.".magenta
22
+ puts "#{descr}#{((Time.now - start).to_f * 1000).round(1)}ms elapsed."
23
23
  res
24
24
  end
25
25
 
data/lib/rails_ops.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'active_type'
2
2
  require 'schemacop'
3
3
  require 'request_store'
4
+ require 'ostruct'
4
5
 
5
6
  module RailsOps
6
7
  AUTH_THREAD_STORAGE_KEY = :rails_ops_authorization_enabled
data/rails_ops.gemspec CHANGED
@@ -1,15 +1,15 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: rails_ops 1.5.8 ruby lib
2
+ # stub: rails_ops 1.6.0.rc0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "rails_ops".freeze
6
- s.version = "1.5.8"
6
+ s.version = "1.6.0.rc0"
7
7
 
8
- s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
8
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Sitrox".freeze]
11
- s.date = "2024-09-11"
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, "lib/generators/operation/USAGE".freeze, "lib/generators/operation/operation_generator.rb".freeze, "lib/generators/operation/templates/controller.erb".freeze, "lib/generators/operation/templates/controller_wrapper.erb".freeze, "lib/generators/operation/templates/create.erb".freeze, "lib/generators/operation/templates/destroy.erb".freeze, "lib/generators/operation/templates/load.erb".freeze, "lib/generators/operation/templates/update.erb".freeze, "lib/generators/operation/templates/view.erb".freeze, "lib/rails_ops.rb".freeze, "lib/rails_ops/authorization_backend/abstract.rb".freeze, "lib/rails_ops/authorization_backend/can_can_can.rb".freeze, "lib/rails_ops/configuration.rb".freeze, "lib/rails_ops/context.rb".freeze, "lib/rails_ops/controller_mixin.rb".freeze, "lib/rails_ops/exceptions.rb".freeze, "lib/rails_ops/hooked_job.rb".freeze, "lib/rails_ops/hookup.rb".freeze, "lib/rails_ops/hookup/dsl.rb".freeze, "lib/rails_ops/hookup/dsl_validator.rb".freeze, "lib/rails_ops/hookup/hook.rb".freeze, "lib/rails_ops/log_subscriber.rb".freeze, "lib/rails_ops/mixins.rb".freeze, "lib/rails_ops/mixins/authorization.rb".freeze, "lib/rails_ops/mixins/log_settings.rb".freeze, "lib/rails_ops/mixins/model.rb".freeze, "lib/rails_ops/mixins/model/authorization.rb".freeze, "lib/rails_ops/mixins/model/nesting.rb".freeze, "lib/rails_ops/mixins/param_authorization.rb".freeze, "lib/rails_ops/mixins/policies.rb".freeze, "lib/rails_ops/mixins/require_context.rb".freeze, "lib/rails_ops/mixins/routes.rb".freeze, "lib/rails_ops/mixins/schema_validation.rb".freeze, "lib/rails_ops/mixins/sub_ops.rb".freeze, "lib/rails_ops/model_mixins.rb".freeze, "lib/rails_ops/model_mixins/ar_extension.rb".freeze, "lib/rails_ops/model_mixins/marshalling.rb".freeze, "lib/rails_ops/model_mixins/parent_op.rb".freeze, "lib/rails_ops/model_mixins/sti_fixes.rb".freeze, "lib/rails_ops/model_mixins/virtual_attributes.rb".freeze, "lib/rails_ops/model_mixins/virtual_attributes/virtual_column_wrapper.rb".freeze, "lib/rails_ops/model_mixins/virtual_has_one.rb".freeze, "lib/rails_ops/model_mixins/virtual_model_name.rb".freeze, "lib/rails_ops/operation.rb".freeze, "lib/rails_ops/operation/model.rb".freeze, "lib/rails_ops/operation/model/create.rb".freeze, "lib/rails_ops/operation/model/destroy.rb".freeze, "lib/rails_ops/operation/model/load.rb".freeze, "lib/rails_ops/operation/model/update.rb".freeze, "lib/rails_ops/profiler.rb".freeze, "lib/rails_ops/profiler/node.rb".freeze, "lib/rails_ops/railtie.rb".freeze, "lib/rails_ops/scoped_env.rb".freeze, "lib/rails_ops/virtual_model.rb".freeze, "rails_ops.gemspec".freeze, "test/db/models.rb".freeze, "test/db/schema.rb".freeze, "test/dummy/Rakefile".freeze, "test/dummy/app/assets/config/manifest.js".freeze, "test/dummy/app/assets/images/.keep".freeze, "test/dummy/app/assets/javascripts/application.js".freeze, "test/dummy/app/assets/javascripts/cable.js".freeze, "test/dummy/app/assets/javascripts/channels/.keep".freeze, "test/dummy/app/assets/stylesheets/application.css".freeze, "test/dummy/app/channels/application_cable/channel.rb".freeze, "test/dummy/app/channels/application_cable/connection.rb".freeze, "test/dummy/app/controllers/application_controller.rb".freeze, "test/dummy/app/controllers/concerns/.keep".freeze, "test/dummy/app/controllers/group_controller.rb".freeze, "test/dummy/app/helpers/application_helper.rb".freeze, "test/dummy/app/jobs/application_job.rb".freeze, "test/dummy/app/mailers/application_mailer.rb".freeze, "test/dummy/app/models/ability.rb".freeze, "test/dummy/app/models/animal.rb".freeze, "test/dummy/app/models/application_record.rb".freeze, "test/dummy/app/models/bird.rb".freeze, "test/dummy/app/models/cat.rb".freeze, "test/dummy/app/models/computer.rb".freeze, "test/dummy/app/models/concerns/.keep".freeze, "test/dummy/app/models/cpu.rb".freeze, "test/dummy/app/models/dog.rb".freeze, "test/dummy/app/models/flower.rb".freeze, "test/dummy/app/models/group.rb".freeze, "test/dummy/app/models/mainboard.rb".freeze, "test/dummy/app/models/nightingale.rb".freeze, "test/dummy/app/models/phoenix.rb".freeze, "test/dummy/app/models/user.rb".freeze, "test/dummy/app/views/layouts/application.html.erb".freeze, "test/dummy/app/views/layouts/mailer.html.erb".freeze, "test/dummy/app/views/layouts/mailer.text.erb".freeze, "test/dummy/bin/bundle".freeze, "test/dummy/bin/rails".freeze, "test/dummy/bin/rake".freeze, "test/dummy/bin/setup".freeze, "test/dummy/bin/update".freeze, "test/dummy/bin/yarn".freeze, "test/dummy/config.ru".freeze, "test/dummy/config/application.rb".freeze, "test/dummy/config/boot.rb".freeze, "test/dummy/config/cable.yml".freeze, "test/dummy/config/database.yml".freeze, "test/dummy/config/environment.rb".freeze, "test/dummy/config/environments/development.rb".freeze, "test/dummy/config/environments/production.rb".freeze, "test/dummy/config/environments/test.rb".freeze, "test/dummy/config/hookup.rb".freeze, "test/dummy/config/initializers/application_controller_renderer.rb".freeze, "test/dummy/config/initializers/assets.rb".freeze, "test/dummy/config/initializers/backtrace_silencers.rb".freeze, "test/dummy/config/initializers/cookies_serializer.rb".freeze, "test/dummy/config/initializers/filter_parameter_logging.rb".freeze, "test/dummy/config/initializers/inflections.rb".freeze, "test/dummy/config/initializers/mime_types.rb".freeze, "test/dummy/config/initializers/rails_ops.rb".freeze, "test/dummy/config/initializers/wrap_parameters.rb".freeze, "test/dummy/config/locales/en.yml".freeze, "test/dummy/config/puma.rb".freeze, "test/dummy/config/routes.rb".freeze, "test/dummy/config/secrets.yml".freeze, "test/dummy/config/spring.rb".freeze, "test/dummy/db/schema.rb".freeze, "test/dummy/lib/assets/.keep".freeze, "test/dummy/log/.keep".freeze, "test/dummy/package.json".freeze, "test/dummy/public/404.html".freeze, "test/dummy/public/422.html".freeze, "test/dummy/public/500.html".freeze, "test/dummy/public/apple-touch-icon-precomposed.png".freeze, "test/dummy/public/apple-touch-icon.png".freeze, "test/dummy/public/favicon.ico".freeze, "test/dummy/tmp/.keep".freeze, "test/test_helper.rb".freeze, "test/unit/rails_ops/generators/operation_generator_test.rb".freeze, "test/unit/rails_ops/hookup_test.rb".freeze, "test/unit/rails_ops/mixins/controller_test.rb".freeze, "test/unit/rails_ops/mixins/model/deep_nesting_test.rb".freeze, "test/unit/rails_ops/mixins/model/marshalling_test.rb".freeze, "test/unit/rails_ops/mixins/param_authorization_test.rb".freeze, "test/unit/rails_ops/mixins/policies_test.rb".freeze, "test/unit/rails_ops/operation/auth_test.rb".freeze, "test/unit/rails_ops/operation/model/create_test.rb".freeze, "test/unit/rails_ops/operation/model/destroy_test.rb".freeze, "test/unit/rails_ops/operation/model/load_test.rb".freeze, "test/unit/rails_ops/operation/model/sti_test.rb".freeze, "test/unit/rails_ops/operation/model/update_test.rb".freeze, "test/unit/rails_ops/operation/model_test.rb".freeze, "test/unit/rails_ops/operation/update_lazy_auth_test.rb".freeze, "test/unit/rails_ops/operation_test.rb".freeze, "test/unit/rails_ops/profiler_test.rb".freeze]
11
+ s.date = "2025-01-22"
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
15
15
  s.summary = "An operations service layer for rails projects.".freeze
@@ -17,16 +17,6 @@ Gem::Specification.new do |s|
17
17
 
18
18
  s.specification_version = 4
19
19
 
20
- s.add_development_dependency(%q<appraisal>.freeze, [">= 0"])
21
- s.add_development_dependency(%q<bundler>.freeze, [">= 0"])
22
- s.add_development_dependency(%q<rake>.freeze, [">= 0"])
23
- s.add_development_dependency(%q<sqlite3>.freeze, ["< 2.0.0"])
24
- s.add_development_dependency(%q<cancancan>.freeze, [">= 0"])
25
- s.add_development_dependency(%q<pry>.freeze, [">= 0"])
26
- s.add_development_dependency(%q<colorize>.freeze, [">= 0"])
27
- s.add_development_dependency(%q<rubocop>.freeze, ["= 1.45.1"])
28
- s.add_development_dependency(%q<sprockets-rails>.freeze, [">= 0"])
29
- s.add_development_dependency(%q<simplecov>.freeze, [">= 0"])
30
20
  s.add_runtime_dependency(%q<active_type>.freeze, [">= 1.3.0"])
31
21
  s.add_runtime_dependency(%q<minitest>.freeze, [">= 0"])
32
22
  s.add_runtime_dependency(%q<rails>.freeze, [">= 0"])
@@ -1,5 +1,6 @@
1
1
  require_relative 'boot'
2
2
 
3
+ require 'logger'
3
4
  require 'rails'
4
5
 
5
6
  require 'active_model/railtie'
data/test/test_helper.rb CHANGED
@@ -7,7 +7,6 @@ require File.expand_path('../test/dummy/config/environment.rb', __dir__)
7
7
  # ActiveRecord::Migrator.migrations_paths = [File.expand_path("../../test/dummy/db/migrate", __FILE__)]
8
8
  require 'rails/test_help'
9
9
  require 'pry'
10
- require 'colorize'
11
10
 
12
11
  # Filter out Minitest backtrace while allowing backtrace from other libraries
13
12
  # to be shown.
@@ -18,6 +18,12 @@ class RailsOps::Operation::AuthTest < ActiveSupport::TestCase
18
18
  model ::Group
19
19
  end
20
20
 
21
+ UPDATE_OP_WITH_SPECIAL_LOAD_OP = Class.new(RailsOps::Operation::Model::Update) do
22
+ model ::Group
23
+
24
+ load_model_authorization_action :create
25
+ end
26
+
21
27
  CREATE_OP = Class.new(RailsOps::Operation::Model::Create) do
22
28
  model ::Group
23
29
  end
@@ -78,12 +84,22 @@ class RailsOps::Operation::AuthTest < ActiveSupport::TestCase
78
84
  def test_permitted_update
79
85
  ctx = RailsOps::Context.new(ability: ABILITY.new(read: true, update: true))
80
86
  assert_nothing_raised do
87
+ # As `read``and `update` is permitted, `new` and `run` should both work for the operation
81
88
  op = UPDATE_OP.new(ctx, id: 1, group: { name: 'Group2' })
82
89
  res = op.run!
83
90
  assert_equal 'Group2', res.model.name
84
91
  end
85
92
  end
86
93
 
94
+ def test_unpermitted_read_permitted_update
95
+ # While `update` is permitted, `read` is not, and therefore already trying to
96
+ # instantiate the operation must fail.
97
+ ctx = RailsOps::Context.new(ability: ABILITY.new(update: true))
98
+ assert_raises CanCan::AccessDenied do
99
+ UPDATE_OP.new(ctx, id: 1, group: { name: 'Group2' })
100
+ end
101
+ end
102
+
87
103
  def test_unpermitted_create
88
104
  ctx = RailsOps::Context.new(ability: ABILITY.new(read: true))
89
105
  assert_raises CanCan::AccessDenied do
@@ -26,16 +26,14 @@ class RailsOps::Operation::Model::LoadTest < ActiveSupport::TestCase
26
26
  end
27
27
 
28
28
  def test_without_id
29
- op = BASIC_OP.new
30
29
  assert_raises_with_message RuntimeError, 'Param :id must be given.' do
31
- op.model
30
+ BASIC_OP.new
32
31
  end
33
32
  end
34
33
 
35
34
  def test_not_found
36
- op = BASIC_OP.new(id: 5)
37
35
  assert_raise ActiveRecord::RecordNotFound do
38
- op.model
36
+ BASIC_OP.new(id: 5)
39
37
  end
40
38
  end
41
39
 
@@ -1,4 +1,5 @@
1
1
  require 'test_helper'
2
+ require 'rails_ops/authorization_backend/can_can_can'
2
3
 
3
4
  class RailsOps::Operation::Model::UpdateTest < ActiveSupport::TestCase
4
5
  include TestHelper
@@ -62,10 +63,8 @@ class RailsOps::Operation::Model::UpdateTest < ActiveSupport::TestCase
62
63
 
63
64
  assert_equal :red, op.model.color
64
65
 
65
- op = FLOWER_OP.new(id: flower2.id)
66
-
67
66
  assert_raises ActiveRecord::RecordNotFound do
68
- op.model
67
+ FLOWER_OP.new(id: flower2.id)
69
68
  end
70
69
  end
71
70
 
@@ -90,4 +89,78 @@ class RailsOps::Operation::Model::UpdateTest < ActiveSupport::TestCase
90
89
  refute op2.model.respond_to?(:planted_is_true)
91
90
  refute op2.model.respond_to?(:response_to_everything)
92
91
  end
92
+
93
+ def test_load_authorization_order
94
+ RailsOps.config.authorization_backend = 'RailsOps::AuthorizationBackend::CanCanCan'
95
+
96
+ op_klass = Class.new(RailsOps::Operation::Model::Update) do
97
+ model ::Group
98
+ end
99
+
100
+ ability = Class.new do
101
+ include CanCan::Ability
102
+
103
+ def initialize
104
+ super
105
+ can :read, Group, color: 'red'
106
+ can :update, Group, color: 'red'
107
+ end
108
+ end.new
109
+
110
+ context = RailsOps::Context.new(ability: ability)
111
+
112
+ assert_nothing_raised do
113
+ model = Group.create!(color: 'red')
114
+ op_klass.run!(context, id: model.id, group: { color: 'red' })
115
+ end
116
+
117
+ assert_raises CanCan::AccessDenied do
118
+ model = Group.create!(color: 'blue')
119
+ op_klass.run!(context, id: model.id, group: { color: 'red' })
120
+ end
121
+
122
+ assert_nothing_raised do
123
+ model = Group.create!(color: 'red')
124
+ op_klass.run!(context, id: model.id, group: { color: 'blue' })
125
+ end
126
+ ensure
127
+ RailsOps.config.authorization_backend = nil
128
+ end
129
+
130
+ def test_update_authorization_order
131
+ RailsOps.config.authorization_backend = 'RailsOps::AuthorizationBackend::CanCanCan'
132
+
133
+ op_klass = Class.new(RailsOps::Operation::Model::Update) do
134
+ model ::Group
135
+ end
136
+
137
+ ability = Class.new do
138
+ include CanCan::Ability
139
+
140
+ def initialize
141
+ super
142
+ can :read, Group
143
+ can :update, Group, color: 'red'
144
+ end
145
+ end.new
146
+
147
+ context = RailsOps::Context.new(ability: ability)
148
+
149
+ assert_nothing_raised do
150
+ model = Group.create!(color: 'red')
151
+ op_klass.run!(context, id: model.id, group: { color: 'red' })
152
+ end
153
+
154
+ assert_raises CanCan::AccessDenied do
155
+ model = Group.create!(color: 'blue')
156
+ op_klass.run!(context, id: model.id, group: { color: 'red' })
157
+ end
158
+
159
+ assert_nothing_raised do
160
+ model = Group.create!(color: 'red')
161
+ op_klass.run!(context, id: model.id, group: { color: 'blue' })
162
+ end
163
+ ensure
164
+ RailsOps.config.authorization_backend = nil
165
+ end
93
166
  end
@@ -12,6 +12,7 @@ class RailsOps::Operation::UpdateLazyAuthTest < ActiveSupport::TestCase
12
12
 
13
13
  def perform
14
14
  fail osparams.exception if osparams.exception
15
+ save!
15
16
  @done = true
16
17
  end
17
18
  end
@@ -21,32 +22,48 @@ class RailsOps::Operation::UpdateLazyAuthTest < ActiveSupport::TestCase
21
22
 
22
23
  def initialize(read: false, update: false)
23
24
  super()
24
- can :read, Group if read
25
- can :update, Group if update
25
+ can :read, Group, color: %w[red green] if read
26
+ can :update, Group, color: 'red' if update
26
27
  end
27
28
  end
28
29
 
29
30
  setup do
30
31
  Group.delete_all
31
- Group.create!(id: 1, name: 'Group')
32
+ Group.create!(id: 1, name: 'Red Group', color: 'red')
33
+ Group.create!(id: 2, name: 'Blue group', color: 'blue')
34
+ Group.create!(id: 3, name: 'Green group', color: 'green')
32
35
  RailsOps.config.authorization_backend = 'RailsOps::AuthorizationBackend::CanCanCan'
33
36
  end
34
37
 
35
- def test_unpermitted_read
38
+ def test_unpermitted_read_permitted_color
36
39
  ctx = RailsOps::Context.new(ability: ABILITY.new)
37
40
  assert_raises CanCan::AccessDenied do
38
41
  BASIC_OP.new(ctx, id: 1)
39
42
  end
40
43
  end
41
44
 
42
- def test_permitted_read
45
+ def test_unpermitted_read_unpermitted_color
46
+ ctx = RailsOps::Context.new(ability: ABILITY.new)
47
+ assert_raises CanCan::AccessDenied do
48
+ BASIC_OP.new(ctx, id: 2)
49
+ end
50
+ end
51
+
52
+ def test_permitted_read_permitted_color
43
53
  ctx = RailsOps::Context.new(ability: ABILITY.new(read: true))
44
54
  assert_nothing_raised do
45
55
  BASIC_OP.new(ctx, id: 1)
46
56
  end
47
57
  end
48
58
 
49
- def test_unpermitted_update
59
+ def test_permitted_read_unpermitted_color
60
+ ctx = RailsOps::Context.new(ability: ABILITY.new(read: true))
61
+ assert_raises CanCan::AccessDenied do
62
+ BASIC_OP.new(ctx, id: 2)
63
+ end
64
+ end
65
+
66
+ def test_unpermitted_update_permitted_color
50
67
  ctx = RailsOps::Context.new(ability: ABILITY.new(read: true))
51
68
  op = BASIC_OP.new(ctx, id: 1)
52
69
  assert_raises CanCan::AccessDenied do
@@ -54,11 +71,27 @@ class RailsOps::Operation::UpdateLazyAuthTest < ActiveSupport::TestCase
54
71
  end
55
72
  end
56
73
 
57
- def test_permitted_update
74
+ def test_unpermitted_update_unpermitted_color
75
+ ctx = RailsOps::Context.new(ability: ABILITY.new(read: true))
76
+ op = BASIC_OP.new(ctx, id: 1)
77
+ assert_raises CanCan::AccessDenied do
78
+ op.run!
79
+ end
80
+ end
81
+
82
+ def test_permitted_update_permitted_color
58
83
  ctx = RailsOps::Context.new(ability: ABILITY.new(read: true, update: true))
59
84
  op = BASIC_OP.new(ctx, id: 1)
60
85
  assert_nothing_raised do
61
86
  op.run!
62
87
  end
63
88
  end
89
+
90
+ def test_permitted_update_unpermitted_color
91
+ ctx = RailsOps::Context.new(ability: ABILITY.new(read: true, update: true))
92
+ op = BASIC_OP.new(ctx, id: 3, group: { color: 'red' })
93
+ assert_raises CanCan::AccessDenied do
94
+ op.run!
95
+ end
96
+ end
64
97
  end
@@ -168,13 +168,21 @@ class RailsOps::OperationTest < ActiveSupport::TestCase
168
168
  end
169
169
 
170
170
  def test_inspect
171
- assert_equal 'RailsOps::OperationTest::BASIC_OP ({"foo"=>:bar})',
172
- BASIC_OP.new(foo: :bar).inspect
171
+ # See https://bugs.ruby-lang.org/issues/20433#note-10
172
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4.0')
173
+ assert_equal 'RailsOps::OperationTest::BASIC_OP ({"foo" => :bar})', BASIC_OP.new(foo: :bar).inspect
174
+ else
175
+ assert_equal 'RailsOps::OperationTest::BASIC_OP ({"foo"=>:bar})', BASIC_OP.new(foo: :bar).inspect
176
+ end
173
177
  end
174
178
 
175
179
  def test_inspect_with_numeric_param_keys
176
- assert_equal 'RailsOps::OperationTest::BASIC_OP ({1=>2})',
177
- BASIC_OP.new(1 => 2).inspect
180
+ # See https://bugs.ruby-lang.org/issues/20433#note-10
181
+ if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.4.0')
182
+ assert_equal 'RailsOps::OperationTest::BASIC_OP ({1 => 2})', BASIC_OP.new(1 => 2).inspect
183
+ else
184
+ assert_equal 'RailsOps::OperationTest::BASIC_OP ({1=>2})', BASIC_OP.new(1 => 2).inspect
185
+ end
178
186
  end
179
187
 
180
188
  def test_with_rollback_on_exception
metadata CHANGED
@@ -1,155 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_ops
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.8
4
+ version: 1.6.0.rc0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-11 00:00:00.000000000 Z
11
+ date: 2025-01-22 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: appraisal
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: sqlite3
57
- requirement: !ruby/object:Gem::Requirement
58
- requirements:
59
- - - "<"
60
- - !ruby/object:Gem::Version
61
- version: 2.0.0
62
- type: :development
63
- prerelease: false
64
- version_requirements: !ruby/object:Gem::Requirement
65
- requirements:
66
- - - "<"
67
- - !ruby/object:Gem::Version
68
- version: 2.0.0
69
- - !ruby/object:Gem::Dependency
70
- name: cancancan
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - ">="
74
- - !ruby/object:Gem::Version
75
- version: '0'
76
- type: :development
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: pry
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :development
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
- - !ruby/object:Gem::Dependency
98
- name: colorize
99
- requirement: !ruby/object:Gem::Requirement
100
- requirements:
101
- - - ">="
102
- - !ruby/object:Gem::Version
103
- version: '0'
104
- type: :development
105
- prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- requirements:
108
- - - ">="
109
- - !ruby/object:Gem::Version
110
- version: '0'
111
- - !ruby/object:Gem::Dependency
112
- name: rubocop
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - '='
116
- - !ruby/object:Gem::Version
117
- version: 1.45.1
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - '='
123
- - !ruby/object:Gem::Version
124
- version: 1.45.1
125
- - !ruby/object:Gem::Dependency
126
- name: sprockets-rails
127
- requirement: !ruby/object:Gem::Requirement
128
- requirements:
129
- - - ">="
130
- - !ruby/object:Gem::Version
131
- version: '0'
132
- type: :development
133
- prerelease: false
134
- version_requirements: !ruby/object:Gem::Requirement
135
- requirements:
136
- - - ">="
137
- - !ruby/object:Gem::Version
138
- version: '0'
139
- - !ruby/object:Gem::Dependency
140
- name: simplecov
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - ">="
144
- - !ruby/object:Gem::Version
145
- version: '0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - ">="
151
- - !ruby/object:Gem::Version
152
- version: '0'
153
13
  - !ruby/object:Gem::Dependency
154
14
  name: active_type
155
15
  requirement: !ruby/object:Gem::Requirement
@@ -226,8 +86,8 @@ dependencies:
226
86
  - - "<="
227
87
  - !ruby/object:Gem::Version
228
88
  version: '3.1'
229
- description:
230
- email:
89
+ description:
90
+ email:
231
91
  executables: []
232
92
  extensions: []
233
93
  extra_rdoc_files: []
@@ -250,6 +110,7 @@ files:
250
110
  - gemfiles/rails_7.0.gemfile
251
111
  - gemfiles/rails_7.1.gemfile
252
112
  - gemfiles/rails_7.2.gemfile
113
+ - gemfiles/rails_8.0.gemfile
253
114
  - lib/generators/operation/USAGE
254
115
  - lib/generators/operation/operation_generator.rb
255
116
  - lib/generators/operation/templates/controller.erb
@@ -399,11 +260,11 @@ files:
399
260
  - test/unit/rails_ops/operation/update_lazy_auth_test.rb
400
261
  - test/unit/rails_ops/operation_test.rb
401
262
  - test/unit/rails_ops/profiler_test.rb
402
- homepage:
263
+ homepage:
403
264
  licenses:
404
265
  - MIT
405
266
  metadata: {}
406
- post_install_message:
267
+ post_install_message:
407
268
  rdoc_options: []
408
269
  require_paths:
409
270
  - lib
@@ -414,12 +275,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
414
275
  version: '0'
415
276
  required_rubygems_version: !ruby/object:Gem::Requirement
416
277
  requirements:
417
- - - ">="
278
+ - - ">"
418
279
  - !ruby/object:Gem::Version
419
- version: '0'
280
+ version: 1.3.1
420
281
  requirements: []
421
- rubygems_version: 3.5.4
422
- signing_key:
282
+ rubygems_version: 3.4.6
283
+ signing_key:
423
284
  specification_version: 4
424
285
  summary: An operations service layer for rails projects.
425
286
  test_files: