おもしろwebサービス開発日記

Ruby や Rails を中心に、web技術について書いています

最近のお仕事について

ここ半年ほど、Clinical Platform さんのお手伝いをしています。最近エンジニア募集を開始したので、どんな感じでお仕事を進めているかを簡単に紹介しようと思います。

作っているもの

クリニック向けのクラウド電子カルテです。

Ruby や Rails のバージョン

Railsエンジニアが会社を選ぶときは使っているRubyとRailsのバージョンで決めるらしいので一応。

  • Rails 4.2.4
  • Ruby 2.2.2
  • PostgreSQL 9.3.6

Ruby と PostgreSQL のバージョンが少し古いのは、本番環境である Engine Yard のバージョンに合わせているためです。gem 類は、週に一回自動で bundle update のプルリクエストが生成される仕組みが作られているためほぼ全て最新バージョンです。

(2015/11/09追記)Engin Yard が Ruby 2.2.3 に対応したので、現時点では 2.2.3 になっています。

フロントエンドは vue.js を使っています。特にカルテ内では多くの情報を素早く編集、閲覧する必要があるので多用しています。 今のエンジニアは僕含めサーバサイドが得意なメンバーが多いので、苦手なフロントエンド成分を補充するために週1で社内勉強会を開催しています。現在のテーマは React です(今の所 React に書き換える予定はありません)。

開発の進め方

普通のことを普通に進めています。Railsでウェブサービスを開発する、といったときにみなさんが想像するようなことは大体やってるはずです。これ不便だな、こうやったら便利になるのでは?と思ったら10分後にはpivotalでチケット化されていて、誰かがカッとなったときに導入されます。

  • Pivotal Tracker でチケット駆動開発
  • esa でドキュメント管理&日報管理
  • GitHub でソースコードレビュー
  • 通知類とチャットは Idobata に集約
  • CircleCI で CI まわす
    • 自動で rubocop, simplecov, brakeman などを動かして GitHub にコメントをつける
  • 朝会
  • 週一の全体ミーティング&KPT

ただ、技術的にチャレンジングなことをしている!エッジな技術をどんどん採用している!…というわけではなくて、どうやったらユーザである医療従事者の方々にとって便利なサービスになるか、を一番重視しています。

クリニックによってカルテの使い方は全然異なるので、それらを置き換える電子カルテの仕様を決めることは難しく、トライアンドエラーの連続になります。最初から決まったものを作るのではなくて、小さく作って様子を見てから大きく育てていく。当然、仕様も降って湧いてくるというより、みんなで考えて決めていく感じです。

エッジな技術に囲まれて仕事をしたい!という人よりは、チームで楽しく開発したいとか、ユーザに価値を届けるために知恵を絞るのが好きとかいう人が向いてると思います。

もちろん、新技術を採用する明確なメリットがあるなら、躊躇なく採用していくつもりです!

雰囲気

楽しくやっています。個人的にはもともとの知り合いばかり周りにいるのでやりやすいという面もありますが、それを差し引いても良い雰囲気で開発できていると思います。

チームがどんな雰囲気で開発をしているか、チームメイトである @tatsuoSakurai のエントリも参考になると思います。

カッとなるチーム - 開発チームのモチベーションをチョットあげる工夫のお話 - tatsuo.sakurai's blog

最後に

興味ある方は、Wantedly 経由、もしくは @netwillnet、その他関係者っぽい人に声かけてください!採用決まったら一緒にペアプロしましょう (\( ⁰⊖⁰)/)

医療界にインパクトを与えるクラウドサービスを一緒に開発しませんか? - クリニカル・プラットフォーム株式会社の求人 - Wantedly

Rails 4.2 で postgresql を使う場合の注意事項

Rails 4.2 では、DB の string 型が255文字制限ではなくなりました。もともとあまり意味がなかったらしいです(詳しい人補足求む)。対象は postgresql と sqlite で、mysql は対象外です。

PostgreSQL, remove varchar limit. by senny · Pull Request #14579 · rails/rails

Rails 4.2 以降は、string は文字数制限未指定 の character varying 型に変換されます。ただそれって、text 型と何が違うんでしょうね。

と、調べた限りでは違いはなさそうでした。

string - PostgreSQL: Difference between text and varchar (character varying) - Stack Overflow

ちなみに Rails 4.1 で string 型のカラムに256文字以上の文字列を入れて保存するとこのようになります(PostgreSQLは 9.4.1 でためしてます)。Rails 4.2 だとエラーにならず普通に保存されます。

ActiveRecord::StatementInvalid: PG::StringDataRightTruncation: ERROR:  value too long for type character varying(255)

この挙動を頼りにして、これまで string 型に validation をかけてこなかったみなさん、ちゃんと validation かけていきましょうね(\( ⁰⊖⁰)/)

補足 character varying(n) の挙動について

上記では制限している文字数以上の文字を入れようとするとエラーと書きましたが、例外があります。超過分の文字列が空白の場合はそれが切り詰められて格納され、エラーにはならないようです。SQL標準の仕様らしいけど変な挙動ですね…。

SQL defines two primary character types: character varying(n) and character(n), where n is a positive integer. Both of these types can store strings up to n characters (not bytes) in length. An attempt to store a longer string into a column of these types will result in an error, unless the excess characters are all spaces, in which case the string will be truncated to the maximum length. (This somewhat bizarre exception is required by the SQL standard.)

PostgreSQL: Documentation: 9.4: Character Types

mysql の場合

mysql は特に挙動は変わっていません。mysql 5.6 のデフォルトである STRICT_ALL_TABLES が有効になっていると次のようなエラーになります。無効だとエラーにはならず、256文字以降が自動でカットされて格納されます

ActiveRecord::StatementInvalid: Mysql::Error: Data too long for column 'name' at row 1: INSERT INTO `users` (`created_at`, `name`, `updated_at`) VALUES (?, ?, ?)

Rails で fat model を避けるための、あまり知られていない方法について

このエントリで書いた内容は、ほぼ Growing Rails Applications in Practice の内容が元になっています。英語ですが、ここで挙げた内容以外にもコードを綺麗に保つテクニックが書かれており、かつページ数も少なく読みやすいです。コードを綺麗に保つのが好きな方は一読してみることをおすすめします。

はじめに

Rails で fat model を避けるための方法は、7 Patterns to Refactor Fat ActiveRecord Models を始めとして、多くのやり方が存在します*1

validation や callback は ActiveRecord(以下AR) を継承せずとも利用することができます。7 Patterns to Refactor Fat ActiveRecord Models の 「3. Extract Form Objects」がそのいい例ですね。ただ、(ARの)モデルに関する validation や callback をかけたい場合は、モデルに直接書いてしまう人が多いのではないでしょうか。

しかしそれは悲劇の始まりでもあります。例を挙げてみましょう。

友達と好きな趣味を共有しあうSNSのようなサービスを考えてみます。さらに、

  • 自分自身の変更は友達に通知される
  • 趣味は必ず1つ以上持っていなければならない

とします。すると User モデルは次のようになるでしょう

class User < ActiveRecord::Base
  has_many :friends, through: :friendships
  has_many :friendships
  has_many :hobbies, through: :likes
  has_many :likes

  validate :should_have_hobbies

  before_save :notify_friends

  private

  def notify_friends
    # 友達に自分自身の変更を通知するコード
  end

  def should_have_hobbies
    errors[:base] << '好きな趣味を登録してください。' if hobbies.count == 0
  end
end

「自分自身の変更は友達に通知される」を callback で、「趣味は必ず1つ以上持っていなければならない」を validation で実装しています。一見、何の問題もないように思えます。

それぞれ、具体的に何が辛いのか見ていきましょう。

callback の辛さ

このサービスは、毎日の運勢を占ってくれる機能があります。毎日バッチ処理でランダムに 0..100 の点数を出し、User の luck カラムに格納することにします。するとどうなるでしょうか。

ユーザは、友達全員の今日の運勢を通知として毎日受け取ることになってしまいますね。

もちろん、 skip_callback メソッドを利用することで、 callback 処理をスキップさせることは可能です。しかし、通知をさせずに User を変更したいときに、毎回それを念頭に置いてコードを書くのは大変ですね。そしてそれを忘れてしまうともっと大変です。

