Strugling with has_many :through

Hey all

Im trying to get has_many :through relationship working, as i head its
better and more robust that HABTM.

I have the following tables/objects…

class Currency < ActiveRecord::Base
has_many :product_prices
has_many :products, :through => :product_prices
end

class Product < ActiveRecord::Base
has_many :product_prices
has_many :currencies, :through => :product_prices
end

class ProductPrice < ActiveRecord::Base
belongs_to :product
belongs_to :currency
end

Is that the correct configuration for what is effectivly a HABTM
relationship? If not could somone please point me in the right
direction!!

Furthermore, if i wanted to submit data into the product prices
table/object, how would i do that? The same was as any other commit?
Object.new(params[:object]) ?

Cheers all

Tim

HABTM does have its use. If your domain does not need any attributes
other
than the foreign keys of the tables that are connected then HABTM is
good
enough. In this case the association class is just a join table.

When your application demands that other attributes needs to be
persisted
that belongs to the association class then you have to go for the
has_many
:through relationship.

has_many :currencies, :through => :product_prices
end

class ProductPrice < ActiveRecord::Base
belongs_to :product
belongs_to :currency
end

Is that the correct configuration for what is effectivly a HABTM
relationship? If not could somone please point me in the right
direction!!

This is correct.

Furthermore, if i wanted to submit data into the product prices

table/object, how would i do that? The same was as any other commit?
Object.new(params[:object]) ?

When a product is associated to the currency, you can do:

product_price = ProductPrice.new
product_price.attribute1 = params[:attribute1]

and so on.

I may be missing somthing here, but in my controller i have

def add
@currencies = Currency.find(:all, :conditions => [‘currency_active =
1’])
unless request.post?
# cast a new product object
@product = Product.new
@prices = @product.product_prices.new
else
@product = Product.new(params[:product])
@prices = @product.product_prices.new(params[:prices])
if @product.save
# do the saving…
end
end
end

And in my view (this is an extract just to do with the currencys…)…

<% for currency in @currencies -%>

<%=currency.currency_name%>
<%=text_field :prices, :unit_price %>
<% end -%>

But when i submit, it sets both values to the same value. I know i need
somthing like… params[:prices][1][:unit_price] and so on, with array
indexes, but how do achive this in the HTML form with the rails helpers?
Ive tried all sorts!!

Any help would be welcomed

Cheers

Tim

But when i submit, it sets both values to the same value. I know i need
somthing like… params[:prices][1][:unit_price] and so on, with array
indexes, but how do achive this in the HTML form with the rails helpers?
Ive tried all sorts!!

What version of Rails are you using?

What version of Rails are you using?

Im using version 1.1.6

On 10/23/06, Tim P. [email protected] wrote:

What version of Rails are you using?

Im using version 1.1.6

Instead of using:
@prices = @product.product_prices.new(params[:prices])

Try this:

@prices = ProductPrice.new(params[:prices])
@product.product_prices << @prices

@prices = ProductPrice.new(params[:prices])
@product.product_prices << @prices

Ok cool, cheers, i’ll give a whirl. Effectivly, rather than setting a
new object, its appending the posted paramaters to the product_prices in
the @product object.

Thats all well and good, i understand that (i think) but in my view
code, how do i set it up so that is posts two different values from a
display loop? In order for me to have say, a euro price and a sterling
price, i would want to have the form effectivly generate somthing like

That would work wouldnt it? As it would then just pass back into the
prices object which would append onto my product object?

Cheers for any help

Tim

Thanks for the suggestion, but it wouldnt unfortunatly,

As for instance, the currencys are dynamic, so in another setup you
might have USD, Yen, GBP and Euro’s, so what you would idealy do is have
it in some kind of object and insert the rows dynamicly, but ive no idea
on the syntax?

Possibly, if i had it in an array, or hash, i could loop through them
doing the appends and saves. But that to me, seems not to be a clean
rails way.

I know what i want to achiveve, but im not sure on the best way to do
it?

cheers

Tim

price, i would want to have the form effectivly generate somthing like

