Part of why I love Rails is that I hate SQL - I think it's more like an assembly language that should be manipulated with higher level tools such as ActiveRecord. I seem to have hit the limits of this approach, however, and I'm out of my depth with the SQL.
I have a complex model with lots of sub-records. I also have a set 30-40 named_scopes that implement the business logic from the client. These scopes get chained together conditionally, which is why I have those joins_
scopes so the joins don't get clobbered.
I've got a couple of them that don't work right, or at least not how the client wants them to work. Here's a rough idea of the model structure, with a few named scopes (not all needed for the example) that illustrate my approach and indicate my problems. (please forgive any syntax errors)
class Man < ActiveRecord::Base
has_many :wives
named_scope :has_wife_named lambda { |n| { :conditions => { :wives => {:name => n}}}}
named_scope :has_young_wife_named lambda { |n| { :conditions => { :wives => {:name => n, :age => 0..30}}}}
named_scope :has_yw_named_v2 lambda { |n| { :conditions => ["wives.name = ? AND wives.age <= 30", n]}}
named_scope :joins_wives :joins => :wives
named_scope :has_red_cat :conditions => { :cats => {:color => 'red'}}
named_scope :has_cat_of_color lambda { |c| { :conditions => { :cats => {:color => c}}}}
named_scope :has_7yo_cat :conditions => { :cats => {:age => 7}}
named_scope :has_cat_of_age lambda { |a| { :conditions => { :cats => {:age => a}}}}
named_scope :has_cat_older_than lambda { |a| { :conditions => ["cats.age > ?", a] }}
named_scope :has_cat_younger_than lambda { |a| { :conditions => ["cats.age < ?", a] }}
named_scope :has_cat_fatter_than lambda { |w| { :conditions => ["cats.weight > ?", w] } }
named_scope :joins_wives_cats :joins => {:wives => :cats}
end
class Wife < ActiveRecord::Base
belongs_to :man
has_many :cats
end
class Cat < ActiveRecord::Base
belongs_to :wife
end
I can find men whose wives have cats that are red AND seven years old
@men = Man.has_red_cat.has_7yo_cat.joins_wives_cats.scoped({:select => 'DISTINCT men'})
And I can even find men whose wives have cats that are over 20 pounds and over 6 years old
@men = Man.has_cat_fatter_than(20).has_cat_older_than(5).joins_wives_cats.scoped({:select => 'DISTINCT men'})
But that's not what I want. I want to find the men whose wives have amongst them at least one red cat and one seven year old cat, which need not be the same cat, or to find the men whose wives have amongst them at least one cat above a given weight and one cat older than a given age.
(in subsequent examples, please assume the presence of the appropriatejoins_
andDISTINCT
)I can find men with wives named Esther
@men = Man.has_wife_named('Esther')
I can even find men with wives named Esther, Ruth OR Ada (sweet!)
@men = Man.has_wife_named(['Esther', 'Ruth', 'Ada'])
but I want to find men with wives named Esther AND Ruth AND Ada.
Ha ha, only joking, actually, I need this: I can find men with wives under 30 named Esther
@men = Man.has_young_wife_named('Esther')
find men with young wives named Esther, Ruth or Ada
@men = Man.has_young_wife_named(['Esther', 'Ruth', 'Ada'])
but as above I want to find men with young wives named Esther AND Ruth AND Ada. Fortunately, the minimum is fixed in this case, but it would be nice to specify a minimum age as well.
is there a way to test for an inequality with a hash syntax, or do you always have to revert to
:conditions => ["", n]
- note the difference betweenhas_young_wife_named
andhas_yw_named_v2
- I like the first better, but the range only works for finite values. If you're looking for an old wife, I guess you could usea..100
but then when a wife turns 101 years old she drops off the search. (hmm. can she cook? j/k)is there a way to use a scope within a scope? I'd love it if
:has_red_cat
could use:has_cat_of_color
somehow, or if there was some way to use the scope from a child record in its parent, so I could put the cat related scopes into theWife
model.
I really don't want to do this in straight SQL without using named_scope
, unless there's something else actually nicer - suggestions for plugins and whatnot greatly appreciated, or direction into the sort of SQL I'll need to learn. A friend suggested that UNIONs or sub-searches would work here, but those don't seem to be discussed much in the context of Rails. I don't yet know anything about views - would they be useful? Is there a rails-happy way to make them?
Thank you!
As I was going to St Ives
I met a man with seven wives
Each wife had seven sacks
Each sack had seven cats
Each cat had seven kits
Kits, cats, sacks, wives
How many were going to St Ives?