Skip to content

Instantly share code, notes, and snippets.

@data-doge
Last active March 29, 2016 04:42
Show Gist options
  • Save data-doge/7b06d01e6b05d1181c9b to your computer and use it in GitHub Desktop.
Save data-doge/7b06d01e6b05d1181c9b to your computer and use it in GitHub Desktop.
# ./lib/tasks/cobudget.rake
# scheduled to run every 10 minutes on our heroku server
namespace :cobudget do
desc "delivers recent activity digest emails to users"
task deliver_recent_activity_digest: :environment do
DeliverRecentActivityDigest.run!
end
end
# ./app/extras/deliver_recent_activity_digest.rb
# goes through every user with active memberships, and
# 1. sends them an email containing recent activity for their groups
# 2. sets the time range for the next, scheduled fetch of recent activity
class DeliverRecentActivityDigest
def self.run!
current_time = DateTime.now.utc
User.with_active_memberships.each do |user|
if user.subscription_tracker.ready_to_fetch?(current_time: current_time)
UserService.send_recent_activity_email(user: user)
user.subscription_tracker.update_next_fetch_time_range!
end
end
end
end
# ./spec/extras/deliver_recent_activity_digest_spec.rb
require 'rails_helper'
describe "DeliverRecentActivityDigest" do
after { Timecop.return }
describe "#run!" do
before do
allow(UserService).to receive(:send_recent_activity_email)
end
context "user has no active memberships" do
before do
create(:user)
end
it "does nothing" do
expect(UserService).not_to receive(:send_recent_activity_email)
DeliverRecentActivityDigest.run!
end
end
context "user has at least one active membership" do
before do
make_user_group_member
end
context "current_time is before user's next scheduled fetch" do
it "does nothing" do
time = (user.subscription_tracker.next_recent_activity_fetch_scheduled_at - 1.minute).utc
Timecop.freeze(time) do
expect(UserService).not_to receive(:send_recent_activity_email)
DeliverRecentActivityDigest.run!
end
end
end
context "current_time is after user's next scheduled fetch" do
it "calls fetches activity for user and updates their subscription_tracker's next fetch_time_range" do
old_next_recent_activity_scheduled_at = user.subscription_tracker.next_recent_activity_fetch_scheduled_at
time = (old_next_recent_activity_scheduled_at + 1.minute).utc
Timecop.freeze(time) do
expect(UserService).to receive(:send_recent_activity_email)
DeliverRecentActivityDigest.run!
expect(user.subscription_tracker.reload.recent_activity_last_fetched_at).to eq(old_next_recent_activity_scheduled_at)
end
end
end
end
end
end
# ./app/models/subscription_tracker.rb
class SubscriptionTracker < ActiveRecord::Base
belongs_to :user
after_update :update_recent_activity_last_fetched_at
def next_recent_activity_fetch_scheduled_at
interval = case notification_frequency
when "hourly" then 1.hour
when "daily" then 1.day
when "weekly" then 1.week
end
recent_activity_last_fetched_at + interval if interval
end
def ready_to_fetch?(current_time: )
notification_frequency != "never" && current_time >= next_recent_activity_fetch_scheduled_at
end
def subscribed_to_any_activity?
comment_on_your_bucket || comment_on_bucket_you_participated_in || bucket_idea_created || bucket_started_funding || bucket_fully_funded || funding_for_your_bucket || funding_for_a_bucket_you_participated_in || your_bucket_fully_funded || recent_activity_last_fetched_at
end
def last_fetched_at_formatted
case notification_frequency
when "hourly" then recent_activity_last_fetched_at.in_time_zone((user.utc_offset || 0) / 60).strftime("%l:%M%P").strip
when "daily" then "yesterday"
when "weekly" then "last week"
end
end
def next_fetch_time_range
if recent_activity_last_fetched_at && next_recent_activity_fetch_scheduled_at
recent_activity_last_fetched_at..next_recent_activity_fetch_scheduled_at
end
end
def update_next_fetch_time_range!
update(recent_activity_last_fetched_at: next_recent_activity_fetch_scheduled_at)
end
private
def update_recent_activity_last_fetched_at
if self.notification_frequency_changed?
time_now = DateTime.now.utc
datetime = case self.notification_frequency
when "hourly" then time_now.beginning_of_hour.utc
when "daily" then (time_now.in_time_zone((self.user.utc_offset || 0) / 60).beginning_of_day + 6.hours).utc
when "weekly" then (time_now.in_time_zone((self.user.utc_offset || 0) / 60).beginning_of_week + 6.hours).utc
end
self.update_columns(recent_activity_last_fetched_at: datetime)
end
end
end
# ./spec/models/subscription_tracker.rb
# ./app/models/user.rb
require 'securerandom'
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
include DeviseTokenAuth::Concerns::User
...
after_create :create_default_subscription_tracker
has_many :groups, through: :memberships
has_many :memberships, foreign_key: "member_id", dependent: :destroy
has_one :subscription_tracker, dependent: :destroy
has_many :allocations, dependent: :destroy
has_many :comments, dependent: :destroy
has_many :contributions, dependent: :destroy
has_many :buckets, dependent: :destroy
scope :with_active_memberships, -> { joins(:memberships).where(memberships: {archived_at: nil}) }
validates :name, presence: true
validates_format_of :email, :with => /\A[^@]+@([^@\.]+\.)+[^@\.]+\z/
def name_and_email
"#{name} <#{email}>"
end
...
def membership_for(group)
memberships.find_by(group_id: group.id)
end
...
def active_groups
Group.joins(:memberships).where(memberships: {member: self, archived_at: nil})
end
private
...
def create_default_subscription_tracker
SubscriptionTracker.create(
user: self,
notification_frequency: "hourly",
recent_activity_last_fetched_at: DateTime.now.utc.beginning_of_hour
)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment