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,159 @@
1
+ module Groupify
2
+ module ActiveRecord
3
+
4
+ # Usage:
5
+ # class Group < ActiveRecord::Base
6
+ # acts_as_group, :members => [:users]
7
+ # ...
8
+ # end
9
+ #
10
+ # group.add(member)
11
+ #
12
+ module Group
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ @default_member_class = nil
17
+ @member_klasses ||= Set.new
18
+ has_many :group_memberships, :dependent => :destroy
19
+ end
20
+
21
+ def member_classes
22
+ self.class.member_classes
23
+ end
24
+
25
+ def add(*args)
26
+ opts = args.extract_options!
27
+ membership_type = opts[:as]
28
+ members = args.flatten
29
+ return unless members.present?
30
+
31
+ clear_association_cache
32
+
33
+ members.each do |member|
34
+ member.group_memberships.where(group_id: self.id).first_or_create!
35
+ if membership_type
36
+ member.group_memberships.where(group_id: self.id, membership_type: membership_type).first_or_create!
37
+ end
38
+ member.clear_association_cache
39
+ end
40
+ end
41
+
42
+ # Merge a source group into this group.
43
+ def merge!(source)
44
+ self.class.merge!(source, self)
45
+ end
46
+
47
+ module ClassMethods
48
+ def with_member(member)
49
+ #joins(:group_memberships).where(:group_memberships => {:member_id => member.id, :member_type => member.class.to_s})
50
+ member.groups
51
+ end
52
+
53
+ def default_member_class
54
+ @default_member_class ||= (User rescue false)
55
+ end
56
+
57
+ def default_member_class=(klass)
58
+ @default_member_class = klass
59
+ end
60
+
61
+ # Returns the member classes defined for this class, as well as for the super classes
62
+ def member_classes
63
+ (@member_klasses ||= Set.new).merge(superclass.method_defined?(:member_classes) ? superclass.member_classes : [])
64
+ end
65
+
66
+ # Define which classes are members of this group
67
+ def has_members(name)
68
+ klass = name.to_s.classify.constantize
69
+ register(klass)
70
+ end
71
+
72
+ # Merge two groups. The members of the source become members of the destination, and the source is destroyed.
73
+ def merge!(source_group, destination_group)
74
+ # Ensure that all the members of the source can be members of the destination
75
+ invalid_member_classes = (source_group.member_classes - destination_group.member_classes)
76
+ invalid_member_classes.each do |klass|
77
+ if klass.joins(:group_memberships).where(:group_memberships => {:group_id => source_group.id}).count > 0
78
+ raise ArgumentError.new("#{source_group.class} has members that cannot belong to #{destination_group.class}")
79
+ end
80
+ end
81
+
82
+ source_group.transaction do
83
+ source_group.group_memberships.update_all(:group_id => destination_group.id)
84
+ source_group.destroy
85
+ end
86
+ end
87
+
88
+ protected
89
+
90
+ def register(member_klass)
91
+ (@member_klasses ||= Set.new) << member_klass
92
+
93
+ associate_member_class(member_klass)
94
+
95
+ member_klass
96
+ end
97
+
98
+ module MemberAssociationExtensions
99
+ def as(membership_type)
100
+ where(group_memberships: {membership_type: membership_type})
101
+ end
102
+
103
+ def delete(*args)
104
+ opts = args.extract_options!
105
+ members = args
106
+
107
+ if opts[:as]
108
+ proxy_association.owner.group_memberships.
109
+ where(member_id: members.map(&:id), member_type: proxy_association.reflection.options[:source_type]).
110
+ as(opts[:as]).
111
+ delete_all
112
+ else
113
+ super(*members)
114
+ end
115
+ end
116
+
117
+ def destroy(*args)
118
+ opts = args.extract_options!
119
+ members = args
120
+
121
+ if opts[:as]
122
+ proxy_association.owner.group_memberships.
123
+ where(member_id: members.map(&:id), member_type: proxy_association.reflection.options[:source_type]).
124
+ as(opts[:as]).
125
+ destroy_all
126
+ else
127
+ super(*members)
128
+ end
129
+ end
130
+ end
131
+
132
+ def associate_member_class(member_klass)
133
+ association_name = member_klass.name.to_s.pluralize.underscore.to_sym
134
+ source_type = member_klass.base_class
135
+
136
+ has_many association_name, through: :group_memberships, source: :member, source_type: source_type, extend: MemberAssociationExtensions
137
+ override_member_accessor(association_name)
138
+
139
+ if member_klass == default_member_class
140
+ has_many :members, through: :group_memberships, source: :member, source_type: source_type, extend: MemberAssociationExtensions
141
+ override_member_accessor(:members)
142
+ end
143
+ end
144
+
145
+ def override_member_accessor(association_name)
146
+ define_method(association_name) do |*args|
147
+ opts = args.extract_options!
148
+ membership_type = opts[:as]
149
+ if membership_type.present?
150
+ super().as(membership_type)
151
+ else
152
+ super()
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,137 @@
1
+ module Groupify
2
+ module ActiveRecord
3
+
4
+ # Usage:
5
+ # class User < ActiveRecord::Base
6
+ # acts_as_group_member
7
+ # ...
8
+ # end
9
+ #
10
+ # user.groups << group
11
+ #
12
+ module GroupMember
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ unless respond_to?(:group_memberships)
17
+ has_many :group_memberships, :as => :member, :autosave => true, :dependent => :destroy
18
+ end
19
+
20
+ has_many :groups, :through => :group_memberships, :class_name => @group_class_name do
21
+ def as(membership_type)
22
+ return self unless membership_type
23
+ where(group_memberships: {membership_type: membership_type})
24
+ end
25
+
26
+ def delete(*args)
27
+ opts = args.extract_options!
28
+ groups = args.flatten
29
+
30
+ if opts[:as]
31
+ proxy_association.owner.group_memberships.where(group_id: groups.map(&:id)).as(opts[:as]).delete_all
32
+ else
33
+ super(*groups)
34
+ end
35
+ end
36
+
37
+ def destroy(*args)
38
+ opts = args.extract_options!
39
+ groups = args.flatten
40
+
41
+ if opts[:as]
42
+ proxy_association.owner.group_memberships.where(group_id: groups.map(&:id)).as(opts[:as]).destroy_all
43
+ else
44
+ super(*groups)
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ def in_group?(group, opts={})
51
+ criteria = {group_id: group.id}
52
+
53
+ if opts[:as]
54
+ criteria.merge!(membership_type: opts[:as])
55
+ end
56
+
57
+ group_memberships.exists?(criteria)
58
+ end
59
+
60
+ def in_any_group?(*args)
61
+ opts = args.extract_options!
62
+ groups = args
63
+
64
+ groups.flatten.each do |group|
65
+ return true if in_group?(group, opts)
66
+ end
67
+ return false
68
+ end
69
+
70
+ def in_all_groups?(*args)
71
+ opts = args.extract_options!
72
+ groups = args.flatten
73
+
74
+ groups.to_set.subset? self.groups.as(opts[:as]).to_set
75
+ end
76
+
77
+ def in_only_groups?(*args)
78
+ opts = args.extract_options!
79
+ groups = args.flatten
80
+
81
+ groups.to_set == self.groups.as(opts[:as]).to_set
82
+ end
83
+
84
+ def shares_any_group?(other, opts={})
85
+ in_any_group?(other.groups, opts)
86
+ end
87
+
88
+ module ClassMethods
89
+ def group_class_name; @group_class_name ||= 'Group'; end
90
+ def group_class_name=(klass); @group_class_name = klass; end
91
+
92
+ def as(membership_type)
93
+ joins(:group_memberships).where(group_memberships: { membership_type: membership_type })
94
+ end
95
+
96
+ def in_group(group)
97
+ return none unless group.present?
98
+
99
+ joins(:group_memberships).where(group_memberships: { group_id: group.id }).uniq
100
+ end
101
+
102
+ def in_any_group(*groups)
103
+ groups = groups.flatten
104
+ return none unless groups.present?
105
+
106
+ joins(:group_memberships).where(group_memberships: { group_id: groups.map(&:id) }).uniq
107
+ end
108
+
109
+ def in_all_groups(*groups)
110
+ groups = groups.flatten
111
+ return none unless groups.present?
112
+
113
+ joins(:group_memberships).
114
+ group(:"group_memberships.member_id").
115
+ where(:group_memberships => {:group_id => groups.map(&:id)}).
116
+ having("COUNT(group_memberships.group_id) = #{groups.count}").
117
+ uniq
118
+ end
119
+
120
+ def in_only_groups(*groups)
121
+ groups = groups.flatten
122
+ return none unless groups.present?
123
+
124
+ joins(:group_memberships).
125
+ group(:"group_memberships.member_id").
126
+ having("COUNT(DISTINCT group_memberships.group_id) = #{groups.count}").
127
+ uniq
128
+ end
129
+
130
+ def shares_any_group(other)
131
+ in_any_group(other.groups)
132
+ end
133
+
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,49 @@
1
+ module Groupify
2
+ module ActiveRecord
3
+
4
+ # Join table that tracks which members belong to which groups
5
+ #
6
+ # Usage:
7
+ # class GroupMembership < ActiveRecord::Base
8
+ # acts_as_group_membership
9
+ # ...
10
+ # end
11
+ #
12
+ module GroupMembership
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ attr_accessible(:member, :group, :group_name, :membership_type, :as) if ActiveSupport::VERSION::MAJOR < 4
17
+
18
+ belongs_to :member, :polymorphic => true
19
+ belongs_to :group
20
+ end
21
+
22
+ def membership_type=(membership_type)
23
+ self[:membership_type] = membership_type.to_s if membership_type.present?
24
+ end
25
+
26
+ def as=(membership_type)
27
+ self.membership_type = membership_type
28
+ end
29
+
30
+ def as
31
+ membership_type
32
+ end
33
+
34
+ module ClassMethods
35
+ def named(group_name=nil)
36
+ if group_name.present?
37
+ where(group_name: group_name)
38
+ else
39
+ where("group_name IS NOT NULL")
40
+ end
41
+ end
42
+
43
+ def as(membership_type)
44
+ where(membership_type: membership_type)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,48 @@
1
+ module Groupify
2
+ module ActiveRecord
3
+ module Model
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # Define a scope that returns nothing.
8
+ # This is built into ActiveRecord 4, but not 3
9
+ unless self.class.respond_to? :none
10
+ def self.none
11
+ where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil)))
12
+ end
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+ def acts_as_group(opts = {})
18
+ include Groupify::ActiveRecord::Group
19
+
20
+ if (member_klass = opts.delete :default_members)
21
+ self.default_member_class = member_klass.to_s.classify.constantize
22
+ end
23
+
24
+ if (member_klasses = opts.delete :members)
25
+ member_klasses.each do |member_klass|
26
+ has_members(member_klass)
27
+ end
28
+ end
29
+ end
30
+
31
+ def acts_as_group_member(opts = {})
32
+ @group_class_name = opts[:class_name] || 'Group'
33
+ include Groupify::ActiveRecord::GroupMember
34
+ end
35
+
36
+ def acts_as_named_group_member(opts = {})
37
+ include Groupify::ActiveRecord::NamedGroupMember
38
+ end
39
+
40
+ def acts_as_group_membership(opts = {})
41
+ include Groupify::ActiveRecord::GroupMembership
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ ActiveRecord::Base.send :include, Groupify::ActiveRecord::Model
@@ -0,0 +1,105 @@
1
+ module Groupify
2
+ module ActiveRecord
3
+
4
+ class NamedGroupCollection < Set
5
+ def initialize(member)
6
+ @member = member
7
+ @named_group_memberships = member.group_memberships.named
8
+ @group_names = @named_group_memberships.pluck(:group_name).map(&:to_sym)
9
+ super(@group_names)
10
+ end
11
+
12
+ def add(named_group, opts={})
13
+ named_group = named_group.to_sym
14
+ membership_type = opts[:as]
15
+
16
+ if @member.new_record?
17
+ @member.group_memberships.build(group_name: named_group, membership_type: nil)
18
+ else
19
+ @member.transaction do
20
+ @member.group_memberships.where(group_name: named_group, membership_type: nil).first_or_create!
21
+ end
22
+ end
23
+
24
+ if membership_type
25
+ if @member.new_record?
26
+ @member.group_memberships.build(group_name: named_group, membership_type: membership_type)
27
+ else
28
+ @member.group_memberships.where(group_name: named_group, membership_type: membership_type).first_or_create!
29
+ end
30
+ end
31
+
32
+ super(named_group)
33
+ end
34
+
35
+ alias_method :push, :add
36
+ alias_method :<<, :add
37
+
38
+ def merge(*args)
39
+ opts = args.extract_options!
40
+ named_groups = args.flatten
41
+ named_groups.each do |named_group|
42
+ add(named_group, opts)
43
+ end
44
+ end
45
+
46
+ alias_method :concat, :merge
47
+
48
+ def include?(named_group, opts={})
49
+ named_group = named_group.to_sym
50
+ if opts[:as]
51
+ as(opts[:as]).include?(named_group)
52
+ else
53
+ super(named_group)
54
+ end
55
+ end
56
+
57
+ def delete(*args)
58
+ opts = args.extract_options!
59
+ named_groups = args.flatten.compact
60
+
61
+ remove(named_groups, :delete_all, opts)
62
+ end
63
+
64
+ def destroy(*args)
65
+ opts = args.extract_options!
66
+ named_groups = args.flatten.compact
67
+
68
+ remove(named_groups, :destroy_all, opts)
69
+ end
70
+
71
+ def clear
72
+ @named_group_memberships.delete_all
73
+ super
74
+ end
75
+
76
+ alias_method :delete_all, :clear
77
+ alias_method :destroy_all, :clear
78
+
79
+ # Criteria to filter by membership type
80
+ def as(membership_type)
81
+ @named_group_memberships.as(membership_type).pluck(:group_name).map(&:to_sym)
82
+ end
83
+
84
+ protected
85
+
86
+ def remove(named_groups, method, opts)
87
+ if named_groups.present?
88
+ scope = @named_group_memberships.where(group_name: named_groups)
89
+
90
+ if opts[:as]
91
+ scope = scope.where(membership_type: opts[:as])
92
+ end
93
+
94
+ scope.send(method)
95
+
96
+ unless opts[:as]
97
+ named_groups.each do |named_group|
98
+ @hash.delete(named_group)
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end