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