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?