24 Feb 2008, 07:20
Paul_pragsmall

Paul Jewell (2 posts)

Whilst playing with the habtm code I was informed that push_with_attributes was deprecated, and I should look to use :through instead. So, here is my effort at refreshing the code, with comments:

#! /bin/env ruby
#  Model for webshop stock management example in
#  Enterprise Integration with Ruby
#
#  Modified to remove the deprecated push_with_attributes
#  call. For more information, refer to "Agile Web Development with Rails"
#  chapter 18 - sub section "Using Models as Join Tables"

require 'rubygems'
require 'active_record'

# Modify the connection details to match your setup
# - Book Reference file 5, page 34
ActiveRecord::Base.establish_connection(
  :adapter => 'mysql',
  :host => '127.0.0.1',
  :username => 'paul',
  :database => 'webshop'
)

# One change to the Bouquet class - note the reference to :through
# in the second has_many line. This allows a link to the flowers
# table via an intermediate table called ingredients

class Bouquet < ActiveRecord::Base
  has_many :ingredients
  has_many :flowers, :through => :ingredients

  def price
    ingredients.inject(self.base_price) do |total, i|
      total += i.flower.price * i.quantity.to_i
    end
  end

  # When adding flowers to the bouquet, check to see if the
  # flower is already in the list - if so change the quantity
  # rather than creating a new addition
  # The Ingredients.new call is used as the << operator doesn't
  # allow for intermediate table attributes to be set.

  def add(flower,quantity)
    if (ingredient = self.ingredients.find(:first,
                                           :conditions => "flower_id = #{flower.id}"))
      ingredient.quantity += quantity
      ingredient.save
    else
      Ingredients.new(:flower_id => flower.id,
                      :bouquet_id => self.id,
                      :quantity => quantity).save!
    end
  end

  def to_s
    print "A #{self.name} costs $#{self.price} and contains #{self.flowers.size} flowers: \n"
    self.ingredients.each { |i| print i.flower.name, " ", i.quantity, " off\n" }
  end

end

# The link table known as bouquets_flowers is now not used. It's use is
# not recommended for cases where more detail than the link foreign keys
# are required.
#
# mysql statements to create:
# CREATE TABLE ingredients(
#   id int unsigned NOT NULL auto_increment primary key,
#   bouquet_id int NOT NULL,
#   flower_id int NOT NULL,
#   quantity int NOT NULL,
# );
#
# alter table ingredients add constraint fk1_bouquet
#   foreign key (bouquet_id) references bouquets(id);
# alter table ingredients add constraint fk1_flowers
#   foreign key (flower_id) references flowers(id);

class Ingredient < ActiveRecord::Base
  belongs_to :flower
  belongs_to :bouquet
end

# Note the reference to bouquets :through ingredients in the second
# has_many line

class Flower < ActiveRecord::Base
  has_many :ingredients
  has_many :bouquets, :through => :ingredients
  belongs_to :stock_item
end

class StockItem < ActiveRecord::Base
  has_one :flower
end

I am pretty new to ruby and ActiveRecord, and therefore would have no problem with constructive criticism :-)!

Hopefully this can clarify :through for anyone else who comes across the same issue.

Rgds., Paul

  You must be logged in to comment