Acts_as_ferret and associations

I have the following models:

class Book < ActiveRecord::Base
  acts_as_ferret
  belongs_to :author
end

class Author < ActiveRecord::Base
    has_many :books
end

and in the controller:

def search
if params[:query]
@query = params[:query]
@total, @books = Book.full_text_search(@query, :page =>
(params[:page]||1))
@pages = pages_for(@total)
else
@books = []
end
end

I can use the acts_as_ferret plugin to search for a book by title but it
isn’t picking up the authors through the association. So I can’t do a
search for books by author.

I’ve a feeling I’ve left some config out here. Can anyone help me out?

Hi Matthew

I’m fairly new to AAF as well, but I think if you read through this:
http://projects.jkraemer.net/acts_as_ferret/rdoc/classes/FerretMixin/Acts/ARFerret/ClassMethods.html#M000006
it might help you. I think essentially it boils down to the fact that
attributes are indexed by default but associations are not, so you might
have to pass some options in your call to acts_as_ferret.

Pete.

To clarify, say your Author class has the fields, ‘first_name’ and
‘surname’. To have these indexed by the Book class you would need to do
some things:

  1. Create methods in Book.rb to access these fields:

def author_first_name
return author.first_name
end

def author_surname
return author_surname
end

  1. Pass these fields as options to the call to acts_as_ferret:

class Book < ActiveRecord::Base
acts_as_ferret :additional_fields => [‘author_first_name’,
‘author_surname’]
belongs_to :author
end

I’m not sure it there’s a more elegant solution, but that should do the
trick.

Pete.

Thanks Pete. I’ll give this a go.

Do you mean:

def author_surname
return author.surname
end

and not:

def author_surname
return author_surname
end

M.

Jens K. wrote:

that’s exactly what I would have suggested, but please use symbols for
field names in your call to acts_as_ferret.

So:

class Book < ActiveRecord::Base
acts_as_ferret :additional_fields => [:author_first_name,
:author_surname]
belongs_to :author

def author_first_name
return author.first_name
end

def author_surname
return author.surname
end

end

?

Matthew Planchant wrote:

class Book < ActiveRecord::Base
acts_as_ferret :additional_fields => [:author_first_name,
:author_surname]
belongs_to :author

def author_first_name
return author.first_name
end

def author_surname
return author.surname
end
end

This works. Thanks.

Will this work with many-to-many relationships?

For example:

class Book < ActiveRecord::Base
acts_as_ferret :additional_fields => [:topic_title]

has_many :book_topics, :dependent => true
has_many :topics, :through => :book_topics

def topic_title
return topic.title
end

end

On Tue, Nov 07, 2006 at 12:08:10AM +0100, Pete Royle wrote:
[…]

  1. Create methods in Book.rb to access these fields:
    […]
  2. Pass these fields as options to the call to acts_as_ferret:

class Book < ActiveRecord::Base
acts_as_ferret :additional_fields => [‘author_first_name’,
‘author_surname’]
belongs_to :author
end

I’m not sure it there’s a more elegant solution, but that should do the
trick.

that’s exactly what I would have suggested, but please use symbols for
field names in your call to acts_as_ferret.

cheers,
Jens


webit! Gesellschaft für neue Medien mbH www.webit.de
Dipl.-Wirtschaftsingenieur Jens Krämer [email protected]
Schnorrstraße 76 Tel +49 351 46766 0
D-01069 Dresden Fax +49 351 46766 66

On Tue, Nov 07, 2006 at 11:35:38AM +0100, Matthew Planchant wrote:

def topic_title
return topic.title
end

this won’t work, as there is no method ‘topic’ in your Book class.
But you could index the titles of all topics:

acts_as_ferret :additional_fields => [:topic_titles]
def topic_titles
topics.collect { |topic| topic.title }.join ’ ’
end

Jens


webit! Gesellschaft für neue Medien mbH www.webit.de
Dipl.-Wirtschaftsingenieur Jens Krämer [email protected]
Schnorrstraße 76 Tel +49 351 46766 0
D-01069 Dresden Fax +49 351 46766 66

Jens K. wrote:

On Tue, Nov 07, 2006 at 11:35:38AM +0100, Matthew Planchant wrote:

