Extending model

Hi,

I’m looking for a way to extend one of my models to allow some level
of abstraction between what goes into it and how it is stored.

For example, say I have a Product and I want to set it’s price. A
person using the website will type the price in euros. Internally, I’d
like to store the price as an integer value of cents.

I can currently do this with some ugly code in the controller, but I’d
like the model to just accept a price in euros and automatically
convert it to cents.

In plain Ruby, I could do something like this:

  • Take in prices in euros, store them as cents internally (doesn’t

work right :frowning: )

  • def price

  • unless cents.nil?

  •  return cents / 100.0
    
  • else

  •  return cents
    
  • end

  • end

  • def price=(euros)

  • cents = euros * 100

  • end

But it doesn’t work in RoR. Is there something I’m missing? I have a
horrible feeling there should be a ‘self’ somewhere in there…

David B.


Site: http://antidis.com/

David B. wrote:

  • def price=(euros)
  • …>cents = euros * 100
  • .end

    But it doesn’t work in RoR. Is there something I’m missing? I have a .
    horrible feeling there should be a ‘self’ somewhere in there…


We develop, watch us RoR, in numbers too big to ignore.

Dave,

have a look at the before_save callback [1], which lets you do do
stuff like euro/cent conversion before a record is saved.

If you want conversion to happen before the record is saved, I would
use a specialized setter method. Supposing your column is called
‘cents’ and you want to be able to set the value of that column by
passing in Euros, do something like this in your model:

def price_in_euros=(euros)
self[:cents] = euros.nil? ? 0 : euros * 100
end

cheers,
Gerret

[1]
http://api.rubyonrails.com/classes/ActiveRecord/Callbacks.html#M000652

On 23/12/2005 2:50 PM, David B. wrote:

Hi,

I’m looking for a way to extend one of my models to allow some level
of abstraction between what goes into it and how it is stored.

But it doesn’t work in RoR. Is there something I’m missing? I have a
horrible feeling there should be a ‘self’ somewhere in there…

You’re talking about Facade Columns, where the data stored in the
database is in a different format to how you handle it in the
application. When you overwrite accessor methods, you need to use the
read_attribute and write_attribute methods to get the data from/put the
data to the database.

Your price example should be as simple as:

def price
read_attribute(‘price’) / 100.0
end

def price=(euros)
write_attribute(‘price’, euros * 100)
end

HTH

Dave,

(non-working) code. What I find confusing, is that for READING, just
using ‘cents’ works. (the ‘price’ method worked, but ‘price=’ did
not).

Is this an inconsistency in AR, or Ruby, or is there something I’m missing?

Internally AR keeps your attribute values in a Hash. When you write
self[:cents] = 10
you’re writing directly to the attributes Hash. If instead you write
cents = 10
you’re initializing a local variables with value 10.

As for reading attributes, AR gives you a shortcut. When you just use
“cents” inside your model instance, and there is no variable or method
‘cents’ in your current scope, then ActiveRecord::Base will intercept
your call, and figure out that you’re trying to call a method that has
the same name as one of the attributes. AR will then be nice enough
to return you the attribute in question.

cheers
gerret

Gerret A. wrote:

Internally AR keeps your attribute values in a Hash. When you write
self[:cents] = 10
you’re writing directly to the attributes Hash. If instead you write
cents = 10
you’re initializing a local variables with value 10.

What I tried to suggest with my too-cute post (sorry, Christmasy mood)
was that David write

self.cents = ...

which is the same as using self[:cents].

That’s one of the major traps of Ruby: calls to setter methods
inside its class require a self receiver, otherwise the variable
is interpreted as a local. I don’t fully understand why it has
to be this way.


We develop, watch us RoR, in numbers too big to ignore.

Thanks Gerret, that makes sense.

Dave

On 12/23/05, Gerret A. [email protected] wrote:

you’re writing directly to the attributes Hash. If instead you write
cheers
gerret


Rails mailing list
[email protected]
http://lists.rubyonrails.org/mailman/listinfo/rails


Site: http://antidis.com/

Gerret A. wrote:

[1] http://www.rubycentral.com/book/tut_expressions.html
Thanks Gerret.

Here’s a recent thread from comp.lang.ruby discussing this, with
contributions from Mats:

http://groups.google.com/group/comp.lang.ruby/browse_frm/thread/b0b54ca108fb589d

And another discussion:

http://groups.google.com/group/comp.lang.ruby/browse_frm/thread/6b4e7c7785f9c71


We develop, watch us RoR, in numbers too big to ignore.

I’m sorry, I don’t understand.

I’m quite new to both Rails and Ruby. If this is a scoping problem, I
don’t know exactly why and I don’t know how to fix it.

Dave

On 12/23/05, Mark Reginald J. [email protected] wrote:

We develop, watch us RoR, in numbers too big to ignore.


Rails mailing list
[email protected]
http://lists.rubyonrails.org/mailman/listinfo/rails


Site: http://antidis.com/

Mark,

That’s one of the major traps of Ruby: calls to setter methods
inside its class require a self receiver, otherwise the variable
is interpreted as a local. I don’t fully understand why it has
to be this way.

I didn’t fully understand that myself. Looking about for the answer I
found [1]; here’s the relevant portion:

"Sidebar: Using Accessors Within a Class

Why did we write self.leftChannel in the example on page 74? Well,
there’s a hidden gotcha with writable attributes. Normally, methods
within a class can invoke other methods in the same class and its
superclasses in functional form (that is, with an implicit receiver of
self). However, this doesn’t work with attribute writers. Ruby sees
the assignment and decides that the name on the left must be a local
variable, not a method call to an attribute writer."

[1] http://www.rubycentral.com/book/tut_expressions.html

cheers
Gerret

Thanks guys, both of you; this is fantastic. No more messy code in the
controller! :slight_smile:

Gerret, in your example code, you access cents through self[:cents].
That seems to be the major difference between your code and my
(non-working) code. What I find confusing, is that for READING, just
using ‘cents’ works. (the ‘price’ method worked, but ‘price=’ did
not).

Is this an inconsistency in AR, or Ruby, or is there something I’m
missing?

Thanks again,
Dave

On 12/23/05, Paul B. [email protected] wrote:

end
http://lists.rubyonrails.org/mailman/listinfo/rails


Site: http://antidis.com/