また、before_save などの callback 用メソッドに if や unless などのオプションを渡して、skip_callback とは逆に、「callbackを実行したい時だけなんらかの処理をする」という仕組みにすることもできます。例えば

class User < ActiveRecord::Base
  before_save :notify_friends, if: :enable_notification

  attr_reader :enable_notification

  def use_notification
    @enable_notification = true
  end
end

のようにして、

user.use_noticication
user.save

のようにしたときだけ通知を飛ばすような仕組みです。これは一見うまいやり方に見えます。実際、アプリケーションコードが少ない時はこれでもうまく回ると思います。しかしアプリケーションコードが肥大してくると、大量の callback とその条件分岐のためのコードによりモデルが太ってきてしまいます。

さらに、上記の例だと条件が単純なため特に問題はないですが、複雑な条件を満たした時のみ callback を実行したいようなケースもありますね。その条件に対して、さらに callback 実行をオンオフするための条件式を追加するのはなるべくやりたくない感じです。

validation の辛さ

次に、User モデルのユニットテストを書いてみましょう。User を save したときに、ちゃんと User#notify_friends が実行されるかをテストしてみます。素直に書くとこんな感じでしょうか。

require 'rails_helper'

RSpec.describe User, type: :model do
  describe '#notify_friends' do
    it 'save したときに、User#notify_friends が実行されていること' do
      user = User.new
      expect(user).to receive(:notify_friends)
      user.save
    end
  end
end

しかしこれだとうまくテストが通りません。なぜでしょうか。

validate :should_have_hobbies があるので、関連する hobbies がないと validation が通らず、 callback が実行されないからですね。次のように Hobby を作り、User と関連付けさせれば通ります。

hobby = Hobby.create!(name: 'ジョギング')
user = User.new
user.likes.create!(hobby: hobby)
user.save

しかし、User を保存するテストコード全てに、このようなコードを追加していくのは大変です。factory_girl などの fixture replacement を使うことで大変さをいくらか減らせますが、そうすると今度は factory_girl の定義自体の複雑さが増して辛いことになったりします。

ではどうするとよい?

ここまで、 callback や validation をモデルにそのまま書くことにより、それらを必要としない場合に複雑さをもたらす例を見てきました。この複雑さを解消するにはどのようにするべきでしょうか?

それには継承を使います。具体的なコード例として、先述の User モデルを書きなおしてみます。

class User < ActiveRecord::Base
  has_many :friends, through: :friendships
  has_many :friendships
  has_many :hobbies, through: :likes
  has_many :likes
end
class User::AttributeUpdator < User
  before_save :notify_friends
  validate :should_have_hobbies

  private

  def notify_friends
    # 友達に自分自身の変更を通知するコード
  end

  def should_have_hobbies
    errors[:base] << '好きな趣味を登録してください。' if hobbies.count == 0
  end
end

通常の User と、何らかの属性を変更するとき用に使う User::AttributeUpdator というクラスに分け、User::AttributeUpdator は User を継承するようにしました。User::AttributeUpdator は User のサブクラスなので、当然 User 側で定義している has_many の関連も使えます。

これにより、他の友だちに通知させたいときは User::AttributeUpdator を利用し、そうでないときは User を使うという形で使い分けをすることができます。

必要なときに必要な分だけの validation や callback を設定することができ、さらにそれぞれの処理をサブクラスに移すことで User モデルがスリムになりました。

やりましたね!…と言いたいところですが、いくつか注意事項があります。

継承を利用する場合の注意点

form_forurl_for に ActiveRecord のオブジェクトを引数として渡した時、URLの組み立てに model_name が使われます。そして User::AttributeUpdator.new.model_name.to_s #=> 'User::AttributeUpdator' なので、User::AttributeUpdator オブジェクトを渡した時は意図した URL が作られないことになります。

また、User モデルが STI を利用していたとしましょう。その場合 User::AttributeUpdator は STI とは違うので、 type カラムに入れたくないですね。

これらを解決するために、active_type という gem を使います。

普通に Gemfile に入れて bundle install して、User::AttributeUpdator の定義箇所を次のようにするとうまく動きます。

class User::AttributeUpdator < ActiveType::Record[User]
 #...
