In my Contact object, I’ve created an initialize method:
def initialize(attributes = nil)
super(attributes)
self.create_contact_preference unless self.contact_preference
end
The purpose of this initialize method is to add a contact_preference
member to the contact if they don’t have one already in the database
(which none of the legacy records do).
However, it is clear that my initialize method is not being called due
to the find() invocation.
Does find() call a different constructor on an AR object? If so, which
one?
Am I creating my contact_preference member incorrectly?
Well, I think I’ve verified the behavior that I’m seeing based on the
code below and the use of allocate, instead of new. As we can see,
find() does not call initialize at all.
What I was hoping to be able to do here was to have the Contact object
be smart enough to generate it’s contact_preference member when
necessary. I don’t think that this is an unreasonable expectation for
an object.
I guess I’ve been thinking of Ruby’s new method as the equivalent to new
in Java, but in fact, Ruby’s allocate is equivalent to Java’s new.
This is slightly confusing.
Does anyone know why AR::Base uses allocate instead of new? I’m sure
that there’s a good reason.
I see that there is an “after_find()” callback available to me. Is this
what I need?
def instantiate(record)
object =
if subclass_name = record[inheritance_column]
if subclass_name.empty?
allocate
else
require_association_class(subclass_name)
begin
compute_type(subclass_name).allocate
rescue NameError
raise SubclassNotFound,
"The single-table inheritance mechanism failed to locate the
subclass: ‘#{record[inheritance_column]}’. " +
"This error is raised because the column
‘#{inheritance_column}’ is reserved for storing the class in case of
inheritance. " +
"Please rename this column if you didn’t intend it to be
used for storing the inheritance class " +
“or overwrite #{self.to_s}.inheritance_column to use another
column for that information.”
end
end
else
allocate
end
object.instance_variable_set("@attributes", record)
object
end
initialize is called with Contact.new, Contact.find constructs object
using
Contact.instantiate. You can probably do this:
class Contact
class << self
def instantiate_with_contact_preference(record)
object = instantiate_without_contact_preference(record)
# Do whatever you want with the object here
object
end
I’m still kind of annoyed at the difference in behavior.
I’m trying to figure out why finders couldn’t/shouldn’t call initialize
methods.
What’s the difference in me applying my object customizations to my
object in after_find vs. initialize?
If the finders called initialize, then the instantiation model for
AR::Base descendants would be consistent.
As it stands now, I have AR::Base descendants that do “create from
scratch” initialization in the initialize method and other AR::Base
descendants that do “create from persisted DB data” initialization in
the “after_find” method.
For someone new to Ruby as I am, who is already comfortable with OO,
it’s weird to have to do object initialization a special way just for
retrieved DB objects.
Just a guess here. When you find() a record, you aren’t creating a
brand new object, but are reconstructing an old object from a persistent
representation of it stored in the database. So AR::initialize isn’t
useful since it can’t tell whether it would be for a new object or a
reconstructed one. Therefore you use after_initialize or after_find to
init the required attributes.
Or maybe it’s something else. Like I said, I’m just guessing.
Just a guess here. When you find() a record, you aren’t creating a
brand new object, but are reconstructing an old object from a persistent
representation of it stored in the database. So AR::initialize isn’t
useful since it can’t tell whether it would be for a new object or a
reconstructed one. Therefore you use after_initialize or after_find to
init the required attributes.
Yes, but in reality, I am creating a new object in memory regardless of
whether I do it directly with new() or indirectly with find(). I should
be able to have an initialization hook that is consistent regardless of
instantiation method. If I’m doing stuff to my AR::Base descendant that
doesn’t even have anything to do with the database, than why can’t that
work be done the same way regardless of how the object is created?
Clearly the hooks are there, I’m just wondering why/complaining that
they aren’t the same. Just another detail that I have to master, and I
wanna know why :).