Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How To Set ActiveRecord Limits by Subscription Plan Type in Rails 4.2 App?

I have a multi-tenant Rails 4.2 app using Devise for Authentication. I have setup subscription plans as follows: Individual{Monthly & Yearly}, Business{Monthly & Yearly}. The Individual plan has a limit of 5 users and 5 plans. The Business plan has unlimited users and plans.

I have been trying to setup a way to stop users on the Individual plans from being able to create new Plan and User records when they have reached their limit.

I have tried this: in rails how to limit users post count saved in database before asking to upgrade their account

My code looked like this in the "plan" model:

before_save :check_plan_quota
private

def check_plan_quota
if self.account.plans.count >= 5
  @quota_warning = "Plan limit reached."
  return false
 end
end

And I had this in my "dashboard" controller (because this is where the plans are created and listed):

before_filter :check_quota
  private

 def check_quota
  if Plan.count >= 5
    @quota_warning = "Plan limit reached."
  end
end

I was able to get it to work where the user could not add any more records once they reached 5 plans and also made it so the "add plan" button went away based on the @quota_warning.

          <p>
      <% if @quota_warning.present? %>
       <button type="button" class="btn btn-default"> You have <%= Plan.count %> Plans </button> &nbsp;&nbsp; - &nbsp;&nbsp; <button type="button" class="btn btn-secondary"> <%= @quota_warning %></button> 
       <% else %>
        <a href="/plans/new" class="btn btn-primary" data-toggle="modal" data-target="#styledModal">New Plan</a> &nbsp;&nbsp; - &nbsp;&nbsp; <button type="button" class="btn btn-default"> You have <%= Plan.count %> Plans </button>
        <% end %> 
      </p>

BUT This restricted all users to only 5 plans and try as I may I was not able to get an "if" statement working that would only apply this limit to users of the individual monthly or yearly plans.

Then I tried this on the "user" controller.

before_filter :check_user_limit, :only => :create

def check_user_limit
  redirect_to new_user_url if current_account.reached_user_limit?
end

And in the "account" model I put.

class Account < ActiveRecord::Base

has_many :users, dependent: :destroy, :inverse_of => :account
accepts_nested_attributes_for :users

has_one :admin, -> { where(admin: true) }, :class_name => "User"
accepts_nested_attributes_for :admin

:user_limit => Proc.new {|a| a.users.count }

has_many :plans, dependent: :destroy, :inverse_of => :account
accepts_nested_attributes_for :plans

:plan_limit => Proc.new {|a| a.plans.count }

The user_limit and plan_limit fields were added to the account column in the database so each account should have these limits set on signup. As you can imagine one can't use the above code like that but being new to rails, I don't know how to use it better or even if it's the best route.

The first way I tried seemed simple but I couldn't get it work with something like

if account.user.plan.stripe_id = 'monthly' or 'yearly and Plan.count >= 5
@quota_warning = "Limit reached"
end

My app structure is like so:

  • Account - owns all the features and the user.
  • User - belongs to the account and owns the subscription via User.plan but the features don't belong to user. I don't want the features tied to the user via belongs_to.

The only limit I need to focus on is for individual accounts since the pro accounts have no limits.

It seems like using the user_limit and plan_limit code in the model and the simple check on create in the controller is a little better than the other but I can't get it to work. I'm not sure if I like having the db fields for that either. I understand what the Proc.new code is doing but not what I can apply it to. I tried applying it to the end of has_one :admin but got the following error:

Unknown key: :user_limit. Valid keys are: :class_name, :class, :foreign_key, :validate, :autosave, :dependent, :primary_key, :inverse_of, :required, :as, :foreign_type

I'm not sure if this is enough info to help me but if I need to add more info, please let me know.

like image 310
T. Sanders Avatar asked Nov 20 '25 13:11

T. Sanders


1 Answers

I'd break this down into bite-sized methods / chunks of logic, then build the functionality you want based on that. Something along the lines of:

INDIVIDUAL_USER_LIMIT = 5
INDIVIDUAL_PLAN_LIMIT = 5

def individual_plan?
  self.plan.type == :individual # or some such logic.
end

def over_user_limit?
  self.individual_plan? && self.users.count > INDIVIDUAL_USER_LIMIT
end

def over_plan_limit?
  self.individual_plan? && self.plans.count > INDIVIDUAL_PLANS_LIMIT
end

Probably also worth your time to take a good look at implementing a custom validator, which would look something like

class UserLimitValidator < ActiveModel::Validator
  def validate(account)
    if account.over_user_limit?
      account.errors[:users] << 'Too many users for your plan!'
    end
  end
end

And allow you to validate that the models you are saving have the intended restraints on them.

As a thing to be aware of while implementing this, be wary of calling self.plans.count or self.users.count, as it makes an additional database query and can cause some nasty performance issues at scale, such as the N+1 query problem.

like image 185
gdpelican Avatar answered Nov 23 '25 04:11

gdpelican



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!