2013-02-19 11 views
10

Gli utenti si iscrivono alle e-mail contenenti gli ultimi video, ma impostano anche quando ottenere tali e-mail.Come implementare correttamente le attività ricorrenti con l'ora/frequenza impostata dall'utente

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 

Come possiamo inviare le nostre email in tempo e frequenza esatta scelti dagli utenti nel loro fuso orario senza rovinare (come l'invio doppie messaggi di posta elettronica o manca il tempo)

Le uniche frequenze sono giornalieri e settimanali, se giornaliero allora il giorno è nullo.

Io uso redis come database per questo, fammi sapere come farlo nel modo giusto!

+0

se si stesse utilizzando delayed_job - https://github.com/collectiveidea/delayed_job - è possibile impostare il run_at su ogni lavoro a nulla vuoi (abbina il tempo di Abbonamento, un lavoro per programma) e poi quando il lavoro sta completando fai in modo che si accenda da solo in base all'impostazione del tuo Scheduler – house9

+0

Quanto esatto devi essere? Se è di un minuto, è accettabile? –

risposta

0

Penso che si può sfruttare questo gioiello per questo scopo: https://github.com/bvandenbos/resque-scheduler

saluti,

+0

Non sono sicuro se sia la soluzione giusta, sembra che non sia possibile creare dinamicamente attività ricorrenti create (non utilizzando un file di configurazione yaml) – Ryan

+0

@Ryan Negli stati di configurazione dell'attività altrimenti: # Se si vuole essere in grado di modificare dinamicamente la pianificazione , # decommenta questa riga. Una pianificazione dinamica può essere aggiornata tramite i metodi # Resque :: Scheduler.set_schedule (e remove_schedule). # Quando dynamic è impostato su true, il processo di pianificazione cerca le modifiche di pianificazione di # e le applica al volo. # Nota: questa funzione è disponibile solo in> = 2.0.0. #Resque :: Scheduler.dynamic = true – fmendez

0

è possibile utilizzare il DelayedJob o Resque Gems, che rappresentano ciascuna istanza di un'operazione pianificata come una riga in una tabella del database. Esistono metodi per l'attivazione, la pianificazione e il ritiro di attività dal calendario.

1

Ho usato delayed_job per attività simili in passato. Probabilmente puoi usare la stessa tecnica con resque. In sostanza, è necessario pianificare il lavoro successivo alla fine del lavoro corrente.

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 

Riscadenziamento avvertimenti

Aggiornare il codice per gestire la terminazione del ciclo. È possibile gestirlo aggiungendo una colonna boolean denominata active (impostarla su true per impostazione predefinita). Per disabilitare l'abbonamento, impostare la colonna su false.

def send_email 
    return unless active? 
    # do stuff and then schedule the next run 
    ensure 
    send_email if active? 
    end 

Impostare la max_attempts per il lavoro a 1. In caso contrario si inondare la coda. Nella soluzione sopra, i lavori per send_email verranno tentati una volta.

+0

Aggiornamento della risposta per gestire 'max_attempts' in modo elegante. –

2

Ho intenzione di espandere la risposta di fmendez utilizzando lo resque-scheduler gem.

In primo luogo, creiamo il lavoratore che invia i messaggi di posta elettronica

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 

Nel vostro modello di sottoscrizione, aggiungere un metodo next_sending_time per calcolare la prossima volta che una e-mail deve essere inviato.

# 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 
0

Ho appena implementato questo per il mio progetto, ho trovato un modo molto semplice per farlo è quello di utilizzare il Ogni volta che Gem (trovate qui https://github.com/javan/whenever).

Per iniziare, vai alla tua app e mettere

gem 'whenever' 

quindi utilizzare:

wheneverize . 

nel terminale.Questo creerà un file schedule.rb nella tua cartella di configurazione.

Inserisci le tue regole nel tuo schedule.rb (mostrato sotto) e consenti loro di chiamare determinati metodi - ad esempio, il mio chiama il metodo modello DataProviderUser.cron che eseguirà qualsiasi codice che abbia lì.

Una volta creato il file, per avviare il processo di cron, sull'uso riga di comando:

whenever 
whenever -w 

e

whenever -c 

fermate/cancella i cron jobs.

La documentazione delle gemme su github è davvero utile ma ti consiglio di impostare l'output sul tuo file di registro (come ho fatto di seguito). Speranza che aiuta :)

nel mio schedule.rb ho:

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 
1

Questo è più di un problema a livello di sistema che essere specifica al rubino.

Prima di tutto, memorizza tutto il tuo tempo internamente come GMT. I fusi orari non sono reali, a parte l'impostazione di preferenza (nella tabella dell'utente) che compensa il tempo nella visualizzazione. Il sistema di coordinate in cui viene eseguita la matematica dovrebbe essere coerente.

Quindi ogni frequenza corrisponde a un'impostazione cronjob: giornaliera, lunedì, martedì, ecc. In realtà, dai dati nella tabella, non sono necessarie due colonne, a meno che non si veda questo come cambiando.

Quindi, quando il cron si attiva, utilizzare un programmatore (come linux AT) da gestire quando la posta elettronica si spegne. Questo è più un problema di pianificazione a livello di sistema, o almeno, mi fiderei che il sistema sia più in grado di gestirlo. Ha bisogno di gestire il caso in cui il sistema viene riavviato, compresi i servizi e l'app.

Un avvertimento, tuttavia, è che se si invia un grande volume di posta, non si può davvero garantire che la posta verrà inviata in base alle preferenze. Potrebbe essere necessario ridurlo indietro per evitare di essere scaricato/bloccato (per esempio, 1000/messaggi distribuiti su un'ora). Diverse reti avranno soglie di utilizzo differenti.

Quindi, fondamentalmente:

cron -> a -> inviare la posta