rails あるある

59
佐藤 竜之介(Ryunosuke SATO) Ruby札幌 札幌市中央区Ruby会議01 Rails あるある ~ 現場での悩みとアンチパターン ~ 2014.02.08 http://www.nce.co.uk/features/transport/network-rail-changing-track/8622890.article

Upload: ryunosuke-sato

Post on 19-Jan-2015

13.469 views

Category:

Technology


4 download

DESCRIPTION

札幌市中央区Ruby会議01 での発表資料です。

TRANSCRIPT

Page 1: Rails あるある

佐藤 竜之介(Ryunosuke SATO)Ruby札幌札幌市中央区Ruby会議01

Rails あるある~ 現場での悩みとアンチパターン ~

2014.02.08

http://www.nce.co.uk/features/transport/network-rail-changing-track/8622890.article

Page 2: Rails あるある

Ruby札幌From Sapporo, with Love for Ruby.

提供

Page 3: Rails あるある

自己紹介

Page 4: Rails あるある

@tricknotesI am a software developer who love JavaScript and Ruby.

http://tricknotes.hateblo.jp/

Page 5: Rails あるある
Page 6: Rails あるある

I love OSS

Page 7: Rails あるある

札幌市中央区Ruby会議01

Page 8: Rails あるある

Sapporo.jshttp://sapporojs.org/

Page 9: Rails あるある

よろしくお願いします

Page 10: Rails あるある

佐藤 竜之介(Ryunosuke SATO)Ruby札幌札幌市中央区Ruby会議01

Rails あるある~ 現場での悩みとアンチパターン ~

2014.02.08

http://www.nce.co.uk/features/transport/network-rail-changing-track/8622890.article

Page 11: Rails あるある

今日の話

Rails には便利で魅力的な機能がたくさんありますそれらを使えば、簡単にアプリケーションを作ること

ができますしかし、使いどころを間違えると、あとで変更に弱くなってしまい開発が苦しくなることがあります。

自分が体験した”あるある”ネタを紹介しつつ、メンテナンスしやすいアプリケーションについて

考えてみます

Page 12: Rails あるある

すでに Rails をやっているひと、これからやろうとしているひとにとって、快適に開発をするため

のヒントになれば嬉しい

* 状況設定は架空のものです *

Page 13: Rails あるある

対象バージョン

* Ruby 2.0, 2.1* Rails 3.2, 4.0

Page 14: Rails あるある

あるある集

Page 15: Rails あるある

‘‘default_scope

社員レコードは論理削除で...

あるある①

Page 16: Rails あるある

状況

* 社員の勤怠システムを考える* 社員は退職することができる* 社員が退職した場合、社員は社員一覧に表示されない* ただ、社員の勤怠履歴を参照することはできる必要がある* そのため、社員レコードに対しては論理削除を適用する* `default_scope` !!

Page 17: Rails あるある

default_scope とは

“デフォルト” の検索条件を指定できる機能

class User < ActiveRecord::Base default_scope lambda { where(deleted_at: nil) }end

User.all

User.where(deleted_at: nil)

Page 18: Rails あるある

default_scope とは

“デフォルト” の検索条件を指定できる機能

@user.update_attribute :deleted_at, DateTime.now

@user.destroy

http://api.rubyonrails.org/classes/ActiveRecord/Scoping/Default/ClassMethods.html#method-i-default_scope

Page 19: Rails あるある

Staff Attendance1 n

Page 20: Rails あるある

そもそも、”削除” ではないのでは...??

@attendance.staff #=> nil

