Active Record is doing a lot of magic to simplify associations between objects and their records in the database. Ruby is making this particularly easy due to ability of running methods during class definitions, which makes for instant domain specific languages.
Consider this:
class Project < ActiveRecord::Base
belongs_to :portfolio
has_one :project_manager
has_many :milestones
end
Our project objects are now able to respond to methods such as project.portfolio, project.has_project_manager?, and project.create_in_milestones("deadline" => Date.today + 5). This massively simplifies the wiring of the domain model and makes it much easier to traverse the graph when you get hold of just one of the ends.
I'm also particularly fond of the naming scheme, which makes reading aloud a natural task. "Project belongs to Portfolio", "Project has one Project Manager", and "Project has many Milestones". Great.
But how do we represent many-to-many relationships? Currently, it has the slightly clumsy macro of has_and_belongs_to_many, as illustrated below:
class Project < ActiveRecord::Base
has_and_belongs_to_many :categories
end
This makes it possible to do things like project.add_categories(critical, technical) that'll create relationships with the critical and technical category objects through a join tabel. It's also fairly readable as you'd say "Project has and belongs to many Categories".
But, it's also a little clumsy. So here's a challenge: By which other name may the m-m association macro in Active Record smell as sweet? The rules are pretty simple:
It has to read aloud with natural ease
It has to follow the form of has_one, has_many, and belongs_to
It has to be less clumsy than the current naming
There's an honorable mentioning in the Active Record README at stake to anyone who can crack this (priceless tribute, really!).
The only other name I can think of just cuts out a few characters, "has_and_is_of_many. Would it make sense to change "belongs_to" to "belongs_to_one" and add "belongs_to_many" letting the combination of the two, "belongs_to_many" and "has_many" identify the many to many relationship?
This is often an equal relationship, so it doesn't necessarily make sense to think of it as one party belonging to another. Where the other macros has clearly defined parent/child relations, this is more like brother/sister.
Challenge by Xen on May 12, 1:36
The best I could come up with at this hour:
references :categories
uses :categories
Actually, I'd be inclined to use 'xrefs :categories', as that's 'natual language' when I talk with my fellow coders.
Challenge by Luke Holden on May 12, 8:06
Different macro names I can think of... none of which are all that great... or even worth using. Some of which are kindof silly.
While I like the relations/reference notation, they could just as well describe a one-to-one link, which makes them a little too vague.
Challenge by Morten on May 12, 16:23
The relationship is called "many to many" - people know what that is right away, which is important. Unfortunatly, this means that it's hard to get rid of the verbosity in the macro name.
Well, it's also called "one-to-many" and "one-to-one", but still Active Record chooses to name it "has_many" and "has_one". I like the last suggestion, though ;).
Challenge by Lau Taarnskov on May 12, 17:33
"Project ... has many-to-many relationship with Categories."
becomes: has_many_to_many :categories
reading fast and running away in one minute.. but could'nt we just express has_and_belongs_to_many as a pair of separated methods ?
Challenge by Rel on May 23, 19:54
module Relation
#mix in as needed
def has_many (...&block) ... end
end
class Project
has_many :categories { inverse :has_many :projects }
end
Gives navigation in both directions, either by storing or searching. Using "inverse" is useful for all other relations too.
Have you considered using a Relation (as in pairings of objects) package, with domain, co-domain, and other relation-like operators? Then allow objects to contain relations between other objects. Each Project instance contains its own relation (set of pairs) between Person and Task.
These relations are only used to describe relations between Active Record objects, so the "inverse" idea seems to just be another way of saying "has_many :projects" right in the Category object.
And that was really what I didn't want as it necessitates knowledge of both sides of the relation at the time the macro is handled. It's incredibly much simpler just to parse macros on a per-object basis.
I'm not sure I understand the relation idea? Is it so project can describe relations between person and task that neither person nor task wants to know about? If so, they already kinda does need to know about it as associations in all but the many-t0-many case relies on foreign keys. So I don't know how much it would help.
Challenge by Rel on May 24, 10:44
I think many-many should be declared "as though" at both ends; hence I wrote the version with the "inverse" in the block. You can always handle that block right in the Project class if you need.
Moreover, has_many could do the "inverse" thing on each category Object if needed, without knowledge of the class at the other end.
The relation idea is separate, and applies even if implemented by foreign keys. It solves a particular problem: declaring multiplicity at the class level leaves a whole host of constraints unstated. Declaring it at the object level makes those constraints simple. Google "Constraint Diagrams" by Stuart Kent.
Challenge by Ian on May 26, 2:25
So why isn't splitting it into:
has_many: categories
belongs_to_many: categories
an option? Its clear what its doing without the need for a long line.
Because "has_many" uses a foreign key on child classes approach whereas has_and_belongs_to_many uses a join table (and it's the latter we want). Also, it doesn't really work to split it in two since the macro does all the work in one go. Adding the methods needed to the class in question and so on.
Challenge by Xen on May 26, 14:03
Haven't come up with a better name, but I was looking at:
belongs_to :portfolio
has_one :project_manager
What's the difference? And what about might_have_one :project_manager, is that implemented somehow?
If you use belongs_to, you hold the foreign key (in the example the class in question will have portfolio_id as a foreign key). If you use has_one, the foreign key sits on the other class. "has_one" also adds a number of extra methods, like build_project_manager, that belongs_to doesn't get.
might_have_one is the same. With "has_one" you get a query method, such as has_project_manager?, to determine whether a relation exists.
You can do it with multiple helpers by deferring accessor creation 'til (say) the first time you create an ActiveRecord.
Then has_many, belongs_to etc are used to build MethodBuilder method objects associated with each of the symbols. ActiveRecord::new then calls each MethodBuilder in turn the first time it's executed for a given class (or, for bonus points you could track whether a MethodBuilder has been created or changed since the last time new was called and execute any new ones every time).
So, has_many would do something like: 'target_class.attr_cache.method_builder.set_has_many(...)'
if the AccessorBuilder has already been set with 'belongs_to' it would replace itself with a ManyToManyAccessorBuilder
Challenge by corey lawson on February 13, 10:33
Why not just make it work with:
has_many: categories;
belongs_to: categories;
?
The more I think about it, the more Rails' internal language starts to resemble OCL (Object Constraint Language), and that's not a bad thing. Hmm...
Honestly, has_and_belongs_to_many is only clumsy in its length. I think it succinctly states the relationship in a way that most of the other suggested names don't. I can wrap my head around habtm so much more easily than things like crossreferences_with, interacts with many. In addition, the suggested names just don't convey the same information.
I'd just alias habtm to has_and_belongs_to_many and be done with it. That acronym seems to be the most famous of all Rails-isms anyway, so why not make the best of it?
(a little more serious)
has_multiple_relations_with
has_many_relationships_with
corresponds_to_many
These go a little further in describing the same thing as has_and_belongs_to_many... I'm still partial to the original, but my favorite of my suggestions is corresponds_to_many
My favorite of the rest is xrefs, but that's because of its shortness. I don't think using it would follow POLS, though. Again, something like that seems better as an alias for, rather than a replacement of has_and_belongs_to_many
It's like relates_to_many, but the important part is the 'co' prefix, which implies a two-way relationship. def: (have a mutual relationship or connection, in which one thing affects or depends on another).
Hope I'm not spamming your comments.. Just thought up the new one while playing with Dictionary.app