groupify 0.6.0.rc1 → 0.6.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,105 @@
1
+ module Groupify
2
+ module ActiveRecord
3
+
4
+ # Usage:
5
+ # class User < ActiveRecord::Base
6
+ # acts_as_named_group_member
7
+ # ...
8
+ # end
9
+ #
10
+ # user.named_groups << :admin
11
+ #
12
+ module NamedGroupMember
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ unless respond_to?(:group_memberships)
17
+ has_many :group_memberships, :as => :member, :autosave => true, :dependent => :destroy
18
+ end
19
+ end
20
+
21
+ def named_groups
22
+ @named_groups ||= NamedGroupCollection.new(self)
23
+ end
24
+
25
+ def named_groups=(groups)
26
+ groups.each do |group|
27
+ self.named_groups << group
28
+ end
29
+ end
30
+
31
+ def in_named_group?(named_group, opts={})
32
+ named_groups.include?(named_group, opts)
33
+ end
34
+
35
+ def in_any_named_group?(*args)
36
+ opts = args.extract_options!
37
+ named_groups = args.flatten
38
+ named_groups.each do |named_group|
39
+ return true if in_named_group?(named_group, opts)
40
+ end
41
+ return false
42
+ end
43
+
44
+ def in_all_named_groups?(*args)
45
+ opts = args.extract_options!
46
+ named_groups = args.flatten.to_set
47
+ named_groups.subset? self.named_groups.as(opts[:as]).to_set
48
+ end
49
+
50
+ def in_only_named_groups?(*args)
51
+ opts = args.extract_options!
52
+ named_groups = args.flatten.to_set
53
+ named_groups == self.named_groups.as(opts[:as]).to_set
54
+ end
55
+
56
+ def shares_any_named_group?(other, opts={})
57
+ in_any_named_group?(other.named_groups.to_a, opts)
58
+ end
59
+
60
+ module ClassMethods
61
+ def as(membership_type)
62
+ joins(:group_memberships).where(group_memberships: {membership_type: membership_type})
63
+ end
64
+
65
+ def in_named_group(named_group)
66
+ return none unless named_group.present?
67
+
68
+ joins(:group_memberships).where(group_memberships: {group_name: named_group}).uniq
69
+ end
70
+
71
+ def in_any_named_group(*named_groups)
72
+ named_groups.flatten!
73
+ return none unless named_groups.present?
74
+
75
+ joins(:group_memberships).where(group_memberships: {group_name: named_groups.flatten}).uniq
76
+ end
77
+
78
+ def in_all_named_groups(*named_groups)
79
+ named_groups.flatten!
80
+ return none unless named_groups.present?
81
+
82
+ joins(:group_memberships).
83
+ group(:"group_memberships.member_id").
84
+ where(:group_memberships => {:group_name => named_groups}).
85
+ having("COUNT(DISTINCT group_memberships.group_name) = #{named_groups.count}").
86
+ uniq
87
+ end
88
+
89
+ def in_only_named_groups(*named_groups)
90
+ named_groups.flatten!
91
+ return none unless named_groups.present?
92
+
93
+ joins(:group_memberships).
94
+ group("group_memberships.member_id").
95
+ having("COUNT(DISTINCT group_memberships.group_name) = #{named_groups.count}").
96
+ uniq
97
+ end
98
+
99
+ def shares_any_named_group(other)
100
+ in_any_named_group(other.named_groups.to_a)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -1,509 +1,16 @@
1
1
  require 'mongoid'
2
2
  require 'set'
3
3
 
4
- # Groups and members
5
4
  module Groupify
6
5
  module Mongoid
7
- module Adapter
8
- extend ActiveSupport::Concern
9
-
10
- included do
11
- def none; where(:id => nil); end
12
- end
13
-
14
- module ClassMethods
15
- def acts_as_group(opts = {})
16
- include Groupify::Mongoid::Group
17
-
18
- if (member_klass = opts.delete :default_members)
19
- self.default_member_class = member_klass.to_s.classify.constantize
20
- end
21
-
22
- if (member_klasses = opts.delete :members)
23
- member_klasses.each do |member_klass|
24
- has_members(member_klass)
25
- end
26
- end
27
- end
28
-
29
- def acts_as_group_member(opts = {})
30
- @group_class_name = opts[:class_name] || 'Group'
31
- include Groupify::Mongoid::GroupMember
32
- end
33
-
34
- def acts_as_named_group_member(opts = {})
35
- include Groupify::Mongoid::NamedGroupMember
36
- end
37
- end
38
- end
6
+ require 'groupify/adapter/mongoid/model'
39
7
 
