Adding to has_many on create and edit


#1

Hello,

I am fairly new to RoR and I have a question I hope can be solved
elegantly (so many things are, so why not this one too?). I have a
working solution, but I want to be sure I am doing things the “rails
way” (having come from PHP). You could say I am looking for a little
validation (no flames please).

I have a table of units like so:

CREATE TABLE units (
id int(11) NOT NULL auto_increment,
number varchar(255) NOT NULL default ‘’,
description text NOT NULL,
PRIMARY KEY (id)
) TYPE=MyISAM;

that has_many :prices like so:

CREATE TABLE prices (
id int(11) NOT NULL auto_increment,
price decimal(10,2) NOT NULL default ‘0.00’,
unit_id int(11) default NULL,
created_at datetime NOT NULL default ‘0000-00-00 00:00:00’,
PRIMARY KEY (id),
KEY unit_id (unit_id)
) TYPE=MyISAM;

My goal is to be able to create a unit and set its price at the same
time. Then, when I update a unit, should I change the price, I want
to add a new row and time stamp to the price table.

My unit model looks like this:


class Unit < ActiveRecord::Base
has_many :prices
has_one :most_recent_price, :class_name => “Price”, :order =>
‘created_at DESC’
end

The has_one :most_recent_price lets me grab the most recent price for
display and comparison purposes.

I can currently accomplish my aims in the controller like so:


class AdminUnitsController < ApplicationController
#snip

def create
@unit = Unit.new(params[:unit])
@unit.prices.create(“price” => params[:price])
@levels = Level.find(:all, :order => “LPAD
(levels.elevator_number,5,“0”) ASC”)
@unit_types = UnitType.find(:all, :order => “unit_types.name
ASC”)
if @unit.save
Unit.find(@unit.id).update_attributes(params[:unit])
flash[:notice] = ‘Unit was successfully created.’
redirect_to :action => ‘list’
else
render :action => ‘new’
end
end

def update
@unit = Unit.find(params[:id])
unless params[:price] == @unit.most_recent_price.price.to_s
@unit.prices.build(“price” => params[:price])
end
@levels = Level.find(:all, :order => “LPAD
(levels.elevator_number,5,“0”) ASC”)
@unit_types = UnitType.find(:all, :order => “unit_types.name
ASC”)
if @unit.update_attributes(params[:unit])
flash[:notice] = ‘Unit was successfully updated.’
redirect_to :action => ‘show’, :id => @unit
else
render :action => ‘edit’
end
end

#snip
end

and a _form.rhtml like so:


<%= error_messages_for ‘unit’ %>

Number
<%= text_field 'unit', 'number' %>

Description
<%= text_area 'unit', 'description' %>

Base Price
$<%= text_field_tag 'price', @unit.most_recent_price.nil? ? "0.00" : @unit.most_recent_price.price %>

Is this the “rails way”? Is there a better way?

Specifically, is the text_field_tag ‘price’ construct the best way to
show/capture the price value or is there a “rails way” of showing/
capturing the price value and capturing it with params[] that I don’t
know about? If there isn’t, would it be best to move the ternary
operator statement into the model or is kosher to keep it in the view?

Thanks for any input,

Nick


#2

Hi !

2006/3/9, Nicholas P. Mueller removed_email_address@domain.invalid:

   flash[:notice] = 'Unit was successfully created.'
   redirect_to :action => 'list'
 else
   render :action => 'new'
 end

end

Hmm, this is too complicated. If you look at your log, you’ll see two
updates to the database:

SQL INSERT - corresponds to “if @unit.save
SQL UPDATE - corresponds to
Unit.find(@unit.id).update_attributes(params[:unit])

You don’t really need the second line - notice you did
Unit.new(params[:unit]) ? This has the same effect as an
update_attributes to an existing object.

   flash[:notice] = 'Unit was successfully updated.'
   redirect_to :action => 'show', :id => @unit
 else
   render :action => 'edit'
 end

end

That looks fine.

Is this the “rails way”? Is there a better way?

I wouldn’t say there’s a Rails way for this case. Your solution is
pragmatic, and it works. I would have probably done it the same way.

One thing you might want to investigate. Two times you have this code:

 @levels = Level.find(:all, :order => "LPAD

(levels.elevator_number,5,“0”) ASC")
@unit_types = UnitType.find(:all, :order => “unit_types.name
ASC”)

You should extract that to a filter, like this:

class AdminUnitsController < ApplicationController
before_filter :load_lookup_data, :except => [:index, :list, :destroy]

protected
def load_lookup_data
@levels = Level.find(:all, :order =>
“LPAD(levels.elevator_number,5,“0”) ASC”)
@unit_types = UnitType.find(:all, :order => “unit_types.name
ASC”)
end
end

Hope that helps !


#3

Thanks for the advice!

RE: the insert followed by update, that was from some code I had cut/
paste from another project.

RE: the filter - I was looking for a way to do something like that!
Thanks for pointing it out to me! I’ve written a few projects where
I was cutting and pasting the same code blocks all over and thought
there was probably a way to do something, but hadn’t yet bothered to
look.

Thanks so much,

Nicholas P. Mueller