Salil の提案に代わるものは、代わりに単一のビットマスクされた列を使用することですsubscription_mask
。
まず、適切なモデルで配列定数を設定します (仮定User
):
# user.rb
SUBSCRIPTION_TYPES = ["Daily", "Weekly", "Monthly"]
この配列の最後にある限り、サブスクリプションの種類を後で追加できます。この 1 つの列に大量のフラグを非常に効率的に格納できます。
次に、仮想属性を使用して、ビットマスクと対話する一連のメソッドを作成できますsubscriptions
。
# Make subscriptions attr_accessible so we can mass assign to it (useful for forms)
attr_accessible :subscriptions
# Basic setter, overwrites all subscriptions with an array of subscriptions
def subscriptions=(subs)
self.subscription_mask = (SUBSCRIPTION_TYPES & subs).map{ |s| 2**SUBSCRIPTION_TYPES.index(r) }.inject(0, :+)
end
# Basic getter, returns an array of the set subscriptions
def subscriptions
SUBSCRIPTION_TYPES.reject do |s|
((subscription_mask || 0) & 2**SUBSCRIPTION_TYPE.index(s).zero?
end
end
# Check if a subscription is set
def has_subscription? (sub)
self.subscriptions.includes? sub
end
# Add subscriptions to the current mask without affecting any others
def set_subscriptions (*subs)
self.subscriptions = self.subscriptions | subs
end
# Remove subscriptions from the current mask without affecting any others
def unset_subscriptions (*subs)
self.subscriptions = self.subscriptions - subs
end
そこにはいくつかの方法がありますが、さまざまな状況で役立つことがわかりました。
これらのメソッドを使用すると、次のようにユーザーのサブスクリプションを設定できます。
@user.subscriptions = ["Weekly", "Monthly"]
そして、次のサブスクリプションがあるかどうかを確認します。
@user.has_subscription? "Daily" # => false, in this case
その同じユーザーが毎日のメッセージを購読していて、毎週のメッセージを購読解除した場合:
@user.set_subscriptions "Daily"
@user.unset_subscriptions "Weekly" # => ["Weekly", "Monthly"]
# Or, to do both changes at once (you need to re-set Weekly though)
@user.subscriptions = ["Weekly", "Monthly"]
このアプローチのもう 1 つの利点は、これをクエリで使用できることです。たとえば、毎週のサブスクリプションですべてのユーザーを取得するには、次のようにします。
User.where("subscription_mask & ? > 0", 2**SUBSCRIPTION_TYPES.index("Weekly"))
少し面倒ですが、メソッドまたはスコープを使用して簡単にクリーンアップできます。