Rails あるある
-
Upload
ryunosuke-sato -
Category
Technology
-
view
13.472 -
download
4
description
Transcript of Rails あるある
佐藤 竜之介(Ryunosuke SATO)Ruby札幌札幌市中央区Ruby会議01
Rails あるある~ 現場での悩みとアンチパターン ~
2014.02.08
http://www.nce.co.uk/features/transport/network-rail-changing-track/8622890.article
Ruby札幌From Sapporo, with Love for Ruby.
提供
自己紹介
@tricknotesI am a software developer who love JavaScript and Ruby.
http://tricknotes.hateblo.jp/
I love OSS
札幌市中央区Ruby会議01
よろしくお願いします
佐藤 竜之介(Ryunosuke SATO)Ruby札幌札幌市中央区Ruby会議01
Rails あるある~ 現場での悩みとアンチパターン ~
2014.02.08
http://www.nce.co.uk/features/transport/network-rail-changing-track/8622890.article
今日の話
Rails には便利で魅力的な機能がたくさんありますそれらを使えば、簡単にアプリケーションを作ること
ができますしかし、使いどころを間違えると、あとで変更に弱くなってしまい開発が苦しくなることがあります。
自分が体験した”あるある”ネタを紹介しつつ、メンテナンスしやすいアプリケーションについて
考えてみます
すでに Rails をやっているひと、これからやろうとしているひとにとって、快適に開発をするため
のヒントになれば嬉しい
* 状況設定は架空のものです *
対象バージョン
* Ruby 2.0, 2.1* Rails 3.2, 4.0
あるある集
‘‘default_scope
社員レコードは論理削除で...
あるある①
状況
* 社員の勤怠システムを考える* 社員は退職することができる* 社員が退職した場合、社員は社員一覧に表示されない* ただ、社員の勤怠履歴を参照することはできる必要がある* そのため、社員レコードに対しては論理削除を適用する* `default_scope` !!
default_scope とは
“デフォルト” の検索条件を指定できる機能
class User < ActiveRecord::Base default_scope lambda { where(deleted_at: nil) }end
User.all
User.where(deleted_at: nil)
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
Staff Attendance1 n
そもそも、”削除” ではないのでは...??
@attendance.staff #=> nil
Staff.unscoped { @attendance.staff #=> <#Staff>}
問題
解決1
退会/移動などを state で持っておいて、必要に応じて scope をかけるclass Staff < ActiveRecord::Base scope :only_tenured, lambda { where(state: :tenured) }end
Staff.all
Staff.only_tenured
解決2
まったく参照しない = 不要なデータ不要なデータは実際に消してしまう
Staff.destroy
* “default” は “default”* ある条件のときに解除したくなるものは “default” ではない* まったく参照しないなら DB に残っている 必要はない
ポイント
‘‘serialize
ブログ記事にタグをつけたい
あるある②
* ブログシステムを考える* 記事にはタグを登録することができる* タグは自由入力のテキストで、 ひとつの記事に複数のタグを付けることができる* すでに多くのテーブルが存在していて、 極力テーブルを増やしたくない* `serialize` !!
状況
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
問題
特定のタグをもっている記事だけを取得したいタグ毎に記事を一覧したいときに不便
YAML なので、 SQL で検索できない
Post.where(tags: 'LIKE %Ruby%')
Text 型なので Like 検索はできるけど...
解決1
Tagging TagPost 1 n 1n
タグを別のテーブルに分ける
解決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
解決2
配列型を利用する(データベースがサポートしていれば)
class AddTagsToPosts < ActiveRecord::Migration def change add_column :posts, :tags, :array endend
Post.where("'Ruby' = ANY (tags)")
PostgreSQL の例
* Ruby の世界でしか扱えないデータは扱いづらい* 適切なデータモデルを選択しましょう
ポイント
‘‘save(validate: false)
一時保存のときは入力チェックをしたくない
あるある③
* 会員登録できるサービスを考える* Email だけあれば仮登録できるが、 本登録では名前などその他の情報が必要* ひとまずレコードだけ作りたい* `save(validate: false)`
状況
save(validate: false)
validation をスキップして保存することができる
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-save
* 不完全な状態のデータが保存される* 一部の validation だけ実行するのが困難* データベースに制約をかけられなくなる
問題
解決1
* 状況に応じた validation を行なう
class Staff < ActiveRecord::Base validates :name, presence: { on: :registration }end
@staff.save(context: :registration)
* “仮登録”/”本登録” 状態を持たせて validation
解決2
class Staff < ActiveRecord::Base validates :name, presence: { if: :registration? }
def registration? state == 'registration' endend
@staff.state = 'registration'@staff.save
ポイント
* データを保存するために、 チェックが必須な項目をスキップしてしまう* 不完全なデータが登録されてしまう
‘‘as_json
他のシステムと連携するための JSON の API を提供したい
あるある④
状況
* 人事評価システムを考える* 社員の評価を他システムに対しても提供する* 連携のためのデータを JSON で出力する* `as_json`
モデルを JSON へに変換した場合のデータフォーマットを定義するRails が as_json を呼び出して JSON に出力してくれる
as_json
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json
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
as_json
GET /users.json
[{ “id”: 1, “name”: “tricknotes”, “evaluations”: [{ ... }, { ... }]}]
http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html#method-i-as_json
データの表示/非表示の扱いが難しい* ログインしていると見える情報* 本人だけ見える情報* 権限によって見える情報* 一覧だと不要で、詳細画面だと必要な情報
例: 部長は部下の評価を閲覧できるが、部員は本人の評価のみを閲覧できる
問題
問題
JSON とはデータの表現形式-> View 層の仕事
current_user を参照したくなるので、Model#as_json だと扱いが難しい
context に依存する変換はモデルの仕事ではない
問題
その他のデータ形式をサポートしたくなった場合、似たようなメソッドが並ぶ* as_csv* as_json...
View として JSON を出力する解決
jbuilder
json.array!(@users) do |user| json.extract! user, :id, :name, :evaluationsend
* app/views/users/index.json.jbuilder
ポイント
* Context によって変化するロジックを モデルに持たせない* データの表示形式はモデルに含めない
他にもまだまだ...
* STI* session にオブジェクトを保存* 最終更新日 = updated_at* monkey patch* before/after callback* gem の version 固定 ...
for more information...
もっと複雑な現実問題に対応するためのヒント
http://www.amazon.co.jp/dp/0321604814
Rails AntiPatterns
http://magazine.rubyist.net/?0041-RailsTheBadPartsRuby on Rails: The Bad Parts
まとめ
最初から完璧な設計をするのは困難
アカンと思ったら引き返す/直す勇気を!
機能自体が悪かというとそうでもなくて、使いドコロを間違うと辛い、という話
Rails の機能自体を理解すること、作るものを理解することが大事!!
パッと見て便利そうな機能でも、その機能の意味と、
ドメインを考えて組み立てるの大事!!
状況によって適切な選択かどうかは変わってくる
Rails の機能自体への理解対象領域への理解
作ってわかることもある変更する勇気
http://www.flickr.com/photos/sakura-kame/479871795/
一歩、一歩