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.
@@ -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