permissable 0.1.0 → 0.2.0

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/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