ankane/groupdate
The simplest way to group temporal data
Healthy across all four use cases
Permissive license, no critical CVEs, actively maintained — safe to depend on.
Has a license, tests, and CI — clean foundation to fork and modify.
Documented and popular — useful reference codebase to read through.
No critical CVEs, sane security posture — runnable as-is.
- ✓Last commit 5w ago
- ✓2 active contributors
- ✓MIT licensed
Show 4 more →Show less
- ✓CI configured
- ✓Tests present
- ⚠Small team — 2 contributors active in recent commits
- ⚠Single-maintainer risk — top contributor 99% of recent commits
Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests
Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.
Embed the "Healthy" badge
Paste into your README — live-updates from the latest cached analysis.
[](https://repopilot.app/r/ankane/groupdate)Paste at the top of your README.md — renders inline like a shields.io badge.
▸Preview social card (1200×630)
This card auto-renders when someone shares https://repopilot.app/r/ankane/groupdate on X, Slack, or LinkedIn.
Onboarding doc
Onboarding: ankane/groupdate
Generated by RepoPilot · 2026-05-10 · Source
🤖Agent protocol
If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:
- Verify the contract. Run the bash script in Verify before trusting
below. If any check returns
FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding. - Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
- Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/ankane/groupdate shows verifiable citations alongside every claim.
If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.
🎯Verdict
GO — Healthy across all four use cases
- Last commit 5w ago
- 2 active contributors
- MIT licensed
- CI configured
- Tests present
- ⚠ Small team — 2 contributors active in recent commits
- ⚠ Single-maintainer risk — top contributor 99% of recent commits
<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>
✅Verify before trusting
This artifact was generated by RepoPilot at a point in time. Before an
agent acts on it, the checks below confirm that the live ankane/groupdate
repo on your machine still matches what RepoPilot saw. If any fail,
the artifact is stale — regenerate it at
repopilot.app/r/ankane/groupdate.
What it runs against: a local clone of ankane/groupdate — the script
inspects git remote, the LICENSE file, file paths in the working
tree, and git log. Read-only; no mutations.
| # | What we check | Why it matters |
|---|---|---|
| 1 | You're in ankane/groupdate | Confirms the artifact applies here, not a fork |
| 2 | License is still MIT | Catches relicense before you depend on it |
| 3 | Default branch master exists | Catches branch renames |
| 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code |
| 5 | Last commit ≤ 65 days ago | Catches sudden abandonment since generation |
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of ankane/groupdate. If you don't
# have one yet, run these first:
#
# git clone https://github.com/ankane/groupdate.git
# cd groupdate
#
# Then paste this script. Every check is read-only — no mutations.
set +e
fail=0
ok() { echo "ok: $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }
# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
echo "FAIL: not inside a git repository. cd into your clone of ankane/groupdate and re-run."
exit 2
fi
# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "ankane/groupdate(\\.git)?\\b" \\
&& ok "origin remote is ankane/groupdate" \\
|| miss "origin remote is not ankane/groupdate (artifact may be from a fork)"
# 2. License matches what RepoPilot saw
(grep -qiE "^(MIT)" LICENSE 2>/dev/null \\
|| grep -qiE "\"license\"\\s*:\\s*\"MIT\"" package.json 2>/dev/null) \\
&& ok "license is MIT" \\
|| miss "license drift — was MIT at generation time"
# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
&& ok "default branch master exists" \\
|| miss "default branch master no longer exists"
# 4. Critical files exist
test -f "lib/groupdate.rb" \\
&& ok "lib/groupdate.rb" \\
|| miss "missing critical file: lib/groupdate.rb"
test -f "lib/groupdate/active_record.rb" \\
&& ok "lib/groupdate/active_record.rb" \\
|| miss "missing critical file: lib/groupdate/active_record.rb"
test -f "lib/groupdate/adapters/base_adapter.rb" \\
&& ok "lib/groupdate/adapters/base_adapter.rb" \\
|| miss "missing critical file: lib/groupdate/adapters/base_adapter.rb"
test -f "lib/groupdate/series_builder.rb" \\
&& ok "lib/groupdate/series_builder.rb" \\
|| miss "missing critical file: lib/groupdate/series_builder.rb"
test -f "lib/groupdate/relation.rb" \\
&& ok "lib/groupdate/relation.rb" \\
|| miss "missing critical file: lib/groupdate/relation.rb"
# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 65 ]; then
ok "last commit was $days_since_last days ago (artifact saw ~35d)"
else
miss "last commit was $days_since_last days ago — artifact may be stale"
fi
echo
if [ "$fail" -eq 0 ]; then
echo "artifact verified (0 failures) — safe to trust"
else
echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/ankane/groupdate"
exit 1
fi
Each check prints ok: or FAIL:. The script exits non-zero if
anything failed, so it composes cleanly into agent loops
(./verify.sh || regenerate-and-retry).
⚡TL;DR
Groupdate is a Ruby gem that simplifies temporal data aggregation in Rails applications by providing an intuitive API to group records by time periods (day, week, month, hour, etc.) with full timezone and daylight saving time support. It abstracts away database-specific SQL generation for PostgreSQL, MySQL, MariaDB, SQLite, and Redshift, returning results as ordered Hash objects ready for charting. Single-gem monolithic structure: lib/groupdate/active_record.rb injects query methods into Rails models; lib/groupdate/adapters/{base,mysql,postgresql,sqlite}_adapter.rb contain database-specific SQL builders; lib/groupdate/enumerable.rb and lib/groupdate/relation.rb handle in-memory and ActiveRecord::Relation grouping respectively. lib/groupdate/magic.rb wires up dynamic methods like group_by_day(:field).
👥Who it's for
Rails developers building analytics dashboards, reporting interfaces, and time-series visualizations who need to aggregate user activity, events, or metrics by day/week/month without writing raw SQL or managing timezone complexity manually.
🌱Maturity & risk
Production-ready and actively maintained. The gem has 93,747 lines of Ruby code, a comprehensive test suite across 5+ database adapters (test/adapters/), CI/CD via GitHub Actions (.github/workflows/build.yml), and supports multiple ActiveRecord versions (gemfiles/ with AR 7.2 and 8.0). The CHANGELOG.md indicates ongoing development with periodic updates.
Low risk for core functionality but some factors to monitor: single maintainer (ankane pattern evident across repos), no visible major open issues in the provided data, but timezone edge cases (DST transitions) could surface in fringe locales. Dependencies are minimal—primarily ActiveRecord itself. Redshift support (gemfiles/redshift.gemfile) suggests some users rely on it for data warehouse workloads, so breaking changes impact them.
Active areas of work
The repo appears actively maintained with support for modern Rails versions (7.2, 8.0 in gemfiles/). CI is green. No specific recent PR/milestone data visible in the file list, but the structured test coverage and multi-adapter support indicate ongoing quality assurance.
🚀Get running
git clone https://github.com/ankane/groupdate.git
cd groupdate
bundle install
bundle exec rake test
Daily commands:
For development: bundle exec rake test runs the full test suite against configured adapters. No server to run; this is a library. Tests are data-integration tests that require configured databases (see test/support/activerecord.rb). Individual test files in test/ can be run with ruby -Ilib:test test/basic_test.rb.
🗺️Map of the codebase
lib/groupdate.rb— Entry point that loads all groupdate components and exposes the public API; every contributor must understand the module structure.lib/groupdate/active_record.rb— Core ActiveRecord integration that adds group_by_* methods to relations; this is the primary user-facing interface.lib/groupdate/adapters/base_adapter.rb— Abstract adapter defining the SQL generation contract that all database backends must implement.lib/groupdate/series_builder.rb— Generates complete time series with zero-filled gaps; critical for the 'get entire series' feature.lib/groupdate/relation.rb— Query methods layer that builds and executes grouped queries; handles result transformation and time zone logic.lib/groupdate/magic.rb— Metaprogramming that dynamically creates group_by_day, group_by_week, etc. methods from time period definitions.
🧩Components & responsibilities
- Groupdate module (lib/groupdate.rb) (Ruby module system) — Loads submodules and establishes the public API namespace.
- Failure mode: Circular requires or missing submodule breaks the entire gem.
- Magic (lib/groupdate/magic.rb) (Ruby metaprogramming (define_method, method_missing)) — Defines TIME_PERIOD_NAMES and dynamically generates group_by_* methods for both ActiveRecord and Enumerable.
- Failure mode: Bug in period definitions or method generation breaks all group_by_* calls.
- ActiveRecord integration (lib/groupdate/active_record.rb) (ActiveRecord, monkey-patching) — Patches ActiveRecord::Relation to respond to group_by_* methods; orchestrates adapter selection.
- Failure mode: Breaks if ActiveRecord API changes; adapter detection failure silently falls
🛠️How to make changes
Add support for a new time period (e.g., group_by_quarter)
- Define the period in lib/groupdate/magic.rb by adding a hash entry to TIME_PERIOD_NAMES with format strings for each adapter (
lib/groupdate/magic.rb) - Add format string for PostgreSQL in lib/groupdate/adapters/postgresql_adapter.rb mapping the period to DATE_TRUNC or date_format logic (
lib/groupdate/adapters/postgresql_adapter.rb) - Add corresponding format string for MySQL in lib/groupdate/adapters/mysql_adapter.rb using DATE_FORMAT (
lib/groupdate/adapters/mysql_adapter.rb) - Implement the same in SQLite using STRFTIME in lib/groupdate/adapters/sqlite_adapter.rb (
lib/groupdate/adapters/sqlite_adapter.rb) - Add test cases in test/basic_test.rb to verify grouping works correctly across all adapters (
test/basic_test.rb)
Add support for a new database backend (e.g., Snowflake)
- Create a new adapter file lib/groupdate/adapters/snowflake_adapter.rb inheriting from BaseAdapter (
lib/groupdate/adapters/base_adapter.rb) - Implement the format_sql method and apply_time_zone method for Snowflake date/time functions (
lib/groupdate/adapters/snowflake_adapter.rb) - Register the adapter in lib/groupdate/active_record.rb by adding Snowflake detection in the adapter selection logic (
lib/groupdate/active_record.rb) - Create test file test/adapters/snowflake.rb with basic grouping tests (
test/adapters/snowflake.rb)
Add a custom grouping time period for in-memory enumeration
- Update lib/groupdate/enumerable.rb to add a case handler in the grouping logic that parses your custom period string (
lib/groupdate/enumerable.rb) - Implement the time bucketing logic using Ruby Time#strftime or custom date arithmetic (
lib/groupdate/enumerable.rb) - Add test coverage in test/enumerable_test.rb (
test/enumerable_test.rb)
🔧Why these technologies
- Metaprogramming (define_method, method_missing) — Eliminates code duplication across 20+ time periods by generating group_by_day, group_by_week, etc. dynamically from a single time period hash.
- Adapter pattern (PostgreSQL, MySQL, SQLite, Redshift) — Each database has different date/time functions (DATE_TRUNC vs DATE_FORMAT vs STRFTIME); adapters abstract these differences behind a common interface.
- ActiveRecord monkey-patching (lib/groupdate/active_record.rb) — Seamless integration with Rails; users call group_by_day directly on relations without explicit module imports.
- SeriesBuilder for gap-filling — Generates zero-filled complete time series (the 'entire series' feature); critical for charting libraries like Chartkick.
⚖️Trade-offs already made
-
Metaprogramming for group_by_ method generation*
- Why: Reduces boilerplate significantly and keeps maintenance centralized.
- Consequence: Makes debugging harder (methods not statically visible); contributors must understand magic.rb to add new periods.
-
Database-specific adapters instead of ORM abstraction layers
- Why: Database time functions are fundamentally different; a generic layer would be overly complex.
- Consequence: Adding a new database requires a new adapter file; no auto-discovery mechanism.
-
Timezone handling in Ruby (lib/groupdate/relation.rb) rather than SQL
- Why: Consistent behavior across all databases; handles daylight saving time correctly.
- Consequence: May require post-processing of results; larger memory footprint for large result sets.
-
Support for in-memory enumeration (arrays/hashes) via same group_by_ interface*
- Why: Familiar API for testing; works with non-ActiveRecord data sources.
- Consequence: Two separate code paths (DB adapter vs enumerable); must keep logic in sync.
🚫Non-goals (don't propose these)
- Does not provide custom filtering or aggregation functions beyond count/sum/avg/etc. (those are handled by ActiveRecord).
- Does not support nested grouping (e.g., group by day AND by category in a single call; must use separate group clauses).
- Does not provide real-time streaming or incremental updates; designed for historical batch queries.
- Does not handle time series forecasting or interpolation beyond zero-filling gaps.
- Does not support non-temporal grouping (use ActiveRecord's group method directly).
🪤Traps & gotchas
Timezone math complexity: Day boundaries and week starts are calculated per time_zone; changing Groupdate.time_zone globally affects all queries. Daylight saving transitions can cause off-by-one-hour issues if not tested in target time zones. Adapter mismatch: Each database (PostgreSQL, MySQL, SQLite) has different date/time function names and behavior—a query working on Postgres may silently produce wrong results on MySQL if the adapter isn't tested. Range expansion gotcha: expand_range: true only fills gaps if the result set is empty for a bucket; partial data isn't zero-filled. Week start config: Groupdate.week_start is global; no per-query override exists, only per-method override.
🏗️Architecture
💡Concepts to learn
- SQL Date Truncation & Bucketing — Groupdate abstracts database-specific functions (DATE_TRUNC for Postgres, DATE_FORMAT for MySQL) to partition timestamps into buckets; understanding how each DB natively truncates dates is crucial to debugging adapter bugs.
- Timezone-Aware Time Arithmetic — Rails' Time.zone API applies timezone offsets and DST rules after database queries return UTC; Groupdate must convert bucket boundaries into the target timezone before SQL generation to ensure correct grouping across day boundaries.
- Adapter Pattern / Strategy Pattern — lib/groupdate/adapters/ uses the adapter pattern to swap database-specific SQL generation at runtime; understanding this is essential for adding Redshift, MariaDB, or other backends.
- Metaprogramming & Dynamic Method Definition — lib/groupdate/magic.rb uses define_method to dynamically create group_by_day, group_by_week, etc. methods; modifying the DSL requires understanding Ruby's method_missing and define_method patterns.
- Daylight Saving Time (DST) Handling — During DST transitions, days are 23 or 25 hours long; Groupdate must handle bucketing queries that span these transitions without double-counting or missing records. This is non-trivial and adapter-specific.
- Series Completion / Fill Gaps — lib/groupdate/series_builder.rb fills missing time buckets with zero values when expand_range is true; useful for charts where gaps would be misleading (e.g., no orders on a date still shows 0, not omitted).
- ActiveRecord Relation Composition — Groupdate wraps and extends ActiveRecord::Relation chains (e.g., User.where(...).group_by_day(:created_at)); understanding relation scoping and lazy evaluation is needed to avoid N+1 queries or premature execution.
🔗Related repos
ankane/chartkick— Official companion gem by the same author; Groupdate results feed directly into Chartkick for time-series visualization without transformation.ankane/active_median— Complementary gem for computing median and percentile aggregates on grouped data (Groupdate supports count/sum/avg but not percentiles natively).rails/rails— Groupdate is a Rails plugin that extends ActiveRecord; understanding Rails' relation and time zone APIs is essential.ankane/blazer— Another ankane project for building SQL-based dashboards; often used with Groupdate for backend data aggregation in analytics platforms.scenic-views/scenic— Database view management gem; useful when you want to materialize Groupdate queries as database views for performance.
🪄PR ideas
To work on one of these in Claude Code or Cursor, paste:
Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.
Add comprehensive tests for Trilogy adapter
The repo supports multiple database adapters (MySQL, PostgreSQL, SQLite, Redshift) with corresponding test files in test/adapters/, but test/adapters/trilogy.rb exists without a corresponding test runner integration. Trilogy is an increasingly popular MySQL client for Rails. Adding proper test coverage would ensure groupdate works reliably with this adapter.
- [ ] Implement test suite in test/adapters/trilogy.rb with core grouping operations (by_day, by_hour, by_week, etc.)
- [ ] Add trilogy to the Gemfile test group or create a gemfiles/trilogy.gemfile
- [ ] Integrate trilogy adapter tests into .github/workflows/build.yml CI pipeline
- [ ] Verify all grouping functions work with timezone support in trilogy adapter
Add async/concurrent query support tests and documentation
test/async_test.rb exists in the test suite, suggesting async support is implemented, but there's no documentation in README.md about how to use async grouping queries. This is a valuable feature for Rails 7+ async query support that new users won't discover.
- [ ] Document async grouping usage in README.md with code examples (e.g., User.group_by_day(:created_at).count.load_async)
- [ ] Add examples for chaining async queries with groupdate
- [ ] Expand test/async_test.rb with edge cases (error handling, cancellation, timeout scenarios)
- [ ] Add async usage examples to CONTRIBUTING.md for maintainability
Add integration tests for multiple group operations across adapters
test/multiple_group_test.rb exists but likely tests only one adapter. The file structure shows separate adapter test files (test/adapters/mysql.rb, postgresql.rb, etc.) but no systematic cross-adapter testing for complex operations like multiple grouping with time zones and series filling.
- [ ] Extend test/multiple_group_test.rb to parametrize tests across all 5 adapters (MySQL, PostgreSQL, SQLite, Redshift, Trilogy)
- [ ] Add specific tests for multiple_group with day_start and week_start options across adapters
- [ ] Add tests combining group_by_day with group_by_hour_of_day across different timezones per adapter
- [ ] Verify series filling and zero-filling works correctly in test/zeros_test.rb for all adapters
🌿Good first issues
- Add support for nanosecond-precision grouping (currently supports second as minimum) by extending lib/groupdate/adapters/base_adapter.rb with a :nanosecond case and updating lib/groupdate/magic.rb to register group_by_nanosecond.
- Write comprehensive test coverage for DST transitions in test/day_start_test.rb and test/week_start_test.rb; currently they exist but lack edge cases like 2 AM day_start during spring-forward transitions in multiple time zones.
- Implement result caching/memoization in lib/groupdate/relation.rb to avoid re-executing identical group_by queries within the same request; add a :cache option to group_by_* methods.
📝Recent commits
Click to expand
Recent commits
6255e75— Version bump to 6.8.0 [skip ci] (ankane)8780dfa— Dropped support for Ruby < 3.3 and Active Record < 7.2 (ankane)520f223— Fixed CI (ankane)4019dc5— Test with Ruby 4.0 on CI (ankane)c6b0e06— Updated Gemfile [skip ci] (ankane)e82664f— Fixed warning with profile task [skip ci] (ankane)3ff2c2a— Switched to ActiveRecord::Schema.define [skip ci] (ankane)c924a21— Updated Gemfile for Windows (ankane)762b9dc— Test with Active Record 8.1.0 on CI (ankane)91652c3— Updated test setup [skip ci] (ankane)
🔒Security observations
The groupdate gem is a data grouping library with moderate security concerns. The primary risks stem from dynamic SQL query construction in database adapters (High severity), time zone handling complexity (Medium), and lack of input validation for grouping parameters (Medium). The codebase appears to be a popular, well-maintained library, but static analysis reveals missing input validation safeguards and potential SQL injection vectors that should be addressed. No hardcoded credentials or obvious misconfigurations were detected. Recommend conducting thorough code review of adapter implementations and adding comprehensive input validation and parameterization checks.
- High · SQL Injection Risk in Dynamic Query Construction —
lib/groupdate/adapters/ (mysql_adapter.rb, postgresql_adapter.rb, sqlite_adapter.rb). The codebase appears to construct SQL queries dynamically based on grouping parameters (day, week, hour, etc.) across multiple database adapters (MySQL, PostgreSQL, SQLite). Without visible input validation or parameterization in the adapter files, there is a potential risk of SQL injection if user-supplied grouping parameters are not properly sanitized. Fix: Ensure all SQL query construction uses parameterized queries or prepared statements. Validate and sanitize all user inputs before incorporating them into SQL queries. Review adapter implementations to confirm proper use of ActiveRecord's query building methods. - Medium · Potential Time Zone Handling Vulnerability —
lib/groupdate/series_builder.rb, lib/groupdate/magic.rb. The gem heavily relies on time zone support including daylight saving time calculations. Improper time zone handling in series_builder.rb or magic.rb could lead to incorrect date calculations or bypass of business logic that depends on accurate temporal data, potentially allowing attackers to manipulate time-based access controls or data filtering. Fix: Implement comprehensive testing for edge cases around DST transitions and time zone boundaries. Ensure time zone conversions use well-tested libraries. Document and validate assumptions about time zone handling. - Medium · Missing Input Validation on Grouping Parameters —
lib/groupdate/query_methods.rb, lib/groupdate/relation.rb. The query_methods.rb and relation.rb files likely accept grouping interval parameters (second, minute, hour, day, week, etc.) that are used to construct queries. There is no visible validation that these parameters are from an allowed whitelist, which could lead to unexpected query behavior or performance issues. Fix: Implement strict whitelist validation for all accepted grouping intervals. Reject any parameters not explicitly defined as valid. Log rejected parameters for security monitoring. - Medium · Lack of Rate Limiting on Grouping Operations —
lib/groupdate/active_record.rb, lib/groupdate/enumerable.rb. The gem provides grouping functionality that could be exploited to trigger expensive database queries through repeated calls with various grouping parameters. No visible rate limiting mechanism exists to prevent abuse. Fix: Implement rate limiting at the application level. Consider adding query complexity analysis to prevent overly expensive grouping operations. Document performance implications for large datasets. - Low · Potential XSS Risk in Chartkick Integration —
README.md, documentation context. The README mentions integration with Chartkick for visualization. While the gem itself is backend-focused, improper handling of results in frontend code could lead to XSS vulnerabilities if group keys (which may contain user data) are rendered without proper escaping. Fix: Document security best practices for frontend integration. Ensure that any example code or documentation emphasizes proper output encoding when rendering grouped results in HTML/JavaScript contexts.
LLM-derived; treat as a starting point, not a security audit.
👉Where to read next
- Open issues — current backlog
- Recent PRs — what's actively shipping
- Source on GitHub
Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.