rails complexos consultar - sindicatos? sub-select? Eu ainda posso usado named_scope?
-
06-07-2019 - |
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
-
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 dojoins_
eDISTINCT
apropriado) -
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.
-
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.
-
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 entrehas_young_wife_named
ehas_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 usara..100
mas depois quando a mulher se transforma 101 anos de idade, ela cai fora da pesquisa. (hmm. Ela pode cozinhar? J / k) -
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 modeloWife
.
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?
Solução
Bem, eu tive grandes resultados com named_scope
s 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.