ActiveRecordのjoinsで全カラムを取得する方法 (Rails小ネタ)
ActiveRecordではjoinsメソッドを利用して、inner joinすることができる。
例:
Blog.joins(:article) #=> SELECT "blogs".* FROM "blogs" INNER JOIN "articles" ON "articles"."blog_id" = "blogs"."id"
ここで、問題になるのは"blogs".*である。articlesの情報はとれていない。
Includes Vs Joins in Rails - Nikhil Lingutla
よって、上記で解説されているように、.selectをチェーンしてやらないといけない。
極めて単純な解決策としては、以下のscopeを作成してやれば良い。
scope :joins_get_all_columns, ->(*tables) {
joins(*tables).select("*")
}
作成されるSQLは下記。
Blog.joins_get_all_columns(:article) #=> SELECT * FROM "blogs" INNER JOIN "articles" ON "articles"."blog_id" = "blogs"."id"
この方法には、同じカラム名があると、どちらかで上書きされる問題がある。
もう少し頑張って、下記のようなスコープを作成すれば、カラム名の重複は避けられると思われる。
scope :joins_get_all_columns, ->(*tables) {
select_stmt = [%{"#{self.table_name}".*}] # joinするテーブルは通常通りのカラム名
select_stmt << tables.map{|t|
ar = ActiveRecord::Base.const_get(t.to_s.classify.singularize)
table_name = ar.table_name
table_name_single = table_name.singularize
ar.attribute_names.map{|col|
%{"#{table_name}".#{col} as #{table_name_single}_#{col}} # "table名_column名"
}
}
joins(*tables).select(select_stmt.flatten.join(","))
}
作成されるSQLは下記。
Blog.joins_get_all_columns(:article) #=> SELECT "blogs".*,"articles".id as article_id,"articles".content as article_content,"articles".created_at as article_created_at,"articles".updated_at as article_updated_at FROM "blogs" INNER JOIN "articles" ON "articles"."blog_id" = "blogs"."id"
最後に、LEFT OUTER JOIN対応版。
scope :joins_get_all_columns, ->(*tables,outer) {
outer_join= outer && []
my_table = self.table_name
select_stmt = [%{"#{my_table}".*}]
select_stmt << tables.map{|t|
ar = ActiveRecord::Base.const_get(t.to_s.classify.singularize)
table_name = ar.table_name
table_name_single = table_name.singularize
outer_join << %{LEFT OUTER JOIN "#{table_name}" ON "#{table_name}"."#{my_table.singularize}_id" = "#{my_table}"."id"} if outer
ar.attribute_names.map{|col|
%{"#{table_name}"."#{col}" as "#{table_name_single}_#{col}"}
}
}
joins(outer_join || tables).select(select_stmt.flatten.join(","))
}
Blog.joins_get_all_columns(:article,false) #=> inner join
Blog.joins_get_all_columns(:article,true) #=> left outer join
Railsでアプリを作ってると、Railsをいじるのが楽しくなって脱線してしまうから困る。