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