Incorporating runtime parameters in has_many :conditions

I'm trying to model the concept of a person who goes by various
nicknames. My Person model contains a "name" attribute containing their
real name, and a "has_many :nicknames" relationship. Through my business
rules, I'm enforcing the requirement that a person's real name is always
included in the database as a nickname.

Rule #1: If you're duplicating data, you're doing the wrong thing, unless
          you have a VERY SERIOUS performance problem and you have measured
          the performance advantage of a denormalization very carefully.

(If this seems like a really bad data structure, I'm open to suggestions.
However, having weighed up various alternatives, this seems to me to be the
least worst solution.)

I'd recommend either:

   1) Not storing their real name in nicknames.

or

   2) Removing the "name" attribute from Person and adding an extra attribute
      to your Nicknames model named real_name that is a boolean.

I prefer #1 because real names are decided NOT nicknames, so why would they
be stored there?

If you like #2, then I'd highly recommend renaming that model Names, because
they'll decidedly NOT be just nicknames any longer!

I find a lot of DB designers paint themselves into corners by doing the sort
of thing that you've done here. If the tables names don't fit the use, it's
not that you have a special need, it's because your data model is wrong. :slight_smile:

If the data model is wrong, you're going to write a larger volume of code, and
even worse, a larger volume of *complex* code to 'decipher' the data that will
become hopelessly entangled over time.

I now want to add an "alternative_nicknames" relationship, which will
comprise all of the person's nicknames *excluding* their real name. I've
tried

With the advice above, there's no need to do this. In #1 real name is one place
and nicknames are another place, or in #2 the two are very obviously separate
via the boolean column which will make it easier to pinpoint real names -vs-
nicknames.

has_many :alternative_nicknames, :class_name => "Nickname",
  :conditions => ["nickname <> ?", self.name]

- however, since has_many is a class-level method, the :conditions block
is evaluated at the point of loading the class, and therefore self.name
returns "Person" rather than the person's name. Out of desperation I've
even tried wrapping the has_many declaration in an after_initialize
callback, but then it complains that has_many is not defined. The
examples in the Rails docs and the Pragmatic book only demonstrate
static SQL fragments being used in the :conditions clause, not ones
incorporating parameters. Any ideas how I can achieve this?

There's nothing magic in the *operation* of has_many for the simplistic
cases, so you *could* leave out the has_many declaration and just code
an accessor method of your own:

def alternative_nicknames
   self.nicknames.find(:all, :conditions => ["nickname <> ?", self.name])
end

One thing I think you need to be a bit skeptical of, however, is in the
clear overloading of the Nicknames.nickname column. You're not just
storing a nickname there, but through usage, you're also using it to
define a boolean relationship as well, based on variable data in the
parent table.

If you use method #1 above, you could access the various pieces very
simply and without any sort of magic at all.

person.name
person.nicknames
person.all_names

class Person
   def all_names
     [name,nicknames.collect { |n| n.nickname}]
   end
end