asc99c
asc99c

Reputation: 3905

Easiest way to edit associations in Rails

I have what I imagine must be a common situation, but possibly can't find the way to phrase the question to get the solution...

I have a couple of linked models in my application:

class Product < ActiveRecord::Base
  validates_uniqueness_of :prod_code
end

class Stock < ActiveRecord::Base
  belongs_to :product
end

In fact there are a number of other models which also belong to Product. By default, on the stock record, I just see the product_id field, which is an auto-incrementing number that isn't much help to the user. Products have a unique prod_code which is on barcodes etc. and is the natural key of the product database.

What I would like is for the create/edit screens for the stock and other linked models to show a text field for the prod_code, and to be able to respond to parameters in the form stock[prod_code] in a sensible way (e.g. look up the prod_code, and set the prod_id based on the result), and automagically (e.g. Stock.new(params[:stock]) should work.

To clarify, setting stock[prod_code] would not change anything in the product database; it would instead change the product_id for the relevant stock record i.e. link the stock record to a different product record.

At present, I've got various methods defined in the stock model such as prod_code= that make this work. But as I mentioned, there are actually multiple models that refer back to my products table. Is there any way I can define something inside the Product model?

e.g. something like a referenced_by method that would tell all linked models to deal with the prod_code argument by looking it up in the products table?

class Product < ActiveRecord::Base
  validates_uniqueness_of :prod_code
  referenced_by :prod_code
end

Upvotes: 0

Views: 2297

Answers (2)

Petrik de Heus
Petrik de Heus

Reputation: 970

You could use prod_code instead of product_id as the foreign_key:

class Stock
  belongs_to :product, :foreign_key => "prod_code"
end

Or you can set it on Product:

class Product
  has_many :stocks, :foreign_key => "prod_code"
  has_many :images, :foreign_key => "prod_code"
  has_many :sizes,  :foreign_key => "prod_code"
end

Upvotes: 0

trueunlessfalse
trueunlessfalse

Reputation: 1233

What you probably want to use are nested forms.

First you can tell your model to accept the nested attributes (api for nested attributes),like

accepts_nested_attributes_for :product

in your stock model.

Then in your form you can use fields_for (api for fields_for), to nest product fields into your stock form. Imagine something like:

= form_for(@stock) do |form|
  = form.text_field :some_stock_attribute
  = form.fields_for(@stock.product) do |product_form|
     = product_form.text_field :prod_code

This essentially nests the name of your prod_code form field, to "stock[product_attributes][prod_code]", while, thanks to the accepts_nested_attributes_for, your stock model is prepared to pass the product_attributes to the associated product.

Update (see comments):

A referenced_by method as you imagine, does not exist. Also you will have to take care of a bit more, then just changing the associated product. You will still want to display an error, if the prod_code doesn't exist, i assume.

Roughly you can add a virtual attribute to the stock, which allows your stock form to have a text_field :prod_code (add attr :prod_code to stock model).

Second, before validation you will probably need a method, that looks up the product for the prod_code, and changes the association, or adds an error on :prod_code.

this could look a bite like this:

class Stock
  attr :prod_code

  before_validation :associate_product_from_prod_code

  def associate_product_from_prod_code
    unless self.product = Product.where(:prod_code => prod_code)
      errors[:prod_code] << "Product Code is not valid"
    end
  end

end

Upvotes: 2

Related Questions