Nested categories, nesting has_many :through a HABTM relationship

Hi everybody:
I am currently using Rails 3.0.3, and having trouble with aggregating
and displaying nested category information.

My situation:

I have three models:

Model: Car
Model: Feature
Model: Feature_type

A Car has_and_belongs_to_many Features
Feature belongsTo Feature_types
Feature_type has_many Features

I want to be able to pull (for a view) a cars Feature_types and then
the child features of that category for the car.

The pseudocode would be:

  <% Car.feature_types.each do |feature_type| %>
    <h1><%= feature_type.name %></h1>
    <% feature_type.features.each do |feature| %>
      <span><% feature.name %></span>
    <% end %>
  <% end %>

This would work, but, the problem is, Car is not related to
Feature_types. I tried to do a has_many :through Features for the car,
but the relationship will not nest through a HABTM relationship.

I don't know what the best solution is here or how to solve this, so,
all help would be greatly appreciated.

Hi everybody:
I am currently using Rails 3.0.3, and having trouble with aggregating
and displaying nested category information.

My situation:

I have three models:

Model: Car
Model: Feature
Model: Feature_type

To stick to the rails conventions that should be FeatureType

A Car has_and_belongs_to_many Features
Feature belongsTo Feature_types
Feature_type has_many Features

I want to be able to pull (for a view) a cars Feature_types and then
the child features of that category for the car.

The pseudocode would be:

<% Car.feature_types.each do |feature_type| %>

I presume you mean @car.feature_types.... so that you want the feature
types for a specific car rather than for the class Car

<h1><%= feature_type.name %></h1>
<% feature_type.features.each do |feature| %>
<span><% feature.name %></span>

You realise that the pseudo code above would give all the features for
a feature type, not just the features that relate to the given car.

<% end %>
<% end %>

This would work, but, the problem is, Car is not related to
Feature_types. I tried to do a has_many :through Features for the car,
but the relationship will not nest through a HABTM relationship.

I don't know what the best solution is here or how to solve this, so,
all help would be greatly appreciated.

I suggest providing a method 'feature_types' of the car model that
picks up the features for the car and extracts an array of unique
feature types. Then your pseudo code can become actual code.

Colin

This was posted a while ago, and being new to Ruby/Rails I was curious how to tackle it since I’m coming over from verbose Java-land. If you want you can just jump to the end where I ask my question (but I left the original comments for reference).

Hi everybody:

I am currently using Rails 3.0.3, and having trouble with aggregating

and displaying nested category information.

My situation:

I have three models:

Model: Car

Model: Feature

Model: Feature_type

To stick to the rails conventions that should be FeatureType

A Car has_and_belongs_to_many Features

Feature belongsTo Feature_types

Feature_type has_many Features

I want to be able to pull (for a view) a cars Feature_types and then

the child features of that category for the car.

The pseudocode would be:

<% Car.feature_types.each do |feature_type| %>

I presume you mean @car.feature_types… so that you want the feature

types for a specific car rather than for the class Car

