Railsエンジニアな皆さん、モデリングしてますか?
ひとりでシステム構築しているなら不要かもしれませんが、チームで活動し、ある程度の規模のシステムを構築/改修する場合は、いきなり実装するのではなくモデリングをしましょう!
モデリングの手段はたくさんありますが、統一記法であるUMLに従うといろいろ下記のようなメリットを享受できてよいかと思います。
- 視覚的な表現によって構造・振る舞いを直感的に把握できる
- 開発に関わるメンバ全員が共通の言語でコミュニケーションできる
本記事ではRailsシステムをUMLでモデリングする際の表現方法を紹介します。想定する読者は、上位者の指示の下Railsシステムの構築/改修をすることができ、今後ステップアップとして実装設計とか構造設計と呼ばれるフェーズを独力で実施できることを望むような人(およびその上位者)です。これによって少しでも多くの人が実装設計できるようになることを願います。
なお、モデリングツールとしては「astah* community」がオススメです。無償で使えて基本的なモデリング作業はほとんどできてしまう。しかも商用利用OK! (ただし、ver. 7.0からは商用利用できなくなってしまいました...)
モデリングツールをまだインストールしていない人はぜひ入れておきましょう。
astah* community - 無償UMLモデリングツール | Astah
サンプルシステム
本記事で例示するシステムは下記のユースケースを想定したものとします。
構造設計
モデルの表現
RailsのモデルはO/Rマッピングなので、クラス図と特に相性がよいです。
コードと1対1で完全に一致するような正確性よりも、関係者間で共通の認識を持てることが重要です。そのため部分的に表現を省略することで、読み手に注目してもらいたい箇所が際立つようにします。機能改修テーマであれば、変更のある属性やメソッドだけを記載するといった工夫をすると良いと思います。
属性の表現
クラス図ではクラスの属性とメソッドを表現できます。これを使ってモデルを表現します。
id
やcreated_at
、updated_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
型などRubyやRailsでは標準で存在するけどモデリングツールにはデフォルトで存在しないようなクラスは、ruby
パッケージを作成してそこに突っ込んでおくとよいです。
スコープの表現
モデルのスコープは静的メソッドにステレオタイプを付けて表現します。
上図のようにクラス図では、静的メソッドは下線付き、ステレオタイプは<< >>
で表現します。
STIの表現
STI(Single Table Inheritance: 単一テーブル継承)はクラスの継承線にステレオタイプを付けて表現します。
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関連はクラス間の集約を用いて表現します。白抜きひし形が付いている方が所有する側、線の繋がる先が所有される側になります。
上記の例の場合、所有される側のUserAttribute
にuser_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関連はクラス間の集約に多重度を付けて表現します。
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関連は、集約線にロール名をつけることで表現します。
上記の例ではSTIと組み合わせてあるのでちょっと複雑になっていますが、Imageモデルの所有者にowner
というロール名を付けています。Imageモデルにはowner_type
とowner_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
ネームスペースの表現
ネームスペースはパッケージを使用して表現します。
class Image::Avator < Image
end
class Image::Photo < Image
end
Mix-inの表現
モジュールのMix-inは依存関係を用いて表現します。
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メソッドとして表現します。
strong parameter用のメソッドなどのアクションから呼ばれるメソッドたちは可視性をprivate(もしくはprotected)にしたメソッドで表現します。
フィルターの表現
コントローラのフィルター機能はステレオタイプ付きprivateメソッドで表現します。
完成図
以上を踏まえて作成したクラス図が下記になります。
(...うーん、配置にセンスがないなぁ。)
振る舞い設計
システムの振る舞いはシーケンス図などを利用して表現します。
アクションの表現
DelayedJobやsidekiqなどを用いた非同期処理を含む場合も上図のようにシーケンス図内に記載することができます。
まとめ
本記事ではRailsシステムをUMLでモデリングする際の表現方法を紹介しました。特に(Rails的な意味の)モデルの構造やモデル間の関係を表現する際に役立つのではないでしょうか。
ソースコードで語り合う前に(UML的な意味の)モデルを使ってチームメンバ間で会話してみましょう。より早く問題点を見つけられるかもしれませんし、その後のコーディングがより速くなると思います。
お試しあれ。