Module: VoteFu::Concerns::Voteable::InstanceMethods

Defined in:
lib/vote_fu/concerns/voteable.rb

Instance Method Summary collapse

Instance Method Details

#broadcast_vote_update(vote: nil, action: :updated) ⇒ Object

Broadcast vote updates via Turbo Streams and ActionCable



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/vote_fu/concerns/voteable.rb', line 264

def broadcast_vote_update(vote: nil, action: :updated)
  return unless vote_fu_voteable_options[:broadcasts]

  # Turbo Streams broadcast
  if respond_to?(:broadcast_replace_to)
    broadcast_replace_to(
      [self, :votes],
      target: "#{self.class.name.underscore}_#{id}_vote_widget",
      partial: "vote_fu/votes/widget",
      locals: { voteable: self }
    )
  end

  # ActionCable broadcast (for custom JS handling)
  if defined?(VoteFu::VotesChannel)
    VoteFu::VotesChannel.broadcast_vote_update(self, vote: vote, action: action)
  end
end

#decrement_vote_counters(value) ⇒ Object



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/vote_fu/concerns/voteable.rb', line 227

def decrement_vote_counters(value)
  return unless vote_fu_voteable_options[:counter_cache]

  updates = {}
  updates[:votes_count] = -1 if has_attribute?(:votes_count)
  updates[:votes_total] = -value if has_attribute?(:votes_total)
  updates[:upvotes_count] = -1 if has_attribute?(:upvotes_count) && value.positive?
  updates[:downvotes_count] = -1 if has_attribute?(:downvotes_count) && value.negative?

  self.class.update_counters(id, **updates) if updates.any?
end

#hot_score(gravity: nil) ⇒ Float

Reddit Hot ranking score

Parameters:

  • (defaults to: nil)

    Gravity parameter

Returns:



209
210
211
212
# File 'lib/vote_fu/concerns/voteable.rb', line 209

def hot_score(gravity: nil)
  gravity ||= VoteFu.configuration.hot_ranking_gravity
  VoteFu::Algorithms::RedditHot.call(self, gravity: gravity)
end

#increment_vote_counters(value) ⇒ Object

Counter cache methods



215
216
217
218
219
220
221
222
223
224
225
# File 'lib/vote_fu/concerns/voteable.rb', line 215

def increment_vote_counters(value)
  return unless vote_fu_voteable_options[:counter_cache]

  updates = {}
  updates[:votes_count] = 1 if has_attribute?(:votes_count)
  updates[:votes_total] = value if has_attribute?(:votes_total)
  updates[:upvotes_count] = 1 if has_attribute?(:upvotes_count) && value.positive?
  updates[:downvotes_count] = 1 if has_attribute?(:downvotes_count) && value.negative?

  self.class.update_counters(id, **updates) if updates.any?
end

#percent_against(scope: nil) ⇒ Float

Percentage of downvotes

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:

  • 0.0 to 100.0



153
154
155
156
157
158
# File 'lib/vote_fu/concerns/voteable.rb', line 153

def percent_against(scope: nil)
  total = votes_count(scope: scope)
  return 0.0 if total.zero?

  (votes_against(scope: scope).to_f / total * 100).round(1)
end

#percent_for(scope: nil) ⇒ Float

Percentage of upvotes

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:

  • 0.0 to 100.0



143
144
145
146
147
148
# File 'lib/vote_fu/concerns/voteable.rb', line 143

def percent_for(scope: nil)
  total = votes_count(scope: scope)
  return 0.0 if total.zero?

  (votes_for(scope: scope).to_f / total * 100).round(1)
end

#plusminus(scope: nil) ⇒ Integer

Net score (upvotes minus downvotes) Uses counter cache if available

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:



132
133
134
135
136
137
138
# File 'lib/vote_fu/concerns/voteable.rb', line 132

def plusminus(scope: nil)
  if has_attribute?(:votes_total) && scope.nil?
    read_attribute(:votes_total) || 0
  else
    votes_for(scope: scope) - votes_against(scope: scope)
  end
end

#update_vote_counters(old_value, new_value) ⇒ Object



239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/vote_fu/concerns/voteable.rb', line 239

def update_vote_counters(old_value, new_value)
  return unless vote_fu_voteable_options[:counter_cache]

  updates = {}

  if has_attribute?(:votes_total)
    updates[:votes_total] = new_value - old_value
  end

  if has_attribute?(:upvotes_count)
    old_up = old_value.positive? ? 1 : 0
    new_up = new_value.positive? ? 1 : 0
    updates[:upvotes_count] = new_up - old_up if new_up != old_up
  end

  if has_attribute?(:downvotes_count)
    old_down = old_value.negative? ? 1 : 0
    new_down = new_value.negative? ? 1 : 0
    updates[:downvotes_count] = new_down - old_down if new_down != old_down
  end

  self.class.update_counters(id, **updates) if updates.any?
end

#vote_stream_name(scope: nil) ⇒ Object

Subscribe to vote updates via ActionCable



284
285
286
287
# File 'lib/vote_fu/concerns/voteable.rb', line 284

def vote_stream_name(scope: nil)
  base = "vote_fu:#{self.class.name}:#{id}"
  scope.present? ? "#{base}:#{scope}" : base
end

#voted_by?(voter, direction: nil, scope: nil) ⇒ Boolean

Check if a voter has voted on this item

Parameters:

  • The voter to check

  • (defaults to: nil)

    :up, :down, or nil for any

  • (defaults to: nil)

    Optional voting scope

Returns:



165
166
167
168
169
170
171
172
173
174
175
# File 'lib/vote_fu/concerns/voteable.rb', line 165

def voted_by?(voter, direction: nil, scope: nil)
  vote = received_votes.find_by(voter: voter, scope: scope)
  return false unless vote

  case direction
  when nil then true
  when :up, :positive then vote.up?
  when :down, :negative then vote.down?
  else false
  end
end

#voters(scope: nil) ⇒ Array<ActiveRecord::Base>

Get all voters who voted on this item

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:



180
181
182
# File 'lib/vote_fu/concerns/voteable.rb', line 180

def voters(scope: nil)
  received_votes.with_scope(scope).includes(:voter).map(&:voter).uniq
end

#voters_against(scope: nil) ⇒ Array<ActiveRecord::Base>

Get voters who downvoted

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:



194
195
196
# File 'lib/vote_fu/concerns/voteable.rb', line 194

def voters_against(scope: nil)
  received_votes.with_scope(scope).down.includes(:voter).map(&:voter).uniq
end

#voters_for(scope: nil) ⇒ Array<ActiveRecord::Base>

Get voters who upvoted

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:



187
188
189
# File 'lib/vote_fu/concerns/voteable.rb', line 187

def voters_for(scope: nil)
  received_votes.with_scope(scope).up.includes(:voter).map(&:voter).uniq
end

#votes_against(scope: nil) ⇒ Integer

Count of downvotes

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:



110
111
112
# File 'lib/vote_fu/concerns/voteable.rb', line 110

def votes_against(scope: nil)
  received_votes.with_scope(scope).down.count
end

#votes_count(scope: nil) ⇒ Integer

Total number of votes

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:



117
118
119
# File 'lib/vote_fu/concerns/voteable.rb', line 117

def votes_count(scope: nil)
  received_votes.with_scope(scope).count
end

#votes_for(scope: nil) ⇒ Integer

Count of upvotes

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:



103
104
105
# File 'lib/vote_fu/concerns/voteable.rb', line 103

def votes_for(scope: nil)
  received_votes.with_scope(scope).up.count
end

#votes_total(scope: nil) ⇒ Integer

Sum of all vote values

Parameters:

  • (defaults to: nil)

    Optional voting scope

Returns:



124
125
126
# File 'lib/vote_fu/concerns/voteable.rb', line 124

def votes_total(scope: nil)
  received_votes.with_scope(scope).sum(:value)
end

#wilson_score(confidence: 0.95, scope: nil) ⇒ Float

Wilson Score Lower Bound

Parameters:

  • (defaults to: 0.95)

    Confidence level (0.0 to 1.0)

  • (defaults to: nil)

    Optional voting scope

Returns:

  • Score from 0.0 to 1.0



202
203
204
# File 'lib/vote_fu/concerns/voteable.rb', line 202

def wilson_score(confidence: 0.95, scope: nil)
  VoteFu::Algorithms::WilsonScore.call(self, confidence: confidence, scope: scope)
end