end

ちょっと見慣れない書き方ですね。やっていることはそれほど複雑ではないので、気になる人は独自の gem を作ってしまってもいいかもしれません*2

クラスの変更

ログインが必要なアプリーションを作る時、大抵次のように current_user のようなメソッドを定義することになると思います。

def current_user
  @current_user ||= User.find(session[:user_id])
end

ログインユーザが自分の情報を変更するケースを考えてみましょう。この場合は User::AttributeUpdator を利用するのでしたね。しかし current_user は User クラスのオブジェクトを返してきます。困りました。

そこで、User オブジェクトを User::AttributeUpdator オブジェクトに変換します。

先ほど紹介した active_type が変換用のメソッドを用意してくれています。

user = ActiveType.cast(current_user, User::AttributeUpdator)
user.update(params[:user])

おわりに

継承を使うことにより、callback や validation を分割し、必要なときのみ使えるようにすることができました。このテクニックを実際に仕事のコードとして使ってみましたが、想定通りに整理されたコードを作ることが出来てなかなかよい感じです。

しかし、このテクニック自体はあまり認知されておらず、知らない人が見ると面食らう可能性があるのでもうちょっと普及して欲しいところです。

また、ここで紹介したよりももっと良い方法があるのではないか?という気持ちもあります。もし、もっといい方法知ってるぜ!という方がいたらブログのコメントなりTwitterでメンションなりいただけると嬉しいです (\( ⁰⊖⁰)/)

*1:パーフェクト Ruby on Rails でも一部触れていますね

*2:作ろうと思ったのですがなかなか時間取れないのでした

iphone のカメラを修理した

iphone6 plus で写真を撮るときに、フォーカスが合わずにぼやけた写真しか撮れなくなってしまいました。

ググるとどうやらハードウェアの不具合の模様。

iPhone 6 Plusのカメラのピントが合わない不具合が発生 - 非天マザー by B-CHAN

ジーニアスバーに持って行こうと思って調べるも、予約可能な日時を考えると一週間ほど待つことになりそうなので、宅配便を使った修理サービスを使用してみることにしました。apple のことだから代替機とかも用意してくれるのではと勝手に思い込んでいたのですが、普通に iphone を持って行かれてしまい、しばらく電話なし生活を余儀なくされました。

普段ほとんど電話を使うことはないので基本的には問題ないのですが、一点困ることがありました。僕の家はインターフォンがないので、宅配のものを受け取るのに電話が必須だったのです…><

つまり iphone を受け取るのに iphone が必要という詰んだ状況に。困った。

部室にあった検証用のSIMフリーのAndroid端末をお借りして、iphone の SIM を入れればなんとかなる…と思いきや、SIMのサイズが合わない。

そこで電気屋に行ってSIMサイズを変更するアダプタ購入した*1ら電話を使うことができるようになりました。

ググったらどうもiphoneのSIMはアダプタを使っても、端末によって使えたり使えなかったりするようなので、今回電話が使えたのは運が良かったようです*2

というわけでなんとかiphoneを修理することができました。どうやらググると代替機を用意してくれるプランもあったようです。次回はこれを使うか、おとなしくジーニアスバーを利用したいと思います…。

Apple - サポート - 修理サービス Q&A センター

*1:新宿の大きい電気屋を二軒まわりました。SIMアダプタの在庫は少ないようですね…

*2:ただ、インターネットは使えませんでした。

nested attributes なレコードを、特定の属性が空の時に削除する

nested attributes なレコードを削除したい場合、accepts_nested_attributes_forallow_destroy: true オプションを渡すと削除可能になります。削除するには、対象となる対象に { _destroy: 1 } のようなパラメータを渡します。

これを踏まえて素直にフォームを作ろうとすると、削除用のチェックボックスをつける事になるでしょう。しかし次のようなフォームにチェックボックスをつけると、ユーザにとってわかりづらいUIになってしまいます。単純にテキストフィールドを空にして更新したらレコードが削除されて欲しい。

f:id:willnet:20150725173208p:plain

そこで次のようにします。

