authorize 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.
- data/.gitignore +5 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +42 -0
- data/LICENSE +20 -0
- data/README +155 -0
- data/Rakefile +25 -0
- data/TODO.txt +9 -0
- data/authorize.gemspec +25 -0
- data/generators/authorize/USAGE +8 -0
- data/generators/authorize/authorize_generator.rb +7 -0
- data/generators/authorize/templates/migrate/create_authorizations.rb +26 -0
- data/install.rb +1 -0
- data/lib/authorize.rb +2 -0
- data/lib/authorize/action_controller.rb +59 -0
- data/lib/authorize/action_view.rb +4 -0
- data/lib/authorize/active_record.rb +37 -0
- data/lib/authorize/bitmask.rb +84 -0
- data/lib/authorize/exceptions.rb +30 -0
- data/lib/authorize/graph.rb +4 -0
- data/lib/authorize/graph/directed_acyclic_graph.rb +10 -0
- data/lib/authorize/graph/directed_acyclic_graph_reverse_traverser.rb +27 -0
- data/lib/authorize/graph/directed_acyclic_graph_traverser.rb +30 -0
- data/lib/authorize/graph/directed_graph.rb +27 -0
- data/lib/authorize/graph/edge.rb +58 -0
- data/lib/authorize/graph/factory.rb +39 -0
- data/lib/authorize/graph/fixtures.rb +33 -0
- data/lib/authorize/graph/graph.rb +55 -0
- data/lib/authorize/graph/traverser.rb +89 -0
- data/lib/authorize/graph/undirected_graph.rb +14 -0
- data/lib/authorize/graph/vertex.rb +53 -0
- data/lib/authorize/permission.rb +97 -0
- data/lib/authorize/redis.rb +2 -0
- data/lib/authorize/redis/array.rb +36 -0
- data/lib/authorize/redis/base.rb +165 -0
- data/lib/authorize/redis/connection_manager.rb +88 -0
- data/lib/authorize/redis/connection_specification.rb +16 -0
- data/lib/authorize/redis/factory.rb +64 -0
- data/lib/authorize/redis/fixtures.rb +22 -0
- data/lib/authorize/redis/hash.rb +34 -0
- data/lib/authorize/redis/model_reference.rb +21 -0
- data/lib/authorize/redis/model_set.rb +19 -0
- data/lib/authorize/redis/set.rb +42 -0
- data/lib/authorize/redis/string.rb +17 -0
- data/lib/authorize/resource.rb +4 -0
- data/lib/authorize/resource_pool.rb +87 -0
- data/lib/authorize/role.rb +115 -0
- data/lib/authorize/test_helper.rb +42 -0
- data/lib/authorize/trustee.rb +4 -0
- data/lib/authorize/version.rb +3 -0
- data/rails/init.rb +5 -0
- data/tasks/authorize_tasks.rake +4 -0
- data/test/Rakefile +7 -0
- data/test/app/controllers/application_controller.rb +5 -0
- data/test/app/controllers/thingy_controller.rb +11 -0
- data/test/app/controllers/widgets_controller.rb +2 -0
- data/test/app/models/public.rb +14 -0
- data/test/app/models/user.rb +8 -0
- data/test/app/models/widget.rb +7 -0
- data/test/config/boot.rb +109 -0
- data/test/config/database.yml +25 -0
- data/test/config/environment.rb +28 -0
- data/test/config/environments/development.rb +4 -0
- data/test/config/environments/test.rb +0 -0
- data/test/config/initializers/mask.rb +1 -0
- data/test/config/initializers/redis.rb +8 -0
- data/test/config/routes.rb +5 -0
- data/test/db/.gitignore +1 -0
- data/test/db/schema.rb +26 -0
- data/test/log/.gitignore +2 -0
- data/test/public/javascripts/application.js +2 -0
- data/test/public/javascripts/controls.js +963 -0
- data/test/public/javascripts/dragdrop.js +972 -0
- data/test/public/javascripts/effects.js +1120 -0
- data/test/public/javascripts/prototype.js +4225 -0
- data/test/script/about +3 -0
- data/test/script/console +3 -0
- data/test/script/dbconsole +3 -0
- data/test/script/destroy +3 -0
- data/test/script/generate +3 -0
- data/test/script/performance/benchmarker +3 -0
- data/test/script/performance/profiler +3 -0
- data/test/script/performance/request +3 -0
- data/test/script/plugin +3 -0
- data/test/script/process/inspector +3 -0
- data/test/script/process/reaper +3 -0
- data/test/script/process/spawner +3 -0
- data/test/script/runner +3 -0
- data/test/script/server +3 -0
- data/test/test/fixtures/authorize/role_graph.yml +11 -0
- data/test/test/fixtures/permissions.yml +27 -0
- data/test/test/fixtures/redis/redis.yml +8 -0
- data/test/test/fixtures/redis/role_graph.yml +29 -0
- data/test/test/fixtures/roles.yml +28 -0
- data/test/test/fixtures/users.yml +12 -0
- data/test/test/fixtures/widgets.yml +12 -0
- data/test/test/functional/controller_class_test.rb +36 -0
- data/test/test/functional/controller_test.rb +46 -0
- data/test/test/test_helper.rb +35 -0
- data/test/test/unit/bitmask_test.rb +112 -0
- data/test/test/unit/fixture_test.rb +59 -0
- data/test/test/unit/graph_directed_acyclic_graph_reverse_traverser_test.rb +43 -0
- data/test/test/unit/graph_directed_acyclic_graph_traverser_test.rb +57 -0
- data/test/test/unit/graph_directed_graph_test.rb +66 -0
- data/test/test/unit/graph_edge_test.rb +53 -0
- data/test/test/unit/graph_graph_test.rb +50 -0
- data/test/test/unit/graph_traverser_test.rb +43 -0
- data/test/test/unit/graph_vertex_test.rb +57 -0
- data/test/test/unit/permission_test.rb +123 -0
- data/test/test/unit/redis_array_test.rb +60 -0
- data/test/test/unit/redis_connection_manager_test.rb +54 -0
- data/test/test/unit/redis_factory_test.rb +85 -0
- data/test/test/unit/redis_fixture_test.rb +18 -0
- data/test/test/unit/redis_hash_test.rb +43 -0
- data/test/test/unit/redis_model_reference_test.rb +39 -0
- data/test/test/unit/redis_set_test.rb +68 -0
- data/test/test/unit/redis_string_test.rb +25 -0
- data/test/test/unit/redis_test.rb +121 -0
- data/test/test/unit/resource_pool_test.rb +93 -0
- data/test/test/unit/resource_test.rb +33 -0
- data/test/test/unit/role_test.rb +143 -0
- data/test/test/unit/trustee_test.rb +35 -0
- data/test/tmp/.gitignore +2 -0
- data/uninstall.rb +1 -0
- metadata +319 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
class ConnectionSpecification
|
4
|
+
attr_reader :db_spec
|
5
|
+
|
6
|
+
def initialize(db_spec)
|
7
|
+
@db_spec = db_spec
|
8
|
+
end
|
9
|
+
|
10
|
+
# Factory method returning a new connection to the database conforming to the specification
|
11
|
+
def connect!
|
12
|
+
::Redis.new(db_spec)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
# A Factory is designed to help build relevant test fixtures in the context of a test. In order to preserve a high
|
4
|
+
# signal-to-noise ratio in the test, a factory needs to *concisely* build fixtures even at the expense of supporting
|
5
|
+
# cool features. As a result, index management and references to other values are the responsibility of the programmer.
|
6
|
+
class Factory
|
7
|
+
attr_reader :db
|
8
|
+
|
9
|
+
def self.build(ns = nil, &block)
|
10
|
+
self.new.tap do |f|
|
11
|
+
f.namespace(ns, &block) if ns
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(db = Base.db)
|
16
|
+
@namespace = nil
|
17
|
+
@db = db
|
18
|
+
end
|
19
|
+
|
20
|
+
def namespace(name, &block)
|
21
|
+
@old_namespace, @namespace = @namespace, subordinate_key(name)
|
22
|
+
if block_given?
|
23
|
+
self.instance_eval(&block)
|
24
|
+
@namespace = @old_namespace
|
25
|
+
else
|
26
|
+
self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def string(name, value = "")
|
31
|
+
key = subordinate_key(name)
|
32
|
+
db.set(key, value)
|
33
|
+
namespace(name){yield} if block_given?
|
34
|
+
Redis::String.load(key)
|
35
|
+
end
|
36
|
+
|
37
|
+
def hash(name, value = {})
|
38
|
+
key = subordinate_key(name)
|
39
|
+
value.each {|k, v| db.hset(key, k, v)}
|
40
|
+
namespace(name){yield} if block_given?
|
41
|
+
Redis::Hash.load(key)
|
42
|
+
end
|
43
|
+
|
44
|
+
def set(name, value = ::Set[])
|
45
|
+
key = subordinate_key(name)
|
46
|
+
value.each {|v| db.sadd(key, v)}
|
47
|
+
namespace(name){yield} if block_given?
|
48
|
+
Redis::Set.load(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def array(name, value = [])
|
52
|
+
key = subordinate_key(name)
|
53
|
+
value.each {|v| db.rpush(key, v)}
|
54
|
+
namespace(name){yield} if block_given?
|
55
|
+
Redis::Array.load(key)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
def subordinate_key(key)
|
60
|
+
Authorize::Redis::Base.subordinate_key(@namespace, key)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
# Persist Ruby objects to Redis DB using natural type affinity
|
4
|
+
module Fixtures
|
5
|
+
def create_fixtures(db, pathname, flush = true)
|
6
|
+
db.flushdb if flush
|
7
|
+
fixtures = YAML.load(ERB.new(pathname.read).result)
|
8
|
+
fixtures.each do |node|
|
9
|
+
node.each_pair do |key, value|
|
10
|
+
case value
|
11
|
+
when ::Hash then value.each_pair {|k, v| db.hset(key, k, v)}
|
12
|
+
when ::Set then value.each {|v| db.sadd(key, v)}
|
13
|
+
when ::Array then value.each {|v| db.rpush(key, v)}
|
14
|
+
else db.set(key, value) # String, Fixnum, NilClass
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
module_function :create_fixtures
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
class Hash < Base
|
4
|
+
undef to_a # In older versions of Ruby, Object#to_a is invoked and #method_missing is never called.
|
5
|
+
|
6
|
+
def valid?
|
7
|
+
%w(none hash).include?(db.type(id))
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(k)
|
11
|
+
db.hget(id, k)
|
12
|
+
end
|
13
|
+
alias [] get
|
14
|
+
|
15
|
+
def set(k, v)
|
16
|
+
db.hset(id, k, v)
|
17
|
+
end
|
18
|
+
alias []= set
|
19
|
+
|
20
|
+
def merge(h)
|
21
|
+
return self if h.empty?
|
22
|
+
args = h.inject([]) do |m,(k,v)|
|
23
|
+
m << k
|
24
|
+
m << v
|
25
|
+
end
|
26
|
+
db.hmset(id, *args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def __getobj__
|
30
|
+
db.hgetall(id)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
# Support foreign keys that reference Redis::Base-like models.
|
4
|
+
module ModelReference
|
5
|
+
# Load the model whose key is held in the given string key.
|
6
|
+
def load_reference(key, klass)
|
7
|
+
reference = klass.db.get(key)
|
8
|
+
reference && klass.load(reference)
|
9
|
+
end
|
10
|
+
|
11
|
+
def set_reference(key, model)
|
12
|
+
if model
|
13
|
+
Authorize::Redis::String.db.set(key, model.id)
|
14
|
+
else
|
15
|
+
Authorize::Redis::String.db.del(key) && nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
module_function :load_reference, :set_reference
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
# A persistent set of homomorphic Redis-like models
|
4
|
+
class ModelSet < Redis::Set
|
5
|
+
def initialize(klass)
|
6
|
+
super()
|
7
|
+
@klass = klass
|
8
|
+
end
|
9
|
+
|
10
|
+
[:add, :delete, :include?].each do |m|
|
11
|
+
define_method(m) {|v| super(v.id)}
|
12
|
+
end
|
13
|
+
|
14
|
+
def __getobj__
|
15
|
+
super.map{|eid| @klass.load(eid)}.to_set.freeze
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Authorize
|
2
|
+
module Redis
|
3
|
+
class Set < Base
|
4
|
+
undef to_a # In older versions of Ruby, Object#to_a is invoked and #method_missing is never called.
|
5
|
+
|
6
|
+
def valid?
|
7
|
+
%w(none set).include?(db.type(id))
|
8
|
+
end
|
9
|
+
|
10
|
+
def add(v)
|
11
|
+
db.sadd(id, v)
|
12
|
+
end
|
13
|
+
|
14
|
+
def <<(v)
|
15
|
+
add(v)
|
16
|
+
end
|
17
|
+
|
18
|
+
def delete(v)
|
19
|
+
db.srem(id, v)
|
20
|
+
end
|
21
|
+
|
22
|
+
def include?(v)
|
23
|
+
db.sismember(id, v)
|
24
|
+
end
|
25
|
+
alias member? include?
|
26
|
+
|
27
|
+
def sample(n = 1)
|
28
|
+
return method_missing(:sample, n) unless n == 1
|
29
|
+
db.srandmember(id)
|
30
|
+
end
|
31
|
+
|
32
|
+
def first(n = 1)
|
33
|
+
return method_missing(:first, n) unless n == 1
|
34
|
+
sample(n)
|
35
|
+
end
|
36
|
+
|
37
|
+
def __getobj__
|
38
|
+
db.smembers(id).to_set
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
require 'set'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Authorize
|
6
|
+
# Arbitrate thread-safe access to a limited set of expensive and perishable objects
|
7
|
+
class ResourcePool
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :@pool, :include?
|
11
|
+
def_delegators :@tokens, :empty?
|
12
|
+
|
13
|
+
attr_reader :num_waiting
|
14
|
+
|
15
|
+
# Create a new unfilled resource pool with a capacity of of at most max_size objects. The pool
|
16
|
+
# is lazily filled by the factory lambda.
|
17
|
+
def initialize(max_size, factory)
|
18
|
+
@factory = factory
|
19
|
+
@pool = []
|
20
|
+
@tokens = []
|
21
|
+
@monitor = Monitor.new
|
22
|
+
@tokens_cv = @monitor.new_cond
|
23
|
+
@num_waiting = 0
|
24
|
+
max_size.times {|i| @tokens.unshift(i)}
|
25
|
+
end
|
26
|
+
|
27
|
+
def size
|
28
|
+
@pool.compact.length
|
29
|
+
end
|
30
|
+
|
31
|
+
def available
|
32
|
+
@tokens.size
|
33
|
+
end
|
34
|
+
|
35
|
+
# Checkout an object from the pool. Arguments are passed unmolested to ConditionVariable#wait to manage timeouts.
|
36
|
+
def checkout(*args)
|
37
|
+
@monitor.synchronize do
|
38
|
+
until token = @tokens.pop
|
39
|
+
begin
|
40
|
+
@num_waiting += 1
|
41
|
+
raise "Timed out during checkout" unless @tokens_cv.wait(*args)
|
42
|
+
ensure
|
43
|
+
@num_waiting -= 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@pool[token] ||= @factory.call
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Return an object to the pool
|
51
|
+
def checkin(obj)
|
52
|
+
@monitor.synchronize do
|
53
|
+
token = @pool.index(obj)
|
54
|
+
raise "#{obj} has not been checked out from this pool" unless token && !@tokens.include?(token)
|
55
|
+
@tokens.push(token) if token
|
56
|
+
@tokens_cv.signal
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Expire resources in inventory with the given block. Available (not reserved) pool members are
|
61
|
+
# yielded to the block. The object is expired (removed permanently from the pool) if the block
|
62
|
+
# returns a true-ish value.
|
63
|
+
def expire
|
64
|
+
@monitor.synchronize do
|
65
|
+
@tokens.each do |i|
|
66
|
+
next unless obj = @pool[i]
|
67
|
+
@pool[i] = nil if yield obj
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Freshen objects in inventory with the given block
|
73
|
+
def freshen
|
74
|
+
expire {|obj| yield(obj) ; return false}
|
75
|
+
end
|
76
|
+
|
77
|
+
# Remove all resources from the pool and revoke all tokens
|
78
|
+
# TODO: don't brutally/blindly revoke tokens -raise an exception or fire a callback, and address waiting threads.
|
79
|
+
def clear!
|
80
|
+
@monitor.synchronize do
|
81
|
+
@pool.each_index {|i| @tokens << i}
|
82
|
+
@tokens.uniq!
|
83
|
+
@pool.clear
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'authorize/redis'
|
2
|
+
|
3
|
+
class Authorize::Role < ActiveRecord::Base
|
4
|
+
set_table_name 'authorize_roles'
|
5
|
+
belongs_to :resource, :polymorphic => true
|
6
|
+
has_many :permissions, :class_name => "Authorize::Permission", :dependent => :delete_all
|
7
|
+
validates_uniqueness_of :name, :scope => [:resource_type, :resource_id]
|
8
|
+
validates_uniqueness_of :relation, :scope => [:resource_type, :resource_id]
|
9
|
+
after_create :create_vertex
|
10
|
+
before_destroy :destroy_vertex
|
11
|
+
|
12
|
+
named_scope :as, lambda{|relation| {:conditions => {:relation => relation}}}
|
13
|
+
named_scope :identity, {:conditions => {:relation => nil}}
|
14
|
+
|
15
|
+
GRAPH_ID = Authorize::Graph::DirectedAcyclicGraph.subordinate_key(Authorize::Role, 'graph')
|
16
|
+
VERTICES_ID_PREFIX = Authorize::Graph::DirectedAcyclicGraph.subordinate_key(Authorize::Role, 'vertices')
|
17
|
+
|
18
|
+
def self.const_missing(const)
|
19
|
+
if global_role = scoped(:conditions => {:resource_type => nil, :resource_id => nil}).find_by_relation(const.to_s)
|
20
|
+
const_set(const, global_role)
|
21
|
+
else
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.graph
|
27
|
+
@graph ||= Authorize::Graph::DirectedGraph.load(GRAPH_ID).tap do |g|
|
28
|
+
g.vertex_namespace = VERTICES_ID_PREFIX
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_vertex
|
33
|
+
self.class.graph.vertex(id)
|
34
|
+
end
|
35
|
+
|
36
|
+
def destroy_vertex
|
37
|
+
vertex.destroy
|
38
|
+
end
|
39
|
+
|
40
|
+
# Link from this role's vertex to other's vertex in the system role graph. This role becomes the parent.
|
41
|
+
def link(other)
|
42
|
+
self.class.graph.join(nil, vertex, other.vertex)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Unlink this role's vertex from other's vertex in the system role graph.
|
46
|
+
def unlink(other)
|
47
|
+
self.class.graph.disjoin(vertex, other.vertex)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates or updates the unique permission for a given resource to have the given modes
|
51
|
+
# Example: public.may(:list, :read, widget)
|
52
|
+
def may(*args)
|
53
|
+
p = permissions.for(args.pop).find_or_initialize_by_role_id(id) # need a #find_or_initialize_by_already_specified_scope
|
54
|
+
p.mask += Authorize::Permission::Mask[*args]
|
55
|
+
p.save
|
56
|
+
p.mask.complete
|
57
|
+
end
|
58
|
+
|
59
|
+
# Updates or deletes the unique permission for a given resource to not have the given modes
|
60
|
+
# Example: public.may_not(:update, widget)
|
61
|
+
def may_not(*args)
|
62
|
+
p = permissions.for(args.pop).first
|
63
|
+
return Authorize::Permission::Mask[] unless p
|
64
|
+
p.mask -= Authorize::Permission::Mask[*args].complete
|
65
|
+
p.mask.empty? ? p.destroy : p.save
|
66
|
+
p.mask.complete
|
67
|
+
end
|
68
|
+
|
69
|
+
# Test if all given modes are permitted for the given resource
|
70
|
+
def may?(*args)
|
71
|
+
return false unless p = permissions.for(args.pop).first
|
72
|
+
mask = Authorize::Permission::Mask[*args].complete
|
73
|
+
mask.subset?(p.mask)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Test if none of the given modes are permitted for the given resource
|
77
|
+
def may_not?(*args)
|
78
|
+
return true unless p = permissions.for(args.pop).first
|
79
|
+
mask = Authorize::Permission::Mask[*args].complete
|
80
|
+
(mask & p.mask).empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_s
|
84
|
+
(name || "%s") % resource rescue "!! INVALID ROLE NAME !!"
|
85
|
+
end
|
86
|
+
|
87
|
+
def vertex
|
88
|
+
raise 'Not possible to dereference vertex for an unpersisted role' unless id
|
89
|
+
@vertex ||= self.class.graph.vertex_by_name(id)
|
90
|
+
end
|
91
|
+
|
92
|
+
def roles
|
93
|
+
ids = traverser.map{|v| v.id.slice(/#{VERTICES_ID_PREFIX}::(\d+)/, 1)}
|
94
|
+
self.class.find(ids).to_set
|
95
|
+
end
|
96
|
+
|
97
|
+
def descendants
|
98
|
+
roles.delete(self)
|
99
|
+
end
|
100
|
+
|
101
|
+
def ancestors
|
102
|
+
ids = reverse_traverser.map{|v| v.id.slice(/#{VERTICES_ID_PREFIX}::(\d+)/, 1)}
|
103
|
+
ids -= [id.to_s]
|
104
|
+
self.class.find(ids).to_set
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def traverser
|
109
|
+
@traverser ||= Authorize::Graph::DirectedAcyclicGraphTraverser.traverse(vertex)
|
110
|
+
end
|
111
|
+
|
112
|
+
def reverse_traverser
|
113
|
+
@reverse_traverser ||= Authorize::Graph::DirectedAcyclicGraphReverseTraverser.traverse(vertex)
|
114
|
+
end
|
115
|
+
end
|