Associations question

Hello,

I have a People table and a Addresses table. A person can have one or
more addresses, but should at least have one, so there is a address_id
field in People.

Now, I would like to have a form to fill the name of a new person and
its address from the same place. I could use person.address.country, for
example (it works), but I would like to simply use person.country for
some reasons (I have a generic controller and generic views which can
make this kind of forms automatically if I give a list of fields, but it
has to be directly in the model).

I could add the “country” field to the model, but I have a lot of
fields I would like to “import”.

I tried to use the :include option, but mysql complains because I have
a field which has the same name in both People and Addresses table (and
it should stay this way).

I tried to generate model methods “on the fly” with define_method, but
I’ve not been able to do it, as I couldn’t write generic code to call
the corresponding method of another model. This is because I can’t know
the name of the method I’m currently in. Hope this is somewhat clear :wink:

So, my question is: how should I do that without writing a lot of
redundant code?

Thank you,

Yannick M. http://www.inma.ucl.ac.be/~majoros
Informaticien UCL/INMA-MEMA
4, avenue G. Lemaître
B-1348 Louvain-la-Neuve
Tel: +32-10-47.80.10
Fax: +32-10-47.21.80

I’m fairly new to rails, so don’t take my advice. But here it is anyway.

Part of the rails ethos (IMO) is built around enforcing things like
foreign key constraints in the app, and not in the database. Putting
an address_id in the people table seems as though it is intended to
enforce a rule that should be enforced in the application as well. If
the relationship is one person to many addresses, then the data model
should be exactly that - a person_id in the addresses table. You can
have an “is_primary_address” column in the addresses table, or
something along those lines, but the enforcement that there is at
least one address should be done in the application. If you were to
take that approach, the redundancy in the db would be gone and so
should the redundancy in the app.

As for how to handle that in the app, I think you’d be able to do
something like this:

<%= text_field ‘person’, ‘first_name’ %>
… (more person fields) …
<%= text_field, ‘address’, ‘street’ %>
… (more address fields) …

#People controller
def create
@person = Person.new(params[:person])
@person.primary_address = Address.new(params[:address])
if @person.save
… (error handling and redirect) …
end

Then have the Person model take care of setting itself as the
Address’ person_id and make the Address primary in the save method.
This allows you to wrap the saves in a transaction, it’s all very
testable at the model level, and it has none of the redundancy you’re
interested in avoiding.

Hope this helps.
David

ps - if any of you more experienced railers think this is totally out
of whack, please pipe up. I’m coming from the TDD java world and
trying to figure out how to swim in this sparkly, new ocean.

If a person always has at least one address and you feel that, for
example, country is really an attribute of person for whatever reason,
you could consider adding the address fields to the person table.

From what you say about your controller working by default with a single
address per person, you presumably handle addresses after the first in
different controller and view code. So, consider a “person” table with
one set of address fields and a “further addresses” table. If your main
address per person is always treated differently from the further
addresses, this would work. From what you say about your controller,
this would seem to be the case.

This is not a normalized database design and some would consider it
heresy. However, it depends on what your design represents in the real
world.

However, I would always, by preference, choose the simple design where
tables are normalized and independent. Even if there seems to be a good
reason to go away from this now, you may regret it later.

You could, of course, modify your generic controller to handle a
relationship like this generically.

Julian

David C. wrote:

least one address should be done in the application. If you were to
take that approach, the redundancy in the db would be gone and so
should the redundancy in the app.

Hello,

Thank you for your quick answer.

I don’t think there is a design problem in my db (others please correct
me if you don’t think so). There is no redundancy in it, and I don’t
want to enforce there is at least one address for each person in my db.
Actually, address_id can be null. If it isn’t, then it has to be valid,
naturally.

I’ll try to put it another way. For some reason, I want to have a
“country” method in my “person” model. It should look this up in
Address, so I could just do this:

class Person < ActiveRecord::Base
has_one :address
def country
self.address.country if self.address
end
end

This does what I want, but I’d like more. Besides “country”, there are
a lot of other methods from Address I’d like to make available in
Person. I’d like to write something to do it automatically. I tried
this:

for col in Address.content_columns
define method( col.name )
self.Address.send col.name
end
end

But this doesn’t work fully neither, because it is only executed when
it is called, so col.name in the body of the function is not set to the
right column.

I tried to do it by overriding ActiveRecord::find in my model to have
:include = :address, but mysql does complain because I have some
identical field names in People and Addresses tables (say,
last_update_timestamp ), which makes the JOIN query ambigous.

As I write this, I’m thinking of another option: I could write de sql
query by hand… But I don’t like this solution for other reasons.

What could I do?

Thank you,

Yannick M. http://www.inma.ucl.ac.be/~majoros
Informaticien UCL/INMA-MEMA
4, avenue G. Lemaître
B-1348 Louvain-la-Neuve
Tel: +32-10-47.80.10
Fax: +32-10-47.21.80

On Jul 5, 2006, at 7:12 AM, Yannick M. wrote:

person_id in the addresses table. You can have an
I don’t think there is a design problem in my db (others please
correct me if you don’t think so). There is no redundancy in it,
and I don’t want to enforce there is at least one address for each
person in my db. Actually, address_id can be null. If it isn’t,
then it has to be valid, naturally.

I was responding to this (from your initial email): “A person can
have one or more addresses, but should at least have one”

Even if it’s really 0…n, this is telling me that the foreign key
should be in the address table.

How do you associate the other addresses to the person? If there is a
person_id in the addresses table in addition to the address_id in the
people table, that may not look redundant, but to me it is. These
relationships should only go one way or the other, unless there is a
many to many relationship, in which case that should be expressed in
a separate table.

are a lot of other methods from Address I’d like to make available
in Person. I’d like to write something to do it automatically. I
tried this:

for col in Address.content_columns
define method( col.name )
self.Address.send col.name
end
end

How about this?

for col in Address.content_columns
name = col.name
define_method(name) { return address.send(name) }
end

relationships should only go one way or the other, unless there is a
many to many relationship, in which case that should be expressed in a
separate table.

Ok, there is a person_id in address table too. I could do it like you
say, but this won’t help me anyway: I still want a “country” method for
Person, which would end up doing some lookup in Address. How can I
achieve that?

Yannick M. http://www.inma.ucl.ac.be/~majoros
Informaticien UCL/INMA-MEMA
4, avenue G. Lemaître
B-1348 Louvain-la-Neuve
Tel: +32-10-47.80.10
Fax: +32-10-47.21.80

Mon calendrier en ligne : http://www.inma.ucl.ac.be/~majoros/calendar
Accents bizarres ? http://www.inma.ucl.ac.be/~majoros/email.html

Yannick M. wrote:

many to many relationship, in which case that should be expressed in
a separate table.
Ok, there is a person_id in address table too. I could do it like you
say, but this won’t help me anyway: I still want a “country” method
for Person, which would end up doing some lookup in Address. How can I
achieve that?
A couple of emails back you suggested this:

class Person < ActiveRecord::Base
has_one :address
def country
self.address.country if self.address
end
end

If you remove address_id from people, you could do this

class Person < ActiveRecord::Base
has_many :addresses
def country
self.addresses[0].country unless addresses[0].empty?
end
end

As for doing it dynamically, I’m not sure what the best approach would
be. Did you try my suggestion from the last email?

for col in Address.content_columns
name = col.name
define_method(name) { return address.send(name) }
end

If that works, then this should work:

for col in Address.content_columns
name = col.name
define_method(name) { return addresses[0].send(name) } unless
addresses[0].empty?
end

I don’t really have any other ideas. Anyone else care to weigh in?

Julian G. wrote:

this would seem to be the case.

It is not really treated differently. I should be able to list the main
address and the other ones without any difference, in another context.

This is not a normalized database design and some would consider it
heresy. However, it depends on what your design represents in the real
world.

