acts_as_joinable 1.1.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,6 +2,7 @@ class DefaultPermissionSet < ActiveRecord::Base
2
2
  include Joinable::PermissionsAttributeWrapper
3
3
 
4
4
  belongs_to :joinable, :polymorphic => true
5
+ has_many :permission_links, lambda { where("#{PermissionLink.table_name}.joinable_type = #{table_name}.joinable_type") }, :primary_key => :joinable_id, :foreign_key => :joinable_id
5
6
 
6
7
  after_update :raise_existing_member_permissions
7
8
 
@@ -15,6 +16,7 @@ class DefaultPermissionSet < ActiveRecord::Base
15
16
  end
16
17
  end
17
18
 
19
+ # Easy way to choose basic permission sets
18
20
  def access_model=(model)
19
21
  case model.to_s
20
22
  when 'open'
@@ -3,6 +3,7 @@ class Membership < ActiveRecord::Base
3
3
 
4
4
  belongs_to :joinable, :polymorphic => true
5
5
  belongs_to :user
6
+ has_many :permission_links, lambda { where("#{PermissionLink.table_name}.joinable_type = #{table_name}.joinable_type") }, :primary_key => :joinable_id, :foreign_key => :joinable_id
6
7
 
7
8
  before_save :prevent_locked_permission_changes, :normalize_owner_permissions
8
9
 
@@ -0,0 +1,3 @@
1
+ module ActsAsJoinable
2
+ VERSION = "0.0.1"
3
+ end
@@ -1,6 +1,3 @@
1
- # GEM DEPENDENCIES
2
- require 'postgres_ext'
3
-
4
1
  require 'joinable/acts_as_permissable'
5
2
  require 'joinable/acts_as_joinable'
6
3
  require 'joinable/acts_as_joinable_component'
@@ -8,20 +8,20 @@ module Joinable #:nodoc:
8
8
  # These unpacked permissions are put into an array with the singular permissions (eg. find)
9
9
  # and stored in a *permissions* class variable.
10
10
  #
11
- # In addition, The grouped permissions are stored in a separate *component_permissions_hashes* class variable.
11
+ # In addition, The grouped permissions are stored in a separate *component_permissions_hash* class variable.
12
12
  #
13
13
  # NOTE: The permissions are passed in-order because in the view we expect to find certain permission patterns.
14
14
  # eg. the simple project permission level is determined by looking for a string of permissions that span
15
15
  # several components, (labels, writeboards, files, etc...).
16
16
  # TODO: Remove the aforementioned order dependency
17
17
  def acts_as_joinable(options = {})
18
- extend ClassMethods unless (class << self; included_modules; end).include?(ClassMethods)
19
- include InstanceMethods unless included_modules.include?(InstanceMethods)
18
+ extend ClassMethods
19
+ include InstanceMethods
20
20
 
21
21
  options.assert_valid_keys :component_permissions
22
- self.component_permissions_hashes = options[:component_permissions]
22
+ self.component_permissions_hash = options[:component_permissions]
23
23
 
24
- self.permissions = [:find, :view]
24
+ self.permissions = [:find, :view]
25
25
  add_flattened_component_permissions(options[:component_permissions])
26
26
  self.permissions += [:manage, :own]
27
27
  end
@@ -34,12 +34,10 @@ module Joinable #:nodoc:
34
34
  # eg. {:labels => [:view, :apply, :remove, :create, :delete]} becomes
35
35
  # [:view_labels, :apply_labels, :remove_labels, :create_labels, :delete_labels]
36
36
  # and is added to self.permissions
37
- def add_flattened_component_permissions(component_permissions_hashes)
38
- for component_permissions_hash in component_permissions_hashes
39
- component_permissions_hash.each do |component_name, component_permissions|
40
- component_permissions.each { |component_permission| self.permissions << "#{component_permission}_#{component_name}".to_sym }
41
- end
42
- end
37
+ def add_flattened_component_permissions(component_permissions_hash)
38
+ component_permissions_hash.each do |component_name, component_permissions|
39
+ component_permissions.each { |component_permission| self.permissions << "#{component_permission}_#{component_name}".to_sym }
40
+ end
43
41
  end
44
42
  end
45
43
 
@@ -47,38 +45,28 @@ module Joinable #:nodoc:
47
45
  include Joinable::ActsAsPermissable::ClassMethods
