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,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