I’m trying to figure out an elegant way to do this:
I have the following three tables:
people, employer, employees
And consequently the following three models:
class Person < ActiveRecord::Base
end
class Employer < ActiveRecord::Base
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :person
belongs_to :employer
end
I want to be able to say: @employee.lastname, instead of saying @employee.person.lastname
Do I need to write individual method_missing’s for all of the person
attributes in Employee to forward the .lastname and .firstname calls
onto the :person association? Is there a smarter less brittle way?
I want to be able to say: @employee.lastname, instead of saying @employee.person.lastname
Do I need to write individual method_missing’s for all of the person
attributes in Employee to forward the .lastname and .firstname calls
onto the :person association? Is there a smarter less brittle way?
If you want a method, just write it:
class Employee < ActiveRecord::Base
…
def lastname
person.lastname
end
# etc.
end
Then you don’t have to worry about method_missing because it isn’t
missing
(I’m not clear on why an Employee belongs to a Person, but that’s a
different matter.)
I wanted to exploit missing_method in some way, so that I don’t have to
write a whole lot of extra methods like you described. Also, as my
Person model grows, I want all these attributes to be automagically
available.
The Employee model is basically the model to describe the relationship
between a person and an employer. In it I can record extra employee
specific data. I also have other types of people, like contacts etc. I
thought of not going the STI route, in case we want to store all the
people in an LDAP directory …
Plus, by having an employee model (separate from a person model), allows
me to create RESTful URLS more easily - ie I’m giving the person context
wrt to an employer within the URL structure.
Dunno - am I being to abstract about it all?
Joerg
unknown wrote:
Hi –
On Mon, 17 Jul 2006, Joerg D. wrote:
end
I want to be able to say: @employee.lastname, instead of saying @employee.person.lastname
Do I need to write individual method_missing’s for all of the person
attributes in Employee to forward the .lastname and .firstname calls
onto the :person association? Is there a smarter less brittle way?
If you want a method, just write it:
class Employee < ActiveRecord::Base
…
def lastname
person.lastname
end
# etc.
end
Then you don’t have to worry about method_missing because it isn’t
missing
(I’m not clear on why an Employee belongs to a Person, but that’s a
different matter.)
Is there a problem with using method missing in situations like this?
In your book, you show how to do it, does this mean that perhaps it
shouldn’t be done?
I do talk about it in the book, though as I re-read those pages I see
that I seem to be presenting it mostly in the light of something that
Rails uses a lot in its own source code. And I give only non-Rails
examples of how to use it. That might have reflected a subconscious
feeling that using it in AR model files is potentially problematic
I do think that if you’re systematically sending messages from one
model to another, it’s a strong sign that the domain model needs some
redesign. Having created a domain model, you shouldn’t need to
circumvent it, so to speak.
Now - what if I have contacts too. So an employer has employees and
contacts. Both are people. And both relate back to the employer. I could
use STI and store everything in a people table. But then a person can
only either be an employee or a contact - but not both. That is if I am
using the automagic column ‘type’. I suppose I could also skip the
‘type’ column and create isEmployee and isContact columns to handle this
but it kinda makes things a bit ugly.
The other alternative is to have an employees and a contacts table, and
both have the firstname lastname, all the address fields and tel numbers
etc duplicated. So there are two place in the app where a ‘person’ is
defined. Also I’ll end up with duplicate data, in the case where a
person can be both a contact and an employee of an employer.
Therefore my thinking was that both contacts and employees are people
(stored in the people table), and their relationship to the employer is
represented by the employees and the contacts table.
Anyway.
unknown wrote:
Hi –
On Mon, 17 Jul 2006, Joerg D. wrote:
I wanted to exploit missing_method in some way, so that I don’t have to
write a whole lot of extra methods like you described. Also, as my
Person model grows, I want all these attributes to be automagically
available.
To me it sounds like a sign that the domain model isn’t right. The
fact that Employee needs the same behavior as Person suggests a
subclassing relationship.
The Employee model is basically the model to describe the relationship
between a person and an employer. In it I can record extra employee
specific data. I also have other types of people, like contacts etc. I
thought of not going the STI route, in case we want to store all the
people in an LDAP directory …
The word “employee” definitely means a person, not a relationship
between a person and a company. If you want to model the
relationship, you should probably call it a position, or a hiring, or
something like that. That would also force the issue of clarifying
what’s a person and what isn’t.
Plus, by having an employee model (separate from a person model), allows
me to create RESTful URLS more easily - ie I’m giving the person context
wrt to an employer within the URL structure.
(I’ll leave that part to you; I’m still working on figuring out why
I’m supposed to care about URLs so much
Dunno - am I being to abstract about it all?
I just think “employee” is the wrong concept for a relationship
between a person and an employer, and that the need for method_missing
is a sign that the domain model isn’t right.
David
I want to be able to say: @employee.lastname, instead of saying
def lastname
different matter.)
This way a person can have several bosses and employees and contacts
all at the same time. Also the real world object of a person is only
modeled once.
I wanted to exploit missing_method in some way, so that I don’t have to
write a whole lot of extra methods like you described. Also, as my
Person model grows, I want all these attributes to be automagically
available.
To me it sounds like a sign that the domain model isn’t right. The
fact that Employee needs the same behavior as Person suggests a
subclassing relationship.
The Employee model is basically the model to describe the relationship
between a person and an employer. In it I can record extra employee
specific data. I also have other types of people, like contacts etc. I
thought of not going the STI route, in case we want to store all the
people in an LDAP directory …
The word “employee” definitely means a person, not a relationship
between a person and a company. If you want to model the
relationship, you should probably call it a position, or a hiring, or
something like that. That would also force the issue of clarifying
what’s a person and what isn’t.
Plus, by having an employee model (separate from a person model), allows
me to create RESTful URLS more easily - ie I’m giving the person context
wrt to an employer within the URL structure.
(I’ll leave that part to you; I’m still working on figuring out why
I’m supposed to care about URLs so much
Dunno - am I being to abstract about it all?
I just think “employee” is the wrong concept for a relationship
between a person and an employer, and that the need for method_missing
is a sign that the domain model isn’t right.
David
I want to be able to say: @employee.lastname, instead of saying
def lastname
different matter.)
This way a person can have several bosses and employees and contacts
all at the same time. Also the real world object of a person is only
modeled once.
Peter
He has that now… the problem is he doesn’t want to write:
But that’s because Joerg has the intermediate table named “employee”,
when it really needs a non-human name, and that obscures the fact that
the relation between employer and employee is not, itself, a person.
You want something like:
class Employer
has_many :employees, :through => :positions, :source => :person
where the positions table has employer_id and person_id. Then you can
do things like:
@employer = Employer.find(params[:id]) @employees = @employer.employees
…
<% @employees.each do |employee| %>
<%= employee.firstname %> …
…
<% end %>
Here is a VERY GOOFY idea… create an act_as_delegate mixin.
class Employee < ActiveRecord::Base
act_as_delegate :to => :person
end
and this way your method_missing’s will have something to point to
first, IE the act_as_delegate will check the Person class. But this
all seems a tad wonky perverse and has an odor of wrongness…
Definitely utilize Peter’s structure, but this act_as_delegate thingy
could work. I have this odd feeling that this is built into Ruby
already, I just don’t know where to look to find it…
Hmmm. It makes sense to maybe label the middle table something other
than employees - to be able to then use the :through directive. In this
case, though - I’m not at all interested in their position at work - I’m
more interested in their employee number, when they started or finished
working etc. … the ‘employee’ details other than their personal
details. Maybe call it employment_details or something more generic.
I’ll need to think about the long term benefits from all these different
solutions a little more.
This way a person can have several bosses and employees and contacts
all at the same time. Also the real world object of a person is only
modeled once.
Peter
He has that now… the problem is he doesn’t want to write:
But that’s because Joerg has the intermediate table named “employee”,
when it really needs a non-human name, and that obscures the fact that
the relation between employer and employee is not, itself, a person.
You want something like:
class Employer
has_many :employees, :through => :positions, :source => :person
where the positions table has employer_id and person_id. Then you can
do things like:
@employer = Employer.find(params[:id]) @employees = @employer.employees
…
<% @employees.each do |employee| %>
<%= employee.firstname %> …
…
<% end %>
I want to be able to say: @employee.lastname, instead of saying @employee.person.lastname
Do I need to write individual method_missing’s for all of the person
attributes in Employee to forward the .lastname and .firstname calls
onto the :person association? Is there a smarter less brittle way?
There’s actually a delegate method that you use for this. It doesn’t
show up in the current docs because no docs were written…but if you
look at http://dev.rubyonrails.org/ticket/5002 (when trac comes back
up) you’ll be able to see the usage.
Simple example:
class Employee < AR::Base
belongs_to :person
delegate :first_name, :last_name, :to => :person
end
If you have an employee object and call employee.first_name, it
delegates it to the person assocation.
So there’s a more elegant way to do what you want. As others have
said though, I think you might need to rework your domain model.
The only problem I now see with this is that you won’t be able to access
the employee specifc properties that easily.
So, if I do
employee = Employer.find(1).employees.first
I can get employee.firstname etc, but won’t be able to get
employee.employee_number (which would be stored on the relationship
table)
Nor be able to do a query where I can query on attributes across the two
tables.
Basically - multiple table inheritance isn’t really possible yet in
ActiveRecord.
Perhaps the ‘best’ solution - where having a SINGLE record for a person
that can be multiple things like an employee or a contact etc - is to
have a people table which stores ALL possible fields for personal data
and employee specific data and contact specific data and any other
‘role’ specific data.
And have the middle relationship tables as employee_joins and
contact_joins or something that hold no other information other than the
fact that a person_id and an employer_id belong together. And then use
the :through directive to join them to an Employer with an :employees
association.
But that’s because Joerg has the intermediate table named “employee”,
when it really needs a non-human name, and that obscures the fact that
the relation between employer and employee is not, itself, a person.
You want something like:
class Employer
has_many :employees, :through => :positions, :source => :person
where the positions table has employer_id and person_id. Then you can
do things like:
@employer = Employer.find(params[:id]) @employees = @employer.employees
…
<% @employees.each do |employee| %>
<%= employee.firstname %> …
…
<% end %>
David
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.