Raccoon Tech Blog [株式会社ラクーン 技術戦略部ブログ]

株式会社ラクーン 技術戦略部より、tipsやノウハウなど技術的な話題を発信いたします。

Ruby

RubyKaigi2018レポート

こんにちは。開発チームの大原です。

今年も弊社の技術サポート制度を利用してRubyKaigi 2018に参加してきました。

各発表をテーマごとに整理し、私が見た発表については要約をつけて紹介します。

ちなみに去年のレポートはこちらです。RubyKaigi2017レポート

概要

全体的なまとめと感想

  • Ruby3に向けての大きな話題である、パフォーマンス、型、並行性については
    • Ruby2.6からオプションでJITが使えるようになる。全体的に速度改善の開発が進んでいる
    • 言語仕様に型注釈が入ることはなく、Steepのように、コメントや別ファイルで型を指定するような方法が有力。Stripe社のSorbeという型チェッカーの完成度が高いことが話題に。
    • 並行性は進捗なし
  • mruby系の話題が多く採用されていました。mrubyはそれほど使ったことが無いので、もっと使ってみたいと思いました。
  • 休憩時間のプレイリストがジョジョ4部で素晴らしかったです。
  • 次回の RubyKaigi2019 は 2019-04-18..2019-04-20 福岡 (詳細な場所は未定)
続きを読む

Ruby on Rails5.0へ移行

こんにちは。開発ユニットbcの堀口です。
URIHO開発を担当しています。

今回は弊社のグループ会社である株式会社トラスト&グロースのサービス「URIHO」で使用しているRuby on Railsについてです。
2017年4月27日にRuby on Rails 5.1が正式リリースされましたね。(Rails 5.1のリリースノートはこちら)
「URIHO」ではRails4.2系を使っていますが、このリリースを機にRails5.1に移行しよう!ということになりました。
それでは、さっそくRails5.1に移行するぞ!...といきたいところですが、まずはリリースノートを見てましょう。
既存のアプリケーションをアップグレードするのであれば、その前に質のよいテストカバレッジを用意するのはよい考えです。アプリケーションがRails 5.0までアップグレードされていない場合は先にそれを完了し、アプリケーションが正常に動作することを十分確認してからRails 5.0にアップデートしてください。アップグレードの注意点などについてはRuby on Railsアップグレードガイド を参照してください。
「Ruby on Rails 5.1リリースノート」 https://railsguides.jp/5_1_release_notes.html
どうやらRails5.1にアップデートする前にRails5.0へアップデートしておく必要があるようです。
というわけで本記事ではRails4.2系からRails5.0へのアップデート手順について紹介します!

続きを読む

RubyKaigi2017レポート

itsukushima

こんにちは。開発チームの大原です。

2017年9月18日~20日に広島国際会議場で開催されたRubyKaigi2017に参加してきました。

弊社には技術サポート制度があり、カンファレンス参加費用を出してもらったり、業務として参加させてもらいました。感謝!

(ついでに宮島観光もしてきました。鳥居やまわりの景色が壮大でした!)

各発表を話題ごとに整理して簡単に紹介します。

続きを読む

Rails + JBuilder + AngularJSでJSONベースの画面処理


こんにちは、開発ユニットbcです。

去る8月2日、弊社の子会社である株式会社トラスト&グロースが、新サービス「URIHO」(https://uriho.jp)を開始しました。
(「URIHO」は年商5億円以下の企業様を対象とした、売掛債権の保証サービスです。ご興味ある方はサイトをご覧ください。)

当ユニットがこの新システムの開発を担当したのですが、その中からJSONをベースにした画面処理をどう実装したかについて簡単にご紹介します。

構成としてはサーバサイドにRailsとJBuilder、フロントにAngularJSを採用しています。
JBuilderにはJSON編集のための強力な機能が備わっており、また、AngularJSとの相性が非常に良いと感じました。

処理の流れ

sqd

処理のおおまかな流れとしては上記のような感じです。
では、画面イメージとソースコードで説明していきます。 
続きを読む

[Rails]Model/テーブル設計で必ず覚えておきたいSTI

開発チームの下田です。

Ruby on Railsについて、基本的なこと(modelやcontrollerが何か知っている程度)を理解されている方を対象としています。

BtoBクラウド受注・発注システムCORECはRailsで開発しています。 開発初期のコードを見直してみると失敗したなと思うことがあります。 中でも特に開発効率に影響しているなと思うのが、RailsのActiveRecordのSTI(Single Table Inheritance、単一テーブル継承)を活用しなかったことです。

STIを簡単に言うと、モデルを永続化するときに、どのクラスなのかというメタ情報を含めてデータベースに保存します。 これだけではわかりづらいと思うので、失敗した例と改善例からご紹介したいと思います。

悪い設計

仮に下記の仕様のコードを書くとします。

要件1:
 CORECではWEBアプリから発注書を送信できます。送信方法は次の3つから選べます。

  • WEBアプリ内で送信する
  • メールで送信する
  • FAXで送信する

この時、次のようなコードにしていました。
(項目名、メソッド名は意味が通じるように和名にしています)

# 注文書model 
class Order < ActiveRecord::Base
  include FAX送信モジュール
  validates :送信方法, :presence => true
 
  # WEBで送信するなら送信先IDが必須 
  validates :送信先ユーザ_id, :presence => true, if: ->{ 送信方法 == 'WEB' }
 
  # メールならメールアドレスが必須 
  validates :メールアドレス, :presence => true, if: ->{ 送信方法 == 'メール' }
 
  # FAXならFAX番号が必須 
  validates :FAX番号, :presence => true, if: ->{ 送信方法 == 'FAX' }
 
  has_many :FAX送信結果
 
  def 送信する
    self.送信済 = true
    validate!
    case 送信方法
    when 'メール'
      注文Mailer.メール注文書送信.deliver
    when 'FAX'
      FAX送信する
    end
    save
  end
 
  private
 
  def FAX送信する
    FAX送信結果.create(送信結果: FAX送信モジュール.送信)
  end
end


何が問題か

当初は送信するときだから、送信手段ごとに処理をcase-whenで振り分けることに違和感はありませんでした。しかし、開発を進めていると、同じコードがあちこちに現れて読みづらいコードになり始めました。

要件2:

  • FAXは送信に失敗することがあるので、結果判定をする
  • WEBとメールは必ず成功したとみなす
class Order < ActiveRecord::Base
  # ~~ 略 ~~ 
 
  def 送信成功?
    # メソッドごとに送信方法による分岐がある状態 
    case 送信方法
    when 'FAX'
      FAX送信結果.送信成功?
    default
      true
    end
  end
end


あるべき姿

FAXもメールもすべて注文書の情報なのでOrderクラスにしたことは正しいのですが、送信方法によってふるまいが違います。役割が同じでふるまいが違うなら、サブクラスを作りポリモーフィズムを持たせるべきです。

sti


class Order < ActiveRecord::Base
  def 送信する
    self.送信済 = true
    save
  end
 
  def 送信成功?
    false
  end
end
 
# WEBで送信する注文書クラス
class Order::Web < Order
  validates :送信先ユーザ_id, :presence => true
 
  def 送信する
    validate!
    super
  end
end
 
# メールで送信する注文書クラス
class Order::Mail < Order
  validates :メールアドレス, :presence => true
 
  def 送信する
    validate!
    注文Mailer.メール注文書送信.deliver
    super
  end
end
 
# FAXで送信する注文書クラス 
class Order::Fax < Order
  validates :FAX番号, :presence => true
 
  has_many :FAX送信結果
 
  def 送信する
    validate!
    FAX送信する
    super
  end
 
  def 送信成功?
    FAX送信結果.送信成功?
  end
 
  private
 
  def FAX送信する
    FAX送信結果.create(送信結果: FAX送信モジュール.送信)
  end
end

メソッドの中から冗長なcase文が消えます。また、relationやvalidationのふるまいも送信方法ごとに整理され、見通しが良くなりました。

このようなクラス設計にしたい場合はSTIを使用します。STIを使用するにはtype:stringカラムを追加します。

  create_table "orders", force: true do |t|
    t.string   "type" # 追加するとSTIになる 
    t.integer  "送信先ユーザ_id"
    t.string   "メールアドレス"
    t.string   "FAX番号"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

typeカラムがある場合とない場合を比較してみましょう。

# typeカラムがない場合 
order = Order::Mail.new(メールアドレス: 'hoge@example.com')
order.save
# id: 1 
# メールアドレス: "hoge@example.com" 
 
# OrderからロードするとOrderになる 
Order.find(order.id)
=> #<Order id: 1, メールアドレス: "hoge@example.com"> 
 
# Order::MailからロードすればOK 
Order::Mail.find(order.id)
=> #<Order::Mail id: 1, メールアドレス: "hoge@example.com"> 
# typeカラムがあり、STIになっている場合 
order = Order::Mail.new(メールアドレス: 'hoge@example.com')
order.save
# type: 'Order::Mail' # クラス名がtypeに自動的にsaveされる 
# id: 1 
# メールアドレス: "hoge@example.com" 
# created_at: 2015-01-01 12-34-56 
# updated_at: 2015-01-01 12-34-56 
 
# Orderからロードしても、きちんとOrder::Mailになる 
Order.find(order.id)
=> #<Order::Mail id: 1, メールアドレス: "hoge@example.com"> 
 
# サブクラスからロードすると、typeを検索条件に自動で加えてくれる 
Order::Mail.all.to_sql
=> "select * from orders where type = 'Order::Mail'"

データベースに保存し永続化した後でもふるまいを維持し続けてくれるので、クラス設計を適切に行えば複雑な仕様が追加されても、良い状態のコードを保てると思います。

運用上の注意

開発には非常に便利なSTIですが、運用を考えて開発しなければならないことが1点あります。typeカラムに存在しないclass名が設定されていると、ActiveRecord::SubclassNotFound例外が発生します。 ActiveRecord::SubclassNotFound例外が発生するのは、2パターンがあります。

  • リリース時に並行稼動している時に、新しいAPで保存した新しいサブクラスを古いAPでロードした
  • リリースしたがロールバックし、新しいAPで保存した新しいサブクラスを古いAPでロードした<

どちらの場合も、何も変更しないサブクラスを作り、先行してリリースしておくと、予期せぬ例外が起きません。フェイルソフトになります。

# 仮置きする。 
class Order::NewClass < Order
end

もしくはデフォルトスコープに現バージョンで存在しているclassのみを指定すると、ロードされないので例外が発生しません。サブクラスを追加した時に追加し忘れたり、where句が思わぬところで効いたりしないか確認が必要です。

class Order < ActiveRecord::Base
  # 条件に加えてしまう。
  default_scope ->{ where(type: [Order, Order::Corec, Order::Mail, Order::Fax]) }
  # ~~ 略 ~~ 
end

おわりに

STI以外にも、ああすればよかったと思うことは多くあります。その中でもSTIについて書いたのは、知っているか知らないかでModelの設計に差が出てしまうため、気づいた時には手軽にはリファクタリングできなくなっているからです。その反面、ある程度の規模のアプリケーションでなければ使い道が無くRuby on Rails チュートリアルに載っていなかったりと軽視されがちです。 新しくmodelを設計するときには頭の片隅に置いておくといいと思います。

記事検索