48
46
 
49
47
  def self.extended(base)
50
- base.cattr_accessor :permissions, :component_permissions_hashes
51
-
52
- base.has_many :membership_invitations, :as => :joinable, :dependent => :destroy, :before_add => :add_initiator
53
- base.has_many :membership_requests, :as => :joinable, :dependent => :destroy
54
- base.has_many :memberships, :as => :joinable, :dependent => :destroy, :order => "id ASC", :before_remove => :add_initiator
55
-
56
- base.has_many :invitees, :class_name => "User", :through => :membership_invitations, :source => :user
57
- base.has_many :requestees, :class_name => "User", :through => :membership_requests, :source => :user
58
- base.has_many :members, :class_name => "User", :through => :memberships, :source => :user
59
-
60
- base.has_many :non_owner_memberships, :as => :joinable, :class_name => 'Membership', :conditions => "permissions NOT LIKE '%own%'"
61
- base.has_one :owner_membership, :as => :joinable, :class_name => 'Membership', :conditions => "permissions LIKE '%own%'"
62
-
63
- base.has_many :permission_links, :as => :joinable, :dependent => :destroy
48
+ base.class_eval do
49
+ cattr_accessor :permissions, :component_permissions_hash
64
50
 
65
- base.has_one :default_permission_set, :as => :joinable, :dependent => :destroy
51
+ has_many :membership_invitations, :as => :joinable, :dependent => :destroy, :before_add => :add_initiator
52
+ has_many :membership_requests, :as => :joinable, :dependent => :destroy
53
+ has_many :memberships, lambda { order :id }, :as => :joinable, :dependent => :destroy, :before_remove => :add_initiator
66
54
 
67
- base.after_create :add_owner_membership
55
+ has_many :invitees, :class_name => "User", :through => :membership_invitations, :source => :user
56
+ has_many :requestees, :class_name => "User", :through => :membership_requests, :source => :user
57
+ has_many :members, :class_name => "User", :through => :memberships, :source => :user
68
58
 
69
- base.class_eval do
70
- # Return all *joinables* that a User is a member of with the appropriate permissions
71
- scope :with_permission, lambda {|user, permission| where(with_permission_sql(user, permission)) }
59
+ has_many :permission_links, :as => :joinable, :dependent => :destroy
60
+ has_one :default_permission_set, :as => :joinable, :dependent => :destroy
61
+
62
+ scope :with_member, lambda {|user| joins(:memberships).where(:memberships => {:user_id => user}).order("memberships.created_at DESC") }
72
63
 
73
- #scope :open, lambda { where(default_permission_set_permission_exists_sql(joinable_type, joinable_id, 'find')) }
64
+ accepts_nested_attributes_for :default_permission_set
65
+ accepts_nested_attributes_for :membership_invitations, :allow_destroy => true
66
+ accepts_nested_attributes_for :memberships, :allow_destroy => true, :reject_if => proc { |attributes| attributes['locked'] == 'true' }
74
67
 
75
- # TODO: Why is this NULLS LAST? Probably because we want the results in some specific order when joined with users, but couldn't we order manually in the find?
76
- scope :with_member, lambda {|user| joins(:memberships).where(:memberships => {:user_id => (user.is_a?(User) ? user.id : user)}).order("memberships.created_at DESC NULLS LAST") }
68
+ after_create :add_owner_membership
77
69
  end
78
-
79
- base.accepts_nested_attributes_for :default_permission_set
80
- base.accepts_nested_attributes_for :membership_invitations, :allow_destroy => true
81
- base.accepts_nested_attributes_for :memberships, :allow_destroy => true, :reject_if => proc { |attributes| attributes['locked'] == 'true' }
82
70
  end
83
71
 
84
72
  def permissions_string
@@ -141,36 +129,35 @@ module Joinable #:nodoc:
141
129
  user_id = user.id
142
130
  end
143
131
 
144
- joinable_type = options[:type_column] || name
145
- joinable_id = options[:id_column] || table_name + ".id"
132
+ joinable_id = options[:id_column] || "#{table_name}.id"
146
133
 
147
134
  if permission == :find
