ドットインストール Rails まとめ
ドットインストールさんで Rails について振り返ったので、勉強メモ。
Rails は前にちょこっとやった時は、ただコードを書き写していただけに等しかったのですが、今回、RubyやらActiveRecord やらを勉強しなおした後にやると理解、納得しながらできたので得るものは大きかったです。
これは、自分で何もみずに同じようなアプリを作るという試みの際の解答のようなものになればと思い残しておきます。
[1] 事前準備
実行環境
自分の実行環境は以下のとおり。
補足
gem に関しては全て Bundler を使って入れます。
詳しくは rbenv と bundler で rails 環境構築メモ に書きましたので宜しければ。
あと、毎回 bundle exec
と打つのがめんどかったので be
というエイリアスを設定しています。適宜読み替えてください。
手順
まずは事前準備をしていきます。
1. rails new してフォルダに移動
$ be rails new taskapp
$ cd taskapp
2. Gemfile
を編集
gem 'therubyracer', platforms: :ruby
のコメントを外す
3. gem をインストールする
$ bundle install
[2] Project に関するあれやこれ
タスク管理アプリということで、プロジェクト毎にタスクがあると思うので、まずは大本となるプロジェクト部分に関するあれやこれを作っていきます。
以下、実際の手順。
手順
1. Model と Controller を用意する
$ be rails g model Project title:string
で Model を作成- Model の名前は単数形で最初を大文字にする必要がある
- データ型のデフォルトは文字列なので
:string
は省略可能
$ be rake db:migrate
でデータベースを作る。$ be rails g controller Projects
でController を作成- Controller の名前は複数形で最初を大文字にする必要がある
2. ルーティングをいじる
config/routes.rb
を編集
Rails.application.routes.draw do resources :projects root 'projects#index' end
3. View を作る
app/views/projects/index.html.erb
を作成する- 現時点で中身は空でもおk
4. 共通点プレートをいじる
① 共通ロゴと共通リンクを設定する
app/assets/images
に 適当なlogo.png
ファイルを入れておくapp/views/layouts/application.html.erb
を編集
<!DOCTYPE html> <html> <head> <title>Taskapp2</title> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> </head> <body> <%= image_tag "logo.png" %> <%= yield %> <%= link_to "Home", projects_path %> </body> </html>
image_tag
ヘルパーとlink_to
ヘルパーを利用して上記のように書くprojects_path
は$ be rake routes
でルーティングを確認した際の左端( Prefix カラム) に_path
をつけたもの
② 共通スタイルシートを設定する
app/assets/stylesheets/application.css
をいじる
body {background-color: #eee;}
5. 新規作成フォームを作成し、データを保存し、表示する
① app/controllers/projects_controller.rb
をいじる
class ProjectsController < ApplicationController def index @projects = Project.all end def show @project = Project.find(params[:id]) end def new @project = Project.new end def create @project = Project.new(project_params) @project.save redirect_to projects_path end private def project_params params[:project].permit(:title) end end
private
以下はセキュリティ対策のためのもの。
② app/views/projects/new.html.erb
を作成する
<h1>Add New</h1> <%= form_for @project do |f| %> <p> <%= f.label :title %><br> <%= f.text_field :title %> </p> <p> <%= f.submit %> </p> <% end %>
form_for
ヘルパーを利用する- submit したものが Projects#create に送られる
③ app/views/layouts/application.html.erb
をいじる
<!DOCTYPE html> <html> <head> <title>Taskapp2</title> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> </head> <body> <%= image_tag "logo.png" %> <%= yield %> <%= link_to "Add New", new_project_path %> | <%= link_to "Home", projects_path %> </body> </html>
④ app/views/projects/index.html.erb
を編集する
<h1>Projects</h1> <ul> <% @projects.each do |project| %> <li> <%= link_to project.title, project_path(project.id) %> </li> <% end %> </ul>
⑤ app/views/projects/show.html.erb
を作成する
<h1><%= @project.title %></h1>
6. Validation を設定してエラーメッセージを表示する
- 今ままでは空の状態でも新規登録できてしまうので、
app/models/project.rb
を編集
class Project < ActiveRecord::Base validates :title, presence: true end
- 今の設定のままだと、Validation に引っかかった時でも、 プロジェクト一覧にリダイレクトしてしまうので、
app/controllers/projects_controller.rb
の create アクション を次のように編集
def create @project = Project.new(project_params) if @project.save redirect_to projects_path else render 'new' end end
- エラーメッセージを表示するとより親切なので
app/views/projects/new.html.erb
を編集していく
<h1>Add New</h1> <%= form_for @project do |f| %> <p> <%= f.label :title %><br> <%= f.text_field :title %> <% if @project.errors.any? %> <%= @project.errors.messages[:title][0] %> <% end %> </p> <p> <%= f.submit %> </p> <% end %>
エラーが発生している場合、
@project.errors
にエラーメッセージが入るので、<% if @project.errors.any? %>
で条件分岐をかけて、メッセージを表示させている。また、エラーメッセージ、バリデーションルールは任意のものに設定できる。
app/models/project.rb
を編集
class Project < ActiveRecord::Base validates :title, presence: {message: "入力してください"}, length: {minimum: 3, message: "短すぎ!"} end
7. 編集フォームを作ってデータを更新する
- まずは編集フォームを作っていく。
app/views/projects/edit.html.erb
を作成。といっても中身は<h1>
タグだけ変えて、あとはapp/views/projects/new.html.erb
と一緒。
<h1>Edit</h1> <%= form_for @project do |f| %> <p> <%= f.label :title %><br> <%= f.text_field :title %> <% if @project.errors.any? %> <%= @project.errors.messages[:title][0] %> <% end %> </p> <p> <%= f.submit %> </p> <% end %>
- 次に編集画面へのリンクを追加する。
app/views/projects/index.html.erb
を編集
<h1>Projects</h1> <ul> <% @projects.each do |project| %> <li> <%= link_to project.title, project_path(project.id) %> <%= link_to "[Edit]", edit_project_path(project.id) %> </li> <% end %> </ul>
- アクションをまだ作っていないので、
app/controllers/projects_controller.rb
に edit アクションを追加
def edit @project = Project.find(params[:id]) end
- この時点で編集画面へジャンプできるが、update アクションをまだ作っていないので、更新できない。
app/controllers/projects_controller.rb
に update アクションを追加
def update @project = Project.find(params[:id]) if @project.update(project_params) redirect_to projects_path else render 'edit' end end
8. データを削除する
app/views/projects/index.html.erb
を編集し、Delete リンクを追加する。data: {confirm: "are you sure?"}
と記述することで確認アラートを出すことができる。
<h1>Projects</h1> <ul> <% @projects.each do |project| %> <li> <%= link_to project.title, project_path(project.id) %> <%= link_to "[Edit]", edit_project_path(project.id) %> <%= link_to "[Delete]", project_path(project.id), method: :delete, data: {confirm: "are you sure?"} %> </li> <% end %> </ul>
- destroy アクションを追加する。
app/controllers/projects_controller.rb
に追加。
def destroy @project = Project.find(params[:id]) @project.destroy redirect_to projects_path end
9. 重複するコードを共通化し、 Dry を目指す
まずフォームで重複する部分があるので、共通化していく。
app/views/projects/new.html.erb
とapp/views/projects/edit.html.erb
の共通部分をカットし、<%= render 'form' %>
で置き換える。
<h1>Add New</h1> <%= render 'form' %>
<h1>Edit</h1> <%= render 'form' %>
app/views/projects/_form.html.erb
を作成し、共通部分をコピペ。
<%= form_for @project do |f| %> <p> <%= f.label :title %><br> <%= f.text_field :title %> <% if @project.errors.any? %> <%= @project.errors.messages[:title][0] %> <% end %> </p> <p> <%= f.submit %> </p> <% end %>
次に、Controller でも重複する部分があるので、共通化していく。
@project = Project.find(params[:id])
が大量に含まれるため、before_action
でまとめる。その際、set_project
関数を private に用意すると後々編集する際に便利。
class ProjectsController < ApplicationController before_action :set_project, only: [:show, :edit, :update, :destroy] def index @projects = Project.all end def show end (中略) private def project_params params[:project].permit(:title) end def set_project @project = Project.find(params[:id]) end end
[3] Task に関するあれやこれ
次に、プロジェクトに属するタスクを設定するあれやこれを作っていきます。
以下、実際の手順。
手順
1. Model と Controller を用意する
$ be rails g model Task title done:boolean project:references
で Model を作る- 繰り返しになるが、モデル名は単数形、
stirng
型は省略可能 booelan
は真偽値型で、project:references
は Project モデルと紐付けろ、という意味- タスクが終わっているかどうかという項目の
done
はデフォルトで 未完了を表すfalse
であるのが望ましいので、db/migrate/**********_create_tasks.rb
を以下のように修正。
- 繰り返しになるが、モデル名は単数形、
class CreateTasks < ActiveRecord::Migration def change create_table :tasks do |t| t.string :title t.boolean :done, default: false t.references :project, index: true t.timestamps end end end
$ be rake db:migrate
でデータベースを作る$ be rails g controller Tasks
で Controller を作る
2. Association を設定する
- 先ほど Task モデルを作った際には Project モデルとの紐付けオプションをつけて実行したが、 Project モデルを作った際にそのようなオプションはつけなかったので手動で設定する必要がある。
app/models/project.rb
を編集。
class Project < ActiveRecord::Base has_many :tasks validates :title, presence: {message: "入力してください"}, length: {minimum: 3, message: "短すぎ!"} end
これで、「一対多の関係」、つまり、「1つの Project に対して、複数の Task」 という関係ができた。
続いて、Task に関するルーティングをいじっていく。とりあえず、 create アクションと、 destroy アクションができればいいので以下のように、
config/routes.rb
を編集する
Rails.application.routes.draw do resources :projects do resources :tasks, only: [:create, :destroy] end root 'projects#index' end
3. 新規作成フォームを作り、 task を保存する
- task の新規作成フォームを作るために、
app/views/projects/show.html.erb
を編集する
<h1><%= @project.title %></h1> <u1> <% @project.tasks.each do |task| %> <li><%= task.title %></li> <% end %> <li> <%= form_for [@project, @project.tasks.build] do |f| %> <%= f.text_field :title %> <%= f.submit %> <% end %> </li> </u1>
- create アクションがまだないので、
app/controllers/tasks_controller.rb
に追加
class TasksController < ApplicationController def create @project = Project.find(params[:project_id]) @task = @project.tasks.create(task_params) redirect_to project_path(@project.id) end private def task_params params[:task].permit(:title) end end
- 空の task を弾くためのバリデーションを設定する。
app/models/task.rb
を編集
class Task < ActiveRecord::Base belongs_to :project validates :title, presence: true end
4. Task を削除する
- Delete リンクを追加するために、
app/views/projects/show.html.erb
を編集する
<h1><%= @project.title %></h1> <u1> <% @project.tasks.each do |task| %> <li> <%= task.title %> <%= link_to "[Delete]", project_task_path(task.project_id, task.id), method: :delete, data: {confirm: "are you sure?"} %> </li> <% end %> <li> <%= form_for [@project, @project.tasks.build] do |f| %> <%= f.text_field :title %> <%= f.submit %> <% end %> </li> </u1>
- destroy アクションをまだ作っていないので、
app/controllers/tasks_controller.rb
に追加
def destroy @task = Task.find(params[:id]) @task.destroy redirect_to project_path(params[:project_id]) end
5. チェックボックスを用意して done を切り替える
app/views/projects/show.html.erb
のリストの中のタスクタイトルの前にチェックボックスを追加。check_box_tag
ヘルパーを利用。最初の引数にはid
を、2番目の引数にはvalue
を、3番目の引数にはチェックされているかどうかのtrue/false
をそれぞれ指定できる。- その後ろには、 HTML に渡したい属性をいくらでも追加できる。今回は、後で jQuery で使いたいので、扱いやすいように
task.id
とproject_id
をdata-id
とdata-project-id
という属性で管理できるようにした。 - 下のコードを追記してから、ブラウザでソースを見ると、
<input data-id="…" data-project_id="…" id="" name="" type="checkbox" value="" />
というのが出てきており、data-id 属性と data-project_id 属性で Task の id と Project の id が管理できているのがわかる。
<h1><%= @project.title %></h1> <u1> <% @project.tasks.each do |task| %> <li> <%= check_box_tag '', '', task.done, {'data-id' => task.id, 'data-project_id' => task.project_id} %> <%= task.title %> <%= link_to "[Delete]", project_task_path(task.project_id, task.id), method: :delete, data: {confirm: "are you sure?"} %> </li> <% end %> <li> <%= form_for [@project, @project.tasks.build] do |f| %> <%= f.text_field :title %> <%= f.submit %> <% end %> </li> </u1>
- 次に、チェックボックスが切り替わるたびに、 POST を送りたい。そのための toggle アクションを jQuery を用いつつ、用意する。
app/views/projects/show.html.erb
の一番下に以下のスクリプトを追加する。 - このスクリプトの意味は、「チェックボックスがクリックされた際に、 Ajax で
/projects/xxx/tasks/xxx/toggle
に POST しなさい」という意味。
<script> $(function(){ $("input[type=checkbox]").click(function(){ $.post('/projects/'+$(this).data('project_id')+'/tasks/'+$(this).data('id')+'/toggle'); }); }) </script>
- POST のルーティングが未設定なので
config/routes.rb
で設定する。以下を追記。
post '/projects/:project_id/tasks/:id/toggle' => 'tasks#toggle'
- POST 先の toggle アクションがないので作る。
app/controllers/tasks_controller.rb
に追加。また、今回は、 toggle アクションリクエストした際に、テンプレートを使わないので、その旨も書く。
def toggle @task = Task.find(params[:id]) @task.done = !@task.done @task.save render nothing: true end
- 以上で、タスクの完了/未完了がページをまたいでも保持されるようになる。
6. Tasks の数を表示する
- まず残タスク数を表示したいので、「
done
がfalse
のものを検索条件として保存しなさい」という意味で、app/models/tasks.rb
に以下を追記。 - これで
:unfinished
関数ができるので、どこからでも残タスク数にアクセスできる。
scope :unfinished, -> {where(done: false)}
- 実際に表示するために、
app/views/projects/index.html.erb
を以下のように書き換える。タイトル部分に先ほど定義した:unfinished
関数 を用いて、残タスク数を表示している。
<h1>Projects</h1> <ul> <% @projects.each do |project| %> <li> <%= link_to project.title, project_path(project.id) %> (<%= project.tasks.unfinished.count %>/<%= project.tasks.count %>) <%= link_to "[Edit]", edit_project_path(project.id) %> <%= link_to "[Delete]", project_path(project.id), method: :delete, data: {confirm: "are you sure?"} %> </li> <% end %> </ul>