permissable 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -4,10 +4,7 @@ Permissable creates the ability to add a permissions system to "resources" based
4
4
  It allows you to define a member using the +permissable+ method:
5
5
 
6
6
  class User < ActiveRecord::Base
7
- permissable do |configure|
8
- configure.permission_for :read, :posts, :categories
9
- configure.permission_for [:read, :write, :moderate], :comments
10
- end
7
+ has_permissions_for [:section, :category], :to => [:read, :write, :moderate]
11
8
  end
12
9
 
13
10
  Permissable uses a single "permissions" table (and subsuquently a Permission model), with a polymorphic relationship
@@ -24,18 +21,47 @@ This documention is incomplete... I'll add more later.
24
21
  == Usage
25
22
 
26
23
  class User < ActiveRecord::Base
27
- permissable do |configure|
28
- configure.permission_for :read, :posts, :categories
29
- configure.permission_for [:read, :write, :moderate], :comments
30
- end
24
+ has_permissions_for [:section, :category], :to => [:read, :write, :moderate]
31
25
  end
32
26
 
33
- The permissable method accepts a block containing permission declarations.
34
- Each call to permission_for accepts two attributes.
27
+ The permissable method accepts two options:
35
28
 
36
29
  * method: The permission you want to assign (read, write, moderate, etc)
37
- * resources: A list of resources to assign those permissions to
30
+ * options: A hash of options.
31
+
32
+ The only required option is the :to key, which defines a number of permisssions to assign to the speficied resource(s)
33
+
34
+ == Setting Permissions via Association
35
+
36
+ Permissable also allows you to specify permissions via an association. For instance say your User has_many roles, and you would like the
37
+ permissions to be based on a particular user's roles. To do this, add the :through option when setting permissions:
38
+
39
+ :through => :roles
40
+
41
+ When permissable does permission lookups on any of the resources specified in has_permissions_for it will use the assocation to define them.
42
+ Instead of looking up User.permissions, it will now lookup user.roles.permissions.
43
+
44
+ == Always allowing specific members
45
+
46
+ If you would like can? to always pass for a particular member, use the :allow_with option with has_permissions_for. The allow_with option accepts a method name within your
47
+ member model that will be called prior to checking permissions from the database.
48
+
49
+ class User < ActiveRecord::Base
50
+ has_permissions_for [:section, :category], :to => [:read, :write, :moderate], :allow_with => :is_admin?
51
+
52
+ def is_admin?
53
+ // your logic here. If this resolves to true... can? will also be true.
54
+ end
55
+ end
56
+
57
+ == Planned Updates
58
+
59
+ In the near future there are plans to add the ability to "eager_load" permissions on a user the first time it is created. When using this configuration
60
+ option, ALL permissions relating to that user are looked up in a single database query, and then cached to the user instance. By default I think this may be the proper way to handle it anyway.
38
61
 
62
+ We are also planning to implement permission "chains". Basically this will define a series of "levels" to your permission. For instance a user
63
+ who can "write" a model, would also be able to "read" a model. You would be able to tell Permissable which methods chain from each other and
64
+ the order they should rely on. Think about how a statemachine transitions from one state to another in a way....
39
65
 
40
66
  == Note on Patches/Pull Requests
41
67
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -5,27 +5,61 @@ module Permissable
5
5
  def self.included(base)
6
6
  base.send :include, InstanceMethods
7
7
  base.send :attr_protected, :member_identifier
8
+ base.send :attr_protected, :permissable_lookups
8
9
  end
9
10
 
10
11
  # This module includes methods that should exist on ALL members.
11
12
  module InstanceMethods
12
13
 
13
14
  # The can? method returns a boolen value specifying whether or not this member can perform the specific method on resource
14
- def can?(method, resource)
15
- permissions_for(resource, method).exists?
15
+ def can?(methods, resource)
16
+ unless allow_permission_with_method.nil?
17
+ if self.respond_to? "#{allow_permission_with_method}"
18
+ return true if (send "#{allow_permission_with_method}")
19
+ end
20
+ end
21
+ methods = [methods].flatten.collect{ |m| m.to_sym }
22
+ methods = find_methods_from_chain(methods)
23
+ permissions_for(resource, methods).exists?
16
24
  end
17
25
 
18
26
  # Alias to can? to get the inverse.
19
27
  def cannot?(method, resource); !can?(method, resource); end
20
28
 