148
- "#{membership_permission_exists_sql(user_id, joinable_type, joinable_id, 'find')} OR #{membership_invitation_permission_exists_sql(user_id, joinable_type, joinable_id, 'find')} OR #{default_permission_set_permission_exists_sql(joinable_type, joinable_id, 'find')}"
135
+ "#{membership_permission_exists_sql(user_id, joinable_id, 'find')} OR #{membership_invitation_permission_exists_sql(user_id, joinable_id, 'find')} OR #{default_permission_set_permission_exists_sql(joinable_id, 'find')}"
149
136
  elsif permission.to_s.starts_with?('view')
150
- "#{membership_permission_exists_sql(user_id, joinable_type, joinable_id, permission)} OR #{default_permission_set_permission_exists_sql(joinable_type, joinable_id, permission)}"
137
+ "#{membership_permission_exists_sql(user_id, joinable_id, permission)} OR #{default_permission_set_permission_exists_sql(joinable_id, permission)}"
151
138
  elsif permission == :join
152
- "#{membership_invitation_permission_exists_sql(user_id, joinable_type, joinable_id, 'view')} OR #{default_permission_set_permission_exists_sql(joinable_type, joinable_id, 'view')}"
139
+ "#{membership_invitation_permission_exists_sql(user_id, joinable_id, 'view')} OR #{default_permission_set_permission_exists_sql(joinable_id, 'view')}"
153
140
  elsif permission.to_s.starts_with?('join_and_')
154
- default_permission_set_permission_exists_sql(joinable_type, joinable_id, permission.to_s.gsub('join_and_', ''))
141
+ default_permission_set_permission_exists_sql(joinable_id, permission.to_s.gsub('join_and_', ''))
155
142
  elsif permission == :collaborate
156
- "EXISTS (SELECT id FROM memberships WHERE memberships.joinable_type = '#{joinable_type}' AND memberships.joinable_id = #{joinable_id} AND memberships.user_id = #{user_id} AND memberships.permissions && '{#{(collaborator_permissions - viewer_permissions).join(",")}}')"
143
+ "EXISTS (SELECT id FROM memberships WHERE memberships.joinable_type = '#{self.name}' AND memberships.joinable_id = #{joinable_id} AND memberships.user_id = #{user_id} AND memberships.permissions && '{#{(collaborator_permissions - viewer_permissions).join(",")}}')"
157
144
  else
158
- membership_permission_exists_sql(user_id, joinable_type, joinable_id, permission)
145
+ membership_permission_exists_sql(user_id, joinable_id, permission)
159
146
  end
160
147
  end
161
148
 
162
149
  private
163
150
 
164
- def membership_permission_exists_sql(user_id, joinable_type, joinable_id, permission)
165
- "EXISTS (SELECT id FROM memberships WHERE memberships.joinable_type = '#{joinable_type}' AND memberships.joinable_id = #{joinable_id} AND memberships.user_id = #{user_id} AND #{permission_sql_condition('memberships.permissions', permission)})"
151
+ def membership_permission_exists_sql(user_id, joinable_id, permission)
152
+ "EXISTS (SELECT id FROM memberships WHERE memberships.joinable_type = '#{self.name}' AND memberships.joinable_id = #{joinable_id} AND memberships.user_id = #{user_id} AND #{permission_sql_condition('memberships.permissions', permission)})"
166
153
  end
167
154
 
168
- def membership_invitation_permission_exists_sql(user_id, joinable_type, joinable_id, permission)
169
- "EXISTS (SELECT id FROM membership_invitations WHERE membership_invitations.joinable_type = '#{joinable_type}' AND membership_invitations.joinable_id = #{joinable_id} AND membership_invitations.user_id = #{user_id} AND #{permission_sql_condition('membership_invitations.permissions', permission)})"
155
+ def membership_invitation_permission_exists_sql(user_id, joinable_id, permission)
156
+ "EXISTS (SELECT id FROM membership_invitations WHERE membership_invitations.joinable_type = '#{self.name}' AND membership_invitations.joinable_id = #{joinable_id} AND membership_invitations.user_id = #{user_id} AND #{permission_sql_condition('membership_invitations.permissions', permission)})"
170
157
  end
171
158
 