Mmm, I don’t really agree; what I told the list is actually a
simplified version of the situation, which is rather complicated.
Actually, I have different models which are derived from Person, and one
of them would need to have the country field calculated from Address.
This is because, for some people using some specific controller, they
have to be able to browse a list of “special” people and to add one,
together with an address, in one operation. My generic controller and
views make it simple to generate a form for all “columns” of a model. I
don’t think it would be good to make it handle the relations, because
I’d need to have structures to describe these very special cases. And
actually, I think the approach of having a different model is good. I
could be wrong, but this application begins to grow, and I need to keep
things as simple as possible (I have 20+ tables which are listed and
edited in similar ways, with occasionnaly some customization like this).
I think this is the best I can do at this point, but it seems I just
can’t write the code to generate these methods :frowning:

However, I would always, by preference, choose the simple design where
tables are normalized and independent.

They are :wink:

Even if there seems to be a good
reason to go away from this now, you may regret it later.

You could, of course, modify your generic controller to handle a
relationship like this generically.

Don’t think so, see above :frowning:

I’m a bit lost…

Yannick M. http://www.inma.ucl.ac.be/~majoros
Informaticien UCL/INMA-MEMA
4, avenue G. Lemaître
B-1348 Louvain-la-Neuve
Tel: +32-10-47.80.10
Fax: +32-10-47.21.80

Daniel N wrote:

Example delegator setup.

class Person < ActiveRecord::Base
has_one :address

def method_missing( m, *args, &block )
self.address.send(m, *args, &block )
end
end

Thank you very much, I think that’s what I’ll do :wink:

Yannick M. http://www.inma.ucl.ac.be/~majoros
Informaticien UCL/INMA-MEMA
4, avenue G. Lemaître
B-1348 Louvain-la-Neuve
Tel: +32-10-47.80.10
Fax: +32-10-47.21.80

On 7/5/06, Yannick M. [email protected] wrote:

have an “is_primary_address” column in the addresses table, or
me if you don’t think so). There is no redundancy in it, and I don’t
def country
self.Address.send col.name
last_update_timestamp ), which makes the JOIN query ambigous.
I think it would complain even in model space. If you have a field in
address that is the same as that in Person, the persons method will be
overwritten with what you’ve suggested above.

What you could try that may / my not be what you after is to delegate
missing methods to the address object associated with the person.

From David Blacks awsome Ruby For Rails book www.manning.com/black he talks
about setting up delegators. In this case, if a method is missing from
Person, delegate whatever method call to the persons address object. so
person.country would, provided that there is no method country defined
in
your person model, provide you with whatever self.address.country would
return. I’m not too sure what the ramifications for you will be. If
anyone
else out there would like to take a guess about whether or not this is a
good idea or not, please…

Example delegator setup.

class Person < ActiveRecord::Base
has_one :address

def method_missing( m, *args, &block )
self.address.send(m, *args, &block )
end
end

As I write this, I’m thinking of another option: I could write de sql

David C. wrote:

end
Thank you very much, I think that’s what I’ll do :wink:
Yannick, would you please report back to the list and let us know if
this works for you and, if not, why not?

Ok, it’s working great. :wink: Thank you very much!

Just one more thing to make it perfect: how could I check if a method
is present in my other model? I tried with
self.address.methods.includes?, but I’ve got really weird results, so I
used rescue instead…

Yannick M. http://www.inma.ucl.ac.be/~majoros
Informaticien UCL/INMA-MEMA
4, avenue G. Lemaître
B-1348 Louvain-la-Neuve
Tel: +32-10-47.80.10
Fax: +32-10-47.21.80

Yannick M. wrote:

Thank you very much, I think that’s what I’ll do :wink:
Yannick, would you please report back to the list and let us know if
this works for you and, if not, why not?

Thanks,
David

On Jul 6, 2006, at 4:49 AM, Yannick M. wrote:

end
end
Thank you very much, I think that’s what I’ll do :wink:
Yannick, would you please report back to the list and let us know
if this works for you and, if not, why not?

