SlideShare a Scribd company logo
1 of 16
Download to read offline
とあるプロジェクトの
つらみなコード
2019.01.16 Otemach.rb #13
Yuya Taki
self.inspect
[1] https://github.com/muramurasan/okuribito [2] http://poject.herokuapp.com/
➢ gemのロゴを書いたり…
➢ たまにQiitaの記事を書いたり…
➢ 新卒で某SIerに就職
➢ 渋谷の企業にてRuby on Railsを学ぶ
➢ 2016年10月にENECHANGEにjoin
➢ Railsと戯れてかれこれ4年目
➢ よちよち.kt (次回:2019年2月2日)
Name : Yuya Taki
GitHub : yuyasat
Qiita : yuyasat
[1]
若輩者ですので、何卒優しくご教授いただければと
思います。
(commitはしていない)
➢ ○よ○よ風ゲームをReact.jsで実装し
たり
[2]
システム概要
申し込みページ
管理画面
RDB(PostgreSQL)
申込レコードの新規作成
申込レコードの新規作成・更新
外部システム
API
随所でredirect、render
class OrdersController < ApplicationController
def update
# 送信ボタンが二つあり、送信ボタンのnameによって挙動を変える
return send_to_api_2 if params.key?(:commit_api)
update_order
end
private
def update_order
@order.assign_attributes(order_params)
@order.status = :confirmed unless @order.status_changed?
if @order.save
redirect_to request.referer
else
common_show
@error_messages = ['エラーがあります。']
render :show
end
end
def send_to_api_2
return render_error('ステータスが確認済みではありません') unless @order.status_confirmed? # ・・・[1]
send_to_api_1 # API2を呼ぶ前に、API1を呼ぶ
return if performed? # ・・・[2]
# Api2クラスはAPI2をコールするために実装したクラス
response = Api2.post("/api2_url", params: { order: @order })
unless response.success?
@error_messages = response.errors # 詳細な実装は、GET先のインターフェスによる
raise # ・・・[3]
end
@order.status = :linked
@order.save!
@notice = 'API2に送信しました。'
redirect_to request.referer
rescue StandardError => e # ・・・[4]
@notice = nil
@error_messages = ['API2に失敗しました。'].concat(@error_messages || [])
アクションの中でredirectやrenderは
局所化しよう。
詳しくは、「現場から学ぶ、Railsの
Controllerアンチパターン」をお読みくだ
さい。
随所でredirectやrenderを行うとアク
ションの全貌が把握しにくくなる。
performed?を呼び出す必要となる実装
はもっと良い設計があるはず。
STI
AmpereChangeOrder
STI
CapacityChangeOrder
ChangeOrder
STI
ConsumerChangeOrder
契約変更系のモデル
STI
LeaveOrder
STI
PlanChangeOrder
アンペア変更
(eg. 40A → 50A)
容量変更
(eg. 60A → 7kVA)
需要家情報変更
(eg. 名義変更)
解約 料金プラン変更
各モデルの詳細画面のURLを返すメソッド(つらみ例)
class ChangeOrder < ApplicationRecord
def resource_path
routes = Rails.application.routes.url_helpers
case self
when AmpereChangeOrder
routes.ampere_change_order_path(self)
when CapacityChangeOrder
routes.capacity_change_order_path(self)
when ConsumerChangeOrder
routes.consumer_change_order_path(self)
when LeaveOrder
routes.leave_order_path(self)
when PlanChangeOrder
routes.plan_change_order_path(self)
end
end
end
STI
AmpereChangeOrder
STI
ConsumerChangeOrder
ChangeOrder
せっかく継承を用いているのだから、うまく活用しよう。
各モデルの詳細画面のURLを返すメソッド(改善例)
class AmpereChangeOrder < ChangeOrder
def resource_path
Rails.application.routes.url_helpers.ampere_change_order_path(self)
end
end
class ChangeOrder < ApplicationRecord
end
親クラスからはresource_pathメソッドを削除ない
し、ダミーで実装
子クラスに記述
class ConsumerChangeOrder < ChangeOrder
def resource_path
Rails.application.routes.url_helpers.consumer_change_order_path(self)
end
end
同様の例
class ChangeOrder < ApplicationRecord
def ampere_change_order?
type == 'AmpereChangeOrder'
end
def consumer_change_order?
type == 'ConsumerChangeOrder'
end
end
class AmpereChangeOrder < ChangeOrder
def ampere_change_order?
true
end
end
class ChangeOrder < ApplicationRecord
def ampere_change_order?
false
end
def consumer_change_order?
false
end
end
class ConsumerChangeOrder < ChangeOrder
def consumer_change_order?
true
end
end
親クラスはfalseで定義して、各子クラスに
て必要なものだけをオーバーライドするよ
う。
DecoratorでActiveRecordの属性を上書きしている
class ElecContractDecorator < Draper::Decorator
delegate_all
def status
{
'before_check' => '申込内容確認前 ',
'completed' => '完了',
}[object.status]
end
end
viewやCSV出力、API連携で利用しているが、 decorateして
いるのか、素のActiveRecordなのかわからない
class ElecContractsController < ApplicationController
def show
@elec_contract =
ElecContract.find(params[:id]).decorate
end
end
Draperの使用例:
DecoratorでActiveRecordの上書きはしないよ
うにしよう。
viewで使う用のメソッドを定義する場合は、
suffixを用いるなど、チーム内で方針を決めよ
う。
たとえば、enumerizeというgemを使っているの
であれば、表示用のメソッドには _textが生える
のでそれに合わせて enum以外も_textにする
など。
decorateしてあっても、元の値を使いたい時もある
 例)元の値を使おうとする
  → decorateしてあるので、decorateされた値が出力
  → objectを付けてdecorateを解除する
  => 二度手間!
