【发布时间】:2021-08-11 00:10:31
【问题描述】:
我有代码可以向我的网站成员发送邀请,它可以通过电子邮件、实时通知或 One Signal 发送。该代码在开发中运行良好,直到我将邀请移至 ActiveJob 以使用 Sidekiq 和 Redis 在后台处理。我这样做只是为了当组织的维护者上传联系人的 CSV 文件以邀请到他们的组织时。 (因此,一些客户希望邀请大约 10,000 多人的后台工作,如果在控制器内完成,这会使系统陷入困境。)
如果我将任务移动到 ActiveJob,我会在 Sidekiq 输出中收到此错误:
WARN: NameError: uninitialized constant DeliveryMethods
我以为这是因为我没有在ActiveJob中放置require语句,所以我在ActiveJob的顶部添加了这个:
require 'application_notification'
但是,我得到了同样的错误信息。
我很茫然。任何帮助将不胜感激。这是代码sn-ps。如果您还需要什么,请告诉我。
版本
红宝石:'3.0.2'
导轨:7.0.0.alpha
gem 'rails', :github => 'rails/rails', :branch => 'main'
Redis: '~> 4.1.3'
Sidekiq:'6.0.7'
结果输出
# Terminal Output
Started POST "/import_wizard/organization/1" for ::1 at 2021-08-10 16:47:30 -0700
Processing by InvitationsController#invite_imports as JS
Parameters: {"authenticity_token"=>"--REDACTED--", "invitable_type"=>"organization", "invitable_id"=>"1"}
Member Load (1.1ms) SELECT "members".* FROM "members" WHERE "members"."id" = $1 ORDER BY "members"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/concerns/cookies_concern.rb:171:in `load_cookies'
Organization Load (1.3ms) SELECT "organizations".* FROM "organizations" WHERE "organizations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ app/controllers/invitations_controller.rb:216:in `set_invitable'
ImportResult Load (0.8ms) SELECT "import_results".* FROM "import_results" WHERE "import_results"."invitable_id" = $1 AND "import_results"."status" = $2 LIMIT $3 [["invitable_id", 1], ["status", 1], ["LIMIT", 1]]
↳ app/controllers/invitations_controller.rb:261:in `set_imports_to_invite'
ImportRecord Load (0.7ms) SELECT "import_records".* FROM "import_records" WHERE "import_records"."import_result_id" = $1 AND "import_records"."status" = $2 [["import_result_id", 32], ["status", "ready"]]
↳ app/controllers/invitations_controller.rb:155:in `invite_imports'
[ActiveJob] Enqueued InviteImportedMembersJob (Job ID: 69355585-cfef-4f1f-bf90-eae0f24d5f98) to Sidekiq(imports) with arguments:
#<GlobalID:0x00007fbeba81d0a0 @uri=#<URI::GID gid://prayer-nook/Organization/1>>,
[#<GlobalID:0x00007fbeba81c6a0 @uri=#<URI::GID gid://prayer-nook/ImportRecord/309>>,
#<GlobalID:0x00007fbeba817d08 @uri=#<URI::GID gid://prayer-nook/ImportRecord/310>>,
#<GlobalID:0x00007fbeba817470 @uri=#<URI::GID gid://prayer-nook/ImportRecord/311>>,
#<GlobalID:0x00007fbeba816d68 @uri=#<URI::GID gid://prayer-nook/ImportRecord/312>>,
#<GlobalID:0x00007fbeba816250 @uri=#<URI::GID gid://prayer-nook/ImportRecord/313>>,
#<GlobalID:0x00007fbeba8157b0 @uri=#<URI::GID gid://prayer-nook/ImportRecord/318>>,
#<GlobalID:0x00007fbeba814a40 @uri=#<URI::GID gid://prayer-nook/ImportRecord/319>>],
#<GlobalID:0x00007fbeb9a5f198 @uri=#<URI::GID gid://prayer-nook/Member/1>>
Rendering invitations/invite_imports.js.erb
Rendered invitations/invite_imports.js.erb (Duration: 0.1ms | Allocations: 10)
Completed 200 OK in 317ms (Views: 3.4ms | ActiveRecord: 95.8ms | Allocations: 57969)
控制器动作
invite_imports_task 的注释行是我在控制器中使用与在 ActiveJob 中运行的完全相同的代码创建的方法,但可以正常工作。所以,我知道代码可以工作,它只是移动到现在导致问题的 ActiveJob。
# InvitationsController#invite_imports
# app/controllers/invitations_controller.rb
def invite_imports
set_invitable
set_imports_to_invite
@import_step = 4
imports_to_invite_array = []
@imports_to_invite.each do |record|
imports_to_invite_array << record
end
InviteImportedMembersJob.perform_later(@invitable, imports_to_invite_array, @authenticated_member)
# invite_imports_task(@invitable, imports_to_invite_array, @authenticated_member)
respond_to do |format|
format.js
end
end
有效工作
# app/jobs/invite_imported_members_job.rb
class InviteImportedMembersJob < ApplicationJob
require 'application_notification'
queue_as :imports
def perform(invitable, imports_to_invite, sender)
set_import_result(invitable)
imported_emails = imports_to_invite.map {|member| member[:email]}
member_list = Member.where(email: imported_emails)
member_email_list = member_list.pluck(:email)
non_member_email_list = imported_emails - member_email_list
sent_invites = []
error_in_sending_invites = []
member_list.each do |member|
invitation = Invitation.new(invitable: invitable, sender: sender, recipient:member)
if invitation.save
invitable.invited_members << member
sent_invites << member.email
else
error_in_sending_invites << member.email
end
end
non_member_email_list.each do |member|
InvitationMailer.with(recipient_email: member, sender: sender).app_invitation.deliver_later
waitlist = InvitationWaitlist.create(email: member, invitable: invitable, sender: sender)
# in this case the member variable is only an email address
if waitlist.save
sent_invites << member
else
error_in_sending_invites << member
end
end
update_import_records(invitable, sent_invites, error_in_sending_invites)
update_import_result
create_cue_notification(invitable)
end
private
def set_import_result(invitable)
@import_result = ImportResult.find_by(invitable:invitable, status: 'waiting')
end
def update_import_records(invitable, sent_invites, error_in_sending_invites)
if sent_invites.count > 0
ImportRecord.where(import_result_id:@import_result.id, email: sent_invites).update_all(status:'sent')
end
if error_in_sending_invites.count > 0
ImportRecord.where(import_result_id:@import_result.id, email: error_in_sending_invites).update_all(status:'error_in_sending')
end
end
def update_import_result
@import_result.completed!
end
def create_cue_notification(invitable)
hide_old_cues(invitable)
CueService.new(@import_result, set_cue_recipients(invitable), false).call!
end
def hide_old_cues(invitable)
Cue.where(cueable: @import_result).update(status:'hidden')
end
def set_cue_recipients(invitable)
if invitable.is_a?(Organization)
return invitable.maintainers
elsif invitable.is_a?(Group)
return invitable.owner
else
return nil
end
end
end
申请通知
# app/notifications/application_notification.rb
class ApplicationNotification < Noticed::Base
deliver_by :database, format: :format_for_database
deliver_by :action_cable, channel: 'NotificationsChannel', format: :format_for_action_cable
deliver_by :one_signal, class: "DeliveryMethods::OneSignal", format: :format_for_one_signal
def format_for_database
{
type: self.class.name,
params: params
}
end
end
DeliveryMethod::OneSignal
# app/notifications/delivery_methods/one_signal.rb
class DeliveryMethods::OneSignal < Noticed::DeliveryMethods::Base
def deliver
return unless app_id.present? && one_signal_url.present? && player_id.present?
params = {"app_id" => app_id,
"contents" => {"en" => message},
"headings" => {"en" => "Prayer Nook"},
"include_player_ids" => [player_id],
"data" => data
}
uri = URI.parse(one_signal_url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri.path,'Content-Type' => 'application/json;charset=utf-8')
request.body = params.as_json.to_json
response = http.request(request)
puts "OneSignal response: #{response.body}"
end
private
def app_id
ENV['ONE_SIGNAL_APP_ID']
end
def one_signal_url
ENV['ONE_SIGNAL_API_URL']
end
def player_id
recipient.site_profile.one_signal_id
end
def message
if (method = options[:format])
notification.send(method)[:message]
else
"Message from Prayer Nook"
end
end
def data
if (method = options[:format])
notification.send(method)[:data]
else
{ }
end
end
end
来自邀请模型
## app/models/invitation.rb
def send_notifications
if self.invitable_type == 'Group'
GroupInvitationNotification.with(invitation: self, group: self.invitable, sender: self.sender).deliver_later(self.recipient)
elsif self.invitable_type == 'Organization'
OrgInvitationNotification.with(invitation: self, organization: self.invitable, sender: self.sender).deliver_later(self.recipient)
end
end
OrgInvitationNotification
# app/notifications/org_invitation_notification.rb
class OrgInvitationNotification < ApplicationNotification
# this class inherits other delivery methods from ApplicationNotification: database, action_cable, and one_signal
deliver_by :email, mailer: "InvitationMailer", method: :org_invitation, if: :immediate_email_notifications?
# required params
param :invitation
param :organization
param :sender
# helper methods to make rendering easier.
def format_for_action_cable
html = ApplicationController.render(
partial: 'notifications/toast',
locals: { header: "You've been invited",
message: message,
link_path: invitation_path(params[:invitation])
}
)
params.merge(html: html)
end
def format_for_one_signal
{
message: message,
data: { page: 'invitation', id: params[:invitation].id }
}
end
def immediate_email_notifications?
recipient.site_profile.invitations_email_notifications == 'immediately'
end
def message
t(".message", sender: params[:sender].full_name, org_name: params[:organization].name)
end
def url
invitation_url(params[:invitation])
end
end
更新 每个 @LamPhan 的 cmets 的新代码块:
# From config/application.rb
class Application < Rails::Application
# Initialize configuration defaults for originally generated Rails version.
config.load_defaults 6.0
config.active_job.queue_adapter = :sidekiq
config.active_record.encryption.support_unencrypted_data = true
config.active_record.legacy_connection_handling = false
# Settings in config/environments/* take precedence over those specified here.
# Application configuration can go into files in config/initializers
# -- all .rb files in that directory are automatically loaded after loading
# the framework and any gems in your application.
config.generators do |g|
g.test_framework :rspec,
fixtures: false,
view_specs: false,
helper_specs: false,
routing_specs: false
end
config.autoloader = :classic
end
【问题讨论】:
-
以防万一...您是否也重新启动了sidekiq实例?
-
你运行的是什么 Rails 版本?
-
@DennyMueller,是的,我也确实重新启动了 sidekiq 实例。可以肯定的是,我重新启动了 rails 服务器、redis 服务器、sidekiq 实例和 livereload 实例。还是不行。 :-( @LamPhan,我正在运行 Rails 7.0.0.alpha。我的 Gemfile 显示
gem 'rails', :github => 'rails/rails', :branch => 'main'由于使用多数据库应用程序并且需要编写两者之间的关联,我需要运行 7 alpha。7.0 增加了一些解决方法到那个问题。我已经用这些信息更新了我的问题。 -
您使用的是
zeitwerkloader 还是classic?您是否将notifications文件夹下的任何文件/文件夹添加到config.autoload_paths? -
@LamPhan,非常感谢您的帮助。我想出了解决办法!因此,当我删除
config.autoloader = :classic时,所有 ActiveJobs 都停止了。但是,当我将其更改为:config.autoloader = :zeitwerk时,它们都重新开始工作并且不再出现错误。因此,我能够将邀请发送移动到 ActiveJob!非常感谢您的想法并帮助我解决此问题。如果你想创建一个答案,我会这样标记它!
标签: ruby-on-rails notifications sidekiq onesignal rails-activejob