{"id":25487832,"url":"https://github.com/kholdrex/simple_query","last_synced_at":"2025-04-09T22:11:40.499Z","repository":{"id":278110895,"uuid":"934389775","full_name":"kholdrex/simple_query","owner":"kholdrex","description":"A lightweight, multi-DB-friendly, and high-performance query builder for ActiveRecord, featuring streaming, bulk updates, and read-model support.","archived":false,"fork":false,"pushed_at":"2025-03-15T22:43:49.000Z","size":63,"stargazers_count":7,"open_issues_count":1,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-09T22:11:36.295Z","etag":null,"topics":["activerecord","arel","database","orm","performance","query-builder","rails","ruby","sql"],"latest_commit_sha":null,"homepage":"https://github.com/kholdrex/simple_query","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/kholdrex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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}},"created_at":"2025-02-17T18:45:14.000Z","updated_at":"2025-03-15T22:43:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"cd20d716-0277-42dd-b078-e1764f22aaa3","html_url":"https://github.com/kholdrex/simple_query","commit_stats":null,"previous_names":["kholdrex/simple_query"],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kholdrex%2Fsimple_query","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kholdrex%2Fsimple_query/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kholdrex%2Fsimple_query/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kholdrex%2Fsimple_query/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kholdrex","download_url":"https://codeload.github.com/kholdrex/simple_query/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119294,"owners_count":21050755,"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","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","arel","database","orm","performance","query-builder","rails","ruby","sql"],"created_at":"2025-02-18T20:28:11.755Z","updated_at":"2025-04-09T22:11:40.492Z","avatar_url":"https://github.com/kholdrex.png","language":"Ruby","readme":"# SimpleQuery\n\nSimpleQuery is a lightweight and efficient query builder for ActiveRecord, designed to provide a flexible and performant way to construct complex database queries in Ruby on Rails applications.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'simple_query'\n```\n\nAnd then execute:\n```bash\nbundle install\n```\n\nOr install it yourself as:\n```bash\ngem install simple_query\n```\n\n## Configuration\n\nBy default, `SimpleQuery` does **not** automatically patch `ActiveRecord::Base`. You can **manually** include the module in individual models or in a global initializer:\n\n```ruby\n# Manual include (per model)\nclass User \u003c ActiveRecord::Base\n  include SimpleQuery\nend\n\n# or do it globally\nActiveRecord::Base.include(SimpleQuery)\n```\nIf you prefer a “just works” approach (i.e., every model has `.simple_query`), you can opt in:\n\n```ruby\n# config/initializers/simple_query.rb\nSimpleQuery.configure do |config|\n  config.auto_include_ar = true\nend\n```\n\nThis tells SimpleQuery to automatically do `ActiveRecord::Base.include(SimpleQuery)` for you.\n\n## Usage\n\nSimpleQuery offers an intuitive interface for building queries with joins, conditions, and aggregations. Here are some examples:\n\nBasic query\n```ruby\nUser.simple_query.select(:name, :email).where(active: true).execute\n```\n\nQuery with join\n\nSimpleQuery now supports **all major SQL join types** — including LEFT, RIGHT, and FULL — through the following DSL methods:\n```ruby\nUser.simple_query\n    .left_join(:users, :companies, foreign_key: :user_id, primary_key: :id)\n    .select(\"users.name\", \"companies.name\")\n    .execute\n```\n\nComplex query with multiple joins and conditions\n```ruby\nUser.simple_query\n    .select(:name)\n    .join(:users, :companies, foreign_key: :user_id, primary_key: :id)\n    .join(:companies, :projects, foreign_key: :company_id, primary_key: :id)\n    .where(Company.arel_table[:industry].eq(\"Technology\"))\n    .where(Project.arel_table[:status].eq(\"active\"))\n    .where(User.arel_table[:admin].eq(true))\n    .execute\n```\n\nLazy execution\n```ruby\nUser.simple_query\n    .select(:name)\n    .where(active: true)\n    .lazy_execute\n```\n\nPlaceholder-Based Conditions\n\nSimpleQuery now supports **ActiveRecord-style placeholders**, letting you pass arrays with `?` or `:named` placeholders to your `.where` clauses:\n\n```ruby\n# Positional placeholders:\nUser.simple_query\n    .where([\"name LIKE ?\", \"%Alice%\"])\n    .execute\n\n# Named placeholders:\nUser.simple_query\n    .where([\"email = :email\", { email: \"alice@example.com\" }])\n    .execute\n\n# Multiple placeholders in one condition:\nUser.simple_query\n    .where([\"age \u003e= :min_age AND age \u003c= :max_age\", { min_age: 18, max_age: 35 }])\n    .execute\n```\n\n## Custom Read Models\nBy default, SimpleQuery returns results as `Struct` objects for maximum speed. However, you can also define a lightweight model class for more explicit attribute handling or custom logic.\n\n**Create a read model** inheriting from `SimpleQuery::ReadModel`:\n```ruby\nclass MyUserReadModel \u003c SimpleQuery::ReadModel\n  attribute :identifier, column: :id\n  attribute :full_name,  column: :name\nend\n```\n\n**Map query results** to your read model:\n```ruby\nresults = User.simple_query\n              .select(\"users.id AS id\", \"users.name AS name\")\n              .where(active: true)\n              .map_to(MyUserReadModel)\n              .execute\n\nresults.each do |user|\n  puts user.identifier    # =\u003e user.id from the DB\n  puts user.full_name     # =\u003e user.name from the DB\nend\n```\nThis custom read model approach provides more clarity or domain-specific logic while still being faster than typical ActiveRecord instantiation.\n\n## Named Scopes\nSimpleQuery now supports named scopes, allowing you to reuse common query logic in a style similar to ActiveRecord’s built-in scopes. To define a scope, use the simple_scope class method in your model:\n```ruby\nclass User \u003c ActiveRecord::Base\n  include SimpleQuery\n\n  simple_scope :active do\n    where(active: true)\n  end\n\n  simple_scope :admins do\n    where(admin: true)\n  end\n\n  # Block-based scope with parameter\n  simple_scope :by_name do |name|\n    where(name: name)\n  end\n\n  # Lambda-based scope with parameter\n  simple_scope :by_name, -\u003e(name) { where(name: name) }\nend\n```\nYou can then chain these scopes seamlessly with the normal SimpleQuery DSL:\n\n```ruby\n# Parameterless scopes\nresults = User.simple_query.active.admins.execute\n\n# Parameterized scope\nresults = User.simple_query.by_name(\"Jane Doe\").execute\n\n# Mixing scopes with other DSL calls\nresults = User.simple_query\n              .by_name(\"John\")\n              .active\n              .select(:id, :name)\n              .order(name: :asc)\n              .execute\n```\n### How It Works\n\nEach scope block (e.g. by_name) is evaluated in the context of the SimpleQuery builder, so you can call any DSL method (where, order, etc.) inside it.\nParameterized scopes accept arguments — passed directly to the block (e.g. |name| above).\nScopes return self, so you can chain multiple scopes or mix them with standard query methods.\n\n## Streaming Large Datasets\n\nFor massive queries (millions of rows), **SimpleQuery** offers a `.stream_each` method to avoid loading the entire result set into memory. It **automatically** picks a streaming approach depending on your database adapter:\n\n- **PostgreSQL**: Uses a **server-side cursor** via `DECLARE ... FETCH`.\n- **MySQL**: Uses `mysql2` gem’s **streaming** (`stream: true, cache_rows: false, as: :hash`).\n\n```ruby\n# Example usage:\nUser.simple_query\n    .where(active: true)\n    .stream_each(batch_size: 10_000) do |row|\n  # row is a struct or read-model instance\n  puts row.name\nend\n```\n\n## Performance\n\nSimpleQuery aims to outperform standard ActiveRecord queries at scale. We’ve benchmarked **1,000,000** records on **both PostgreSQL** and **MySQL**, with the following results:\n\n### PostgreSQL (1,000,000 records)\n```\n🚀 Performance Results (1000,000 records):\nActiveRecord Query:                  10.36932 seconds\nSimpleQuery Execution (Struct):      3.46136 seconds\nSimpleQuery Execution (Read model):  2.20905 seconds\n\n----------------------------------------------------\nActiveRecord find_each:              6.10077 seconds\nSimpleQuery stream_each:             2.75639 seconds\n\n--- AR find_each Memory Report ---\nTotal allocated: 1.98 GB (16,001,659 objects)\nRetained:        ~2 KB\n\n--- SimpleQuery stream_each Memory Report ---\nTotal allocated: 1.38 GB (8,000,211 objects)\nRetained:        ~3 KB\n```\n- **Struct-based** approach remains the fastest, skipping model overhead.\n- **Read model** approach is still significantly faster than standard ActiveRecord while allowing domain-specific logic.\n\n### MySQL (1,000,000 records)\n```\n🚀 Performance Results (1000,000 records):\nActiveRecord Query:                  10.45833 seconds\nSimpleQuery Execution (Struct):      3.04655 seconds\nSimpleQuery Execution (Read model):  3.69052 seconds\n\n----------------------------------------------------\nActiveRecord find_each:              5.04671 seconds\nSimpleQuery stream_each:             2.96602 seconds\n\n--- AR find_each Memory Report ---\nTotal allocated: 1.32 GB (11,001,445 objects)\nRetained:        ~2.7 KB\n\n--- SimpleQuery stream_each Memory Report ---\nTotal allocated: 1.22 GB (8,000,068 objects)\nRetained:        ~3.9 KB\n```\n- Even in MySQL, **Struct** was roughly **three times faster** than ActiveRecord’s overhead.\n- Read models still outperform AR, though by a narrower margin in this scenario.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/kholdrex/simple_query. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/kholdrex/simple_query/blob/master/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the SimpleQuery project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/kholdrex/simple_query/blob/master/CODE_OF_CONDUCT.md).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkholdrex%2Fsimple_query","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkholdrex%2Fsimple_query","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkholdrex%2Fsimple_query/lists"}