By the sounds of it, this join table represents a whole new model. I'm not sure exactly what your requirements are, but I'll play out one potential solution. For now, let's call the join model a SalesRelationship.
I'm going to call the item/product objects "products", since to me it's a little less generic.
The migration for this would look something like:
class CreateSalesRelationship < ActiveRecord::Migration
def self.up
create_table :sales_relationship |t|
t.string :product_id
t.string :other_product_id
t.string :type
t.timestamps
end
end
def self.down
drop_table :sales_relationship
end
end
You can include any other attributes necessary in that migration as well. Next, create a SalesRelationship model:
class SalesRelationship < ActiveRecord::Base
belongs_to :product
belongs_to :other_product, :class_name => "Product
end
Then, create subclasses for the different types of relationship:
class CrossSell < SalesRelationship
end
class UpSell < SalesRelationship
end
class Complement < SalesRelationship
end
class Substitute < SalesRelationship
end
Then set up the relationships on the Product model:
class Product < ActiveRecord::Base
has_many :sales_relationships, :dependent => :destroy
has_many :cross_sells
has_many :up_sells
has_many :complements
has_many :substitutes
has_many :cross_sale_products, :through => :cross_sells, :source => :other_product
has_many :up_sale_products, :through => :up_sells, :source => :other_product
has_many :complementary_products, :through => :complements, :source => :other_product
has_many :substitute_products, :through => :substitutes, :source => :other_product
end
Now you should be able to create and add related products all you want.
@product1.substitute_products << @product2
new_product = @product2.complementary_products.build
For extra credit, you could write a simple validation on the SalesRelationship model that ensures a product is never related to itself. That may or may not be necessary, depending on your requirements.