172
- def default_permission_set_permission_exists_sql(joinable_type, joinable_id, permission)
173
- "EXISTS (SELECT id FROM default_permission_sets WHERE default_permission_sets.joinable_type = '#{joinable_type}' AND default_permission_sets.joinable_id = #{joinable_id} AND #{permission_sql_condition('default_permission_sets.permissions', permission)})"
159
+ def default_permission_set_permission_exists_sql(joinable_id, permission)
160
+ "EXISTS (SELECT id FROM default_permission_sets WHERE default_permission_sets.joinable_type = '#{self.name}' AND default_permission_sets.joinable_id = #{joinable_id} AND #{permission_sql_condition('default_permission_sets.permissions', permission)})"
174
161
  end
175
162
  end
176
163
 
@@ -241,28 +228,35 @@ module Joinable #:nodoc:
241
228
  # user. This method also supports caching of a membership
242
229
  # request in order to facilitate eager loading.
243
230
  #NOTE: See :membership_request_for documentation for an in depth example of this type of behaviour
244
- def membership_for(user)
231
+ def membership_for(user_id)
245
232
  if cached_membership != nil
246
233
  cached_membership
247
234
  else
248
- memberships.where(:user_id => user.id).first
235
+ memberships.where(:user_id => user_id).first
249
236
  end
250
237
  end
251
238
 
252
239
  # Find out whether this Joinable has a membership for a certain user and return true, else false
253
- def membership_for?(user)
254
- !membership_for(user).nil?
240
+ def membership_for?(user_id)
241
+ !membership_for(user_id).nil?
255
242
  end
256
243
 
257
- # Returns the timestamp of the last time the memberships were updated for this joinable
258
- def memberships_updated_at
259
- memberships.maximum(:updated_at)
244
+ # Returns a unique cache key any time the memberships were updated for this joinable
245
+ def memberships_cache_key
246
+ "#{memberships.size}_#{memberships.maximum(:updated_at).to_f}"
260
247
  end
261
248
 
262
- delegate :access_model, :to => :default_permission_set
249
+ # Convenience method for reading or writing the default permission set's access_model
250
+ delegate :access_model, 'access_model=', :to => :default_permission_set
251
+
252
+ # Convenience method for reading the default permission set's access_model
253
+ def default_permissions
254
+ self.default_permission_set.permissions
255
+ end
263
256
 
264
- def access_model=(model)
265
- default_permission_set.access_model = model
257
+ # Convenience method for writing the default permission set's access_model
258
+ def default_permissions=(permissions)
259
+ self.default_permission_set.permissions = permissions
266
260
  end
267
261
 
268
262
  # Returns true or false depending on whether or not the user has the specified permission for this object.
@@ -287,8 +281,8 @@ module Joinable #:nodoc:
287
281
 
288
282
  cache_path = "permissions/#{self.class.table_name}/#{self.id}/user_#{user.id}_#{key}"
289
283
 
290
- if defined?(RAILS_CACHE)
291
- permissions = RAILS_CACHE.read(cache_path)
284
+ if defined?(Rails.cache)
285
+ permissions = Rails.cache.read(cache_path)
292
286
  if permissions && (value = permissions[permission_name]) != nil
293
287
  return value
294
288
  end
@@ -297,18 +291,23 @@ module Joinable #:nodoc:
297
291
  # The permission isn't cached yet, so cache it
298
292
  value = self.class.with_permission(user, permission_name).exists?(self.id)
299
293
 
300
- if defined?(RAILS_CACHE)
294
+ if defined?(Rails.cache)
301
295
  if permissions
302
296
  permissions = permissions.dup
303
297
  permissions[permission_name] = value
304
298
  else
305
299
  permissions = {permission_name => value}
306
300
  end
307
- RAILS_CACHE.write(cache_path, permissions)
301
+ Rails.cache.write(cache_path, permissions)
308
302
  end
309
303
  return value
310
304
  end
311
305
 
306
+ # Ensure the joinable has a set of default permissions (an empty set unless one already exists)
307
+ def default_permission_set
308
+ super || self.build_default_permission_set
309
+ end
310
+
312
311
  private
313
312
 
314
313
  # Adds an initiator to a membership or invitation to possibly use in feed generation
@@ -318,9 +317,8 @@ module Joinable #:nodoc:
318
317
 
319
318
  # Adds an permission entry with full access to the object by the user associated with the object if one does not already exist
320
319
  def add_owner_membership
