Class: UserGroupMembership

Inherits:
DagLink show all
Includes:
UserGroupMembershipMixins::ValidityRange, UserGroupMembershipMixins::ValidityRangeForIndirectMemberships
Defined in:
app/models/user_group_membership.rb

Overview

In this application, all user group memberships, i.e. memberships of a certain user in a certain group, are stored implicitly in the dag_links table in order to minimize the number of database queries that are necessary to find out whether a user is member in a certain group through an indirect membership.

This class allows abstract access to the UserGroupMemberships themselves, and to their properties like since when the membership exists.

Direct Known Subclasses

StatusGroupMembership

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from DagLink

#delete_cache, #fill_cache

Methods inherited from ActiveRecord::Base

#readonly?

Class Method Details

.create(params) ⇒ Object

Create a membership of the ‘u` in the group `g`.

membership = UserGroupMembership.create( user: u, group: g )


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'app/models/user_group_membership.rb', line 50

def self.create( params )
  if UserGroupMembership.find_by( params ).present?
    raise 'Membership already exists: id = ' + UserGroupMembership.find_by( params ).id.to_s
  else
    user = params[:user]
    user ||= User.find params[:user_id] if params[:user_id]
    user ||= User.find_by_title params[:user_title] if params[:user_title]
    raise "Could not create UserGroupMembership without user." unless user
    
    group = params[ :group ]
    group ||= Group.find params[:group_id] if params[:group_id]
    raise "Could not create UserGroupMembership without group." unless group
    
    new_membership = DagLink
      .create(ancestor_id: group.id, ancestor_type: 'Group', descendant_id: user.id, descendant_type: 'User')
      .becomes(UserGroupMembership)
      
    # This needs to be called manually, since DagLink won't call the proper callback.
    #
    new_membership.set_valid_from_to_now(true)
    new_membership.save
    
    return new_membership
  end
end

.find_all_by(params) ⇒ Object

Find all memberships that match the given parameters. This method returns an ActiveRecord::Relation object, which means that the result can be chained with scope methods.

memberships = UserGroupMembership.find_all_by( user: u )
memberships = UserGroupMembership.find_all_by( group: g )
memberships = UserGroupMembership.find_all_by( user: u, group: g ).now
memberships = UserGroupMembership.find_all_by( user: u, group: g ).in_the_past
memberships = UserGroupMembership.find_all_by( user: u, group: g ).now_and_in_the_past


90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'app/models/user_group_membership.rb', line 90

def self.find_all_by( params )
  user = params[ :user ]
  user ||= User.find params[:user_id] if params[:user_id]
  user ||= User.find_by_title params[:user_title] if params[:user_title]
  group = params[ :group ]
  group ||= Group.find params[:group_id] if params[:group_id]
  links = UserGroupMembership
    .where( :descendant_type => "User" )
    .where( :ancestor_type => "Group" )
  links = links.where( :descendant_id => user.id ) if user
  links = links.where( :ancestor_id => group.id ) if group
  links = links.order('valid_from')
  return links
end

.find_all_by_group(group) ⇒ Object



117
118
119
# File 'app/models/user_group_membership.rb', line 117

def self.find_all_by_group( group )
  self.find_all_by( group: group )
end

.find_all_by_user(user) ⇒ Object



113
114
115
# File 'app/models/user_group_membership.rb', line 113

def self.find_all_by_user( user )
  self.find_all_by( user: user )
end

.find_all_by_user_and_group(user, group) ⇒ Object



125
126
127
# File 'app/models/user_group_membership.rb', line 125

def self.find_all_by_user_and_group( user, group )
  self.find_all_by( user: user, group: group )
end

.find_by(params) ⇒ Object

Find the first membership that matches the parameters ‘params`. This is a shortcut for `find_all_by( params ).first`. Use this, if you only expect one membership to be found.



109
110
111
# File 'app/models/user_group_membership.rb', line 109

def self.find_by( params )
  self.find_all_by( params ).limit( 1 ).first
end

.find_by_user_and_group(user, group) ⇒ Object



121
122
123
# File 'app/models/user_group_membership.rb', line 121

def self.find_by_user_and_group( user, group )
  self.find_by( user: user, group: group )
end

Instance Method Details

#corporationObject

If this membership is a subgroup membership of a corporation, this method will return the corporation. Otherwise, this will return nil.

corporation

|-------- group 
            |---( membership )---- user

membership = UserGroupMembership.find_by_user_and_group( user, group )
membership.corporation == corporation


181
182
183
184
185
# File 'app/models/user_group_membership.rb', line 181

def corporation
  if self.group && self.user
    ( ( self.group.ancestor_groups + [ self.group ] ) && self.user.corporations ).first
  end
end

#destroyObject

The regular destroy method won’t trigger DagLink’s callbacks properly, causing the former dag link bug. By calling the DagLink’s destroy method we’ll ensure the callbacks are called and indirect memberships are destroyed correctly.



293
294
295
# File 'app/models/user_group_membership.rb', line 293

