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/
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.
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 <mrj-bzGI/hKkdgQnC9Muvcwxkw@public.gmane.org> 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 > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- Site: http://antidis.com/
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 <david.barrett-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> 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 <mrj-bzGI/hKkdgQnC9Muvcwxkw@public.gmane.org> 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 > > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > > http://lists.rubyonrails.org/mailman/listinfo/rails > > > > > -- > Site: http://antidis.com/ > _______________________________________________ > Rails mailing list > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >
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
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 <paul-7GxnDbOnBiZAfugRpC6u6w@public.gmane.org> 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 > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > http://lists.rubyonrails.org/mailman/listinfo/rails >-- Site: http://antidis.com/
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 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.
Thanks Gerret, that makes sense. Dave On 12/23/05, Gerret Apelt <gerret.apelt-Re5JQEeQqe8AvxtiuMwx3w@public.gmane.org> 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 > Rails-1W37MKcQCpIf0INCOvqR/iCwEArCW2h5@public.gmane.org > 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
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.htmlThanks 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.