321
- Membership.create(:joinable => self, :user => user, :permissions => self.class.permissions_string) unless Membership.where(:joinable_type => self.class.to_s, :joinable_id => self.id, :user_id => user.id).exists?
320
+ Membership.where(:joinable => self, :user => user).first_or_create!(:permissions => self.class.permissions_string)
322
321
  end
323
322
  end
324
323
  end
325
- end
326
-
324
+ end
@@ -46,10 +46,6 @@ module Joinable #:nodoc:
46
46
  def self.extended(base)
47
47
  base.has_one :permission_link, :as => :component, :dependent => :destroy
48
48
  base.after_create :find_joinable_and_create_permission_link
49
-
50
- base.class_eval do
51
- scope :with_permission, lambda { |user, permission| select("#{table_name}.*").where(with_permission_sql(user, permission)) }
52
- end
53
49
  end
54
50
 
55
51
  # Returns the SQL necessary to find all components for which there is no associated joinable or
@@ -72,49 +68,50 @@ module Joinable #:nodoc:
72
68
  user_id = user.id
73
69
  end
74
70
 
75
- component_type = options[:type_column] || name
76
- component_id = options[:id_column] || table_name + ".id"
71
+ component_id_column = options[:id_column] || "#{table_name}.id"
77
72
 
78
73
  permission_without_join_and_prefix = permission.gsub('join_and_', '')
79
- comparison_permission = permission_without_join_and_prefix == 'view' ? "permission_links.component_view_permission" : "'#{permission_without_join_and_prefix}'"
74
+ permission_or_column = permission_without_join_and_prefix == 'view' ? "permission_links.component_view_permission" : "'#{permission_without_join_and_prefix}'"
80
75
 
81
76
  if permission.starts_with?('view')
82
- "#{no_inherited_permissions_exist_sql(component_type, component_id)} OR #{membership_permission_exists_sql(user_id, component_type, component_id, comparison_permission)} OR #{default_permission_set_permission_exists_sql(component_type, component_id, comparison_permission)}"
77
+ "#{no_inherited_permissions_exist_sql(component_id_column)} OR #{membership_permission_exists_sql(user_id, component_id_column, permission_or_column)} OR #{default_permission_set_permission_exists_sql(component_id_column, permission_or_column)}"
83
78
  elsif permission.starts_with?('join_and_')
84
- default_permission_set_permission_exists_sql(component_type, component_id, comparison_permission)
79
+ default_permission_set_permission_exists_sql(component_id_column, permission_or_column)
85
80
  else
86
- "#{no_inherited_permissions_exist_sql(component_type, component_id)} OR #{membership_permission_exists_sql(user_id, component_type, component_id, comparison_permission)}"
81
+ "#{no_inherited_permissions_exist_sql(component_id_column)} OR #{membership_permission_exists_sql(user_id, component_id_column, permission_or_column)}"
87
82
  end
88
83
  end
89
84
 
90
85
  private
91
86
 
92
87
  # All components that don't have a permission link to a joinable
93
- def no_inherited_permissions_exist_sql(component_type, component_id)
94
- "NOT EXISTS (SELECT * FROM permission_links WHERE permission_links.component_type = '#{component_type}' AND permission_links.component_id = #{component_id})"
88
+ def no_inherited_permissions_exist_sql(component_id_column)
89
+ permission_links_for_joinable = PermissionLink.where(:component_type => self.name).where("permission_links.component_id = #{component_id_column}")
90
+
91
+ return "NOT EXISTS (#{ permission_links_for_joinable.to_sql })"
95
92
  end
96
93
 
97
94
  # All components that have an associated membership with a specific permission
98
95
  #
99
96
  # The view permission requires special handling because it may be customized in the permission_link.
100
97
  # For more information see the *recurse_to_inherit_custom_view_permission* method.
