用户订阅包含最后视频的电子邮件,但他们也会设置何时收到这些电子邮件。
Subscription(user_id, frequency, day, time, time_zone)
user_id | frequency | day | time | time_zone
1 | daily | null | 16:00 | GMT
2 | weekly | friday | 11:00 | UTC
3 | weekly | monday | 18:00 | EST
我们如何以用户在其时区选择的确切时间和频率发送电子邮件,而不会搞砸(例如发送双重电子邮件或丢失时间)
唯一的频率是每日和每周,如果是每天,则该日为空。
我使用redis作为数据库,让我知道如何以正确的方式做到这一点!
答案 0 :(得分:2)
我将使用resque-scheduler gem扩展fmendez的答案。
首先,让我们创建发送电子邮件的工作人员
class SubscriptionWorker
def self.perform(subscription_id)
subscription = Subscription.find subscription_id
# ....
# handle sending emails here
# ....
# Make sure that you don't have duplicate workers
Resque.remove_delayed(SubscriptionWorker, subscription_id)
# this actually calls this same worker but sets it up to work on the next
# sending time of the subscription. next_sending_time is a method that
# we implement in the subscription model.
Resque.enqueue_at(subscription.next_sending_time, SubscriptionWorker, subscription_id)
end
end
在订阅模型中,添加next_sending_time
方法以计算下次发送电子邮件的时间。
# subscription.rb
def next_sending_time
parsed_time = Time.parse("#{time} #{time_zone}") + 1.day
if frequency == 'daily'
parsed_time
else
# this adds a day until the date matches the day in the subscription
while parsed_time.strftime("%A").downcase != day.downcase
parsed_time += 1.day
end
end
end
答案 1 :(得分:1)
我过去曾使用delayed_job
执行类似的任务。可能你可以使用与resque
相同的技术。基本上,您必须在当前作业结束时安排下一个作业。
class Subscription < ActiveRecord::Base
after_create :send_email
def send_email
# do stuff and then schedule the next run
ensure
send_email
end
handle_asynchronously :send_email, :run_at => Proc.new{|s| s.deliver_at }
def daily? (frequency == "daily");end
def max_attempts 1;end
def time_sec
hour,min=time.split(":").map(&:to_i)
hour.hours + min.minutes
end
def days_sec
day.nil? ? 0 : Time::DAYS_INTO_WEEK[day.to_sym].days
end
def interval_start_time
time = Time.now.in_time_zone(time_zone)
daily? ? time.beginning_of_day : time.beginning_of_week
end
def deliver_at
run_at = interval_start_time + days_sec + time_sec
if time.past?
run_at = daily? ? run_at.tomorrow : 1.week.from_now(run_at)
end
run_at
end
end
重新安排警告
更新代码以处理循环终止。您可以通过添加名为boolean
的{{1}}列来处理此问题(默认情况下将其设置为active
)。要禁用订阅,请将列设置为false。
true
将作业的 def send_email
return unless active?
# do stuff and then schedule the next run
ensure
send_email if active?
end
设置为1.否则,您将填充队列。在上面的解决方案中,max_attempts
的作业将尝试一次。
答案 2 :(得分:1)
这更像是一个系统级问题,而不是特定于ruby。
首先,将所有时间都存储在GMT内部。时区不是真实的,除了作为偏好设置(在用户的表中)偏移视图中的时间。完成数学运算的坐标系应该是一致的。
然后每个频率对应一个cronjob设置:每天,星期一,星期二等。真的,从表格中的数据来看,你不需要两列,除非你认为这是改变。
然后,当cron触发时,使用调度程序(如linux AT
)来处理电子邮件何时消失。这更多的是系统级调度问题,或者至少,我更信任系统来处理这个问题。它需要处理重启系统的情况,包括服务和应用程序。
但有一点需要注意的是,如果您要发送大量邮件,实际上无法保证邮件会根据喜好发送。您可能实际上必须将其限制回来以避免被转储/阻止(例如,1000 /消息传播超过一小时)。不同的网络将具有不同的使用阈值。
基本上是这样的:
cron - &gt; at - &gt;发送邮件
答案 3 :(得分:0)
我认为你可以为此目的利用这个宝石:https://github.com/bvandenbos/resque-scheduler
此致
答案 4 :(得分:0)
您可以使用 DelayedJob 或 Resque Gems,它们将计划任务的每个实例表示为数据库表中的一行。有一些方法可以从日历中触发,安排和撤销任务。
答案 5 :(得分:0)
我刚刚为我的项目实现了这个,我发现一个非常简单的方法就是使用Whenever Gem(在这里找到https://github.com/javan/whenever)。
要开始使用,请转到您的应用并输入
gem 'whenever'
然后使用:
wheneverize .
在终端。这将在配置文件夹中创建schedule.rb文件。
您将规则放在schedule.rb中(如下所示)并让它们调用某些方法 - 例如,我调用模型方法DataProviderUser.cron,它将运行我在那里的任何代码。
创建此文件后,要启动cron作业,请在命令行中使用:
whenever
whenever -w
和
whenever -c
停止/清除cron作业。
github上的gems文档非常有用,但我建议你将输出设置为你自己的日志文件(正如我在下面所做的那样)。希望有所帮助:)
在我的schedule.rb中我有:
set :output, 'log/cron.log'
every :hour do
runner "DataProviderUser.cron('hourly')"
end
every :day do
runner "DataProviderUser.cron('daily')"
end
every '0 0 * * 0' do
runner "DataProviderUser.cron('weekly')"
end
every 14.days do
runner "DataProviderUser.cron('fortnightly')"
end
every :month do
runner "DataProviderUser.cron('monthly')"
end