以前のブログで、既にレコードが存在していればUPDATEをし、存在していなければINSERTをする方法として、MySQLの「on duplicate key update」を紹介しました。(以前のブログもぜひ、あわせてお読みください。)
ただ、以前のブログでも触れているように「on duplicate key update」には「auto_increment」に関わる注意点があります。
そこで、今回は「on duplicate key update」を使わずに、Railsのプログラムで同様な処理を実現させようと思います。
テーブルの説明
テーブルは以前と同じテーブルを使いますが、こちらで再掲しておきます。
idカラムがPRIMARY KEYで「auto_increment」として定義しています。mst_spot_idがスポットを特定するためのidで、UNIQUEインデックスとなっています。
また、更新対象となるカラムは、like_countでデフォルト値が0としています。
mysql> desc spot_like_counts;
+----------------------------+-----------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------------------+-----------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| mst_spot_id | int(11) | NO | UNI | NULL | |
| like_count | int(11) | NO | | 0 | |
| created_at | datetime | NO | | NULL | |
| updated_at | datetime | NO | | NULL | |
+----------------------------+----------+-------+-----+---------+----------------+
RailsプログラムでのUPDATE or INSERT
処理としては、likeをする処理です。
実際には、1ユーザで同じ場所に複数回likeできないように、履歴なども管理する必要がありますが、説明を単純化するためにそのあたりは省略しています。
ポイントは、下記に記載してきます。
def add_like(mst_spot_id)
begin
spot_like_count = SpotLikeCount.find_or_create_by(mst_spot_id: mst_spot_id)
rescue ActiveRecord::RecordNotUnique => ex
# SELECTとINSERTの間で万が一別のトランザクションでINSERTが走った場合を考慮
spot_like_count = SpotLikeCount.find_by(mst_spot_id: mst_spot_id)
end
check_in_spot_like_count.increment!(:like_count)
end
find_or_create_byとActiveRecord::RecordNotUnique
find_or_create_byは、まずSELECTでレコードを取得しようとし、存在しなければINSERTを発行します。SQLとしては、下記が発行されます。
SELECT `spot_like_counts`.* FROM `spot_like_counts` WHERE `spot_like_counts`.`mst_spot_id` = 1 LIMIT 1
INSERT INTO `spot_like_counts` (`mst_spot_id`, `created_at`, `updated_at`) VALUES (1, '2016-12-20 08:48:15', '2016-12-20 08:48:15')
上記のとおり、SQLが2本分かれて実行されることから、仮にSELECT文とINSERT文の間に別のアクセスでINSERTが実行された場合、mst_spot_idはUNIQUEインデックスとなっていることから、INSERTに失敗して、例外が発生します。そこで、rescueで「ActiveRecord::RecordNotUnique」を捕捉し、再度SELECT文で更新対象となるレコードを取得するようにします。
increment!
increment!(:like_count)でlike_countを1つインクリメントするUPDATE文を実行します。発行されるSQLは下記になります。
UPDATE `spot_like_counts` SET `like_count` = COALESCE(`like_count`, 0) + 1 WHERE `spot_like_counts`.`id` = 1
これで、既にレコードが存在していても、していなくてもlike_countを1つインクリメントすることができます。
本日のブログはいかがでしたでしょうか。他にも方法はあると思いますので、ぜひもっとよい方法があれば、コメントをいただければと思います。
また、末尾になりますが、ぜひ以前のブログもご参照ください。
0 件のコメント:
コメントを投稿