Forum: Ruby on Rails Delaying initialization of associations until first access

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
John W. (Guest)
on 2006-02-06 22:24
(Received via mailing list)
Guys,

Say I have the following:
-----
class Person < ActiveRecord::Base
  has_one :house
end

class House < ActiveRecord::Base
  belongs_to :person
  attr_accessor :color
end
-----
Then I have the following code:
-----
john = Person.new
john.house.color = "Blue"
john.save
-----
What I would like to have happen, is that on first call to john.house,
if
house hadn't been initialized a House object would be created and
assigned
to the house field.

I've struggled to get this to work. Some have suggested overriding
Person#save to make sure a House record was created everytime I created
a
Person record, but I really, really don't want to do this. If I have
100000 Person objects, and only 20% of them own a house, all those empty
House records in the database is a lot of waste.

I've tried variations on the below code, each of which has failed to
work.
It *seemed* to work, but it doesn't persist to the database properly:

class Person < ActiveRecord::Base
  has_one :house

  def house
    if !read_attribute(:house)
      h = House.new
      h.save
      write_attribute(:house, h)
    end
    read_attribute(:house)
  end
end

Can anyone tell me the proper way to accomplish this behavior?

Thanks!
John
Nick S. (Guest)
on 2006-02-06 22:33
(Received via mailing list)
What exactly doesn't get saved properly? From the looks of it you are
not setting the house's person property, which leads me to think that
the person_id isn't getting set in the database? Yes? No?

So, before your h.save call, just add a h.person = self. Or you could
just create and save all in one step. Your total method would be:
def house
   return House.create(:person_id => self.id) if !read_attribute(:house)
   read_attribute(:house)
end

Just have the create call, no need to write the attribute or assign it
anything else, everything should work just fine like that.

I think this will work, but its untested  ;)

-Nick
John W. (Guest)
on 2006-02-06 23:25
(Received via mailing list)
Nick S. said:
> So, before your h.save call, just add a h.person = self. Or you could
> just create and save all in one step. Your total method would be:
> def house
>    return House.create(:person_id => self.id) if !read_attribute(:house)
>    read_attribute(:house)
> end

Yes, I've tried this in two forms:

def house
 if !read_attribute(:house)
   h = House.new
   h.person = self
# second form tried was
# h.person_id = self.id
   h.save
   write_attribute(:house, h)
 end
 read_attribute(:house)
end

Both "seem" to work, and the House record is created. However, if I then
do:

someperson.house.color = "Red"
someperson.save

I look in my logs, and the person record is being updated but the house
record is not. Then, if I reload the person:

>> someperson = Person.find(1)
>> someperson.house.color
=> nil

Examining the logs, it seems that in  "if !read_attribute(:house)",
read_attribute(:house) never tests to true, *even* if the record already
exists. This is counter intuitive and bewildering.

Any ideas what I may be doing wrong?

Thanks!

John
John W. (Guest)
on 2006-02-06 23:29
(Received via mailing list)
Nick S. said:
> So, before your h.save call, just add a h.person = self. Or you could
> just create and save all in one step. Your total method would be:
> def house
>    return House.create(:person_id => self.id) if !read_attribute(:house)
>    read_attribute(:house)
> end

Yes, I've tried this in two forms:

def house
 if !read_attribute(:house)
   h = House.new
   h.person = self
# second form tried was
# h.person_id = self.id
   h.save
   write_attribute(:house, h)
 end
 read_attribute(:house)
end

Both "seem" to work, and the House record is created. However, if I then
do:

someperson.house.color = "Red"
someperson.save

I look in my logs, and the person record is being updated but the house
record is not. Then, if I reload the person:

>> someperson = Person.find(1)
>> someperson.house.color
=> nil

Examining the logs, it seems that in  "if !read_attribute(:house)",
read_attribute(:house) never tests to true, *even* if the record already
exists. This is counter intuitive and bewildering.

Any ideas what I may be doing wrong?

Thanks!

John
Nick S. (Guest)
on 2006-02-07 03:47
(Received via mailing list)
On 2/6/06, John W. <removed_email_address@domain.invalid> wrote:
> def house
>
> Both "seem" to work, and the House record is created. However, if I then do:
>
> someperson.house.color = "Red"
> someperson.save

You are going to need to save your house model seperatly from your
person model. But! If you always want to do this, through in a
before_save filter for the person class that automatically saves the
house model at the same time. Should be simple enough.

> exists. This is counter intuitive and bewildering.
For this, try using `attribute_present?` instead. Seems to be built
for what you are trying to do. Haven't used it personally so not sure.
Just dug it out of the API.

Hope this helps!
-Nick
John W. (Guest)
on 2006-02-07 16:41
(Received via mailing list)
On Monday, February 06, 2006, at 8:44 PM, Nick S. wrote:
>You are going to need to save your house model seperatly from your
>person model. But! If you always want to do this, through in a
>before_save filter for the person class that automatically saves the
>house model at the same time. Should be simple enough.

This part works...when I set the before_save filter, the House is indeed
saved to the database. However:

>For this, try using `attribute_present?` instead. Seems to be built
>for what you are trying to do. Haven't used it personally so not sure.
>Just dug it out of the API.

Switched it to attribute_present?...same behavior. Isn't surprising
since attribute_present? uses read_attribute under the covers. So, while
we're successful in writing to the database, when you reload the Person
object ActiveRecord for some reason doesn't see the connection and
attribute_present? resolves false, therefore creating another entry in
the table.

I am at a complete loss.

The code now looks like this:

def before_save
    self.house.save
end

def part_count
  if !attribute_present?("house")
    h = House.new
    h.color = "blue"
    h.person = self
    write_attribute("house", h)
  end
  read_attribute("house")
end

Any other thoughts?
John W. (Guest)
on 2006-02-07 16:41
(Received via mailing list)
On Monday, February 06, 2006, at 8:44 PM, Nick S. wrote:
>You are going to need to save your house model seperatly from your
>person model. But! If you always want to do this, through in a
>before_save filter for the person class that automatically saves the
>house model at the same time. Should be simple enough.

This part works...when I set the before_save filter, the House is indeed
saved to the database. However:

>For this, try using `attribute_present?` instead. Seems to be built
>for what you are trying to do. Haven't used it personally so not sure.
>Just dug it out of the API.

Switched it to attribute_present?...same behavior. Isn't surprising
since attribute_present? uses read_attribute under the covers. So, while
we're successful in writing to the database, when you reload the Person
object ActiveRecord for some reason doesn't see the connection and
attribute_present? resolves false, therefore creating another entry in
the table.

I am at a complete loss.

The code now looks like this:

def before_save
    self.house.save
end

def part_count
  if !attribute_present?("house")
    h = House.new
    h.color = "blue"
    h.person = self
    write_attribute("house", h)
  end
  read_attribute("house")
end

Any other thoughts?
John W. (Guest)
on 2006-02-07 16:44
(Received via mailing list)
Update...I have it working!

I'm not sure why this works and our previous attempts didn't (would love
the explanation).  greendale at rails.technoweenie.net provided this
code, which worked:

alias :_house :house

def house
  _house or self.house = House.new
end

Now, that's simple. I can't understand when this worked and
read_attribute didn't?
This topic is locked and can not be replied to.