29
+ # Assign permissions to a member if they don't already exist.
30
+ # TODO: There has to be a friendlier way to mass assign permissions to roles rather than
31
+ # looping each one for existence and then saving separately.
32
+ def can!(methods, resources)
33
+ [resources].flatten.each do |resource|
34
+
35
+ # Kind of unecessary but since some methods allow you to specify a Classname directly, this just
36
+ # safegaurds against trying to do the same here.
37
+ next if resource.is_a?(Class)
38
+
39
+ # Get the member identifier for this resource
40
+ identifier = member_identifier(resource)
41
+
42
+ [methods].flatten.each do |method|
43
+ # Permission already exists, continue.
44
+ next if can?(method, resource)
45
+
46
+ # Create a new permission for each member (once if its self, multiple times if its associated)
47
+ [identifier[:member_id]].flatten.each do |member_id|
48
+ perm = Permission.new(:member_id => member_id, :member_type => identifier[:member_type], :permission_type => method.to_s.downcase)
49
+ perm.resource = resource
50
+ perm.save
51
+ end
52
+ end
53
+ end
54
+
55
+ end
21
56
 
22
57
  # This sets the member information for our permission lookup based on the current resource scope.
23
58
  # These attributes correspond to the correct member_id and member_type in our permissions table.
24
- def member_identifier(scope)
59
+ def member_identifier(resource)
25
60
 
26
61
  @member_identifier ||= {}
27
- # The scope should be the classname of a resource we are getting identifiers for
28
- scope = scope.to_s.classify
62
+ scope = fetch_scope(resource)
29
63
 
30
64
  return @member_identifier[scope] unless @member_identifier[scope].nil?
31
65
  return { :member_id => self.id, :member_type => self.class.to_s } unless permissable_associations.has_key?(scope)
@@ -39,18 +73,51 @@ module Permissable
39
73
 
40
74
  # Provide an instance method to our associations
41
75
  def permissable_associations; self.class.permissable_associations; end
76
+ # Find our permission override if available
77
+ def allow_permission_with_method; self.class.permissable_options[:allow_permission_with_method]; end
78
+ # See if there is a permissions chain
79
+ def permission_chain; self.class.permissable_options[:permission_chain] || {}; end
42
80
 
43
81
  private
44
82
 
83
+ def find_methods_from_chain(methods)
84
+
85
+ return methods if permission_chain.empty?
86
+ allowed_methods = []
87
+
88
+ methods.each do |method|
89
+ permission_chain.each_pair do |key, value|
90
+ value = [value].flatten.collect{ |v| v.to_sym }
91
+ allowed_methods << key.to_sym if value.include?(method)
92
+ end
93
+ end
94
+
95
+ allowed_methods << methods
96
+ allowed_methods.flatten.uniq
97
+
98
+ end
99
+
45
100
  # Looks up permissions for a particular resource.
46
101
  def permissions_for(resource, methods = nil)
47
- scope = resource.class.to_s.classify
48
- return self.permissions unless permissable_associations.has_key?(scope)
102
+ scope = fetch_scope(resource)
103
+ return self.permissions.with_permission_to(methods) unless permissable_associations.has_key?(scope)
49
104
  relation = Permission.where(member_identifier(scope)).for_resource(resource)
50
105
  relation = relation.with_permission_to(methods) unless methods.nil?
51
106
  relation
52
107
  end
53
108
 
109
+ # Returns the member responsible for this resource. This can either be an instance of self, or an instance or
110
+ # array of assocated instances.
111
+ def permissable_member(resource)
112
+ scope = fetch_scope(resource)
113
+ (permissable_associations.has_key?(scope)) ? send("#{permissable_associations[scope]}".to_sym) : self
114
+ end
115
+
116
+ def fetch_scope(resource)
117
+ return resource if resource.is_a?(String)
118
+ (resource.is_a?(Class)) ? resource.to_s : resource.class.to_s.classify
119
+ end
120
+
54
121
  end
55
122
 
56
123
  end
@@ -1,10 +1,7 @@
1
1
  module Permissable
2
2
 
3
3
  module Resource
4
-
5
- def self.included(base)
6
- end
7
-
4
+
8
5
  end
9
6
 
10
7
  end
data/lib/permissable.rb CHANGED
@@ -56,6 +56,7 @@ module Permissable
56
56
  raise Permissable::PermissionNotDefined, "has_permissions_for missing the :to option." unless options.has_key?(:to) and !options[:to].empty?