def destroy
  DagLink.where(id: self.id).first.destroy
end

#direct_groupsObject

Returns the direct groups shown in the figures above in the description of ‘direct_memberships`.



245
246
247
# File 'app/models/user_group_membership.rb', line 245

def direct_groups
  direct_memberships.collect { |membership| membership.group }
end

#direct_memberships(options = {}) ⇒ Object

Returns the direct memberships corresponding to this membership (self). For clarification, consider the following structure:

group1
  |---- group2
          |---- user

user is not a direct member of group1, but an indirect member. But user is a direct member of group2. Thus, this method, called on a membership of user and group1 will return the membership between user and group2.

UserGroupMembership.find_by( user: user, group: group1 ).direct_memberships.should 
  include( UserGroupMembership.find_by( user: user, group: group2 ) )

An indirect membership can also have several direct memberships, as shown in this figure:

group1
  |--------------- group2
  |                  |
  |---- group3       |
          |------------- user

Here, group2 and grou3 are children of group1. user is member of group2 and group3. Hence, the indirect membership of user and group1 will include both direct memberships.



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'app/models/user_group_membership.rb', line 216

def direct_memberships(options = {})
  descendant_groups_of_self_group = self.group.descendant_groups
  descendant_group_ids_of_self_group = descendant_groups_of_self_group.pluck(:id)
  group_ids = descendant_group_ids_of_self_group + [ self.group.id ]
  
  memberships = UserGroupMembership
  if options[:with_invalid] || self.read_attribute( :valid_to )
    # If the membership itself is invalidated, also consider the invalidated direct memberships.
    # Otherwise, one has to call `direct_memberships_now_and_in_the_past` rather than
    # `direct_memberships` in order to have the invalidated direct memberships included.
    memberships = memberships.with_invalid 
  end
  
  memberships = memberships
    .find_all_by_user( self.user )
    .where( :direct => true )
    .where( :ancestor_id => group_ids, :ancestor_type => 'Group' )
    
  memberships = memberships.order('valid_from')
  memberships
end

#direct_memberships_now_and_in_the_pastObject



238
239
240
# File 'app/models/user_group_membership.rb', line 238

def direct_memberships_now_and_in_the_past
  direct_memberships(with_invalid: true)
end

#groupObject



153
154
155
# File 'app/models/user_group_membership.rb', line 153

def group
  self.ancestor
end

#group_idObject



157
158
159
# File 'app/models/user_group_membership.rb', line 157

def group_id
  self.ancestor_id
end

#indirect_membershipsObject

Access Methods to Associated Indirect Memberships



253
254
255
256
257
258
259
# File 'app/models/user_group_membership.rb', line 253

def indirect_memberships
  self.group.ancestor_groups.collect do |ancestor_group|
    UserGroupMembership.with_invalid.find_by_user_and_group(self.user, ancestor_group)
  end.select do |item|
    item != nil
  end
end

#move_to(group, options = {}) ⇒ Object



276
277
278
# File 'app/models/user_group_membership.rb', line 276

def move_to(group, options = {})
  move_to_group(group, options)
end

#move_to_group(group_to_move_in, options = {}) ⇒ Object

Destroy the current membership and move the user over to the given group.

group1                       group2
  |---- user       =>          |---- user


269
270
271
272
273
274
275
# File 'app/models/user_group_membership.rb', line 269

def move_to_group( group_to_move_in, options = {} )
  time = (options[:time] || options[:date] || options[:at] || Time.zone.now).to_datetime
  invalidate at: time
  new_membership = UserGroupMembership.create(user: self.user, group: group_to_move_in)
  new_membership.update_attribute(:valid_from, time)
  return new_membership
end

#promote_to(new_group, options = {}) ⇒ Object



280
281
282
# File 'app/models/user_group_membership.rb', line 280

def promote_to( new_group, options = {} )
  self.move_to_group( new_group, options )
end

#titleObject

Title, e.g. ‘Membership of John Doe in GroupXY’



39
40
41
# File 'app/models/user_group_membership.rb', line 39

def title
  I18n.translate( :membership_of_user_in_group, user_name: self.user.title, group_name: self.group.name )
end

#userObject

Access Methods to Associated User and Group



133
134
135
# File 'app/models/user_group_membership.rb', line 133

def user
  self.descendant
end

#user=(new_user) ⇒ Object



137
138
139
140
# File 'app/models/user_group_membership.rb', line 137

def user=(new_user)
  self.descendant_id = new_user.id
  self.descendant_type = 'User'
end

#user_idObject



142
143
144
# File 'app/models/user_group_membership.rb', line 142

def user_id
  self.descendant_id
end

#user_titleObject



146
147
148
# File 'app/models/user_group_membership.rb', line 146

def user_title
  user.try(:title)
end

#user_title=(new_user_title) ⇒ Object



149
150
151
# File 'app/models/user_group_membership.rb', line 149

def user_title=(new_user_title)
  self.user = User.find_by_title(new_user_title)
end