def topic_title
return topic.title
end

this won’t work, as there is no method ‘topic’ in your Book class.
But you could index the titles of all topics:

acts_as_ferret :additional_fields => [:topic_titles]
def topic_titles
topics.collect { |topic| topic.title }.join ’ ’
end

Thanks. I’ll give this a shot.

Hi there, I have a similar problem. I’m building a CMS and we have a
tree model called component_instances. This model has polymorphic
associations with many other models (e.g. Links, Folders, Pages etc…)
I want the user to be able to search for an instance using data stored
in the associated models. To start with in the backend interface, I only
want them to be able to search for items by name, so I came up with
this:

class ComponentInstance < ActiveRecord::Base
has_many :permissions
has_many :groups, :through => :permissions
belongs_to :component #will be in different database ??
acts_as_tree :order => ‘position’
acts_as_list :scope => ‘parent_id’
belongs_to :instance, :polymorphic => true
acts_as_ferret(
:fields => :instance_name
)

def instance_name
instance.name
end

end

Now that looks like it should work. But when I search for an item by
name (knowing that it exists), nothing shows. I know the search itself
works because becure I added the :fields condition, it would pick up and
return results for the word “folder” as it was used to define the
polymorphic associations on the instances table.

So firstly your help with that problem would be most appreciated. Then
we make things trickier by adding the fact that I’d like the end users
on the front end to be able to search the site not just using the name
field, but basically anything in any of the instance tables.

As a laughing point, when I wrote the original application in PHP using
Mysql fulltext search, the query for the frontend search was 2 pages
long! :slight_smile:

Looking forward to your great ideas! :slight_smile:

Hi!

On Thu, Nov 16, 2006 at 07:04:55AM +0100, Brendon M. wrote:
[…]

Now that looks like it should work. But when I search for an item by
name (knowing that it exists), nothing shows. I know the search itself
works because becure I added the :fields condition, it would pick up and
return results for the word “folder” as it was used to define the
polymorphic associations on the instances table.

have a look in your development log when you save an instance of class
ComponentInstance. There should be a line like
‘adding field instance_name with value …’
showing what value aaf indexed for your instance name.

Maybe the instance isn’t there yet when the save takes place ?

So firstly your help with that problem would be most appreciated. Then
we make things trickier by adding the fact that I’d like the end users
on the front end to be able to search the site not just using the name
field, but basically anything in any of the instance tables.

that’s easy, just index all the fields you want for frontend and backend
search (including the instance_name field) and use a special QueryParser
restricted to only the instance_name field for your backend search:

QueryParser qp = QueryParser.new(:fields => [:instance_name])
ComponentInstance.find_by_contents(qp.parse user_query)

with this solution, backend users could still manually construct
complex queries to search other fields, but queries not using any field
names will default to only search the instance_name field.

As a laughing point, when I wrote the original application in PHP using
Mysql fulltext search, the query for the frontend search was 2 pages
long! :slight_smile:

dont tell me any details :wink:

cheers,
Jens


webit! Gesellschaft für neue Medien mbH www.webit.de
Dipl.-Wirtschaftsingenieur Jens Krämer [email protected]
Schnorrstraße 76 Tel +49 351 46766 0
D-01069 Dresden Fax +49 351 46766 66

Jens K. wrote:

Hi!

On Thu, Nov 16, 2006 at 07:04:55AM +0100, Brendon M. wrote:
[…]

Now that looks like it should work. But when I search for an item by
name (knowing that it exists), nothing shows. I know the search itself
works because becure I added the :fields condition, it would pick up and
return results for the word “folder” as it was used to define the
polymorphic associations on the instances table.

have a look in your development log when you save an instance of class
ComponentInstance. There should be a line like
‘adding field instance_name with value …’
showing what value aaf indexed for your instance name.

Maybe the instance isn’t there yet when the save takes place ?

I already had a few records in the database so I assumed when i added
the ferrit aa that it would index those. I will try adding a new record
and see what it says when i get back to work on Monday. :slight_smile:

So firstly your help with that problem would be most appreciated. Then
we make things trickier by adding the fact that I’d like the end users
on the front end to be able to search the site not just using the name
field, but basically anything in any of the instance tables.

