0
votes

I'm having some trouble coming up with the most efficient data model for storing logical criteria. The criteria can be as follows:

  • Field = Value
  • Field != Value
  • Field1 = Value1 AND Field2 = Value2
  • Field1 = Value1 OR Field2 = Value2

But can also be nested as so:

  • (Field1 = Value1 OR Field2 = Value2) AND Field3 = Value3

The nesting can be infinitely deep.

I started with a model like this:

Criterion
  Field Name
  Comparison Operator
  Comparison Value

This allows me to express simple criteria such as 'Field = Value'.

How should I go about chaining the Criterion together using AND/OR statements and what is the best way to handle the nesting?

FYI (might add more context) - this is for a Rails/Mongoid project. I have a Document which embeds_many :criteria. I need to be able to call Document.criteria_string and have the document iterate over all the criteria and assemple it into a single statement accounting for the nesting, chaining and positioning of the criteria.

Thanks!

1

1 Answers

2
votes

I think I understand what you are trying to do and the best way to approach this is to think of your logical criteria as a tree where each criteria can either be the leaf (and have a name, operator and value) or it can be a reference to another criteria. The criteria_string should be smart enough to handle the different types of Criteria you need. - A leaf of the tree is a key or value (defines the string key) - A branch of the tree is a Criteria itself

The Criteria can be modeled as such

class Criteria
  include Mongoid::Document
  field :string, type: String
  field :operator, type: String
  embeds_one :name, class_name: 'Criteria'
  embeds_one :value, class_name: 'Criteria'

  def criteria_string
    string || "#{name.criteria_string} #{operator} #{value.criteria_string}"
  end
end

The key is to have both the name and the value be another embedded criteria and only have a string defined when the Criteria represents an actual name or a value string

Criteria.new(
  name: Criteria.new(string: 'city'),
  operator: '!=',
  value: Criteria.new(string: 'San Francisco') 
).criteria_string

#=> "city != San Francisco"