de-normalized table associations

Hi,

Working with legacy DB here...

2 tables:   Projects   Workers

A worker record has many fields in it to hold project codes as in: project1, project2, project3, ... project30 (great, isn't it?...)

Normalizing the DB is out of the question.

I've been thinking about this for a while and I can't find a way to associate both tables as in:

"Project has_many :workers" "Worker.project1 belongs_to :project" "Worker.project2 belongs_to :project" etc.

Any ideas?

Pepe

you need has_and_belongs_to_many http://railsbrain.com/api/rails-2.3.2/doc/index.html?a=M001888&name=has_and_belongs_to_many

Thanks Ben, but I don't believe so. As I said in the OP, the table is 'de-normalized'. I don't have 1 record per worker and project to be able to associate the tables the usual way. I have many fields holding project code values in the same record, obviously all of them with different names. In other words, I have many fields to "hook into" for the association to happen, not just one neat field I can associate with.

you need has_and_belongs_to_many http://railsbrain.com/api/rails-2.3.2/doc/index.html?a=M001888&name=has_and_belongs_to_many

How, precisely will a join-table help, when he says that "Normalizing the DB is out of the question"? :-/

Hi,

Working with legacy DB here...

2 tables: Projects Workers

A worker record has many fields in it to hold project codes as in: project1, project2, project3, ... project30 (great, isn't it?...)

Normalizing the DB is out of the question.

I've been thinking about this for a while and I can't find a way to associate both tables as in:

"Project has_many :workers" "Worker.project1 belongs_to :project" "Worker.project2 belongs_to :project" etc.

You are highly limited given your inability to re-structure the database, and I wonder whether you want to reconsider negotiation with whatever powers that be to refactor the database a little (or a lot! :wink:

But working with the tables you have, you can at least use AR as much as possible...

# worker model belongs_to :project1, :class_name => "Project", :foreign_key => "project1_foreign_key_name_in_workers_table" belongs_to :project2, :class_name => "Project", :foreign_key => "project2_foreign_key_name_in_workers_table" belongs_to :project3, :class_name => "Project", :foreign_key => "project3_foreign_key_name_in_workers_table"

The project model is going to be harder, and you're not going to be able to intuitively assign workers to projects, but you can at least fudge-together some helper methods to make it look like you're leveraging the power of Rails! :slight_smile:

#project model has_many :project1_keyed_workers, :class_name => "Worker", :foreign_key => "project1_foreign_key_name_in_workers_table" has_many :project2_keyed_workers, :class_name => "Worker", :foreign_key => "project2_foreign_key_name_in_workers_table" has_many :project3_keyed_workers, :class_name => "Worker", :foreign_key => "project3_foreign_key_name_in_workers_table"

def workers   # build and array of all the arrays of workers, flatten it and get rid of nil values   ( << project1_keyed_workers << project2_keyed_workers << project3_keyed_workers).flatten.compact end

Hope this helps.... Michael

whoa! that doesn't look very legible once posted. If you can't make sense of my code snippet, let me know and I'll post a Gist link.

In fact, I might as well do that anyway: http://gist.github.com/312634

Thanks a lot Michael for your explanation and efforts. :slight_smile:

I had thought about going the way you propose but it seemed to me like too much work (I think the total amount of columns I would need to write code for is 32) for what I actually need. If there is an easy way of making the association work both ways I surely would like to know, however, while you were trying to help me I think I found a neat solution for my current need, which is not having the association work both ways but accessing the project information for a given worker.

The problem I'm trying to solve is a little bit more complicated than I originally posted. I am not really dealing with ID values. The Projects table has an ID and also a project code, which is also supposed to be unique "when it has a value" (this is enforced at application level). The project ID is not what is stored in the 'project' columns in the Worker record, it is the project code instead.

The solution I have so far works quite well and uses virtual attirbutes (below). The first virtual attribute is a list of all project codes actually stored in the record. If a 'project*' column is empty it does not provide a value to the list. The second one is the projects information based on those project codes the first virtual attribute provides.

class Worker < ActiveRecord::Base   attr_reader :project_codes, :projects

  def project_codes     self.attributes.delete_if {|key, value| key.downcase !~ /project/

value.blank? }.values.map{|v| v.strip}.uniq

  end

  def projects     Project.find_all_by_code self.project_codes   end end

Thanks a lot Michael for your explanation and efforts. :slight_smile:

I had thought about going the way you propose but it seemed to me like too much work (I think the total amount of columns I would need to write code for is 32) for what I actually need. The Projects table has an ID and also a project code, which is also supposed to be unique "when it has a value" (this is enforced at application level). The project ID is not what is stored in the 'project' columns in the Worker record, it is the project code instead.

Well, the first thing you have to do is creep up behind the original developer of that table with your Louisville Slugger in hand...

But seriously, whoever produced that table should be fired. If my plumber connected the taps that badly I'd get wet every time I change channel on the TV. [1]

If there is an easy way of making the association work both ways I surely would like to know

Given your table structure, I think you've done well to get as much use out of it as you have; your "projects" method is another little fudge I might have considered. You might get something else working (both ways with a little hassle) using named scopes rather than associations; defining them (all 32+!) in a loop to be DRY, and concatenating their results like I suggested with the associations. But it's a lot of hard work, and not really what Rails is best at.

Honestly, I think you'd be better off spending the time scouring the job sites for a new employer; somewhere where databases are free to enjoy the benefits of friendly ORMs :slight_smile:

Good luck, Michael

[1] If it was *you* that created the current tables, but "years ago, before you knew what you were doing".... well, it's fair enough that you're trying to sort it out now... as long as you're suitably apologetic :wink:

Michael,

If it were me who had created that monstrosity I would have apologized in advance, believe me. :slight_smile:

Funny enough, I might very probably be on the phone talking to the "owner of the beast" in a few minutes and believe it or not, instead of starting to keep things 'sane' and add additional information in new tables he has already suggested adding a brand new set of 32 columns for the related values we need to store now! :smiley:

The sad thing is that the way things are now we might just need to put up with it! This guy is obviously on his way out and the application will be rewritten and the DB redesigned at some point in the near future. The owner of the application is trying to find the "path of least resistance" right now to make things work so we can meet the deadlines we have and start working on the replacement software asap so we don't need have this other person involved any longer.

I'll remember to bring my Louisville Slugger if I ever meet him in person. :wink: