ActiveRecord で arel_table を使い易くするライブラリ arel-helpers の README.md のサンプルを試しに動かしてみた。
公式
camertron/arel-helpers
https://github.com/camertron/arel-helpers
試した環境
% ./bin/rake about
About your application's environment
Ruby version 2.1.3-p242 (x86_64-linux)
RubyGems version 2.2.2
Rack version 1.5
Rails version 4.1.8
JavaScript Runtime Node.js (V8)
Active Record version 4.1.8
Action Pack version 4.1.8
Action View version 4.1.8
Action Mailer version 4.1.8
Active Support version 4.1.8
準備
適当なプロジェクトを作成。
% rails new ahexample --skip-bundle && cd ahexample && bundle install --path=vendor/bundle
Gemfile に以下を追記して再度 bundle install。
gem 'arel-helpers'
arel-helpers 2.0.1 がインストールされた。
サンプルのテーブル構造はライブラリ本体の spec から拝借した。
arel-helpers の spec に含まれるテストテーブル作成用の migrations.rb とテスト用モデルクラスが定義された models.rb を lib 以下にコピー。
% cp vendor/bundle/ruby/2.1.0/gems/arel-helpers-2.0.1/spec/env/* ./lib
このままだと README.md のサンプルが上手く動作しないので migrations.rb の方を以下のように修正した。
# encoding: UTF-8
class CreatePostsTable < ActiveRecord::Migration
def change
create_table :posts do |t|
t.column :title, :string
t.column :created_at, :datetime
t.references :author
end
end
end
class CreateCommentsTable < ActiveRecord::Migration
def change
create_table :comments do |t|
t.references :post
t.references :author
end
end
end
class CreateAuthorsTable < ActiveRecord::Migration
def change
create_table :authors do |t|
t.references :collab_posts
t.column :username, :string
end
end
end
class CreateFavoritesTable < ActiveRecord::Migration
def change
create_table :favorites do |t|
t.references :post
end
end
end
class CreateCollabPostsTable < ActiveRecord::Migration
def change
create_table :collab_posts do |t|
t.references :authors
end
end
end
rails console 起動。
% ./bin/rails c
コピーしたファイルを読み込む。
> require_relative './lib/models'
=> true
> require_relative './lib/migrations'
=> true
以下を実行してテーブル作成。
> CreatePostsTable.new.change
> CreateCommentsTable.new.change
> CreateAuthorsTable.new.change
> CreateFavoritesTable.new.change
> CreateCollabPostsTable.new.change
これで準備は完了。
使ってみる
arel-helpers を使うと Post.arel_table[:id]
のような記述が Post[:id]
と書けるようになる。
> Post.where(Post[:id].eq(1))
Post Load (0.9ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = 1
=> #<ActiveRecord::Relation []>
これで毎回 arel_table
と書かなくて良くなるので、OR なんか書く時はかなりすっきりする。
> Post.where(Post[:id].eq(1).or(Post[:id].eq(2)))
Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE (("posts"."id" = 1 OR "posts"."id" = 2))
=> #<ActiveRecord::Relation []>
ArelHelpers.join_association
を使う事で、ActiveRecord + arel_table だと複雑になってしまうような JOIN がシンプルに書ける。
> Post.joins(ArelHelpers.join_association(Post, :comments))
Post Load (0.1ms) SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id"
=> #<ActiveRecord::Relation []>
単純な INNER JOIN なら普通に ActiveRecord で JOIN した方が良さそう。
LEFT OUTER JOIN はいい感じ。
> Post.joins(ArelHelpers.join_association(Post, :comments, Arel::OuterJoin))
Post Load (0.2ms) SELECT "posts".* FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id"
=> #<ActiveRecord::Relation []>
JOIN に複雑な条件を指定したい場合は join_association
にブロックを渡して指定する。
> Post.joins(
* ArelHelpers.join_association(Post, :comments, Arel::OuterJoin) do |assoc_name, join_conditions|
* join_conditions.and(Post[:author_id].eq(4))
> end
> )
Post Load (0.2ms) SELECT "posts".* FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" AND "posts"."author_id" = 4
=> #<ActiveRecord::Relation []>
いちいち ArelHelpers.join_association
って書くのが面倒な場合は、Model 内で以下のように ArelHelpers::JoinAssociation
を include する事で直接 Model のメソッドとして join_association
を呼べるようになる。~
class Post < ActiveRecord::Base
include ArelHelpers::ArelTable
include ArelHelpers::JoinAssociation
has_many :comments
has_many :favorites
end
これで先ほどのクエリは以下のように書けるようになる。
> Post.joins(
* Post.join_association(:comments, Arel::OuterJoin) do |assoc_name, join_conditions|
* join_conditions.and(Post[:author_id].eq(4))
> end
> )
Post Load (0.1ms) SELECT "posts".* FROM "posts" LEFT OUTER JOIN "comments" ON "comments"."post_id" = "posts"."id" AND "posts"."author_id" = 4
=> #<ActiveRecord::Relation []>
検索画面で使う検索用クエリなどのように、より細かく複雑な条件を指定したクエリを組み立てたい場合のために ArelHelpers::QueryBuilder
というクラスが提供されている。
lib 以下に ArelHelpers::QueryBuilder
を継承した以下のようなクラスを作成。
class PostQueryBuilder < ArelHelpers::QueryBuilder
def initialize(query = nil)
# whatever you want your initial query to be
super(query || post.unscoped)
end
def with_title_matching(title)
reflect(
query.where(post[:title].matches("%#{title}%"))
)
end
def with_comments_by(usernames)
reflect(
query
.joins(:comments => :author)
.where(author[:username].in(usernames))
)
end
def since_yesterday
reflect(
query.where(post[:created_at].gteq(Date.yesterday))
)
end
private
def author
Author
end
def post
Post
end
end
作成したら rails console 上で読み込む。
> require_relative "./lib/post_query_builder"
=> true
以下のような感じで使う。
> PostQueryBuilder.new.with_comments_by(['camertron', 'catwithtail']).with_title_matching("arel rocks").since_yesterday
Post Load (0.1ms) SELECT "posts".* FROM "posts" INNER JOIN "comments" ON "comments"."post_id" = "posts"."id" INNER JOIN "authors" ON "authors"."id" = "comments"."author_id" WHERE "authors"."username" IN ('camertron', 'catwithtail') AND ("posts"."title" LIKE '%arel rocks%') AND ("posts"."created_at" >= '2014-12-03')
=> #<PostQueryBuilder:0x007f7d74790088 @query=#<ActiveRecord::Relation []>>
ArelHelpers::QueryBuilder
を継承したクラスを作成する事で複雑なクエリの組み立てもシンプルに記述できるようになる。
実際の案件などで複雑なクエリを find_by_sql
を使わずに書こうとするとかなり arel_table
を多用するハメになるので、ちょっとすっきり書けるだけでもかなり有用なライブラリだと思う。
シンプルで覚える事も少ないので、導入してもそれほど混乱を招くことも無さそうなのもいい。