人間は、いつどのタイミングで decorateしたかがわからない
モデルとDecoratorで同じメソッド名で異なる挙動
class OrderItem < ApplicationRecord
def full_name(with_space: false)
return "#{family_name}#{given_name}" unless with_space
"#{family_name} #{given_name}"
end
end
class OrderItemDecorateor < Draper::Decorator
delegate_all
def full_name(sep: '', prefix: '', kana: false)
if corporate_name.present?
send("corporate_name#{kana ? '_kana' : ''}")
else
full_name_person(sep: sep, prefix: prefix, kana: kana)
end
end
def full_name_person(sep: '', prefix: '', kana: false)
value = send("#{prefix}family_name#{kana ? '_kana' : ''}") || ''
value += sep.presence || ' '
value + (send("#{prefix}given_name#{kana ? '_kana' : ''}") || '')
end
end
モデルに定義された full_nameは引
数なしではスペースなしだが、
Decoratorで定義されたfull_nameは
スペースありとなっている。
モデルとDecoratorで同じメソッド名を
定義しないようにしよう。
当初decoratorに実装していても、モ
デル側でも使用したくなった場合は、
モデル側に移動しよう。
RDBを使っているのに値をjsonに突っ込む
create_table "change_orders", force: :cascade do |t|
t.string "type", null: false
t.bigint "elec_contract_id", null: false
t.integer "status", default: -1, null: false
t.jsonb "items", default: [], null: false
t.datetime "created_at"
t.datetime "updated_at"
end
たとえば、アンペア変更の申し込
みであれば姓名は不要になる。
%w[contract_name_type corporate_name corporate_name_kana
family_name family_name_kana given_name given_name_kana
phone_number_type phone_number_1 phone_number_2 phone_number_3
contact_type].each do |name|
define_method(name) {
items[name]
}
define_method("#{name}=") { |val|
items[name] = val
}
end
変更系の申し込みには、必ずしも
全てのカラムが必要なわけではな
い。
しかし、RDBを使っているのだか
ら、ActiveRecordの旨味を十分に
活用するのならば、jsonにString
として入れるのではなく、カラムを
利用した方が良い。データベース
の型も利用できる。
nullが増えることに抵抗はある
が、STIを使う以上、nullのカラム
が増えるのは許容しよう。
無駄なレコードの生成(挙動の説明)
電気の申込には、連絡先という項目があ
る
CRMでは、需要家本人・需要家本人以
外の選択があり、需要家本人以外を選
ぶと、入力フィールドがでてくる
無駄なレコードの生成(もともとの実装)
def show
@order_item = OrderItem.find_by(id: params[:id])
if @order_item.another_contacts.size.zero?
@order_item.another_contacts << AnotherContact.new
end
end
<%= o.fields_for :another_contacts do |ac| %>
<%= ac.text_field :family_name %>
<%= ac.text_field :given_name %>
<% end %>
CRMでは、申込があったものを管理するの
で、@order_itemはすでにデータベースに永
続化されている。
class AnotherContact < ApplicationRecord
enum contact_type: {
invalidity: -1,
}, _prefix: true
end
永続化されたレコードに対して関連レコード
を追加しようとすると関連レコードも永続化さ
れてしまう。
正規に作られたレコードかどうかを判別する
ためのenumが存在。
class OrderItem < ApplicationRecord
has_many :another_contacts
accepts_nested_attributes_for :another_contacts, reject_if: :all_blank, allow_destroy: true
end
無駄なレコードの生成(改善例)
<%= eo.fields_for :another_contacts,
eo.object.another_contacts.blank? ? eo.object.another_contacts.new : nil do |ac| %>
<%= ac.text_field :family_name %>
<%= ac.text_field :given_name %>
<% end %>
def show
@order_item = OrderItem.find_by(id: params[:id])
end
Controllerでは何もせず、viewのfields_forにてanother_contactsがあるかないかで処理を変える
※新規生成の時は、この記述で OK。
<%= o.fields_for :another_contacts do |ac| %>
<%= c.text_field :family_name %>
<%= c.text_field :given_name %>
<% end %>
accepts_nested_attributes_forとfields_forを
用いる場合は、新規作成時と更新の時で挙動が異な
るので実装時には注意しよう。
まとめ
➢ 同じチームの人に意見を聞こう。
➢ データベース設計は後戻りがしにくいので初期の段階できちんとレ
ビューしてもらおう。
➢ リリースが迫っていてレビューの時間が十分にとれない場合でも、あと
からリファクタリングができるように、しっかりとテストを書こう。
○ テストがあると開発速度がアップする
ご静聴ありがとうございました。

