This is a fork of baby_squeel. I aim to keep it compatible with Rails and publish my commits in case others find them useful. However, I do not recommend using baby_squeel or this fork. I plan to remove functions which I don’t need from this project without providing deprecations.
Release
update lib/baby_squeel/version.rb
update CHANGELOG.md
$ git add .
$ git commit -m "release x.y.z.internalA"
$ gem build baby_squeel.gemspec
upload gem to own gemserver
Have you ever used the 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.
BabySqueel provides a Squeel-like query DSL for Active Record while hopefully avoiding the majority of the version upgrade difficulties via a minimum of monkeypatching. ❤️
Add this line to your application's Gemfile:
gem 'baby_squeel'And then execute:
$ bundle
Or install it yourself as:
$ gem install baby_squeel
With Active Record, you might write something like this:
Post.where('created_at >= ?', 2.weeks.ago)But then someone tells you, "Hey, you should use Arel!". So you convert your query to use Arel:
Post.where(Post.arel_table[:created_at].gteq(2.weeks.ago))Well, that's great, but it's also pretty verbose. Why don't you give BabySqueel a try:
Post.where.has { created_at.gteq(2.weeks.ago) }BabySqueel'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:
Post.where.has { |post| post.created_at.gteq(2.weeks.ago) }Okay, so we have some models:
class Post < ActiveRecord::Base
belongs_to :author
has_many :comments
end
class Author < ActiveRecord::Base
has_many :posts
has_many :comments, through: :posts
end
class Comment < ActiveRecord::Base
belongs_to :post
endPost.where.has { title.eq('My Post') }
# SELECT "posts".* FROM "posts"
# WHERE "posts"."title" = 'My Post'
Post.where.has { title.matches('My P%') }
# SELECT "posts".* FROM "posts"
# WHERE ("posts"."title" LIKE 'My P%')
Author.where.has { name.matches('Ray%').and(id.lt(5)).or(name.lower.matches('zane%').and(id.gt(100))) }
# SELECT "authors".* FROM "authors"
# WHERE ("authors"."name" LIKE 'Ray%' AND "authors"."id" < 5 OR LOWER("authors"."name") LIKE 'zane%' AND "authors"."id" > 100)
Post.joins(:author).where.has { author.name.eq('Ray') }
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."name" = 'Ray'
Post.joins(author: :posts).where.has { author.posts.title.matches('%fun%') }
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# INNER JOIN "posts" "posts_authors" ON "posts_authors"."author_id" = "authors"."id"
# WHERE ("posts_authors"."title" LIKE '%fun%')Post.joining { author }
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
Post.joining { [author.outer, comments] }
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
Post.joining { author.comments }
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# INNER JOIN "posts" "posts_authors_join" ON "posts_authors_join"."author_id" = "authors"."id"
# INNER JOIN "comments" ON "comments"."post_id" = "posts_authors_join"."id"
Post.joining { author.outer.comments.outer }
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# LEFT OUTER JOIN "posts" "posts_authors_join" ON "posts_authors_join"."author_id" = "authors"."id"
# LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts_authors_join"."id"
Post.joining { author.comments.outer }
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# LEFT OUTER JOIN "posts" "posts_authors_join" ON "posts_authors_join"."author_id" = "authors"."id"
# LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts_authors_join"."id"
Post.joining { author.outer.posts }
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# INNER JOIN "posts" "posts_authors" ON "posts_authors"."author_id" = "authors"."id"
Post.joining { author.on(author.id.eq(author_id).or(author.name.eq(title))) }
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON ("authors"."id" = "posts"."author_id" OR "authors"."name" = "posts"."title")
Post.joining { |post| post.author.as('a').on { id.eq(post.author_id).or(name.eq(post.title)) } }
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" "a" ON ("a"."id" = "posts"."author_id" OR "a"."name" = "posts"."title")
Picture.joining { imageable.of(Post) }
# SELECT "pictures".* FROM "pictures"
# INNER JOIN "posts" ON "posts"."id" = "pictures"."imageable_id" AND "pictures"."imageable_type" = 'Post'
Picture.joining { imageable.of(Post).outer }
# SELECT "pictures".* FROM "pictures"
# LEFT OUTER JOIN "posts" ON "posts"."id" = "pictures"."imageable_id" AND "pictures"."imageable_type" = 'Post'Post.joins(:author).where.has {
author.id.in Author.select(:id).where(name: 'Ray')
}
# SELECT "posts".* FROM "posts"
# INNER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IN (
# SELECT "authors"."id" FROM "authors"
# WHERE "authors"."name" = 'Ray'
# )Given this polymorphism:
# app/models/picture.rb
belongs_to :imageable, polymorphic: true
# app/models/post.rb
has_many :pictures, as: :imageableThe query might look like this:
Picture.joining { imageable.of(Post) }The following methods give you access to BabySqueel's DSL:
| BabySqueel | Active Record Equivalent |
|---|---|
joining |
joins |
where.has |
where |
- Pick an Active Record version to develop against, then export it:
export AR=7.2.2. - Run
bin/setupto install dependencies. - Run
raketo run the specs.
Onliner to run the specs with different rails versions
export AR='~> 7.2.3'; rm Gemfile.lock; bin/setup; rake
export AR='~> 8.0.4'; rm Gemfile.lock; bin/setup; rake
export AR='~> 8.1.1'; rm Gemfile.lock; bin/setup; rake
export AR='main'; rm Gemfile.lock; bin/setup; rake
You can also run bin/console to open up a prompt where you'll have access to some models to experiment with.
- Update baby_squeel.gemspec
- Add the version to test matrix build.yml
- Update development section in the README.md
- If you need to change the code consider to add a version check methode in version_helper.rb
- Run the specs with all supported versions
- Add comment to the unreleased section in CHANGELOG.md
Bug reports and pull requests are welcome on GitHub at https://github.com/rocket-turtle/baby_squeel.
The gem is available as open source under the terms of the MIT License.