{"id":45157325,"url":"https://github.com/paradedb/rails-paradedb","last_synced_at":"2026-04-14T22:01:19.603Z","repository":{"id":337356550,"uuid":"1148371866","full_name":"paradedb/rails-paradedb","owner":"paradedb","description":"Rails Gem to extend ActiveRecord for ParadeDB","archived":false,"fork":false,"pushed_at":"2026-04-08T16:32:12.000Z","size":516,"stargazers_count":9,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-08T18:29:21.304Z","etag":null,"topics":["activerecord","orm","paradedb","ruby"],"latest_commit_sha":null,"homepage":"","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/paradedb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","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":"AGENTS.md","dco":null,"cla":null},"funding":{"github":["paradedb"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2026-02-02T22:18:16.000Z","updated_at":"2026-04-08T16:32:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/paradedb/rails-paradedb","commit_stats":null,"previous_names":["paradedb/rails-paradedb"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/paradedb/rails-paradedb","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paradedb%2Frails-paradedb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paradedb%2Frails-paradedb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paradedb%2Frails-paradedb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paradedb%2Frails-paradedb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/paradedb","download_url":"https://codeload.github.com/paradedb/rails-paradedb/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/paradedb%2Frails-paradedb/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31817128,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T18:05:02.291Z","status":"ssl_error","status_checked_at":"2026-04-14T18:05:01.765Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["activerecord","orm","paradedb","ruby"],"created_at":"2026-02-20T05:01:44.323Z","updated_at":"2026-04-14T22:01:19.568Z","avatar_url":"https://github.com/paradedb.png","language":"Ruby","funding_links":["https://github.com/sponsors/paradedb"],"categories":[],"sub_categories":[],"readme":"\u003c!-- ParadeDB: Postgres for Search and Analytics --\u003e\n\u003ch1 align=\"center\"\u003e\n  \u003ca href=\"https://paradedb.com\"\u003e\u003cimg src=\"https://github.com/paradedb/paradedb/raw/main/docs/logo/readme.svg\" alt=\"ParadeDB\"\u003e\u003c/a\u003e\n\u003cbr\u003e\n\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eSimple, Elastic-quality search for Postgres\u003c/b\u003e\u003cbr/\u003e\n\u003c/p\u003e\n\n\u003ch3 align=\"center\"\u003e\n  \u003ca href=\"https://paradedb.com\"\u003eWebsite\u003c/a\u003e \u0026bull;\n  \u003ca href=\"https://docs.paradedb.com\"\u003eDocs\u003c/a\u003e \u0026bull;\n  \u003ca href=\"https://paradedb.com/slack/\"\u003eCommunity\u003c/a\u003e \u0026bull;\n  \u003ca href=\"https://paradedb.com/blog/\"\u003eBlog\u003c/a\u003e \u0026bull;\n  \u003ca href=\"https://docs.paradedb.com/changelog/\"\u003eChangelog\u003c/a\u003e\n\u003c/h3\u003e\n\n---\n\n# rails-paradedb\n\n[![Gem Version](https://img.shields.io/gem/v/rails-paradedb)](https://rubygems.org/gems/rails-paradedb)\n[![Ruby Requirement](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Frubygems.org%2Fapi%2Fv1%2Fversions%2Frails-paradedb.json\u0026query=%24%5B0%5D.ruby_version\u0026label=ruby\u0026logo=ruby)](https://rubygems.org/gems/rails-paradedb)\n[![Gem Downloads](https://img.shields.io/gem/dt/rails-paradedb)](https://rubygems.org/gems/rails-paradedb)\n[![Codecov](https://codecov.io/gh/paradedb/rails-paradedb/graph/badge.svg)](https://codecov.io/gh/paradedb/rails-paradedb)\n[![License](https://img.shields.io/github/license/paradedb/rails-paradedb?color=blue)](https://github.com/paradedb/rails-paradedb?tab=MIT-1-ov-file#readme)\n[![Slack URL](https://img.shields.io/badge/Join%20Slack-purple?logo=slack\u0026link=https%3A%2F%2Fparadedb.com%2Fslack)](https://paradedb.com/slack)\n[![X URL](https://img.shields.io/twitter/url?url=https%3A%2F%2Ftwitter.com%2Fparadedb\u0026label=Follow%20%40paradedb)](https://x.com/paradedb)\n\nThe official Ruby client for [ParadeDB](https://paradedb.com), built for ActiveRecord.\nUse Elastic-quality full-text search, scoring, snippets, facets, and aggregations directly from Rails.\n\n## Features\n\n- BM25 index management in Rails migrations (`create_paradedb_index`, `remove_bm25_index`, `reindex_bm25`)\n- Chainable ActiveRecord search API (`matching_all`, `matching_any`, `term`, `phrase`, `regex`, `near`, `parse`, and more)\n- Relevance and highlighting (`with_score`, `with_snippet`, `with_snippets`, `with_snippet_positions`)\n- Facets and aggregations (`with_facets`, `facets`, `with_agg`, `facets_agg`, `aggregate_by`)\n- More Like This similarity search (`more_like_this`)\n- Arel integration for advanced query composition with native ParadeDB operators\n- Diagnostics helpers and rake tasks for index health and verification\n- Optional runtime index validation to detect missing/drifted BM25 indexes\n\n## Requirements \u0026 Compatibility\n\n| Component  | Supported                                        |\n| ---------- | ------------------------------------------------ |\n| Ruby       | 3.2+                                             |\n| Rails      | 7.2+                                             |\n| ParadeDB   | 0.22.0+                                          |\n| PostgreSQL | 15+ (PostgreSQL adapter with ParadeDB extension) |\n\nNotes:\n\n- CI runs Ruby `3.2` through `4.0` across Rails `7.2` and `8.1` on PostgreSQL `18`.\n- Schema compatibility is checked against every ParadeDB release.\n- The maintained minimum ParadeDB version is `0.22.0`; update `README.md`, `RELEASE.md`, and CI in the same PR whenever that floor changes.\n\n## Installation\n\n```ruby\ngem \"rails-paradedb\"\n```\n\n```bash\nbundle install\n```\n\n## Quick Start\n\n### Prerequisites\n\nMake sure your Rails app uses PostgreSQL and that `pg_search` is installed in the target database:\n\n```sql\nCREATE EXTENSION IF NOT EXISTS pg_search;\n```\n\n### 1. Define Your Model and Index\n\n```ruby\nclass MockItem \u003c ActiveRecord::Base\n  include ParadeDB::Model\n\n  self.table_name = \"mock_items\"\n  self.primary_key = \"id\"\nend\n\nclass MockItemIndex \u003c ParadeDB::Index\n  self.table_name = :mock_items\n  self.key_field = :id\n  self.index_name = :search_idx\n  self.fields = {\n    id: nil,\n    description: nil,\n    category: nil,\n    rating: nil,\n    in_stock: nil,\n    created_at: nil,\n    metadata: nil,\n    weight_range: nil\n  }\nend\n```\n\n### 2. Create the BM25 Index in a Migration\n\n```ruby\nclass AddMockItemBm25Index \u003c ActiveRecord::Migration[7.2] # use your app's migration version\n  def up\n    create_paradedb_index(MockItemIndex, if_not_exists: true)\n  end\n\n  def down\n    remove_bm25_index :mock_items, name: :search_idx, if_exists: true\n  end\nend\n```\n\n### 3. Search\n\n```ruby\nMockItem.search(:description).matching_all(\"running shoes\")\nMockItem.search(:description).matching_any(\"wireless\", \"bluetooth\")\nMockItem.search(:description).term(\"electronics\")\n```\n\n## Query API\n\n```ruby\n# Full text\nMockItem.search(:description).matching_all(\"running shoes\")\nMockItem.search(:description).matching_any(\"wireless bluetooth\")\n\n# Query-time tokenizer override\nMockItem.search(:description).matching_any(\"running shoes\", tokenizer: \"whitespace\")\nMockItem.search(:description).matching_any(\"running shoes\", tokenizer: \"whitespace('lowercase=false')\")\n\n# Fuzzy options on match/term\n# Note: tokenizer overrides are mutually exclusive with fuzzy options.\nMockItem.search(:description).matching_any(\"runing shose\", distance: 1)\nMockItem.search(:description).matching_all(\"runing\", distance: 1, prefix: true)\nMockItem.search(:description).term(\"shose\", distance: 1, transposition_cost_one: true)\n\n# Other query types\nMockItem.search(:description).phrase(\"running shoes\", slop: 2)\nMockItem.search(:description).phrase(\"running shoes\", tokenizer: \"whitespace\")\nMockItem.search(:description).phrase(%w[running shoes])\nMockItem.search(:description).regex(\"run.*\")\nMockItem.search(:description).near(ParadeDB.proximity(\"running\").within(3, \"shoes\"))\nMockItem.search(:description).near(ParadeDB.proximity(\"running\").within(3, \"shoes\", ordered: true))\nMockItem.search(:description).near(ParadeDB.proximity(\"hiking\", \"running\").within(2, \"shoes\"))\nMockItem.search(:description).near(ParadeDB.proximity(\"running\").within(2, \"shoes\", \"sneakers\", ordered: true))\nMockItem.search(:description).near(ParadeDB.regex_term(\"run.*\").within(3, \"shoes\"))\nMockItem.search(:description).near(ParadeDB.proximity(\"trail\").within(1, \"running\").within(1, \"shoes\"))\nMockItem.search(:description).near(ParadeDB.proximity(\"running\").within(3, \"shoes\"), boost: 2.0)\nMockItem.search(:description).near(ParadeDB.proximity(\"running\").within(3, \"shoes\"), const: 1.0)\nMockItem.search(:description).regex_phrase(\"run.*\", \"shoes\")\nMockItem.search(:description).phrase_prefix(\"run\", \"sh\", max_expansion: 100)\nMockItem.search(:description).parse(\"running AND shoes\", lenient: true)\n\n# Match-all / exists / ranges\nMockItem.search(:id).match_all\nMockItem.search(:id).exists\nMockItem.search(:rating).range(gte: 3, lt: 5)\nMockItem.search(:weight_range).range_term(\"(10, 12]\", relation: \"Intersects\")\n\n# Similarity\nMockItem.more_like_this(42, fields: [:description])\n```\n\n## Scoring and Highlighting\n\n```ruby\nresults = MockItem.search(:description)\n                 .matching_all(\"shoes\")\n                 .with_score\n                 .order(search_score: :desc)\n\nMockItem.search(:description)\n       .matching_all(\"shoes\")\n       .with_snippet(:description, start_tag: \"\u003cb\u003e\", end_tag: \"\u003c/b\u003e\", max_chars: 80)\n\nMockItem.search(:description)\n       .matching_all(\"running\")\n       .with_snippets(:description, max_chars: 15, limit: 2, offset: 0, sort_by: :position)\n\nMockItem.search(:description)\n       .matching_all(\"running\")\n       .with_snippet_positions(:description)\n```\n\n## Facets and Aggregations\n\n```ruby\n# Rows + facets (requires order + limit)\nrelation = MockItem.search(:description)\n                  .matching_all(\"shoes\")\n                  .with_facets(:category, size: 10)\n                  .order(:id)\n                  .limit(10)\n\nrows = relation.to_a\nfacets = relation.facets\n\n# Facets-only aggregate\nMockItem.search(:description).matching_all(\"shoes\").facets(:category)\n\n# Named aggregations\nMockItem.search(:description).matching_all(\"shoes\").facets_agg(\n  docs: ParadeDB::Aggregations.value_count(:id),\n  avg_rating: ParadeDB::Aggregations.avg(:rating)\n)\n\n# Window aggregations + rows\nMockItem.search(:description).matching_all(\"shoes\").with_agg(\n  exact: false,\n  docs: ParadeDB::Aggregations.value_count(:id),\n  stats: ParadeDB::Aggregations.stats(:rating)\n).order(:id).limit(10)\n\n# Grouped aggregations\nMockItem.search(:id).match_all.aggregate_by(\n  :category,\n  docs: ParadeDB::Aggregations.value_count(:id)\n)\n```\n\nIf you group by text/JSON fields, index those fields using `:literal` or `:literal_normalized`.\n\n## ActiveRecord and Arel Composition\n\nUse ParadeDB conditions with normal ActiveRecord scopes:\n\n```ruby\nMockItem.search(:description)\n        .matching_all(\"shoes\")\n        .where(in_stock: true)\n        .where(MockItem.arel_table[:rating].gteq(4))\n        .order(created_at: :desc)\n```\n\nFor advanced SQL composition, ParadeDB operators are also available through Arel predications:\n\n```ruby\nt = MockItem.arel_table\nMockItem.where(t[:description].pdb_match(\"running shoes\"))\n```\n\n## Diagnostics Helpers\n\nRuby helpers:\n\n```ruby\nParadeDB.paradedb_indexes\nParadeDB.paradedb_index_segments(\"search_idx\")\nParadeDB.paradedb_verify_index(\"search_idx\", sample_rate: 0.1)\nParadeDB.paradedb_verify_all_indexes(index_pattern: \"search_idx\")\n```\n\nAvailability depends on the installed `pg_search` version.\n\nRepository development tasks (from this repo's `Rakefile`):\n\n```bash\nrake paradedb:diagnostics:indexes\nrake \"paradedb:diagnostics:index_segments[search_idx]\"\nrake \"paradedb:diagnostics:verify_index[search_idx]\" SAMPLE_RATE=0.1\nrake paradedb:diagnostics:verify_all_indexes INDEX_PATTERN=search_idx\n```\n\n## Index Validation\n\nBy default, index validation is disabled. You can enable runtime checks globally:\n\n```ruby\n# config/initializers/paradedb.rb\nParadeDB.index_validation_mode = :warn  # :warn, :raise, or :off\n```\n\nWhen enabled, `rails-paradedb` validates that the expected BM25 index exists and can raise\n`ParadeDB::IndexDriftError` or `ParadeDB::IndexClassNotFoundError` depending on mode.\n\n## Common Errors\n\n### \"No search field set. Call .search(column) first.\"\n\n```ruby\n# ❌ Missing .search(...)\nMockItem.matching_all(\"shoes\")\n\n# ✅ Start with .search(column)\nMockItem.search(:description).matching_all(\"shoes\")\n```\n\n### \"with_facets requires ORDER BY and LIMIT\"\n\n```ruby\n# ❌ Missing order/limit\nMockItem.search(:description).matching_all(\"shoes\").with_facets(:category).to_a\n\n# ✅ Include both\nrelation = MockItem.search(:description)\n                   .matching_all(\"shoes\")\n                   .with_facets(:category)\n                   .order(:id)\n                   .limit(10)\nrelation.to_a\nrelation.facets\n```\n\n### \"search(:field) is not indexed\"\n\n```ruby\n# ❌ Field not in your ParadeDB::Index fields hash\nMockItem.search(:title).matching_all(\"shoes\")\n\n# ✅ Add :title to the index definition, then migrate\n```\n\n## Security\n\n`rails-paradedb` builds SQL through Arel nodes and quoted literals (`Arel::Nodes.build_quoted`)\nrather than manual string interpolation. Tokenizer expressions are validated and search operators are\nrendered through typed nodes, with unit and integration coverage for quoting and edge cases.\n\n## Examples\n\n- [Quick Start](examples/quickstart/quickstart.rb)\n- [Faceted Search](examples/faceted_search/faceted_search.rb)\n- [Autocomplete](examples/autocomplete/autocomplete.rb)\n- [More Like This](examples/more_like_this/more_like_this.rb)\n- [Hybrid RRF](examples/hybrid_rrf/hybrid_rrf.rb)\n- [RAG](examples/rag/rag.rb)\n- [Examples README](examples/README.md)\n\n## Documentation\n\n- **ParadeDB Official Docs**: \u003chttps://docs.paradedb.com\u003e\n- **ParadeDB Website**: \u003chttps://paradedb.com\u003e\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for development setup, test commands, linting, and PR workflow.\n\n## Support\n\nIf you're missing a feature or found a bug, open a\n[GitHub Issue](https://github.com/paradedb/rails-paradedb/issues/new/choose).\n\nFor community support:\n\n- Join the [ParadeDB Slack Community](https://paradedb.com/slack)\n- Ask in [ParadeDB Discussions](https://github.com/paradedb/paradedb/discussions)\n\nFor commercial support, contact [sales@paradedb.com](mailto:sales@paradedb.com).\n\n## Acknowledgments\n\nWe would like to thank the following members of the community for their valuable feedback and reviews during the development of this package:\n\n- [Eric Barendt](https://github.com/ebarendt) - Engineering at Modern Treasury\n- [Matthew Higgins](https://github.com/matthuhiggins) - Engineering at Modern Treasury\n- [Patrick Schmitz](https://github.com/bullfight) - Engineering at Modern Treasury\n\n## License\n\nrails-paradedb is licensed under the [MIT License](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparadedb%2Frails-paradedb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparadedb%2Frails-paradedb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparadedb%2Frails-paradedb/lists"}