access-granted 1.1.2 → 1.3.3

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
- SHA1:
3
- metadata.gz: b6d400982e35b05842762be3929a3bfb9d1a9de6
4
- data.tar.gz: 3825d04d00c581e55b21339c92e6b8b3b5e90672
2
+ SHA256:
3
+ metadata.gz: 115b6ed416c4bfa4b6d94d53520388c382b65966aa8ce7c4072d9991a630d1d3
4
+ data.tar.gz: 0af1baa07da37953f292b4bb8d24680cbebdf70c1495203d7a4312d4735584bb
5
5
  SHA512:
6
- metadata.gz: f1fa9bad01415dc1642a0d7d4c6396f4db625f88f87c81f59649fb230547da4f32cbbcb01bb3362a4716cdfba702ed97719afc2eecee688a8383c05901e2f9b6
7
- data.tar.gz: bc6346e03d9d8502f4e42fce4ec0eba1939fd5e3c01846380f480c1e0feb4936af16840d2caf507f8b75e4b66f9e7b9b03841e42cac2c34200d512d409d2e738
6
+ metadata.gz: 6554c68a9ddd5f04866afef389d59d48ddbd63fc6173c6955b39075fb9f23ac5c8d1036f13cc8f7ac9fed997217d1d69b725c5f6e0dc83550a1e8eea293e6e6d
7
+ data.tar.gz: 162efc4e19ad3fa554778b00dfd46d28f951ac90a65d94c2f3037f554dc590f9822eb7db6a19c792f65eeaba78adcc77386f9956e88c7f00f9127a2a231141e9
data/.travis.yml CHANGED
@@ -1,9 +1,11 @@
1
1
  language: ruby
2
2
  sudo: false
3
3
  rvm:
4
- - 1.9.3
5
4
  - 2.0
6
5
  - 2.1
7
6
  - 2.2
7
+ - 2.3
8
+ - 2.4
9
+ - 2.5
8
10
  - jruby-1
9
11
  - jruby