class User < ActiveRecord::Base
  accepts_nested_attributes_for :family_members,
                                reject_if: :reject_family_member,
                                allow_destroy: true

  def reject_family_member(attributes)
    exists = attributes[:id].present?
    empty = attributes[:email].blank?
    attributes.merge!(_destroy: 1) if exists && empty
    !exists && empty
  end
end

reject_if の手続きの中で、レコードとして保存済みでかつメールアドレスが空のものに { _destroy: 1 } を追加しています。これでチェックボックスなしで nested attributes なレコードを削除することが出来ました。

参考

ruby on rails - Destroy on blank nested attribute - Stack Overflow

ActiveJob はまだちょっと使うには早いかも

Rails 4.2 から導入された ActiveJob は、sidekiq や resque などのバックグラウンドジョブ系 gem を、同じ利用方法で扱えるようにしてくれます。

これは便利だなーと思い、sidekiq を ActiveJob を通じて使ってみたのですが、しばらく使った後に「まだ本格的に使うのは早いかも」と感じました。

リトライ機能が貧弱

sidekiq は、ジョブが失敗した時にリトライする機能があります。失敗するたびに次にリトライする間隔が伸び、一定回数失敗したら完全に失敗として扱われます。リトライ間隔や、完全に失敗になるまでの回数はもちろん変更可能です。

Web上でリトライしているジョブや完全に失敗したジョブを確認することもできます。

ActiveJob を使うと、この細やかなリトライ機能を失うことになります。例えば、次のように例外を拾って再度キューにジョブを入れることはできますが、リトライ間隔や最大のリトライ回数などはサポートしていません。この場合は無限にリトライすることになってしまいますね。

class SiteScrapperJob < ActiveJob::Base
  rescue_from(ErrorLoadingSite) do
    retry_job queue: :low_priority
  end

  def perform(*args)
    raise ErrorLoadingSite if cannot scrape
  end
end

リトライをサポートしてくれる gem

gocardless/activejob-retry という gem があります。これを使えば問題は解決しそうですが、READMEを読むと

This is an alpha library in active development, so the API may change.

との事なので、まだ実践で使うには早いのかなと思います。また、仮に alpha でなくなったとしても、ActiveJobの性質上、sidekiq の WebUI でリトライしているジョブの数を確認したりはできないはず。

まとめ

ActiveJob を使うと、様々なバックグラウンドジョブ系の gem を意識せず同じ使い方で使えるのがメリットだと思います。しかし1つの gem をずっと使うのであれば、その gem 特有の機能が使えなくなるデメリットが大きいように思えます。個人的には、今の段階ではなるべく ActiveJob は避けていくのがよいのかなと思います*1

*1:deliver_later くらいなら問題ないと思います

色彩検定2級を受けてきた

6月28日(日)に色彩検定2級の試験を受けてきました。

色彩検定協会/カラーコーディネーター

webサービスを作るときに、どんな色を使ったらキマるのか全然わからず適当に勘で決めていたのですが、色彩検定の勉強をする中である程度のセオリーをつかめた気がします。

まず去年受けた3級の試験勉強を通じて、色には色相とトーン(彩度、明度)があり、それぞれの種類と人に与える印象の違いについて学びました。

これがわかると、例えばビジネス関連のサービスを作りたかったら、色相は誠実な印象を与える青で、トーンは落ち着いた印象のライトグレイッシュを使おうか…などと、論理的に色を絞り込むことができます。今書いたような例がぴったりハマるようになるには経験が必要ですが、勘で決めていた頃よりはだいぶ前進できた気がします。

2級では、複数の色をどのように組み合わせると調和して見えやすいか学びました。例えば同系のトーンの組み合わせだと調和するとか。webサービスを作るには複数色必要なので、2級の範囲までは学んで損はないように思います。色とかどうやって決めたらいいのという人にはオススメです。

色彩検定の範囲には、色の分類の仕方などのwebデザインに直接使える項目以外にも、目の構造とか光の波長についてとかファッション、インテリアなどの項目もあります。僕は雑学を学ぶつもりでひと通りやりましたが、無駄なことをしたくない人は、色の分類のところだけ拾い読みするのもいいんじゃないかと思います。

勉強したいけどモチベーションが続かない!という人は毎月やっているデザインビギナーズのミートアップに来るとよいと思います ;-)