How make AR attributes custom classes?

In an ActiveRecord model, I want to cast some attributes as specific
custom classes. Example, if I store packed numbers for something like
a phone number, I want them cast as PhoneNumber objects within
ActiveRecord. My PhoneNumber class has various formatters and
extractors and validators. This same idea might apply to SSN, and
even serial numbers, product model numbers etc where various ranges
of characters within the stored string have significant meaning.

I’m reading through ActiveRecord RDocs, and it sounds like I might
want to simply override the default accessors, but that looks like
I’d have to recast the raw value every time with read_attribute. This
would be OK if I was doing a simple conversion of a unit or something
like the RDocs make example of, but these classes are more complex,
and I’d prefer the casting take place one time and remain in effect.

Another alternative is to create a new attribute which is the type
cast version of the raw attribute. So if I have mainPhone as a table
column and native attribute via reflection, I could have main_phone
(or whatever variant) be created as the PhoneNumber class and use the
attr= method to update it and also to drive the update down to
mainPhone. This is the approach I understand the most readily, but I
suspect there’s a better Rails way.

Looking for some idiomatic wisdom…

Much thanks (yet again)

– gw (www.railsdev.ws)

On 20 Nov 2007, at 09:37, Greg W. wrote:

In an ActiveRecord model, I want to cast some attributes as specific
custom classes. Example, if I store packed numbers for something like
a phone number, I want them cast as PhoneNumber objects within
ActiveRecord. My PhoneNumber class has various formatters and
extractors and validators. This same idea might apply to SSN, and
even serial numbers, product model numbers etc where various ranges
of characters within the stored string have significant meaning.

It sounds like you want composed_of

class Foo < AR::Base
composed_of :bar, :class_name => ‘PhoneNumber’, :mapping => %w(bar
serialize)
end

if f is an instance of foo and you do f.bar, then you’ll essentially
get back the result of
PhoneNumber.new(attributes_before_typecast[‘bar’]).

When you save the record, AR will stick the result of
phone_number.serialize in the bar column (composed_of also lets you
have multiple columns mapping to a single object).

An important restriction is that the value objects used must be
immutable, so foo.bar.change_some_attribute is naughty, but foo.bar =
some_instance_of_phone_number is ok

Fred

On Nov 20, 2007, at 1:46 AM, Frederick C. wrote:

It sounds like you want composed_of

class Foo < AR::Base
composed_of :bar, :class_name => ‘PhoneNumber’, :mapping => %w(bar
serialize)
end

if f is an instance of foo and you do f.bar, then you’ll essentially
get back the result of
PhoneNumber.new(attributes_before_typecast[‘bar’]).

Yep, looks promising.

When you save the record, AR will stick the result of
phone_number.serialize in the bar column (composed_of also lets you
have multiple columns mapping to a single object).

I don’t want a serialized version of the object saved–just the plain
string it really is behind the mask of the PhoneNumber object. Can I
impose this preference?

Looks like you’re running help desk duty today… thanks.

– gw (www.railsdev.ws)

On Nov 20, 2007, at 2:27 AM, Frederick C. wrote:

serialize was a bad example - if you set the mapping to %w(bar banana)
then it will store phone_number.banana in the bar column - it can be
anything you want

Ah :slight_smile:

Yep, got it all working. Sweet. Thanks.

– gw (www.railsdev.ws)

On Nov 20, 2007, at 2:52 AM, Greg W. wrote:

impose this preference?

serialize was a bad example - if you set the mapping to %w(bar
banana)
then it will store phone_number.banana in the bar column - it can be
anything you want

Yep, got it all working. Sweet. Thanks.

Well, not “all.” A follow up on this…

The composed_of is working relative to reading the object, but I am
having trouble getting data from a form back into the data table via
the composed of object.

class UserProfile < ActiveRecord::Base

self.primary_key = “rcrd_id”

belongs_to :privileged_user,
:foreign_key => “user_id”

composed_of :userPhone,
:class_name=>‘PhoneNumber’,
:mapping => [‘userPhone’, ‘raw’]

The form itself has fields only intended for the UserProfile table
(none for privileged_user), and the normal inputs are being saved
just fine through a process in the controller like this

@thisUser.profile.attributes = (params[:thisProfile])
@thisUser.profile.save

The only thing not being updated is my userPhone object.

