Completed
Pull Request — master (#145)
by
unknown
01:56
created

Session.other_presenter_names()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 3
rs 10
1
class Session < ActiveRecord::Base
2
3
  has_many :categorizations, :dependent => :destroy
4
  has_many :categories, :through => :categorizations
5
  belongs_to :participant  # TODO: rename to 'owner'
6
7
  has_many :presentations, :dependent => :destroy
8
  has_many :presenters, :through => :presentations, :source => :participant
9
  belongs_to :event
10
  belongs_to :timeslot
11
  belongs_to :room
12
  belongs_to :level
13
  has_many :attendances, :dependent => :destroy
14
  has_many :participants, :through => :attendances
15
16
  delegate :name, to: :room, prefix: true, allow_nil: true
17
  delegate :starts_at, to: :timeslot, allow_nil: true
18
  delegate :name, to: :level, prefix: true, allow_nil: true
19
20
  scope :with_attendence_count, -> { select('*').joins("LEFT OUTER JOIN (SELECT session_id, count(id) AS attendence_count FROM attendances GROUP BY session_id) AS attendence_aggregation ON attendence_aggregation.session_id = sessions.id") }
21
22
  scope :for_current_event, -> { where(event_id: Event.current_event.id) }
23
24
  scope :recent, -> { order('created_at desc') }
25
  
26
  scope :random_order, -> { order('random()') }
27
28
  validates_presence_of :description
29
  validates_presence_of :event_id
30
  validates_presence_of :participant_id
31
  validates_presence_of :title
32
  validates_length_of :summary, :maximum => 100, :allow_blank => true
33
  #validates_uniqueness_of :timeslot_id, :scope => :room_id, :allow_blank => true, :message => 'and room combination already in use'
34
35
36
  attr_accessor :name, :email
37
38
  after_create :create_presenter
39
40
  def self.swap_timeslot_and_rooms(session_1, session_2)
41
    Session.transaction do
42
      session_1.room, session_2.room = session_2.room, session_1.room
43
      session_1.timeslot, session_2.timeslot = session_2.timeslot, session_1.timeslot
44
      session_1.save
45
      session_2.save
46
    end
47
  end
48
49
  def self.swap_rooms(session_1, session_2)
50
    if session_1.timeslot != session_2.timeslot
51
      raise "Sessions must be in the same timeslot to swap"
52
    end
53
54
    Session.transaction do
55
      session_1.room, session_2.room = session_2.room, session_1.room
56
      session_1.save
57
      session_2.save
58
    end
59
  end
60
61
  def self.attendee_preferences
62
    result = {}
63
    sessions = Event.current_event.sessions.includes(:participants)
64
65
    sessions.each do |session|
66
      prefs = {}
67
68
      session.participant_ids.each do |p_id|
69
        prefs[p_id] = 1
70
      end
71
72
      result[session.id] = prefs
73
    end
74
75
    result
76
  end
77
78
79
  def self.session_similarity()
80
    Rails.cache.fetch('session_similarity', :expires_in => 30.minutes) do
81
      preferences = Session.attendee_preferences
82
      ::Recommender.calculate_similar_items(preferences, 5)
83
    end
84
  end
85
86
  def presenter_names
87
    presenters.map(&:name)
88
  end
89
90
  def other_presenters
91
    presenters.reject{ |p| p == self.participant }
92
  end
93
  def other_presenter_names
94
    other_presenters.map(&:name)
95
  end
96
97
98
  def attending?(user)
99
    return false if user.nil?
100
101
    participants.include?(user)
102
  end
103
104
  def recommended_sessions
105
    similarity = Session.session_similarity()
106
    recommended = similarity[self.id]
107
108
    if recommended
109
      # find will not order by recommendation strength; use conditions instead of find to ignore missing sessions in the cache
110
      sessions = Session.where(["id in (?)", recommended.map { |r| r[1] }])
111
      sessions.sort_by do |session|
112
        recommended.find_index { |r| r[1] == session.id }
113
      end
114
    else
115
      []
116
    end
117
  end
118
119
  def attendance_count
120
    @attendance_count ||= attendances.count
121
  end
122
123
  attr_writer :attendance_count
124
125
  def self.preload_attendance_counts(sessions)
126
    sessions_by_id = {}
127
    sessions.each do |session|
128
      sessions_by_id[session.id] = session
129
    end
130
    
131
    Attendance
132
      .select("session_id, count(*) as attendance_count")
133
      .where('session_id in (?)', sessions_by_id.keys)
134
      .group('session_id')
135
      .each do |row|
136
        sessions_by_id[row.session_id].attendance_count = row.attendance_count
137
      end
138
139
    nil
140
  end
141
142
  # Estimates actual event-day interest for this session relative to other sessions,
143
  # expressed as a corrected number of votes.
144
  #
145
  # Sessions that were created earlier tend to accumulate more votes, so the naive method
146
  # of using raw vote count will underestimate interest in sessions created later.
147
  # To fix that, we take the number of votes this session received as a proportion of all
148
  # votes cast since it was created. We also include a normalizing factor for last-minute
149
  # sessions created after most of the voting was already done.
150
  #
151
  def estimated_interest
152
    @estimated_interest ||= begin
153
      session_votes     = attendance_count.to_f
154
      possible_votes    = event.attendances.where('attendances.created_at >= ?', created_at).count.to_f
155
      session_count     = event.sessions.count.to_f
156
      participant_count = event.participants.count.to_f
157
158
      # For sessions created at the last minute, we don't have enough information to make
159
      # a good estimate; both session_votes and possible_votes are too low. If we just divide
160
      # session_votes / possible_votes, we'll get wildly inaccurate answers when the denominator
161
      # is small.
162
      #
163
      # We therefore add some ghost "ballast votes" across the board to all sessions, so as to
164
      # make estimated_interest tend toward the mean in cases when there are few real votes.
165
166
      ballast_votes = 3.0
167
168
      (session_votes + ballast_votes) / (possible_votes + ballast_votes * session_count) * participant_count
169
    end
170
  end
171
172
  def to_h
173
    SessionsJsonBuilder.new.to_hash(self)
174
  end
175
176
  private
177
178
  # assign the creator as the first presenter
179
  def create_presenter
180
    presentations.create(participant: participant)
181
  end
182
183
end
184