devひよこのあしあと

いつでもひよっこな気持ちで学びと挑戦を

UMLでRailsモデリング

Railsエンジニアな皆さん、モデリングしてますか?

ひとりでシステム構築しているなら不要かもしれませんが、チームで活動し、ある程度の規模のシステムを構築/改修する場合は、いきなり実装するのではなくモデリングをしましょう!

モデリングの手段はたくさんありますが、統一記法であるUMLに従うのがいろいろメリットを享受できてよいかと思います。特に下記のメリットが重要です。

  • 視覚的な表現によって構造・振る舞いを直感的に把握できる
  • 開発に関わるメンバ全員が共通の言語でコミュニケーションできる

本記事ではRailsシステムをUMLモデリングする際の表現方法を紹介します。想定する読者は、上位者の指示の下Railsシステムの構築/改修をすることができ、今後ステップアップとして実装設計とか構造設計と呼ばれるフェーズを独力で実施できることを望むような人(およびその上位者)です。これによって少しでも多くの人が実装設計できるようになることを願います。

なお、モデリングツールとしては「astah* community」がオススメです。無償で使えて基本的なモデリング作業はほとんどできてしまう。しかも商用利用OK! (ただし、ver. 7.0からは商用利用できなくなってしまいました...) モデリングツールをまだインストールしていない人はぜひ入れておきましょう。

astah* community - 無償UMLモデリングツール | Astah

サンプルシステム

本記事で例示するシステムは下記のユースケースを想定したものとします。

f:id:devchick:20160312100400p:plain

構造設計

モデルの表現

RailsのモデルはO/Rマッピングなので、クラス図と特に相性がよいです。 コードと1対1で完全に一致するような正確性よりも、関係者間で共通の認識を持てることが重要です。そのため部分的に表現を省略することで、読み手に注目してもらいたい箇所が際立つようにします。機能改修テーマであれば、変更のある属性やメソッドだけを記載するといった工夫をすると良いと思います。

属性の表現

クラス図ではクラスの属性とメソッドを表現できます。これを使ってモデルを表現します。

f:id:devchick:20160312100418p:plain

idcreated_atupdated_atといった属性は大抵のモデルには存在しますが、煩雑になりますのでいちいち記述しないようにします。また、ActiveRecordの機能で属性に対するセッター/ゲッターは自動的に作成されますが、これも煩雑になりますので記述しないようにします。(属性をpublicにすることで表現してもいいのですが、毎度可視性を変更するのは面倒臭いのでやらないほうがいいでしょう。)

マイグレーションファイルにすると以下のようになります。

class CreateUsers < ActiveRecord::Migration
  def change
    create_table :users do |t|
      t.string   :login
      t.string   :email
      t.string   :hashed_password
      t.string   :password_salt
      t.datetime :activated_at
      t.boolean  :enabled, null: false, default: true
      t.timestamps
    end
  end
end

なお、Datetime型やText型などRubyRailsでは標準で存在するけどモデリングツールにはデフォルトで存在しないようなクラスは、rubyパッケージを作成してそこに突っ込んでおくとよいです。

f:id:devchick:20160312100434p:plain

スコープの表現

モデルのスコープは静的メソッドにステレオタイプを付けて表現します。

f:id:devchick:20160312100451p:plain

上図のようにクラス図では、静的メソッドは下線付き、ステレオタイプは<< >>で表現します。

STIの表現

STI(Single Table Inheritance: 単一テーブル継承)はクラスの継承線にステレオタイプを付けて表現します。

f:id:devchick:20160312100506p:plain

typeカラムに具象クラス名を入れておくことで、インスタンスを取り出した時にActiveRecordが自動的にそのクラスのインスタンスに変換してくれます。

もうちょっと細かいSTIの話は下記のサイトを参照ください。 【Rails】ActiveRecord:単一テーブル継承(sti)とポリモーフィック関連を未だにぱっと思い出せないのでまとめ。 - 記すに足らず。

継承線に<<STI>>というステレオタイプを付けることで表していますので、typeカラムはクラス中に記載しなくてもよいのですが、「typeカラムに具象クラス名が入っているのでここはこのインスタンスになるんです〜」というSTIの説明を何度もすることがあるため、私はいつも記載するようにしています。関係者がSTIをしっかり理解してくれているならtypeカラムは省略してもよいのではないでしょうか。

class CreateImages < ActiveRecord::Migration
  def change
    create_table :images do |t|
      t.string   :type
      t.integer  :width
      t.integer  :height
      t.binary   :data
      t.timestamps
    end
  end
end

class Image < ActiveRecord::Base
end

class Avator < Image
end

class Photo < Image
end

has_one関連の表現

