備忘録的な何か

技術ブログ的な何かです

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をいじるのが楽しくなって脱線してしまうから困る。