More Related Content

What's hot

GoらしいAPIを求める旅路 (Go Conference 2018 Spring)
GoらしいAPIを求める旅路 (Go Conference 2018 Spring)GoらしいAPIを求める旅路 (Go Conference 2018 Spring)
GoらしいAPIを求める旅路 (Go Conference 2018 Spring)lestrrat
 
GoCon 2015 Summer GoのASTをいじくって新しいツールを作る
GoCon 2015 Summer GoのASTをいじくって新しいツールを作るGoCon 2015 Summer GoのASTをいじくって新しいツールを作る
GoCon 2015 Summer GoのASTをいじくって新しいツールを作るMasahiro Wakame
 
SlowQueryとの戦い
SlowQueryとの戦いSlowQueryとの戦い
SlowQueryとの戦いKen Gotoh
 
よいことも悪いこともぜんぶPHPが教えてくれた
よいことも悪いこともぜんぶPHPが教えてくれたよいことも悪いこともぜんぶPHPが教えてくれた
よいことも悪いこともぜんぶPHPが教えてくれたMoriyoshi Koizumi
 
メタメタプログラミングRuby
メタメタプログラミングRubyメタメタプログラミングRuby
メタメタプログラミングRubyemasaka
 
PHPBLT#6 PHPの未来に入るかもしれない機能の紹介
PHPBLT#6 PHPの未来に入るかもしれない機能の紹介PHPBLT#6 PHPの未来に入るかもしれない機能の紹介
PHPBLT#6 PHPの未来に入るかもしれない機能の紹介sters
 
