require "fiona7/search_engine" module Fiona7 class NaiveSearchEngine < SearchEngine def results @objects || execute_search @objects.map {|o| o.id} end def objects @objects || execute_search @objects end protected def execute_search fetch_all apply_filters exclude_widgets order_results limit_size end def fetch_all obj_class_filter = @query.find {|q| (q[:field] == :_obj_class || q[:field] == "_obj_class") && q[:operator] == :equal } id_filter = @query.find {|q| (q[:field] == :id || q[:field] == "id") && q[:operator] == :equal } if obj_class_filter if obj_class_filter[:negate] @all = @klass.where.not(obj_class: obj_class_filter[:value]) else @all = @klass.where(obj_class: obj_class_filter[:value]) end @query.delete(obj_class_filter) elsif id_filter if id_filter[:negate] @all = @klass.where.not(obj_id: id_filter[:value]) else @all = @klass.where(obj_id: id_filter[:value]) end @query.delete(id_filter) else # FIXME: this is deprecated @all = @klass.all end end def apply_filters @query.each do |filter| apply_filter(filter) end end def exclude_widgets # skip when handling ActiveRelation return unless @all.kind_of?(Array) widget_regexp = /\/_widgets\/([0-9]+\/){0,1}[^\/]+\Z/ @all.to_a.delete_if {|o| o.path =~ widget_regexp } end def order_results if !@all.kind_of?(Array) field = @sort.to_sym if field == :_last_changed @all = @all.order("last_changed" => @order) elsif field == :id @all = @all.order("obj_id" => @order) else # NOTE: we actually ignore sorting here !! @all = @all.order("last_changed" => :desc) end return end sort_field = @sort @all = @all.to_a @all.sort! do |a,b| a_value = a.send(:[], resolve_field_name(a,sort_field)) b_value = b.send(:[], resolve_field_name(b,sort_field)) if a_value.nil? 1 elsif b_value.nil? -1 else a_value <=> b_value end end @all.reverse! if @order == :desc end def limit_size @count = @all.count @objects = if @all.kind_of?(Array) @all[@offset, @limit] || [] else @all.offset(@offset).limit(@limit).to_a end end protected def apply_filter(filter) field = filter[:field] operator = filter[:operator].to_sym value = filter[:value] case filter[:operator] when :equals, :equal if field == :_modification || field == "_modification" if value != ["new", "edited", "deleted"] raise "Unsupported modification values: #{value.inspect}. Only #{["new", "edited", "deleted"].inspect} are supported" else if (@all.is_a?(Array)) if filter[:negate] @all = @all.to_a.select {|o| !o.edited? } else @all = @all.to_a.select {|o| o.edited? } end else if filter[:negate] @all = @all.where.not(is_edited: '1') else @all = @all.where(is_edited: '1') end end end elsif field == :_path value = [value] unless value.is_a?(Array) value.map{|v| v.gsub!('.', '_') } if (@all.is_a?(Array)) if filter[:negate] @all = @all.select do |o| !value.include?(o.path) end else @all = @all.select do |o| value.include?(o.path) end end else if filter[:negate] @all = @all.where.not(:path => value) else @all = @all.where(:path => value) end end elsif field == :_permalink value = [value] unless value.is_a?(Array) if (@all.is_a?(Array)) if filter[:negate] @all = @all.select do |o| !value.include?(o.permalink) end else @all = @all.select do |o| value.include?(o.permalink) end end else if filter[:negate] @all = @all.where.not(:permalink => value) else @all = @all.where(:permalink => value) end end elsif field == :_parent_path parent_obj = @klass.where(path: value).first if parent_obj parent_obj_id = parent_obj.id if (@all.is_a?(Array)) if filter[:negate] @all = @all.select do |o| o.parent_obj_id != parent_obj_id end else @all = @all.select do |o| o.parent_obj_id == parent_obj_id end end else if filter[:negate] @all = @all.where.not(:parent_obj_id => parent_obj_id) else @all = @all.where(:parent_obj_id => parent_obj_id) end end else @all = [] end else if value.is_a?(Array) @all = @all.to_a.select do |o| resolved_field = resolve_field_name(o, field) field_value = o.send(:[], resolved_field) if resolved_field == :obj_id field_value = field_value.to_s end if filter[:negate] if field_value.is_a?(Array) (value & field_value).empty? else !value.include?(field_value) end else if field_value.is_a?(Array) !(value & field_value).empty? else value.include?(field_value) end end end else @all = @all.to_a.select do |o| resolved_field = resolve_field_name(o, field) field_value = o.send(:[], resolved_field) if resolved_field == :obj_id field_value = field_value.to_s end if filter[:negate] field_value != value else field_value == value end end end end when :prefix, :prefix_search if !@all.is_a?(Array) && field.to_sym == :_path # TODO: handle this properly value = value.first if value.kind_of?(Array) if filter[:negate] @all = @all.where.not("path LIKE ?", "#{value}%") else @all = @all.where("path LIKE ?", "#{value}%") end else if filter[:negate] @all = @all.to_a.select {|o| !o.send(:[], resolve_field_name(o,field)).to_s.start_with?(value) } else @all = @all.to_a.select {|o| o.send(:[], resolve_field_name(o,field)).to_s.start_with?(value) } end end when :greater_than if filter[:negate] @all = @all.to_a.select {|o| o.send(:[], resolve_field_name(o,field)).to_s <= (value) } else @all = @all.to_a.select {|o| o.send(:[], resolve_field_name(o,field)).to_s > (value) } end when :less_than if filter[:negate] @all = @all.to_a.select {|o| o.send(:[], resolve_field_name(o,field)).to_s >= (value) } else @all = @all.to_a.select {|o| o.send(:[], resolve_field_name(o,field)).to_s < (value) } end else raise "Operator: #{filter[:operator]} not supported" end end def resolve_field_name(obj, field) field = field.to_sym case field when :_obj_class :obj_class when :_path :path when :_modification :modification when :id :obj_id when :_permalink :permalink when :_last_changed :last_changed when :_name :name else Fiona7::TypeRegister.instance.read(obj.obj_class).find_attribute(field).real_name.to_sym end end end end