{"id":13879818,"url":"https://github.com/rzane/baby_squeel","last_synced_at":"2025-05-15T11:00:15.485Z","repository":{"id":5741875,"uuid":"53811164","full_name":"rzane/baby_squeel","owner":"rzane","description":":pig: An expressive query DSL for Active Record","archived":false,"fork":false,"pushed_at":"2024-01-09T08:43:01.000Z","size":400,"stargazers_count":501,"open_issues_count":22,"forks_count":52,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-04-14T16:57:49.630Z","etag":null,"topics":["activerecord","arel","dsl","orm","query","sql","squeel"],"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/rzane.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}},"created_at":"2016-03-13T22:30:58.000Z","updated_at":"2025-04-12T19:07:08.000Z","dependencies_parsed_at":"2023-11-21T02:28:21.646Z","dependency_job_id":"f4a4a72f-03e7-4189-b3c1-14f33e6d6ff2","html_url":"https://github.com/rzane/baby_squeel","commit_stats":{"total_commits":318,"total_committers":10,"mean_commits":31.8,"dds":0.3176100628930818,"last_synced_commit":"515fe6816b6c31eee87babda2e54a2a0c1b6b9cc"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rzane%2Fbaby_squeel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rzane%2Fbaby_squeel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rzane%2Fbaby_squeel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rzane%2Fbaby_squeel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rzane","download_url":"https://codeload.github.com/rzane/baby_squeel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254328384,"owners_count":22052632,"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","dsl","orm","query","sql","squeel"],"created_at":"2024-08-06T08:02:34.533Z","updated_at":"2025-05-15T11:00:15.334Z","avatar_url":"https://github.com/rzane.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# BabySqueel 🐷\n\n![Build](https://github.com/rzane/baby_squeel/workflows/Build/badge.svg)\n![Version](https://img.shields.io/gem/v/baby_squeel)\n\nHave you ever used the [Squeel](https://github.com/activerecord-hackery/squeel) gem? It's a really nice way to build complex queries. However, Squeel monkeypatches Active Record internals, because it was aimed at enhancing the existing API with the aim of inclusion into Rails. However, that inclusion never happened, and it left Squeel susceptible to breakage from arbitrary changes in Active Record, eventually burning out the maintainer.\n\nBabySqueel provides a Squeel-like query DSL for Active Record while hopefully avoiding the majority of the version upgrade difficulties via a minimum of monkeypatching. :heart:\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'baby_squeel'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install baby_squeel\n\n## Introduction\n\nWith Active Record, you might write something like this:\n\n```ruby\nPost.where('created_at \u003e= ?', 2.weeks.ago)\n```\n\nBut then someone tells you, \"Hey, you should use Arel!\". So you convert your query to use Arel:\n\n```ruby\nPost.where(Post.arel_table[:created_at].gteq(2.weeks.ago))\n```\n\nWell, that's great, but it's also pretty verbose. Why don't you give BabySqueel a try:\n\n```ruby\nPost.where.has { created_at \u003e= 2.weeks.ago }\n```\n\n#### Quick note\n\nBabySqueel's blocks use `instance_eval`, which means you won't have access to your instance variables or methods. Don't worry, there's a really easy solution. Just give arity to the block:\n\n```ruby\nPost.where.has { |post| post.created_at \u003e= 2.weeks.ago }\n```\n\n## Usage\n\nOkay, so we have some models:\n\n```ruby\nclass Post \u003c ActiveRecord::Base\n  belongs_to :author\n  has_many :comments\nend\n\nclass Author \u003c ActiveRecord::Base\n  has_many :posts\n  has_many :comments, through: :posts\nend\n\nclass Comment \u003c ActiveRecord::Base\n  belongs_to :post\nend\n```\n\n##### Selects\n\n```ruby\nPost.selecting { (id + 5).as('id_plus_five') }\n# SELECT (\"posts\".\"id\" + 5) AS id_plus_five FROM \"posts\"\n\nPost.selecting { id.sum }\n# SELECT SUM(\"posts\".\"id\") FROM \"posts\"\n\nPost.joins(:author).selecting { [id, author.id] }\n# SELECT \"posts\".\"id\", \"authors\".\"id\" FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n```\n\n##### Wheres\n\n```ruby\nPost.where.has { title == 'My Post' }\n# SELECT \"posts\".* FROM \"posts\"\n# WHERE \"posts\".\"title\" = 'My Post'\n\nPost.where.has { title =~ 'My P%' }\n# SELECT \"posts\".* FROM \"posts\"\n# WHERE (\"posts\".\"title\" LIKE 'My P%')\n\nAuthor.where.has { (name =~ 'Ray%') \u0026 (id \u003c 5) | (name.lower =~ 'zane%') \u0026 (id \u003e 100) }\n# SELECT \"authors\".* FROM \"authors\"\n# WHERE (\"authors\".\"name\" LIKE 'Ray%' AND \"authors\".\"id\" \u003c 5 OR LOWER(\"authors\".\"name\") LIKE 'zane%' AND \"authors\".\"id\" \u003e 100)\n\nPost.joins(:author).where.has { author.name == 'Ray' }\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# WHERE \"authors\".\"name\" = 'Ray'\n\nPost.joins(author: :posts).where.has { author.posts.title =~ '%fun%' }\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# INNER JOIN \"posts\" \"posts_authors\" ON \"posts_authors\".\"author_id\" = \"authors\".\"id\"\n# WHERE (\"posts_authors\".\"title\" LIKE '%fun%')\n```\n\n##### Orders\n\n```ruby\nPost.ordering { [id.desc, title.asc] }\n# SELECT \"posts\".* FROM \"posts\"\n# ORDER BY \"posts\".\"id\" DESC, \"posts\".\"title\" ASC\n\nPost.ordering { (id * 5).desc }\n# SELECT \"posts\".* FROM \"posts\"\n# ORDER BY \"posts\".\"id\" * 5 DESC\n\nPost.select(:author_id).group(:author_id).ordering { id.count.desc }\n# SELECT \"posts\".\"author_id\" FROM \"posts\"\n# GROUP BY \"posts\".\"author_id\"\n# ORDER BY COUNT(\"posts\".\"id\") DESC\n\nPost.joins(:author).ordering { author.id.desc }\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# ORDER BY \"authors\".\"id\" DESC\n```\n\n##### Joins\n\n```ruby\nPost.joining { author }\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n\nPost.joining { [author.outer, comments] }\n# SELECT \"posts\".* FROM \"posts\"\n# LEFT OUTER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# INNER JOIN \"comments\" ON \"comments\".\"post_id\" = \"posts\".\"id\"\n\nPost.joining { author.comments }\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# INNER JOIN \"posts\" \"posts_authors_join\" ON \"posts_authors_join\".\"author_id\" = \"authors\".\"id\"\n# INNER JOIN \"comments\" ON \"comments\".\"post_id\" = \"posts_authors_join\".\"id\"\n\nPost.joining { author.outer.comments.outer }\n# SELECT \"posts\".* FROM \"posts\"\n# LEFT OUTER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# LEFT OUTER JOIN \"posts\" \"posts_authors_join\" ON \"posts_authors_join\".\"author_id\" = \"authors\".\"id\"\n# LEFT OUTER JOIN \"comments\" ON \"comments\".\"post_id\" = \"posts_authors_join\".\"id\"\n\nPost.joining { author.comments.outer }\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# LEFT OUTER JOIN \"posts\" \"posts_authors_join\" ON \"posts_authors_join\".\"author_id\" = \"authors\".\"id\"\n# LEFT OUTER JOIN \"comments\" ON \"comments\".\"post_id\" = \"posts_authors_join\".\"id\"\n\nPost.joining { author.outer.posts }\n# SELECT \"posts\".* FROM \"posts\"\n# LEFT OUTER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# INNER JOIN \"posts\" \"posts_authors\" ON \"posts_authors\".\"author_id\" = \"authors\".\"id\"\n\nPost.joining { author.on((author.id == author_id) | (author.name == title)) }\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON (\"authors\".\"id\" = \"posts\".\"author_id\" OR \"authors\".\"name\" = \"posts\".\"title\")\n\nPost.joining { |post| post.author.as('a').on { (id == post.author_id) | (name == post.title) } }\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" \"a\" ON (\"a\".\"id\" = \"posts\".\"author_id\" OR \"a\".\"name\" = \"posts\".\"title\")\n\nPicture.joining { imageable.of(Post) }\n# SELECT \"pictures\".* FROM \"pictures\"\n# INNER JOIN \"posts\" ON \"posts\".\"id\" = \"pictures\".\"imageable_id\" AND \"pictures\".\"imageable_type\" = 'Post'\n\nPicture.joining { imageable.of(Post).outer }\n# SELECT \"pictures\".* FROM \"pictures\"\n# LEFT OUTER JOIN \"posts\" ON \"posts\".\"id\" = \"pictures\".\"imageable_id\" AND \"pictures\".\"imageable_type\" = 'Post'\n```\n\n##### Grouping\n\n```ruby\nPost.selecting { id.count }.grouping { author_id }.when_having { id.count \u003e 5 }\n# SELECT COUNT(\"posts\".\"id\") FROM \"posts\"\n# GROUP BY \"posts\".\"author_id\"\n# HAVING (COUNT(\"posts\".\"id\") \u003e 5)\n```\n\n##### Functions\n\n```ruby\nPost.selecting { coalesce(author_id, 5).as('author_id_with_default') }\n# SELECT coalesce(\"posts\".\"author_id\", 5) AS author_id_with_default FROM \"posts\"\n```\n\n##### Subqueries\n\n```ruby\nPost.joins(:author).where.has {\n  author.id.in Author.select(:id).where(name: 'Ray')\n}\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# WHERE \"authors\".\"id\" IN (\n#   SELECT \"authors\".\"id\" FROM \"authors\"\n#   WHERE \"authors\".\"name\" = 'Ray'\n# )\n```\n\n##### Exists\n\n```ruby\nPost.where.has {\n  exists Post.where.has { author_id == 1 }\n}\n# SELECT \"posts\".* FROM \"posts\"\n# WHERE (\n#   EXISTS(\n#     SELECT \"posts\".* FROM \"posts\"\n#     WHERE \"posts\".\"author_id\" = 1\n#   )\n# )\n```\n\n##### Custom SQL Operators\n\n```ruby\nauthors = Author.selecting { name.op('||', quoted('-dizzle')).as('swag') }\n# SELECT \"authors\".\"name\" || '-dizzle' AS swag FROM \"authors\"\n\nauthors.first.swag #=\u003e 'Ray Zane-dizzle'\n```\n\n##### Querying tables without Active Record models\n\n```ruby\ntable = BabySqueel[:some_table]\n\nPost.joining {\n  table.on(table.post_id == id)\n}.where.has {\n  table.some_column == 1\n}\n```\n\n##### Polymorphism\n\nGiven this polymorphism:\n\n```ruby\n# app/models/picture.rb\nbelongs_to :imageable, polymorphic: true\n\n# app/models/post.rb\nhas_many :pictures, as: :imageable\n```\n\nThe query might look like this:\n\n```ruby\nPicture.\n  joining { imageable.of(Post) }.\n  selecting { imageable.of(Post).id }\n```\n\n##### Helpers\n\n```ruby\n# SQL Literals\nPost.select('1 as one').ordering { sql('one').desc }\n\n# Quoting\nPost.selecting { title.op('||', quoted('diddly')) }\n\n# Functions\nPost.selecting { func('coalesce', id, 1) }\n```\n\n## Sifters\n\nSifters are like little snippets of conditions that can take arguments.\n\n```ruby\nclass Post \u003c ActiveRecord::Base\n  sifter :funny do\n    title == 'rabies'\n  end\nend\n\nclass Author \u003c ActiveRecord::Base\n  sifter :name_contains do |string|\n    name =~ \"%#{string}%\"\n  end\nend\n\nPost.joins(:author).where.has {\n  sift(:funny) | author.sift(:name_contains, 'blergh')\n}\n# SELECT \"posts\".* FROM \"posts\"\n# INNER JOIN \"authors\" ON \"authors\".\"id\" = \"posts\".\"author_id\"\n# WHERE (\"posts\".\"title\" = 'rabies' OR \"authors\".\"name\" LIKE '%blergh%')\n```\n\n## What's what?\n\nThe following methods give you access to BabySqueel's DSL:\n\n| BabySqueel    | Active Record Equivalent |\n| ------------- | ------------------------ |\n| `selecting`   | `select`                 |\n| `ordering`    | `order`                  |\n| `joining`     | `joins`                  |\n| `grouping`    | `group`                  |\n| `where.has`   | `where`                  |\n| `when_having` | `having`                 |\n\n## Migrating from Squeel\n\nCheck out the [migration guide](https://github.com/rzane/baby_squeel/wiki/Migrating-from-Squeel).\n\n## Development\n\n1. Pick an Active Record version to develop against, then export it: `export AR=6.1.4`.\n2. Run `bin/setup` to install dependencies.\n3. Run `rake` to run the specs.\n\nYou can also run `bin/console` to open up a prompt where you'll have access to some models to experiment with.\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/rzane/baby_squeel.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frzane%2Fbaby_squeel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frzane%2Fbaby_squeel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frzane%2Fbaby_squeel/lists"}