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.dllC:\Ruby26-x64\bin にコピー (32bitのときは32bit用のものをインストール)
    • Precompiled Binaries for Windows (sqlite-tools-win32-x86-3320100.zip) をインストールして, sqlite3.exeC:\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.rbget "home/top" => "home#top" が自動追記される。
    • scss: app/assets/stylesheets/home.scss が自動生成される
  • ただし, コントローラーが存在する場合はこのコマンドは使えないので,

    • routing を追記する
    • 対応する controller のアクションを追記する
    • それで呼び出すファイルを新規作成する
  • 処理の流れ

    • GET /home/top を受け取る
    • routing により, home コントローラーの top アクションが呼ばれる
    • それが, app/views/home/top.html.erb を返す。
  • 構成要素

    • views: ページの見た目を作るためのHTMLファイル
    • controller: ブラウザに返すビューを views フォルダの中から見つけ出して返す役割をもつ。コントローラ内のメソッドをアクションと呼ぶ。
    • routing: get "URL" => "コントローラー名#アクション名" という文法(対応表のような感じ)で, リクエストを受け取り, コントローラーを呼び出す。
  • ルーティングは手動で変更可能
    • /get "/" =>
    • /topget "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 ファイルの拡張子の erbEmbedded Ruby
    • コードは<% %>で囲む
    • 変数埋め込みは <%= 変数名 %>
# view で使う変数は一般にアクションで定義する
# ただし, その場合の変数名は @ で始める必要がある
def index
    @items = ["A", "B", "C"]
end
<% @items.each do |item| %><!-- 呼び出しも当然 @ が付く-->
    <li><%= item %></li>
<% end %>
  • マイグレーションファイル(データベースに変更を指示するためのファイル)
    • $ rails g model Post content:textにより, db/migrate の下にマイグレーションファイルが自動生成される。
      • ggenerate も可
      • Post とすると, デフォルトで posts テーブルになる
      • content カラムのデータ型が text
        • 自動で id, created_at, updated_at が管理される。
    • このコマンドで, app/models/post.rb が生成され, Post クラスが定義されている。
    • 次に $ rails db:migrateマイグレーションファイルの内容が DB に反映される
  • $ rails console インタラクティブRuby を実行できる
# 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を埋め込む)
    • 表示ページ(/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

  • テーブルの新規作成
    • $ rails g model User name:string email:string: モデルとマイグレーションファイルの作成
      • db/migrate/20170418070645_create_users.rb が自動生成される
      • app/models/user.rb が自動生成される。
    • $ rails db:migrate: マイグレーションファイルのchangeメソッドの実行
      • users テーブルが自動生成される
# 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 %> で何も埋め込まれない?
    • ログイン後のユーザー情報の保持
      • ログイン時に, session[:id] = 243 とすると, クライアント側にこの値(セッションID)が送信される。(ID に紐づく中身のほうはサーバ側で管理する。)
        • session を送信するときは POST を使う
      • クライアント側はこれを Cookie(デベロッパツールのApplicationとかで確認可能)に保存して, そのサーバにリクエストするたびにこれを送信する。
      • ログアウト時に, session[:id] = nil として消しておく。
        • ログアウト後はログインページにリダイレクトするとよい。
    • ユーザ情報の表示
      • view側で管理する方法(あんまりよくない)
        • app/views/layouts/application.html.erb(各htmlを埋め込む先のファイル) に <% current_user = User.find_by(id: session[:id]) %> を書いて置く
          • この共通htmlで <% if current_user %> でログイン中かで分岐可能
          • 各ページで <% current_user.name %> でカラムにアクセス可能
      • controller で before_action として定義する方法
# 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

# 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") で, @userpassword_digest が文字列 hoge を暗号化したものに一致するかを真偽値として返す。
# app/models/user.rb
class User < ApplicationRecord
  has_secure_password
end