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| %>

<%= feature_type.name %>


<% feature_type.features.each do |feature| %>
<% feature.name %>
<% 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.

On 5 December 2010 03:08, Roger [email protected] wrote:

Model: Feature_type
To stick to the rails conventions that should be FeatureType

<% 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 %>

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

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).

On Sun, Dec 5, 2010 at 7:13 AM, Colin L. [email protected]
wrote:

Model: Feature
the child features of that category for the car.
<% feature.name %>

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 != prev)
results[f.feature_type.name] = []
end
results[f.feature_type.name] << f
prev = 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

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).

On Sun, Dec 5, 2010 at 7:13 AM, Colin L. [email protected]
wrote:

Model: Feature
the child features of that category for the car.
<% feature.name %>

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,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Sent from my iPhone

On Tue, Dec 7, 2010 at 11:00 PM, Marnen Laibow-Koser
[email protected]wrote:

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 Pragmatic Bookshelf: By Developers, For Developers 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.

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.

On Wed, Dec 8, 2010 at 12:44 AM, Marnen Laibow-Koser
[email protected]wrote:

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 ==

(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.

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!

Rick R wrote in post #967105:

On Tue, Dec 7, 2010 at 11:00 PM, Marnen Laibow-Koser
[email protected]wrote:

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 Pragmatic Bookshelf: By Developers, For Developers 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,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Sent from my iPhone

On Wed, Dec 8, 2010 at 2:39 AM, Roger [email protected] wrote:

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.)

increase efficiency:

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

On Wed, Dec 8, 2010 at 2:39 AM, Roger [email protected] wrote:

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.)


Rick R

Rick R wrote in post #967255:

On Wed, Dec 8, 2010 at 2:39 AM, Roger [email protected] wrote:

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,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]