Progate Ruby on Rails5
Ruby on Railsの環境構築をしてみよう!
windowsOS
- Ruby のインストール(略)
- SQLite3のインストール
- https://sqlite.org/index.html にアクセスして Download
- Precompiled Binaries for Windows (sqlite-dll-win64-x64-3320100.zip) (64bitの場合) をインストールして,
sqlite3.dll
をC:\Ruby26-x64\bin
にコピー (32bitのときは32bit用のものをインストール) - Precompiled Binaries for Windows (sqlite-tools-win32-x86-3320100.zip) をインストールして,
sqlite3.exe
をC:\Ruby26-x64\bin
にコピー
- Rails のインストール
gem install rails -v "5.2.3" # gem はパッケージマネージャー rails -v
- Rails アプリケーションの作成
rails new sample_app -G # sample_app はアプリケーション名 ## 以下は, sqlite3のインストールに失敗してエラーが出た場合 ### 多分, Installing sqlite3 1.4.2 with native extensions のこと cd sample_app ridk exec pacman -S mingw-w64-x86_64-sqlite3 bundle install ## 以上, sqlite3のインストールに失敗してエラーが出た場合
- ローカルにサーバを立てる
cd sample_app rails s # これで localhost:3000 にアクセスできる # Ctrl + C でサーバは止まる
macOS
- Homebrew, rbenv はインストール済みとする
rbenv versions # Rails 5系はRuby 2.2.2以上対応
- Rails のインストール
gem install rails -v "5.2.4.1" rails -v
- アプリケーションの作成とローカルにサーバを立てる
rails new sample_app # sample_app はアプリケーション名 cd sample_app rails s # これで localhost:3000 にアクセスできる # Ctrl + C でサーバは止まる
Ruby on Rails5 I
- アプリケーションの作成:
$ rails new アプリケーション名
サーバの起動:
$ rails server
ページの生成:
$ rails generate controller home top
(コントローラー名, アクション名)- views:
app/views/home/top.html.erb
が自動生成される - controller:
app/controllers/home_controller.rb
が自動生成され,top
アクションが追加される。 - routing:
config/routes.rb
にget "home/top" => "home#top"
が自動追記される。 - scss:
app/assets/stylesheets/home.scss
が自動生成される
- views:
ただし, コントローラーが存在する場合はこのコマンドは使えないので,
- routing を追記する
- 対応する controller のアクションを追記する
- それで呼び出すファイルを新規作成する
処理の流れ
GET /home/top
を受け取る- routing により,
home
コントローラーのtop
アクションが呼ばれる - それが,
app/views/home/top.html.erb
を返す。
構成要素
- views: ページの見た目を作るためのHTMLファイル
- controller: ブラウザに返すビューを views フォルダの中から見つけ出して返す役割をもつ。コントローラ内のメソッドをアクションと呼ぶ。
- routing:
get "URL" => "コントローラー名#アクション名"
という文法(対応表のような感じ)で, リクエストを受け取り, コントローラーを呼び出す。
- ルーティングは手動で変更可能
/
はget "/" =>
/top
はget "top" =>
<a href="/top">
のようにリンクを張る
- 「stylesheets」フォルダの中に保存されているCSSファイルはすべてのビューに適用される。
- 画像は
public
フォルダに置く。public/hoge.png
は<img src="/hoge.png" >
のようにアクセスできる。
Ruby on Rails5 II
$ rails generate controller
は$ rails g controller
と省略可能- view ファイルの拡張子の
erb
はEmbedded Ruby
- コードは
<% %>
で囲む - 変数埋め込みは
<%= 変数名 %>
- コードは
# view で使う変数は一般にアクションで定義する # ただし, その場合の変数名は @ で始める必要がある def index @items = ["A", "B", "C"] end
<% @items.each do |item| %><!-- 呼び出しも当然 @ が付く--> <li><%= item %></li> <% end %>
# app/models/post.rb class Post < ApplicationRecord end
$ rails console # Post のコンストラクタやsaveメソッドはApplicationRecordを継承している > post1 = Post.new(content: "Hoge") # レコードの生成 > post1.save # DB に登録 > p1 = Post.first # DB の最初のレコードの取り出し > p1.content # カラムの値をみる。 > pall = post.all # 全てを配列として取り出す > post.all[0].content
- 例えば, 次のように DB の値を埋め込む
def index @records = Post.all end
views/layouts/application.html.erb
に共通のHTMLを書いておく- つまり, 表示の共通部分(ヘッダなど)はここに書いて, それ以外を各
erb
ファイルにこれまで通り書く。それが, このファイルの<%= yield %>
に埋め込まれて表示される。
- つまり, 表示の共通部分(ヘッダなど)はここに書いて, それ以外を各
- リンク生成メソッド link_to
<%= link_to("hoge","/top") %>
は<a href="/top">hoge</a>
として埋め込まれる
Ruby on Rails5 III
モデル名.find_by(カラム名: 値)
で, カラムがその値なレコードを返す。- パラメータをとるルーティング
# config/routes.rb Rails.application.routes.draw do get "posts/index" => "posts#index" # ルーティングは合致するURLを上から順に探す。 get "posts/:id" => "posts#show" # /posts/hoge などが該当 end
# app/controllers/posts_controller.rb class PostsController < ApplicationController def index @posts = Post.all.order(created_at: :desc) # created_at カラムの値を降順(:desc)に並び替えた配列にする end def show @id = params[:id] @post = Post.find_by(id: @id) end end
<!-- app/views/posts/show.html.erb --> <p><%= "idが #{@id} のとき" %></p> <p><%= @post.content %></p>
- post
# config/routes.rb Rails.application.routes.draw do get "posts/new" => "posts#new" post "posts/create" => "posts#create" # post を受ける end
# app/controllers/posts_controller.rb class PostsController < ApplicationController def new end def create @post = Post.new(content: params[:content]) # params[:content] でリクエストパラメータを受け取る # 代わりに content: "hoge" のようにすればデフォルトの値を設定できる。 @post.save redirect_to("/posts/index") # view を用意する代わりにリダイレクトする end end
<!-- app/views/posts/new.html.erb --> <%= form_tag("/posts/create") do %> <!-- <form action="/posts/create" method="post"> --> <!-- 上で, do , 下で, end を忘れないように --> <textarea name="content"></textarea> <input type="submit" value="投稿"> <% end %> <!-- 今回は </form> -->
Ruby on Rails5 IV
- DB の書き換え
post = Post.find_by(id: 1) # インスタンスをDBから取り出して post.content = "hoge" # 代入するだけ post.save # 変更後のインスタンスをDBに保存
- DB のレコードの削除
post = Post.find_by(id: 1) # インスタンスをDBから取り出して post.destroy # 破棄して DB 更新
- 編集ページ
get "posts/:id/edit" => "posts#edit"
- 編集ページ
app/views/posts/edit.html.erb
の用意- edit コントローラで
@post = Post.find_by(id: params[:id])
とレコードを変数としてページに渡す <textarea name="content"><%= @post.content %></textarea>
のように初期値を設置する/posts/:id/update
とかに post する(:id
は@post.id
を埋め込む)
- edit コントローラで
- 表示ページ(
/posts/show/:id
) から,<%= link_to("編集", "/posts/#{@id}/edit") %>
のようなリンクを張っておく) - アクション
posts#update
の用意params[:id]
,params[:content]
を受け取る- レコードを書き換えて
/posts/#{params[:id]}/show
とかにリダイレクトする
- 破棄(ページを用意しない分簡単)
link_to("破棄", /posts/#{@post.id}/delete, {method: "post"})
で/posts/:id/show
とかからリンクを張るlink_to
の第三引数でメソッドを指定できる(デフォルトでget
)
Ruby on Rails5 V
- validation
# app/models/post.rb class Post < ApplicationRecord validates: content, {presence: true, length: {maximum: 140}} # これで Post のインスタンス(e.g. post)は content カラムが # 空, または 文字数140字以上 # だと post.save 出来なくなる # さらに, post.errors.full_messages にエラーメッセージの配列が格納される。(デフォルトは空の配列) end
- save の戻り値は true または false なので, これを受けてその後の動きを変更すれば良い
- 表示
redirect_to("URL")
: get "URL" にリダイレクトされる。そのため, ルーティングされたコントローラのアクションを経由する。(e.g.redirect_to("/posts/index")
)render("PATH")
: 別のアクションを経由せずに、直接ビューを描画する。PATH は pwd が views で, 拡張子はいらない。(e.g.render("posts/index")
)
- ページ上に1度だけ表示(リロードすると消える)されるメッセージをフラッシュと呼ぶ。
- アクションで
flash
というハッシュをflash[:hoge] = "fuga"
とすると, view でflash[:hoge]
を使用できる。 - この
hoge: "fuga"
は1度表示された後に自動で削除される。
- アクションで
Ruby on Rails5 VI
- name, email カラムを持つ users データベースの作成
$ rails g model User name:string email:string
$ rails db:migrate
- validation
# app/models/user.rb class User < ApplicationRecord validates :name, {presence: true} validates :email, {presence: true, uniqueness: true} # 他のレコードと重複無し end
Ruby on Rails5 VII
- テーブルの新規作成
# db/migrate/20170418070645_create_users.rb class CreateUsers < ActiveRecord::Migration[5.0] def change create_table :users do |t| t.string :name t.string :email t.timestamps end end end
# app/models/user.rb class User < ApplicationRecord end
- テーブルのカラム追加
$ rails g migration add_image_name_to_users
: マイグレーションファイルのみの作成db/migrate/20200531023644_add_image_name_to_users.rb
が自動生成される。- これに下のように追記する。
# db/migrate/20200531023644_add_image_name_to_users.rb class AddImageNameToUsers < ActiveRecord::Migration[5.0] def change # ここから先を追記する add_column :users, :image, :string # テーブル名, カラム名, データ型を指定する。 # 同じ要領で, remove_column でカラムの削除が可能 end end
- 画像の投稿
<%= form_tag("/users/upload",{multipart: true}) do %> <!-- {multipart: true} を与える --> <input type="file" name="gazo"> <!-- type="file" --> <input type="text" name="name"> <input type="submit" value="投稿"> <% end %>
- これを
ユーザid.jpg
としてDBにファイル名を登録し, public/user_images/ユーザid.jpg
としてファイル本体を保存する
# app/controllers/users_controller.rb class UsersController < ApplicationController def update @user = User.find_by(id: params[:id]) @user.name = params[:name] if params[:gazo] # 画像がある場合のみ @user.image_name = "#{@user.id}.jpg" # このファイル名で保存するので一意である必要がある。 image = params[:gazo] # 画像のバイナリが入っている # 保存先のパスと image.read File.binwrite("public/user_images/#{@user.image_name}", image.read) # cf. テキストの保存は write メソッド # File.write("public/sample.txt", "Hello World!") end @user.save end end
Ruby on Rails5 VIII
- ルーティングで, 同じURLにget, postを設置可能。ただし, コントローラーは違うアクションにする必要がある。
- ログイン機能
- ユーザ名とパスワードを入力するフォームを作る
- ユーザ名(
{uniqueness: true}
なカラム)とパスワードを "/login" とかに post する <input type="password">
: 入力文字が伏字になる。
- ユーザ名(
- post "/login" をうけるコントローラーを作る
@user = User.find_by(name: params[:name], password: params[:password])
- 複数カラムをアンド検索することができる
@user
が存在するかで挙動を分岐- あれば
flash[:notice]
とかしてログインページにリダイレクト
- あれば
- 存在しなければ, ログインフォームを再描画
- リダイレクトではないのは, 受け取ったパラメータを @email, @password に代入してこれを埋め込むため。
- エラーメッセージは @error_message とかに固定値を代入してこれを埋め込めばよい
- 最初にフォームをGETしたときはこれらは
nil
なので, <%= @email %> で何も埋め込まれない?
- 最初にフォームをGETしたときはこれらは
- ログイン後のユーザー情報の保持
- ユーザ情報の表示
- view側で管理する方法(あんまりよくない)
app/views/layouts/application.html.erb
(各htmlを埋め込む先のファイル) に<% current_user = User.find_by(id: session[:id]) %>
を書いて置く- この共通htmlで
<% if current_user %>
でログイン中かで分岐可能 - 各ページで
<% current_user.name %>
でカラムにアクセス可能
- この共通htmlで
- controller で before_action として定義する方法
- view側で管理する方法(あんまりよくない)
- ユーザ名とパスワードを入力するフォームを作る
# app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :set_current_user # ※ 全アクションの前に実施するアクション def set_current_user @current_user = User.find_by(id: session[:user_id]) end def authenticate_user # ログインしていない場合(@current_user == nil)に表示させないためには, アクションの前にリダイレクトさせてしまえばよい。 if @current_user == nil flash[:notice] = "ログインが必要です" redirect_to("/login") # 当然だが, このページをログイン必須にするとリダイレクトがループする。 end end def forbid_login_user # 逆にログインしているとアクセスできないページ if @current_user flash[:notice] = "すでにログインしています" redirect_to("/posts/index") end end def ensure end
# app/controllers/users_controller.rb class UsersController < ApplicationController # 上の before_action :set_current_user (※の行)は継承されている before_action :authenticate_user, {only: [:index, :show, :edit, :update]} # :authenticate_user は継承されている。 # only をつけた場合は配列で指定されたアクションの前だけに実行される。 before_action :forbid_login_user, {only: [:new, :create, :login_form, :login]} before_action :ensure_correct_user, {only: [:edit, :update]} def index @users = User.all end # 以下略 def ensure_correct_user # ここで定義することもできる if @current_user.id != params[:id].to_i # これで id が違う場合に書き換え系のアクション にはアクセスできなくなる。 flash[:notice] = "権限がありません" redirect_to("/posts/index") end end end
Ruby on Rails5 IX
- インスタンスメソッドをmodelに定義することも可能
# app/models/post.rb class Post < ApplicationRecord validates :user_id, {presence: true} def user # 自身(postsテーブルのレコード)のuser_id と一致する id を持つusersテーブルのレコードを返す return User.find_by(id: self.user_id) end end
- レコードの検索
find_by
メソッドはレコードを一件だけ見つけてそのレコードを返す。where
メソッドはレコードを全て見つけて, レコードの配列を返す。(引数などはfind_by
と同じ。)
Ruby on Rails5 X
- コントローラーは手動でも作成可能(viewファイルが不要な時)
- html要素に対して
link_to
を使うとき, 第一引数の文字列の代わりに下のようにする。- 第二引数以降は同じ。
<%= link_to("URL", {method: "post"}) do %> <!-- 上で do が必要, 下で end が必要, --> <img src="URL"> # 画像など <% end %>
Ruby on Rails5 XI
- Gemfile (package.jsonみたいな感じ)
gem 'パッケージ名', 'バージョン'
を列挙- バージョンの指定はオプションで, 範囲指定もできる。
- 指定なしの場合は最新のものとなる。
- dependencies, devDependencies よりも細かくインストールする環境を指定できるらしい。
source 'https://rubygems.org' gem 'rails', '5.0.3' # Use Puma as the app server gem 'puma', '3.6.2' # Use SCSS for stylesheets gem 'sass-rails', '5.0.6' # Use Uglifier as compressor for JavaScript assets gem 'uglifier', '3.0.4' gem 'pry-rails', '0.3.4' # Use jquery as the JavaScript library gem 'jquery-rails', '4.2.2' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '5.0.1' group :development, :test do gem 'sqlite3', '1.3.13' gem 'byebug', '9.0.6', platform: :mri gem 'web-console', '3.4.0' gem 'listen', '3.0.8' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring', '2.0.1' gem 'spring-watcher-listen', '2.0.1' end
- Gemfile.lock:
bundle
などで自動生成されるgemの依存関係を示したもの(package-lock.jsonみたいな感じ) $ bundle install
Gemfile.lock
を元にインストールを行います。- ただし,
Gemfile.lock
になく,Gemfile
にある gem があるとき, 必要な gem たちをインストールした上でGemfile.lock
を更新する。($ npm ci
に近い)
$ bundle update
$ npm i
にほぼ同じ。
パスワードの暗号化
gem 'bcrypt'
- パスワードを暗号化したいDBテーブルのmodelのクラスを下記のようにする。
- そのうえで, カラム名は
password_digest
とする。 - ただし, テーブル上のカラム名は
password_digest
だが, パスワードのセットはpassword
に対して行う(それがpassword_digest
)に自動反映される。 - パスワードの比較は, レコードに対して authenticate メソッドを使う
@user.authenticate("hoge")
で,@user
のpassword_digest
が文字列 hoge を暗号化したものに一致するかを真偽値として返す。
# app/models/user.rb class User < ApplicationRecord has_secure_password end