Learning deep associations in models

Hello,

I’m a web developer comping from Zend Framework and I’m trying to learn
Rails the right way and avoid Bad Practice from the start.

I have a few questions in my head that I’m unable to get answered on my
own, so I’m going to try and get it explained with an example.

Let’s say I’m building a site for a restaurant and the website have a
Menu model (which is essentially categories, like Pizza, Burgers etc…),
a Product model that belongs to the menu and an Ingredient model which
belongs to Product, code:

class Menu < ActiveRecord::Base
has_many :products

… validations and other logic

end

class Product < ActiveRecord::Base
belongs_to :menu
has_many :ingredients

accepts_nested_attributes_for :ingredients

… validations and other logic

end

class Ingredient < ActiveRecord::Base
belongs_to :product

validates :name, :presence => true, :unique => true

… other validations and other logic.

end

So far so good, however now I’m stuck because I’m not sure how to code
what I have in mind without ending up with a spaghetti code. In the new
product form, I would like the user to be able to add ingredients (
accepts_nested_attributes_for already there so I should be good for
having the fields in the form ) but when the form has been filled and
submitted, I want to create an ingredient if and only if it does not
already exists, if on the other hand, it does, in this case the
association should happen on the one that already exists, for example:

Let’s say that I already have :onion in the ingredients table, if I
added a new product that has onion, I should not have two rows for the
:onion ingredient (should not happen since I added a validation), but
instead the association should happen on the one that already exists…

How can I accomplish that? Should I be using a before_create hook? What
should I do in that hook? or should I create a custom create method?

Thank you.

Wael

The easiest solution I could find to this problem is to remove
accepts_nested_attributes_for from the Product model and in the
controller do:

@product = Product.new(params[:product])
@ingredient = find_or_initialise_by_name(params[:ingredient]

if @product.save && @ingredient.save
redirect_to @product, :flash => ‘Success’
else
redirect_to :new
end

What do you think guys, do you have a better solution ?

Wael N. wrote in post #1000565:

Hello,

I’m a web developer comping from Zend Framework and I’m trying to learn
Rails the right way and avoid Bad Practice from the start.

I have a few questions in my head that I’m unable to get answered on my
own, so I’m going to try and get it explained with an example.

Let’s say I’m building a site for a restaurant and the website have a
Menu model (which is essentially categories, like Pizza, Burgers etc…),
a Product model that belongs to the menu and an Ingredient model which
belongs to Product, code:

class Menu < ActiveRecord::Base
has_many :products

… validations and other logic

end

class Product < ActiveRecord::Base
belongs_to :menu
has_many :ingredients

accepts_nested_attributes_for :ingredients

… validations and other logic

end

class Ingredient < ActiveRecord::Base
belongs_to :product

validates :name, :presence => true, :unique => true

… other validations and other logic.

end

So far so good, however now I’m stuck because I’m not sure how to code
what I have in mind without ending up with a spaghetti code. In the new
product form, I would like the user to be able to add ingredients (
accepts_nested_attributes_for already there so I should be good for
having the fields in the form ) but when the form has been filled and
submitted, I want to create an ingredient if and only if it does not
already exists, if on the other hand, it does, in this case the
association should happen on the one that already exists, for example:

Let’s say that I already have :onion in the ingredients table, if I
added a new product that has onion, I should not have two rows for the
:onion ingredient (should not happen since I added a validation), but
instead the association should happen on the one that already exists…

How can I accomplish that? Should I be using a before_create hook? What
should I do in that hook? or should I create a custom create method?

Thank you.

Wael

On May 24, 2011, at 6:06 AM, Wael N. wrote:

Let’s say I’m building a site for a restaurant and the website have a

belongs_to :product
accepts_nested_attributes_for already there so I should be good for
having the fields in the form ) but when the form has been filled and
submitted, I want to create an ingredient if and only if it does not
already exists, if on the other hand, it does, in this case the
association should happen on the one that already exists, for example:

Let’s say that I already have :onion in the ingredients table, if I
added a new product that has onion, I should not have two rows for the
:onion ingredient (should not happen since I added a validation), but
instead the association should happen on the one that already exists…

This is a great place to look at find_or_create_by_[your attribute
here]. I might try

@ingredient = Ingredient.find_or_create_by_name params[:ingredient]
[:name]
@product.igredients << @ingredient

Not sure about the params part, but the rest should work.

Walter

On Tuesday, May 24, 2011 4:06:22 AM UTC-6, Ruby-Forum.com User wrote:

Menu model (which is essentially categories, like Pizza, Burgers etc…),
belongs_to :menu
validates :name, :presence => true, :unique => true
already exists, if on the other hand, it does, in this case the
association should happen on the one that already exists, for example:

Let’s say that I already have :onion in the ingredients table, if I
added a new product that has onion, I should not have two rows for the
:onion ingredient (should not happen since I added a validation), but
instead the association should happen on the one that already exists…

I just want to clarify something (possibly unrelated).

So, are you saying that if I have product A which includes an onion
ingredient record, and I create product B and attempt to add an onion
ingredient record, that A’s onion should be located and made to refer to
product B?

I’m guessing this IS NOT the behavior you want (one product stealing
another’s ingredients). However, on my first pass, that’s what it kind
of
sounded like. Do you instead mean that you only an ingredient (name) to
be
unique amongst the set of ingredients that all belong to the same
product
(so product C can’t have two separate onion ingredient records)? Or, are
you
truly trying to keep the ingredients unique across the board (one onion
record in the whole table, regardless of how many products want to
have
it)?

If it is the latter case, I’d think you’d need some kind of
has_and_belongs_to_many relationship between products and ingredients
(either a simple habtm join table or a full join model with has_many
:through => … relationships).

Anyhow, sorry if this is tangential to your actual question.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs