Pergunta

Parte da razão pela qual eu amo Rails é que eu odeio SQL - Eu acho que é mais como uma linguagem de montagem que devem ser manipulado com ferramentas de nível superior, como ActiveRecord. I parecem ter atingido os limites dessa abordagem, no entanto, e eu estou fora da minha profundidade com o SQL.

Eu tenho um modelo complexo com lotes de sub-registros. Eu também tenho um conjunto 30-40 named_scopes que implementam a lógica de negócios do cliente. Estes espaços se encadeados condicionalmente, razão pela qual eu tenho esses âmbitos joins_ de modo a junta não destroçada.

Eu tenho um par deles que não funcionam bem, ou pelo menos não como o cliente quer que eles trabalho. Aqui está uma idéia aproximada da estrutura do modelo, com alguns escopos nomeados (não todos necessários para o exemplo) que ilustram a minha abordagem e indicam os meus problemas. (por favor, perdoe quaisquer erros de sintaxe)

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
  1. posso encontrar homens cujas mulheres têm gatos que são vermelho e sete anos de idade

    @men = Man.has_red_cat.has_7yo_cat.joins_wives_cats.scoped({:select => 'DISTINCT men'})
    

    E eu posso até achar homens cujas mulheres têm gatos que são mais de 20 libras e mais de 6 anos de idade

    @men = Man.has_cat_fatter_than(20).has_cat_older_than(5).joins_wives_cats.scoped({:select => 'DISTINCT men'})
    

    Mas isso não é o que eu quero. Eu quero encontrar os homens cujas mulheres têm entre eles pelo menos um gato vermelho e um gato de sete anos, que não precisa ser o mesmo gato, ou para encontrar os homens cujas mulheres têm entre eles pelo menos um gato acima de um determinado peso e um gato com uma determinada idade.
    (em exemplos seguintes, por favor assuma a presença do joins_ e DISTINCT apropriado)

  2. posso encontrar homens com esposas chamada Esther

    @men = Man.has_wife_named('Esther')
    

    Eu posso mesmo encontrar homens com esposas chamada Ester, Ruth OU Ada (doce!)

    @men = Man.has_wife_named(['Esther', 'Ruth', 'Ada'])
    

    mas eu quero encontrar homens com esposas chamada Ester, Ruth e Ada.

  3. Ha ha apenas brincando, na verdade, eu preciso disso: eu posso encontrar homens com esposas menos de 30 anos chamada Esther

    @men = Man.has_young_wife_named('Esther')
    

    encontrar homens com jovens esposas chamada Ester, Ruth ou Ada

    @men = Man.has_young_wife_named(['Esther', 'Ruth', 'Ada'])
    

    mas como acima Eu quero encontrar homens com jovens esposas chamada Ester, Ruth e Ada. Felizmente, o mínimo é corrigido neste caso, mas seria bom para especificar uma idade mínima também.

  4. Existe uma maneira de teste para uma desigualdade com uma sintaxe de hash, ou você sempre tem que reverter para :conditions => ["", n] - note a diferença entre has_young_wife_named e has_yw_named_v2 - I como o primeiro melhor, mas o intervalo funciona somente para valores finitos. Se você está procurando uma mulher de idade, eu acho que você poderia usar a..100 mas depois quando a mulher se transforma 101 anos de idade, ela cai fora da pesquisa. (hmm. Ela pode cozinhar? J / k)

  5. Existe uma maneira de usar um escopo dentro de um escopo? Eu adoraria se :has_red_cat poderia usar :has_cat_of_color de alguma forma, ou se havia alguma maneira de usar o escopo de um registro filho em seu pai, para que eu pudesse colocar áreas relacionadas ao gato no modelo Wife.

Eu realmente não quero fazer isso em SQL direto sem usar named_scope, a menos que haja algo mais realmente agradáveis ??- sugestões para plugins e outros enfeites muito apreciada, ou direção para o tipo de SQL eu vou ter de aprender. Um amigo sugeriu que os sindicatos ou sub-pesquisas iria trabalhar aqui, mas aqueles que não parecem ser discutido tanto no contexto do Rails. Eu ainda não sei nada sobre pontos de vista - eles seriam úteis? Existe uma maneira rails-felizes para torná-los?

Obrigado!

Como eu estava indo para St Ives
Eu conheci um homem com sete esposas
Cada esposa tinha sete sacos
Cada saco tinha sete gatos
Cada gato tinha sete kits
Kits, gatos, sacos, esposas
Quantos estavam indo para St Ives?

Foi útil?

Solução

Bem, eu tive grandes resultados com named_scopes como estes:

named_scope :has_cat_older_than   lambda { |a| { :conditions => ["men.id in ( select man_id from wives where wives.id in ( select wife_id from cats where age > ? ) )", a] } }

e

named_scope :has_young_wife_named lambda { |n| { :conditions => ["men.id in ( select man_id from wives where name = ? and age < 30)", n] } }

Agora que posso fazer com sucesso

Member.has_cat_older_than(6).has_young_wife_named('Miriam').has_young_wife_named('Vashti')

e conseguir o que eu estou esperando. Esses escopos não exigem o uso da junta, e eles parecem jogar bem com a outra denominada junta.

w00t!

Comentários provocou sobre se esta é uma maneira eficiente de fazer isso, ou se há um mais 'rails-y' caminho. Alguma maneira de incluir um âmbito de outro modelo como um SQL subconsulta fragmento poderia ser útil ...

Outras dicas

Eu usei construct_finder_sql para realizar a subselect de um named_scope dentro de outro. Pode não ser para todos, mas usá-lo nos permitem secar um par de named_scopes usamos para relatórios.

Man.has_cat_older_than(6).send(:construct_finder_sql,{})

Tente isso no seu script / console.

Você usou a solução mais natural para Rails. SQL reta terão o mesmo desempenho por isso não há razão para usá-lo.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top