101
- def membership_permission_exists_sql(user_id, component_type, component_id, comparison_permission)
102
- "EXISTS (SELECT * FROM memberships
103
- INNER JOIN permission_links ON memberships.joinable_type = permission_links.joinable_type
104
- AND memberships.joinable_id = permission_links.joinable_id
105
- WHERE permission_links.component_type = '#{component_type}'
106
- AND permission_links.component_id = #{component_id}
107
- AND memberships.user_id = #{user_id}
108
- AND #{comparison_permission} = ANY(memberships.permissions))"
98
+ def membership_permission_exists_sql(user_id, component_id_column, permission_or_column)
99
+ memberships_allowing_permission = Membership.joins(:permission_links)
100
+ .where(:user_id => user_id)
101
+ .where("permission_links.component_type" => self.name)
102
+ .where("permission_links.component_id = #{component_id_column}")
103
+ .where(permission_sql_condition('memberships.permissions', permission_or_column, :raw => true))
104
+
105
+ return "EXISTS (#{ memberships_allowing_permission.to_sql })"
109
106
  end
110
107
 
111
- def default_permission_set_permission_exists_sql(component_type, component_id, comparison_permission)
112
- "EXISTS (SELECT * FROM default_permission_sets
113
- INNER JOIN permission_links ON default_permission_sets.joinable_type = permission_links.joinable_type
114
- AND default_permission_sets.joinable_id = permission_links.joinable_id
115
- WHERE permission_links.component_type = '#{component_type}'
116
- AND permission_links.component_id = #{component_id}
117
- AND #{comparison_permission} = ANY(default_permission_sets.permissions))"
108
+ def default_permission_set_permission_exists_sql(component_id_column, permission_or_column)
109
+ sets_allowing_permission = DefaultPermissionSet.joins(:permission_links)
110
+ .where("permission_links.component_type" => self.name)
111
+ .where("permission_links.component_id = #{component_id_column}")
112
+ .where(permission_sql_condition('default_permission_sets.permissions', permission_or_column, :raw => true))
113
+
114
+ return "EXISTS (#{ sets_allowing_permission.to_sql })"
118
115
  end
119
116
  end
120
117
 
@@ -130,11 +127,9 @@ module Joinable #:nodoc:
130
127
  # Useful for outputting information to the user while they
131
128
  # are creating a new component.
132
129
  def who_will_be_able_to_view?
