Extending model

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

Extending model

David Barrett
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 :( )
-  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 Barrett

--
Site: http://antidis.com/
_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Extending model

Mark Reginald James
David Barrett 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.

_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Re: Extending model

David Barrett
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 James <[hidden email]> wrote:

> David Barrett 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.
>
> _______________________________________________
> Rails mailing list
> [hidden email]
> http://lists.rubyonrails.org/mailman/listinfo/rails
>


--
Site: http://antidis.com/
_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Re: Extending model

Gerret Apelt
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 12/23/05, David Barrett <[hidden email]> wrote:

> 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 James <[hidden email]> wrote:
> > David Barrett 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.
> >
> > _______________________________________________
> > Rails mailing list
> > [hidden email]
> > http://lists.rubyonrails.org/mailman/listinfo/rails
> >
>
>
> --
> Site: http://antidis.com/
> _______________________________________________
> Rails mailing list
> [hidden email]
> http://lists.rubyonrails.org/mailman/listinfo/rails
>
_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Extending model

Paul Bernays
In reply to this post by David Barrett
On 23/12/2005 2:50 PM, David Barrett 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.
>
> <snip>
>
> 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
_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Extending model

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

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 Bernays <[hidden email]> wrote:

> On 23/12/2005 2:50 PM, David Barrett 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.
> >
> > <snip>
> >
> > 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
> _______________________________________________
> Rails mailing list
> [hidden email]
> http://lists.rubyonrails.org/mailman/listinfo/rails
>


--
Site: http://antidis.com/
_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Extending model

Gerret Apelt
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
_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Extending model

Mark Reginald James
Gerret Apelt 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.

_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Extending model

David Barrett
In reply to this post by Gerret Apelt
Thanks Gerret, that makes sense.

Dave

On 12/23/05, Gerret Apelt <[hidden email]> wrote:

> 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
> _______________________________________________
> Rails mailing list
> [hidden email]
> http://lists.rubyonrails.org/mailman/listinfo/rails
>


--
Site: http://antidis.com/
_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Re: Extending model

Gerret Apelt
In reply to this post by Mark Reginald James
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
_______________________________________________
Rails mailing list
[hidden email]
http://lists.rubyonrails.org/mailman/listinfo/rails
Reply | Threaded
Open this post in threaded view
|

Re: Extending model

Mark Reginald James
Gerret Apelt wrote:

> "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

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.

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