rails_ops 1.5.8 → 1.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d447da1ff197bd043c6831fd1fd4136541a064faf398ad78f376f45d6c98bda3
4
- data.tar.gz: 351fb718726616969c07e48bc3967d7f3cd4e6bdda07512b1b5e45a878cd66fc
3
+ metadata.gz: b96c0188f23bcdbb31835f29ac2125a816db7d2345ad5c56786fec2ee9506381
4
+ data.tar.gz: b9dbe9d1111615b955c4e8d533ce2ed6e8351ad03a648cd52bdea23166145311
5
5
  SHA512:
6
- metadata.gz: 2086a657d14d7addab4f5e995afa457853f574e0c74bfa074043f28d22cb6f2acc9367cf2d29c9a457218897baf9fb15430452025e4c8fdbcd5f29c9616f7663
7
- data.tar.gz: e3ead99ce7cdddc297535f11ad37acdf36244468bc686114d2342bbb0b13289a1f669ca150c4af3b2d1100482336dc697de38f6a78b2e2fac0923d9f21f6bf1b
6
+ metadata.gz: 32af49ae5fb81eb1e0e56555f790dc2bd72621dd8d8a42da5adee18d4a5fba1a827bcc51aeb4562cd0721ff2656bcea40c68265fa28416fc5d380437fde1f80c
7
+ data.tar.gz: 64f6322f440685110cb8e76c1d65e127ad133fdc3188e42af37111562c7380854a9b181129b315005892e3fa8a1386d055bc5420c75ebe6cae6ffc5b7e86edcc
@@ -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,81 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.6.0.rc1 (2025-01-22)
4
+
5
+ * Add minimum version requirement for `rails` (rails `> 4` is required)
6
+
7
+ ## 1.6.0.rc0 (2025-01-22)
8
+
9
+ * Adapt the way model authorization works for an additional layer of security:
10
+
11
+ * Update-Operations (operations inheriting from `RailsOps::Operation::Model::Update`)
12
+ now perform their model authorization immediately after the model is loaded (in `build_model`).
13
+
14
+ Previously, the authorization was only performed after the attributes have
15
+ already been assigned, never checking authorization against the "pristine"
16
+ model.
17
+
18
+ * Load-Operations (operations inheriting from
19
+ `RailsOps::Operation::Model::Load`) now always load their associated model
20
+ **directly on OP instantiation**. Previously, this was only the case if load
21
+ model authorization was enabled.
22
+
23
+ In addition, the method `model_authorization` has been renamed to
24
+ `load_model_authorization` in order to separate it from the method
25
+ `model_authorization` in `RailsOps::Operation::Model::Update`.
26
+
27
+ Internal reference: `#133622`.
28
+
29
+ ### Migrating from earlier versions
30
+
31
+ Check that operations using model authorization still work as expected, especially
32
+ operations inheriting from `RailsOps::Operation::Model::Update` or from
33
+ `RailsOps::Operation::Model::Load`:
34
+
35
+ * For operations inheriting from `RailsOps::Operation::Model::Load`: Rename all
36
+ uses of `model_authorization` to `load_model_authorization`.
37
+
38
+ * For operations inheriting from `RailsOps::Operation::Model::Update`, you need
39
+ to make sure that running the model authorization on the "pristine" model (before
40
+ assigning the new attributes) is still applying your authorization logic in
41
+ the correct way (i.e. authorizing on the model *before* assigning the attributes
42
+ applies authorization correctly).
43
+ If you need to authorize the state *after* assigning the params to the model,
44
+ you'll need add that check manually in your operation.
45
+
46
+
47
+ One example of how this behaviour was changed: A user may only update
48
+ a `Group` object with a `color` of `'red'`:
49
+
50
+ ```ruby
51
+ class Ability
52
+ can :update, Group, color: 'red'
53
+ end
54
+ ```
55
+
56
+ Before, this was the way RailsOps handled it:
57
+
58
+ ```ruby
59
+ model = find_record(params[:id]) # => model = Group(color: 'blue')
60
+ model.assign_attributes(params) # params = { color: 'red' }
61
+ authorize! :update, model
62
+ ```
63
+
64
+ This works, because RailsOps already assigned `'red'` to the `color` attribute of the model. This means
65
+ that the `authorize!` call will succeed, even though the original model in the database is not permissible
66
+ to be updated by the user.
67
+
68
+ Afterwards, the behaviour is as follows:
69
+
70
+ ```ruby
71
+ model = find_record(params[:id]) # => model = Group(color: 'blue')
72
+ authorize! :update, model # => Fails with CanCan::AccessDenied, as the user may not update the group
73
+ # [...]
74
+ ```
75
+
76
+ After applying these changes, carefully test your application, run unit tests etc.
77
+ to ensure all operations still behave as expected in regards to authorization.
78
+
3
79
  ## 1.5.8 (2024-09-11)