Visual basic14 の話
Visual basic14 の話Visual basic14 の話
Visual basic14 の話Kazuki Kachi
 

What's hot (9)

GoらしいAPIを求める旅路 (Go Conference 2018 Spring)
GoらしいAPIを求める旅路 (Go Conference 2018 Spring)GoらしいAPIを求める旅路 (Go Conference 2018 Spring)
GoらしいAPIを求める旅路 (Go Conference 2018 Spring)
 
GoCon 2015 Summer GoのASTをいじくって新しいツールを作る
GoCon 2015 Summer GoのASTをいじくって新しいツールを作るGoCon 2015 Summer GoのASTをいじくって新しいツールを作る
GoCon 2015 Summer GoのASTをいじくって新しいツールを作る
 
SlowQueryとの戦い
SlowQueryとの戦いSlowQueryとの戦い
SlowQueryとの戦い
 
よいことも悪いこともぜんぶPHPが教えてくれた
よいことも悪いこともぜんぶPHPが教えてくれたよいことも悪いこともぜんぶPHPが教えてくれた
よいことも悪いこともぜんぶPHPが教えてくれた
 
PHP7を魔改造した話
PHP7を魔改造した話PHP7を魔改造した話
PHP7を魔改造した話
 
メタメタプログラミングRuby
メタメタプログラミングRubyメタメタプログラミングRuby
メタメタプログラミングRuby
 
PHPBLT#6 PHPの未来に入るかもしれない機能の紹介
PHPBLT#6 PHPの未来に入るかもしれない機能の紹介PHPBLT#6 PHPの未来に入るかもしれない機能の紹介
PHPBLT#6 PHPの未来に入るかもしれない機能の紹介
 
node-perl
node-perlnode-perl
node-perl
 
Visual basic14 の話
Visual basic14 の話Visual basic14 の話
Visual basic14 の話
 

Similar to とあるプロジェクトのつらみなコード

10年目の『エブリスタ』を支える技術
10年目の『エブリスタ』を支える技術10年目の『エブリスタ』を支える技術
10年目の『エブリスタ』を支える技術DeNA
 
~Dockerfileの開発を劇的に楽にする~ Dockerfile開発環境 EDGE
~Dockerfileの開発を劇的に楽にする~ Dockerfile開発環境 EDGE~Dockerfileの開発を劇的に楽にする~ Dockerfile開発環境 EDGE
~Dockerfileの開発を劇的に楽にする~ Dockerfile開発環境 EDGE辰徳 斎藤
 
ゲットーの斜め上をゆくWebアプリケーションフレームワークの開発
ゲットーの斜め上をゆくWebアプリケーションフレームワークの開発ゲットーの斜め上をゆくWebアプリケーションフレームワークの開発
ゲットーの斜め上をゆくWebアプリケーションフレームワークの開発emasaka
 
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)Hiroaki KOBAYASHI
 
成長を加速する minne の技術基盤戦略
成長を加速する minne の技術基盤戦略成長を加速する minne の技術基盤戦略
成長を加速する minne の技術基盤戦略Hiroshi SHIBATA
 
Ruby Sapporo Night Vol4
Ruby Sapporo Night Vol4Ruby Sapporo Night Vol4
Ruby Sapporo Night Vol4Koji SHIMADA
 
実践Sass 後編
実践Sass 後編実践Sass 後編
実践Sass 後編kosei27
 
Sinatraでwebアプリケーション開発を学ぶ
Sinatraでwebアプリケーション開発を学ぶSinatraでwebアプリケーション開発を学ぶ
Sinatraでwebアプリケーション開発を学ぶHiroshi Oyamada
 
