activerecord-hackery/meta_search

Quick fix for multiple attributes with different types (with OR)

Opened this issue · 1 comments

eLod commented

I'm using this workaround to enable something like search(:title_matches_or_description_starts_with => 'foo'). However my solution lifts the functionality that applies the condition from Where#evaluate to Builder (because we need to OR together different kind of predicates and that is out of scope for a single where), so it's not optimal:

module MetaSearch
  module MultiAttributes
    private
      def method_missing(method_id, *args, &block)
        if match = matches_multi_attributes_method(method_id)
          (args.any? || method_name =~ /=$/) ? set_multi_attributes_method_value(match, args.first) : get_multi_attributes_method_value(match, predicates)
        else
          super
        end
      end

      def matches_multi_attributes_method(method_id)
        attributes = method_id.to_s.split(/_or_/)
        attributes.collect do |attribute_with_predicate|
          return unless match = matches_attribute_method(attribute_with_predicate)
          match.captures
        end
      end

      def get_multi_attributes_method_value(attributes_with_predicates)
        search_attributes[multi_attributes_key(attributes_with_predicates)]
      end

      def set_multi_attributes_method_value(attributes_with_predicates, val)
        search_attributes[multi_attributes_key(attributes_with_predicates)] = val
        conditions = attributes_with_predicates.collect do |(attribute, predicate)|
          where = Where.new(predicate)
          casted_val = cast_attributes(where.cast || column_type(attribute), val)
          if where.validate(casted_val)
            arel_attribute = get_attribute(attribute)
            if where.splat_param?
              arel_attribute.send(where.predicate, *where.format_param(casted_val))
            else
              arel_attribute.send(where.predicate, where.format_param(casted_val))
            end
          end
        end.compact
        @relation = @relation.where(conditions.inject(nil) {|memo, c| memo ? memo.or(c) : c}) if conditions.any?
      end

      def multi_attributes_key(attributes_with_predicates)
        attributes_with_predicates.collect {|a_w_p| a_w_p.join('_')}.join('_or_')
      end
  end

  Builder.send :include, MultiAttributes
end

+1

I'd like the ability to search multiple attributes given a single search string. I could probably build a custom Where but this seems like a commonly requested feature.