skill_tree 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/generators/skill_tree/install_generator.rb +20 -0
- data/lib/generators/skill_tree/orm_helpers.rb +14 -0
- data/lib/generators/skill_tree/templates/acl.rb +42 -0
- data/lib/generators/skill_tree/templates/create_skill_tree_acl_system.rb +48 -0
- data/lib/generators/skill_tree/templates/initializer.rb +1 -0
- data/lib/skill_tree.rb +15 -0
- data/lib/skill_tree/controller.rb +57 -0
- data/lib/skill_tree/errors.rb +5 -0
- data/lib/skill_tree/models.rb +10 -0
- data/lib/skill_tree/models/acl.rb +13 -0
- data/lib/skill_tree/models/acl_mapping.rb +16 -0
- data/lib/skill_tree/models/acl_ownership.rb +11 -0
- data/lib/skill_tree/models/permission.rb +10 -0
- data/lib/skill_tree/models/role.rb +22 -0
- data/lib/skill_tree/models/user_role.rb +14 -0
- data/lib/skill_tree/parser.rb +44 -0
- data/lib/skill_tree/parser/acl_parser.rb +42 -0
- data/lib/skill_tree/parser/role_parser.rb +57 -0
- data/lib/skill_tree/resource.rb +21 -0
- data/lib/skill_tree/resource/callbacks.rb +20 -0
- data/lib/skill_tree/resource/class_methods.rb +10 -0
- data/lib/skill_tree/resource/instance_methods.rb +52 -0
- data/lib/skill_tree/resource/relations.rb +18 -0
- data/lib/skill_tree/resource/scopes.rb +38 -0
- data/lib/skill_tree/subject.rb +53 -0
- data/lib/skill_tree/version.rb +3 -0
- data/spec/db/schema.rb +59 -0
- data/spec/skill_tree/controller_spec.rb +103 -0
- data/spec/skill_tree/generators/install_generator_spec.rb +34 -0
- data/spec/skill_tree/models/acl_mapping_spec.rb +46 -0
- data/spec/skill_tree/models/acl_ownership_spec.rb +30 -0
- data/spec/skill_tree/models/acl_spec.rb +32 -0
- data/spec/skill_tree/models/permission_spec.rb +16 -0
- data/spec/skill_tree/models/role_spec.rb +41 -0
- data/spec/skill_tree/models/user_role_spec.rb +16 -0
- data/spec/skill_tree/parser_spec.rb +71 -0
- data/spec/skill_tree/resource_spec.rb +137 -0
- data/spec/skill_tree/subject_spec.rb +65 -0
- data/spec/skill_tree_spec.rb +8 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/support/acls/acl_example.rb +19 -0
- data/spec/support/acls/acl_example2.rb +24 -0
- data/spec/support/acls/acl_example3.rb +24 -0
- data/spec/support/acls/acl_example4.rb +24 -0
- data/spec/support/acls/subject_acl.rb +57 -0
- data/spec/support/capture_stdout.rb +12 -0
- data/spec/support/controllers.rb +71 -0
- data/spec/support/model_builder.rb +15 -0
- data/spec/support/models.rb +16 -0
- metadata +232 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'skill_tree/resource/callbacks.rb'
|
|
2
|
+
require 'skill_tree/resource/class_methods.rb'
|
|
3
|
+
require 'skill_tree/resource/instance_methods.rb'
|
|
4
|
+
require 'skill_tree/resource/relations.rb'
|
|
5
|
+
require 'skill_tree/resource/scopes.rb'
|
|
6
|
+
|
|
7
|
+
module SkillTree
|
|
8
|
+
module Resource
|
|
9
|
+
module Initializer
|
|
10
|
+
def as_skill_tree_resource(options = {})
|
|
11
|
+
send :include, SkillTree::Resource::InstanceMethods
|
|
12
|
+
send :include, SkillTree::Resource::Relations
|
|
13
|
+
send :include, SkillTree::Resource::Scopes
|
|
14
|
+
send :include, SkillTree::Resource::Callbacks
|
|
15
|
+
send :extend, SkillTree::Resource::ClassMethods
|
|
16
|
+
send :skill_tree_options=, options
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
ActiveRecord::Base.send :extend, SkillTree::Resource::Initializer
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module SkillTree
|
|
2
|
+
module Resource
|
|
3
|
+
module Callbacks
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.before_save do |model|
|
|
6
|
+
unless model.acl
|
|
7
|
+
acl = SkillTree::Parser::Initializer.default_acl_for(model)
|
|
8
|
+
model.acl = acl if acl
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
base.after_save :skill_tree_setup_resource_owner
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def skill_tree_setup_resource_owner
|
|
15
|
+
admin = skill_tree_options[:admin]
|
|
16
|
+
send(admin).role!(:admin, self) if admin && acl && try(admin)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module SkillTree
|
|
2
|
+
module Resource
|
|
3
|
+
module InstanceMethods
|
|
4
|
+
def acl
|
|
5
|
+
acl_ownerships.first.acl if acl_ownerships.first
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def acl=(value)
|
|
9
|
+
if acl_ownerships.empty?
|
|
10
|
+
acl_ownerships.new(acl: value)
|
|
11
|
+
else
|
|
12
|
+
fail SkillTree::AclAlreadySet,
|
|
13
|
+
"#{self.class.name} already has an acl. Use .acl! instead."
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def acl!(value)
|
|
18
|
+
new_acl = parse_acl(value)
|
|
19
|
+
ActiveRecord::Base.transaction do
|
|
20
|
+
acl_ownerships.destroy_all
|
|
21
|
+
acl_ownerships.create!(acl: new_acl)
|
|
22
|
+
end if new_acl != acl
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def permissions_for(user)
|
|
26
|
+
role = SkillTree::Models::Role.find_for(user, self)
|
|
27
|
+
SkillTree::Models::Permission.joins(
|
|
28
|
+
acl_mappings: [:role, :acl])
|
|
29
|
+
.where('("roles"."id" = ?)', role)
|
|
30
|
+
.where('("acls"."id" = ?)', acl)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def default_permission?(action, role = :user)
|
|
34
|
+
acls.joins(acl_mappings: [:permission, :role])
|
|
35
|
+
.where('"permissions"."name" = ?', action.to_s)
|
|
36
|
+
.where('"roles"."name" = ?', role.to_s).any?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
alias_method :has_default_permission?, :default_permission?
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def parse_acl(value)
|
|
44
|
+
if value.is_a? ActiveRecord::Base
|
|
45
|
+
value
|
|
46
|
+
else
|
|
47
|
+
SkillTree::Models::Acl.find_by!(name: value.to_s)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module SkillTree
|
|
2
|
+
module Resource
|
|
3
|
+
module Relations
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.has_many :acl_ownerships,
|
|
6
|
+
as: :resource,
|
|
7
|
+
class_name: 'SkillTree::Models::AclOwnership'
|
|
8
|
+
base.has_many :acls,
|
|
9
|
+
through: :acl_ownerships,
|
|
10
|
+
class_name: 'SkillTree::Models::Acl'
|
|
11
|
+
base.has_many :roles, through: :acls,
|
|
12
|
+
class_name: 'SkillTree::Models::Role'
|
|
13
|
+
base.has_many :user_roles,
|
|
14
|
+
as: :resource, class_name: 'SkillTree::Models::UserRole'
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module SkillTree
|
|
2
|
+
module Resource
|
|
3
|
+
module Scopes
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
included do
|
|
6
|
+
scope :where_user_can, lambda { |user, action|
|
|
7
|
+
if user
|
|
8
|
+
where_logged_user_can(action, user.id)
|
|
9
|
+
else
|
|
10
|
+
where_guest_can(action)
|
|
11
|
+
end
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
scope :with_permissions, lambda {
|
|
15
|
+
joins(acls: { acl_mappings: [:permission, :role] })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
scope :where_guest_can, lambda { |action|
|
|
19
|
+
with_permissions
|
|
20
|
+
.where('"permissions"."name" = ?', action.to_s)
|
|
21
|
+
.where('"roles"."name" = ?', 'guest')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
scope :where_logged_user_can, lambda { |action, user_id|
|
|
25
|
+
with_permissions
|
|
26
|
+
.joins("LEFT OUTER JOIN \"user_roles\""\
|
|
27
|
+
" ON \"user_roles\".\"resource_id\" = \"#{table_name}\".\"id\""\
|
|
28
|
+
" AND \"user_roles\".\"resource_type\" = '#{name}'")
|
|
29
|
+
.where('"permissions"."name" = ?', action.to_s)
|
|
30
|
+
.where('("user_roles"."role_id" = "roles"."id"'\
|
|
31
|
+
' AND "user_roles"."user_id" = ?) OR ("roles"."name" = ?)',
|
|
32
|
+
user_id, 'user')
|
|
33
|
+
.uniq
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module SkillTree
|
|
2
|
+
module Subject
|
|
3
|
+
module Initializer
|
|
4
|
+
def as_skill_tree_subject
|
|
5
|
+
send :include, SkillTree::Subject::InstanceMethods
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
module InstanceMethods
|
|
9
|
+
def self.included(base)
|
|
10
|
+
base.send :has_many, :user_roles,
|
|
11
|
+
class_name: 'SkillTree::Models::UserRole'
|
|
12
|
+
base.send :has_many, :roles,
|
|
13
|
+
through: :user_roles, class_name: 'SkillTree::Models::Role'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def can?(action, resource)
|
|
17
|
+
resource.default_permission?(action) ||
|
|
18
|
+
permitted_with_role?(action, resource)
|
|
19
|
+
end
|
|
20
|
+
alias_method :allowed_to?, :can?
|
|
21
|
+
|
|
22
|
+
def role?(role_name, resource)
|
|
23
|
+
user_roles.joins(:role)
|
|
24
|
+
.where(roles: { name: role_name.to_s })
|
|
25
|
+
.where(resource: resource).any?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def role!(role, resource)
|
|
29
|
+
role = SkillTree::Models::Role.find_by_name!(role)
|
|
30
|
+
user_role = user_roles.find_or_initialize_by(resource: resource)
|
|
31
|
+
user_role.update!(role: role)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def unrole!(role, resource)
|
|
35
|
+
role = SkillTree::Models::Role.find_by_name!(role)
|
|
36
|
+
active_roles = user_roles.where(role: role, resource: resource)
|
|
37
|
+
active_roles.destroy_all
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
alias_method :has_role?, :role?
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def permitted_with_role?(action, resource)
|
|
45
|
+
user_roles.joins(role: { acl_mappings: [:permission, :acl] })
|
|
46
|
+
.where('"permissions"."name" = ?', action.to_s)
|
|
47
|
+
.where('"acls"."id" = ?', resource.acl)
|
|
48
|
+
.where(resource: resource).any?
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
ActiveRecord::Base.send :extend, SkillTree::Subject::Initializer
|
data/spec/db/schema.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
ActiveRecord::Schema.define(version: 0) do
|
|
2
|
+
create_table :users do |t|
|
|
3
|
+
t.string :name
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
create_table :posts do |t|
|
|
7
|
+
t.string :text
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
create_table :post_with_authors do |t|
|
|
11
|
+
t.string :text
|
|
12
|
+
t.references :user
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
create_table :acls do |t|
|
|
16
|
+
t.string :name, null: false
|
|
17
|
+
t.integer :version, null: false
|
|
18
|
+
end
|
|
19
|
+
add_index :acls, :name, unique: true
|
|
20
|
+
|
|
21
|
+
create_table :roles do |t|
|
|
22
|
+
t.string :name, null: false
|
|
23
|
+
end
|
|
24
|
+
add_index :roles, :name, unique: true
|
|
25
|
+
|
|
26
|
+
create_table :permissions do |t|
|
|
27
|
+
t.string :name, null: false
|
|
28
|
+
end
|
|
29
|
+
add_index :permissions, :name, unique: true
|
|
30
|
+
|
|
31
|
+
create_table :acl_mappings do |t|
|
|
32
|
+
t.references :acl, null: false
|
|
33
|
+
t.references :role, null: false
|
|
34
|
+
t.references :permission, null: true
|
|
35
|
+
|
|
36
|
+
t.timestamps null: false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
add_index :acl_mappings, [:acl_id, :role_id, :permission_id], unique: true
|
|
40
|
+
|
|
41
|
+
create_table :user_roles do |t|
|
|
42
|
+
t.references :user
|
|
43
|
+
t.references :role, null: false
|
|
44
|
+
t.references :resource, polymorphic: true
|
|
45
|
+
|
|
46
|
+
t.timestamps null: false
|
|
47
|
+
end
|
|
48
|
+
add_index :user_roles, [:resource_type, :resource_id]
|
|
49
|
+
# Only can't be admin of a resource multiple times
|
|
50
|
+
add_index :user_roles, [:user_id, :role_id, :resource_type, :resource_id],
|
|
51
|
+
unique: true, name: :user_roles_avoid_duplicate_roles
|
|
52
|
+
|
|
53
|
+
create_table :acl_ownerships do |t|
|
|
54
|
+
t.references :acl, null: false
|
|
55
|
+
t.references :resource, polymorphic: true
|
|
56
|
+
end
|
|
57
|
+
# There can be only one-to-many relationship with a resource
|
|
58
|
+
add_index :acl_ownerships, [:resource_type, :resource_id], unique: true
|
|
59
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe TestController, type: :controller do
|
|
4
|
+
before(:each) do
|
|
5
|
+
stub_const('Rails', double(root: double))
|
|
6
|
+
allow(Rails.root).to receive(:join)
|
|
7
|
+
.and_return('spec/support/acls/subject_acl.rb')
|
|
8
|
+
SkillTree::Parser::Initializer.parse!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
let(:user) { User.create! }
|
|
12
|
+
|
|
13
|
+
let(:admin) do
|
|
14
|
+
user = User.create!
|
|
15
|
+
user.role! :admin, resource
|
|
16
|
+
user
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
let!(:resource) { Post.create! }
|
|
20
|
+
|
|
21
|
+
it '#index' do
|
|
22
|
+
expect_any_instance_of(TestController).not_to receive(:update)
|
|
23
|
+
expect { get :index }.to raise_error SkillTree::NotAuthorizedError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it '#show' do
|
|
27
|
+
expect_any_instance_of(TestController).not_to receive(:update)
|
|
28
|
+
expect { get :index }.to raise_error SkillTree::NotAuthorizedError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it '#update' do
|
|
32
|
+
expect_any_instance_of(TestController).to receive(:update)
|
|
33
|
+
get :update
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe '#create' do
|
|
37
|
+
it 'is open for a logged user' do
|
|
38
|
+
allow_any_instance_of(TestController).to receive(:current_user)
|
|
39
|
+
.and_return(user)
|
|
40
|
+
expect_any_instance_of(TestController).to receive(:create)
|
|
41
|
+
get :create
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
it 'is blocked for a guest' do
|
|
45
|
+
expect_any_instance_of(TestController).not_to receive(:create)
|
|
46
|
+
expect { get :create }.to raise_error SkillTree::NotAuthorizedError
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#destroy' do
|
|
51
|
+
it 'is open for an admin' do
|
|
52
|
+
allow_any_instance_of(TestController).to receive(:current_user)
|
|
53
|
+
.and_return(admin)
|
|
54
|
+
expect_any_instance_of(TestController).to receive(:destroy)
|
|
55
|
+
get :destroy
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'is blocked for a logged user' do
|
|
59
|
+
allow_any_instance_of(TestController).to receive(:current_user)
|
|
60
|
+
.and_return(user)
|
|
61
|
+
expect_any_instance_of(TestController).not_to receive(:destroy)
|
|
62
|
+
expect { get :destroy }.to raise_error SkillTree::NotAuthorizedError
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'is blocked for a guest' do
|
|
66
|
+
expect_any_instance_of(TestController).not_to receive(:destroy)
|
|
67
|
+
expect { get :destroy }.to raise_error SkillTree::NotAuthorizedError
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe Test2Controller, type: :controller do
|
|
73
|
+
before(:each) do
|
|
74
|
+
stub_const('Rails', double(root: double))
|
|
75
|
+
allow(Rails.root).to receive(:join)
|
|
76
|
+
.and_return('spec/support/acls/subject_acl.rb')
|
|
77
|
+
SkillTree::Parser::Initializer.parse!
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
let(:user) { User.create! }
|
|
81
|
+
let!(:resource) { Post.create! }
|
|
82
|
+
|
|
83
|
+
describe '#index' do
|
|
84
|
+
it 'returns error if the user is not logged in' do
|
|
85
|
+
expect_any_instance_of(Test2Controller).not_to receive(:index)
|
|
86
|
+
expect { get :index }.to raise_error SkillTree::NotAuthorizedError
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'works only when user is logged in' do
|
|
90
|
+
allow_any_instance_of(Test2Controller).to receive(:current_user)
|
|
91
|
+
.and_return(user)
|
|
92
|
+
|
|
93
|
+
expect_any_instance_of(Test2Controller).to receive(:index)
|
|
94
|
+
get :index
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe '#current_acl' do
|
|
99
|
+
it 'differs between classes with common parent' do
|
|
100
|
+
expect(TestController.current_acl).not_to eq(Test2Controller.current_acl)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe SkillTree::InstallGenerator, type: :generator do
|
|
4
|
+
destination File.expand_path('../../tmp', __FILE__)
|
|
5
|
+
arguments %w()
|
|
6
|
+
|
|
7
|
+
before(:all) do
|
|
8
|
+
prepare_destination
|
|
9
|
+
run_generator
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'creates a migration, an acl and an initializer' do
|
|
13
|
+
expect(destination_root).to have_structure {
|
|
14
|
+
no_file 'test.rb'
|
|
15
|
+
directory 'config' do
|
|
16
|
+
directory 'initializers' do
|
|
17
|
+
file 'skill_tree.rb' do
|
|
18
|
+
contains 'SkillTree.init!'
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
file 'acl.rb' do
|
|
22
|
+
contains 'Example acl.rb file'
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
directory 'db' do
|
|
26
|
+
directory 'migrate' do
|
|
27
|
+
migration 'create_skill_tree_acl_system' do
|
|
28
|
+
contains 'class CreateSkillTreeAclSystem'
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe SkillTree::Models::AclMapping, type: :model do
|
|
4
|
+
it 'belongs to an acl' do
|
|
5
|
+
expect(subject).to respond_to(:acl)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
it 'validates presence of acl' do
|
|
9
|
+
expect(subject).to have(1).error_on(:acl)
|
|
10
|
+
subject.acl = build(:acl)
|
|
11
|
+
expect(subject).to have(0).error_on(:acl)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it 'belongs to a role' do
|
|
15
|
+
expect(subject).to respond_to(:role)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it 'validates presence of role' do
|
|
19
|
+
expect(subject).to have(1).error_on(:role)
|
|
20
|
+
subject.role = build(:role)
|
|
21
|
+
expect(subject).to have(0).error_on(:role)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'belongs to a permission' do
|
|
25
|
+
expect(subject).to respond_to(:permission)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'validates presence of permission' do
|
|
29
|
+
expect(subject).to have(1).error_on(:permission)
|
|
30
|
+
subject.permission = build(:permission)
|
|
31
|
+
expect(subject).to have(0).error_on(:permission)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it 'validates uniqueness of relations' do
|
|
35
|
+
acl_mapping = create(:acl_mapping,
|
|
36
|
+
role: create(:role, name: 'a'),
|
|
37
|
+
acl: create(:acl, name: 'b', version: 0),
|
|
38
|
+
permission: create(:permission, name: 'c'))
|
|
39
|
+
subject.assign_attributes(
|
|
40
|
+
role: acl_mapping.role,
|
|
41
|
+
acl: acl_mapping.acl,
|
|
42
|
+
permission: acl_mapping.permission
|
|
43
|
+
)
|
|
44
|
+
expect(subject).to have(1).error_on(:acl_id)
|
|
45
|
+
end
|
|
46
|
+
end
|