has_one関連はクラス間の集約を用いて表現します。白抜きひし形が付いている方が所有する側、線の繋がる先が所有される側になります。

f:id:devchick:20160312100529p:plain

上記の例の場合、所有される側のUserAttributeuser_idカラムが必要ですが、関連線があることで自ずと必要なことがわかりますので記載を省略します。

class CreateUserAttributes < ActiveRecord::Migration
  def change
    create_table :user_attributes do |t|
      t.integer  :user_id
      t.string   :title
      t.string   :zip_code
      t.string   :address
      t.string   :phone_number
      t.string   :url
      t.timestamps
    end
  end
end

class User < ActiveRecord::Base
  has_one :user_attribute
end

class UserAttribute < ActiveRecord::Base
  belongs_to :user
end

has_many関連の表現

has_many関連はクラス間の集約に多重度を付けて表現します。

f:id:devchick:20160312100547p:plain

has_one関連のときと同様にArticle側のuser_idカラムは省略します。

class CreateArticles < ActiveRecord::Migration
  def change
    create_table :articles do |t|
      t.integer  :user_id
      t.string   :title
      t.text     :content
      t.timestamps
    end
  end
end

class User < ActiveRecord::Base
  has_many :articles
end

class Article < ActiveRecord::Base
  belongs_to :user
end

polymorphic関連の表現

polymorphic関連は、集約線にロール名をつけることで表現します。

f:id:devchick:20160312100603p:plain

上記の例ではSTIと組み合わせてあるのでちょっと複雑になっていますが、Imageモデルの所有者にownerというロール名を付けています。Imageモデルにはowner_typeowner_idカラムを記載していますが、polymorphic関連であることを関係者が認識してくれるなら記載を省略してもよいでしょう。

polymorphic関連についてもうちょっと細かい話は下記のサイトを参照ください。 【Rails】ActiveRecord:単一テーブル継承(sti)とポリモーフィック関連を未だにぱっと思い出せないのでまとめ。 - 記すに足らず。

class AddOwnerToImages < ActiveRecord::Migration
  def change
    add_column :images, :owner_type, :string
    add_column :images, :owner_id,   :integer
  end
end

class User < ActiveRecord::Base
  has_one :avator, as: :owner
end

class Article < ActiveRecord::Base
  has_many :photos, as: :owner
end

class Image < ActiveRecord::Base
  belongs_to :owner, polymorphic: true
end

ネームスペースの表現

ネームスペースはパッケージを使用して表現します。

f:id:devchick:20160312100619p:plain

class Image::Avator < Image
end

class Image::Photo < Image
end

Mix-inの表現

モジュールのMix-inは依存関係を用いて表現します。

f:id:devchick:20160312100630p:plain

Mix-inされるモジュールにはモジュールであることを示すためにステレオタイプで<<module>>を記載します。そしてMix-inする側からされる側へ、<<include>>ステレオタイプ付きの依存関係を引いて表現します。

class User < ActiveRecord::Base
  include User::Exportable
end

module User::Exportable
  extend ActiveSupport::Concern
  
  module ClassMethods
    def export
      ...
    end
  end
  
  def to_csv
      ...
  end
end

コントローラの表現

コントローラの構造をクラス図で表現することはできますが、コントローラに複雑な構造を持たせることもあまりないでしょうから、わざわざ図に起こすまでもないかも。ここでは、もし可視化したい要求があった場合の書き方を示します。

アクションの表現

アクションはシンプルにクラスのpubicメソッドとして表現します。

f:id:devchick:20160312100645p:plain

strong parameter用のメソッドなどのアクションから呼ばれるメソッドたちは可視性をprivate(もしくはprotected)にしたメソッドで表現します。

フィルターの表現

コントローラのフィルター機能はステレオタイプ付きprivateメソッドで表現します。

f:id:devchick:20160312100657p:plain

完成図

以上を踏まえて作成したクラス図が下記になります。

f:id:devchick:20160312100710p:plain

(...うーん、配置にセンスがないなぁ。)

振る舞い設計

システムの振る舞いはシーケンス図などを利用して表現します。

アクションの表現

f:id:devchick:20160312100730p:plain

DelayedJobやsidekiqなどを用いた非同期処理を含む場合も上図のようにシーケンス図内に記載することができます。

まとめ

本記事ではRailsシステムをUMLモデリングする際の表現方法を紹介しました。特に(Rails的な意味の)モデルの構造やモデル間の関係を表現する際に役立つのではないでしょうか。

ソースコードで語り合う前に(UML的な意味の)モデルを使ってチームメンバ間で会話してみましょう。より早く問題点を見つけられるかもしれませんし、その後のコーディングがより速くなると思います。 お試しあれ。