Forum: Ruby on Rails adding to has_many on create and edit

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Nicholas P. Mueller (Guest)
on 2006-03-10 04:07
(Received via mailing list)
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' %>

<!--[form:unit]-->
<p><label for="unit_number">Number</label><br/>
<%= text_field 'unit', 'number'  %></p>

<p><label for="unit_description">Description</label><br/>
<%= text_area 'unit', 'description'  %></p>

<p><label for="unit_price">Base Price</label><br />
$<%= text_field_tag 'price', @unit.most_recent_price.nil? ? "0.00" :
@unit.most_recent_price.price %>
</p>

<!--[eoform:unit]-->
-----------

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
8d4966c3e834dc2fb6900c5fff7c7ea5?d=identicon&s=25 François B. (fbeausoleil)
on 2006-03-10 04:23
(Received via mailing list)
Hi !

2006/3/9, Nicholas P. Mueller <railsdev@restechservices.net>:
>        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 !
Nicholas P. Mueller (Guest)
on 2006-03-10 05:29
(Received via mailing list)
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
This topic is locked and can not be replied to.