Reports
Um report no Nerdify representa um bloco de relatório dinâmico, usado para gerar, transformar e exibir dados em tabelas, gráficos ou componentes visuais de maneira declarativa e altamente flexível. Ele pode conter qualquer componente visual filho (como chart, list, component, etc.), mas seu papel principal é ser a fonte de dados computada.
🧠 Conceito
- Um
reportfunciona como um container, visualmente semelhante a umfieldset, mas com uma diferença essencial: ele gera umresourcepróprio com base em dados computados dinamicamente. - Esse resource é gerado a partir da configuração passada na opção
data:e armazenado com o nome definido no blocoreport :NOME_DO_RESOURCE_QUE_SERÁ_GERADO, *options....
⚙️ Sintaxe básica
report :nome_do_relatorio_e_resource_gerado, data: metodo_ou_hash_de_opcoes do
# componentes visuais que podem utilizar os dados computados para exibir em diferentes formatos (chart, list, component, etc.)
end
Você pode definir o conteúdo do relatório de duas formas principais:
- Com símbolo → chama um método do modelo que já deve retornar os dados computados
- Com hash de configurações → permite compor escopos, filtros, pipelines e transformações para o nerdify gerar o resultado internamente.
🔧 Opções disponíveis em data:
✅ Quando data: é um símbolo
report :grafico_geral, data: :metodo_de_consulta
Nesse caso, o método metodo_de_consulta será chamado dentro da instância do modelo e deve retornar um array de objetos ou qualquer estrutura que será renderizada. Ele pode fazer uso de fields, atributos e outros metodos de instancia do modelo para ajudar a montar a consulta e retornar o resultado. Você pode escrever este método como quiser, desde que retorne os dados. Porém, é recomendado utilizar o Aggregation Framework para ter uma consulta mais otimizada.
🛠 Quando data: é um hash de configuração
report :receitas, data: {
resource: "financial_transactions",
scope: :not_invoices,
where: { financial_account_id: ":financial_account_id" },
project: {status: "$status", value: "$value", category_id: "$category_id"},
group: "$category_id",
pipeline: :revenues_by_category_pipeline,
map: ->(item) { ... },
reduce: ->(result, object) { ... }
}
| Chave | Tipo | Descrição |
|---|---|---|
resource | String | Nome da collection base no MongoDB (modelo em snake_case no plural) |
scope | Symbol | Escopo do modelo (scopes do Mongoid, como :all, :active, etc.) |
where | Hash | Filtros adicionais. Pode referenciar campos de filtros com :resource.campo |
project | Hash | Define os campos que devem ser considerados da collection |
group | Hash | Condições de agrupamento dos dados |
pipeline | Symbol | Nome de um método que define uma pipeline complementar |
map | Proc | Recebe cada item e transforma como necessário |
reduce | Proc | Recebe o array completo e o modelo, permite pós-processamento final |
Na prática o que acontece no exemplo acima é a seguinte sequência:
- O nerdify utilizará o modelo FinancialTransaction e a collection financial_transactions como base para rodar o aggregation
- Vai montar o match no inicio da pipeline baseada no escopo
not_invoicesdefinido dentro da classe FinancialTransaction - Vai incluir no match um
Mongoid::Criteriacontendo o hash definido no parametrowhere. Ao utilizar o valor:financial_account_id, vai substituir esse valor pelo valor deself.financial_account_iddo modelo onde o report está sendo definido. Isso deve ser utilizado para criar filtros personalizados. - O
projectvai definir os campos que devem ser considerados na collection - O
groupvai agrupar os resultados pelo valor definido - O atributo
pipelinevai definir um metodo de instancia do modelo que será usado para complementar a pipeline montada até então pelos atributos anteriores. A pipeline retornada deverá ser um array de regras do Aggregation Framework. Ela será concatenada com a pipeline gerada pelo Nerdify a partir dos outros parametros informados. Como é um metodo de instancia você consegue utilizar valores do objeto usandoself.metodoouself.fieldpara montar o array da pipeline com mais personalização e filtros dinamicos. - Os atributos
mapereduceservem para tratar os dados gerados pela consulta antes de retorna-los na resposta da requisição.
Com essa combinação em sequência é possivel gerar praticamente qualquer tipo de relatório otimizado. Você pode combinar várias formas e atributos, podendo usar apenas um, todos ou a combinação de atributos e valores que quiser. Se eventualmente não for possível montar o relatório desejado dessa maneira, você deve utilizar o data: :metodo e computar os dados de forma 100% independente e personalizada.
🔍 Explicando em detalhes os blocos dinâmicos
pipeline:
A pipeline é um método definido no modelo que retorna um array no estilo do aggregation framework do MongoDB:
def revenues_by_category_pipeline
[
{ "$match": { type: "revenue" } },
{ "$group": { _id: "$category", total: { "$sum": "$value" } } }
]
end
map: e reduce:
Esses blocos são executados após a query:
map:→ transforma cada item individualreduce:→ transforma o array completo antes do envio à interface
📦 O que é retornado no JSON?
O report gera uma chave com seu nome no JSON retornado da API:
{
"resources": {
"grafico_receitas": [
{ "_id": "Serviços", "value": 1200 },
{ "_id": "Produtos", "value": 850 }
]
}
}
Estrutura de resultado para montar gráficos
Para utilizar os componentes padrão de gráficos do Nerdify, você deve retornar os dados sempre em um dos dois padrões que específico abaixo.
Para gráficos que não tem series de valores, você deve retornar apenas _id e value, onde um será a legenda e outro o valor. Exemplo muito comum em donuts, pizzas, etc...
[
{_id: "Legenda 1", value: "Valor 1"},
{_id: "Legenda 2", value: "Valor 2"},
{_id: "Legenda 2", value: "Valor 3"}
]
E defina o componente do gráfico:
chart :nome_do_grafico, type: :pie, resources: :nome_do_report
Em gráficos com séries de dados, por exemplo, um gráfico de barra para um fluxo de caixa que teremos não só um valor como vários, devemos usar o _id para representar o agrupamento e um nome para cada valor. Como mostro a seguir:
[
{_id: "2023", revenues: "Valor de receita", expenses: "Valor de despesa", balance: "Valor de saldo"},
{_id: "2024", revenues: "Valor de receita", expenses: "Valor de despesa", balance: "Valor de saldo"},
{_id: "2025", revenues: "Valor de receita", expenses: "Valor de despesa", balance: "Valor de saldo"},
]
No componente do gráfico é que você determina a configuração de como utilizar estes valores para montar as séries. Segue um exemplo abaixo:
chart :nome_do_grafico, type: :bar, resources: :nome_do_report, type: :line, series: [
{ name: "balance", label: "Saldo", backgroundColor: "#6263AC", tension: 0.4, type: "line", stack: "balance" },
{ name: "revenues", label: "Receitas", backgroundColor: "#62AC63", type: "bar", stack: "transactions" },
{ name: "expenses", label: "Despesas", backgroundColor: "#E27572", type: "bar", stack: "transactions" },
], labels: %w[2023 2024 2025]
No exemplo acima, as barras que representam despesas e receitas serão exibidas uma sob a outra em formato de barra, enquanto o saldo será visualizado independente delas duas em formato de linha.
Na seção sobre gráficos você poderá visualizar mais detalhes de como fazer isso.
Estrutura de resultado para montar tabelas
Para utilizar os componentes padrão de tabelas do Nerdify, você deve retornar os dados sempre como um array de objetos, onde a configuração da tabela determina as colunas que serão exibidas na tabela. Por exemplo:
[
{_id: "2023", one: "Nome", two: "Valor"},
{_id: "2024", one: "Nome", two: "Valor"},
{_id: "2025", one: "Nome", two: "Valor"}
]
E na tabela definir:
list :nome, type: :table, resources: :nome_do_report do
add :one
add :two
end
Para saber como montar tabelas e arvores mais complexas, visualize a seção sobre Lists desta documentação.
🧰 Estilização
Você pode estilizar um report com as mesmas opções visuais de um fieldset, como:
size:styles:header:
report :receitas, header: true, size: 12, styles: { padding: {}, background: "transparent" } do
end
🔁 Integração com filtros do modelo
Se o modelo define um fieldset, você pode usar os campos dele no where: usando :nome_do_campo ou dentro dos métodos de pipeline usando self.nome_do_campo.
Exemplo:
where: { final_date: ":final_date" }
O valor será substituído dinamicamente pelos valores dos filtros preenchidos.
🧪 Exemplo de uso em um modelo completo
class TransactionReport
include Nerdify::Model
orm :mongoid
fieldset :grupo_de_campos_para_formulario_de_filtros do
field :financial_account_id, type: :select, collection: "financial_accounts"
end
report :grafico_receitas, data: {
resource: "financial_transactions",
scope: :not_invoices,
where: { financial_account_id: ":financial_account_id" },
pipeline: :revenues_by_category_pipeline,
map: ->(item) {
{
categoria: item.dig("_id", "category"),
total: item["total"]
}
},
reduce: ->(dados, modelo) {
dados.sort_by { |d| -d[:total] }
}
} do
chart :receitas_em_doughnut, resources: :grafico_receitas, type: :doughnut
list :tabela_de_receitas_agrupadas, type: :table, resources: :grafico_receitas do
component :text, name: :nome_da_coluna, type: :span
component :text, name: :outra_coluna, type: :span
end
end
No exemplo acima, para utilizar o relatório você poderá fazer algo como:
report = TransactionReport.new
report.render({financial_account_id: 123})
# ou
report = TransactionReport.new(financial_account_id: 123)
report.render
Mas você não precisa se preocupar com utilização nesse formato se você usar em conjunto um controlador que herde de Nerdify::ReportsController. Neste caso, o controlador cuidará de tudo, inclusive a montagem dos filtros, componentes, gráficos etc de forma automática dentro dos padrões de ux/ui do template e do projeto. Você não precisará escrever nenhuma action para isso.
Quando você utiliza um controlador do Nerdify, ele vai montar as páginas usando os componentes definidos no modelo como charte list em combinação com os dados computados pelo report. No exemplo a cima a página teria os filtros no topo com opção de selecionar uma conta, e logo abaixo um card com o nome do report e dentro teria um grafico em donuts e uma tabela.
class TransactionReportsController < Nerdify::ReportsController
#authenticate ....
#template ....
end
🧠 Dica prática
Você pode reutilizar o mesmo pipeline com escopos e where: diferentes para gerar relatórios comparativos, ou criar pipelines reutilizáveis com tipos diferentes (ex: receita ou despesa).
🚀 Conclusão
O bloco report é a peça central da construção de dashboards e relatórios de análises no Nerdify. Ele permite isolar a lógica de obtenção dos dados diretamente no modelo, usando agregações complexas, pós-processamentos e integração com filtros — tudo isso com uma sintaxe clara e integrada ao restante do framework.