Mark Murphy
Mark Murphy

Reputation: 2874

Rails subscribable models for notifications

I have a few models that I would like users to be able to subscribe or unsubscribe to i.e. opt-in or out of receiving notifications when certain events happen.

For example, I have a Video model which users can comment on and review. If a user subscribes to a video they would receive notifications (similar to Github's) when something happens e.g. comments, reviews, status changes, etc.

I'm wondering how to set this up in terms of models and their relationships to one another.

For example, would I create a generic NotificationSubscription model like so?:

class NotificationSubscription
  belongs_to :subscribable, polymorphic: true
  belongs_to :subscriber, class_name: "User", foreign_key: "user_id"
end

class User
  has_many :notification_subscriptions, dependant: :destroy
end

Then in the the subscribable models:

class Video
  has_many :notification_subscriptons, as: :subscribable, dependant: :destroy
  has_many :notification_subscribers, through: :notification_subscriptons, source: :subscriber
end

or with a concern:

module Subscribable
  extend ActiveSupport::Concern

  included do
    has_many :notification_subscriptions, as: :subscribable, dependent: :destroy
    has_many :subscribers, through: notification_subscriptions
  end

  module ClassMethods
  end

  def subscription_for(subscriber)
    subscriber.try(:notification_subscriptions).try(:find_by, subscribable: self)
  end

  def subscribe(subscriber)
    return false if subscription_for(subscriber).present?
    subscription = NotificationSubscription.new(subscribable: self, subscriber: subscriber)
    subscription.save
  end

  def unsubscribe(subscriber)
    subscription = subscription_for(subscriber)
    return false unless subscription.present?
    subscription.destroy
  end
end

class Video
  include Subscribable
end

and the notification_subscription schema:

class CreateNotificationSubscriptions < ActiveRecord::Migration
  def change
    create_table :notification_subscriptions do |t|
      t.references :user, index: true
      t.references :subscribable, polymorphic: true, index: true
      t.boolean    :subscribed
      t.boolean    :ignored
      t.string     :reason

      t.timestamps
    end

    add_index :notifications_subscriptions, [:user_id, :subscribable_id, :subscribable_type], unique: true
  end
end

Upvotes: 1

Views: 830

Answers (1)

WattsInABox
WattsInABox

Reputation: 4636

I would use a polymorphic association, but your verbiage is slightly wrong, I think. The example given is:

http://guides.rubyonrails.org/association_basics.html#polymorphic-associations

class Picture < ActiveRecord::Base
  belongs_to :imageable, polymorphic: true
end

class Employee < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

class Product < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

It's much simpler than your polymorphic code was making it. It's really a combination of what you have in your first and second examples.

Slightly off-topic, but hopefully this is helpful, I would use ActiveSupport::Notifications for the events themselves. This way you can fire an event and have a separate object subscribe to that event to send the subscribers (users) notifications out via direct email or by kicking off a Sidekiq job. http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html

Upvotes: 1

Related Questions