-
Notifications
You must be signed in to change notification settings - Fork 22
LearningRailsWithTriglav03
今回は Rails のディレクトリ構造と MVC について簡単に学んで、M, V, C のファイルを作成して機能追加してみます。
MVC(Model View Controller) とは、アプリケーションアーキテクチャパターンの一種です。
具体的な内容については、ホワイトボードで図を書きながら説明します。
Triglav の中で、MVC に関連のあるディレクトリとファイルは以下のような構成になっています。(一部のみ抜粋しています。)
app
|-- controllers
| |-- activities_controller.rb
| |-- api_controller.rb
| `-- application_controller.rb
|-- models
| |-- activity.rb
| `-- user.rb
`-- views
|-- activities
| `-- index.html.erb
|-- api
| `-- index.html.erb
`-- layouts
`-- application.html.erb
controllers の下にはコントローラが、models の下にはモデルが、views の下にはコントローラ/アクションに対応したビューが配置されます。
コントローラは更にアクションという単位に細分化されます。
例えば、app/controllers/hosts_controller.rb の中身を見てみると、以下のようになっています。
class HostsController < ApplicationController
respond_to :html, :json
def index
@hosts_without_deleted = Host.without_deleted
@deleted_hosts = Host.deleted
respond_with @hosts_without_deleted
end
def show
@host = Host.find_by_name(params[:id])
@munin = Munin.new
respond_with @host
end
... 以下略 ...
class HostsController
がコントローラの定義、def index
や def show
がアクションの定義です。
Rails では、HTTP メソッドや URL によって、呼び出されるコントローラ/アクションが決まります。HTTP メソッドや URL と、コントローラ/アクションを紐づけることをルーティングと呼びます。ルーティングは config/routes.rb で定義されています。
Triglav::Application.routes.draw do
root 'root#index'
get '/caveat', to: 'root#caveat'
get '/signin' => redirect('/auth/github')
delete '/signout', to: 'sessions#destroy'
get '/auth/:provider/callback', to: 'sessions#create'
if Rails.env.development?
get '/dev_signin' => redirect('/auth/developer')
post '/auth/developer/callback', to: 'sessions#create'
end
resources :users, constraints: { id: /[^\/\.]+/ }, only: %w(show update)
... 以下略 ...
end
どのメソッドや URL に、どういったコントローラ/アクションが紐付いているかは、rake routes コマンドで確認できます。
$ rake routes
root GET / root#index
caveat GET /caveat(.:format) root#caveat
signin GET /signin(.:format) redirect(301, /auth/github)
... 中略 ...
revert_host PUT /hosts/:id/revert(.:format) hosts#revert {:id=>/[^\/]+/}
host_comments POST /hosts/:host_id/comments(.:format) comments#create {:id=>/[^\/]+/, :host_id=>/[^\/]+/}
hosts GET /hosts(.:format) hosts#index {:id=>/[^\/]+/}
POST /hosts(.:format) hosts#create {:id=>/[^\/]+/}
new_host GET /hosts/new(.:format) hosts#new {:id=>/[^\/]+/}
edit_host GET /hosts/:id/edit(.:format) hosts#edit {:id=>/[^\/]+/}
host GET /hosts/:id(.:format) hosts#show {:id=>/[^\/]+/}
PATCH /hosts/:id(.:format) hosts#update {:id=>/[^\/]+/}
PUT /hosts/:id(.:format) hosts#update {:id=>/[^\/]+/}
DELETE /hosts/:id(.:format) hosts#destroy {:id=>/[^\/]+/}
... 以下略 ...
ルーティングはちょっとややこしいので、今回は深くは触れません。
もう一度 app/controllers/hosts_controller.rb の中身を見てみます。
class HostsController < ApplicationController
respond_to :html, :json
def index
@hosts_without_deleted = Host.without_deleted
@deleted_hosts = Host.deleted
respond_with @hosts_without_deleted
end
def show
@host = Host.find_by_name(params[:id])
@munin = Munin.new
respond_with @host
end
... 以下略 ...
/hosts にアクセスした場合、このコントローラの中の index アクションが呼び出されます。また、/hosts/users001.sqale.jp といった形でアクセスすると、show アクションが呼び出されます。(rake routes で確認してみましょう。)
最初の方にある respond_to :html, :json
は、入力/出力フォーマットとして、HTML と JSON に対応することを表しています。
たとえば、/hosts または /hosts.html にアクセスすると HTML がレスポンスボディとして返り、/hosts.json にアクセスすると、JSON がレスポンスボディとして返ります。実際にアクセスして試してみてください。
出力フォーマットは .format の形で URL 中で指定しますが、指定がない場合には、.html が指定されていると解釈されます。
コントローラ/アクションが呼び出されると、その中で定義されている処理が呼び出されます。
/hosts にアクセスした場合は def index ... end
内の処理が呼び出されます。
この中で使われているモデル Host
の実体が、app/models/host.rb で、以下のような内容になってます。
class Host < ActiveRecord::Base
include ActiveModel::ForbiddenAttributesProtection
include LogicallyDeletableRole
include HasHostRelationsRole
include ::HasDeclarativePathRole
validates :name, presence: true, uniqueness: true, length: { maximum: 100 }, format: { with: /\A[^\/]+\Z/ }
validates :ip_address, format: { with: /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/ }, allow_blank: true
validates :description, length: { maximum: 255 }
has_many :host_relations, dependent: :delete_all
has_many :services, through: :host_relations
has_many :roles, through: :host_relations
has_many :activities, as: :model
has_many :comments, as: :model
accepts_nested_attributes_for :host_relations,
allow_destroy: true,
reject_if: lambda{ |attrs|
attrs[:service_id].blank? || attrs[:role_id].blank?
}
end
このモデルクラスは ActiveRecord という、データベースを扱うクラスを継承していますが、内容については、今回は深くは触れません。
もう1度 index アクションの中を見てみます。
def index
@hosts_without_deleted = Host.without_deleted
@deleted_hosts = Host.deleted
respond_with @hosts_without_deleted
end
@hosts_without_deleted = Host.without_deleted
で、変数 @hosts_without_deleted
に Host モデルの without_deleted
メソッドの実行結果を代入しています。また、変数 @deleted_hosts
に Host モデルの deleted
メソッドの実行結果を代入しています。
最後に、respond_with @hosts_without_deleted
で、ビューの処理を行っています。この処理では、/hosts にアクセスした場合には、フォーマットが HTML であるので、app/views/hosts/index.html.erb というテンプレートを描画します。テンプレートの内容は以下のようになっています。
<%- model_class = Host -%>
<div class="page-header">
<h1><%= model_class.model_name.human(count: 2) %></h1>
</div>
<%= render partial: 'shared/hosts', locals: { hosts: @hosts_without_deleted, is_deletable: true } %>
<%= link_to t('helpers.links.new'), new_host_path, class: 'btn btn-primary lsf-icon', title: 'plus' %>
<% unless @deleted_hosts.blank? -%>
<hr />
<h2><%= t('.destroyed_hosts') %></h2>
<%= render partial: 'shared/hosts', locals: { hosts: @deleted_hosts, is_deletable: false } %>
<% end -%>
この中で、先ほど出てきた変数 @hosts_without_deleted
と @deleted_hosts
が使われています。
では、/hosts.json にアクセスした場合は、どのような動きになるでしょうか?この場合、最後の respond_with @hosts_without_deleted
の部分の動きが変わります。
まず、JSON フォーマットに対応したテンプレート app/views/hosts/index.json.erb を探して描画しようとします。ところが、このファイルは存在しません。そこで Rails は respond_with に引数として与えられた変数 @hosts_without_deleted
の内容を、JSON フォーマットに変換して出力します。結果として、以下のようなレスポンスが返ります。
[{"host":{"id":1,"ip_address":"","name":"users001.sqale.jp","description":"","created_at":"2012-11-27T09:42:26Z","updated_at":"2012-11-27T09:42:26Z","deleted_at":null,"active":true}}]
Triglav にミドルウェアの名前とバージョンを管理する機能を追加してみましょう。
最初に、Triglav の root ディレクトリに移動し、master ブランチを最新の状態にします。
$ git checkout master
$ git pull origin master
次に、ミドルウェア管理機能追加用のブランチを作成し、そのブランチに移動します。
$ git checkout -b manage-middleware
Rails には scaffold という、自動でコード生成してくれる機能がありますので、まずはこれを利用して、必要なファイルを自動生成してみます。ミドルウェアは、名前とバージョンを管理するものとします。
$ bundle exec rails generate scaffold middleware name:string version:string
invoke active_record
create db/migrate/20121129035419_create_middlewares.rb
create app/models/middleware.rb
invoke rspec
create spec/models/middleware_spec.rb
invoke resource_route
route resources :middlewares
invoke scaffold_controller
create app/controllers/middlewares_controller.rb
invoke erb
create app/views/middlewares
create app/views/middlewares/index.html.erb
create app/views/middlewares/edit.html.erb
create app/views/middlewares/show.html.erb
create app/views/middlewares/new.html.erb
create app/views/middlewares/_form.html.erb
invoke rspec
create spec/controllers/middlewares_controller_spec.rb
create spec/views/middlewares/edit.html.erb_spec.rb
create spec/views/middlewares/index.html.erb_spec.rb
create spec/views/middlewares/new.html.erb_spec.rb
create spec/views/middlewares/show.html.erb_spec.rb
create spec/routing/middlewares_routing_spec.rb
invoke rspec
create spec/requests/middlewares_spec.rb
invoke helper
create app/helpers/middlewares_helper.rb
invoke rspec
create spec/helpers/middlewares_helper_spec.rb
invoke assets
invoke coffee
create app/assets/javascripts/middlewares.js.coffee
invoke scss
create app/assets/stylesheets/middlewares.css.scss
invoke scss
identical app/assets/stylesheets/scaffolds.css.scss
こんな形で、たくさんファイルが生成されます。
コマンドでは middleware と単数系で指定しましたが、生成されたファイルを見ると、middleware が単数系のものもあれば複数形のものもあります。どのケースで単数系が使われ、どのケースで複数形が使われるかは、Rails の規約で決まっています。
生成されたファイルの中に、db/migrate/20121129035419_create_middlewares.rb というファイルがあります。(数字の部分は実行した日時によって変わります。)これは、ミドルウェア管理用のテーブルをデータベースに追加するためのファイルで、内容は以下のようになっています。
class CreateMiddlewares < ActiveRecord::Migration
def change
create_table :middlewares do |t|
t.string :name
t.string :version
t.timestamps
end
end
end
テーブル名は Rails の規約に従い、複数形になっています。
rake db:migrate を実行することで、テーブルが作成されます。
$ bundle exec rake db:migrate
== CreateMiddlewares: migrating ==============================================
-- create_table(:middlewares)
-> 0.4230s
== CreateMiddlewares: migrated (0.4231s) =====================================
作成されたテーブルを見てみます。
$ mysql -uroot triglav_development
mysql> show tables;
+-------------------------------+
| Tables_in_triglav_development |
+-------------------------------+
| activities |
| comments |
| host_relations |
| hosts |
| middlewares |
| roles |
| schema_migrations |
| services |
| users |
+-------------------------------+
9 rows in set (0.00 sec)
mysql> desc middlewares;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | YES | | NULL | |
| version | varchar(255) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
5 rows in set (0.01 sec)
指定された、name と version が存在しています。id, created_at, updated_at は自動で追加されます。
他のファイルの内容を見る前に、http://yourhost:3000/middlewares にアクセスして、ミドルウェアの追加、編集、削除などができることを確認してみましょう。
git diff でルーティング設定がどのように変わったかを見てみましょう。
$ git diff config/routes.rb
diff --git a/config/routes.rb b/config/routes.rb
index 28ef6f9..0abc8c3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,6 @@
Triglav::Application.routes.draw do
+ resources :middlewares
+
root 'root#index'
get '/caveat', to: 'root#caveat'
resources :middlewares
という一行が追加されています。これにより、以下のようなルーティングが設定されます。
$ rake routes |grep middleware
middlewares GET /middlewares(.:format) middlewares#index
POST /middlewares(.:format) middlewares#create
new_middleware GET /middlewares/new(.:format) middlewares#new
edit_middleware GET /middlewares/:id/edit(.:format) middlewares#edit
middleware GET /middlewares/:id(.:format) middlewares#show
PATCH /middlewares/:id(.:format) middlewares#update
PUT /middlewares/:id(.:format) middlewares#update
DELETE /middlewares/:id(.:format) middlewares#destroy
app/controllers/middlewares_controller.rb の内容を見てみましょう。
class MiddlewaresController < ApplicationController
# GET /middlewares
# GET /middlewares.json
def index
@middlewares = Middleware.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @middlewares }
end
end
# GET /middlewares/1
# GET /middlewares/1.json
def show
@middleware = Middleware.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: @middleware }
end
end
... 以下略 ...
index, show, new, edit, create, update,destroy といったアクションが自動生成されていることが確認できます。
app/models/middleware.rb の内容を確認してみましょう。
class Middleware < ActiveRecord::Base
end
これだけです。
app/views/middlewares の下に、複数のビューファイルが生成されています。
$ ls app/views/middlewares
_form.html.erb edit.html.erb index.html.erb new.html.erb show.html.erb
M, V, C それぞれのファイルの内容に関する説明は、ハンズオンの場でディスプレイに映しながら説明します。
最後に、manage-middleware ブランチにコミットして、機能追加開発を終えましょう。
$ git add .
$ git commit
今回は MVC とは何かについて軽く学び、Rails の scaffold を用いてコードを自動生成してみました。
追加した機能はその中だけで閉じていて、Triglav の他の部分とつながっていないため、機能追加した、という実感はあまりないかもしれませんが、Rails で開発を行うためには、M, V, C それぞれのファイルをどの位置にどのように配置するのか、といったことは掴んでもらえたかと思います。
また、scaffold は、やりたいことによっては、不要なファイルやアクションも追加されてしまうので、Rails での開発に慣れてくると、scaffold は使わずに、自前でファイルを作ることが多いです。
とはいえ、scaffold で自動生成されるファイルを見ることによって、Rails で開発する上でのルールがわかるので、慣れないうちは scaffold を大いに活用してください。特に、単数形と複数形の使い分けは覚えにくいので、忘れたら scaffold で適当にファイルを生成してみましょう。
単に確認のために作成し、確認後に元の状態に戻したい場合には、以下のように git コマンドを実行することで、生成されたファイルやディレクトリを削除し、変更されたファイルを元に戻すことができます。
$ git clean -nd
Would remove app/assets/javascripts/middlewares.js.coffee
Would remove app/assets/stylesheets/middlewares.css.scss
Would remove app/controllers/middlewares_controller.rb
Would remove app/helpers/middlewares_helper.rb
Would remove app/models/middleware.rb
Would remove app/views/middlewares/
Would remove db/migrate/20121129065540_create_middlewares.rb
Would remove spec/controllers/
Would remove spec/helpers/middlewares_helper_spec.rb
Would remove spec/models/middleware_spec.rb
Would remove spec/requests/
Would remove spec/routing/
Would remove spec/views/
$ git clean -fd
Removing app/assets/javascripts/middlewares.js.coffee
Removing app/assets/stylesheets/middlewares.css.scss
Removing app/controllers/middlewares_controller.rb
Removing app/helpers/middlewares_helper.rb
Removing app/models/middleware.rb
Removing app/views/middlewares/
Removing db/migrate/20121129065540_create_middlewares.rb
Removing spec/controllers/
Removing spec/helpers/middlewares_helper_spec.rb
Removing spec/models/middleware_spec.rb
Removing spec/requests/
Removing spec/routing/
Removing spec/views/
$ git checkout .