For the moment I am using a callback of before_validation which
ultimately may not be th right one, but that’s what I am using for
now to just figure it out. Logging confirms it is being called, etc.

I know the composed_of objects have to be updated a certain way,
which I though was as follows:

newPhone = PhoneNumber.new(params[:userPhone])
self.userPhone = newPhone

That first line is a valid way to instantiate my PhoneNumber class.
It is being passed a hash created by using form input names of
userPhone[areacode] and userPhone[prefix] etc (the four parts of a
phone number including extension).

It appears though I am not actually updating the model attribute or
something. That mapping to ‘raw’ is a valid method which returns just
the fully concatenated string (no formatting), and that’s verified to
be working as expected.

I’ve tried a number of different approaches, but I’m failing to see
how these pieces fit together.

– gw (www.railsdev.ws)

@thisUser.profile.save
newPhone = PhoneNumber.new(params[:userPhone])
be working as expected.

I’ve tried a number of different approaches, but I’m failing to see
how these pieces fit together.

I finally resorted to using this in the controller

@thisUser.profile.attributes = (params[:thisProfile])
@thisUser.profile.updateCompositions(params)
@thisUser.profile.save

and creating an updateCompositions method in UserProfile

def updateCompositions(formData)
newPhone = PhoneNumber.new(formData[:userPhone])
self.userPhone = newPhone
end

I’d bet bytes to bits that’s not the Railsy way to do it, but it
works – although all I effectively accomplished was passing params
into UserProfile. It has to be available to it natively or I don’t
see how the other non composed_of field updates would be working.

Hrmmm. Is crear as mud.

– gw (www.railsdev.ws)

I know the composed_of objects have to be updated a certain way,
which I though was as follows:

  newPhone = PhoneNumber.new(params[:userPhone])
  self.userPhone = newPhone

Maybe you can add this in your model

def userPhone= new_phone
self.userPhone = PhoneNumber.new new_phone
end

Regards,

Rafael Mueller

On 20 Nov 2007, at 10:05, Greg W. wrote:

When you save the record, AR will stick the result of
phone_number.serialize in the bar column (composed_of also lets you
have multiple columns mapping to a single object).

I don’t want a serialized version of the object saved–just the plain
string it really is behind the mask of the PhoneNumber object. Can I
impose this preference?

serialize was a bad exam[;e - if you set the mapping to %w(bar banana)
then it will store phone_number.banana in the bar column - it can be
anything you want

Fred

Actually, in your view you can have a “phone” field, and in your model
the
method

def phone= new_phone
self.userPhonePhoneNumber.new new_phone
end

Regards,

Rafael Mueller

We need an ACL system that covers not only controllers and actions, but
model objects as well.

We’ve started to implement ActiveAcl:

http://activeacl.rubyforge.org/

but are concerned that the project has had no activity in eons, and is
overly complex.

We just discovered the Authorization Plugin, which seems simpler and has
a more active community:

http://www.writertopia.com/developers/authorization

Can anyone validate that one is stabler and/or more feature complete
than the other?

Our use cases are in line with the standard “message forum” use cases,
e.g.:

Message Forum “F1”:

  • anyone can read or post
  • anyone with role “M1” can moderate

Message Forum “F2”

  • no one can read or post, unless they have role “U2”
  • anyone with role “M2” can moderate

etc etc.

Thank you,

Bryan

On Tue, 20 Nov 2007 17:35:39 -0800, Greg W. wrote:

I’ve tried a number of different approaches, but I’m failing to see
how these pieces fit together.

I’m not sure, but I think the answer is somewhere between “they don’t”
and “not very well”.

In general, Rails doesn’t abstract the idea of “column type” very much -
a
lot of it is magic in the database adapters, which know a great deal
about
the native types each database can store. But last time I played with
it,
“composed of” just didn’t present a nice way to create a new data type,
define its serialization and display formats, validations, etc.

Part of it may just be the inherent “impedance mapping”, but I like to
think that some smart people could figure out a better way that would
make
value objects easier to work with in Rails. There was someone on the
Rails-core list that mentioned a "column object a while ago; that may be
your ideal answer.


Jay L. |
Boston, MA | My character doesn’t like it when they
Faster: jay at jay dot fm | cry or shout or hit.
http://www.jay.fm | - Kristoffer