Ok, it’s working great. :wink: Thank you very much!

Cool. And thanks to Daniel for the suggestion (and the Black book
recommendation).

Just one more thing to make it perfect: how could I check if a
method is present in my other model? I tried with
self.address.methods.includes?, but I’ve got really weird results,
so I used rescue instead…

try self.address.methods.include? (no s)

David C. wrote:

 self.address.send(m, *args, &block )

Just one more thing to make it perfect: how could I check if a method
is present in my other model? I tried with
self.address.methods.includes?, but I’ve got really weird results, so
I used rescue instead…

try self.address.methods.include? (no s)

Ok, that was a type, that’s actually what I did.

The problem is that, sometimes, when executing method_missing,
self.address.includes?( “Country” ) returns nil. The strangest part is
naturally that it doesn’t happen all the time.

Yannick M. http://www.inma.ucl.ac.be/~majoros
Informaticien UCL/INMA-MEMA
4, avenue G. Lemaître
B-1348 Louvain-la-Neuve
Tel: +32-10-47.80.10
Fax: +32-10-47.21.80

On 7/6/06, Tom W. [email protected] wrote:

try self.address.methods.include? (no s)

The problem is that, sometimes, when executing method_missing,
self.address.includes?( “Country” ) returns nil. The strangest part is
naturally that it doesn’t happen all the time.

ActiveRecord itself uses method missing to provide access to
attributes, so address.country may not be an actual method.

Instead of self.address.methods.include? you should use self.address.respond_to?

i.e

self.address.respond_to ‘country’

On 7/6/06, Tom W. [email protected] wrote:

i.e

self.address.respond_to ‘country’

Third time lucky:

self.address.respond_to? ‘country’

Tom

On 7/6/06, Yannick M. [email protected] wrote:

David C. wrote:

On Jul 6, 2006, at 4:49 AM, Yannick M. wrote:

David C. wrote:

Just one more thing to make it perfect: how could I check if a method
is present in my other model? I tried with
self.address.methods.includes?, but I’ve got really weird results, so
I used rescue instead…

try self.address.methods.include? (no s)

The problem is that, sometimes, when executing method_missing,
self.address.includes?( “Country” ) returns nil. The strangest part is
naturally that it doesn’t happen all the time.

ActiveRecord itself uses method missing to provide access to
attributes, so address.country may not be an actual method.

Instead of self.address.methods.include? you should use
self.address.respond_to?

Tom

On Jul 6, 2006, at 6:47 AM, Tom W. wrote:

self.address.respond_to?
Yannick - what are you doing if self.address.respond_to? ‘country’
returns false? Just curious.

David C. wrote:

Instead of self.address.methods.include? you should use
self.address.respond_to?

Again, the situation is a bit more complex than what I describe.

I could try to give more information by describing a situation which is
closer to what I do.

Let’s say I have an model whose name is “Action”. It’s not really the
best example, but it’s the best I could think of. Describing the whole
db would take a lot of time.

Action has one Person, and Person has one Address (this is not really
the case, but…).

For some reason, I need to access my_action.Country instead of
my_action.person.address.Country to have the country of the person who
is performing the action.

So, I need to check if Country is a method of Person, and if it isn’t,
see if it is a method of Country.

Note that by using method_missing, an interesting side-effect is that
Action.Country= also works, which I needed anyway :wink:

Yannick M. http://www.inma.ucl.ac.be/~majoros
Informaticien UCL/INMA-MEMA
4, avenue G. Lemaître
B-1348 Louvain-la-Neuve
Tel: +32-10-47.80.10
Fax: +32-10-47.21.80

Yannick M. wrote:

the case, but…).

For some reason, I need to access my_action.Country instead of
my_action.person.address.Country to have the country of the person who
is performing the action.

So, I need to check if Country is a method of Person, and if it isn’t,
see if it is a method of Country.

Note that by using method_missing, an interesting side-effect is that
Action.Country= also works, which I needed anyway :wink:
Cool. Thanks for taking the time.

Cheers,
David

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs