ActiveRecord permite que você defina escopos (e escopos padrão), como você deve saber. Mas, às vezes, você pode precisar acessar dados sem o escopo padrão.
Vamos ver um exemplo:
class User < ActiveRecord::Base
has_many :posts
end
class Post < ActiveRecord::Base
default_scope -> { where(draft: false) }
belongs_to :user
end
Um exemplo muito simples, e esse escopo padrão permitirá que você não se importe com rascunhos de postagens.
Mas você logo descobrirá como isso pode facilmente enganá-lo.
Digamos que você precise acessar todos os rascunhos de um usuário, a abordagem óbvia será:
User.first.posts.where(draft: true)
E você encontrará um conjunto de resultados vazio. Isso ocorre porque o escopo padrão é aplicado, a consulta resultante é:
SELECT "posts".* FROM "posts" WHERE "posts"."draft" = 'f' AND "posts"."user_id" = $1 AND "posts"."draft" = 't' [["user_id", 1]]
Como você pode ver, você está procurando por postagens que sejam rascunho e não rascunho, o que leva a um conjunto vazio (a menos que esteja trabalhando em um sistema quântico ).
Bem, então deixe-me remover o escopo:
User.first.posts.unscoped.where(draft: true)
E em seus testes você pode pensar que resolveu seu problema, porque tentar isso em seu sistema de desenvolvimento parece apresentar a solução certa.
Mas vamos dar uma olhada na consulta resultante:
SELECT "posts".* FROM "posts" WHERE "posts"."draft" = 't'
Espere, onde está nossa condição? O método é muito mais poderoso do que você pensava, certo? Está retirando tudo , até a relação de usuário que temos na mesma linha!"posts"."user_id"
unscoped
Esse é o comportamento pretendido, mas pode levar a bugs muito difíceis de detectar.
Temos algumas soluções aqui.
Primeiro:
Post.unscoped{ User.first.posts.where(draft: true) }
Passar um bloco dentro removerá o escopo padrão, mas todas as condições dentro do bloco serão preservadas..unscoped
Segunda solução (feia):
Use rewhere
User.first.posts.rewhere(draft: true)
Mesmo que rewhere
seja uma ferramenta poderosa, em uma primeira leitura você pode ficar confuso ao vê-la sem um where
antes explícito .
Terceiro (mais feio):
Post.unscoped.where(user_id: User.first.id, draft: true)
Estamos apenas ignorando o belongs_to
aqui, parece mais um hack do que uma solução.
Quarta (melhor) solução:
Evite usar default_scope
em tudo
default_scope
traz mais problemas do que benefícios a longo prazo:
Muda (silenciosamente) o comportamento das instruções mais utilizadas, assim , sem nenhuma dica.
Post.first
Post.all
Veja também default_scope
é mau .
No final da história, tendo seu Post
modelo desta forma:
class Post < ActiveRecord::Base
belongs_to :user
scope :published, -> { where(draft: false) }
scope :draft, -> { where(draft: false) }
end
E chame posts
assim:
User.first.posts.draft
é muito mais claro e significativo.
Nota de rodapé : esteja ciente de que também scope
são influenciados por default_scope
. Por exemplo, se você esperava que esta solução funcionasse:
class Post < ActiveRecord::Base
default_scope -> { where(draft: false) }
belongs_to :user
scope :published, -> { where(draft: false) }
scope :draft, -> { where(draft: true) }
end
Eu tenho más notícias para você, o resultado
User.first.posts.draft
levará a uma nossa consulta quântica:
SELECT "posts".* FROM "posts" WHERE "posts"."draft" = 'f' AND "posts"."user_id" = $1 AND "posts"."draft" = 't' [["user_id", 1]]