iPadを買った!!
iPad、Apple Pencil、Smart Keybordを買いました!!
目的はお絵かきだったのですが、どうやらRubyの開発環境も整えられると知り、必要なアプリを入れてみました〜!!
Gitを使うアプリ
Gitを使うためにWorking Copyというアプリを入れました!
以下のように自分のgithubアカウントと連携し、リモートのリポジトリを見たりクローンしたり出来るようになります!
エディタアプリ
GoCoEditというアプリを入れました!
Smart Keybordも買ったのでタイピングもPCのようにできました!!
お絵かきアプリ
ibis Paintというアプリを入れて、Apple Pencilで菜々緒ちゃんを描いてみました!
色んなペンが使えたり、色が簡単に塗れたり、紙より便利ですね!!
多機能なので使いこなすには勉強が必要そうです!
感想その他
今日はApple Pencilのつかい心地を試すだけのつもりだったのですが、思わず買ってしまいました!!
買ってよかったです!!
includesの挙動模索
普段、N+1問題を防ぐためにとりあえずincludesをしてしまうのですが、includesがどんな挙動をしているのか理解していなかったので調べることにしました!
(昨日からActiveRecord読みたい気持ちが高まっていたため、includesのソースコードを読むことに挑戦したのですが良く分からず、動かしながら挙動を理解する方針に変更しましたw)
定義元のコード
includesメソッドは以下のように定義されておりました。
引数があるかチェックし、クローンしたオブジェクトにincludes!を呼んでいます。
active_record/relation/query_methods.rb
def includes(*args)
check_if_method_has_arguments!(:includes, args)
spawn.includes!(*args)
end
※check_if_method_has_arguments!メソッドは引数がないときにArgumentErrorを起こすメソッドのようです。
def check_if_method_has_arguments!(method_name, args)
if args.blank?
raise ArgumentError, "The method .#{method_name}() must contain arguments."
end
end
※spawnはオブジェクト?をクローンしているようです。
module ActiveRecord
module SpawnMethods
# This is overridden by Associations::CollectionProxy
def spawn #:nodoc:
clone
end
※includes!は配列からnilと空文字を取り除き、配列の入子を平坦化し、self.includes_valuesに引数を代入しています。
def includes!(*args) # :nodoc:
args.reject!(&:blank?)
args.flatten!
self.includes_values |= args
self
end
includesが結局何しているのか分からない。。。w
挙動を調べる
reviewモデルとcommentモデルがあり、以下のような関係とします。
has_many :comments
belongs_to :review
- 子モデルをincludes
preloadした時のように、reviewの全レコードをselect文で取得し、取得したreviewのidと一致するreview_idカラムを持っているcommentモデルのレコードを取得しています。
irb(main):095:0> reviews = Review.includes(:comments)
Review Load (0.5ms) SELECT `reviews`.* FROM `reviews` LIMIT 11
Comment Load (0.3ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`review_id` IN (1, 2, 3, 4, 5)
=> #<ActiveRecord::Relation [#<Review id: 1, rate: 5, body: "Good">, #<Review id: 2, rate: 3, body: "So-so">, #<Review id: 3, rate: 5, body: "Great">, #<Review id: 4, rate: 1, body: "Bad">, #<Review id: 5, rate: 2, body: "I do not like it">]>
- 親モデルをincludes
こちらもpreloadのような挙動です。
irb(main):002:0> comments = Comment.includes(:review)
Comment Load (0.4ms) SELECT `comments`.* FROM `comments` LIMIT 11
Review Load (45.6ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` IN (1, 2)
=> #<ActiveRecord::Relation [#<Comment id: 1, body: "hoge", review_id: 1, created_at: "2019-11-16 08:07:32", updated_at: "2019-11-16 08:07:32">, #<Comment id: 2, body: "fuga", review_id: 2, created_at: "2019-11-16 08:07:41", updated_at: "2019-11-16 08:07:41">]>
- 子レコードをincludesして親レコードで絞り込み
こちらもpreloadのような挙動です。
irb(main):101:0> reviews = Review.includes(:comments).where(id: [1.2])
Review Load (0.9ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` = 1 LIMIT 11
Comment Load (0.4ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`review_id` = 1
=> #<ActiveRecord::Relation [#<Review id: 1, rate: 5, body: "Good">]>
- 子レコードをincludesして子レコードで絞り込み
この場合、eager_loadのようにreviewsテーブルに対してcommentsテーブルをleft outer joinしています。
その後、最初のselect文で取得したcommentsテーブルのレコードのreview_idと一致するidを持つReviewのレコードを取得しています。
irb(main):103:0> reviews = Review.includes(:comments).where(comments: {id: [1,2]})
SQL (2.7ms) SELECT DISTINCT `reviews`.`id` FROM `reviews` LEFT OUTER JOIN `comments` ON `comments`.`review_id` = `reviews`.`id` WHERE `comments`.`id` IN (1, 2) LIMIT 11
SQL (0.3ms) SELECT `reviews`.`id` AS t0_r0, `reviews`.`rate` AS t0_r1, `reviews`.`body` AS t0_r2, `comments`.`id` AS t1_r0, `comments`.`body` AS t1_r1, `comments`.`review_id` AS t1_r2, `comments`.`created_at` AS t1_r3, `comments`.`updated_at` AS t1_r4 FROM `reviews` LEFT OUTER JOIN `comments` ON `comments`.`review_id` = `reviews`.`id` WHERE `comments`.`id` IN (1, 2) AND `reviews`.`id` IN (1, 2)
=> #<ActiveRecord::Relation [#<Review id: 1, rate: 5, body: "Good">, #<Review id: 2, rate: 3, body: "So-so">]>
- 親レコードをincludesして子レコードで絞り込み
こちらはpreloadのような挙動でした。
irb(main):106:0> comments = Comment.includes(:review).where(id: [1,2])
Comment Load (1.3ms) SELECT `comments`.* FROM `comments` WHERE `comments`.`id` IN (1, 2) LIMIT 11
Review Load (0.4ms) SELECT `reviews`.* FROM `reviews` WHERE `reviews`.`id` IN (1, 2)=> #<ActiveRecord::Relation [#<Comment id: 1, body: "hoge", review_id: 1, created_at: "2019-11-16 08:07:32", updated_at: "2019-11-16 08:07:32">, #<Comment id: 2, body: "fuga", review_id: 2, created_at: "2019-11-16 08:07:41", updated_at: "2019-11-16 08:07:41">]>
- 親レコードをincludesして親レコードで絞り込み
こちらは不可でした。
irb(main):107:0> comments = Comment.includes(:review).where(review: {id: [1,2,3]})
SQL (1.5ms) SELECT `comments`.`id` AS t0_r0, `comments`.`body` AS t0_r1, `comments`.`review_id` AS t0_r2, `comments`.`created_at` AS t0_r3, `comments`.`updated_at` AS t0_r4, `reviews`.`id` AS t1_r0, `reviews`.`rate` AS t1_r1, `reviews`.`body` AS t1_r2 FROM `comments` LEFT OUTER JOIN `reviews` ON `reviews`.`id` = `comments`.`review_id` WHERE `review`.`id` IN (1, 2, 3) LIMIT 11
Traceback (most recent call last):
ActiveRecord::StatementInvalid (Mysql2::Error: Unknown column 'review.id' in 'where clause': SELECT `comments`.`id` AS t0_r0, `comments`.`body` AS t0_r1, `comments`.`review_id` AS t0_r2, `comments`.`created_at` AS t0_r3, `comments`.`updated_at` AS t0_r4, `reviews`.`id` AS t1_r0, `reviews`.`rate` AS t1_r1, `reviews`.`body` AS t1_r2 FROM `comments` LEFT OUTER JOIN `reviews` ON `reviews`.`id` = `comments`.`review_id` WHERE `review`.`id` IN (1, 2, 3) LIMIT 11)
感想その他
includeの使い分け模索&コードリーディングは引き続き行おうと思います。
gemのコードリーディングする時はruby mineが便利ですね!久々にruby mine使って思いました!
銀座Railsへ行った
銀座Railsへ行って、Railsコミッターの@kamipoさんと@yahondaさんのセッションをお聞きしてきました!
Active Recordの難しい話もありましたが、お話が面白かったので分からないなりにも楽しむことができました!!
自分もすぐに取り入れていこうと思った内容もありました!
具体的には以下です!
- 不具合だけなおすのではなく根本の理由もわかった上で修正する
- プルリクの説明が足りないとどんな状況でどんな現象が起きたのか等々聞き出すために工数がかかるので、どんな問題があり、具体的にどんな状況で発生し、何故この実装にしたかをしっかり書く
(仕事時にはissueやプルリク作成時にテンプレ項目をしっかり埋めるようにするぞ)
- プルリク投げた部分の実装が、どんな経緯でその実装になっているのかgit blame等して理解し、その旨も記載する
楽しかったし、すぐに取り入れたい自分の課題も見つかったし、参加出来て良かったです!!
ありがとうございました!
画像は頂いたCDです!
SQLの集合関数
SQLの集合関数についてしらべました〜
集合関数とは
SQLにもとから用意されている関数です。
集合関数の種類
以下の5種類の関数があります。
- COUNT:総数を取得
- SUM:総和を取得
- MAX:最大値を取得
- MIN:最小値を取得
- AVG:平均を取得
使い方
以下のようなreviewsテーブルがあるとします。
count
以下のようにcount関数を使ってreviewsテーブルのレコードの総数を取得できます。
SELECT count(*) FROM reviews;
count(*)
5
whereで絞った結果のレコード数を取得もできます。
SELECT count(*) FROM reviews WHERE rate = 5;
count(*)
2
sum
以下のようにrateカラムの合計を取得できます。
SELECT sum(rate) FROM reviews;
sum(rate)
16
max
以下のようにrateカラムの最大値を取得できます。
SELECT max(rate) FROM reviews;
max(rate)
5
min
以下のようにrateカラムの最小値を取得できます。
SELECT min(rate) FROM reviews;
min(rate)
1
以下のようにrateカラムの平均値を取得できます。
SELECT avg(rate) FROM reviews;
avg(rate)
3.2
感想その他
SQLの便利さが少しずつわかってきた気がします。。。!!
エスケープ文字
「%」や「_」をワイルドカードとしてではなく文字列として検索したいときに使用するエスケープ文字
エスケープ文字とは
「\」です。
「%」や「_」をワイルドカードとしてではなく文字列として検索したいときに使用します。
例:以下のようなeventsテーブルがあるとします。
エスケープ文字を使わない場合
SELECT * FROM events WHERE name LIKE '%_%';
「%」は0文字以上の任意の文字列、「_」は任意の1文字を意味するので「%_%」だと1文字以上の文字列は全部ヒットしてしまいます。
エスケープ文字を使う場合
そこで以下のように_の前にエスケープ文字をつけて「%\_%」とすれば、nameに_が含まれるレコードのみ取得できます。
SELECT * FROM events WHERE name LIKE '%\_%';
railsのメソッドの場合
> Event.where('name LIKE ?', '%\_%')
Event Load (0.4ms) SELECT `events`.* FROM `events` WHERE (name LIKE '%\\_%') LIMIT 11
=> #<ActiveRecord::Relation [#<Event id: 7, name: "hayashi_yoshino", date: "2015-10-30", description: "", created_at: "2019-11-13 11:20:16", updated_at: "2019-11-13 11:20:16">]>
感想その他
LIKE句はrailsもSQLも似ているな似ているな〜と思いました。
LIKE句
SQLのLIKE句について調べました。
LIKE句とは
完全一致だけでなく部分一致、前方一致、後方一致など曖昧な条件を指定できる構文です。
前提
以下のようなeventsテーブルがあるとします。
完全一致検索
LIKE '検索文字' で、完全一致する文字列を検索することができます。
SELECT * FROM events WHERE name LIKE 'Whistler';
曖昧検索
LIKE句にワイルドカードを組み合わせることで曖昧検索することができます。
ワイルドカード
- %
「%」は0文字以上の任意の文字列を意味します
- _
「_」は任意の1文字を意味します
部分一致
SELECT * FROM events WHERE name LIKE '%e%';
nameにeが含まれるものが取得されます。
前方一致
SELECT * FROM events WHERE name LIKE 'B%';
nameの1文字目がBであるものが取得されます。
後方一致
SELECT * FROM events WHERE name LIKE '%er';
nameの最後の2文字がerであるものが取得されます。
文字数指定
SELECT * FROM events WHERE name LIKE '_owser';
nameが "任意の1文字 + owse"であるものが取得されます。
感想その他
「%」や「_」をワイルドカードとしてではなく文字列として検索したいときに使用するエスケープ文字については後日調べたいと思います。
IN句
以前から理解したいと思っていたIN句について調べました〜
IN句とは
一致するかの条件をまとめて判断出来るSQLの構文です。
具体例
以下のようなeventsテーブルを操作するとします。
IN句を使う場合
以下のようにIN句を使うことで複数の条件を指定してデータを取得する事ができます。
SELECT * FROM events WHERE id IN (1,2);
※アティブレコードだと
> Event.where(id: [1,2])
Event Load (0.4ms) SELECT `events`.* FROM `events` WHERE `events`.`id` IN (1, 2) LIMIT 11
=> #<ActiveRecord::Relation [#<Event id: 1, name: "Whistler", date: "1999-11-09", description: "Went hiking.", created_at: "2019-11-09 13:23:43", updated_at: "2019-11-09 13:25:38">, #<Event id: 2, name: "Puffball", date: nil, description: "Chased", created_at: "2019-11-09 13:26:48", updated_at: "2019-11-09 13:27:45">]>
IN句を使わない場合
SELECT * FROM events WHERE id = 1 OR id = 2;
※アティブレコードだと
SELECT * FROM events WHERE id = 1 OR id = 2; というSQLが吐かれるメソッドはよく分かりませんでしたが、
IN句ではなくレコード数分SQLが吐かれるような書き方は以下です。
irb(main):004:0> arry =
=>
irb(main):005:0> [1,2].each do |i|
irb(main):006:1* arry << Event.find(i)
irb(main):007:1> end
Event Load (7.7ms) SELECT `events`.* FROM `events` WHERE `events`.`id` = 1 LIMIT 1
Event Load (0.8ms) SELECT `events`.* FROM `events` WHERE `events`.`id` = 2 LIMIT 1
=> [1, 2]
この例くらいの単純なデータ取得ならわざわざ配列作ってeachを回すことはしないですが、関連テーブルまたいで云々~みたいに複雑な処理をする際は配列をeachで回してしまいがちです。。。( eachが使い慣れているので)
感想その他
以前rubyで書いたSQLが大量に吐かれるスクリプトを指摘され、同時にIN句についての話を少し教えてもらった事があったのですが、理解しきれておりませんでした。
railsのどのメソッド使うとどんなSQLが吐かれるかパッとイメージできるようになりたいです。
それと、サブクエリにつては後日調べますw