groupify 0.5.1 → 0.6.0.rc1
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.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.gitignore +1 -0
- data/.rspec +4 -0
- data/.travis.yml +7 -4
- data/Appraisals +24 -0
- data/Gemfile +6 -9
- data/README.md +181 -25
- data/Rakefile +1 -0
- data/gemfiles/rails_3.2.gemfile +18 -0
- data/gemfiles/rails_4.0.gemfile +18 -0
- data/gemfiles/rails_4.1.gemfile +18 -0
- data/groupify.gemspec +4 -4
- data/lib/groupify/adapter/active_record.rb +317 -59
- data/lib/groupify/adapter/mongoid.rb +304 -49
- data/lib/groupify/version.rb +1 -1
- data/spec/groupify/active_record_spec.rb +460 -148
- data/spec/groupify/mongoid_spec.rb +510 -162
- data/spec/spec_helper.rb +14 -3
- metadata +15 -13
- data/gemfiles/activesupport3.gemfile +0 -18
- data/gemfiles/activesupport4.gemfile +0 -24
- data/spec/groupify/mongoid.yml +0 -13
@@ -55,17 +55,25 @@ module Groupify
|
|
55
55
|
@member_klasses ||= Set.new
|
56
56
|
end
|
57
57
|
|
58
|
-
def members
|
59
|
-
|
60
|
-
end
|
58
|
+
# def members
|
59
|
+
# self.class.default_member_class.any_in(:group_ids => [self.id])
|
60
|
+
# end
|
61
61
|
|
62
62
|
def member_classes
|
63
63
|
self.class.member_classes
|
64
64
|
end
|
65
65
|
|
66
|
-
def add(*
|
67
|
-
|
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|
|
68
73
|
member.groups << self
|
74
|
+
membership = member.group_memberships.find_or_initialize_by(as: membership_type)
|
75
|
+
membership.groups << self
|
76
|
+
membership.save!
|
69
77
|
end
|
70
78
|
end
|
71
79
|
|
@@ -76,11 +84,11 @@ module Groupify
|
|
76
84
|
|
77
85
|
module ClassMethods
|
78
86
|
def with_member(member)
|
79
|
-
|
87
|
+
member.groups
|
80
88
|
end
|
81
89
|
|
82
90
|
def default_member_class
|
83
|
-
@default_member_class ||=
|
91
|
+
@default_member_class ||= User rescue false
|
84
92
|
end
|
85
93
|
|
86
94
|
def default_member_class=(klass)
|
@@ -96,11 +104,6 @@ module Groupify
|
|
96
104
|
def has_members(name)
|
97
105
|
klass = name.to_s.classify.constantize
|
98
106
|
register(klass)
|
99
|
-
|
100
|
-
# Define specific members accessor, i.e. group.users
|
101
|
-
define_method name.to_s.pluralize.underscore do
|
102
|
-
klass.any_in(:group_ids => [self.id])
|
103
|
-
end
|
104
107
|
end
|
105
108
|
|
106
109
|
# Merge two groups. The members of the source become members of the destination, and the source is destroyed.
|
@@ -124,8 +127,82 @@ module Groupify
|
|
124
127
|
|
125
128
|
def register(member_klass)
|
126
129
|
(@member_klasses ||= Set.new) << member_klass
|
130
|
+
associate_member_class(member_klass)
|
127
131
|
member_klass
|
128
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
|
129
206
|
end
|
130
207
|
end
|
131
208
|
|
@@ -141,28 +218,93 @@ module Groupify
|
|
141
218
|
#
|
142
219
|
module GroupMember
|
143
220
|
extend ActiveSupport::Concern
|
221
|
+
include MemberScopedAs
|
144
222
|
|
145
223
|
included do
|
146
|
-
has_and_belongs_to_many :groups, :
|
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
|
147
276
|
end
|
148
277
|
|
149
|
-
def in_group?(group)
|
150
|
-
|
278
|
+
def in_group?(group, opts={})
|
279
|
+
groups.as(opts[:as]).include?(group)
|
151
280
|
end
|
152
281
|
|
153
|
-
def in_any_group?(*
|
282
|
+
def in_any_group?(*args)
|
283
|
+
opts = args.extract_options!
|
284
|
+
groups = args
|
285
|
+
|
154
286
|
groups.flatten.each do |group|
|
155
|
-
return true if in_group?(group)
|
287
|
+
return true if in_group?(group, opts)
|
156
288
|
end
|
157
289
|
return false
|
158
290
|
end
|
159
|
-
|
160
|
-
def in_all_groups?(*
|
161
|
-
|
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
|
162
297
|
end
|
163
|
-
|
164
|
-
def
|
165
|
-
|
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)
|
166
308
|
end
|
167
309
|
|
168
310
|
module ClassMethods
|
@@ -170,23 +312,110 @@ module Groupify
|
|
170
312
|
def group_class_name=(klass); @group_class_name = klass; end
|
171
313
|
|
172
314
|
def in_group(group)
|
173
|
-
group.present? ?
|
315
|
+
group.present? ? self.in(group_ids: group.id) : none
|
174
316
|
end
|
175
317
|
|
176
318
|
def in_any_group(*groups)
|
177
|
-
groups.present? ?
|
319
|
+
groups.present? ? self.in(group_ids: groups.flatten.map(&:id)) : none
|
178
320
|
end
|
179
|
-
|
321
|
+
|
180
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)
|
181
327
|
groups.present? ? where(:group_ids => groups.flatten.map(&:id)) : none
|
182
328
|
end
|
183
329
|
|
184
330
|
def shares_any_group(other)
|
185
|
-
in_any_group(other.groups)
|
331
|
+
in_any_group(other.groups.to_a)
|
186
332
|
end
|
187
333
|
|
188
334
|
end
|
189
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
|
190
419
|
|
191
420
|
# Usage:
|
192
421
|
# class User
|
@@ -200,51 +429,77 @@ module Groupify
|
|
200
429
|
#
|
201
430
|
module NamedGroupMember
|
202
431
|
extend ActiveSupport::Concern
|
432
|
+
include MemberScopedAs
|
203
433
|
|
204
434
|
included do
|
205
|
-
field :named_groups, :
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
named_groups.uniq!
|
435
|
+
field :named_groups, type: Array, default: -> { [] }
|
436
|
+
|
437
|
+
after_initialize do
|
438
|
+
named_groups.extend NamedGroupCollection
|
439
|
+
named_groups.member = self
|
211
440
|
end
|
212
441
|
end
|
213
442
|
|
214
|
-
def in_named_group?(
|
215
|
-
named_groups.include?(
|
443
|
+
def in_named_group?(named_group, opts={})
|
444
|
+
named_groups.as(opts[:as]).include?(named_group)
|
216
445
|
end
|
217
446
|
|
218
|
-
def in_any_named_group?(*
|
219
|
-
|
220
|
-
|
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)
|
221
453
|
end
|
454
|
+
|
222
455
|
return false
|
223
456
|
end
|
224
457
|
|
225
|
-
def in_all_named_groups?(*
|
226
|
-
|
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
|
227
469
|
end
|
228
470
|
|
229
|
-
def shares_any_named_group?(other)
|
230
|
-
in_any_named_group?(other.named_groups)
|
471
|
+
def shares_any_named_group?(other, opts={})
|
472
|
+
in_any_named_group?(other.named_groups, opts)
|
231
473
|
end
|
232
474
|
|
233
475
|
module ClassMethods
|
234
|
-
def in_named_group(named_group)
|
235
|
-
named_group
|
476
|
+
def in_named_group(named_group, opts={})
|
477
|
+
in_any_named_group(named_group, opts)
|
236
478
|
end
|
237
479
|
|
238
480
|
def in_any_named_group(*named_groups)
|
239
|
-
named_groups.
|
481
|
+
named_groups.flatten!
|
482
|
+
return none unless named_groups.present?
|
483
|
+
|
484
|
+
self.in(named_groups: named_groups.flatten)
|
240
485
|
end
|
241
|
-
|
486
|
+
|
242
487
|
def in_all_named_groups(*named_groups)
|
243
|
-
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)
|
244
499
|
end
|
245
500
|
|
246
|
-
def shares_any_named_group(other)
|
247
|
-
in_any_named_group(other.named_groups)
|
501
|
+
def shares_any_named_group(other, opts={})
|
502
|
+
in_any_named_group(other.named_groups, opts)
|
248
503
|
end
|
249
504
|
end
|
250
505
|
end
|