Querying against numeric fields? e.g. price:( >= min_price)

Using acts_as_ferret I’m trying to do a query like:

active:(true) title|body:(#{params[:s]}) product_price:( >=
#{params[:min]})

Where I want to return only the active products that contain the search
term in the title or body and has a minimum price >= params[:min]

I’m finding that even though I’m indexing the product price as an
integer (so no .00 to cause confusion) I’m getting results in the 50
value range as well as 500 if I set the min price as 500. I presume
ferret is doing the price as a string comparison, but is there any way
to make it do a numeric match?

Thanks

On 9/13/06, Tom B. [email protected] wrote:

integer (so no .00 to cause confusion) I’m getting results in the 50
value range as well as 500 if I set the min price as 500. I presume
ferret is doing the price as a string comparison, but is there any way
to make it do a numeric match?

Thanks

Hi Tom,

You need to pad all numbers to a fixed width when adding them to the
index as well as when querying the index. Usually you’d write the code
to do this yourself. I’ve recently come up with another way to do
this.

require 'ferret'

module Ferret::Analysis
  class IntegerTokenizer
    def initialize(num, width)
      @num = num.to_i
      @width = width
      @done = false
    end
    def next
      if @done
        return nil
      else
        @done = true
        puts Token.new("%0#{@width}d" % @num, 0, @width)
        return Token.new("%0#{@width}d" % @num, 0, @width)
      end
    end
    def text=(text)
      @num = text.to_i
      @done = false
    end
  end

  class IntegerAnalyzer
    def initialize(width)
      @width = width
    end
    def token_stream(field, input)
      return IntegerTokenizer.new(input, @width)
    end
  end
end

include Ferret::Analysis
analyzer = PerFieldAnalyzer.new(StandardAnalyzer.new)
analyzer[:num] = IntegerAnalyzer.new(5)

index = Ferret::Index::Index.new(:analyzer => analyzer)
docs = [
  {:num => 1,    :data => "yes"},
  {:num => 1,    :data => "no"},
  {:num => 10,   :data => "yes"},
  {:num => 10,   :data => "no"},
  {:num => 100,  :data => "yes"},
  {:num => 100,  :data => "no"},
  {:num => 1000, :data => "yes"},
  {:num => 1000, :data => "no"}
]

docs.each { |d| index << d }

puts index.process_query('data:yes AND num:[10 100]')
puts index.search('data:yes AND num:[10 100]')

This will only work with the working copy of Ferret from the
subversion repository. I’m still not convinced that this is the best
way to do it.

Cheers,
Dave

Hi, David.

Can you tell me where to include your codes for integration with
Rails(acts_as_ferret)?
I just want to pad 0’s to do numeric comparision

I am pretty new to Rails and Ferret.

Thanks.
Yaxm

This doesn’t work because “10” is used.

puts index.process_query('data:yes AND num:[10 100]')
puts index.search('data:yes AND num:[10 100]')

One must pad the integer with zero’s just like the analyzer:

puts index.process_query(‘data:yes AND num:[00010 00100]’)
puts index.search(‘data:yes AND num:[00010 00100]’)

To use this in a Rails app:
include the IntegerAnalyzer defnition in a file.

then require the file in your model.

inside your model class:

include Ferret::Analysis
analyzer = PerFieldAnalyzer.new(StandardAnalyzer.new)
analyzer[:num] = IntegerAnalyzer.new(5)
analyzer[:blah_field] = IntegerAnalyzer.new(5)

acts_as_ferret(
{:fields => [:blah_field] }
, {:analyzer => analyzer}

David B. wrote:

On 9/13/06, Tom B. [email protected] wrote:

integer (so no .00 to cause confusion) I’m getting results in the 50
value range as well as 500 if I set the min price as 500. I presume
ferret is doing the price as a string comparison, but is there any way
to make it do a numeric match?

Thanks

Hi Tom,

You need to pad all numbers to a fixed width when adding them to the
index as well as when querying the index. Usually you’d write the code
to do this yourself. I’ve recently come up with another way to do
this.

require 'ferret'

module Ferret::Analysis
  class IntegerTokenizer
    def initialize(num, width)
      @num = num.to_i
      @width = width
      @done = false
    end
    def next
      if @done
        return nil
      else
        @done = true
        puts Token.new("%0#{@width}d" % @num, 0, @width)
        return Token.new("%0#{@width}d" % @num, 0, @width)
      end
    end
    def text=(text)
      @num = text.to_i
      @done = false
    end
  end

  class IntegerAnalyzer
    def initialize(width)
      @width = width
    end
    def token_stream(field, input)
      return IntegerTokenizer.new(input, @width)
    end
  end
end

include Ferret::Analysis
analyzer = PerFieldAnalyzer.new(StandardAnalyzer.new)
analyzer[:num] = IntegerAnalyzer.new(5)

index = Ferret::Index::Index.new(:analyzer => analyzer)
docs = [
  {:num => 1,    :data => "yes"},
  {:num => 1,    :data => "no"},
  {:num => 10,   :data => "yes"},
  {:num => 10,   :data => "no"},
  {:num => 100,  :data => "yes"},
  {:num => 100,  :data => "no"},
  {:num => 1000, :data => "yes"},
  {:num => 1000, :data => "no"}
]

docs.each { |d| index << d }

puts index.process_query('data:yes AND num:[10 100]')
puts index.search('data:yes AND num:[10 100]')

This will only work with the working copy of Ferret from the
subversion repository. I’m still not convinced that this is the best
way to do it.

Cheers,
Dave