Rails Modeling And/Or Logic in a database model with Acts As Taggable On

44 Views Asked by At

I am trying to model logic that would allow a user defined search based on boolean logic to return a certain subset of TaggedModel. Below is the code. It has not been tested and probably has typos as it was written on SO just to think through the question.

# Basic widget, could be anything.
class TaggedModel < ActiveRecord
  acts_as_taggable_on
end

# Represents a single piece of logic that can
# be chained together to create complex queries.
# Currently this would create n + 1 db queries, n = number of statements 
# @attr inner_join_type [String] And/Or when it comes to tags within the statement.
# @attr preceding_statement [TagStatement] @optional The statement preceding this one.
# @attr preceding_statement_join_type [String] @optional And/Or to apply with
class TagStatement < ActiveRecord
  acts_as_taggable_on

  JOIN_TYPES = ['or','and']

  belongs_to :preceding_statement, optional: true, class: 'TagStatement'
  has_one :subsequent_statement, class: 'TagStatement', foreign_key: :preceding_statement_id

  validates :inner_join_type, presence: true, inclusion: { in: JOIN_TYPES }
  validates :preceding_statement_join_type, presence: true, inclusion: { in: JOIN_TYPES }, if: :preceding_statement

  # Scope for all master statements.
  # @return [ActiveRecord::Relation]
  def self.master_statements
    where(preceding_statement_id: nil)
  end

  # If the statement is a master statement.
  # @return [Boolean]
  def is_master
    preceding_statement_id.nil?
  end
  
  # The basic statement applying to a single tagged model.
  # @return [ActiveRecord::Relation<TaggedModel>]
  def statement
    if inner_join_type === 'or'
      TaggedModel.tagged_with(tags, any: true)
    else
      TaggedModel.tagged_with(tags, match_all: true)
    end
  end
  
  # Chains all the statements from this record and subsequent records.
  # TODO: Write all the logic in here. Some sort of looping statement to go
  # through all the records returned by the statements of the current model
  # plus any subsequent models adding removing as necessary the ids from the
  # ids array.
  # @return [ActiveRecord::Relation<TaggedModel> The tagged models that apply given statement logic
  def chained_statement(ids=[])
    ids = []
    if is_master
      ids = (ids + statement.&ids).uniq # Uniq probably extra here
    else
      if preceding_statement_join_type == 'or'
        ids.push(statement.&ids).uniq # Or so just add all ids together
      else
        ids = ids & statement.&ids # AND so remove any ids not present in statement ids.
      end
    end
    if subsequent_statement
      ids = subsequent_statement.chained_statement(ids)
    end
    ids
  end
end

Examples of things a user could do:

Create a set of statements that would find models tagged with

('blue' OR 'red') AND ('green') => Models tagged blue or red but always with green. ('blue' AND 'red') OR ('green' AND 'RED') => Models tagged with blue and red or green and red.

I feel like I'm on the right track but maybe I'm reinventing the wheel?

0

There are 0 best solutions below