CodeIgniterによるPhwittr
CodeIgniterによるPhwittrCodeIgniterによるPhwittr
CodeIgniterによるPhwittrkenjis
 
ぱっと見でわかるC++11
ぱっと見でわかるC++11ぱっと見でわかるC++11
ぱっと見でわかるC++11えぴ 福田
 
ノンプログラマーでも明日から使えるJavaScript簡単プログラム 先生:柳井 政和
ノンプログラマーでも明日から使えるJavaScript簡単プログラム 先生:柳井 政和ノンプログラマーでも明日から使えるJavaScript簡単プログラム 先生:柳井 政和
ノンプログラマーでも明日から使えるJavaScript簡単プログラム 先生:柳井 政和schoowebcampus
 
serverspecを使用したサーバ設定テストの実例
serverspecを使用したサーバ設定テストの実例serverspecを使用したサーバ設定テストの実例
serverspecを使用したサーバ設定テストの実例Koichi Shimozono
 
入門 超絶技巧プログラミング !
入門 超絶技巧プログラミング !入門 超絶技巧プログラミング !
入門 超絶技巧プログラミング !Nobutada Matsubara
 
Dart のコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法について
Dart のコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法についてDart のコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法について
Dart のコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法についてKosuke Saigusa
 
node+socket.io+enchant.jsでチャットゲーを作る
node+socket.io+enchant.jsでチャットゲーを作るnode+socket.io+enchant.jsでチャットゲーを作る
node+socket.io+enchant.jsでチャットゲーを作るKiyoshi SATOH
 
Play2 scalaを2年やって学んだこと
Play2 scalaを2年やって学んだことPlay2 scalaを2年やって学んだこと
Play2 scalaを2年やって学んだことdcubeio
 
コードの自動修正によって実現する、機能開発を止めないフレームワーク移行
コードの自動修正によって実現する、機能開発を止めないフレームワーク移行コードの自動修正によって実現する、機能開発を止めないフレームワーク移行
コードの自動修正によって実現する、機能開発を止めないフレームワーク移行gree_tech
 
わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61TATSUYA HAYAMIZU
 

Similar to とあるプロジェクトのつらみなコード (20)

10年目の『エブリスタ』を支える技術
10年目の『エブリスタ』を支える技術10年目の『エブリスタ』を支える技術
10年目の『エブリスタ』を支える技術
 
~Dockerfileの開発を劇的に楽にする~ Dockerfile開発環境 EDGE
~Dockerfileの開発を劇的に楽にする~ Dockerfile開発環境 EDGE~Dockerfileの開発を劇的に楽にする~ Dockerfile開発環境 EDGE
~Dockerfileの開発を劇的に楽にする~ Dockerfile開発環境 EDGE
 
Mina 20130417
Mina 20130417Mina 20130417
Mina 20130417
 
ゲットーの斜め上をゆくWebアプリケーションフレームワークの開発
ゲットーの斜め上をゆくWebアプリケーションフレームワークの開発ゲットーの斜め上をゆくWebアプリケーションフレームワークの開発
ゲットーの斜め上をゆくWebアプリケーションフレームワークの開発
 
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)
仕事の手離れを良くする手段としての、静的検査のあるテンプレートエンジン (YATT::Lite talk at 2014 テンプレートエンジンNight)
 
成長を加速する minne の技術基盤戦略
成長を加速する minne の技術基盤戦略成長を加速する minne の技術基盤戦略
成長を加速する minne の技術基盤戦略
 
Ruby Sapporo Night Vol4
Ruby Sapporo Night Vol4Ruby Sapporo Night Vol4
Ruby Sapporo Night Vol4
 
実践Sass 後編
実践Sass 後編実践Sass 後編
実践Sass 後編
 
Sinatraでwebアプリケーション開発を学ぶ
Sinatraでwebアプリケーション開発を学ぶSinatraでwebアプリケーション開発を学ぶ
Sinatraでwebアプリケーション開発を学ぶ
 