4
80
 
5
81
  * 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,10 +1,10 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails_ops (1.5.8)
4
+ rails_ops (1.6.0.rc1)
5
5
  active_type (>= 1.3.0)
6
6
  minitest
7
- rails
7
+ rails (> 4)
8
8
  request_store
9
9
  schemacop (>= 3.0.0, <= 3.1)
10
10
 
@@ -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
@@ -23,18 +23,22 @@ Requirements & Installation
23
23
 
24
24
  ### Requirements
25
25
 
26
+ * `rails > 4`
27
+
26
28
  - RailsOps only works with Rails applications, with the following Rails versions being tested in the CI:
27
29
  * Rails 6.0.x
28
30
  * Rails 6.1.x
29
31
  * Rails 7.0.x
30
32
  * Rails 7.1.x
31
33
  * Rails 7.2.x
34
+ * Rails 8.0.x
32
35
  - Additionally, the following Ruby versions are covered by our unit tests:
33
36
  * 2.7.8
34
37
  * 3.0.1
35
38
  * 3.1.0
36
39
  * 3.2.0
37
40
  * 3.3.0
41
+ * 3.4.0
38
42
  - 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
43
  - Prior Rails and Ruby versions may be supported but they are not tested in the CI.
40
44
  - Rails Ops' model operations require ActiveRecord but are database / adapter
@@ -1353,8 +1357,9 @@ authorization check based on this model.
1353
1357
 
1354
1358
  While you can override this method to perform custom authorization, RailsOps
1355
1359
  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.
1360
+ `model_authorization_action` (or `load_model_authorization` for operations
1361
+ inheriting from `RailsOps::Operation::Model::Load`), you can specify an action
1362
+ verb that is used for authorizing your model.
1358
1363
 
1359
1364
  ```ruby
1360
1365
  class Operations::User::Load < RailsOps::Operation::Model::Load
@@ -1362,7 +1367,23 @@ class Operations::User::Load < RailsOps::Operation::Model::Load
1362
1367
 
1363
1368
  # This automatically calls `authorize_model! :read` after operation
1364
1369
  # instantiation.
1365
- model_authorization_action :read
1370
+ load_model_authorization :read
1371
+ end
1372
+ ```
1373
+
1374
+ Another example for an update operation:
1375
+
1376
+ ```ruby
1377
+ class Operations::User::Update < RailsOps::Operation::Model::Update
1378
+ model User
1379
+
1380
+ # This automatically calls `authorize_model! :read` after operation
1381
+ # instantiation.
1382
+ load_model_authorization :read
1383
+
1384
+ # This automatically calls `authorize_model! :update` after operation
1385
+ # instantiation.
1386
+ model_authorization :update
1366
1387
  end
1367
1388
  ```
1368
1389
 
@@ -1793,4 +1814,4 @@ Rails architecture.
1793
1814
 
1794
1815
  ## Copyright
1795
1816
 
1796
- Copyright © 2017 - 2024 Sitrox. See `LICENSE` for further details.
1817
+ Copyright © 2017 - 2025 Sitrox. See `LICENSE` for further details.
data/Rakefile CHANGED
@@ -16,19 +16,9 @@ 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
- spec.add_dependency 'rails'
21
+ spec.add_dependency 'rails', '> 4'
32
22
  spec.add_dependency 'request_store'
33
23
  spec.add_dependency 'schemacop', '>= 3.0.0', '<= 3.1'
34
24
  end
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.5.8
1
+ 1.6.0.rc1
@@ -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.rc1 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.rc1"
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,19 +17,9 @@ 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
- s.add_runtime_dependency(%q<rails>.freeze, [">= 0"])
22
+ s.add_runtime_dependency(%q<rails>.freeze, ["> 4"])
33
23
  s.add_runtime_dependency(%q<request_store>.freeze, [">= 0"])
34
24
  s.add_runtime_dependency(%q<schemacop>.freeze, [">= 3.0.0", "<= 3.1"])
35
25
  end
@@ -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.rc1
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
@@ -182,16 +42,16 @@ dependencies:
182
42
  name: rails
183
43
  requirement: !ruby/object:Gem::Requirement
184
44
  requirements:
185
- - - ">="
45
+ - - ">"
186
46
  - !ruby/object:Gem::Version
187
- version: '0'
47
+ version: '4'
188
48
  type: :runtime
189
49
  prerelease: false
190
50
  version_requirements: !ruby/object:Gem::Requirement
191
51
  requirements:
192
- - - ">="
52
+ - - ">"
193
53
  - !ruby/object:Gem::Version
194
- version: '0'
54
+ version: '4'
195
55
  - !ruby/object:Gem::Dependency
196
56
  name: request_store
197
57
  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: