Concerns e composição
Herança força uma hierarquia rígida — concern deixa você compartilhar comportamento entre models sem isso, desde que não vire uma gaveta de bagunça.
Produto e Pedido não têm nenhuma relação de "é um" entre si — mas os dois precisam poder ser arquivados sem sair do banco (um campo arquivado, um scope pra listar só os ativos, um método pra marcar como arquivado). Copiar esse código nos dois models é duplicação; criar uma superclasse comum só pra compartilhar isso força uma hierarquia que não faz sentido semântico. Um concern resolve exatamente esse caso: comportamento compartilhado sem herança.
Um concern é um módulo com uma DSL pra rodar código na inclusão
module Arquivavel
extend ActiveSupport::Concern
included do
scope :ativos, -> { where(arquivado: false) }
scope :arquivados, -> { where(arquivado: true) }
end
def arquivar!
update!(arquivado: true)
end
endActiveSupport::Concern é o que dá acesso ao bloco included do ... end: tudo dentro dele roda no contexto da classe que inclui o módulo, e é por isso que scope — que só existe em classe ActiveRecord — funciona ali dentro, mesmo estando escrito num módulo solto.
class Produto < ApplicationRecord
include Arquivavel
end
class Pedido < ApplicationRecord
include Arquivavel
end
Produto.ativos # só os não arquivados
produto.arquivar! # marca como arquivado, sem duplicar lógica em nenhum dos dois modelsController também usa concern — não é uma ferramenta exclusiva de model. Um app/controllers/concerns/paginavel.rb compartilhando a mesma lógica de paginação entre ProdutosController e PedidosController segue a mesma ideia: comportamento comum, sem repetir código, sem forçar os dois controllers a herdarem de uma terceira classe intermediária.
Quando um concern vira gaveta de bagunça
O anti-padrão mais comum não é usar concern demais — é criar um concern genérico (Helpers, Util, Shared) e ir empilhando ali qualquer método que não tem lugar óbvio, sem relação lógica entre um método e outro. Isso é pior do que não ter concern nenhum: agora o comportamento de um model está espalhado num arquivo que ninguém sabe o que faz só de olhar o nome.
O teste prático: um concern deveria ter um nome que descreve uma capacidade coesa — Arquivavel, Pesquisavel, Notificavel. Se você não consegue dar ao concern um nome assim, sem ser um substantivo genérico, é sinal de que ele deveria virar duas classes separadas, ou nem existir.
Exercício
Produto e Categoria têm uma coluna nome em comum. Escreva um concern Pesquisavel que adiciona um método de classe buscar_por_termo(termo), usando where com placeholder (nunca interpolando o termo direto na query).