ryanto/acts_as_votable

View on GitHub
lib/acts_as_votable/votable.rb

Summary

Maintainability
B
4 hrs
Test Coverage
# frozen_string_literal: true

require "acts_as_votable/helpers/words"

module ActsAsVotable
  module Votable
    include Helpers::Words
    include Cacheable

    def self.included(base)
      # allow the user to define these himself
      aliases = {

        vote_up: [
          :up_by, :upvote_by, :like_by, :liked_by,
          :up_from, :upvote_from, :upvote_by, :like_from, :liked_from, :vote_from
        ],

        vote_down: [
          :down_by, :downvote_by, :dislike_by, :disliked_by,
          :down_from, :downvote_from, :downvote_by, :dislike_by, :disliked_by
        ],

        get_up_votes: [
          :get_true_votes, :get_ups, :get_upvotes, :get_likes, :get_positives, :get_for_votes,
        ],

        get_down_votes: [
          :get_false_votes, :get_downs, :get_downvotes, :get_dislikes, :get_negatives
        ],
        unvote_by: [
          :unvote_up, :unvote_down, :unliked_by, :undisliked_by
        ]
      }

      base.class_eval do
        has_many :votes_for, class_name: "ActsAsVotable::Vote", as: :votable, dependent: :delete_all do
          def voters
            includes(:voter).map(&:voter)
          end
        end

        aliases.each do |method, links|
          links.each do |new_method|
            alias_method(new_method, method)
          end
        end

      end
    end

    attr_accessor :vote_registered

    def vote_registered?
      return self.vote_registered
    end

    def default_conditions
      {
        votable_id: self.id,
        votable_type: self.class.base_class.name.to_s
      }
    end

    # voting
    def vote_by(args = {})
      return false if args[:voter].nil?

      options = { vote: true, vote_scope: nil }.merge(args)

      self.vote_registered = false

      # find the vote
      votes = find_votes_by(options[:voter], options[:vote_scope])

      if options[:duplicate] || !votes.exists?
        # this voter has never voted
        vote = ActsAsVotable::Vote.new(
          votable: self,
          voter: options[:voter],
          vote_scope: options[:vote_scope]
        )
      else
        # this voter is potentially changing his vote
        vote = votes.last
      end

      last_update = vote.updated_at

      vote.vote_flag = votable_words.meaning_of(options[:vote])

      #Allowing for a vote_weight to be associated with every vote. Could change with every voter object
      vote.vote_weight = (options[:vote_weight].to_i if options[:vote_weight].present?) || 1

      vote_saved = false
      ActiveRecord::Base.transaction do
        self.vote_registered = false
        vote_saved = vote.save
        if vote_saved
          self.vote_registered = true if last_update != vote.updated_at
          update_cached_votes(options[:vote_scope])
        end
      end
      vote_saved
    end

    def unvote(args = {})
      return false if args[:voter].nil?
      votes = find_votes_by(args[:voter], args[:vote_scope])

      ActiveRecord::Base.transaction do
        deleted_count = votes.delete_all
        update_cached_votes(args[:vote_scope]) if deleted_count > 0
      end
      self.vote_registered = false
      return true
    end

    def vote_up(voter, options = {})
      self.vote_by voter: voter, vote: true, vote_scope: options[:vote_scope], vote_weight: options[:vote_weight]
    end

    def vote_down(voter, options = {})
      self.vote_by voter: voter, vote: false, vote_scope: options[:vote_scope], vote_weight: options[:vote_weight]
    end

    def unvote_by(voter, options = {})
      self.unvote voter: voter, vote_scope: options[:vote_scope] #Does not need vote_weight since the votes_for are anyway getting destroyed
    end

    # results
    def find_votes_for(extra_conditions = {})
      votes_for.where(extra_conditions)
    end

    def find_votes_by(voter, vote_scope)
      find_votes_for(voter_id:   voter.id,
                     vote_scope: vote_scope,
                     voter_type: voter.class.base_class.name)
    end

    def get_up_votes(options = {})
      vote_scope_hash = scope_or_empty_hash(options[:vote_scope])
      find_votes_for({ vote_flag: true }.merge(vote_scope_hash))
    end

    def get_down_votes(options = {})
      vote_scope_hash = scope_or_empty_hash(options[:vote_scope])
      find_votes_for({ vote_flag: false }.merge(vote_scope_hash))
    end

    # voters
    def voted_on_by?(voter)
      votes = find_votes_for voter_id: voter.id, voter_type: voter.class.base_class.name
      votes.exists?
    end

    def voted_up_by?(voter)
      votes = find_votes_for(voter_id: voter.id,
                             vote_flag: true,
                             voter_type: voter.class.base_class.name)
      votes.exists?
    end

    def voted_down_by?(voter)
      votes = find_votes_for(voter_id: voter.id,
                             vote_flag: false,
                             voter_type: voter.class.base_class.name)
      votes.exists?
    end

    private

    def scope_or_empty_hash(vote_scope)
      vote_scope ? { vote_scope: vote_scope } : {}
    end
  end
end