Different ferret fields for instances of the same model?

Hi all,

So far as I know, while using acts_as_ferret, we should add the
following declaration in the ActiveRecord model which is going to be
indexed:

acts_as_ferret({:fields => @@ferrect_fields})

in which @@ferrect_fields is a hash containing all the field to be
indexed. This is pretty much for some simple situations. But I got a
more complex situation that I want to define the fields to be indexed
for every instance of the same model.

My requirements are something like this:

Suppose we have a model “Product”, in “Product” I’ve declared a
polymorphic relationship with model “Property1” and “Property2”, the
following code will show this:

class Product < ActiveRecord::Base
belongs_to :property, :polymorphic => true
@@ferret_fields = {…}
acts_as_ferret({:fields => @@ferret_fields})
end

class Property1 < ActiveRecord::Base
has_one :product, :as => :property
end

class Property2 < ActiveRecord::Base
has_one :product, :as => :property
end

Now I want to provide full text search capability for “Product” and it’s
obvious that “Product” should contains its “property” while being
indexed. So I should define “ferret_fields” class method in “Property1”
and “Property2” to collect all their fields and dynamically define the
corresponding method in “Product”. The code is something like this:

class Property1 < ActiveRecord::Base
has_one :product, :as => :property
def self.ferret_fields
# return a hash containing all the fields to be indexed in aaf’s
format
end
end

class Property2 < ActiveRecord::Base
has_one :product, :as => :property
def self.ferret_fields
# return a hash containing all the fields to be indexed in aaf’s
format
end
end

class Product < ActiveRecord::Base
belongs_to :property, :polymorphic => true
@@ferret_fields = {…}
@@ferret_fields.merge!(Property1.ferret_fields)
@@ferret_fields.merge!(Property2.ferret_fields)
acts_as_ferret({:fields => @@ferret_fields})
Property1.ferret_fields.keys.each do |field|
define_method("#{field}") do
result = property.send("#{field}")
end
end
Property2.ferret_fields.keys.each do |field|
define_method("#{field}") do
result = property.send("#{field}")
end
end
end

But there are two problems in the above code:

  1. If the property object in a product object is “Property1”,
    property.send("#{field}") in “Property2”'s block will cause a method
    missing error, vice versa.
  2. Say “Property1” has 500 fields as well as “Property2”, each product
    will be indexed using 1000 fields while only at most 500 fields contains
    value.

How can I solve these problems and meet my requirements? Any ideas about
this?

Jens K. wrote:

I’d just rescue that and return nil for the field:

Property2.ferret_fields.keys.each do |field|
define_method("#{field}") do
result = property.send("#{field}") rescue nil
end
end

For now, I’m rescuing the “NoMethodError” which works fine as well.

  1. Say “Property1” has 500 fields as well as “Property2”, each product
    will be indexed using 1000 fields while only at most 500 fields contains
    value.

Do you really need to be able to run queries against each single one of
these 1000 fields? If not, you could concatenate their values into a
single large :properties field.
I’m afraid so… because many of these fields are number fields and
users will want to search these fields like “field1 is bigger than 1.5
and smaller than 0.7”.

On Tue, Aug 14, 2007 at 04:51:59AM +0200, Allen Young wrote:

  1. Say “Property1” has 500 fields as well as “Property2”, each product
    will be indexed using 1000 fields while only at most 500 fields contains
    value.

Do you really need to be able to run queries against each single one of
these 1000 fields? If not, you could concatenate their values into a
single large :properties field.
I’m afraid so… because many of these fields are number fields and
users will want to search these fields like “field1 is bigger than 1.5
and smaller than 0.7”.

Ok. To avoid the 500 nil fields per record you could override the to_doc
instance method in your model and only add the relevant fields depending
on the type of your property. The original implementation is in
acts_as_ferret’s
instance_methods.rb .

Jens


Jens Krämer
http://www.jkraemer.net/ - Blog
http://www.omdb.org/ - The new free film database

Hi Allen!

comments inline

On Fri, Aug 10, 2007 at 05:21:38AM +0200, Allen Young wrote:

Suppose we have a model “Product”, in “Product” I’ve declared a
polymorphic relationship with model “Property1” and “Property2”, the
following code will show this:

class Product < ActiveRecord::Base
belongs_to :property, :polymorphic => true
@@ferret_fields = {…}
acts_as_ferret({:fields => @@ferret_fields})
end

[…]

Now I want to provide full text search capability for “Product” and it’s
obvious that “Product” should contains its “property” while being
indexed. So I should define “ferret_fields” class method in “Property1”
and “Property2” to collect all their fields and dynamically define the
corresponding method in “Product”. The code is something like this:

[…]

end
  1. If the property object in a product object is “Property1”,
    property.send(“#{field}”) in “Property2”'s block will cause a method
    missing error, vice versa.

I’d just rescue that and return nil for the field:

Property2.ferret_fields.keys.each do |field|
define_method(“#{field}”) do
result = property.send(“#{field}”) rescue nil
end
end

  1. Say “Property1” has 500 fields as well as “Property2”, each product
    will be indexed using 1000 fields while only at most 500 fields contains
    value.

Do you really need to be able to run queries against each single one of
these 1000 fields? If not, you could concatenate their values into a
single large :properties field.

Cheers,
Jens


Jens Krämer
http://www.jkraemer.net/ - Blog
http://www.omdb.org/ - The new free film database