data/CHANGELOG.md CHANGED
@@ -1,3 +1,42 @@
1
+ # 1.3.3
2
+
3
+ - Fix compatibility with Rails 6.0 and Zeitwerk ([PR #53](https://github.com/chaps-io/access-granted/pull/53)), thanks [jraqula](https://github.com/dmorehouse)!
4
+
5
+ # 1.3.2
6
+
7
+ - Expose `applicable_roles` method on the policy instance. This allows insight into what roles actually apply to a given user.
8
+
9
+ # 1.3.1
10
+
11
+ - Add information about action and subject when raising AccessDenied exception ([PR #46](https://github.com/chaps-io/access-granted/pull/46)), thanks [jraqula](https://github.com/jraqula)!
12
+
13
+ # 1.3.0
14
+
15
+ - Drop support for Ruby 1.9.3, it might still work but we are no longer testing against it.
16
+ - Start testing against Rubies 2.3-2.5 in CI
17
+ - Move Rails integration into Railties, this fixes some load order issues ([PR #45](https://github.com/chaps-io/access-granted/pull/45)), thanks [jraqula](https://github.com/jraqula)!
18
+
19
+ # 1.2.0
20
+
21
+ - Cache whole blocks of identical permissions when one of them is checked.
22
+ For example, assuming we have a given permissions set:
23
+
24
+ ```ruby
25
+ can [:update, :destroy, :archive], Post do |post, user|
26
+ post.user_id == user.id
27
+ end
28
+ ```
29
+
30
+ When resolving one of them like this:
31
+
32
+ ```ruby
33
+ can? :update, @post
34
+ ```
35
+
36
+ Access Granted will cache the result for each of the remaining actions, too.
37
+ So next time when checking permissions `:destroy` or `:archive`, AG will serve the result from cache instead of running the block again.
38
+
39
+
1
40
  # 1.1.2
2
41
 
3
42
  - Expose internal `block` instance variable in Permission class
data/README.md CHANGED
@@ -19,11 +19,13 @@ Run the bundle command to install it. Then run the generator:
19
19
 
20
20
  Add the `policies` (and `roles` if you're using it to split up your roles into files) directories to your autoload paths in `application.rb`:
21
21
 
22
- config.autoload_paths += %W(#{config.root}/app/policies #{config.root}/app/roles)
22
+ ```ruby
23
+ config.autoload_paths += %W(#{config.root}/app/policies #{config.root}/app/roles)
24
+ ```
23
25
 
24
26
  ### Supported Ruby versions
25
27
 
26
- Because it has **zero** runtime dependencies it is guaranteed to work on all major Ruby versions MRI 1.9.3-2.2, Rubinius >= 2.X and JRuby >= 1.7.
28
+ Because it has **zero** runtime dependencies it is guaranteed to work on all major Ruby versions MRI `2.0` - `2.5`, Rubinius `>= 2.X` and JRuby `>= 1.7`.
27
29
 
28
30
  ## Summary
29
31
 
@@ -31,26 +33,26 @@ AccessGranted is meant as a replacement for CanCan to solve major problems:
31
33
 
32
34
  1. Performance
33
35
 
34
- On average AccessGranted is **20 times faster** in resolving identical permissions and takes less memory.
35
- See [benchmarks](https://github.com/chaps-io/access-granted/blob/master/benchmarks).
36
+ On average AccessGranted is **20 times faster** in resolving identical permissions and takes less memory.
37
+ See [benchmarks](https://github.com/chaps-io/access-granted/blob/master/benchmarks).
36
38
 
37
39
  2. Roles
38
40
 
39
- Adds support for roles, so no more `if`s and `else`s in your Policy file. This makes it extremely easy to maintain and read the code.
41
+ Adds support for roles, so no more `if`s and `else`s in your Policy file. This makes it extremely easy to maintain and read the code.
40
42
 
41
43
  3. Whitelists
42
44
 
43
- This means that you define what the user can do, which results in clean, readable policies regardless of application complexity.
44
- You don't have to worry about juggling `can`s and `cannot`s in a very convoluted way!
45
+ This means that you define what the user can do, which results in clean, readable policies regardless of application complexity.
46
+ You don't have to worry about juggling `can`s and `cannot`s in a very convoluted way!
45
47
 
46
- _Note_: `cannot` is still available, but has a very specifc use. See [Usage](#usage) below.
48
+ _Note_: `cannot` is still available, but has a very specifc use. See [Usage](#usage) below.
47
49
 
48
50
  4. Framework agnostic
49
51
 
50
- Permissions can work on basically any object and AccessGranted is framework-agnostic,
51
- but it has Rails support out of the box. :)
52
- It does not depend on any libraries, pure and clean Ruby code. Guaranteed to always work,
53
- even when software around changes.
52
+ Permissions can work on basically any object and AccessGranted is framework-agnostic,
53
+ but it has Rails support out of the box. :)
54
+ It does not depend on any libraries, pure and clean Ruby code. Guaranteed to always work,
55
+ even when software around changes.
54
56
 
55
57
  ## Usage
56
58
 
@@ -210,6 +212,41 @@ class ApplicationController < ActionController::Base
210
212
  end
211
213
  ```
212
214
 
215
+ You can also extract the action and subject which raised the error,
216
+ if you want to handle authorization errors differently for some cases:
217
+ ```ruby
218
+ rescue_from "AccessGranted::AccessDenied" do |exception|
219
+ status = case exception.action
220
+ when :read # invocation like `authorize! :read, @something`
221
+ 403
222
+ else
223
+ 404
224
+ end
225
+
226
+ body = case exception.subject
227
+ when Post # invocation like `authorize! @some_action, Post`
228
+ "failed to access a post"
229
+ else
230
+ "failed to access something else"
231
+ end
232
+ end
233
+ ```
234
+
235
+ You can also have a custom exception message while authorizing a request.
236
+ This message will be associated with the exception object thrown.
237
+
238
+ ```ruby
239
+ class PostsController
240
+ def show
241
+ @post = Post.find(params[:id])
242
+ authorize! :read, @post, 'You do not have access to this post'
243
+ render json: { post: @post }
244
+ rescue AccessGranted::AccessDenied => e
245
+ render json: { error: e.message }, status: :forbidden
246
+ end
247
+ end
248
+ ```
249
+
213
250
  #### Checking permissions in controllers
214
251
 
215
252
  To check if the user has a permission to perform an action, use the `can?` and `cannot?` methods.
@@ -280,7 +317,7 @@ or with `cannot?`:
280
317
 
281
318
  ```ruby
282
319
  policy.cannot?(:create, Post) #=> false
283
- policy.cannot?(:update, @ost) #=> true
320
+ policy.cannot?(:update, @post) #=> true
284
321
  ```
285
322
 
286
323
  ## Common examples
@@ -4,7 +4,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "access-granted"
7
- spec.version = "1.1.2"
7
+ spec.version = "1.3.3"
8
8
  spec.authors = ["Piotrek Okoński"]
9
9
  spec.email = ["piotrek@okonski.org"]
10
10
  spec.description = %q{Role based authorization gem}
data/benchmarks/README.md CHANGED
@@ -1,24 +1,24 @@
1
1
  # Benchmark results
2
2
 
3
- Benchmarks ran on Ubuntu 15.04 64bit, i5 2500k @ 4.4Ghz, 16 GB RAM with Ruby 2.2.
3
+ Benchmarks ran on Ubuntu 17.04 64bit, i7 6700k @ 4.0Ghz, 32 GB RAM with Ruby 2.3.
4
4
 
5
5
  ## permissions.rb
6
6
 
7
7
  This benchmark runs `can?` method for the 3 user roles for 20 seconds each, for both CanCan and AccessGranted.
8
8
 
9
9
  ```
10
+ Warming up --------------------------------------
11
+ ag-admin 158.815k i/100ms
12
+ ag-moderator 161.055k i/100ms
13
+ ag-user 161.670k i/100ms
14
+ cancan-admin 14.865k i/100ms
15
+ cancan-moderator 13.181k i/100ms
16
+ cancan-user 18.907k i/100ms
10
17
  Calculating -------------------------------------
11
- ag-admin 21.361k i/100ms
12
- cancan-admin 13.631k i/100ms
13
- ag-moderator 22.328k i/100ms
14
- cancan-moderator 11.679k i/100ms
15
- ag-user 25.860k i/100ms
16
- cancan-user 16.308k i/100ms
17
- -------------------------------------------------
18
- ag-admin 283.174k (± 1.1%) i/s - 5.682M
19
- cancan-admin 160.450k (± 1.0%) i/s - 3.217M
20
- ag-moderator 301.290k (± 1.1%) i/s - 6.029M
21
- cancan-moderator 134.591k (± 1.3%) i/s - 2.698M
22
- ag-user 353.259k (± 0.9%) i/s - 7.086M
23
- cancan-user 198.579k (± 1.6%) i/s - 3.979M
18
+ ag-admin 2.141M (± 3.9%) i/s - 10.799M in 5.052573s
19
+ ag-moderator 2.180M (± 2.1%) i/s - 10.952M in 5.025727s
20
+ ag-user 2.206M (± 0.4%) i/s - 11.155M in 5.056550s
21
+ cancan-admin 158.288k (± 2.4%) i/s - 802.710k in 5.074299s
22
+ cancan-moderator 142.573k (± 2.1%) i/s - 724.955k in 5.087277s
23
+ cancan-user 204.783k (± 2.2%) i/s - 1.040M in 5.080488s
24
24
  ```
@@ -3,19 +3,9 @@ require "access-granted/policy"
3
3
  require "access-granted/permission"
4
4
  require "access-granted/role"
5
5
  require "access-granted/rails/controller_methods"
6
+ require "access-granted/railtie" if defined?(Rails)
6
7
 
7
8
  module AccessGranted
8
9
 
9
10
  end
10
11
 
11
- if defined? ActionController::Base
12
- ActionController::Base.class_eval do
13
- include AccessGranted::Rails::ControllerMethods
14
- end
15
- end
16
-
17
- if defined? ActionController::API
18
- ActionController::API.class_eval do
19
- include AccessGranted::Rails::ControllerMethods
20
- end
21
- end
@@ -3,5 +3,12 @@ module AccessGranted
3
3
 
4
4
  class DuplicatePermission < Error; end;
5
5
  class DuplicateRole < Error; end;
6
- class AccessDenied < Error; end;
6
+ class AccessDenied < Error
7
+ attr_reader :action, :subject, :message
8
+ def initialize(action = nil, subject = nil, message = nil)
9
+ @action = action
10
+ @subject = subject
11
+ @message = message
12
+ end
13
+ end
7
14
  end
@@ -1,13 +1,14 @@
1
1
  module AccessGranted
2
2
  class Permission
3
- attr_reader :action, :subject, :granted, :conditions, :block
3
+ attr_reader :action, :subject, :granted, :conditions, :actions, :block
4
4
 
5
- def initialize(granted, action, subject, user = nil, conditions = {}, block = nil)
5
+ def initialize(granted, action, subject, user = nil, conditions = {}, actions = [], block = nil)
6
6
  @action = action
7
7
  @user = user
8
8
  @granted = granted
9
9
  @subject = subject
10
10
  @conditions = conditions
11
+ @actions = actions
11
12
  @block = block
12
13
  end
13
14
 
@@ -20,10 +21,12 @@ module AccessGranted
20
21
  end
21
22
 
22
23
  def matches_conditions?(subject)
23
- if @block && !subject.is_a?(Class)
24
+ if @block
24
25
  @block.call(subject, @user)
25
- else
26
+ elsif !@conditions.empty?
26
27
  matches_hash_conditions?(subject)
28
+ else
29
+ true
27
30
  end
28
31
  end
29
32
 
@@ -29,31 +29,40 @@ module AccessGranted
29
29
 
30
30
  def can?(action, subject = nil)
31
31
  cache[action] ||= {}
32
- cache[action][subject] ||= check_permission(action, subject)
32
+
33
+ if cache[action][subject]
34
+ cache[action][subject]
35
+ else
36
+ granted, actions = check_permission(action, subject)
37
+ actions.each do |a|
38
+ cache[a] ||= {}
39
+ cache[a][subject] ||= granted
40
+ end
41
+
42
+ granted
43
+ end
33
44
  end
34
45
 
35
46
  def check_permission(action, subject)
36
47
  applicable_roles.each do |role|
37
48
  permission = role.find_permission(action, subject)
38
- return permission.granted if permission
49
+ return [permission.granted, permission.actions] if permission
39
50
  end
40
51
 
41
- false
52
+ [false, []]
42
53
  end
43
54
 
44
55
  def cannot?(*args)
45
56
  !can?(*args)
46
57
  end
47
58
 
48
- def authorize!(action, subject)
59
+ def authorize!(action, subject, message = 'Access Denied')
49
60
  if cannot?(action, subject)
50
- raise AccessDenied
61
+ raise AccessDenied.new(action, subject, message)
51
62
  end
52
63
  subject
53
64
  end
54
65
 
55
- private
56
-
57
66
  def applicable_roles
58
67
  @applicable_roles ||= roles.select do |role|
59
68
  role.applies_to?(user)
@@ -0,0 +1,29 @@
1
+ require 'rails/railtie'
2
+
3
+ module AccessGranted
4
+ class Railtie < ::Rails::Railtie
5
+ initializer :access_granted do
6
+ if ::Rails::VERSION::MAJOR >= 6
7
+ ActiveSupport.on_load(:action_controller_base) do |base|
8
+ base.include AccessGranted::Rails::ControllerMethods
9
+ end
10
+
11
+ ActiveSupport.on_load(:action_controller_api) do |base|
12
+ base.include AccessGranted::Rails::ControllerMethods
13
+ end
14
+ else
15
+ if defined? ActionController::Base
16
+ ActionController::Base.class_eval do
17
+ include AccessGranted::Rails::ControllerMethods
18
+ end
19
+ end
20
+
21
+ if defined? ActionController::API
22
+ ActionController::API.class_eval do
23
+ include AccessGranted::Rails::ControllerMethods
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -53,9 +53,10 @@ module AccessGranted
53
53
  end
54
54
 
55
55
  def add_permission(granted, action, subject, conditions, block)
56
- prepare_actions(action).each do |a|
56
+ prepared_actions = prepare_actions(action)
57
+ prepared_actions.each do |a|
57
58
  raise DuplicatePermission, "Permission `#{a}` is already defined for #{subject} in role `#{name}`" if find_permission(a, subject)
58
- permissions << Permission.new(granted, a, subject, @user, conditions, block)
59
+ permissions << Permission.new(granted, a, subject, @user, conditions, prepared_actions, block)
59
60
  end
60
61
  end
61
62
 
@@ -68,11 +69,8 @@ module AccessGranted
68
69
  end
69
70
 
70
71
  def prepare_actions(action)
71
- if action == :manage
72
- actions = [:read, :create, :update, :destroy]
73
- else
74
- actions = Array(*[action])
75
- end
72
+ actions = Array(*[action])
73
+ actions.flat_map { |a| a == :manage ? [:create, :read, :update, :destroy ] : [a] }
76
74
  end
77
75
  end
78
76
  end
@@ -21,7 +21,11 @@ describe AccessGranted::Rails::ControllerMethods do
21
21
 
22
22
  describe "#authorize!" do
23
23
  it "raises exception when authorization fails" do
24
- expect { @controller.authorize!(:read, String) }.to raise_error(AccessGranted::AccessDenied)
24
+ expect { @controller.authorize!(:read, String) }.to raise_error do |err|
25
+ expect(err).to be_a(AccessGranted::AccessDenied)
26
+ expect(err.action).to eq(:read)
27
+ expect(err.subject).to eq(String)
28
+ end
25
29
  end
26
30
 
27
31
  it "returns subject if authorization succeeds" do
@@ -3,30 +3,23 @@ require 'spec_helper'
3
3
  describe AccessGranted::Permission do
4
4
  subject { AccessGranted::Permission }
5
5
 
6
- describe "#matches_conditions?" do
7
- it "matches when no conditions given" do
8
- perm = subject.new(true, :read, String)
9
- expect(perm.matches_conditions?(String)).to eq(true)
10
- end
6
+ describe "#matches_proc_conditions?" do
11
7
 
12
- it "matches proc conditions" do
8
+ it "matches proc conditions when true" do
13
9
  sub = double("Element", published?: true)
14
- perm = subject.new(true, :read, sub.class, nil, {}, proc {|el| el.published? })
10
+ perm = subject.new(true, :read, sub, nil, {}, [], proc {true})
15
11
  expect(perm.matches_conditions?(sub)).to eq(true)
16
12
  end
17
13
 
18
- it "does not match proc conditions when given a class instead of an instance" do
14
+ it "does not match proc conditions false" do
19
15
  sub = double("Element", published?: true)
20
- perm = subject.new(true, :read, sub.class, nil, {}, proc {|el| el.published? })
21
- expect(perm.matches_conditions?(sub.class)).to eq(true)
16
+ perm = subject.new(true, :read, sub, nil, {}, [], proc {false})
17
+ expect(perm.matches_conditions?(sub)).to eq(false)
22
18
  end
19
+
23
20
  end
24
21
 
25
22
  describe "#matches_hash_conditions?" do
26
- it "matches condition hash is empty" do
27
- perm = subject.new(true, :read, String)
28
- expect(perm.matches_hash_conditions?(String)).to eq(true)
29
- end
30
23
 
31
24
  it "matches when conditions given" do
32
25
  sub = double("Element", published: true)
@@ -39,6 +32,7 @@ describe AccessGranted::Permission do
39
32
  perm = subject.new(true, :read, sub, nil, { published: true, readable: true })
40
33
  expect(perm.matches_hash_conditions?(sub)).to eq(false)
41
34
  end
35
+
42
36
  end
43
37
 
44
38
  describe "#matches_action?" do
@@ -46,6 +40,7 @@ describe AccessGranted::Permission do
46
40
  perm = subject.new(true, :read, String)
47
41
  expect(perm.matches_action?(:read)).to_not be_nil
48
42
  end
43
+
49
44
  end
50
45
 
51
46
  describe "#matches_subject?" do
@@ -73,5 +68,15 @@ describe AccessGranted::Permission do
73
68
  perm = subject.new(true, :read, String)
74
69
  expect(perm.matches_subject? Object.new).to eq(false)
75
70
  end
71
+
76
72
  end
73
+
74
+ describe "#matches_empty_conditions?" do
75
+ it "matches when no conditions given" do
76
+ perm = subject.new(true, :read, String)
77
+ expect(perm.matches_conditions?(String)).to eq(true)
78
+ end
79
+
80
+ end
81
+
77
82
  end
data/spec/policy_spec.rb CHANGED
@@ -136,7 +136,21 @@ describe AccessGranted::Policy do
136
136
  end
137
137
 
138
138
  it "raises AccessDenied if action is not allowed" do
139
- expect { klass.new(@member).authorize!(:create, Integer) }.to raise_error AccessGranted::AccessDenied
139
+ expect { klass.new(@member).authorize!(:create, Integer) }.to raise_error do |err|
140
+ expect(err).to be_a(AccessGranted::AccessDenied)
141
+ expect(err.action).to eq(:create)
142
+ expect(err.subject).to eq(Integer)
143
+ end
144
+ end
145
+
146
+ it "raises AccessDenied with supplied message if action is not allowed" do
147
+ message = 'You are not allowed to create Integer'
148
+ expect { klass.new(@member).authorize!(:create, Integer, message) }.to raise_error do |err|
149
+ expect(err).to be_a(AccessGranted::AccessDenied)
150
+ expect(err.action).to eq(:create)
151
+ expect(err.subject).to eq(Integer)
152
+ expect(err.message).to eq(message)
153
+ end
140
154
  end
141
155
 
142
156
  it "returns the subject if allowed" do
@@ -184,8 +198,9 @@ describe AccessGranted::Policy do
184
198
  end
185
199
  end
186
200
 
187
- describe "#matching_roles" do
201
+ describe "#applicable_roles" do
188
202
  let(:user) { double("User", is_moderator: true, is_admin: true) }
203
+ subject(:policy) { klass.new(user) }
189
204
 
190
205
  before do
191
206
  policy.role(:administrator, { is_admin: true })
@@ -193,9 +208,17 @@ describe AccessGranted::Policy do
193
208
  policy.role(:member)
194
209
  end
195
210
 
196
- shared_examples 'role matcher' do
211
+ context "user matches all roles" do
197
212
  it "returns all matching roles in the order of priority" do
198
- expect(subject.map(&:name)).to eq([:administrator, :moderator, :member])
213
+ expect(policy.applicable_roles.map(&:name)).to eq([:administrator, :moderator, :member])
214
+ end
215
+ end
216
+
217
+ context "user is just an admin" do
218
+ let(:user) { double("User", is_moderator: false, is_admin: true) }
219
+
220
+ it 'returns array with admin and member roles' do
221
+ expect(policy.applicable_roles.map(&:name)).to eq([:administrator, :member])
199
222
  end
200
223
  end
201
224
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: access-granted
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotrek Okoński
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-02 00:00:00.000000000 Z
11
+ date: 2021-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -62,6 +62,7 @@ files:
62
62
  - lib/access-granted/permission.rb
63
63
  - lib/access-granted/policy.rb
64
64
  - lib/access-granted/rails/controller_methods.rb
65
+ - lib/access-granted/railtie.rb
65
66
  - lib/access-granted/role.rb
66
67
  - lib/generators/access_granted/policy_generator.rb
67
68
  - lib/generators/templates/access_policy.rb
@@ -89,8 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
90
  - !ruby/object:Gem::Version
90
91
  version: '0'
91
92
  requirements: []
92
- rubyforge_project:
93
- rubygems_version: 2.5.1
93
+ rubygems_version: 3.1.4
94
94
  signing_key:
95
95
  specification_version: 4
96
96
  summary: Elegant whitelist and role based authorization with ability to prioritize
@@ -101,4 +101,3 @@ test_files:
101
101
  - spec/policy_spec.rb
102
102
  - spec/role_spec.rb
103
103
  - spec/spec_helper.rb
104
- has_rdoc: