acts_as_joinable 1.1.0 → 1.3.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.
@@ -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: []