Rails Validationについて
データ管理はしっかりやりたい
DBにデータが保存される時に、そのデータが正しいかどうかを検証する仕組みをバリデーションという。
バリデーションのトリガー
バリデーションが走るメソッド群
- create
- create!
- save
- save!
- update
- update!
上記のメソッドはオプションでバリデーションをスキップすることも可能
save(validate: false)
バリデーションがスキップされるメソッド群
- decrement!
- decrement_counter
- increment!
- increment_counter
- toggle!
- touch
- update_all
- update_attribute
- update_column
- update_columns
- update_counters
Validate
通常のバリデーションです。 modelに定義して利用します。
class Recipe < ActiveRecord::Base # これでtitleがnullだとデータのInsertが行われず、弾かれます validates :title, presence: true end
valid?
とinvalid?
が用意されておりboolean型の値が返ってきます。
Recipe.new(title: "レシピタイトル").valid? #=> true Recipe.new(title: null).valid? #=> false Recipe.new(title: "レシピタイトル").invalid? #=> false Recipe.new(title: null).invalid? #=> true
カスタムValidate
単体のmodelで独自のvalidationを定義し、使いたい場合に利用する。
validate link_url_cannot_be_load def link_url_cannot_be_load if @obj.url.include?(BASIC_DOMAIN) errors[:link_url] << "でURLは利用することができません。可能ドメインか確認してください" end end
カスタムValidator
複数のmodelで独自のvalidationを定義し、使いまわしたい場合に利用する。
もしくは単体のmodelでも複数の独自validationを定義し使いたい場合に利用する。
開発者にもよるが、自分は上記の理由と定義を決めている。ここは個人の好みにもよるのかもしれない・・・
そしてRailsのapp/以下にvalidators
ディレクトリを作ってその中にカスタムvalidatorをまとめておく
カスタムバリデータはActiveModel::Validatorを拡張したクラス。
クラスにはvalidateメソッド
が実装されている必要がある。
class Recipe << ActiveRecord::Base include ActiveModel::Validations validates_with ContentValidator end class ContentValidator < ActiveModel::Validator # 必ずvalidateメソッドを実装する # 引数にはレコードを1つ取り、それに対してのバリデーションを実行する def validate(record) if record.description.blank? record.errors[:description] << "が空です" end end end
- Validates
- カスタムValidate
- カスタムValidator
3つのバリデーションを使いこない堅牢なシステムを構築していきたい。
データまわりの定義は厳密に決めていくことが大切だと自分は考えています。
理由としては一度データが入ったデータベースは変更が難しくなってしまうからです。
アプリケーションレイヤーのvalidation制御はもちろん。
DB側での外部キー制約、ユニークキー制約、複数の一意制約、null制約などルールをしっかり決めておくことが大切。
新規開発においては、使用がコロコロ変わるのはしょっちゅうあるのである程度変更があることを前提に開発していくと良い。
ただ、リリース段階まで行く時にはきっちりと制約周りは定義することが守るべきルールとしてある。
Rails, Carrierwave, MiniMagick, Piet, 画像アップロード, 画像圧縮
Railsで画像をアップロードし、かつ画像圧縮も行いたい時の実装をメモで残す。
環境
OSX 10.11.6
Rails 5.0.0
Ruby 2.3
Gem
実装に必要なGemをインストール
gem "carrierwave" gem "piet" gem "mini_magick"
bundle install --path vendor/bundle
Piet
Carrierwaveのextensionとして画像圧縮を行えるようにするGem
圧縮を行う類似のGemがいくつか存在するがPietを採用
トータルダウンロード数 比較
piet : 242,713 carrierwave-imageoptimizer : 97,947
jpegoptim
.jpeg画像の圧縮で使用するパッケージ
brew install jpegiotim
pngquant
.png画像の圧縮で使用するパッケージ
画像圧縮を行うPietではpngquantを用いて圧縮を行えるように他のGemを利用している。
Pietのドキュメントに導入方法が記載
pngquantが圧縮効率が良いのでこちらを採用する。
圧縮効率の比較資料
brew install pngquant
アップローダーの作成
bundle exec rails g uploader image #=> app/uploaders/image_uploader.rb
上記のコマンドを実行しアップローダーを作成します。
class ImageUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick include Piet::CarrierWaveExtension process quality: 80 # Large size process resize_to_limit: [640, 640] process :custom_optimize # Midium size version :midium do process resize_to_limit: [300, 300] process :custom_optimize end # Small size version :small do process resize_to_limit: [200, 200] process :custom_optimize end def extension_white_list %w(jpg jpeg gif png) end def mimetype IO.popen(["file", "--brief", "--mime-type", path], in: :close, err: :close) { |io| io.read.chomp.sub(/image\//, "") } end def custom_optimize case mimetype when "png" then pngquant when "jpeg", "gif" then optimize(quality: 90) end end
custom_optimize メソッド独自で定義し、画像の拡張子によって実行する圧縮を分岐させるような形に。
initialize以下のcarrierwave.rbにも定義
CarrierWave.configure do |config| # hogehoge end
module CarrierWave module MiniMagick def quality(percentage) manipulate! do |img| img.quality(percentage.to_s) img = yield(img) if block_given? img end end end end
流れで画像圧縮のGemを作ったので興味があればこちらも。
リファクタリング:Rubyエディション 読んだ
- 作者: Jay Fields,Shane Harvie,Martin Fowler,Kent Beck,長尾高弘
- 出版社/メーカー: アスキー・メディアワークス
- 発売日: 2010/02/27
- メディア: 大型本
- 購入: 9人 クリック: 321回
- この商品を含むブログ (50件) を見る
最近は「コードの質を高め、生産性の高いコードを書く」ことを1つの目標にしていて、綺麗なコードの書き方や設計周りの本を読んでいます。
「リファクタリング:Rubyエディション」は前々から読んでみたいと思っていたのですが、1冊4800円もするので買うのに少しビビってました。(学生財布)
しかしいざ、買って読んでみるとなんでもっと早く買わなかったんだと。4800円をケチってた自分を叱りたくなりました。
内容はもう目から鱗で。今まで自分が思っていた、考えていたことをうまく言語化してくれていて本当に素晴らしかったです(特にカタログ周り)
メタプログラミングRubyも素晴らしい本でしたが、リファクタリング:Rubyも負けないぐらいいい本です。 Rubyエンジニアの人たちの必読書になると思います。
感想
内容は主に4つの構成
1.リファクタリング -最初のサンプル-
リファクタリングの重要性を理解するためにも最初に大きくサンプルコードが載ってあり非常にわかりやすかったです。 学び始めのとっかかりとしては最高。
1つ1つのリファクタリングの積み重ねでここまで綺麗な構成に変わるものだと、before afterで見せてくれます。
また、UMLでも構成を表現してくれるので処理のフローが理解しやすいです。 実務でも構成だったり、処理のフローを整理したい場合の模範にできるなと思いました。
2.リファクタリングの原則
リファクタリングとはこういうものだよ。という概念的な部分の説明
- なぜリファクタリングするのか?
- なぜリファクタリングすべきなのか?
- いつリファクタリングすべきなのか?
- リファクタリングの恩恵はなにか?
- リファクタリングのデメリットはなにか?
- リファクタリングと設計
- リファクタリングとパフォーマンス
これらをわかりやすく説明しています。
「リファクタリングを導入すると、強調点が変わる。事前設計をすることに変わりはないが、完璧な解を探そうとはしなくなる。まずまず妥当な解であれば良い。」
3.コードの臭い
コードの臭いは、リファクタリングをするべき箇所を臭いという表現で説明しています。 3があるおかげで4のカタログの内容理解がグッと高まります。(バイキルト的な)
4.リファクタリングのカタログ
リファクタリングのテクニックをカタログ形式で示してくれています。
カタログを読むことでテクニックが言語化されてまとめられてとても良かったです。
全て頭に入っているわけではないので、今後リファクタリングを進める中で全てマスターしていきたい。
一覧は覚えたテクニックリスト
メソッドの構成方法
- メソッドの抽出
- メソッドのインライン化
- 一時変数のインライン化
- 一時変数から問い合わせメソッドへ
- 一時変数からチェインへ
- 説明用変数の導入
- 一時変数の分割
- 引数への代入の除去
- メソッドからメソッドオブジェクトへ
- アルゴリズム変更
- ループからコレクションクロージャメソッドへ
- 名前付き引数の導入
- 名前付き引数の除去
- 使われていないデフォルト引数の除去
- 動的メソッド定義
オブジェクト間でのメンバの移動
- メソッドの移動
- フィールドの移動
- クラスの抽出
- クラスのインライン化
- 委譲の隠蔽
- 横流しブローカーの除去
データの構成
- 自己カプセル化フィールド
- データ値からオブジェクトへ
- 値から参照へ
- 参照から値へ
- 配列からオブジェクトへ
- マジックナンバーからシンボル定数へ
- タイプコードからポリモーフィズムへ
- タイプコードからモジュールのextendへ
- タイプコードからState/Strategyへ
- 属性初期化の遅延実行
- 属性初期化の先行実行
条件式の単純化
- 条件文の分解
- 条件分岐の組み替え
- 条件式の統合
- 重複する条件分岐の断片の統合
- 制御フラグの除去
- 条件分岐のネストからガード節へ
- 条件分岐からポリモーフィズムへ
Rails ActiveRecordで値が変更する前後の値を取得する
ActiveRecordで値が変更する前後の値を取得することがあったのでメモ
Rails 2.1あたりからActiveRecordの変更前後の値を取得する機能が備わっている。
method | 意味 |
---|---|
changed? | 変更されているかどうか |
changed | 変更されているattribute名の配列 |
changeds | 変更されているattribute名と値のハッシュ。値は変更前後の値を配列で。 |
{attr}_changed? | {attr}が変更されているかどうかの判定 |
{attr}_was | {attr}の変更前の値。変更されていない場合は元の値 |
{attr}_change | {attr}の変更前後の値の配列。変更されていなかったらnil |
{attr}_will_change! | {attr}を変更することを明示。 |
attrには属性名が入ります。nameの場合 name_wasで変更前の値を取得することができる
使用例
Railsのコールバック処理で複数の配列データを1つに統一したい場合に利用する
コールバック処理
- before_save
- before_update
- after_update
etc...
before_update処理でmerge_tagメソッドを実行する
merge_tagメソッドでは既に登録されていた既存の値(tag)と、
変更が加えられた新規の値tag_wasを組み合わせる(merge)処理が行われ、
self.tagに代入される
before_update :merge_tag def merge_tag # tag_wasには元々のtagの値が入り、 # mergeの引数のtagは新たなtagの値になる self.tag = tag_was.merge(tag) end
もっと簡単な例で動きを説明すると
tag = Tag.find(1) #=> #<Tag:0x007fc35ee6fc98 id: 1, name: "ドラえもん", created_at: Wed, 11 Apr 2016 20:21:02 JST +09:00> tag.name #=> "ドラえもん" tag.changed? #=> false tag.name = "のび太" #=> "のび太" tag.changed? #=> true tag.name_was #=> "ドラえもん"
のように、tag.name_wasで変更前の値を取得することができています。
ActiveRecordのちょっとした機能ですが、応用的な処理を実装する時には便利に使えるかなと思います。
Singletonパターン Ruby
シングルトンパターンとは
実装
Rubyにはsingletonと呼ばれるモジュールが定義されているためこれをrequireし、 クラスにincludeすることでシングルトンクラスを定義することができる。
require 'singleton' # シングルトン class SingletonObject # instanceメソッドが定義される include Singleton attr_accessor :counter def initialize @counter = 0 end end obj1 = SingletonObject.instance obj1.counter += 1 puts(obj1.counter) # 1 obj2 = SingletonObject.instance obj2.counter += 1 puts(obj2.counter) # 2 # 前回の+1が引き継がれている obj3 = SingletonObject.new # private method `new` called for SingletonObject:Class # newメソッドはプライベートメソッドとして定義される
acts_as_listとjQueryで並べ替えを実装する
jQueryのsortableを用いて並べ替えを実装する Rails
acts_as_list
モデルの並び順を簡単に操作できるようにするGem
昔のRails1系あたりでは標準で搭載されていたらしいが、 途中でGemとして切り離されて現在は独立したGemとして存在する。
引用:acts_as_list: gem か plugin か
本家がこちら。 https://github.com/swanandp/acts_as_list
導入の仕方などはドキュメントにわかりやすく書かれているのでそこを参考すると良さそう。 acts_as_listでは positionカラム がデータのソート基準となるよう決まっている。 なのでacts_as_listを使いたい場合は、対象とするテーブルのカラムにposition絡むを追加しましょう。
# has_manyでtodo_itemsを持つ親モデル class TodoList < ActiveRecord::Base has_many :todo_items, -> { order(position: :asc) } end # belongs_toでtodo_listに紐づく子モデル class TodoItem < ActiveRecord::Base belongs_to :todo_list # scopeで定義する acts_as_list scope: :todo_list end todo_list = TodoList.find(...) # move_to_bottomで値を操作1,2,3とあるならば1が3になり2,3が繰り上がる todo_list.todo_items.first.move_to_bottom # move_higherで1,2,3,とあるならば2,1,3と変更される todo_list.todo_items.last.move_higher
また、最近の紹介記事ですと、Qiitaに上がっているjnchitoさんの記事が素晴らしくわかりやすかったです。 実装の際に参考にさせてもらいました。 Rails 4で作るドラッグアンドドロップで表示順を変更できるサンプルアプリ(スクリーンキャスト付き)
動画は見ていませんが初心者にもわかりやすいように筆者が参考にした記事や、 動画を用いての実装方法まで紹介していてさすがですね。
今回自分は ranked_model を使った実装を行っていないので機会があればそちらも使ってみたいなと思います。 どうやらranked-modleの方がパフォーマンスが良いようです。(検証はしてません)
Railsで順番を管理するgemとして、ranked-model gemを使います。 acts_as_listも有名ですが、ranked-modelの方がパフォーマンス的に優れています。
ドラッグ&ドロップで並べ替えを行う実装
jQueryのsortableを用いて、ドラッグ&ドロップで操作を行う実装を紹介します。 上記でacts_as_listを紹介しましたが、今回の実装では用いません。 acts_as_listではなくjQueryのsortableを用いることでも並べ替えを良い感じにできるんだぜってことを紹介したいと思います。
# Railsで良い感じに実装するためのGem達 gem 'jquery-ui-rails' gem 'jquery-turbolinks'
// application.jsで読み込みます //= require jquery-ui/effect-highlight //= require jquery-ui/sortable
D&Dを行った際の処理 $('.table-sortable').sortable でsortableを適用させる。 D&Dをトリガーにしてajax処理を行いDB側のposition値を変更する使用
// table_sort.js $(function() { return $('.table-sortable').sortable({ axis: 'y', items: '.sentence', update: function() { // ajax処理で並べ替え後の値をサーバー側に渡す $.ajax({ type: 'POST', url: '/sort', dataType: 'json', // $('.table-sortable').sortable('serialize') で変更後の値を取得 data: $('.table-sortable').sortable('serialize') }); }, // ここはおまけで変更完了エフェクトを付け足す stop: function(e, ui) { return ui.item.children('td').effect('highlight'); } }); });
controller側の実装
def sort Content.all.each do |sentence| content.position = params[:content].index(content.position.to_s) + 1 content.save end render nothing: true end
model側での実装 値を追加した際にpositionカラムに値の最大値をセットするために before_createにset_positionメソッドを定義します
before_create :set_position def set_position if max =Content.where(hoge_id: hoge_id).maximum(:position) self.position = max + 1 end end
slimの実装 id="position_#{content.position}"でデフォルト値をセット
table.table.table-striped.table-sortable thead tr th id th name th description tbody - @contents.each do |content| tr.sontent id="position_#{content.position}" td= content.id td= content.name td= content.description
簡単にイケてる実装ができて便利!
参考
Rails 4で作るドラッグアンドドロップで表示順を変更できるサンプルアプリ(スクリーンキャスト付き)