CodeIgniterによるPhwittr
CodeIgniterによるPhwittrCodeIgniterによるPhwittr
CodeIgniterによるPhwittr
 
ぱっと見でわかるC++11
ぱっと見でわかるC++11ぱっと見でわかるC++11
ぱっと見でわかるC++11
 
ノンプログラマーでも明日から使えるJavaScript簡単プログラム 先生:柳井 政和
ノンプログラマーでも明日から使えるJavaScript簡単プログラム 先生:柳井 政和ノンプログラマーでも明日から使えるJavaScript簡単プログラム 先生:柳井 政和
ノンプログラマーでも明日から使えるJavaScript簡単プログラム 先生:柳井 政和
 
serverspecを使用したサーバ設定テストの実例
serverspecを使用したサーバ設定テストの実例serverspecを使用したサーバ設定テストの実例
serverspecを使用したサーバ設定テストの実例
 
入門 超絶技巧プログラミング !
入門 超絶技巧プログラミング !入門 超絶技巧プログラミング !
入門 超絶技巧プログラミング !
 
Dart のコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法について
Dart のコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法についてDart のコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法について
Dart のコード自動生成の仕組みと、コード自動生成のパッケージを自作する方法について
 
node+socket.io+enchant.jsでチャットゲーを作る
node+socket.io+enchant.jsでチャットゲーを作るnode+socket.io+enchant.jsでチャットゲーを作る
node+socket.io+enchant.jsでチャットゲーを作る
 
GitHub APIとfreshで遊ぼう
GitHub APIとfreshで遊ぼうGitHub APIとfreshで遊ぼう
GitHub APIとfreshで遊ぼう
 
Play2 scalaを2年やって学んだこと
Play2 scalaを2年やって学んだことPlay2 scalaを2年やって学んだこと
Play2 scalaを2年やって学んだこと
 
コードの自動修正によって実現する、機能開発を止めないフレームワーク移行
コードの自動修正によって実現する、機能開発を止めないフレームワーク移行コードの自動修正によって実現する、機能開発を止めないフレームワーク移行
コードの自動修正によって実現する、機能開発を止めないフレームワーク移行
 
わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61わんくま同盟大阪勉強会#61
わんくま同盟大阪勉強会#61
 

