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)

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

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']).

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)

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

Ah :slight_smile:

Yep, got it all working. Sweet. Thanks.

-- gw (www.railsdev.ws)

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)

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.

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,

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,

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

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.