
Ruby on Rails 5アプリケーションプログラミング
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/14
- メディア: 大型本
- この商品を含むブログを見る
序文
「Ruby on Rails 5 アプリケーションプログラミング」学習18日目。
ポモドーロテクニックとともに。
GitHub
進捗
- 第5章 モデル開発
- 5.4 レコードの登録/更新/削除
(学習時間:3.5時間)
- 5.4 レコードの登録/更新/削除
感想
レコードの登録、更新、削除などを行うクエリメソッドについて学習。
ポモドーロテクニック発動。
調子いいときには面倒くさく感じるけど、あんまりやる気ないときには最低限の成果を担保してくれるような気がしなくもない。
そもそも勉強する気力すらわかない時はどうしようもないけれど。
カロリーメイトください。
BGM
ダイエッター / 小林未奈 www.youtube.com
コード実装部分
↓config/routes.rb
# 省略 get 'record/update_all' get 'record/update_all2' get 'record/destroy2/:id', to: 'record#destroy2' get 'record/delete/:id', to: 'record#delete' get 'record/destroy_all' get 'record/delete_all' get 'record/transact' get 'record/enum_rec'
↓/app/controllers/record_controller.rb
class RecordController < ApplicationController # 省略 # レコードの更新例 def update_all cnt = Cd.where(label: 'omake records') .update_all(label: 'サザナミレーベル') render plain: "#{cnt}件のデータを更新しました。" # UPDATE "cds" # SET "label" = 'omake records' # WHERE "cds"."label" = ? # [["label", "サザナミレーベル"]] end # レコードの更新例2 def update_all2 cnt = Cd.order(:label) .limit(5) .update_all('price = price * 0.8') render plain: "#{cnt}件のデータを更新しました。" # UPDATE "cds" # SET price = price * 0.8 # WHERE "cds"."id" # IN ( # SELECT "cds"."id" # FROM "cds" # ORDER BY "cds"."label" ASC # LIMIT ? # ) # [["LIMIT", 5]] end def destroy2 # 削除件数は帰らなかった Cd.destroy(params[:id]) # SELECT "cds".* # FROM "cds" # WHERE "cds"."id" = ? # LIMIT ? # [["id", 1], # ["LIMIT", 1]] # DELETE FROM "artists_cds" # WHERE "artists_cds"."cd_id" = ? # [["cd_id", 1]] # DELETE FROM "cds" # WHERE "cds"."id" = ? [["id", 1]] end def delete # 関連モデルは削除せずに単純にdeleteのみ行う # 削除件数も返るっぽい cnt = Cd.delete(params[:id]) render plain: "#{cnt}件のデータを削除しました。" # DELETE FROM "cds" # WHERE "cds"."id" = ? # [["id", 1]] end def destroy_all # 返り値は削除したモデルの配列 Cd.where.not(label: 'サザナミレーベル').destroy_all render plain: "データをすべて削除しました。" # SELECT "cds".* # FROM "cds" # WHERE ("cds"."label" != ?) # [["label", "サザナミレーベル"]] # DELETE FROM "artists_cds" # WHERE "artists_cds"."cd_id" = ? # [["cd_id", 2]] # DELETE FROM "cds" # WHERE "cds"."id" = ? # [["id", 2]] end def delete_all # 返り値は削除した件数 cnt = Cd.where.not(label: 'サザナミレーベル').delete_all render plain: "#{cnt}件のデータを削除しました。" # DELETE FROM "cds" # WHERE ("cds"."label" != ?) # [["label", "サザナミレーベル"]] end # トランザクション処理の例 def transact # モデル、もしくはインスタンス経由でtransactionを呼び出す Cd.transaction do c1 = Cd.new( jan: '978-4-7741-5067-3', title: 'ダイエッター', price: 2500, label: 'FLAVOR RECORDS', released: '2018-03-21' ) # save()はtrue/falseを返し、 # save!()は失敗した場合に例外を返す # 例外が発生するとrescueブロックが実行される c1.save! # throwだ! # raise '例外発生:処理はキャンセルされました' c2 = Cd.new( jan: '978-4-7741-5067-5', title: 'dinosaur', price: 2500, label: 'nom records', released: '2018-02-13' ) c2.save! end # begin transaction # INSERT INTO "cds" ("jan", "title", "price", "label", "released", "created_at", "updated_at") # VALUES (?, ?, ?, ?, ?, ?, ?) # [["jan", "978-4-7741-5067-3"], # ["title", "ダイエッター"], # ["price", 2500], # ["label", "FLAVOR RECORDS"], # ["released", "2018-03-21"], # ["created_at", "2018-04-10 04:53:19.064469"], # ["updated_at", "2018-04-10 04:53:19.064469"]] # commit transaction render plain: 'トランザクションは成功しました' # catchだ! rescue => e render plain: e.message # begin transaction # INSERT INTO "cds" ("jan", "title", "price", "label", "released", "created_at", "updated_at") # VALUES (?, ?, ?, ?, ?, ?, ?) # [["jan", "978-4-7741-5067-3"], # ["title", "ダイエッター"], # ["price", 2500], # ["label", "FLAVOR RECORDS"], # ["released", "2018-03-21"], # ["created_at", "2018-04-10 04:55:11.265965"], # ["updated_at", "2018-04-10 04:55:11.265965"]] # rollback transaction # 補足 : トランザクション分離レベルの指定例 # 必要になったら調べよう。とりあえずSQLiteは対応していない # だいたいREAD COMMITTEDかREPEATABLE READにしとけばいいんじゃね? # Cd.transaction(isolation: :repeatable_read) do # @cd = Cd.find(1) # @cd.update(price: 3000) # end end # 列挙型の利用例 def enum_rec @review = Review.find(1) # 列挙体statusがpublishedに設定されているレコードのみを取得 # @review = Review.published.where(.....) # SELECT "reviews".* # FROM "reviews" # WHERE "reviews"."status" = ? # ORDER BY "reviews"."updated_at" DESC # LIMIT ? # [["status", 1], # ["LIMIT", 11]] # 列挙型の設定(必要ない場合はSQLは発行されない) @review.published! # 他の書き方の例 # @review.status = 1 # @review.status = :published # UPDATE "reviews" # SET # "status" = ?, # "updated_at" = ? # WHERE "reviews"."id" = ? # [["status", 1], # ["updated_at", "2018-04-10 06:55:00.583813"], # ["id", 1]] render plain: 'ステータス:' + @review.status # 列挙型自体をモデルとして作成すると # 複数のモデルから参照できるかもしれない # render plain: @review.inspect end end
↓/db/migrate/20180410052604_create_members.rb
class CreateMembers < ActiveRecord::Migration[5.1] def change create_table :members do |t| t.string :name t.string :email # 同時実行制御のためのフィールド t.integer :lock_version, default: 0 t.timestamps end end end
↓app/views/members/_form.html.erb
<%# 省略 %> <%# 隠しフィールドとしてlock_versionを仕込んで %> <%# オプティミスティック同時実行制御を行う %> <%= form.hidden_field :lock_version %> <%# 省略 %>
↓/app/controllers/members_controller.rb
# 省略 # PATCH/PUT /members/1 # PATCH/PUT /members/1.json def update respond_to do |format| if @member.update(member_params) format.html { redirect_to @member, notice: 'Member was successfully updated.' } format.json { render :show, status: :ok, location: @member } else format.html { render :edit } format.json { render json: @member.errors, status: :unprocessable_entity } end end rescue ActiveRecord::StaleObjectError render plain: '競合エラーが発生しました。' # 競合エラー発生時のSQL # begin transaction # UPDATE "members" # SET # "name" = '小林未奈', # "updated_at" = '2018-04-10 06:05:25.497660', # "lock_version" = 2 # WHERE # "members"."id" = ? # AND "members"."lock_version" = ? # [["id", 1], # ["lock_version", 1]] # commit transaction # begin transaction # UPDATE "members" # SET # "name" = 'こばやし未奈', # "lock_version" = 2, # "updated_at" = '2018-04-10 06:05:35.310184' # WHERE # "members"."id" = ? # AND "members"."lock_version" = ? # [["id", 1], # ["lock_version", 1]] # rollback transaction end # 省略
↓db/migrate/20180329053536_create_reviews.rb
class CreateReviews < ActiveRecord::Migration[5.1] def change create_table :reviews do |t| t.references :cd, foreign_key: true t.references :listener, foreign_key: true t.integer :status, default: 0, null: false t.text :body t.timestamps end end end
↓/app/models/review.rb
class Review < ApplicationRecord # モデル依存の列挙型はモデルファイル内に定義する # これにより@review.statusみたいな感じで列挙体名が呼び出せる enum status: { draft:0, published:1, deleted:2 } # これも同じ # enum status: {:draft, :published, :deleted} belongs_to :cd belongs_to :listener default_scope{ order(updated_at: :desc) } end
↓/test/fixtures/members.yml
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html one: id: 1 name: 小林未奈 email: kobayashi@calorie.mate lock_version: 0 two: id: 2 name: せりかな email: serikana@calorie.mate lock_version: 0 three: id: 3 name: 泉川そら email: sora@calorie.mate lock_version: 0