Help us understand the problem. What is going on with this article?

Ruby on Rails歴1年半のビギナーが最近覚えたデータの抽出(参照)方法

More than 1 year has passed since last update.

前提

Ruby on Railsを使い始めて、1年半ほど経ちました。
今まではモデル数が5つ以下のシンプルなアプリしか触ってこなかったのですが、最近10以上のモデル数のアプリを触ることが多くなり、手詰まり感を感じ始めてきました。

今回取り上げるテーマ

すぐ書ける? 条件に沿ったデータ抽出の書き方

知っている人はこのメソッドで取得すれば一発!なのですが知らないことで、データの抽出だけに多大な時間をかけてしまい、、その上結局ガリガリとSQLを書くという可読性の低いコードができてしまい、、なんともRailsの良いとこを全く生かしていない書き方になってしまっていました。
その状況を脱するために最近学んだことをまとめておくことにしました。

とりあえずcurrent_user使えばいいでしょ。今までの知識レベル

データ抽出を書く場面に出くわした場合、チームの他の人のコードを疑いもなく真似し、例えばログイン中のユーザーのコメント一覧を取得するというお題の場合、特に意識することもなく

  • current_user.comments という書き方
  • Comment.where(user_id: current_user.id) という書き方

という感じでとりあえず取りたいデータが取れればいーじゃんということにしか意識が向いていませんでした。

参照方法によってパフォーマンスに影響を与えるなんて考えもせず、とりあえず取りたいデータが取れればいーじゃんということにしか意識が向いていませんでした。

仮にこの発展形で、コメントに子となるテーブルが存在してるようなレコードを抽出してそれを1つづつ表示する・・・というケースの場合に、書き方によってはN+1 問題につながるというか、そもそもN+1も言葉しか知らないレベルでした

そんなレベルだと前述の通り、複数テーブルのデータをいい感じに表示したい状況になった場合、うーんと頭を抱えてしまい数日が過ぎていたのです。。あいたた〜|д・)

最近学んだこと

ということで、最近学んだ抽出方法を例を使いながら挙げていきます。

サンプルアプリはこちら

モデル一覧

  • 都道府県一覧:Prefectureモデル
  • 市区一覧:Cityモデル
  • 町村一覧:Townモデル

アソシエーション(関連)

class Prefecture < ActiveRecord::Base
  has_many :cities
end
class City < ActiveRecord::Base
  belongs_to :prefecture
  has_many :towns
end
class Town < ActiveRecord::Base
  belongs_to :city
end

1. 親子関連を定義しているデータの抽出

親から子のデータを取得する場合

Q. 東京都(id=13)に属するCity一覧を取りたい

Prefecture.find(13).cities

Q. 千代田区(City.first, id=13101)に属するTown一覧を取りたい

City.first.towns

Q. 東京都(id=13)に属するTown一覧を取りたい場合

city_ids = City.where(prefecture_id: 13).pluck(:id)
Town.where(city_id: city_ids)

考え方: まず東京都(id=13)に属するCity一覧を取得し、次に各Cityに紐づくTownを取得する。
ActiveRecordのメソッドpluckを使うと必要なカラムのデータのみ取得し、配列にしてくれる。

もちろん、 city_ids変数に入れずに、ワンライナーで書いてもOK!

また、上記の例で既出だがwhereのカラムの条件値に配列を指定することもできる〜。
純粋なSQLではINを使っていたので、これは便利!

Town.where(city_id: [13101, 13102, 13103])

子から親のデータを取得する場合
子が一意に決まっているので、簡単に取れる

Q. あるTown(1番目のTown)に紐づく、CityとPrefectureをとる場合

Town.first.city
Town.first.city.prefecture

2. 件数を取得する

Q. 東京都(id=13)に属するCityの件数

City.where(prefecture_id: 13).count

countでなくsizeメソッドを使っても同じ結果になる。

Q. 東京都に属するTownの件数

合計の件数

