Scope em Ruby on Rails

Artigos - 23/Set/2020 - por André Kanamura

Introdução

No desenvolvimento de aplicações, é muito comum existirem algumas queries que acabam sendo repetidas em diferentes pontos do projeto. Esse é um sinal de que podemos isolar esse trecho de código em um método, por exemplo. Em Ruby on Rails temos a funcionalidade scope, escopo em inglês, que faz exatamente o que seu nome diz: define um escopo para a query, que poderá ser referenciado em qualquer lugar da aplicação como chamada de método da Classe. Dentro de scopes podemos usar qualquer um dos métodos usados para queries, como where, includes e joins. Como resultado da query, será retornada uma ActiveRecord::Relation ou nil, de forma que podemos fazer chamadas de métodos sequenciais.

Como implementar

Vamos usar como exemplo uma aplicação para uma biblioteca onde temos livros cadastrados como objetos de classe Book. Vamos considerar que, além de título, autor e ano de publicação, o livro possui apenas um atributo boolean available para indicar sua disponibilidade para retirada. Se quisermos implementar um scope que retorna apenas os livros que estão disponíveis para locação, ou seja, possuem o atributo available: true, podemos incluir no código do model Book:

class Book < ApplicationRecord
  scope :available , -> { where(available: true) }
end

Com essa implementação, fica disponível o método available para a classe Book. Assim, a query Book.where(available: true) pode ser feita com Book.available:

      Book.create(title: 'Persépolis',
                  author: 'Marjane Satrapi',
                  publication_year: 2009,
                  available: false)
      Book.create(title: 'Orgulho e preconceito',
                  author: 'Jane Austen',
                  publication_year: 1813,
                  available: true)
      Book.create(title: 'Battle Royale',
                  author: 'Koushun Takami',
                  publication_year: 1999,
                  available: true)

    Book.available
    => #<ActiveRecord::Relation [#<Book id: 2, title: "Orgulho e preconceito", author: "Jane Austen", publication_year: "1813", created_at: "2020-09-22 20:32:44", updated_at: "2020-09-22 20:32:44", available: true>, #<Book id: 3, title: "Battle Royale", author: "Koushun Takami", publication_year: "1999", created_at: "2020-09-22 20:32:45", updated_at: "2020-09-22 20:32:45", available: true>]>

Esse método também fica disponível para ser usado dentro de outro scope se for necessário. Além disso, você pode passar argumentos aos scopes. Para o exemplo deste artigo, vamos considerar que queremos implementar uma query que retorna os livros disponíveis de um determinado autor:

class Book < ApplicationRecord
  scope :available , -> { where(available: true) }

  scope :available_from_author, ->(author) {
    available.where(author: author)
  }
end

Com essa implementação, podemos usar o método available_from_author:

      Book.create(title: 'Persépolis',
                  author: 'Marjane Satrapi',
                  publication_year: 2009,
                  available: false)
      Book.create(title: 'Orgulho e preconceito',
                  author: 'Jane Austen',
                  publication_year: 1813,
                  available: true)
      Book.create(title: 'Battle Royale',
                  author: 'Koushun Takami',
                  publication_year: 1999,
                  available: true)

    Book.available_from_author('Jane Austen')
=> #<ActiveRecord::Relation [#<Book id: 2, title: "Orgulho e preconceito", author: "Jane Austen", publication_year: "1813", created_at: "2020-09-22 20:32:44", updated_at: "2020-09-22 20:32:44", available: true>]>

O uso mais interessante de scopes consiste na combinação deles com outras queries. Por exemplo, uma opção alternativa a implementação acima seria criar o scope :from_author e, quando chamarmos esse método, combinarmos com o available:

class Book < ApplicationRecord
  scope :available , -> { where(available: true) }

  scope :from_author, ->(author) {
    where(author: author)
  }
end

# Book.from_author('Jane Austen').available

Se sua aplicação tiver uma associação entre User e Book, poderíamos fazer chamadas como:

  current_user.books.from_author('Jane Austen').available

Conclusão

Cada um dos scopes criados podem ser usados em encadeamento e combinados com o where. Mostramos aqui exemplos de uso mais básico de scopes, mas eles possuem algumas outras funcionalidades interessantes que podem ser úteis para as suas necessidades, como trabalhar com condicionais ou definir um scope padrão. Você pode consultar a documentação oficial do Rails para saber mais sobre ele.

Referências

Foto de perfil do autor
André Kanamura

Dev na Campus Code