40
- # Usage:
41
- # class Group
42
- # include Mongoid::Document
43
- #
44
- # acts_as_group, :members => [:users]
45
- # ...
46
- # end
47
- #
48
- # group.add(member)
49
- #
50
- module Group
51
- extend ActiveSupport::Concern
52
-
53
- included do
54
- @default_member_class = nil
55
- @member_klasses ||= Set.new
56
- end
57
-
58
- # def members
59
- # self.class.default_member_class.any_in(:group_ids => [self.id])
60
- # end
61
-
62
- def member_classes
63
- self.class.member_classes
64
- end
65
-
66
- def add(*args)
67
- opts = args.extract_options!
68
- membership_type = opts[:as]
69
- members = args.flatten
70
- return unless members.present?
71
-
72
- members.each do |member|
73
- member.groups << self
74
- membership = member.group_memberships.find_or_initialize_by(as: membership_type)
75
- membership.groups << self
76
- membership.save!
77
- end
78
- end
79
-
80
- # Merge a source group into this group.
81
- def merge!(source)
82
- self.class.merge!(source, self)
83
- end
84
-
85
- module ClassMethods
86
- def with_member(member)
87
- member.groups
88
- end
89
-
90
- def default_member_class
91
- @default_member_class ||= User rescue false
92
- end
93
-
94
- def default_member_class=(klass)
95
- @default_member_class = klass
96
- end
97
-
98
- # Returns the member classes defined for this class, as well as for the super classes
99
- def member_classes
100
- (@member_klasses ||= Set.new).merge(superclass.method_defined?(:member_classes) ? superclass.member_classes : [])
101
- end
102
-
103
- # Define which classes are members of this group
104
- def has_members(name)
105
- klass = name.to_s.classify.constantize
106
- register(klass)
107
- end
108
-
109
- # Merge two groups. The members of the source become members of the destination, and the source is destroyed.
110
- def merge!(source_group, destination_group)
111
- # Ensure that all the members of the source can be members of the destination
112
- invalid_member_classes = (source_group.member_classes - destination_group.member_classes)
113
- invalid_member_classes.each do |klass|
114
- if klass.any_in(:group_ids => [source_group.id]).count > 0
115
- raise ArgumentError.new("#{source_group.class} has members that cannot belong to #{destination_group.class}")
116
- end
117
- end
118
-
119
- source_group.member_classes.each do |klass|
120
- klass.any_in(:group_ids => [source_group.id]).update_all(:$set => {:"group_ids.$" => destination_group.id})
121
- end
122
-
123
- source_group.delete
124
- end
125
-
126
- protected
127
-
128
- def register(member_klass)
129
- (@member_klasses ||= Set.new) << member_klass
130
- associate_member_class(member_klass)
131
- member_klass
132
- end
133
-
134
- module MemberAssociationExtensions
135
- def as(membership_type)
136
- return self unless membership_type
137
- where(:group_memberships.elem_match => { as: membership_type.to_s, group_ids: [base.id] })
138
- end
139
-
140
- def destroy(*args)
141
- delete(*args)
142
- end
143
-
144
- def delete(*args)
145
- opts = args.extract_options!
146
- members = args
147
-
148
- if opts[:as]
149
- members.each do |member|
150
- member.group_memberships.as(opts[:as]).first.groups.delete(base)
151
- end
152
- else
153
- members.each do |member|
154
- member.group_memberships.in(groups: base).each do |membership|
155
- membership.groups.delete(base)
156
- end
157
- end
158
-
159
- super(*members)
160
- end
161
- end
162
- end
163
-
164
- def associate_member_class(member_klass)
165
- association_name = member_klass.name.to_s.pluralize.underscore.to_sym
166
-
167
- has_many association_name, class_name: member_klass.to_s, dependent: :nullify, foreign_key: 'group_ids', extend: MemberAssociationExtensions
168
-
169
- if member_klass == default_member_class
170
- has_many :members, class_name: member_klass.to_s, dependent: :nullify, foreign_key: 'group_ids', extend: MemberAssociationExtensions
171
- end
172
- end
173
- end
174
- end
175
-
176
- module MemberScopedAs
177
- extend ActiveSupport::Concern
178
-
179
- module ClassMethods
180
- def as(membership_type)
181
- group_ids = criteria.selector["group_ids"]
182
- named_groups = criteria.selector["named_groups"]
183
- criteria = self.criteria
184
-
185
- # If filtering by groups or named groups, merge into the group membership criteria
186
- if group_ids || named_groups
187
- elem_match = {as: membership_type}
188
-
189
- if group_ids
190
- elem_match.merge!(group_ids: group_ids)
191
- end
192
-
193
- if named_groups
194
- elem_match.merge!(named_groups: named_groups)
195
- end
196
-
197
- criteria = where(:group_memberships.elem_match => elem_match)
198
- criteria.selector.delete("group_ids")
199
- criteria.selector.delete("named_groups")
200
- else
201
- criteria = where(:"group_memberships.as" => membership_type)
202
- end
203
-
204
- criteria
205
- end
206
- end
207
- end
208
-
209
- # Usage:
210
- # class User
211
- # include Mongoid::Document
212
- #
213
- # acts_as_group_member
214
- # ...
215
- # end
216
- #
217
- # user.groups << group
218
- #
219
- module GroupMember
220
- extend ActiveSupport::Concern
221
- include MemberScopedAs
222
-
223
- included do
224
- has_and_belongs_to_many :groups, autosave: true, dependent: :nullify, inverse_of: nil, class_name: @group_class_name do
225
- def as(membership_type)
226
- return self unless membership_type
227
- group_ids = base.group_memberships.as(membership_type).first.group_ids
228
-
229
- if group_ids.present?
230
- self.and(:id.in => group_ids)
231
- else
232
- self.and(:id => nil)
233
- end
234
- end
235
-
236
- def destroy(*args)
237
- delete(*args)
238
- end
239
-
240
- def delete(*args)
241
- opts = args.extract_options!
242
- groups = args.flatten
243
-
244
-
245
- if opts[:as]
246
- base.group_memberships.as(opts[:as]).each do |membership|
247
- membership.groups.delete(*groups)
248
- end
249
- else
250
- super(*groups)
251
- end
252
- end
253
- end
254
-
255
- class GroupMembership
256
- include ::Mongoid::Document
257
-
258
- embedded_in :member, polymorphic: true
259
-
260
- field :named_groups, type: Array, default: -> { [] }
261
-
262
- after_initialize do
263
- named_groups.extend NamedGroupCollection
264
- end
265
-
266
- field :as, as: :membership_type, type: String
267
- end
268
-
269
- GroupMembership.send :has_and_belongs_to_many, :groups, class_name: @group_class_name, inverse_of: nil
270
-
271
- embeds_many :group_memberships, class_name: GroupMembership.to_s, as: :member do
272
- def as(membership_type)
273
- where(membership_type: membership_type.to_s)
274
- end
275
- end
276
- end
277
-
278
- def in_group?(group, opts={})
279
- groups.as(opts[:as]).include?(group)
280
- end
281
-
282
- def in_any_group?(*args)
283
- opts = args.extract_options!
284
- groups = args
285
-
286
- groups.flatten.each do |group|
287
- return true if in_group?(group, opts)
288
- end
289
- return false
290
- end
291
-
292
- def in_all_groups?(*args)
293
- opts = args.extract_options!
294
- groups = args
295
-
296
- groups.flatten.to_set.subset? self.groups.as(opts[:as]).to_set
297
- end
298
-
299
- def in_only_groups?(*args)
300
- opts = args.extract_options!
301
- groups = args.flatten
302
-
303
- groups.to_set == self.groups.as(opts[:as]).to_set
304
- end
305
-
306
- def shares_any_group?(other, opts={})
307
- in_any_group?(other.groups.to_a, opts)
308
- end
309
-
310
- module ClassMethods
311
- def group_class_name; @group_class_name ||= 'Group'; end
312
- def group_class_name=(klass); @group_class_name = klass; end
313
-
314
- def in_group(group)
315
- group.present? ? self.in(group_ids: group.id) : none
316
- end
317
-
318
- def in_any_group(*groups)
319
- groups.present? ? self.in(group_ids: groups.flatten.map(&:id)) : none
320
- end
321
-
322
- def in_all_groups(*groups)
323
- groups.present? ? where(:group_ids.all => groups.flatten.map(&:id)) : none
324
- end
325
-
326
- def in_only_groups(*groups)
327
- groups.present? ? where(:group_ids => groups.flatten.map(&:id)) : none
328
- end
329
-
330
- def shares_any_group(other)
331
- in_any_group(other.groups.to_a)
332
- end
333
-
334
- end
335
- end
336
-
337
- module NamedGroupCollection
338
- # Criteria to filter by membership type
339
- def as(membership_type)
340
- return self unless membership_type
341
-
342
- membership = @member.group_memberships.as(membership_type).first
343
- if membership
344
- membership.named_groups
345
- else
346
- self.class.new
347
- end
348
- end
349
-
350
- def <<(named_group, opts={})
351
- named_group = named_group.to_sym
352
- super(named_group)
353
- uniq!
354
-
355
- if @member && opts[:as]
356
- membership = @member.group_memberships.find_or_initialize_by(as: opts[:as])
357
- membership.named_groups << named_group
358
- membership.save!
359
- end
360
-
361
- self
362
- end
363
-
364
- def merge(*args)
365
- opts = args.extract_options!
366
- named_groups = args.flatten
367
-
368
- named_groups.each do |named_group|
369
- add(named_group, opts)
370
- end
371
- end
372
-
373
- def delete(*args)
374
- opts = args.extract_options!
375
- named_groups = args.flatten
376
-
377
- if @member
378
- if opts[:as]
379
- membership = @member.group_memberships.as(opts[:as]).first
380
- if membership
381
- if ::Mongoid::VERSION > "4"
382
- membership.pull_all(named_groups: named_groups)
383
- else
384
- membership.pull_all(:named_groups, named_groups)
385
- end
386
- end
387
-
388
- return
389
- else
390
- memberships = @member.group_memberships.where(:named_groups.in => named_groups)
391
- memberships.each do |membership|
392
- if ::Mongoid::VERSION > "4"
393
- membership.pull_all(named_groups: named_groups)
394
- else
395
- membership.pull_all(:named_groups, named_groups)
396
- end
397
- end
398
- end
399
- end
400
-
401
- named_groups.each do |named_group|
402
- super(named_group)
403
- end
404
- end
405
-
406
- def self.extended(base)
407
- base.class_eval do
408
- attr_accessor :member
409
-
410
- alias_method :delete_all, :clear
411
- alias_method :destroy_all, :clear
412
- alias_method :push, :<<
413
- alias_method :add, :<<
414
- alias_method :concat, :merge
415
- alias_method :destroy, :delete
416
- end
417
- end
418
- end
419
-
420
- # Usage:
421
- # class User
422
- # include Mongoid::Document
423
- #
424
- # acts_as_named_group_member
425
- # ...
426
- # end
427
- #
428
- # user.named_groups << :admin
429
- #
430
- module NamedGroupMember
431
- extend ActiveSupport::Concern
432
- include MemberScopedAs
433
-
434
- included do
435
- field :named_groups, type: Array, default: -> { [] }
436
-
437
- after_initialize do
438
- named_groups.extend NamedGroupCollection
439
- named_groups.member = self
440
- end
441
- end
442
-
443
- def in_named_group?(named_group, opts={})
444
- named_groups.as(opts[:as]).include?(named_group)
445
- end
446
-
447
- def in_any_named_group?(*args)
448
- opts = args.extract_options!
449
- group_names = args.flatten
450
-
451
- group_names.each do |named_group|
452
- return true if in_named_group?(named_group)
453
- end
454
-
455
- return false
456
- end
457
-
458
- def in_all_named_groups?(*args)
459
- opts = args.extract_options!
460
- named_groups = args.flatten.to_set
461
-
462
- named_groups.subset? self.named_groups.as(opts[:as]).to_set
463
- end
464
-
465
- def in_only_named_groups?(*args)
466
- opts = args.extract_options!
467
- named_groups = args.flatten.to_set
468
- named_groups == self.named_groups.as(opts[:as]).to_set
469
- end
470
-
471
- def shares_any_named_group?(other, opts={})
472
- in_any_named_group?(other.named_groups, opts)
473
- end
474
-
475
- module ClassMethods
476
- def in_named_group(named_group, opts={})
477
- in_any_named_group(named_group, opts)
478
- end
479
-
480
- def in_any_named_group(*named_groups)
481
- named_groups.flatten!
482
- return none unless named_groups.present?
483
-
484
- self.in(named_groups: named_groups.flatten)
485
- end
486
-
487
- def in_all_named_groups(*named_groups)
488
- named_groups.flatten!
489
- return none unless named_groups.present?
490
-
491
- where(:named_groups.all => named_groups.flatten)
492
- end
493
-
494
- def in_only_named_groups(*named_groups)
495
- named_groups.flatten!
496
- return none unless named_groups.present?
497
-
498
- where(named_groups: named_groups.flatten)
499
- end
500
-
501
- def shares_any_named_group(other, opts={})
502
- in_any_named_group(other.named_groups, opts)
503
- end
504
- end
505
- end
8
+ autoload :Group, 'groupify/adapter/mongoid/group'
9
+ autoload :GroupMember, 'groupify/adapter/mongoid/group_member'
10
+ autoload :MemberScopedAs, 'groupify/adapter/mongoid/member_scoped_as'
11
+ autoload :NamedGroupCollection, 'groupify/adapter/mongoid/named_group_collection'
12
+ autoload :NamedGroupMember, 'groupify/adapter/mongoid/named_group_member'
506
13
  end
507
14
  end
508
15
 
509
- Mongoid::Document.send :include, Groupify::Mongoid::Adapter
16
+