So you have both Euro and Sterling price displayed on the page as text
fields? And you want to populate those into your product_prices table?

@prices = ProductPrice.new(params[:euro_price])
@product.product_prices << @prices

@prices = ProductPrice.new(params[:sterling_price])
@product.product_prices << @prices

Will this solution work for your scenario?

How does your screen look like? I really don’t understand what you are
saying. Can you explain clearly without making any assumption what you
are
trying to do?

Bala P. wrote:

How does your screen look like? I really don’t understand what you are
saying. Can you explain clearly without making any assumption what you
are
trying to do?

Sure, sorry i appreciate its a little confussing.

I have a currencies table in the database, which could hold multiple
rows for different currencys.
I also have a products table, which details information about products
on the system etc etc. Now, one product will have many prices based on
the currency - so its effectivly a HABTM, but ive changed it to has_many
:through as i heard it was more flexiable.

so basicly, the table structure is this: products HABTM currencies, and
the join table is product_prices. My models look like this:

class Currency < ActiveRecord::Base
has_many :product_prices
has_many :products, :through => :product_prices
end

class Product < ActiveRecord::Base
has_many :product_prices
has_many :currencies, :through => :product_prices
end

class ProductPrices < ActiveRecord::Base
belongs_to :products
belongs_to :currencies
end

So thus, im building an administration where when the client wants to
add a new product, they fill in the details, and say that the currencies
table has two rows, one for euro, and one for sterling, the add product
screen would present two text feilds needing both an inputed price for
sterling for that product and one for euro.

So you see, when i pass the post back to my controller, how do i handle
having two (or more, based on wats in the currencies table) rows to
insert into the product_prices table?

Hope that a good explanation of it

Cheers

Tim

So you see, when i pass the post back to my controller, how do i handle
having two (or more, based on wats in the currencies table) rows to
insert into the product_prices table?

Your question basically boils down to “How to create a new :through
association?”. You can create :through association using the << method.
Both
Product and Price must be saved before you can use that operator.

So,

product = Product.create(:name => “DVD”)
euro_price = Currency.create(:amount => 123)
dollar_price = Currency.create(:amount => 99)

product.currencies << euro_price
product.currencies << dollar_price

You can define two fields name euro_price and dollar_price in your view.
When the user submits, you can do the above in your action.

Ok, i built a solution, id thought id share it back as i thnk you missed
the point a bit!

Its obviously desirable to run everything from the DB as it then stays
dynamic, and your solution of hardcoding :sterling and :euro form
feilds, although would work, is inflexible and would mean adding new
code if a new currency was added - so over its more work and less agile.

So, the solution i came up with was this…

– controller —

def add
@currencies = Currency.find(:all)
unless request.post?
# If this anything BUT a _post request
@product = Product.new
@prices = @product.product_prices.new
else
# handle the post
# process product post
@product = Product.new(params[:product])
@product.valid?
# make new var
@prices = @product.product_prices
# get the prices array and process…
params[:prices].map do |key, val|
tmp = @product.product_prices.new(params[:prices][key])
tmp.valid?
@prices << tmp
end
# Start the DB transaction
# this will raise AR exception if it fails, i.e. the models are
not valid
begin
Product.transaction do
# try and save the product model, thus gaining the DB id
@product.save!
# now, with the id from the product model, save the variable
# number of price rows in the product_prices table
for price in @prices
price.product_id = @product.id
end
@product.product_prices = @prices
@product.save!
end
rescue => e
flash[:error] = e
end
end
end

Thats about as clean as i could make it, the functionality isnt
repeated anywhere else so i didnt need to break it off into a module.

And in my view code:


Using the array type set up, i could then manipulate this with .map do
|key, val| so if i added another currency, sav, Yen, then it would just
adapt and be ready to take a new currency :slight_smile:

My model objects stayed the same as above.

Do you see what i mean? Im not 100% sure this is the best way, but out
of everything i tried it seems to work the best, and its reliable in
terms of validations and the like!

Cheers

Tim