0

I'm using the Heya gem to send email campaigns and I'm getting a weird NoMethodError: undefined method 'concurrent?' for nil:NilClass in production on Render when I run the rails heya:scheduler command. I can't reproduce this error in development or when running the command with -e production on my local machine.

The full stack trace with logs in :debug mode on Render can be found at the bottom of this post.

Here's my config/initializers/heya.rb:

Heya.configure do |config|
  # The name of the model you want to use with Heya.
  config.user_type = "User"

  # The default options to use when processing campaign steps.
  # config.campaigns.default_options = {from: "#{I18n.t('settings.site_name')} <#{I18n.t('settings.newsletter_email')}>"}
  #
  # Campaign priority. When a user is added to multiple campaigns, they are
  # sent in this order. Campaigns are sent in the order that the users were
  # added if no priority is configured.
  config.campaigns.priority = [
    "WelcomeCampaign",
    "EvergreenCampaign"
  ]

end

Here are app/campaigns files:

class ApplicationCampaign < Heya::Campaigns::Base

  default from: "Please Quote Me <hello@pleasequoteme.com>",
          reply_to: "Please Quote Me <hello@pleasequoteme.com>"
end


class WelcomeCampaign < ApplicationCampaign    
  default wait: 0.minutes,
    layout: "newsletter"

  step :intro, wait: 0.minutes,
               subject: "What to expect from Please Quote Me"

  step :warmup, wait: 1.minute,
               subject: "A gun recognizes another gun"
end


class EvergreenCampaign < ApplicationCampaign
  # segment { |u| u.confirmed_at? && u.email_subscriber? }

  default wait: 10.minutes,
    layout: "newsletter"

  step :week_1, wait: 2.minutes,
    subject: "How Charlie Munger spends the first hour of his day"

  step :week_2, wait: 3.minutes,
    subject: "Week Two Subject"
end

Note that I'm adding the users to each campaign in the User model like this:

# Add users to the newsletter after they have confirmed their email address by overriding Devise::Confirmable#after_confirmation
  ## https://stackoverflow.com/questions/4558463/rails-devise-after-confirmation
  def after_confirmation
    WelcomeCampaign.add(self, concurrent: true)
    EvergreenCampaign.add(self, concurrent: true)
    self.update(email_subscriber: true)
  end

Also, if I uncomment the segment line in the Evergreen campaign I get a no method error on my email_subscriber? method. That is an boolean attribute on the User that I can access no problem in the console. My segment line appears to be correct based on the Heya docs. When I comment it out it's a similar failure, but on the concurrent? method in the Heya gem itself. You can see this part of the Heya gem source code here.

Has anyone experienced this issue with Heya? At this point I'm at a loss and filing an issue on Github for the Gem. Am I doing something wrong with how I have Heya set up?

Here's the log file on Render in :debug mode:

render@srv-c96sml0nlki0j45uq0sg-7879d459c5-ngv6f:~/project/src$ bin/rails heya:scheduler --trace
** Invoke heya:scheduler (first_time)
** Invoke environment (first_time)
** Execute environment
** Execute heya:scheduler
D, [2022-07-05T17:58:13.242475 #1228] DEBUG -- :   Heya::CampaignMembership Update All (1.7ms)  UPDATE "heya_campaign_memberships" SET "step_gid" = $1 WHERE "heya_campaign_memberships"."campaign_gid" = $2 AND "heya_campaign_memberships"."step_gid" NOT IN ($3, $4)  [["step_gid", "gid://heya/Heya::Campaigns::Step/EvergreenCampaign%2Fweek_1"], ["campaign_gid", "gid://heya/EvergreenCampaign/EvergreenCampaign"], ["step_gid", "gid://heya/Heya::Campaigns::Step/EvergreenCampaign%2Fweek_1"], ["step_gid", "gid://heya/Heya::Campaigns::Step/EvergreenCampaign%2Fweek_2"]]
D, [2022-07-05T17:58:13.244633 #1228] DEBUG -- :   Heya::CampaignMembership Update All (1.1ms)  UPDATE "heya_campaign_memberships" SET "step_gid" = $1 WHERE "heya_campaign_memberships"."campaign_gid" = $2 AND "heya_campaign_memberships"."step_gid" NOT IN ($3, $4)  [["step_gid", "gid://heya/Heya::Campaigns::Step/WelcomeCampaign%2Fintro"], ["campaign_gid", "gid://heya/WelcomeCampaign/WelcomeCampaign"], ["step_gid", "gid://heya/Heya::Campaigns::Step/WelcomeCampaign%2Fintro"], ["step_gid", "gid://heya/Heya::Campaigns::Step/WelcomeCampaign%2Fwarmup"]]
W, [2022-07-05T17:58:13.246714 #1228]  WARN -- : Scoped order is ignored, it's forced to be batch order.
D, [2022-07-05T17:58:13.250234 #1228] DEBUG -- :   Heya::CampaignMembership Load (2.5ms)  WITH "heya_steps" AS (SELECT * FROM (VALUES ('gid://heya/Heya::Campaigns::Step/EvergreenCampaign%2Fweek_1', 120), ('gid://heya/Heya::Campaigns::Step/EvergreenCampaign%2Fweek_2', 180), ('gid://heya/Heya::Campaigns::Step/WelcomeCampaign%2Fintro', 0), ('gid://heya/Heya::Campaigns::Step/WelcomeCampaign%2Fwarmup', 60)) AS heya_steps (gid,wait)) SELECT "heya_campaign_memberships".* FROM "heya_campaign_memberships" INNER JOIN "heya_steps" ON "heya_steps".gid = "heya_campaign_memberships".step_gid WHERE ("heya_campaign_memberships".concurrent = TRUE
OR "heya_campaign_memberships"."campaign_gid" IN (
  SELECT
    "active_membership"."campaign_gid"
  FROM
    "heya_campaign_memberships" as "active_membership"
  WHERE
    "active_membership"."concurrent" = FALSE
    AND
    (
      "active_membership".user_type = "heya_campaign_memberships".user_type
      AND
      "active_membership".user_id = "heya_campaign_memberships".user_id
    )
  ORDER BY
    array_position(ARRAY[NULL], "active_membership".campaign_gid::text) ASC,
    "active_membership".created_at ASC
  LIMIT 1
)
) AND (("heya_campaign_memberships".last_sent_at <= (TIMESTAMP '2022-07-05 17:58:13.244738' - make_interval(secs := "heya_steps".wait)))
AND (
  (NULL IS NULL OR NULL IS NULL)
  OR (
    "heya_campaign_memberships".user_type = NULL
    AND
    "heya_campaign_memberships".user_id = NULL
  )
)
) ORDER BY "heya_campaign_memberships"."id" ASC LIMIT $1  [["LIMIT", 1000]]
D, [2022-07-05T17:58:13.335870 #1228] DEBUG -- :   User Load (1.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2  [["id", 18], ["LIMIT", 1]]
D, [2022-07-05T17:58:13.338893 #1228] DEBUG -- :   TRANSACTION (1.0ms)  BEGIN
D, [2022-07-05T17:58:13.343638 #1228] DEBUG -- :   Heya::CampaignReceipt Exists? (1.3ms)  SELECT 1 AS one FROM "heya_campaign_receipts" WHERE "heya_campaign_receipts"."user_id" IS NULL AND "heya_campaign_receipts"."step_gid" = $1 LIMIT $2  [["step_gid", "gid://heya/Heya::Campaigns::Step/EvergreenCampaign%2Fweek_1"], ["LIMIT", 1]]
D, [2022-07-05T17:58:13.346390 #1228] DEBUG -- :   Heya::CampaignMembership Load (1.0ms)  SELECT "heya_campaign_memberships".* FROM "heya_campaign_memberships" WHERE "heya_campaign_memberships"."user_id" IS NULL AND "heya_campaign_memberships"."campaign_gid" = $1 ORDER BY "heya_campaign_memberships"."id" ASC LIMIT $2  [["campaign_gid", "gid://heya/EvergreenCampaign/EvergreenCampaign"], ["LIMIT", 1]]
D, [2022-07-05T17:58:13.347669 #1228] DEBUG -- :   TRANSACTION (1.1ms)  ROLLBACK
rails aborted!
NoMethodError: undefined method `concurrent?' for nil:NilClass
/opt/render/project/.gems/ruby/3.0.0/bundler/gems/heya-e62020064dad/lib/heya/campaigns/queries.rb:17:in `block in <module:Queries>'
/opt/render/project/.gems/ruby/3.0.0/bundler/gems/heya-e62020064dad/lib/heya/campaigns/scheduler.rb:42:in `block in process'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/connection_adapters/abstract/transaction.rb:319:in `block in within_new_transaction'
/opt/render/project/.gems/ruby/3.0.0/gems/activesupport-7.0.2.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt'
/opt/render/project/.gems/ruby/3.0.0/gems/activesupport-7.0.2.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize'
/opt/render/project/.gems/ruby/3.0.0/gems/activesupport-7.0.2.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt'
/opt/render/project/.gems/ruby/3.0.0/gems/activesupport-7.0.2.2/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/connection_adapters/abstract/transaction.rb:317:in `within_new_transaction'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/connection_adapters/abstract/database_statements.rb:316:in `transaction'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/transactions.rb:209:in `transaction'
/opt/render/project/.gems/ruby/3.0.0/bundler/gems/heya-e62020064dad/lib/heya/campaigns/scheduler.rb:37:in `process'
/opt/render/project/.gems/ruby/3.0.0/bundler/gems/heya-e62020064dad/lib/heya/campaigns/scheduler.rb:24:in `block in run'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/relation/batches.rb:71:in `each'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/relation/batches.rb:71:in `block in find_each'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/relation/batches.rb:138:in `block in find_in_batches'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/relation/batches.rb:245:in `block in in_batches'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/relation/batches.rb:229:in `loop'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/relation/batches.rb:229:in `in_batches'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/relation/batches.rb:137:in `find_in_batches'
/opt/render/project/.gems/ruby/3.0.0/gems/activerecord-7.0.2.2/lib/active_record/relation/batches.rb:70:in `find_each'
/opt/render/project/.gems/ruby/3.0.0/bundler/gems/heya-e62020064dad/lib/heya/campaigns/scheduler.rb:21:in `run'
/opt/render/project/.gems/ruby/3.0.0/bundler/gems/heya-e62020064dad/lib/tasks/heya_tasks.rake:6:in `block (2 levels) in <main>'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `block in execute'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `each'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:281:in `execute'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:219:in `block in invoke_with_call_chain'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:199:in `synchronize'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:199:in `invoke_with_call_chain'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/task.rb:188:in `invoke'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:160:in `invoke_task'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `block (2 levels) in top_level'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `each'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:116:in `block in top_level'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:125:in `run_with_threads'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:110:in `top_level'
/opt/render/project/.gems/ruby/3.0.0/gems/railties-7.0.2.2/lib/rails/commands/rake/rake_command.rb:24:in `block (2 levels) in perform'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/application.rb:186:in `standard_exception_handling'
/opt/render/project/.gems/ruby/3.0.0/gems/railties-7.0.2.2/lib/rails/commands/rake/rake_command.rb:24:in `block in perform'
/opt/render/project/.gems/ruby/3.0.0/gems/rake-13.0.6/lib/rake/rake_module.rb:59:in `with_application'
/opt/render/project/.gems/ruby/3.0.0/gems/railties-7.0.2.2/lib/rails/commands/rake/rake_command.rb:18:in `perform'
/opt/render/project/.gems/ruby/3.0.0/gems/railties-7.0.2.2/lib/rails/command.rb:51:in `invoke'
/opt/render/project/.gems/ruby/3.0.0/gems/railties-7.0.2.2/lib/rails/commands.rb:18:in `<main>'
/opt/render/project/.gems/ruby/3.0.0/gems/bootsnap-1.10.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
/opt/render/project/.gems/ruby/3.0.0/gems/bootsnap-1.10.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:30:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => heya:scheduler
Lee McAlilly
  • 9,084
  • 12
  • 60
  • 94
  • 2
    Without reading the full text and code here, just looking at the heya gem source code you pointed to, we can draw one simple conclusion: the only reason `membership` would be `nil`, is if such a `user` with such a `campaign` does not exist. Therefore the logical question to ask is: do you have the proper `user` and `campaign` set up on your production server? – Casper Jul 05 '22 at 21:26
  • Correct. Yesterday I tested this by deleting all my users and then went through the sign up process on Production as a new user to see if this is the problem and I am still getting that error. From what I can tell on the Heya docs, I think I am adding Users to the campaigns correctly with `WelcomeCampaign.add(self, concurrent: true)` and `EvergreenCampaign.add(self, concurrent: true)` in the Devise `after_confirmation` callback. – Lee McAlilly Jul 06 '22 at 00:54
  • 1
    The log output shows `user_id` is `NULL` in the queries. Is that normal? Should it be `NULL` or not? Seems suspicious. – Casper Jul 07 '22 at 00:17

1 Answers1

0

Update: Joshua, the creator of the Heya gem helped me sort through this over in a Github issue.

Here's what was causing this error and how I resolved it:

Earlier in the process of setting up Heya I did not have concurrent: true on WelcomeCampaign.add(self, concurrent: true) and EvergreenCampaign.add(self, concurrent: true), but I kept deleting and re-adding users while I was testing it on Production. So, some of the campaign memberships were created with WelcomeCampaign.add(self), so the campaign memberships for those users were saved as if they were not concurrent campaigns while I was trying to schedule concurrent campaigns.

To fix this I just needed to login to the rails console on production and collect Heya::CampaignMembership.all into a variable (e.g. campaign_memberships), and then run destroy_all (campaign_memberships.destroy_all). That removed all of the Heya Campaign Memberships from the production database that were orphans. Then I signed up a new user and all of the campaigns were delivered as expected when I ran rails heya:scheduler.

Lee McAlilly
  • 9,084
  • 12
  • 60
  • 94