-
-
Save kaspth/c6797aa3a913d989215c66350235589d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module User::EventSubscriber | |
extend ActiveSupport::Concern | |
included do | |
has_many :event_subscriptions, dependent: :destroy | |
end | |
# Current.user.subscribe_to_events_from(Current.facility, **facility_event_params) | |
# Current.user.subscribe_to_events_from(Current.account, **account_event_params) | |
# ^ though I can't remember if params can be used with ** yet, may need explicit casting via to_h. | |
def subscribe_to_events_from(source, active: true, **events) | |
subscription_for(source).update! active: active, events: events | |
end | |
# Current.user.unsubscribe_from(Current.account) | |
def unsubscribe_from(source) | |
subscription_for(source).inactive! | |
end | |
# @user.subscription_for(facility) | |
def subscription_for(source) | |
event_subscriptions.find_or_initialize_by(source: source) | |
end | |
end | |
class Event::Subscription < ApplicationRecord | |
belongs_to :user | |
belongs_to :account, default: -> { source&.account } # I'd add a def account; self; end method to Account to ease this kind of duck typing | |
belongs_to :source, polymorphic: true | |
# Adds convenience accessor for one off checks e.g. subscription.new_reservation? | |
store :events, accessors: Events.all_events, coder: ActiveSupport::JSON | |
after_initialize :scope_to_known_source_events | |
# Adds convenience methods using Ruby 3.0 shorthand method syntax. Grouped and condensed to highlight their similarity and the pattern. | |
def active! = update! active: true | |
def inactive! = update! active: false | |
def broadcast_for?(event) | |
active? && enabled_for?(event) | |
end | |
def enabled_for?(event) | |
try(event) | |
end | |
private | |
# Means that it's safe to do @user.subscription_for(facility).events.each do |event| in the view | |
def scope_to_known_source_events | |
events.slice!(*Events.events_for(self)) | |
end | |
end | |
class Event::Subscription::Events | |
GENERAL_EVENTS = %w[ first second third ].freeze | |
# Should be able to add events to ^ or these arrays and have them pop up in the view as a valid new event to subscribe to. | |
TYPE_SPECIFIC_EVENTS = { | |
"Account" => %w[ first second ].freeze, | |
"Facility" => %w[ first second ].freeze | |
} | |
mattr_reader :all_events, default: GENERAL_EVENTS + TYPE_SPECIFIC_EVENTS.values.flatten.uniq | |
def self.events_for(subscription) | |
GENERAL_EVENTS + TYPE_SPECIFIC_EVENTS.fetch(subscription.source_type) # Use fetch to raise and prevent usage with unknown types | |
end | |
end | |
class SomeMailer < ApplicationMailer | |
after_action :prevent_delivery_for_disabled_events | |
def some_mail | |
@event = :first | |
mail subject: "Yo yo" | |
end | |
private | |
def prevent_delivery_for_disabled_events | |
mail.perform_deliveries = params[:recipient].subscription_for(@facility).broadcast_for?(@event) | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# I like the Feature class name! (And I dislike when conditional Features are called flags 😂) | |
# Here I've picked Rollout as the domain language to emphasize the relatively short lived nature of this preliminary feature access — and that there's an end in sight. | |
module ConditionalRollout | |
extend ActiveSupport::Concern | |
class_methods do | |
# Using before_action means you can attach the standard options too and gain flexibility for next to no cost. | |
# requires_preliminary_access_to :some_feature, only: :index, if: -> { Current.user.übercool? } | |
def requires_preliminary_access_to(feature_name, **options) | |
# I'd hoist the admin? conditional since it doesn't quite fit with the account level details. | |
# This also means a `admin_overrule: false` option could more easily be added to this method if need be. | |
before_action -> { enforce_access_to feature_name unless Current.user&.admin? }, **options | |
end | |
end | |
private | |
def enforce_access_to(feature_name) | |
unless current_account.has_preliminary_access_to?(feature) | |
raise AbstractController::ActionNotFound | |
end | |
end | |
end | |
# app/models/account/rollout.rb | |
# included in Account | |
# No need to extend ActiveSupport::Concern since we're just adding some constants and an instance method. | |
module Account::Rollout | |
BETA_ACCOUNTS = %w(some-domain) | |
FEATURES = { | |
some_feature: [] | |
} | |
def has_preliminary_access_to?(feature) | |
if domains = FEATURES.dig(feature.to_sym) | |
domain.in?(BETA_ACCOUNTS) || domain.in?(domains) | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment