Has_many through model?

Hi,

I wonder if anyone know how I can make these relations work, I’m getting
error on relationship.

Is it because I’m not doing it the rails way?
Is it a MUST to have “layout_id” and “snippet_id” in the join table
(container) ? Cannot I choose the column names like “reference_id” and
“container_id” ?

class Layout < ActiveRecord::Base
has_many :snippets, :through => :container
end

class Container < ActiveRecord::Base
belongs_to :layout, :foreign_key => :reference_id
belongs_to :snippets, :foreign_key => :container_id, :class_name =>
“Container::Snippet”
end

class Container::Snippet < ActiveRecord::Base
has_many :layouts, :through => :container
end

Thanks for help.

Regards,
Jamal

Jamal S. wrote:

class Layout < ActiveRecord::Base
has_many :snippets, :through => :container
end

You did not supply the error, but I’m guessing you had something like:

ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the
association :container in model Layout

This is because the “has_many :through” creates an association through
another one. In your case through the “container” association which you
haven’t defined. You should try:

class Layout < ActiveRecord::Base
has_many :containers, :foreign_key => :reference_id
has_many :snippets, :through => :containers
end

Note that the plural “containers” makes more sense here, too.

class Container::Snippet < ActiveRecord::Base
has_many :layouts, :through => :container
end

The same with this model…

That helped allot Mark B. :slight_smile: Thanks.

I just wonder why I cannot create new snippet from layout?

Layout.find(:first).snippets.create(:name => “Let me test”)
NoMethodError: undefined method `layout_id=’ for
#Container::Snippet:0x5cece8

layout_id is reference_id in the container, how can I change the column
name?

Jamal S. wrote:

I just wonder why I cannot create new snippet from layout?

Layout.find(:first).snippets.create(:name => “Let me test”)
NoMethodError: undefined method `layout_id=’ for
#Container::Snippet:0x5cece8

layout_id is reference_id in the container, how can I change the column
name?

The problem here is that, in order to create a record through a join
model, there must be an id available on both models first in order to
create the link. You can see this better when you use #build instead of
#create.

You can solve this by creating the “through” object on the immediate
association. Try:

l = Layout.find :first
l.containers.create :snippet =>
Container::Snippet.new(:name => “Let me test”)

If you refer to l.snippets between loading l and adding the associated
object, then the new association will only be stored in the database so
you need to reload it to see it:

l = Layout.find :first
l.snippets
=> # shows the original snippets
l.containers.create :snippet =>
Container::Snippet.new(:name => “Another test”)
l.snippets
=> # new snippet not shown as snippets already loaded
l.snippets.reload
=> # new snippet now included

Jamal S. wrote:

So I could basically just use has_and_belongs_to_many, since I thought

If your join table will not be used as a model in its own right with
attributes of its own, then HABTM is fine. If you want the join table
to be represented by a real model with its own attributes, then you will
probably start running into problems. HABTM seems to be falling out of
favour, but there is no likelihood of it being dropped, I expect. Use
what fits your requirements best.

So I could basically just use has_and_belongs_to_many, since I thought
has_many :through would be able to create new snippet through container
by using…

l = Layout.find(:first)
s = l.snippets.new

What do you think is the better?

Mark B. wrote:

Jamal S. wrote:

So I could basically just use has_and_belongs_to_many, since I thought

If your join table will not be used as a model in its own right with
attributes of its own, then HABTM is fine. If you want the join table
to be represented by a real model with its own attributes, then you will
probably start running into problems. HABTM seems to be falling out of
favour, but there is no likelihood of it being dropped, I expect. Use
what fits your requirements best.

Thanks for explaining and thanks for help Mark :slight_smile:

I just wonder if you have the time to tell me how I can tell if the user
is creating snippet, og extension?

class Container < ActiveRecord::Base
belongs_to :contents, :foreign_key => :container_id, :class_name =>
“Container::Content”
belongs_to :extensions, :foreign_key => :container_id, :class_name =>
“Container::Extension”
belongs_to :snippets, :foreign_key => :container_id, :class_name =>
“Container::Snippet”
end

Since if the user create snippet then the “container_type” should be
‘Snippet’.

PS: I have already used reference_type as simple inherit table :slight_smile:

class Join::Layout < Container
belongs_to :layout, :foreign_key => :reference_id

private
def self.inheritance_column
‘reference_type’
end
end

Thanks again for your help :slight_smile:

Jamal S. wrote:

I just wonder if you have the time to tell me how I can tell if the user
is creating snippet, og extension?

Since if the user create snippet then the “container_type” should be
‘Snippet’.

It looks like you are trying to use your join model to do inheritance.
Rails can handle this for you. Look for articles on single table
inheritance (STI) and polymorphic associations and see what best fits
your needs.

On 23 Mar 2008, at 17:05, Jamal S. wrote:

It looks like you are trying to use your join model to do

belongs_to snippet, :before_create => :set_container_type

Don’t you just want a polymorphic association ?

Fred

Mark B. wrote:

Jamal S. wrote:

I just wonder if you have the time to tell me how I can tell if the user
is creating snippet, og extension?

Since if the user create snippet then the “container_type” should be
‘Snippet’.

It looks like you are trying to use your join model to do inheritance.
Rails can handle this for you. Look for articles on single table
inheritance (STI) and polymorphic associations and see what best fits
your needs.

I’m already using single table inheritance on “reference_type”.

It would be cool if belongs_to had before_save

belongs_to snippet, :before_create => :set_container_type

def set_container_type
container_type=‘Snippet’
end