Matt Westcott
2006-Nov-28 00:37 UTC
Incorporating runtime parameters in has_many :conditions
Hi, I was wondering if there was any way to include runtime parameters in the :conditions clause of a has_many relationship? 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. (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 now want to add an "alternative_nicknames" relationship, which will comprise all of the person''s nicknames *excluding* their real name. I''ve tried 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? (If it helps explain things any better, I believe this person''s (unanswered) question is equivalent to mine: http://groups.google.com/group/rubyonrails-talk/msg/5006b300e4e5a8cc ) - Matt -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Tom Mornini
2006-Nov-28 02:55 UTC
Re: Incorporating runtime parameters in has_many :conditions
On Nov 27, 2006, at 4:37 PM, Matt Westcott wrote:> 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. :-) 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 > triedWith 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 -- -- Tom Mornini, CTO -- Engine Yard, Ruby on Rails Hosting -- Reliability, Ease of Use, Scalability -- (866) 518-YARD (9273) --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Mark Reginald James
2006-Nov-28 11:02 UTC
Re: Incorporating runtime parameters in has_many :conditions
Matt Westcott wrote:> 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? > > (If it helps explain things any better, I believe this person''s > (unanswered) question is equivalent to mine: > http://groups.google.com/group/rubyonrails-talk/msg/5006b300e4e5a8cc )Write the condition with single quotes to delay evaluation until the association load is done in an instance context: :conditions => ''nickname <> #{name}'' This assumes a sanitized name attribute. If name is instead stored in an unsanitized form you can try something like: :conditions => ''nickname <> #{class.connection.quote(name)}'' It would have been nice to be able to write :conditions => ''["nickname <> ?", #{name}]'' but conditions are currently sanitized then interpolated, not interpolated then sanitized, and to make the reverse work interpolation would have to be changed to handle arrays. -- We develop, watch us RoR, in numbers too big to ignore. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Mark Reginald James
2006-Nov-28 12:59 UTC
Re: Incorporating runtime parameters in has_many :conditions
Mark Reginald James wrote:> :conditions => ''nickname <> #{name}''Oops, scrub this, name isn''t quoted. Use this insead> :conditions => ''nickname <> #{class.connection.quote(name)}''-- We develop, watch us RoR, in numbers too big to ignore. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---
Matt Westcott
2006-Nov-28 23:50 UTC
Re: Incorporating runtime parameters in has_many :conditions
Tom Mornini wrote:> On Nov 27, 2006, at 4:37 PM, Matt Westcott wrote: > >> 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. > > 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.Excellent, thanks - that does make a lot of sense. (Option 2 makes more sense in my case, as I''ve got other models in the system which define relations with (nick)names, so I need to keep them all together.) I think this is one of those occasions where I''m so hung up over a niggly detail of the data model that it takes another pair of eyes to see the obvious solution... cheers. Mark Reginald James wrote:> Write the condition with single quotes to delay evaluation until > the association load is done in an instance context: > :conditions => ''nickname <> #{class.connection.quote(name)}''Thanks for this too - good to know the direct answer for when I need it in future. I have to admit that my paranoid side is now wondering if it''s really doing two passes of evaluation (and if so, what happens if your data itself contains "#{insert malicious ruby code here}") but I''m sure it''ll all make sense to me after an hour or two of head-scratching :-) - Matt -- Posted via http://www.ruby-forum.com/. --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "Ruby on Rails: Talk" group. To post to this group, send email to rubyonrails-talk-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org To unsubscribe from this group, send email to rubyonrails-talk-unsubscribe-/JYPxA39Uh5TLH3MbocFFw@public.gmane.org For more options, visit this group at http://groups.google.com/group/rubyonrails-talk?hl=en -~----------~----~----~----~------~----~------~--~---