kibaekr
kibaekr

Reputation: 1349

Rails: saving filter conditions on attributes and checking whether conditions pass?

I have a SaaS application where an Account has many Users.

In an Account, the account owner can specify certain filters on the users that are applying to be part of the account. For instance, he can set that these requirement conditions must be met in order for user to be accepted into account : user.age > 21, user.name.count > 4.

I was thinking of creating a FilterCondition model, which belongs to Account, where a row could look like account_id: 1, attribute: "age", condition_string: "> 21" or account_id: 1, attribute: "phone_type", condition_string: "== iphone"

and then when I want to only accept users that meet these conditions do something like

  #User.rb
  def passes_requirements?
    account.filter_conditions.each do |filter_conditions|
      attr = filter_condition.attribute
      return false if self.attr != filter_condition.condition
    end 
    true
  end

I know that is completely wrong syntax, but it should get the point across on what I would like to do.

Any suggested ways to allow accounts to save requirement conditions, and then check on Users to see if they meet those requirements?

Upvotes: 4

Views: 1386

Answers (1)

Pete
Pete

Reputation: 2246

It will be easier if the condition string is separated into a comparator (e.g >) and the value to be compared:

class FilterCondition < ActiveRecord::Base
  belongs_to :account

  validates :attribute, presence: true
  validates :comparator, presence: true
  validates :value, presence: true

  def matching_users(query_chain = User.where(account: account))
    query_chain.where("#{attribute} #{safe_comparator} ?", value)
  end

  private
  def safe_comparator
    safe_values = ['=', '>', '>=', '<', '<='] # etc
    return comparator if safe_values.include? comparator
    ''
  end
end

The safe_comparator method reduces the risk of SQL injection into the query. Chaining a collection of filters is a bit tricky, but something like the following idea should work.

class Account < ActiveRecord::Base
  #....
  has_many :filter_conditions
  def filtered_users
    query = User.where(account: self)
    filter_conditions.each do |filter_condition|
      query = filter_condition.matching_users(query)
    end
    query
  end

end


account = Account.first

filter_1 = FilterCondition.create(
  account: account,
  attribute: :age,
  comparator: '>=',
  value: 21
)
filter_2 = FilterCondition.create(
  account: account,
  attribute: :age,
  comparator: '<=',
  value: 99
)


account.filtered_users

Upvotes: 5

Related Questions