Staff.unscoped { @attendance.staff #=> <#Staff>}

問題

Page 21: Rails あるある

解決1

退会/移動などを state で持っておいて、必要に応じて scope をかけるclass Staff < ActiveRecord::Base scope :only_tenured, lambda { where(state: :tenured) }end

Staff.all

Staff.only_tenured

Page 22: Rails あるある

解決2

まったく参照しない = 不要なデータ不要なデータは実際に消してしまう

Staff.destroy

Page 23: Rails あるある

* “default” は “default”* ある条件のときに解除したくなるものは “default” ではない* まったく参照しないなら DB に残っている 必要はない

ポイント

Page 24: Rails あるある

‘‘serialize

ブログ記事にタグをつけたい

あるある②

Page 25: Rails あるある

* ブログシステムを考える* 記事にはタグを登録することができる* タグは自由入力のテキストで、 ひとつの記事に複数のタグを付けることができる* すでに多くのテーブルが存在していて、 極力テーブルを増やしたくない* `serialize` !!

状況

Page 26: Rails あるある

serialize

Ruby のオブジェクトを YAML にシリアライズして、データベースのカラムに保存するclass Post < ActiveRecord::Base serialize :tagsend

@post = [email protected] = ['Ruby', 'chuork01']@post.save

Post.last.tags #=> ['Ruby', 'chuork01']

http://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize

Page 27: Rails あるある

問題

特定のタグをもっている記事だけを取得したいタグ毎に記事を一覧したいときに不便

YAML なので、 SQL で検索できない

Post.where(tags: 'LIKE %Ruby%')

Text 型なので Like 検索はできるけど...

Page 28: Rails あるある

解決1

Tagging TagPost 1 n 1n

タグを別のテーブルに分ける

Page 29: Rails あるある

解決1

タグを別のテーブルに分けるclass Post < ActiveRecord::Base has_many :taggings has_many :tags, through: :taggingsend class Tagging < ActiveRecord::Base belongs_to :post belongs_to :tagend

class Tag < ActiveRecord::Base has_many :taggings has_many :posts, through: :taggingsend

@tag.posts

Page 30: Rails あるある

解決2

配列型を利用する(データベースがサポートしていれば)

class AddTagsToPosts < ActiveRecord::Migration def change add_column :posts, :tags, :array endend

Post.where("'Ruby' = ANY (tags)")

PostgreSQL の例

Page 31: Rails あるある

* Ruby の世界でしか扱えないデータは扱いづらい* 適切なデータモデルを選択しましょう

ポイント

Page 32: Rails あるある

‘‘save(validate: false)

一時保存のときは入力チェックをしたくない

あるある③

Page 33: Rails あるある

* 会員登録できるサービスを考える* Email だけあれば仮登録できるが、 本登録では名前などその他の情報が必要* ひとまずレコードだけ作りたい* `save(validate: false)`

状況

Page 34: Rails あるある

save(validate: false)

validation をスキップして保存することができる

http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save

Page 35: Rails あるある

* 不完全な状態のデータが保存される* 一部の validation だけ実行するのが困難* データベースに制約をかけられなくなる

問題

Page 36: Rails あるある

解決1

* 状況に応じた validation を行なう

class Staff < ActiveRecord::Base validates :name, presence: { on: :registration }end

@staff.save(context: :registration)

Page 37: Rails あるある

* “仮登録”/”本登録” 状態を持たせて validation

解決2

class Staff < ActiveRecord::Base validates :name, presence: { if: :registration? }

def registration? state == 'registration' endend

@staff.state = 'registration'@staff.save

Page 38: Rails あるある

ポイント

* データを保存するために、 チェックが必須な項目をスキップしてしまう* 不完全なデータが登録されてしまう

Page 39: Rails あるある

‘‘as_json

他のシステムと連携するための JSON の API を提供したい

あるある④

Page 40: Rails あるある

状況

* 人事評価システムを考える* 社員の評価を他システムに対しても提供する* 連携のためのデータを JSON で出力する* `as_json`

Page 41: Rails あるある

モデルを JSON へに変換した場合のデータフォーマットを定義するRails が as_json を呼び出して JSON に出力してくれる

as_json

http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

Page 42: Rails あるある

as_json

class User < AvtiveRecord::Base def as_json { id: id, name: name, evaluations: evaluations.as_json } end end

class UsersController < ApplicationController def index @users = User.all end end

Page 43: Rails あるある

as_json

GET /users.json

[{ “id”: 1, “name”: “tricknotes”, “evaluations”: [{ ... }, { ... }]}]

http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json

Page 44: Rails あるある

データの表示/非表示の扱いが難しい* ログインしていると見える情報* 本人だけ見える情報* 権限によって見える情報* 一覧だと不要で、詳細画面だと必要な情報

例: 部長は部下の評価を閲覧できるが、部員は本人の評価のみを閲覧できる

問題

Page 45: Rails あるある

問題

JSON とはデータの表現形式-> View 層の仕事

current_user を参照したくなるので、Model#as_json だと扱いが難しい

context に依存する変換はモデルの仕事ではない

Page 46: Rails あるある

問題

その他のデータ形式をサポートしたくなった場合、似たようなメソッドが並ぶ* as_csv* as_json...

Page 47: Rails あるある

View として JSON を出力する解決

jbuilder

json.array!(@users) do |user| json.extract! user, :id, :name, :evaluationsend

* app/views/users/index.json.jbuilder

Page 48: Rails あるある

ポイント

* Context によって変化するロジックを モデルに持たせない* データの表示形式はモデルに含めない

Page 49: Rails あるある

他にもまだまだ...

Page 50: Rails あるある

* STI* session にオブジェクトを保存* 最終更新日 = updated_at* monkey patch* before/after callback* gem の version 固定 ...

Page 51: Rails あるある

for more information...

Page 52: Rails あるある

もっと複雑な現実問題に対応するためのヒント

Page 53: Rails あるある

http://www.amazon.co.jp/dp/0321604814

Rails AntiPatterns

Page 54: Rails あるある

http://magazine.rubyist.net/?0041-RailsTheBadPartsRuby on Rails: The Bad Parts

Page 55: Rails あるある

まとめ

Page 56: Rails あるある

最初から完璧な設計をするのは困難

アカンと思ったら引き返す/直す勇気を!

機能自体が悪かというとそうでもなくて、使いドコロを間違うと辛い、という話

Rails の機能自体を理解すること、作るものを理解することが大事!!

Page 57: Rails あるある

パッと見て便利そうな機能でも、その機能の意味と、

ドメインを考えて組み立てるの大事!!

状況によって適切な選択かどうかは変わってくる

Page 58: Rails あるある

Rails の機能自体への理解対象領域への理解

作ってわかることもある変更する勇気

Page 59: Rails あるある

http://www.flickr.com/photos/sakura-kame/479871795/

一歩、一歩