133
- User.find_by_sql("SELECT users.*
134
- FROM users JOIN memberships ON users.id = memberships.user_id
135
- WHERE memberships.joinable_type = '#{joinable.class.to_s}'
136
- AND memberships.joinable_id = #{joinable.id}
137
- AND #{self.class.permission_sql_condition('memberships.permissions', recurse_to_inherit_custom_view_permission)}")
130
+ User.joins(:memberships)
131
+ .where(:memberships => {:joinable => joinable})
132
+ .where(permission_sql_condition('memberships.permissions', recurse_to_inherit_custom_view_permission))
138
133
  end
139
134
 
140
135
  def check_permission(user, permission)
@@ -196,6 +191,7 @@ module Joinable #:nodoc:
196
191
  end
197
192
  attr_writer :view_permission
198
193
 
194
+ private
199
195
 
200
196
  # Recurse up the tree to see if any of the intervening joinable_components have a customized view permission
201
197
  # In that case, inherit that customized view permission. This allows searches of the form
@@ -208,7 +204,7 @@ module Joinable #:nodoc:
208
204
  if parent.acts_like?(:joinable) || self.view_permission
209
205
  return self.view_permission || :view
210
206
  elsif parent.acts_like?(:joinable_component)
211
- return parent.recurse_to_inherit_custom_view_permission
207
+ return parent.send(:recurse_to_inherit_custom_view_permission)
212
208
  else
213
209
  return nil
214
210
  end
@@ -10,9 +10,22 @@ module Joinable #:nodoc:
10
10
  return record
11
11
  end
12
12
 
13
- def permission_sql_condition(column, permission)
14
- "'#{permission}' = ANY(#{column})"
15
- end
13
+ # Returns all records where the given user has the given permission
14
+ def with_permission(user, permission)
15
+ where(with_permission_sql(user, permission))
16
+ end
17
+
18
+ # Returns an SQL fragment for a WHERE condition that evaluates to true if the user has the given permission
19
+ # For use when asking
20
+ def with_permission_sql(user, permission, options = {})
21
+ raise NotImplementedError
22
+ end
23
+
24
+ # Returns an SQL fragment for a WHERE condition that checks the given column for the given permission
25
+ def permission_sql_condition(column, permission, options = {})
26
+ permission = "'#{permission}'" unless options[:raw]
27
+ "#{permission} = ANY(#{column})"
28
+ end
16
29
  end
17
30
 
18
31
  module InstanceMethods
@@ -22,8 +35,10 @@ module Joinable #:nodoc:
22
35
 
23
36
  # Returns a list of users who either do or do not have the specified permission.
24
37
  def who_can?(permission)
25
- User.find_by_sql("SELECT * FROM users AS u1 WHERE #{self.class.with_permission_sql('u1.id', permission, :id_column => id)}")
38
+ User.where(with_permission_sql("#{User.table_name}.id", permission, :id_column => id))
26
39
  end
40
+
41
+ delegate :with_permission_sql, :permission_sql_condition, :to => 'self.class'
27
42
  end
28
43
  end
29
44
  end
@@ -2,12 +2,14 @@ module FeedableExtensions
2
2
  def self.add
3
3
  Feed.class_eval do
4
4
  # Filter feeds about public joinables that you haven't joined, unless the feed is actually about you
5
- scope :without_unjoined, lambda {|joinable_type, user|
5
+ scope :not_from_unjoined, lambda {|joinable_type, user|
6
6
  where("feeds.scoping_object_type IS NULL OR
7
7
  feeds.scoping_object_type != '#{joinable_type}' OR
8
8
  (feeds.feedable_type = 'User' AND feeds.feedable_id = #{user.id}) OR
9
9
  EXISTS (SELECT * FROM memberships WHERE memberships.joinable_type = '#{joinable_type}' AND memberships.joinable_id = feeds.scoping_object_id AND memberships.user_id = ?)", user.id)
10
10
  }
11
+ # Filter feeds about public things where the given action took place
12
+ scope :not_unscoped, lambda {|action| where('NOT (feeds.scoping_object_type IS NULL AND feeds.action = ?)', action) }
11
13
 
12
14
  acts_as_joinable_component :parent => 'permission_inheritance_target', :polymorphic => true, :view_permission => lambda {|feed| :find if feed.feedable.acts_like?(:joinable) }
13
15
 
@@ -14,13 +14,13 @@ module Joinable #:nodoc:
14
14
 
15
15
  # Returns an array of the permissions as symbols
16
16
  def permissions
17
- self[:permissions].collect(&:to_sym)
17
+ Array.wrap(self[:permissions]).collect(&:to_sym)
18
18
  end
19
19
 
20
20
  def permissions=(permissions)
21
21
  case permissions
22
- when String
23
- self[:permissions] = permissions.split(' ')
22
+ when String, Symbol
23
+ self[:permissions] = permissions.to_s.split(' ')
24
24
  when Array
25
25
  self[:permissions] = permissions
26
26
  else
@@ -98,6 +98,31 @@ module Joinable #:nodoc:
98
98
  self.permissions += permissions_to_grant
99
99
  end
100
100
 
101
+ # Adds readers for component permission groups and single permissions
102
+ #
103
+ # Used by advanced permission forms to determine how which options to select
104
+ # in the various fields. (eg. which option of f.select :labels_permissions to choose)
105
+ def method_missing(method_name, *args, &block)
106
+ # NOTE: As of Rails 4, respond_to? must be checked inside the case statement otherwise it breaks regular AR attribute accessors
107
+ case method_name.to_s
108
+ when /.+_permissions/
109
+ return component_permissions_reader(method_name) if respond_to?(:joinable_type) && joinable_type.present?
110
+ when /.+_permission/
111
+ return single_permission_reader(method_name) if respond_to?(:joinable_type) && joinable_type.present?
112
+ end
113
+
114
+ super
115
+ end
116
+
117
+ def respond_to?(method_name, include_private = false)
118
+ case method_name.to_s
119
+ when /.+_permissions?/
120
+ return true if respond_to?(:joinable_type) && joinable_type.present?
121
+ end
122
+
123
+ super
124
+ end
125
+
101
126
  private
102
127
 
103
128
  # Verifies that all the access levels are valid for the attached permissible
@@ -112,37 +137,12 @@ module Joinable #:nodoc:
112
137
  self.permissions = permissions.uniq.sort_by { |permission| allowed_permissions.index(permission) }
113
138
  end
114
139
 
115
- # Adds readers for component permission groups and single permissions
116
- #
117
- # Used by advanced permission forms to determine how which options to select
118
- # in the various fields. (eg. which option of f.select :labels_permissions to choose)
119
- def method_missing(method_name, *args)
120
- # add permission_for accessors and mutators
121
-
122
- # NOTE: Don't mess with the method_name variable (e.g. change it to a string)
123
- # since upstream methods might assume it is a symbol.
124
- # NOTE: Ensure we enforce some characters before the '_permission' suffix because Rails 3 creates
125
- if respond_to?(:joinable_type) && joinable_type.present?
126
- if method_name.to_s =~ /.+_permissions/
127
- return component_permissions_reader(method_name)
128
- elsif method_name.to_s =~ /.+_permission/
129
- return single_permission_reader(method_name)
130
- else
131
- super
132
- end
133
- else
134
- super
135
- end
136
- end
137
-
138
140
  # Get a string of all of the permissions the object has for a specific joinable component
139
141
  # eg. labels_permissions # returns 'view_labels apply_labels remove_labels'
140
142
  def component_permissions_reader(method_name)
141
- for component_permissions_hash in joinable_type.constantize.component_permissions_hashes
142
- component_permissions_hash.each do |component_name, component_permissions|
143
- if method_name.to_s == "#{component_name}_permissions"
144
- return component_permissions.collect {|permission| "#{permission}_#{component_name}"}.select {|permission| has_permission?(permission)}.join(" ")
145
- end
143
+ joinable_type.constantize.component_permissions_hash.each do |component_name, component_permissions|
144
+ if method_name.to_s == "#{component_name}_permissions"
145
+ return component_permissions.collect {|permission| "#{permission}_#{component_name}"}.select {|permission| has_permission?(permission)}.join(" ")
146
146
  end
147
147
  end
148
148
 
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :acts_as_joinable do
3
+ # # Task goes here
4
+ # end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_joinable
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,16 +10,32 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-04-30 00:00:00.000000000 Z
13
+ date: 2013-08-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
- name: postgres_ext
16
+ name: pg
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - '>='
29
+ - !ruby/object:Gem::Version
30
+ version: '0'
31
+ - !ruby/object:Gem::Dependency
32
+ name: rails
17
33
  requirement: !ruby/object:Gem::Requirement
18
34
  none: false
19
35
  requirements:
20
36
  - - ~>
21
37
  - !ruby/object:Gem::Version
22
- version: 0.2.2
38
+ version: '4.0'
23
39
  type: :runtime
24
40
  prerelease: false
25
41
  version_requirements: !ruby/object:Gem::Requirement
@@ -27,7 +43,23 @@ dependencies:
27
43
  requirements:
28
44
  - - ~>
29
45
  - !ruby/object:Gem::Version
30
- version: 0.2.2
46
+ version: '4.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: sqlite3
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
31
63
  description: Adds access control to objects by giving them members, each with configurable
32
64
  permissions.
33
65
  email: technical@rrnpilot.org
@@ -40,6 +72,7 @@ files:
40
72
  - app/models/membership_invitation.rb
41
73
  - app/models/membership_request.rb
42
74
  - app/models/permission_link.rb
75
+ - lib/acts_as_joinable/version.rb
43
76
  - lib/acts_as_joinable.rb
44
77
  - lib/joinable/acts_as_joinable.rb
45
78
  - lib/joinable/acts_as_joinable_component.rb
@@ -47,6 +80,7 @@ files:
47
80
  - lib/joinable/acts_as_permissable.rb
48
81
  - lib/joinable/feedable_extensions.rb
49
82
  - lib/joinable/permissions_attribute_wrapper.rb
83
+ - lib/tasks/acts_as_joinable_tasks.rake
50
84
  - README.rdoc
51
85
  homepage: http://github.com/rrn/acts_as_joinable
52
86
  licenses: []
@@ -57,13 +91,13 @@ require_paths:
57
91
  required_ruby_version: !ruby/object:Gem::Requirement
58
92
  none: false
59
93
  requirements:
60
- - - ! '>='
94
+ - - '>='
61
95
  - !ruby/object:Gem::Version
62
96
  version: '0'
63
97
  required_rubygems_version: !ruby/object:Gem::Requirement
64
98
  none: false
65
99
  requirements:
66
- - - ! '>='
100
+ - - '>='
67
101
  - !ruby/object:Gem::Version
68
102
  version: '0'
69
103
  requirements: []