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

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,