views:

32

answers:

2

Given a migration

class CreateTalks < ActiveRecord::Migration
  def self.up
    create_table :talks do |t|
      t.integer :duration_hours
      t.integer :duration_minutes
    end
  end

  def self.down
    drop_table :talks
  end
end

and model

class Talk < ActiveRecord::Base
end

and object

class Duration
   attr_accessor :hours, :minutes
end

how do I map the duration_hours and duration_minutes columns to a Duration property in Talk so that I can do

d = Talk.first.duration
hours = d.hours
minutes = d.minutes

I'm aware that in this case I could translate the hours and minutes into seconds and store these in a single column but I'm looking to understand how I could achieve this type of mapping with ActiveRecord.

A: 

Write custom accessor methods.

Eimantas
Could you expand a bit, do you mean that I add accessor methods for duration_hours and duration_minutes that populate the duration property? If this is the case would they need to be public accessors or could I make them private? (I want to encapsulate the duration_hours and duration_minutes so that the rest of the app only uses the duration property)
opsb
I'd write two methods (duration= (setter) and duration (getter)). First one would parse passed string (in any possible format you can think of) and sets hours and minutes duration appropriately, second one - takes both values from db and combines them together in any format you need to.But to begin with - i'd keep duration as integer of seconds. Or, if you need better precision, as float.
Eimantas
A: 

I'm not sure I understand what you're trying to accomplish... especially with storing hours and minutes in two different columns rather than storing a date/time and extracting the hours and minutes .... buuuut... my understanding of attr_accessor is that:

attr_accessor :duration_hours

is equal to:

def duration_hours
  @duration_hours
end

def duration_hours=(d)
  write_attribute(:duration_hours, d)
end

So what Eimantas is referring to customizing the above methods to store or display information as you see fit... you should already be able to do:

d = Talk.first
hours = d.duration_hours
minutes = d.duration_minutes

Right?

EDIT:

If you're trying to make a list of lectures or something and the amount of time each elapsed using your columns, the way I would do it is (for display purposes only):

def duration
  @duration = self.duration_hours.to_s + ":" + self.duration_minutes.to_s
end

Then call using talk.duration. Sloppy but it works.

Kevin
In retrospect I think my thinking is clouded by my experience with java ORM frameworks like hibernate. In the case of hibernate the database table and the model can be quite different, achieved by defining the mapping. I'm starting to realise the full intention of the active_record pattern i.e. that there is no difference between the model and the database table. Any difference in representation must be achieved using custom accessors as has been described.
opsb
I have a niggling feeling that this is a slightly less efficient approach as the translation must be done each time the accessor is invoked. This is as opposed to the translation being performed once when the model is retrieved. However, from a pragmatic point of view it's likely that the custom accessors are only going to be called once per request in which case the efficiency issue is academic anyway.
opsb
By the way, the reason that I didn't want to use a Time column for duration was that it conveys an entirely different intent. It implies a point in time as opposed to a relative point e.g. 1 hour after the start time(start_time is also included in the real model). Perhaps I should go for a start/end time instead of start/duration but the model will be moved through time quite regularly and so it seem more appropriate. By using start/duration a move requires an update to only 1 column. It's also a more accurate reflection of the conceptual model that the user's have.
opsb