Module: Heya::Campaigns::Queries

Defined in:
lib/heya/campaigns/queries.rb

Constant Summary collapse

NEXT_STEP_SUBQUERY =
<<~SQL
  (WITH steps AS (SELECT * FROM (VALUES :steps_values) AS m (step_gid,campaign_gid,position))
   SELECT m.step_gid FROM steps AS m
   WHERE m.campaign_gid = :campaign_gid
     AND m.position > coalesce((SELECT m.position FROM heya_campaign_receipts AS r
       INNER JOIN steps AS m ON m.step_gid = r.step_gid
         AND m.campaign_gid = :campaign_gid
       WHERE r.user_type = heya_campaign_memberships.user_type
         AND r.user_id = heya_campaign_memberships.user_id
       ORDER BY m.position DESC
       LIMIT 1), -1)
   ORDER BY m.position ASC
   LIMIT 1
  ) = :step_gid
SQL
ACTIVE_CAMPAIGN_SUBQUERY =
<<~SQL
  (WITH heya_campaigns AS (SELECT * FROM (VALUES :campaigns_values) AS campaigns (campaign_gid,position))
  SELECT memberships.campaign_gid FROM heya_campaign_memberships AS memberships
   INNER JOIN heya_campaigns AS campaigns
     ON campaigns.campaign_gid = memberships.campaign_gid
   WHERE memberships.user_type = heya_campaign_memberships.user_type
     AND memberships.user_id = heya_campaign_memberships.user_id
     AND memberships.concurrent = FALSE
   ORDER BY campaigns.position DESC, memberships.created_at ASC
   LIMIT 1
  ) = :campaign_gid
SQL
UsersForStep =

Given a campaign and a step, UsersForStep returns the users who should complete the step.

->(campaign, step) {
  wait_threshold = Time.now.utc - step.wait

  # Safeguard to make sure we never complete the same step twice.
  receipt_query = CampaignReceipt
    .select("heya_campaign_receipts.user_id")
    .where(user_type: campaign.user_class.name)
    .where("heya_campaign_receipts.step_gid = ?", step.gid)

  # https://www.postgresql.org/docs/9.4/queries-values.html
  steps_values = campaign.steps.map { |m|
    ActiveRecord::Base.sanitize_sql_array(
      ["(?, ?, ?)", m.gid, campaign.gid, m.position]
    )
  }.join(", ")

  priority = Heya.config.campaigns.priority.reverse.map { |c| c.is_a?(String) ? c : c.name }
  campaigns_values = Heya.campaigns.map { |c|
    ActiveRecord::Base.sanitize_sql_array(
      ["(?, ?)", c.gid, priority.index(c.name) || -1]
    )
  }.join(", ")

  users = campaign.users
  users
    .where.not(id: receipt_query)
    .where(NEXT_STEP_SUBQUERY.gsub(":steps_values", steps_values), {
      campaign_gid: campaign.gid,
      step_gid: step.gid
    })
    .merge(
      users
        .where("heya_campaign_memberships.concurrent = ?", true)
        .or(
          users.where(ACTIVE_CAMPAIGN_SUBQUERY.gsub(":campaigns_values", campaigns_values), {
            campaign_gid: campaign.gid
          })
        )
    )
    .where(
      "heya_campaign_memberships.last_sent_at <= ?", wait_threshold
    )
}
UsersCompletedStep =

Given a campaign and a step, UsersCompletedStep returns the users who have completed the step.

->(campaign, step) {
  receipt_query = CampaignReceipt
    .select("heya_campaign_receipts.user_id")
    .where(user_type: campaign.user_class.name)
    .where("heya_campaign_receipts.step_gid = ?", step.gid)

  campaign.users
    .where(id: receipt_query)
}
CampaignMembershipsForUpdate =

Given a campaign and a user, CampaignMembershipsForUpdate returns the user’s campaign memberships which should be updated concurrently.

->(campaign, user) {
  membership = CampaignMembership.where(user: user, campaign_gid: campaign.gid).first
  if membership.concurrent?
    CampaignMembership
      .where(user: user, campaign_gid: campaign.gid)
  else
    CampaignMembership
      .where(user: user, concurrent: false)
  end
}
OrphanedCampaignMemberships =

Given a campaign, OrphanedCampaignMemberships returns the campaign memberships which belong to users who no longer exist in the database.

->(campaign) {
  CampaignMembership
    .where(campaign_gid: campaign.gid)
    .where(user_type: campaign.user_class.base_class.name)
    .where.not(user_id: campaign.users.select("id"))
}