groupify 0.6.0.rc1 → 0.6.0.rc2
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/.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
|