57
57
 
58
58
  write_inheritable_attribute(:permissable_associations, {}) if permissable_associations.nil?
59
+ write_inheritable_attribute(:permissable_options, {}) if permissable_options.nil?
59
60
  resources = [resources].flatten
60
61
 
61
62
  resources.each do |resource|
@@ -69,33 +70,47 @@ module Permissable
69
70
  assoc = options[:through].to_s.classify.constantize
70
71
 
71
72
  # Our association also creates a has_many association on our permissions table.
72
- assoc.class_eval do
73
+ assoc.class_eval do
73
74
  has_many(:permissions, :as => :member, :conditions => { :member_type => "#{self.to_s}" }) unless respond_to? :permissions
75
+ include Permissable::Member
76
+ class_inheritable_accessor :permission_types
77
+ write_inheritable_attribute(:permissable_associations, {})
78
+ write_inheritable_attribute(:permissable_options, {}) if permissable_options.nil?
79
+ permissable_options[:allow_permission_with_method] = options[:allow_with] if options.has_key?(:allow_with)
80
+ permissable_options[:permission_chain] = options[:chain] if options.has_key?(:chain)
81
+ self.send :permission_types=, options[:to]
74
82
  end
75
-
83
+
76
84
  end
77
-
85
+
78
86
  # Setup a has_many association of permissions on our resource.
79
- resource.constantize.class_eval do
80
- include Permissable::Resource
87
+ resource.constantize.class_eval do
81
88
  has_many(:permissions, :as => :resource, :conditions => { :resource_type => "#{self.to_s}" }) unless respond_to? :permissions
82
89
  end
83
90
 
91
+ resource.constantize.instance_eval{ include Permissable::Resource }
92
+
84
93
  end
85
94
 
95
+ permissable_options[:allow_permission_with_method] = options[:allow_with] if options.has_key?(:allow_with)
96
+ permissable_options[:permission_chain] = options[:chain] if options.has_key?(:chain)
97
+
86
98
  # This class becomes a member to resources.
87
99
  include Permissable::Member
100
+ class_inheritable_accessor :permission_types
101
+ self.send :permission_types=, options[:to]
88
102
 
89
103
  # Members create a has_many association on permissions as a member.
90
104
  has_many(:permissions, :as => :member, :conditions => { :member_type => "#{self.to_s}" }) unless respond_to? :permissions
91
105
 
92
106
  end
93
107
 
108
+ # Access options such as our method override, or our permission chain.
109
+ def permissable_options; read_inheritable_attribute(:permissable_options); end
110
+
94
111
  # Each time has_permissions_for is called, different associations may exist.
95
- # This provides a way to store and update them all as necessary.
96
- def permissable_associations
97
- read_inheritable_attribute(:permissable_associations)
98
- end
112
+ # This provides a way to store and update them all as necessary.
113
+ def permissable_associations; read_inheritable_attribute(:permissable_associations); end
99
114
 
100
115
  end
101
116
  end
data/permissable.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{permissable}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Brent Kirby"]
12
- s.date = %q{2010-07-26}
12
+ s.date = %q{2010-09-04}
13
13
  s.description = %q{Permissable lets you set a number of 'permissions' to your database models. By assigning 'members' to 'resources' (models) you can specify various permission states.}
14
14
  s.email = %q{brent@kurbmedia.com}
15
15
  s.extra_rdoc_files = [
@@ -28,7 +28,6 @@ Gem::Specification.new do |s|
28
28
  "lib/permissable/member.rb",
29
29
  "lib/permissable/permission.rb",
30
30
  "lib/permissable/resource.rb",
31
- "permissable-0.0.3.gem",
32
31
  "permissable.gemspec",
33
32
  "test/helper.rb",
34
33
  "test/test_permissable.rb"
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: permissable
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
8
+ - 2
9
9
  - 0
10
- version: 0.1.0
10
+ version: 0.2.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Brent Kirby
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-07-26 00:00:00 -04:00
18
+ date: 2010-09-04 00:00:00 -04:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -53,7 +53,6 @@ files:
53
53
  - lib/permissable/member.rb
54
54
  - lib/permissable/permission.rb
55
55
  - lib/permissable/resource.rb
56
- - permissable-0.0.3.gem
57
56
  - permissable.gemspec
58
57
  - test/helper.rb
59
58
  - test/test_permissable.rb
Binary file