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.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/Appraisals +2 -11
- data/README.md +1 -1
- data/gemfiles/rails_4.0.gemfile +1 -1
- data/gemfiles/rails_4.1.gemfile +1 -1
- data/lib/groupify/adapter/active_record.rb +6 -580
- data/lib/groupify/adapter/active_record/group.rb +159 -0
- data/lib/groupify/adapter/active_record/group_member.rb +137 -0
- data/lib/groupify/adapter/active_record/group_membership.rb +49 -0
- data/lib/groupify/adapter/active_record/model.rb +48 -0
- data/lib/groupify/adapter/active_record/named_group_collection.rb +105 -0
- data/lib/groupify/adapter/active_record/named_group_member.rb +105 -0
- data/lib/groupify/adapter/mongoid.rb +7 -500
- data/lib/groupify/adapter/mongoid/group.rb +150 -0
- data/lib/groupify/adapter/mongoid/group_member.rb +132 -0
- data/lib/groupify/adapter/mongoid/member_scoped_as.rb +37 -0
- data/lib/groupify/adapter/mongoid/model.rb +38 -0
- data/lib/groupify/adapter/mongoid/named_group_collection.rb +87 -0
- data/lib/groupify/adapter/mongoid/named_group_member.rb +91 -0
- data/lib/groupify/railtie.rb +1 -1
- data/lib/groupify/version.rb +1 -1
- data/spec/{groupify/active_record_spec.rb → active_record_spec.rb} +13 -0
- data/spec/{groupify/mongoid_spec.rb → mongoid_spec.rb} +13 -0
- metadata +18 -6
@@ -0,0 +1,150 @@
|
|
1
|
+
module Groupify
|
2
|
+
module Mongoid
|
3
|
+
|
4
|
+
# Usage:
|
5
|
+
# class Group
|
6
|
+
# include Mongoid::Document
|
7
|
+
#
|
8
|
+
# acts_as_group, :members => [:users]
|
9
|
+
# ...
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# group.add(member)
|
13
|
+
#
|
14
|
+
module Group
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
|
17
|
+
included do
|
18
|
+
@default_member_class = nil
|
19
|
+
@member_klasses ||= Set.new
|
20
|
+
end
|
21
|
+
|
22
|
+
# def members
|
23
|
+
# self.class.default_member_class.any_in(:group_ids => [self.id])
|
24
|
+
# end
|
25
|
+
|
26
|
+
def member_classes
|
27
|
+
self.class.member_classes
|
28
|
+
end
|
29
|
+
|
30
|
+
def add(*args)
|
31
|
+
opts = args.extract_options!
|
32
|
+
membership_type = opts[:as]
|
33
|
+
members = args.flatten
|
34
|
+
return unless members.present?
|
35
|
+
|
36
|
+
members.each do |member|
|
37
|
+
member.groups << self
|
38
|
+
membership = member.group_memberships.find_or_initialize_by(as: membership_type)
|
39
|
+
membership.groups << self
|
40
|
+
membership.save!
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Merge a source group into this group.
|
45
|
+
def merge!(source)
|
46
|
+
self.class.merge!(source, self)
|
47
|
+
end
|
48
|
+
|
49
|
+
module ClassMethods
|
50
|
+
def with_member(member)
|
51
|
+
member.groups
|
52
|
+
end
|
53
|
+
|
54
|
+
def default_member_class
|
55
|
+
@default_member_class ||= User rescue false
|
56
|
+
end
|
57
|
+
|
58
|
+
def default_member_class=(klass)
|
59
|
+
@default_member_class = klass
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the member classes defined for this class, as well as for the super classes
|
63
|
+
def member_classes
|
64
|
+
(@member_klasses ||= Set.new).merge(superclass.method_defined?(:member_classes) ? superclass.member_classes : [])
|
65
|
+
end
|
66
|
+
|
67
|
+
# Define which classes are members of this group
|
68
|
+
def has_members(name)
|
69
|
+
klass = name.to_s.classify.constantize
|
70
|
+
register(klass)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Merge two groups. The members of the source become members of the destination, and the source is destroyed.
|
74
|
+
def merge!(source_group, destination_group)
|
75
|
+
# Ensure that all the members of the source can be members of the destination
|
76
|
+
invalid_member_classes = (source_group.member_classes - destination_group.member_classes)
|
77
|
+
invalid_member_classes.each do |klass|
|
78
|
+
if klass.in(group_ids: [source_group.id]).count > 0
|
79
|
+
raise ArgumentError.new("#{source_group.class} has members that cannot belong to #{destination_group.class}")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
source_group.member_classes.each do |klass|
|
84
|
+
klass.in(group_ids: [source_group.id]).update_all(:$set => {:"group_ids.$" => destination_group.id})
|
85
|
+
|
86
|
+
if klass.relations['group_memberships']
|
87
|
+
if ::Mongoid::VERSION > "4"
|
88
|
+
klass.in(:"group_memberships.group_ids" => [source_group.id]).add_to_set(:"group_memberships.$.group_ids" => destination_group.id)
|
89
|
+
klass.in(:"group_memberships.group_ids" => [source_group.id]).pull(:"group_memberships.$.group_ids" => source_group.id)
|
90
|
+
else
|
91
|
+
klass.in(:"group_memberships.group_ids" => [source_group.id]).add_to_set(:"group_memberships.$.group_ids", destination_group.id)
|
92
|
+
klass.in(:"group_memberships.group_ids" => [source_group.id]).pull(:"group_memberships.$.group_ids", source_group.id)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
source_group.delete
|
98
|
+
end
|
99
|
+
|
100
|
+
protected
|
101
|
+
|
102
|
+
def register(member_klass)
|
103
|
+
(@member_klasses ||= Set.new) << member_klass
|
104
|
+
associate_member_class(member_klass)
|
105
|
+
member_klass
|
106
|
+
end
|
107
|
+
|
108
|
+
module MemberAssociationExtensions
|
109
|
+
def as(membership_type)
|
110
|
+
return self unless membership_type
|
111
|
+
where(:group_memberships.elem_match => { as: membership_type.to_s, group_ids: [base.id] })
|
112
|
+
end
|
113
|
+
|
114
|
+
def destroy(*args)
|
115
|
+
delete(*args)
|
116
|
+
end
|
117
|
+
|
118
|
+
def delete(*args)
|
119
|
+
opts = args.extract_options!
|
120
|
+
members = args
|
121
|
+
|
122
|
+
if opts[:as]
|
123
|
+
members.each do |member|
|
124
|
+
member.group_memberships.as(opts[:as]).first.groups.delete(base)
|
125
|
+
end
|
126
|
+
else
|
127
|
+
members.each do |member|
|
128
|
+
member.group_memberships.in(groups: base).each do |membership|
|
129
|
+
membership.groups.delete(base)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
super(*members)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def associate_member_class(member_klass)
|
139
|
+
association_name = member_klass.name.to_s.pluralize.underscore.to_sym
|
140
|
+
|
141
|
+
has_many association_name, class_name: member_klass.to_s, dependent: :nullify, foreign_key: 'group_ids', extend: MemberAssociationExtensions
|
142
|
+
|
143
|
+
if member_klass == default_member_class
|
144
|
+
has_many :members, class_name: member_klass.to_s, dependent: :nullify, foreign_key: 'group_ids', extend: MemberAssociationExtensions
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module Groupify
|
2
|
+
module Mongoid
|
3
|
+
|
4
|
+
# Usage:
|
5
|
+
# class User
|
6
|
+
# include Mongoid::Document
|
7
|
+
#
|
8
|
+
# acts_as_group_member
|
9
|
+
# ...
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# user.groups << group
|
13
|
+
#
|
14
|
+
module GroupMember
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
include MemberScopedAs
|
17
|
+
|
18
|
+
included do
|
19
|
+
has_and_belongs_to_many :groups, autosave: true, dependent: :nullify, inverse_of: nil, class_name: @group_class_name do
|
20
|
+
def as(membership_type)
|
21
|
+
return self unless membership_type
|
22
|
+
group_ids = base.group_memberships.as(membership_type).first.group_ids
|
23
|
+
|
24
|
+
if group_ids.present?
|
25
|
+
self.and(:id.in => group_ids)
|
26
|
+
else
|
27
|
+
self.and(:id => nil)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def destroy(*args)
|
32
|
+
delete(*args)
|
33
|
+
end
|
34
|
+
|
35
|
+
def delete(*args)
|
36
|
+
opts = args.extract_options!
|
37
|
+
groups = args.flatten
|
38
|
+
|
39
|
+
|
40
|
+
if opts[:as]
|
41
|
+
base.group_memberships.as(opts[:as]).each do |membership|
|
42
|
+
membership.groups.delete(*groups)
|
43
|
+
end
|
44
|
+
else
|
45
|
+
super(*groups)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class GroupMembership
|
51
|
+
include ::Mongoid::Document
|
52
|
+
|
53
|
+
embedded_in :member, polymorphic: true
|
54
|
+
|
55
|
+
field :named_groups, type: Array, default: -> { [] }
|
56
|
+
|
57
|
+
after_initialize do
|
58
|
+
named_groups.extend NamedGroupCollection
|
59
|
+
end
|
60
|
+
|
61
|
+
field :as, as: :membership_type, type: String
|
62
|
+
end
|
63
|
+
|
64
|
+
GroupMembership.send :has_and_belongs_to_many, :groups, class_name: @group_class_name, inverse_of: nil
|
65
|
+
|
66
|
+
embeds_many :group_memberships, class_name: GroupMembership.to_s, as: :member do
|
67
|
+
def as(membership_type)
|
68
|
+
where(membership_type: membership_type.to_s)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def in_group?(group, opts={})
|
74
|
+
groups.as(opts[:as]).include?(group)
|
75
|
+
end
|
76
|
+
|
77
|
+
def in_any_group?(*args)
|
78
|
+
opts = args.extract_options!
|
79
|
+
groups = args
|
80
|
+
|
81
|
+
groups.flatten.each do |group|
|
82
|
+
return true if in_group?(group, opts)
|
83
|
+
end
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
|
87
|
+
def in_all_groups?(*args)
|
88
|
+
opts = args.extract_options!
|
89
|
+
groups = args
|
90
|
+
|
91
|
+
groups.flatten.to_set.subset? self.groups.as(opts[:as]).to_set
|
92
|
+
end
|
93
|
+
|
94
|
+
def in_only_groups?(*args)
|
95
|
+
opts = args.extract_options!
|
96
|
+
groups = args.flatten
|
97
|
+
|
98
|
+
groups.to_set == self.groups.as(opts[:as]).to_set
|
99
|
+
end
|
100
|
+
|
101
|
+
def shares_any_group?(other, opts={})
|
102
|
+
in_any_group?(other.groups.to_a, opts)
|
103
|
+
end
|
104
|
+
|
105
|
+
module ClassMethods
|
106
|
+
def group_class_name; @group_class_name ||= 'Group'; end
|
107
|
+
def group_class_name=(klass); @group_class_name = klass; end
|
108
|
+
|
109
|
+
def in_group(group)
|
110
|
+
group.present? ? self.in(group_ids: group.id) : none
|
111
|
+
end
|
112
|
+
|
113
|
+
def in_any_group(*groups)
|
114
|
+
groups.present? ? self.in(group_ids: groups.flatten.map(&:id)) : none
|
115
|
+
end
|
116
|
+
|
117
|
+
def in_all_groups(*groups)
|
118
|
+
groups.present? ? where(:group_ids.all => groups.flatten.map(&:id)) : none
|
119
|
+
end
|
120
|
+
|
121
|
+
def in_only_groups(*groups)
|
122
|
+
groups.present? ? where(:group_ids => groups.flatten.map(&:id)) : none
|
123
|
+
end
|
124
|
+
|
125
|
+
def shares_any_group(other)
|
126
|
+
in_any_group(other.groups.to_a)
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Groupify
|
2
|
+
module Mongoid
|
3
|
+
|
4
|
+
module MemberScopedAs
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def as(membership_type)
|
9
|
+
group_ids = criteria.selector["group_ids"]
|
10
|
+
named_groups = criteria.selector["named_groups"]
|
11
|
+
criteria = self.criteria
|
12
|
+
|
13
|
+
# If filtering by groups or named groups, merge into the group membership criteria
|
14
|
+
if group_ids || named_groups
|
15
|
+
elem_match = {as: membership_type}
|
16
|
+
|
17
|
+
if group_ids
|
18
|
+
elem_match.merge!(group_ids: group_ids)
|
19
|
+
end
|
20
|
+
|
21
|
+
if named_groups
|
22
|
+
elem_match.merge!(named_groups: named_groups)
|
23
|
+
end
|
24
|
+
|
25
|
+
criteria = where(:group_memberships.elem_match => elem_match)
|
26
|
+
criteria.selector.delete("group_ids")
|
27
|
+
criteria.selector.delete("named_groups")
|
28
|
+
else
|
29
|
+
criteria = where(:"group_memberships.as" => membership_type)
|
30
|
+
end
|
31
|
+
|
32
|
+
criteria
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Groupify
|
2
|
+
module Mongoid
|
3
|
+
module Model
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
def none; where(:id => nil); end
|
8
|
+
end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def acts_as_group(opts = {})
|
12
|
+
include Groupify::Mongoid::Group
|
13
|
+
|
14
|
+
if (member_klass = opts.delete :default_members)
|
15
|
+
self.default_member_class = member_klass.to_s.classify.constantize
|
16
|
+
end
|
17
|
+
|
18
|
+
if (member_klasses = opts.delete :members)
|
19
|
+
member_klasses.each do |member_klass|
|
20
|
+
has_members(member_klass)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def acts_as_group_member(opts = {})
|
26
|
+
@group_class_name = opts[:class_name] || 'Group'
|
27
|
+
include Groupify::Mongoid::GroupMember
|
28
|
+
end
|
29
|
+
|
30
|
+
def acts_as_named_group_member(opts = {})
|
31
|
+
include Groupify::Mongoid::NamedGroupMember
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Mongoid::Document.send :include, Groupify::Mongoid::Model
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Groupify
|
2
|
+
module Mongoid
|
3
|
+
|
4
|
+
module NamedGroupCollection
|
5
|
+
# Criteria to filter by membership type
|
6
|
+
def as(membership_type)
|
7
|
+
return self unless membership_type
|
8
|
+
|
9
|
+
membership = @member.group_memberships.as(membership_type).first
|
10
|
+
if membership
|
11
|
+
membership.named_groups
|
12
|
+
else
|
13
|
+
self.class.new
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def <<(named_group, opts={})
|
18
|
+
named_group = named_group.to_sym
|
19
|
+
super(named_group)
|
20
|
+
uniq!
|
21
|
+
|
22
|
+
if @member && opts[:as]
|
23
|
+
membership = @member.group_memberships.find_or_initialize_by(as: opts[:as])
|
24
|
+
membership.named_groups << named_group
|
25
|
+
membership.save!
|
26
|
+
end
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def merge(*args)
|
32
|
+
opts = args.extract_options!
|
33
|
+
named_groups = args.flatten
|
34
|
+
|
35
|
+
named_groups.each do |named_group|
|
36
|
+
add(named_group, opts)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete(*args)
|
41
|
+
opts = args.extract_options!
|
42
|
+
named_groups = args.flatten
|
43
|
+
|
44
|
+
if @member
|
45
|
+
if opts[:as]
|
46
|
+
membership = @member.group_memberships.as(opts[:as]).first
|
47
|
+
if membership
|
48
|
+
if ::Mongoid::VERSION > "4"
|
49
|
+
membership.pull_all(named_groups: named_groups)
|
50
|
+
else
|
51
|
+
membership.pull_all(:named_groups, named_groups)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
return
|
56
|
+
else
|
57
|
+
memberships = @member.group_memberships.where(:named_groups.in => named_groups)
|
58
|
+
memberships.each do |membership|
|
59
|
+
if ::Mongoid::VERSION > "4"
|
60
|
+
membership.pull_all(named_groups: named_groups)
|
61
|
+
else
|
62
|
+
membership.pull_all(:named_groups, named_groups)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
named_groups.each do |named_group|
|
69
|
+
super(named_group)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.extended(base)
|
74
|
+
base.class_eval do
|
75
|
+
attr_accessor :member
|
76
|
+
|
77
|
+
alias_method :delete_all, :clear
|
78
|
+
alias_method :destroy_all, :clear
|
79
|
+
alias_method :push, :<<
|
80
|
+
alias_method :add, :<<
|
81
|
+
alias_method :concat, :merge
|
82
|
+
alias_method :destroy, :delete
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|