assemblymade/coderwall

View on GitHub
app/mailers/protip_mailer.rb

Summary

Maintainability
B
5 hrs
Test Coverage
class ProtipMailer < ApplicationMailer

  add_template_helper(UsersHelper)
  add_template_helper(ProtipsHelper)
  add_template_helper(ApplicationHelper)

  SPAM_NOTICE = "You're receiving this email because you signed up for Coderwall. We hate spam and make an effort to keep notifications to a minimum. To change your notification preferences, you can update your email settings here: http://coderwall.com/settings#email or immediately unsubscribe by clicking this link %unsubscribe_url%"
  STARS = {
    protip_upvotes: 'pro tip upvotes',
    followers: 'followers',
    endorsements: 'endorsements',
    protips_count: 'protips'
  }
  CAMPAIGN_ID = 'protip_mailer-popular_protips'
  POPULAR_PROTIPS_EVENT = 'coderwall-popular_protips'

  #################################################################################
  def popular_protips(user, protips, from, to)
    fail 'User is required.' unless user
    # Skip if this user has already been sent and email for this campaign id.
    fail "Already sent email to #{user.id} please check Redis SET #{CAMPAIGN_ID}." unless REDIS.sadd(CAMPAIGN_ID, user.id.to_s)

    fail 'Protips are required.' if protips.nil? || protips.empty?
    fail 'From date is required.' unless from
    fail 'To date is required.' unless to

    headers['X-Mailgun-Campaign-Id'] = CAMPAIGN_ID

    @user = user
    @protips = protips
    @team, @job = self.class.get_team_and_job_for(@user)
    unless @job.nil?
      self.class.mark_sent(@job, @user)
    end
    @issue = campaign_params

    stars = @user.following_users.where('last_request_at > ?', 1.month.ago)
    @star_stat = star_stat_for_this_week
    @star_stat_string = STARS[@star_stat]

    @most = star_stats(stars).sort_by do |star|
      -star[@star_stat]
    end.first
    @most = nil if @most && (@most[@star_stat] <= 0)

    mail(to: @user.email, subject: "It's #{Time.zone.now.strftime('%A')}")
  rescue Exception => ex
    abort_delivery(ex)
  end
  #################################################################################

  def abort_delivery(ex)
    Rails.logger.error("[ProtipMailer.popular_protips] Aborted email '#{ex}' >>\n#{ex.backtrace.join("\n  ")}")
  end

  def self.mark_sent(mailable, user)
    SentMail.create!(user: user, sent_at: user.last_email_sent, mailable: mailable)
  end

  def self.already_sent?(mailable, user)
    SentMail.where(user_id: user.id, mailable_id: mailable.id, mailable_type: mailable.class.name).exists?
  end

  def campaign_params
    {
      utm_campaign: POPULAR_PROTIPS_EVENT,
      utm_content: Date.today.midnight,
      utm_medium: 'email'
    }
  end

  def star_stat_for_this_week
    STARS.keys[week_of_the_month % 4]
  end

  def star_stats(stars, since=1.week.ago)
    stars.collect { |star| star.activity_stats(since, true) }.each_with_index.map { |stat, index| stat.merge(user: stars[index]) }
  end

  def week_of_the_month
    Date.today.cweek - Date.today.at_beginning_of_month.cweek
  end

  def self.get_team_and_job_for(user)
    if user.team.try(:hiring?)
      [user.team, user.team.jobs.sample]
    else
      teams = teams_for_user(user)
      teams.each do |team|
        best_job = team.best_positions_for(user).detect{|job| (job.team_id == user.team_id) or !already_sent?(job, user)}
        return [team, best_job] unless best_job.nil?
      end
    end
    [nil, nil]
  end

  def self.teams_for_user(user)
    Team.most_relevant_featured_for(user).select do |team|
      team.hiring?
    end
  end

  module Queries
    def self.popular_protips(from, to)
      search_results = ProtipMailer::Queries.search_for_popular_protips(from, to)
      public_ids = search_results.map { |protip| protip['public_id'] }

      Protip.eager_load(:user, :comments).where('public_id in (?)', public_ids)
    end

    def self.search_for_popular_protips(from, to, max_results=10)
      url = "#{ENV['ELASTICSEARCH_URL']}/#{ENV['ELASTICSEARCH_PROTIPS_INDEX']}/_search"
      query = {
        'query' => {
          'bool' => {
            'must' => [
              {
                'range' => {
                  'protip.created_at' => {
                    'from' => from.strftime('%Y-%m-%d'),
                    'to' => to.strftime('%Y-%m-%d')
                  }
                }
              }
            ]
          }
        },
        'size' => max_results,
        'sort' => [
          {
            'protip.popular_score' => {
              'order' => 'desc'
            }
          }
        ]
      }
      response = RestClient.post(url, MultiJson.dump(query), content_type: :json, accept: :json)
      # TODO: check for response code
      MultiJson.load(response.body)['hits']['hits'].map { |protip| protip['_source'] }
    end
  end
end