{"id":13411790,"url":"https://github.com/ankane/rollup","last_synced_at":"2025-11-17T14:11:31.570Z","repository":{"id":40595982,"uuid":"293644943","full_name":"ankane/rollup","owner":"ankane","description":"Rollup time-series data in Rails","archived":false,"fork":false,"pushed_at":"2025-10-22T03:45:15.000Z","size":63,"stargazers_count":340,"open_issues_count":3,"forks_count":18,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-11-04T09:09:07.336Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ankane.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-09-07T22:28:56.000Z","updated_at":"2025-10-31T17:36:44.000Z","dependencies_parsed_at":"2023-12-26T20:41:52.574Z","dependency_job_id":"5030db80-49aa-4fc2-a621-f3fe20a7407b","html_url":"https://github.com/ankane/rollup","commit_stats":{"total_commits":76,"total_committers":3,"mean_commits":"25.333333333333332","dds":0.02631578947368418,"last_synced_commit":"d1cb4a5e0efc55992008cbc67358bdec9e225c76"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/ankane/rollup","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Frollup","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Frollup/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Frollup/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Frollup/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ankane","download_url":"https://codeload.github.com/ankane/rollup/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ankane%2Frollup/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":284894076,"owners_count":27080638,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-11-17T02:00:06.431Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T20:01:16.956Z","updated_at":"2025-11-17T14:11:31.554Z","avatar_url":"https://github.com/ankane.png","language":"Ruby","funding_links":[],"categories":["Ruby","Gems"],"sub_categories":["Query Enhancement","Articles"],"readme":"# Rollup\n\n:fire: Rollup time-series data in Rails\n\nWorks great with [Ahoy](https://github.com/ankane/ahoy) and [Searchjoy](https://github.com/ankane/searchjoy)\n\n[![Build Status](https://github.com/ankane/rollup/actions/workflows/build.yml/badge.svg)](https://github.com/ankane/rollup/actions)\n\n## Installation\n\nAdd this line to your application’s Gemfile:\n\n```ruby\ngem \"rollups\"\n```\n\nAnd run:\n\n```sh\nbundle install\nrails generate rollups\nrails db:migrate\n```\n\n## Contents\n\n- [Getting Started](#getting-started)\n- [Creating Rollups](#creating-rollups)\n- [Querying Rollups](#querying-rollups)\n- [Other Topics](#other-topics)\n- [Examples](#examples)\n\n## Getting Started\n\nStore the number of users created by day in the `rollups` table\n\n```ruby\nUser.rollup(\"New users\")\n```\n\nGet the series\n\n```ruby\nRollup.series(\"New users\")\n# {\n#   Wed, 01 Jan 2025 =\u003e 50,\n#   Thu, 02 Jan 2025 =\u003e 100,\n#   Fri, 03 Jan 2025 =\u003e 34\n# }\n```\n\nUse a rake task or background job to create rollups on a regular basis. Don’t worry too much about naming - you can [rename](#naming) later if needed.\n\n## Creating Rollups\n\n### Time Column\n\nSpecify the time column - `created_at` by default\n\n```ruby\nUser.rollup(\"New users\", column: :joined_at)\n```\n\nChange the default column for a model\n\n```ruby\nclass User \u003c ApplicationRecord\n  self.rollup_column = :joined_at\nend\n```\n\n### Time Intervals\n\nSpecify the interval - `day` by default\n\n```ruby\nUser.rollup(\"New users\", interval: \"week\")\n```\n\nAnd when querying\n\n```ruby\nRollup.series(\"New users\", interval: \"week\")\n```\n\nSupported intervals are:\n\n- hour\n- day\n- week\n- month\n- quarter\n- year\n\nOr any number of minutes or seconds:\n\n- 1m, 5m, 15m\n- 1s, 30s, 90s\n\nWeeks start on Sunday by default. Change this with:\n\n```ruby\nRollup.week_start = :monday\n```\n\n### Time Zones\n\nThe default time zone is `Time.zone`. Change this with:\n\n```ruby\nRollup.time_zone = \"Pacific Time (US \u0026 Canada)\"\n```\n\nor\n\n```ruby\nUser.rollup(\"New users\", time_zone: \"Pacific Time (US \u0026 Canada)\")\n```\n\nTime zone objects also work. To see a list of available time zones in Rails, run `rake time:zones:all`.\n\nSee [date storage](#date-storage) for how dates are stored.\n\n### Calculations\n\nRollups use `count` by default. For other calculations, use:\n\n```ruby\nOrder.rollup(\"Revenue\") { |r| r.sum(:revenue) }\n```\n\nWorks with `count`, `sum`, `minimum`, `maximum`, and `average`. For `median` and `percentile`, check out [ActiveMedian](https://github.com/ankane/active_median).\n\n### Dimensions\n\n*PostgreSQL only*\n\nCreate rollups with dimensions\n\n```ruby\nOrder.group(:platform).rollup(\"Orders by platform\")\n```\n\nWorks with multiple groups as well\n\n```ruby\nOrder.group(:platform, :channel).rollup(\"Orders by platform and channel\")\n```\n\nDimension names are determined by the `group` clause. To set manually, use:\n\n```ruby\nOrder.group(:channel).rollup(\"Orders by source\", dimension_names: [\"source\"])\n```\n\nSee how to [query dimensions](#multiple-series).\n\n### Updating Data\n\nWhen you run a rollup for the first time, the entire series is calculated. When you run it again, newer data is added.\n\nBy default, the latest interval stored for a series is recalculated, since it was likely calculated before the interval completed. Earlier intervals aren’t recalculated since the source rows may have been deleted (this also improves performance).\n\nTo recalculate the last few intervals, use:\n\n```ruby\nUser.rollup(\"New users\", last: 3)\n```\n\nTo recalculate a time range, use:\n\n```ruby\nUser.rollup(\"New users\", range: 1.week.ago.all_week)\n```\n\nTo only store data for completed intervals, use:\n\n```ruby\nUser.rollup(\"New users\", current: false)\n```\n\nTo clear and recalculate the entire series, use:\n\n```ruby\nUser.rollup(\"New users\", clear: true)\n```\n\nTo delete a series, use:\n\n```ruby\nRollup.where(name: \"New users\", interval: \"day\").delete_all\n```\n\n## Querying Rollups\n\n### Single Series\n\nGet a series\n\n```ruby\nRollup.series(\"New users\")\n```\n\nSpecify the interval if it’s not day\n\n```ruby\nRollup.series(\"New users\", interval: \"week\")\n```\n\nIf a series has dimensions, they must match exactly as well\n\n```ruby\nRollup.series(\"Orders by platform and channel\", dimensions: {platform: \"Web\", channel: \"Search\"})\n```\n\nGet a specific time range\n\n```ruby\nRollup.where(time: Date.current.all_year).series(\"New Users\")\n```\n\n### Multiple Series\n\n*PostgreSQL only*\n\nGet multiple series grouped by dimensions\n\n```ruby\nRollup.multi_series(\"Orders by platform\")\n```\n\nSpecify the interval if it’s not day\n\n```ruby\nRollup.multi_series(\"Orders by platform\", interval: \"week\")\n```\n\nFilter by dimensions\n\n```ruby\nRollup.where_dimensions(platform: \"Web\").multi_series(\"Orders by platform and channel\")\n```\n\nGet a specific time range\n\n```ruby\nRollup.where(time: Date.current.all_year).multi_series(\"Orders by platform\")\n```\n\n### Raw Data\n\nUses the `Rollup` model to query the data directly\n\n```ruby\nRollup.where(name: \"New users\", interval: \"day\")\n```\n\n### List\n\nList names and intervals\n\n```ruby\nRollup.list\n```\n\n### Charts\n\nRollup works great with [Chartkick](https://github.com/ankane/chartkick)\n\n```erb\n\u003c%= line_chart Rollup.series(\"New users\") %\u003e\n```\n\nFor multiple series, set a `name` for each series before charting\n\n```ruby\nseries = Rollup.multi_series(\"Orders by platform\")\nseries.each do |s|\n  s[:name] = s[:dimensions][\"platform\"]\nend\n```\n\n## Other Topics\n\n### Naming\n\nUse any naming convention you prefer. Some ideas are:\n\n- Human - `New users`\n- Underscore - `new_users`\n- Dots - `new_users.count`\n\nRename with:\n\n```ruby\nRollup.rename(\"Old name\", \"New name\")\n```\n\n### Date Storage\n\nRollup stores both dates and times in the `time` column depending on the interval. For date intervals (day, week, etc), it stores `00:00:00` for the time part. Cast the `time` column to a date when querying in SQL to get the correct value.\n\n- PostgreSQL: `time::date`\n- MySQL: `CAST(time AS date)`\n- SQLite: `date(time)`\n\n## Examples\n\n- [Ahoy](#ahoy)\n- [Searchjoy](#searchjoy)\n\n### Ahoy\n\nSet the default rollup column for your models\n\n```ruby\nclass Ahoy::Visit \u003c ApplicationRecord\n  self.rollup_column = :started_at\nend\n```\n\nand\n\n```ruby\nclass Ahoy::Event \u003c ApplicationRecord\n  self.rollup_column = :time\nend\n```\n\nHourly visits\n\n```ruby\nAhoy::Visit.rollup(\"Visits\", interval: \"hour\")\n```\n\nVisits by browser\n\n```ruby\nAhoy::Visit.group(:browser).rollup(\"Visits by browser\")\n```\n\nUnique homepage views\n\n```ruby\nAhoy::Event.where(name: \"Viewed homepage\").joins(:visit).rollup(\"Homepage views\") { |r| r.distinct.count(:visitor_token) }\n```\n\nProduct views\n\n```ruby\nAhoy::Event.where(name: \"Viewed product\").group_prop(:product_id).rollup(\"Product views\")\n```\n\n### Searchjoy\n\nDaily searches\n\n```ruby\nSearchjoy::Search.rollup(\"Searches\")\n```\n\nSearches by query\n\n```ruby\nSearchjoy::Search.group(:normalized_query).rollup(\"Searches by query\", dimension_names: [\"query\"])\n```\n\nConversion rate\n\n```ruby\nSearchjoy::Search.rollup(\"Search conversion rate\") { |r| r.average(\"(converted_at IS NOT NULL)::int\") }\n```\n\n## History\n\nView the [changelog](https://github.com/ankane/rollup/blob/master/CHANGELOG.md)\n\n## Contributing\n\nEveryone is encouraged to help improve this project. Here are a few ways you can help:\n\n- [Report bugs](https://github.com/ankane/rollup/issues)\n- Fix bugs and [submit pull requests](https://github.com/ankane/rollup/pulls)\n- Write, clarify, or fix documentation\n- Suggest or add new features\n\nTo get started with development:\n\n```sh\ngit clone https://github.com/ankane/rollup.git\ncd rollup\nbundle install\n\n# create databases\ncreatedb rollup_test\nmysqladmin create rollup_test\n\n# run tests\nbundle exec rake test\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankane%2Frollup","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fankane%2Frollup","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fankane%2Frollup/lists"}