<%= [feature_type.name](http://feature_type.name) %>

<% feature_type.features.each do |feature| %>

 <span><% [feature.name](http://feature.name) %></span>

You realise that the pseudo code above would give all the features for

a feature type, not just the features that relate to the given car.

<% end %>

<% end %>

This would work, but, the problem is, Car is not related to

Feature_types. I tried to do a has_many :through Features for the car,

but the relationship will not nest through a HABTM relationship.

I don’t know what the best solution is here or how to solve this, so,

all help would be greatly appreciated.

I suggest providing a method ‘feature_types’ of the car model that

picks up the features for the car and extracts an array of unique

feature types. Then your pseudo code can become actual code.

I suppose this isn’t really a Rails specific question, more of a Ruby question hence the reason I changed the subject (but maybe someone that reads the whole post will end up stating it can be solved with Rails modeling.)

Let’s say I do want to add a “feature_types” method on the Car model.

For summary " A car has “features” and each feature belongs to a “feature_type.” You want a collection/array of “feature_types” and for each feature_type you want the features that the car has (tied to the feature type.) [ example would be for a given car you want to show its available features organized by feature type ]

Car
feature_type: stereos
feature: basic stereo
feature: deluxe stereo
feature_type: car doors
feature: two doors
feature: four doors

Rails will return features for a car with just feature objects. How would you use Ruby’s coolness to easily build up the feature_types?

I’m not super comfortable with Ruby yet so I did the best I could but with more of a java-esque style and am thinking it could be improved upon:

//Car AR model:

def feature_types
results = Hash.new

prev = nil
@features.each do |f|
  if ([f.feature_type.name](http://f.feature_type.name) != prev)
    results[[f.feature_type.name](http://f.feature_type.name)] = []   

  end

  results[[f.feature_type.name](http://f.feature_type.name)] << f
  prev = [f.feature_type.name](http://f.feature_type.name)
end
results

end

//my rspec unit tests

it “should have two feature_types” do
car = Factory.create(:mercedes)
car.feature_types.size.should eq(2)
end

it “should have ‘two doors and four doors’ for the second feature type” do

car = Factory.create(:mercedes)
features = car.feature_types["Feature Type Doors"]
features[0].name.should eq("Two Doors")
features[1].name.should eq("Four Doors")

end

The above passes ok but wondering how to improve it

Rick R wrote in post #967098:

This was posted a while ago, and being new to Ruby/Rails I was curious
how
to tackle it since I'm coming over from verbose Java-land. If you want
you
can just jump to the end where I ask my question (but I left the
original
comments for reference).

> Model: Feature
> the child features of that category for the car.
> <span><% feature.name %></span>
>
> I don't know what the best solution is here or how to solve this, so,
> all help would be greatly appreciated.

I suggest providing a method 'feature_types' of the car model that
picks up the features for the car and extracts an array of unique
feature types. Then your pseudo code can become actual code.

I suppose this isn't really a Rails specific question, more of a Ruby
question hence the reason I changed the subject (but maybe someone that
reads the whole post will end up stating it can be solved with Rails
modeling.)

This is sort of a Rails question.

Let's say I do want to add a "feature_types" method on the Car model.

For summary " A car has "features" and each feature belongs to a
"feature_type." You want a collection/array of "feature_types" and for
each
feature_type you want the features that the car has (tied to the feature
type.) [ example would be for a given car you want to show its available
features organized by feature type ]

Car
   feature_type: stereos
      feature: basic stereo
      feature: deluxe stereo
  feature_type: car doors
      feature: two doors
      feature: four doors

Rails will return features for a car with just feature objects. How
would
you use Ruby's coolness to easily build up the feature_types?

If it were me, I'd use the nested_has_many_through plugin and have done
with it.

You could do something like car.feature_types(:joins =>
:features).collect(&:feature).uniq, though I don't know that I'd
recommend this. You might also be able to use grouping operations on
the DB side to do this more efficiently.

I'm not super comfortable with Ruby yet so I did the best I could but
with
more of a java-esque style and am thinking it could be improved upon:

Indeed it can. See the one-liner above.

[...]

//my rspec unit tests

What's with the // ? This isn't Java.

it "should have two feature_types" do
    car = Factory.create(:mercedes)
    car.feature_types.size.should eq(2)

Better: ...should == 2

  end

it "should have 'two doors and four doors' for the second feature type"
do
    car = Factory.create(:mercedes)
    features = car.feature_types["Feature Type Doors"]
    features[0].name.should eq("Two Doors")
    features[1].name.should eq("Four Doors")

Again, ....should == "Four Doors"

  end

Try not to use factories like fixtures, which is what you're doing here.
Specify the data as you need it.

The above passes ok but wondering how to improve it

--
Rick R

Best,

If it were me, I’d use the nested_has_many_through plugin and have done

with it.

Interesting. I’ll have too look into that. (This initially wasn’t my post but still curious how to do it since I’m sure I’ll run into it.)

You could do something like car.feature_types(:joins =>

:features).collect(&:feature).uniq, though I don’t know that I’d

recommend this. You might also be able to use grouping operations on

the DB side to do this more efficiently.

Could you call car.feature_types without declaring that relationship in some way in the car model? I’d have to declare feature_types as some sort of db relation on the car model right? (Maybe pointless anyway once I research the nested_has_man_through plugin?)

I’m not super comfortable with Ruby yet so I did the best I could but

with more of a java-esque style and am thinking it could be improved upon:

Indeed it can. See the one-liner above.

Are you referring to using the nested_has_many_through plugin or the car.feature_types(:joins =>

:features).collect(&:feature)?

[…]

//my rspec unit tests

What’s with the // ? This isn’t Java.

Oops sorry. Habit. Hopefully that’ll change.

it “should have two feature_types” do

car = Factory.create(:mercedes)
car.feature_types.size.should eq(2)

Better: …should == 2

Just curious why that is better? Most of the rspec matchers I was looking at http://rspec.rubyforge.org/rspec/1.3.0/classes/Spec/Matchers.html seem to refer to eq in place of == and use ‘equal’ for testing if it’s actually the same object instance.

Try not to use factories like fixtures, which is what you’re doing here.

Specify the data as you need it.

Can you please elaborate more on this? This seems contrary to my understanding of setting up tests of your model. I bought the pragmatic rspec book http://www.pragprog.com/titles/achbd/the-rspec-book but haven’t had a chance to read it yet. I thought it was a good idea to use factories (using factory_girl) to create your isolated model objects for testing. It sounds like your stating I should use the real populated DB (even if local.) I’d love some more elaboration here if you get the time.

Thanks again for your time so far.

Rick R wrote in post #967105:

If it were me, I'd use the nested_has_many_through plugin and have done
with it.

Interesting. I'll have too look into that. (This initially wasn't my
post
but still curious how to do it since I'm sure I'll run into it.)

You could do something like car.feature_types(:joins =>
:features).collect(&:feature).uniq, though I don't know that I'd
recommend this. You might also be able to use grouping operations on
the DB side to do this more efficiently.

Could you call car.feature_types without declaring that relationship in
some
way in the car model?

The method has to be declared somewhere, of course. I apparently messed
up the one-liner: if you exchange all instances of "feature_types" and
"features", you will have what I intended.

I'd have to declare feature_types as some sort of
db
relation on the car model right? (Maybe pointless anyway once I research
the
nested_has_man_through plugin?)

Right. Sorry about the mixup.

>
> I'm not super comfortable with Ruby yet so I did the best I could but
> with more of a java-esque style and am thinking it could be improved
upon:

Indeed it can. See the one-liner above.

Are you referring to using the nested_has_many_through plugin or the
car.feature_types(:joins =>
:features).collect(&:feature)?

Yes, with the correction I made in this post.

[...]
> //my rspec unit tests

What's with the // ? This isn't Java.

Oops sorry. Habit. Hopefully that'll change.

>
> it "should have two feature_types" do
> car = Factory.create(:mercedes)
> car.feature_types.size.should eq(2)

Better: ...should == 2

Just curious why that is better?

It's more idiomatic Ruby. You'd write x == 2, not x.eql?(2), and your
matchers should follow the same practice for better readability.

And I think you meant eql -- does eq even exist?

Most of the rspec matchers I was
looking at
http://rspec.rubyforge.org/rspec/1.3.0/classes/Spec/Matchers.html seem
to
refer to eq in place of ==

eql? and == are the same thing, at least in most classes, but I think ==
fits in better with the spirit of the language.

and use 'equal' for testing if it's actually
the
same object instance.

Right. That's what equal? is for. Please read the docs on these three
methods.

Try not to use factories like fixtures, which is what you're doing here.
Specify the data as you need it.

Can you please elaborate more on this? This seems contrary to my
understanding of setting up tests of your model.

How so?

I bought the pragmatic
rspec book http://www.pragprog.com/titles/achbd/the-rspec-book but
haven't
had a chance to read it yet. I thought it was a good idea to use
factories
(using factory_girl) to create your isolated model objects for testing.

It is. But don't create a separate factory for each car with all its
features -- that's barely better than using fixtures. Instead, do
Factory.create :car, :make => 'Mercedes', :feature =>
Factory.create(:feature, :name => 'Stereo')
right in your spec file. This way you can make custom records
containing exactly the properties needed for each individual spec.

This ensures that each spec example only has the records it needs. It
also makes it easier to use something like Faker.

It
sounds like your stating I should use the real populated DB (even if
local.)

Well, factories do use the DB. What are you asking here?

I'd love some more elaboration here if you get the time.

Thanks again for your time so far.

You're welcome!

Best,

The issue with both each of these posts is that the code will not work
in a lot of situations due to one small hole. I was hoping to do this
entire thing with a DB, I think I may have now found that way to do
this through the post.

In either case, the hole with that particular iterator comes from a
possible lack of efficiency [I don't know ruby that well], and a
situational issue.

def feature_types
    results = Hash.new
    prev = nil
    @features.each do |f|
      if (f.feature_type.name != prev)
        results[f.feature_type.name] = []
      end
      results[f.feature_type.name] << f
      prev = f.feature_type.name
    end
    results
  end

was the code posted above for a loop to solve this issue. the logic
issue lies in
      if (f.feature_type.name != prev)
        results[f.feature_type.name] = []
      end

and the using of a "prev" function at all. We can't use a previous
item as a quality method of comparison without retrieving duplicate
results (because your features will potentially [often] have an out-of-
order feature_type list.)

You can't do prev if the feature_type iteration ends up looking like:
[1,2,2,2,1,2]. Both the last 1 and 2 will be included because they
weren't set to be in the prev variable.
The second problem with an iterator like this efficiency. I am not a
Ruby on Rails expert. I do not know if every time you call
f.feature_name.* calls a new query. If it does, that will drop
efficiency on the DB side (calling a potential inf queries as opposed
to just two or three.)

All of that being said, I solved the problem (possibly improperly) by
doing this:

1) Adding a scope definition to feature_types called "with_car", so,
FeatureType.with_car would find me the feature types that, by process,
can exist under the given car. This also uses group to add efficiency
and eliminate duplicates.

2) Iterating through each feature_type and pulling potential features
from the already-established self.features array by using a select to
increase efficiency:

    @features = self.features

    @feature_types = FeatureType.with_car(self)

    @feature_types.each do |feature_type|
      feature_type.features = @features.select {|feature|
feature.feature_type_id == feature_type.id}
    end

This iterator reduces the DB load down to two queries (although I KNOW
this could be reduced using a long, handwritten Join -- or the
nested_has_many plugin)
The potential for duplicate feature_types is removed in the
FeatureType.with_car scope.

If anyone has a solution and can help me do this more the 'rails way,'
that would be excellent. I greatly appreciate the help and wealth of
knowledge already displayed here.

It’s more idiomatic Ruby. You’d write x == 2, not x.eql?(2), and your

matchers should follow the same practice for better readability.

I actually prefer x.size == 3 over x.size.should eql(3), I was just trying to do things the ruby/rspec way.

And I think you meant eql – does eq even exist?

eq does exist now which represents == https://github.com/rspec/rspec-expectations/commit/a23000ef6dbccfe6655265c3f30ac7b826f1943d

(I must have seen some some tutorial using eq() which apparently is valid now - at least as of rspec 2.0.1 which I’m using)

Try not to use factories like fixtures, which is what you’re doing here.

Specify the data as you need it.

Can you please elaborate more on this? This seems contrary to my

understanding of setting up tests of your model.

How so?

I was being an idiot and forgot about fixtures. Because in another forum I was just asking about using a ruby seed file to populate the db up front, in my mind I was equating fixtures with using a seed file to populate the db. That explains the stupid confusion in some of the rest of the previous post. I apologize. Too many newbie things going through my head at once that I was screwing up terms.

I bought the pragmatic

rspec book http://www.pragprog.com/titles/achbd/the-rspec-book but

haven’t

had a chance to read it yet. I thought it was a good idea to use

factories

(using factory_girl) to create your isolated model objects for testing.

It is. But don’t create a separate factory for each car with all its

features – that’s barely better than using fixtures. Instead, do

Factory.create :car, :make => ‘Mercedes’, :feature =>

Factory.create(:feature, :name => ‘Stereo’)

right in your spec file. This way you can make custom records

containing exactly the properties needed for each individual spec.

This ensures that each spec example only has the records it needs. It

also makes it easier to use something like Faker.

Cool. I’ll definitely do that! (I see now totally what you mean about my factories being not much better than fixtures.)

Thanks again!

def feature_types

results = Hash.new

prev = nil

@features.each do |f|

  if ([f.feature_type.name](http://f.feature_type.name) != prev)

    results[[f.feature_type.name](http://f.feature_type.name)] = []

  end

  results[[f.feature_type.name](http://f.feature_type.name)] << f

  prev = [f.feature_type.name](http://f.feature_type.name)

end

results

end

was the code posted above for a loop to solve this issue. the logic

issue lies in
if (f.feature_type.name != prev)

    results[[f.feature_type.name](http://f.feature_type.name)] = []

  end

and the using of a “prev” function at all. We can’t use a previous

item as a quality method of comparison without retrieving duplicate

results (because your features will potentially [often] have an out-of-

order feature_type list.)

You can’t do prev if the feature_type iteration ends up looking like:

[1,2,2,2,1,2]. Both the last 1 and 2 will be included because they

weren’t set to be in the prev variable.

I should have qualified that in my code I’d be making sure to order by featureType (id or name in this simple test case.)

The second problem with an iterator like this efficiency. I am not a

Ruby on Rails expert. I do not know if every time you call

f.feature_name.* calls a new query. If it does, that will drop

efficiency on the DB side (calling a potential inf queries as opposed

to just two or three.)

I’d hope it wouldn’t make a new query as yes that would defeat the purpose. I’d be sure in the initial call to get a car and its features that it was fully hydrated (using :include/:includes or whatever the exact syntax is for making sure you aren’t stuck with an n+1 situation.)

All of that being said, I solved the problem (possibly improperly) by

doing this:

  1. Adding a scope definition to feature_types called “with_car”, so,

FeatureType.with_car would find me the feature types that, by process,

can exist under the given car. This also uses group to add efficiency

and eliminate duplicates.

  1. Iterating through each feature_type and pulling potential features

from the already-established self.features array by using a select to

increase efficiency:

@features = self.features



@feature_types = FeatureType.with_car(self)




@feature_types.each do |feature_type|

feature_type.features = @features.select {|feature|

feature.feature_type_id == feature_type.id}

end

This iterator reduces the DB load down to two queries (although I KNOW

this could be reduced using a long, handwritten Join – or the

nested_has_many plugin)

The potential for duplicate feature_types is removed in the

FeatureType.with_car scope.

If anyone has a solution and can help me do this more the ‘rails way,’

that would be excellent. I greatly appreciate the help and wealth of

knowledge already displayed here.

I’m pretty newb so I can’t comment on the above…

I still think it’s cleaner to just get all features ordered by feature_type_id and then just do your switching in the view (when feature_id) changes. (You create a new header when you see the feature_id change from the previous iteration.)

Rick R wrote in post #967255:

from the already-established self.features array by using a select to
   end

I still think it's cleaner to just get all features ordered by
feature_type_id and then just do your switching in the view (when
feature_id) changes. (You create a new header when you see the
feature_id
change from the previous iteration.)

Don't order. Group.

--
Rick R

Best,