I'm not sure the Subject really captures what I'm trying to figure out
how to do, so bear with me while I describe what I'm doing.
I've got two classes that I'm trying to figure out how best to
structure. One of the classes contains data that is shared by 1 or
more instances of the other class. However the user only ever sees the
non-sharable class. A more concrete example might help... consider the
case of a catalog of CDs. Each user has their own private catalog with
a list of CDs. When the add a CD, not only do they add the CD instance
to their collection, but they also create a shared CD_Info instance
that other users are able to see. Most of the attributes of a user's
CD are stored in the CD_Info instance, but a few (like purchase_price,
and date_purchased, and was_gift?) are stored in the user's CD object.
Also worth noting is that multiple user's CD objects can share a
CD_Info instance... there's no point in duplicating all that data.
So there's a few things I'd like to do.
1) Whenever an object of type CD is retrieved from the database, the
CD_Info object associated with it is loaded too. I know I can use
the :include attribute, but I'd like to make that the default for
loading instances of this type.
2) I want to create a form for adding an object of type CD and, since
most of that metadata is stored in the CD_Info object, have those
fields automatically routed there.
3) Find a CD object based on attributes of CD_Info.
4) Not have to update or add a bunch of helper functions in the CD
class whenever I add another attribute to the CD_Info class.
Any suggestions would be very welcome. Thank you!
So I've been thinking about this more and I may have a solution to my
problem. I'm hoping people with more Rails experience than I have
might sanity-check this idea.
The point of the "shared" metadata is to avoid duplication of data,
partly to keep the DB smaller, but mostly to facilitate returning a
list of "potential matches" to users entering new data... they can
click a "Use This Data" (or some such) button and get all the fields
filled in, except for the ones that are related to their particular
instance (created_at, modified_at, etc.). It hit me this morning that
a self-referential table might be a better way to manage this (at
least within the context of a Rails app) than to separate tables. The
way it would work is to have a parent_id (or whatever its supposed to
be called for the Rails Magic to work) which, if non-nil, refers to
the instance with the metadata in it, and if nil, means that the
current instance has the metadata in it. I'd probably add a count of
how many children refer to each parent and use that to order the
"potential match" list returned to the user. This would only be
allowed to go 2 levels: parent and child.
I think this solution addresses two of the needs I mentioned before
(a single form that has all the fields necessary, and not having to
add a bunch of helper functions to make it look like the user's
object has all the same attributes as the shared object). I'm not
sure it handles automatic loading of the parent by default or finding
a child based on the attributes in the parent.
Am I off track here? Is there a better way to handle these issues?
From a database normalization point of view, take a look at this:
Class CdInfo (this would be the shared one, right?)
Cd contains foreign_keys cd_info_id and user_id
Track contains foreign_key cd_info_id
=> "London Bridge"
Sorry if I've butchered the syntax. Still know SQL better than rails.
That's basically what I'm starting with. Well, sorta (not bothering
with the tracks just yet).
But what I'd like to be able to do is say
and have rails know that the Name property is actually an attribute of
the CDInfo object that each CD is required to have.
And also be able to say:
<% form_for( :cd, :url => cd_path ) -%>
<% end -%>
and have the fields for the CDInfo object show up there too.
My problem isn't so much with the DB and SQL as making sure I'm doing
this in the Rails best-practice way. I'm sure others have had similar
problems and it wouldn't surprise me if there's an elegant way of
handling it (by passing some kind of parameter/flag to the belongs_to
declaration or whatever). I just haven't been able to find such a way.
I see the similarities there. I'm just not sure that really captures
the form use-case, as tags would be added one at a time, or a list of
them all at once from a single form field... all the work is done in
the controller. Also, since tags are a one to many mapping (one object
having many tags), it doesn't address the ability to do a find on one
object using fields in a related object it has a one to one mapping
Being able to use the "form_for( :cd, :url => cd_path )" construct is
key to what I'm trying to accomplish. So far, I still haven't heard of
any way to have a single form where you can specify fields that are in
the object in the form_for() method *and* another object linked by
that one. That's why I'm still thinking the self-referential way may
be what I'm looking for... at least that way I get all the fields for
the form, and only have to do a bit more work in the controller (which
I assume I'd have to be doing anyway).