module ActivityNotification # Defines API for subscription included in Subscription model. module SubscriptionApi extend ActiveSupport::Concern included do # Selects filtered subscriptions by key. # @example Get filtered subscriptions of the @user with key 'comment.reply' # @subscriptions = @user.subscriptions.filtered_by_key('comment.reply') # @scope class # @param [String] key Key of the subscription for filter # @return [ActiveRecord_AssociationRelation, Mongoid::Criteria] Database query of filtered subscriptions scope :filtered_by_key, ->(key) { where(key: key) } # Selects filtered subscriptions by key with filter options. # @example Get filtered subscriptions of the @user with key 'comment.reply' # @subscriptions = @user.subscriptions.filtered_by_key('comment.reply') # @example Get custom filtered subscriptions of the @user # @subscriptions = @user.subscriptions.filtered_by_options({ custom_filter: ["created_at >= ?", time.hour.ago] }) # @scope class # @param [Hash] options Options for filter # @option options [String] :filtered_by_key (nil) Key of the subscription for filter # @option options [Array|Hash] :custom_filter (nil) Custom subscription filter (e.g. ["created_at >= ?", time.hour.ago]) # @return [ActiveRecord_AssociationRelation, Mongoid::Criteria] Database query of filtered subscriptions scope :filtered_by_options, ->(options = {}) { options = ActivityNotification.cast_to_indifferent_hash(options) filtered_subscriptions = all if options.has_key?(:filtered_by_key) filtered_subscriptions = filtered_subscriptions.filtered_by_key(options[:filtered_by_key]) end if options.has_key?(:custom_filter) filtered_subscriptions = filtered_subscriptions.where(options[:custom_filter]) end filtered_subscriptions } # Orders by latest (newest) first as created_at: :desc. # @return [ActiveRecord_AssociationRelation, Mongoid::Criteria] Database query of subscriptions ordered by latest first scope :latest_order, -> { order(created_at: :desc) } # Orders by earliest (older) first as created_at: :asc. # @return [ActiveRecord_AssociationRelation, Mongoid::Criteria] Database query of subscriptions ordered by earliest first scope :earliest_order, -> { order(created_at: :asc) } # Orders by latest (newest) first as subscribed_at: :desc. # @return [ActiveRecord_AssociationRelation, Mongoid::Criteria] Database query of subscriptions ordered by latest subscribed_at first scope :latest_subscribed_order, -> { order(subscribed_at: :desc) } # Orders by earliest (older) first as subscribed_at: :asc. # @return [ActiveRecord_AssociationRelation, Mongoid::Criteria] Database query of subscriptions ordered by earliest subscribed_at first scope :earliest_subscribed_order, -> { order(subscribed_at: :asc) } # Orders by key name as key: :asc. # @return [ActiveRecord_AssociationRelation, Mongoid::Criteria] Database query of subscriptions ordered by key name scope :key_order, -> { order(key: :asc) } end class_methods do # Returns key of optional_targets hash from symbol class name of the optional target implementation. # @param [String, Symbol] optional_target_name Class name of the optional target implementation (e.g. :amazon_sns, :slack) # @return [Symbol] Key of optional_targets hash def to_optional_target_key(optional_target_name) ("subscribing_to_" + optional_target_name.to_s).to_sym end # Returns subscribed_at parameter key of optional_targets hash from symbol class name of the optional target implementation. # @param [String, Symbol] optional_target_name Class name of the optional target implementation (e.g. :amazon_sns, :slack) # @return [Symbol] Subscribed_at parameter key of optional_targets hash def to_optional_target_subscribed_at_key(optional_target_name) ("subscribed_to_" + optional_target_name.to_s + "_at").to_sym end # Returns unsubscribed_at parameter key of optional_targets hash from symbol class name of the optional target implementation. # @param [String, Symbol] optional_target_name Class name of the optional target implementation (e.g. :amazon_sns, :slack) # @return [Symbol] Unsubscribed_at parameter key of optional_targets hash def to_optional_target_unsubscribed_at_key(optional_target_name) ("unsubscribed_to_" + optional_target_name.to_s + "_at").to_sym end end # Subscribes to the notification and notification email. # # @param [Hash] options Options for subscribing to the notification # @option options [DateTime] :subscribed_at (Time.current) Time to set to subscribed_at and subscribed_to_email_at of the subscription record # @option options [Boolean] :with_email_subscription (true) If the subscriber also subscribes notification email # @option options [Boolean] :with_optional_targets (true) If the subscriber also subscribes optional_targets # @return [Boolean] If successfully updated subscription instance def subscribe(options = {}) subscribed_at = options[:subscribed_at] || Time.current with_email_subscription = options.has_key?(:with_email_subscription) ? options[:with_email_subscription] : true with_optional_targets = options.has_key?(:with_optional_targets) ? options[:with_optional_targets] : true new_attributes = { subscribing: true, subscribed_at: subscribed_at, optional_targets: optional_targets } new_attributes = new_attributes.merge(subscribing_to_email: true, subscribed_to_email_at: subscribed_at) if with_email_subscription if with_optional_targets optional_target_names.each do |optional_target_name| new_attributes[:optional_targets] = new_attributes[:optional_targets].merge( Subscription.to_optional_target_key(optional_target_name) => true, Subscription.to_optional_target_subscribed_at_key(optional_target_name) => subscribed_at) end end update(new_attributes) end # Unsubscribes to the notification and notification email. # # @param [Hash] options Options for unsubscribing to the notification # @option options [DateTime] :unsubscribed_at (Time.current) Time to set to unsubscribed_at and unsubscribed_to_email_at of the subscription record # @return [Boolean] If successfully updated subscription instance def unsubscribe(options = {}) unsubscribed_at = options[:unsubscribed_at] || Time.current new_attributes = { subscribing: false, unsubscribed_at: unsubscribed_at, subscribing_to_email: false, unsubscribed_to_email_at: unsubscribed_at, optional_targets: optional_targets } optional_target_names.each do |optional_target_name| new_attributes[:optional_targets] = new_attributes[:optional_targets].merge( Subscription.to_optional_target_key(optional_target_name) => false, Subscription.to_optional_target_unsubscribed_at_key(optional_target_name) => subscribed_at) end update(new_attributes) end # Subscribes to the notification email. # # @param [Hash] options Options for subscribing to the notification email # @option options [DateTime] :subscribed_to_email_at (Time.current) Time to set to subscribed_to_email_at of the subscription record # @return [Boolean] If successfully updated subscription instance def subscribe_to_email(options = {}) subscribed_to_email_at = options[:subscribed_to_email_at] || Time.current update(subscribing_to_email: true, subscribed_to_email_at: subscribed_to_email_at) end # Unsubscribes to the notification email. # # @param [Hash] options Options for unsubscribing the notification email # @option options [DateTime] :subscribed_to_email_at (Time.current) Time to set to subscribed_to_email_at of the subscription record # @return [Boolean] If successfully updated subscription instance def unsubscribe_to_email(options = {}) unsubscribed_to_email_at = options[:unsubscribed_to_email_at] || Time.current update(subscribing_to_email: false, unsubscribed_to_email_at: unsubscribed_to_email_at) end # Returns if the target subscribes to the specified optional target. # # @param [Symbol] optional_target_name Symbol class name of the optional target implementation (e.g. :amazon_sns, :slack) # @param [Boolean] subscribe_as_default Default subscription value to use when the subscription record does not configured # @return [Boolean] If the target subscribes to the specified optional target def subscribing_to_optional_target?(optional_target_name, subscribe_as_default = ActivityNotification.config.subscribe_as_default) optional_target_key = Subscription.to_optional_target_key(optional_target_name) subscribe_as_default ? !optional_targets.has_key?(optional_target_key) || optional_targets[optional_target_key] : optional_targets.has_key?(optional_target_key) && optional_targets[optional_target_key] end # Subscribes to the specified optional target. # # @param [String, Symbol] optional_target_name Symbol class name of the optional target implementation (e.g. :amazon_sns, :slack) # @param [Hash] options Options for unsubscribing to the specified optional target # @option options [DateTime] :subscribed_at (Time.current) Time to set to subscribed_[optional_target_name]_at in optional_targets hash of the subscription record # @return [Boolean] If successfully updated subscription instance def subscribe_to_optional_target(optional_target_name, options = {}) subscribed_at = options[:subscribed_at] || Time.current update(optional_targets: optional_targets.merge( Subscription.to_optional_target_key(optional_target_name) => true, Subscription.to_optional_target_subscribed_at_key(optional_target_name) => subscribed_at) ) end # Unsubscribes to the specified optional target. # # @param [String, Symbol] optional_target_name Class name of the optional target implementation (e.g. :amazon_sns, :slack) # @param [Hash] options Options for unsubscribing to the specified optional target # @option options [DateTime] :unsubscribed_at (Time.current) Time to set to unsubscribed_[optional_target_name]_at in optional_targets hash of the subscription record # @return [Boolean] If successfully updated subscription instance def unsubscribe_to_optional_target(optional_target_name, options = {}) unsubscribed_at = options[:unsubscribed_at] || Time.current update(optional_targets: optional_targets.merge( Subscription.to_optional_target_key(optional_target_name) => false, Subscription.to_optional_target_unsubscribed_at_key(optional_target_name) => unsubscribed_at) ) end # Returns optional_target names of the subscription from optional_targets field. # @return [Array] Array of optional target names def optional_target_names optional_targets.keys.select { |key| key.to_s.start_with?("subscribing_to_") }.map { |key| key.slice(15..-1) } end protected # Validates subscribing_to_email cannot be true when subscribing is false. def subscribing_to_email_cannot_be_true_when_subscribing_is_false if !subscribing && subscribing_to_email? errors.add(:subscribing_to_email, "cannot be true when subscribing is false") end end # Validates subscribing_to_optional_target cannot be true when subscribing is false. def subscribing_to_optional_target_cannot_be_true_when_subscribing_is_false optional_target_names.each do |optional_target_name| if !subscribing && subscribing_to_optional_target?(optional_target_name) errors.add(:optional_targets, "#Subscription.to_optional_target_key(optional_target_name) cannot be true when subscribing is false") end end end end end