2
votes

I have a User and Post model and am using the acts_as_votable gem (which gives me a 'votes' table) to upvote/downvote a post. I want to assign karma to each user, where karma is the number of upvotes a user has gained from all his/her posts minus the total number of downvotes.

Currently I have this instance method on the User's model to calculate karma:

  def karma
    count = 0
    self.posts.each do |post|
      count += post.upvotes.size - post.downvotes.size
    end
    return count
  end

I don't think the above is very efficient with a run time of O(2n), because for every post two additional database queries are required, one for the upvotes, and one for the downvotes.

Any ideas on how to combined the above into a single query, or otherwise make it more efficient?

1

1 Answers

1
votes

Not only about efficiency, such design is not good in terms of OOP. The User model considers too much beyond its scope. Karma is something belongs to user and should not be tied in anything else.

A better approach is to separate "Karma" from Post.

# Add a "karma" column
$ rails g migration AddKarmaToUser

# Or use a dedicated table
class User < ActiveRecord::Base
  has_one :karma

Then, use Controller or service object to change karma. I'll cover Controller here for simplicity. Do not use Model callbacks as that is cross models.

class PostsController < ApplicationController
  def upvote
    post = Post.find(params[:id])
    post.upvote # pseudo API of acts_as_votable
    current_user.increase_karma
  end

  def downvote
    # ...
    current_user.decrease_karma
  end
end

# Model
class User < ActiveRecord::Base
  attr_accessible :karma

  def increase_karma(count=1)
    update_attribute(:karma, karma + count)
  end

  def descrease_karma(count=1)
    # ...
  end
end

# View
user.karma