badge_rules.rb 6.88 KB
# +grant_on+ accepts:
# * Nothing (always grants)
# * A block which evaluates to boolean (recieves the object as parameter)
# * A block with a hash composed of methods to run on the target object with
#   expected values (+votes: 5+ for instance).
#
module Merit
  class BadgeRules
    include Merit::BadgeRulesMethods

    AVAILABLE_RULES = {
      comment_author: [
        {
          action: 'comment#create',
          default_threshold: 5,
          to: :author,
          target_profile: lambda {|comment| comment.profile },
          value: lambda { |comment, author| author.present? ? author.comments.count : 0 }
        }
      ],
      comment_received: [
        {
          action: 'comment#create',
          default_threshold: 5,
          to: lambda {|comment| comment.source.author},
          target_profile: lambda {|comment| comment.profile },
          value: lambda { |comment, author| author.present? ? Comment.where(source_id: Article.where(author_id: author.id)).count : 0 }
        }
      ],
      article_author: [
        {
          action: 'article#create',
          default_threshold: 5,
          to: :author,
          target_profile: lambda {|article| article.profile },
          value: lambda { |article, author| author.present? ? TextArticle.where(author_id: author.id).count : 0 }
        },
      ],
      positive_votes_received: [
          {
          action: 'vote#create',
          default_threshold: 5,
          to: lambda {|vote| vote.voteable.author},
          target_profile: lambda {|vote| vote.voteable.profile },
          value: lambda { |vote, author| vote.voteable ? Vote.for_voteable(vote.voteable).where('vote > 0').count : 0}
        }
      ],
      negative_votes_received: [
        {
          action: 'vote#create',
          default_threshold: 5,
          to: lambda {|vote| vote.voteable.author},
          target_profile: lambda {|vote| vote.voteable.profile },
          value: lambda { |vote, author| Vote.for_voteable(vote.voteable).where('vote < 0').count }
        }
      ],
      votes_performed: [
        {
          action: 'vote#create',
          default_threshold: 5,
          to: lambda {|vote| vote.voter},
          target_profile: lambda {|vote| vote.voteable.profile },
          value: lambda { |vote, voter| voter ? Vote.for_voter(voter).count : 0 }
        }
      ],
      friendly: [
        {
          action: 'friendship#create',
          default_threshold: 5,
          to: lambda {|friendship| friendship.person},
          value: lambda { |friendship, person| person.friends.count }
        }
      ],
      manual: [],

#FIXME review the name of the badges and see a way to make it generic
      creative: [
        {
          action: 'comment#create',
          default_threshold: 5,
          to: :author,
          target_profile: lambda {|comment| comment.profile },
          value: lambda { |comment, author| author.present? ? author.comments.count : 0 }
        },
        {
          action: 'article#create',
          default_threshold: 5,
          to: :author,
          target_profile: lambda {|article| article.profile },
          value: lambda { |article, author| author.present? ? author.articles.count : 0 }
        },
      ],
      observer: [
        {
          action: 'articlefollower#create',
          default_threshold: 5,
          to: lambda {|article| article.person },
          target_profile: lambda {|article_follower| article_follower.article.profile },
          model: 'ArticleFollower',
          value: lambda { |article, person| person.present? ? person.article_followers.count : 0 }
        }
      ],
      mobilizer: [
        {
          action: 'Vote#create',
          default_threshold: 5,
          to: lambda { |vote| vote.voter },
          target_profile: lambda {|vote| vote.voteable.profile },
          value: lambda { |vote, voter| Vote.for_voter(voter).count }
        },
        {
          action: 'Event#create',
          default_threshold: 5,
          to: lambda { |article| article.author },
          target_profile: lambda {|article| article.profile },
          value: lambda { |event, author| author.events.count }
        },
      ],
      generous: [
        {
          action: 'vote#create',
          default_threshold: 5,
          to: lambda {|vote| vote.voter},
          target_profile: lambda {|vote| vote.voteable.profile },
          value: lambda { |vote, voter| voter ? voter.votes.where('vote > 0').count : 0 }
        },
        {
          action: 'comment#create',
          default_threshold: 5,
          to: :author,
          target_profile: lambda {|comment| comment.profile },
          value: lambda { |comment, author| author.present? ? author.comments.count : 0 }
        }
      ],
      articulator: [
        {
          action: 'articlefollower#create',
          default_threshold: 5,
          to: :person,
          target_profile: lambda {|article_follower| article_follower.article.profile },
          model: 'ArticleFollower',
          value: lambda { |article_follower, person| person.present? ? person.article_followers.count : 0 }
        },
        {
          action: 'comment#create',
          default_threshold: 5,
          to: :author,
          target_profile: lambda {|comment| comment.profile },
          value: lambda { |comment, author| author.present? ? author.comments.count : 0 }
        },
      ]
    }

    def target_author(source, setting)
      if setting[:to].is_a? Symbol
        source.send(setting[:to])
      else
        setting[:to].call(source) rescue nil
      end
    end

    def target_profile(source, setting)
      setting[:target_profile].present? ? setting[:target_profile].call(source) : nil
    end

    def check_organization_badge(badge, source, setting)
      !badge.owner.kind_of?(Organization) || badge.owner == target_profile(source, setting)
    end

    def initialize(environment=nil)
      return if environment.nil?
      @environment = environment

      rules = AVAILABLE_RULES
      rules.merge! CONFERENCE_RULES if defined? CONFERENCE_RULES

      environment.gamification_plugin_badges.each do |badge|
        next if rules[badge.name.to_sym].nil?
        rules[badge.name.to_sym].each do |setting|
          options = {badge: badge.name, level: badge.level, to: setting[:to]}
          options[:model_name] = setting[:model] unless setting[:model].nil?
          grant_on setting[:action], options do |source|
            can_be_granted = true
            rules[badge.name.to_sym].each do |s|
              to = target_author(source, setting)
              # pass source and to for different situations
              action = (badge.custom_fields || {}).fetch(s[:action], {})
              can_be_granted &= s[:value].call(source, to) >= action.fetch(:threshold, s[:default_threshold]).to_i
              can_be_granted &= check_organization_badge(badge, source, setting)
            end
            can_be_granted
          end
        end
      end
    end

  end
end