that’s easy, just index all the fields you want for frontend and backend
search (including the instance_name field) and use a special QueryParser
restricted to only the instance_name field for your backend search:

QueryParser qp = QueryParser.new(:fields => [:instance_name])
ComponentInstance.find_by_contents(qp.parse user_query)

with this solution, backend users could still manually construct
complex queries to search other fields, but queries not using any field
names will default to only search the instance_name field.

Excellent! Sounds like a good solution. Will this mean I will have to do
a while bunch of these type of accessor’s in my model. One for each
field in my seperate polymorphic instances?

def instance_linkurl
instance.linkurl
end

etc…

Will that break when ferret trys to trawl a polymorphic model that
doesn’t have a linkurl attribute?

As a laughing point, when I wrote the original application in PHP using
Mysql fulltext search, the query for the frontend search was 2 pages
long! :slight_smile:

dont tell me any details :wink:

cheers,
Jens


webit! Gesellschaft f?e Medien mbH www.webit.de
Dipl.-Wirtschaftsingenieur Jens Krä­¥r [email protected]
Schnorrstraߥ 76 Tel +49 351 46766 0
D-01069 Dresden Fax +49 351 46766 66

On Fri, Nov 17, 2006 at 03:20:43AM +0100, Brendon M. wrote:

Jens K. wrote:
[…]

ComponentInstance.find_by_contents(qp.parse user_query)
def instance_linkurl
instance.linkurl
end

etc…

that depends on what you want to do. If you want fine grained searches
on single fields like linkurl or name then yes, you’ll have to have
these accessors. but with ruby you can easily declare them in a
programmatic way, i.e loop over the attributes of instance and ue
define_method inside the loop…

if you don’t need that many fields for querying you also can aggregate
the values of your instance attributes to a single string and index
that instead.

Will that break when ferret trys to trawl a polymorphic model that
doesn’t have a linkurl attribute?

it probably will, you’ll have to check if the object has this method and
return ‘’ if it doesn’t.

Jens


webit! Gesellschaft für neue Medien mbH www.webit.de
Dipl.-Wirtschaftsingenieur Jens Krämer [email protected]
Schnorrstraße 76 Tel +49 351 46766 0
D-01069 Dresden Fax +49 351 46766 66

Thanks for all your help above :slight_smile:

Coming back to the original query (now that I’m back at work to try it),
when I create a “component instance” i get the following in the logs:

Processing FolderController#new (for 127.0.0.1 at 2006-11-21 10:23:29)
[POST]
Session ID: e3154e167b8382c5050468c261bd0ead
Parameters: {“commit”=>“Add”, “folder”=>{“name”=>“cool folder”},
“action”=>“new”, “controller”=>“admin/construction/folder”,
“parent_id”=>“12”}
e[4;36;1mFolder Columns (0.016000)e[0m e[0;1mSHOW FIELDS FROM
folderse[0m
e[4;35;1mSQL (0.000000)e[0m e[0mBEGINe[0m
e[4;36;1mSQL (0.046000)e[0m e[0;1mINSERT INTO folders (name)
VALUES(‘cool folder’)e[0m
e[4;35;1mSQL (0.094000)e[0m e[0mCOMMITe[0m
e[4;36;1mComponentInstance Columns (0.016000)e[0m e[0;1mSHOW FIELDS
FROM component_instancese[0m
e[4;35;1mComponent Load (0.015000)e[0m e[0mSELECT * FROM components
WHERE (technical_name = ‘folder’) LIMIT 1e[0m
e[4;36;1mComponent Columns (0.000000)e[0m e[0;1mSHOW FIELDS FROM
componentse[0m
e[4;35;1mSQL (0.000000)e[0m e[0mBEGINe[0m
e[4;36;1mComponentInstance Load (0.000000)e[0m e[0;1mSELECT * FROM
component_instances WHERE (parent_id) ORDER BY position DESC LIMIT 1e[0m
e[4;35;1mSQL (0.032000)e[0m e[0mINSERT INTO component_instances
(deleted_root_item, instance_type, deleted_on, enabled,
instance_id, component_id, parent_id, position) VALUES(NULL,
‘Folder’, NULL, 1, 4, 2, 12, 5)e[0m
ferret_create/update: ComponentInstance : 20
creating doc for class: ComponentInstance, id: 20
e[4;36;1mSQL (0.031000)e[0m e[0;1mCOMMITe[0m
Redirected to
http://localhost:3001/admin/construction/construction_zone/12
Completed in 1.01600 (0 reqs/sec) | DB: 0.25000 (24%) | 302 Found
[http://localhost/admin/construction/folder/12/new]

The current model code is:

class ComponentInstance < ActiveRecord::Base
has_many :permissions
has_many :groups, :through => :permissions
belongs_to :component #will be in different database ??
acts_as_tree :order => ‘position’
acts_as_list :scope => ‘parent_id’
belongs_to :instance, :polymorphic => true
acts_as_ferret(
:fields => :instance_name
)

def instance_name
instance.name
end

– And here is the controller method to make a new folder
(component_instance)

def new
if request.post?
@folder = Folder.new(params[:folder])
success = @folder.save

  component_instance = ComponentInstance.new
  component_instance.enabled = 1; #ie True
  component_instance.instance = @folder
  component_instance.parent_id = params[:parent_id]
  component_instance.component = Component.find(:first, :conditions 

=>“technical_name = ‘folder’”)

  if success && component_instance.save
    flash[:notice] = 'A new folder was successfully added.'
    redirect_to :controller => 'construction_zone', :parent_id => 

params[:parent_id]
else
@folder.destroy if success #Make sure the link and
component_instances tables are consistent
flash[:fail] = ‘There was an error when saving the folder’
end
else
@folder = Folder.new
end
end

Any ideas would be greatly appreciated. :slight_smile:

Brendon

Oh so, here’s how to do it :slight_smile:

acts_as_ferret :fields => [‘instance_name’]

def instance_name
instance.name
end

Seems to be the correct syntax :slight_smile: Not sure why the symbol way didn’t do
it?

Cheers,

Brendon

On Mon, Nov 20, 2006 at 11:39:30PM +0100, Brendon M. wrote:

it?
imho it’s not the symbol that was the problem, but the missing brackets
around it - in your original post you wrote

acts_as_ferret :fields => :instance_name

So
acts_as_ferret :fields => [ :instance_name ]
should work, too.

Jens

http://rubyforge.org/mailman/listinfo/ferret-talk


webit! Gesellschaft für neue Medien mbH www.webit.de
Dipl.-Wirtschaftsingenieur Jens Krämer [email protected]
Schnorrstraße 76 Tel +49 351 46766 0
D-01069 Dresden Fax +49 351 46766 66

I think i answered my own question.
in the create method for my note i added
Contact.find(params[:id]).ferret_create()
hooha

Adam J. wrote:

Thanks guys,

The above seems to work for me perfectly except when i add a new not it
doesnt update the index. if i delete and rebuild the index it works
fine. can some one stear me in the right direction please i am guesing i
have to add some metod to the create function in my notes_controller to
update the index ???

Thanks guys,

The above seems to work for me perfectly except when i add a new not it
doesnt update the index. if i delete and rebuild the index it works
fine. can some one stear me in the right direction please i am guesing i
have to add some metod to the create function in my notes_controller to
update the index ???

Thanks, Adam

my code in contacts_controller:

class Contact < ActiveRecord::Base
validates_presence_of :First_Name
validates_presence_of :Last_Name
has_many :notes, :as => :notable

acts_as_ferret :store_class_name => true, :lazy => true, :remote =>
false, :fields =>
{

      :First_Name => { :store => :no, :boost => 2 },
      :Last_Name => { :store => :no, :boost => 1 },
      :description => { :store => :yes, :boost => 0.5 },
      :agent_id => { :store => :no, :boost => 0 },
      :note_note => { :store => :yes, :boost => 0 }

}

def note_note

  @index = Array.new
  for note in self.notes
    @index << note.note
  end
  return @index.join(" ")

end

On Tue, Sep 18, 2007 at 06:10:38AM +0200, Adam J. wrote:

I think i answered my own question.
in the create method for my note i added
Contact.find(params[:id]).ferret_create()

right, however I’d stick that code in a NotesObserver…

Jens

Posted via http://www.ruby-forum.com/.


Ferret-talk mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/ferret-talk


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