とあるプロジェクトのつらみなコード

  • 2. self.inspect [1] https://github.com/muramurasan/okuribito [2] http://poject.herokuapp.com/ ➢ gemのロゴを書いたり… ➢ たまにQiitaの記事を書いたり… ➢ 新卒で某SIerに就職 ➢ 渋谷の企業にてRuby on Railsを学ぶ ➢ 2016年10月にENECHANGEにjoin ➢ Railsと戯れてかれこれ4年目 ➢ よちよち.kt (次回:2019年2月2日) Name : Yuya Taki GitHub : yuyasat Qiita : yuyasat [1] 若輩者ですので、何卒優しくご教授いただければと 思います。 (commitはしていない) ➢ ○よ○よ風ゲームをReact.jsで実装し たり [2]
  • 4. 随所でredirect、render class OrdersController < ApplicationController def update # 送信ボタンが二つあり、送信ボタンのnameによって挙動を変える return send_to_api_2 if params.key?(:commit_api) update_order end private def update_order @order.assign_attributes(order_params) @order.status = :confirmed unless @order.status_changed? if @order.save redirect_to request.referer else common_show @error_messages = ['エラーがあります。'] render :show end end def send_to_api_2 return render_error('ステータスが確認済みではありません') unless @order.status_confirmed? # ・・・[1] send_to_api_1 # API2を呼ぶ前に、API1を呼ぶ return if performed? # ・・・[2] # Api2クラスはAPI2をコールするために実装したクラス response = Api2.post("/api2_url", params: { order: @order }) unless response.success? @error_messages = response.errors # 詳細な実装は、GET先のインターフェスによる raise # ・・・[3] end @order.status = :linked @order.save! @notice = 'API2に送信しました。' redirect_to request.referer rescue StandardError => e # ・・・[4] @notice = nil @error_messages = ['API2に失敗しました。'].concat(@error_messages || []) アクションの中でredirectやrenderは 局所化しよう。 詳しくは、「現場から学ぶ、Railsの Controllerアンチパターン」をお読みくだ さい。 随所でredirectやrenderを行うとアク ションの全貌が把握しにくくなる。 performed?を呼び出す必要となる実装 はもっと良い設計があるはず。
  • 5. STI AmpereChangeOrder STI CapacityChangeOrder ChangeOrder STI ConsumerChangeOrder 契約変更系のモデル STI LeaveOrder STI PlanChangeOrder アンペア変更 (eg. 40A → 50A) 容量変更 (eg. 60A → 7kVA) 需要家情報変更 (eg. 名義変更) 解約 料金プラン変更
  • 6. 各モデルの詳細画面のURLを返すメソッド(つらみ例) class ChangeOrder < ApplicationRecord def resource_path routes = Rails.application.routes.url_helpers case self when AmpereChangeOrder routes.ampere_change_order_path(self) when CapacityChangeOrder routes.capacity_change_order_path(self) when ConsumerChangeOrder routes.consumer_change_order_path(self) when LeaveOrder routes.leave_order_path(self) when PlanChangeOrder routes.plan_change_order_path(self) end end end STI AmpereChangeOrder STI ConsumerChangeOrder ChangeOrder せっかく継承を用いているのだから、うまく活用しよう。
  • 7. 各モデルの詳細画面のURLを返すメソッド(改善例) class AmpereChangeOrder < ChangeOrder def resource_path Rails.application.routes.url_helpers.ampere_change_order_path(self) end end class ChangeOrder < ApplicationRecord end 親クラスからはresource_pathメソッドを削除ない し、ダミーで実装 子クラスに記述 class ConsumerChangeOrder < ChangeOrder def resource_path Rails.application.routes.url_helpers.consumer_change_order_path(self) end end
  • 8. 同様の例 class ChangeOrder < ApplicationRecord def ampere_change_order? type == 'AmpereChangeOrder' end def consumer_change_order? type == 'ConsumerChangeOrder' end end class AmpereChangeOrder < ChangeOrder def ampere_change_order? true end end class ChangeOrder < ApplicationRecord def ampere_change_order? false end def consumer_change_order? false end end class ConsumerChangeOrder < ChangeOrder def consumer_change_order? true end end 親クラスはfalseで定義して、各子クラスに て必要なものだけをオーバーライドするよ う。
  • 9. DecoratorでActiveRecordの属性を上書きしている class ElecContractDecorator < Draper::Decorator delegate_all def status { 'before_check' => '申込内容確認前 ', 'completed' => '完了', }[object.status] end end viewやCSV出力、API連携で利用しているが、 decorateして いるのか、素のActiveRecordなのかわからない class ElecContractsController < ApplicationController def show @elec_contract = ElecContract.find(params[:id]).decorate end end Draperの使用例: DecoratorでActiveRecordの上書きはしないよ うにしよう。 viewで使う用のメソッドを定義する場合は、 suffixを用いるなど、チーム内で方針を決めよ う。 たとえば、enumerizeというgemを使っているの であれば、表示用のメソッドには _textが生える のでそれに合わせて enum以外も_textにする など。 decorateしてあっても、元の値を使いたい時もある  例)元の値を使おうとする   → decorateしてあるので、decorateされた値が出力   → objectを付けてdecorateを解除する   => 二度手間! 人間は、いつどのタイミングで decorateしたかがわからない
  • 10. モデルとDecoratorで同じメソッド名で異なる挙動 class OrderItem < ApplicationRecord def full_name(with_space: false) return "#{family_name}#{given_name}" unless with_space "#{family_name} #{given_name}" end end class OrderItemDecorateor < Draper::Decorator delegate_all def full_name(sep: '', prefix: '', kana: false) if corporate_name.present? send("corporate_name#{kana ? '_kana' : ''}") else full_name_person(sep: sep, prefix: prefix, kana: kana) end end def full_name_person(sep: '', prefix: '', kana: false) value = send("#{prefix}family_name#{kana ? '_kana' : ''}") || '' value += sep.presence || ' ' value + (send("#{prefix}given_name#{kana ? '_kana' : ''}") || '') end end モデルに定義された full_nameは引 数なしではスペースなしだが、 Decoratorで定義されたfull_nameは スペースありとなっている。 モデルとDecoratorで同じメソッド名を 定義しないようにしよう。 当初decoratorに実装していても、モ デル側でも使用したくなった場合は、 モデル側に移動しよう。
  • 11. RDBを使っているのに値をjsonに突っ込む create_table "change_orders", force: :cascade do |t| t.string "type", null: false t.bigint "elec_contract_id", null: false t.integer "status", default: -1, null: false t.jsonb "items", default: [], null: false t.datetime "created_at" t.datetime "updated_at" end たとえば、アンペア変更の申し込 みであれば姓名は不要になる。 %w[contract_name_type corporate_name corporate_name_kana family_name family_name_kana given_name given_name_kana phone_number_type phone_number_1 phone_number_2 phone_number_3 contact_type].each do |name| define_method(name) { items[name] } define_method("#{name}=") { |val| items[name] = val } end 変更系の申し込みには、必ずしも 全てのカラムが必要なわけではな い。 しかし、RDBを使っているのだか ら、ActiveRecordの旨味を十分に 活用するのならば、jsonにString として入れるのではなく、カラムを 利用した方が良い。データベース の型も利用できる。 nullが増えることに抵抗はある が、STIを使う以上、nullのカラム が増えるのは許容しよう。
  • 13. 無駄なレコードの生成(もともとの実装) def show @order_item = OrderItem.find_by(id: params[:id]) if @order_item.another_contacts.size.zero? @order_item.another_contacts << AnotherContact.new end end <%= o.fields_for :another_contacts do |ac| %> <%= ac.text_field :family_name %> <%= ac.text_field :given_name %> <% end %> CRMでは、申込があったものを管理するの で、@order_itemはすでにデータベースに永 続化されている。 class AnotherContact < ApplicationRecord enum contact_type: { invalidity: -1, }, _prefix: true end 永続化されたレコードに対して関連レコード を追加しようとすると関連レコードも永続化さ れてしまう。 正規に作られたレコードかどうかを判別する ためのenumが存在。 class OrderItem < ApplicationRecord has_many :another_contacts accepts_nested_attributes_for :another_contacts, reject_if: :all_blank, allow_destroy: true end
  • 14. 無駄なレコードの生成(改善例) <%= eo.fields_for :another_contacts, eo.object.another_contacts.blank? ? eo.object.another_contacts.new : nil do |ac| %> <%= ac.text_field :family_name %> <%= ac.text_field :given_name %> <% end %> def show @order_item = OrderItem.find_by(id: params[:id]) end Controllerでは何もせず、viewのfields_forにてanother_contactsがあるかないかで処理を変える ※新規生成の時は、この記述で OK。 <%= o.fields_for :another_contacts do |ac| %> <%= c.text_field :family_name %> <%= c.text_field :given_name %> <% end %> accepts_nested_attributes_forとfields_forを 用いる場合は、新規作成時と更新の時で挙動が異な るので実装時には注意しよう。
  • 15. まとめ ➢ 同じチームの人に意見を聞こう。 ➢ データベース設計は後戻りがしにくいので初期の段階できちんとレ ビューしてもらおう。 ➢ リリースが迫っていてレビューの時間が十分にとれない場合でも、あと からリファクタリングができるように、しっかりとテストを書こう。 ○ テストがあると開発速度がアップする