Inheritance in Rails - I need some help

I’ve got a model, “category” and another model “subcategory.” Each
subcategory belongs_to a category, and a category has_many
subcategories.

What I need to do is set it up so that I can search Category and
Subcategory with one .find call. So:

Category.find(:all, <etc)

will find categories and subcategories.

Now, I believe this can be done by making Subcategory a subclass of
Category. Am I right? Unfortunately, there’s almost nothing in AWD about
subclasses and has_many relationships, so I’m lost. I tried to just
change Subcategory to extend Category in the model, but that led to
errors (no such field: “categories.category_id”). What else do I need to
change to allow Subcategory to extend Category and also belong_to a
category?

Thanks,

Adam

Adam,

I would probably do this with a single table that references itself,
rather than have a new model Subcategory extend from Category.
Basically you just need a categories table that contains a parent_id
column referring to another row in that same table. There is a brief
discussion of this at:

http://wiki.rubyonrails.com/rails/pages/HowToCreateASelfReferentialHasManyRelationship

It is also discussed in AWD ( ~ page 255 ) when they go over “Acts As
Tree”.

Hope that helps,

Jeff

On Sat, Apr 15, 2006 at 10:07:10PM +0200, Adam B. wrote:

Now, I believe this can be done by making Subcategory a subclass of
Category. Am I right?

Possibly-sorta, if when you say “subclass” you mean to use single-table
inheritance. In that instance, yes, you can search Categories and also
get
matching subcategories, because STI is wonderfully intelligent about
that
sort of thing, like so:

CREATE TABLE categories (
id INTEGER PRIMARY KEY,
type VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
category_id INTEGER
);

class Category << ActiveRecord::Base
has_many :subcategories
end

class SubCategory << Category
belongs_to :category
end

(All that off the top of my head, so it might not be strictly correct)

Then, when you do a Category.find(), the SQL that will be generated will
include “WHERE type=‘Category’ OR type=‘SubCategory’”, while
SubCategory.find() will only have “WHERE type=‘SubCategory’”.

Speaking structurally, though, I’m not sure that STI really describes
what
you’re looking for. I’d consider putting together my own
Category.find()
type method that also ran the find on the subcategories itself, or else
using something like acts_as_tree to do the whole thing (although I
don’t
know if acts_as_tree has the searching capabilities you want – I’ve
never
used it myself).

  • Matt

Adam
> What I need to do is set it up so that I can search Category and
> Subcategory with one .find call. So:
> Category.find(:all, <etc)
> will find categories and subcategories.

You could aggregate 2 searches in 1 call :

(untested:)

def find_deep (*args)
result = Category.find(args)
result << Subcategory.find(args)
result.flatten
end

Alain

Thanks guys!

-Adam

Hmmm. I have a different but related question. Is there such a thing as
an “includes” condition for .find? i.e. if I’m searching for listings
(which habtm categories) that are in a certain category with id =
category_id I could do:

Listing.find(:all, :conditions => [ “categories INCLUDE ?”, category_id
]

and, as an extension, is there any way to find a listing that is in a
category with category_id = one of [ id1, id2, id3 ]. That way I could
do something like:

Listing.find(:all, :conditions => ["name = ?, or ,
or ",
“%#{params[:search_string]}%”,
<category_ids>, <subcategory_ids>]

and find every listing with one search.

It seems like it’d be something I could do with a pure-Ruby .find, but
I’m not sure how hard that would be to integrate.

-Adam

Formatting on that last pseudo-code chunk got really messed up.

Listing.find(:all, :conditions =>
["name = ?,
or ,
or ",
“%#{params[:search_string]}%”, <category_ids>, <subcategory_ids>]

Adam
> Formatting on that last pseudo-code chunk got really messed up.
> Listing.find(:all, :conditions => ["name = ?,
> or ",
> “%#{params[:search_string]}%”, <category_ids>, <subcategory_ids>]

When in doubt, just do it… with the console:

./script/console
>> Foo.find_by_sql('select * from foes where id in (1,2,3,4,5)')

(On Windows, you launch the console with
ruby script\console
)

Alain

Thanks. I did end up solving the problem with a few extra steps, but
I’ll remember that SQL query in the future. :slight_smile:

-Adam