我正在开发一个 Rails 4.2 应用程序,该应用程序具有人们注册的每周重复活动。他们将在每次活动之前(每周一次)收到一封提醒电子邮件。我想要电子邮件上的一键退订链接。这似乎是一项常见的任务,但我还没有找到一个好的当前解决方案。我看到的一些方向是使用 Rails 4.1 中新增的 MessageVerifier,并且不需要保存令牌字符串以在数据库中进行比较。实现这一点的步骤是什么。我有一个用户模型和一个事件模型。电子邮件将发送给注册定期事件的注册用户。
3 回答
这是我为发送给订阅者的常规电子邮件的取消订阅链接提出的解决方案。它使用 MessageVerifier。
1. 生成 user.id 的加密哈希
在我的事件控制器中,我有一个发送电子邮件的 send_notice 操作。我将为取消订阅链接创建一个变量并将其传递给邮件程序。
# app/controller/events_controller.rb
def send_notice
...
@unsubscribe = Rails.application.message_verifier(:unsubscribe).generate(@user.id)
EventMailer.send_notice(@event, @user, @unsubscribe).deliver_later
end
这将获取用户 ID 并生成一个签名和编码的字符串变量 @unsubscribe。消息验证程序通过将用户 ID 与您的 secret_key_base(在文件 config/secrets.yml 中找到)和您提供消息的名称(在这种情况下我称之为:unsubscribe 但它可以是任何名称)作为他们所说的盐,并通过称为 SHA1 的算法运行它。确保您的 secret_key_base 保持安全。使用环境变量将值存储在生产中。在最后一行中,我们将@unsubscribe 变量与@event 和@user 变量一起传递给邮件程序。
2. 将@unsubscribe 哈希变量添加到邮件程序
# app/mailers/event_mailer.rb
def send_notice(event, user, unsubscribe)
@event = event
@user = user
@unsubscribe = unsubscribe
mail(to: user.email, subject: "Event Info")
end
3. 将退订链接放入邮件中
# app/views/events_mailer/send_notice.html.erb
...
<%= link_to "Unsubscribe", settings_unsubscribe_url(id: @unsubscribe) %>.
注意添加的 (id: @unsubscribe) 参数。这会将编码的用户 ID 附加到以 ? 开头的 URL 的末尾。在所谓的查询参数中。
4.添加路线
添加将用户带到取消订阅页面的路由。然后是他们提交退订时的另一条路线。
# config/routes.rb
get 'settings/unsubscribe'
patch 'settings/update'
5.在User模型中添加订阅字段
我选择向用户模型添加订阅布尔(true/false)字段,但您可以通过多种不同的方式对其进行设置。rails g migration AddSubscriptionToUsers subscription:boolean
.
6. 控制器 - 从 URL 中解码 user_id
我选择添加一个带有取消订阅和更新操作的设置控制器。生成控制器rails g controller Settings unsubscribe
。当用户单击电子邮件中的取消订阅链接时,它将转到 URL 并将编码的用户 ID 附加到 URL 后的问号作为查询参数。这是链接的示例:http://localhost:3000/settings/unsubscribe?id=BAhpEg%3D%3D--be9f8b9e64e13317bb0901d8725ce746b156b152。取消订阅操作将使用 MessageVerifier 取消签名和解码 id 参数以获取实际的用户 id 并使用它来查找用户并将其分配给 @user 变量。
# app/controllers/settings_controller.rb
def unsubscribe
user = Rails.application.message_verifier(:unsubscribe).verify(params[:id])
@user = User.find(user)
end
def update
@user = User.find(params[:id])
if @user.update(user_params)
flash[:notice] = 'Subscription Cancelled'
redirect_to root_url
else
flash[:alert] = 'There was a problem'
render :unsubscribe
end
end
private
def user_params
params.require(:user).permit(:subscription)
end
7. 意见
添加取消订阅页面,其中包含取消订阅的表单。
# app/views/settings/unsubscribe.html.erb
<h4>Unsubscribe from Mysite Emails</h4>
<p>By unsubscribing, you will no longer receive email...</p>
<%= form_for(@user, url: settings_update_path(id: @user.id)) do |f| %>
<%= f.hidden_field(:subscription, value: false) %>
<%= f.submit 'Unsubscribe' %>
<%= link_to 'Cancel', root_url %>
<% end %>
您可以通过多种方式进行设置。在这里,我使用了路由到 settings_update_path 的 form_for 助手,并添加了 id 参数:@user.id。在将表单作为查询参数发送时,这会将用户 ID 附加到 URL。Update 操作将读取它以查找用户并将 Subscription 字段更新为 false。
一个非常好的选择可能是使用 Michael Hartl 的 Rails 教程应用程序。在第 12 章中,他实现了一个系统,用户可以在其中关注其他用户的微博(基本上是推特,推特风格)。您只需要设置模型验证(用户和事件模型)并实现关系模型和控制器。关系模型和控制器处理整个以下机制。本质上,控制器只有创建和删除操作,创建操作将用于创建关系,而销毁用于删除它。要处理取消订阅的情况,您可以简单地让 rails 在 ActionMailer 模板中呈现删除路径。上面的链接为您提供了分步说明,但如果您迷路了,请随时向我提问。
这是我的做法:
class User < ApplicationRecord
has_many :unsubscribes
scope :unsubscribed, -> (list) { joins(:unsubscribes).where(unsubscribes: { list: list }) }
scope :subscribed, -> (list) { where.not(id: User.unsubscribed(list)) }
def subscribed?(list)
unsubscribes.where(list: list).none?
end
def unsubscribed?(list)
!subscribed?(list)
end
end
class Unsubscribe < ApplicationRecord
LISTS = %w[
message_mailer-messages_email
user_mailer-reset_password_email
event_mailer-summary_email
].freeze
belongs_to :user
validates :list,
presence: true,
uniqueness: {
scope: :user_id
},
inclusion: {
in: LISTS
}
end
class CreateUnsubscribes < ActiveRecord::Migration[6.1]
def change
create_table :unsubscribes do |t|
t.string :list, null: false
t.references :user, null: false, foreign_key: true
t.timestamps
end
add_index :unsubscribes, %i[list user_id], unique: true
end
end
class UnsubscribesController < ApplicationController
before_action :load_user
helper_method :unsubscribe_url
helper_method :unsubscribes_url
def show
authorize :unsubscribe
@list = params[:id]
@unsubscribe = Unsubscribe.find_by(user: @user, list: params[:id])
end
def create
@unsubscribe = Unsubscribe.new(unsubscribe_params)
@unsubscribe.user = @user
authorize @unsubscribe
if @unsubscribe.save
redirect_to unsubscribe_url
else
redirect_to unsubscribe_url,
alert: @unsubscribe.errors.full_messages.to_sentence
end
end
def destroy
authorize :unsubscribe
@unsubscribe = Unsubscribe.find_by!(user: @user, list: params[:id])
@unsubscribe.destroy!
redirect_to unsubscribe_url
end
private
def load_user
@user = User.find_signed!(params[:user_id])
end
def unsubscribe_params
params.require(:unsubscribe).permit(:list)
end
def unsubscribe_url
if @unsubscribe
user_unsubscribe_url(user_id: @user.signed_id, id: @unsubscribe.list)
else
user_unsubscribe_url(user_id: @user.signed_id, id: @list)
end
end
def unsubscribes_url
user_unsubscribes_url(user_id: @user.signed_id)
end
end
# app/views/unsubscribes/show.html.slim
- title t(".lists.#{@list}")
- if @unsubscribe
p.text-center.text-green-600= t(".you_are_unsubscribed")
.text-center.mb-4
= button_to t(".subscribe"), unsubscribe_url, method: :delete
- else
p.text-center.text-green-600= t(".you_are_subscribed")
= form_for Unsubscribe.new, url: unsubscribes_url do |f|
= f.hidden_field :list, value: @list
p.text-center
= f.submit t(".unsubscribe")
# config/routes.rb
resources :users do
resources :unsubscribes
end