Town.where(city_id: City.where(prefecture_id: 13)).count

city_ids = City.where(prefecture_id: 13) と変数に入れて、↑の city_id: city_ids としてもOK。

個別の件数

A区: xx件
B区: xx件 みたいに表示したい
  • スタンダードな方法
cities = City.where(prefecture_id: 13)
cities.each { |city| puts "#{city.name} : #{city.towns.count}件" }
  • group句を使った方法
city_ids = City.where(prefecture_id: 13).pluck(:id)
Town.where(city_id: city_ids).group(:city_id).count

結果
{[city_id] => [キーのcityのtown数], ...} のハッシュの形で返ってきてしまった。。

結果を加工することが必要!
ここでRubyのメソッドを使ってみよう。Hashクラスのeachメソッドを使う

town_counts = Town.where(city_id: city_ids).group(:city_id).count
town_counts.each { |city_id, town_count| puts "#{City.find(city_id).name} : #{town_count}件" }

これで希望通りの表示ができた!

3. ActiveRecordのメソッドだけで無理に完結しなくていいんだよ(Rubyを使おう!)

欲しいデータを取得するためには、ActiveRecordのメソッドだけを使って抽出するだけでなく、Rubyのメソッドを使って抽出した結果を加工するという技もあるのです!
(↑の最後の例がその一つ)

Railsビギナーな私は、ついついActiveRecordのメソッドを上手に組み合わせて抽出したい!!という頭しかなかったため、時間をかけて気づけば長々としたSQL文を書いてしまい、後で見たときにこれ何してるの?と自分のコードでもなることがしばしばあったり。。
というのも今までRubyを差し置いて、Ruby on Railsの学習しかしてこなかったので、Rubyの文法やメソッドは正直知らない。。
なんともったいない!Rubyが使えるようになると、ぐっと世界が広がってきた気がします。
では使ってみましょうヽ( ´∇`)ノ

今までのお題をちょっと応用して

Q. 東京都に属するCityの中で、23区のCity23区以外のCity一覧を取得する

  • ActiveRecordのメソッドを使ったスタンダードな方法

23区のCity一覧
どれが23区のCityか分からないので、文字列検索を使って抽出してみる

tokyo_metropolitan_cities = City.where("prefecture_id = ? AND name LIKE '%区'", 13)

ちなみに文字列のみでの検索の場合、
直接値を記述せずに疑問符?を使う方がセキュリティ上良いらしいです。知らなかった〜。参考リンク

23区以外のCity一覧

other_cities = City.where("prefecture_id = ? AND name NOT LIKE '%区'", 13)

結果は無事取れました!が、同じようなSQLを2回発行した感じですね〜。

  • Rubyのメソッドを使った方法
tokyo_metropolitan_cities, other_cities = City.where(prefecture_id: 13).partition { |city| city[:name].match(/.+区/) }

ワンライナーで取れました!
tokyo_metropolitan_citiesには23区のCity一覧が、other_citiesにはそれ以外の東京都のCity一覧が入っています。

条件によって結果を二つの配列に格納するEnumerableクラスのpartition、正規表現を使った文字列の検索を行うmatchメソッドを使いました!
詳しい使い方はリンク先のページを参照してください。
特にpartitionメソッド、かなり使えそうですね〜。

まとめ(なぜこれらを知っておくことが重要なのか)

  • データの参照はController、Modeと様々な箇所で必要になる。毎日のように書く。なのできちんとした方法を覚えておくべき
  • 参照の記述を適当にしているとアプリ全体のパフォーマンスに関わり、バグの要因になる(と思う)
  • 書き方を広く知っておくことで自分の開発効率が格段にアップする

まだ学習途中ですが、これからも様々なコードを見て、自分の使える知識を増やしていきたいと思います。
参照周りでも、scopeなど始め使いこなせるようになるべき知識が数多!
そして、じわりじわりと必要性を感じているRubyも学んでいこうと思います╭( ・ㅂ・)و

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした