lib/appfuel/domain/expr.rb in appfuel-0.2.3 vs lib/appfuel/domain/expr.rb in appfuel-0.2.4

- old
+ new

@@ -1,127 +1,96 @@ module Appfuel module Domain # Domain expressions are used mostly by the criteria to describe filter - # conditions. The class represents a basic expression like "id eq 6", the - # problem with this expression is that we need additional information in - # order to properly map it to something like a db expression. This call - # ensures that additional information exists. Most importantly we need - # a fully qualified domain name in the form of "feature.domain". + # conditions. The class represents a basic expression like "id = 6", the + # problem with this expression is that "id" is relative to the domain + # represented by the criteria. In order to convert that expression to a + # storage expression for a db, that expression must be fully qualified in + # the form of "features.feature_name.domain.id = 6" so that the mapper can + # correctly map to database attributes. This class provides the necessary + # interfaces to allow a criteria to qualify all of its relative expressions. + # It also allows fully qualifed expressions to be used. class Expr - include DomainNameParser - OPS = { - eq: '=', - gt: '>', - gteq: '>=', - lt: '<', - lteq: '<=', - in: 'IN', - like: 'LIKE', - ilike: 'ILIKE', - between: 'BETWEEN' - } - attr_reader :feature, :domain_basename, :domain_name, :domain_attr, :value + attr_reader :feature, :domain_basename, :domain_attr, :attr_list, :op, :value - # Assign the fully qualified domain name, its basename and its attribute - # along with the operator and value. Operator and value are assumed to - # be the first key => value pair of the hash. - # - # @example - # feature domain - # Expr.new('foo.bar', 'id', eq: 6) - # - # or - # global domain - # Expr.new('bar', 'name', like: '%Bob%') - # - # - # @param domain [String] fully qualified domain name - # @param domain_attr [String, Symbol] attribute name - # @param data [Hash] holds operator and value - # @option data [Symbol] the key is the operator and value is the value - # - # @return [Expr] - def initialize(domain, domain_attr, data) - fail "operator value pair must exist in a hash" unless data.is_a?(Hash) - @feature, @domain_basename, @domain_name = parse_domain_name(domain) + def initialize(domain_attr, op, value) + @attr_list = parse_domain_attr(domain_attr) + @op = op.to_s.strip + @value = value - operator, value = data.first - @domain_attr = domain_attr.to_s - self.op = operator - self.value = value + fail "op can not be empty" if @op.empty? + fail "attr_list can not be empty" if @attr_list.empty? + end - fail "domain name can not be empty" if @domain_name.empty? - fail "domain attribute can not be empty" if @domain_attr.empty? + def qualify_feature(feature, domain) + fail "this expr is already qualified" if qualified? + + attr_list.unshift(domain) + attr_list.unshift(feature) + attr_list.unshift('features') + self end - def feature? - !@feature.nil? + def qualify_global(domain) + fail "this expr is already qualified" if qualified? + attr_list.unshift(domain) + attr_list.unshift('global') + self end def global? - !feature? + attr_list[0] == 'global' end - # @return [Bool] - def negated? - @negated + def conjunction? + false end - def expr_string - data = yield domain_name, domain_attr, OPS[op] - lvalue = data[0] - operator = data[1] - rvalue = data[2] + def qualified? + attr_list[0] == 'global' || attr_list[0] == 'features' + end - operator = "NOT #{operator}" if negated? - "#{lvalue} #{operator} #{rvalue}" + def feature + index = global? ? 0 : 1 + attr_list[index] end - def to_s - "#{domain_name}.#{domain_attr} #{OPS[op]} #{value}" + def domain_basename + index = global? ? 1 : 2 + attr_list[index] end - def op - negated? ? "not_#{@op}".to_sym : @op + def domain_name + "#{feature}.#{domain_basename}" end - private + def domain_attr + start_range = global? ? 2 : 3 + end_range = -1 + attr_list.slice(start_range .. end_range).join('.') + end - def op=(value) - negated, value = value.to_s.split('_') - @negated = false - if negated == 'not' - @negated = true - else - value = negated - end - value = value.to_sym - unless supported_op?(value) - fail "op has to be one of [#{OPS.keys.join(',')}]" - end - @op = value + def to_s + "#{attr_list.join('.')} #{op} #{value}" end - def value=(data) - case op - when :in - unless data.is_a?(Array) - fail ":in operator must have an array as a value" - end - when :range - unless data.is_a?(Range) - fail ":range operator must have a range as a value" - end - when :gt, :gteq, :lt, :lteq - unless data.is_a?(Numeric) - fail ":gt, :gteq, :lt, :lteq operators expect a numeric value" - end + def validate_as_fully_qualified + unless qualified? + fail "expr (#{to_s}) is not fully qualified, mapping will not work" end - @value = data + true end - def supported_op?(op) - OPS.keys.include?(op) + private + + def parse_domain_attr(list) + list = list.split('.') if list.is_a?(String) + + unless list.is_a?(Array) + fail "Domain attribute must be a string in the form of " + + "(foo.bar.id) or an array ['foo', 'bar', 'id']" + end + list end end end end