Pular para o conteúdo principal

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 report funciona como um container, visualmente semelhante a um fieldset, mas com uma diferença essencial: ele gera um resource pró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 bloco report :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:

  1. Com símbolo → chama um método do modelo que já deve retornar os dados computados
  2. 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) { ... }
}
ChaveTipoDescrição
resourceStringNome da collection base no MongoDB (modelo em snake_case no plural)
scopeSymbolEscopo do modelo (scopes do Mongoid, como :all, :active, etc.)
whereHashFiltros adicionais. Pode referenciar campos de filtros com :resource.campo
projectHashDefine os campos que devem ser considerados da collection
groupHashCondições de agrupamento dos dados
pipelineSymbolNome de um método que define uma pipeline complementar
mapProcRecebe cada item e transforma como necessário
reduceProcRecebe o array completo e o modelo, permite pós-processamento final

Na prática o que acontece no exemplo acima é a seguinte sequência:

  1. O nerdify utilizará o modelo FinancialTransaction e a collection financial_transactions como base para rodar o aggregation
  2. Vai montar o match no inicio da pipeline baseada no escopo not_invoices definido dentro da classe FinancialTransaction
  3. Vai incluir no match um Mongoid::Criteria contendo o hash definido no parametro where. Ao utilizar o valor :financial_account_id, vai substituir esse valor pelo valor de self.financial_account_id do modelo onde o report está sendo definido. Isso deve ser utilizado para criar filtros personalizados.
  4. O project vai definir os campos que devem ser considerados na collection
  5. O group vai agrupar os resultados pelo valor definido
  6. O atributo pipeline vai 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 usando self.metodo ou self.field para montar o array da pipeline com mais personalização e filtros dinamicos.
  7. Os atributos map e reduce servem 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 individual
  • reduce: → 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.