Labyrinth of Wisdom

-This is My Archive-


お気に入り機能サンプル②(完結)

引き続き、Railsでお気に入り機能のサンプルを作成していきます。

前回は各ユーザが持っているお気に入り一覧を表示させるところまで作成しました。

今回はログインしたユーザがお気に入りの本を登録・削除する、という仕上げの部分を行っていきます。

コントローラ

まずコマンドラインからfavoritesコントローラーを作成します。

rails g controller favorites

次にお気に入りを登録するcreate、お気に入りを削除するdestroyアクションを記述します。

セッションに格納したログインユーザのIDと、本一覧画面から選んだ特定の本のIDを初期値として新しいレコードを作ることで、ユーザ別のお気に入りを登録できる様になります。

お気に入りの削除は、ユーザ別のお気に入り一覧(ユーザ詳細画面内)から特定のお気に入りを選びdestroyメソッドを使うことで可能になります。

  • app/controllers/favorites_controller.rb
  #お気に入り登録用アクション
  def create
    @user_id = session[:id] #ログインしたユーザのID
    @book_id = Book.find(params[:id]).id #特定の本のID
    #book_idに@book_id、user_idに@user_idを入れて、Favoriteモデルに新しいオブジェクトを作る
    @favorite = Favorite.new(book_id: @book_id, user_id: @user_id)
    if @favorite.save
      #保存に成功した場合、本一覧画面に戻る
      redirect_to books_path
    end
  end

  #お気に入り削除用アクション
  def destroy
    @favorite = Favorite.find(params[:id])
    if @favorite.destroy
      #削除に成功した場合、ログインしているユーザの詳細画面に戻る
      redirect_to user_path(session[:id])
  end

ルーティング

先ほど作成したfavoritesコントローラと各アクションにルーティングをしていきます。

お気に入りを登録するcreateアクションは、本一覧から特定の本を選んで登録するので、resources booksの入れ子でルーティングをしています。その際アクション名をaddに指定しています。

お気に入りを削除するdestroyアクションは複数リソースのonlyオプションでルーティングしています。

  • config/routes.rb
  root "top#index"
  resource :sessions, only: [:new, :create, :destroy]
  resources :users

  #以下今回追加分
  resources :books do
    member do #本一覧画面からお気に入り登録をする
      post "add", to: "favorites#create"
    end
  end
  #個人ページからお気に入りを削除する
  resources :favorites, only: [:destroy]

f:id:Labyrinth_of_Wisdom:20160304092540p:plain

ビュー

本一覧画面にお気に入り登録のリンクを記述します。

  • app/views/books/index.html.erb
<table>
  <thead>
    <tr>
      <th></th><th>タイトル</th><th>価格</th>
    </tr>
  </thead>
  <tbody>
    <% @books.each do |book| %>
      <tr>
        <!-- お気に入り登録ボタン ルートに従いパスを記述、POSTメソッドに指定 -->
        <td><%= link_to "登録", add_book_path(book), method: :post %></td>
        <!-- 本の詳細へのリンク -->
        <td><%= link_to book.name,book_path(book) %></td>
        <td><%= book.price %></td>
      </tr>
    <% end %>
  </tbody>
</table>

ユーザの詳細画面を修正していきます。 削除リンクのパスfavorite_pathの引数を@favorite[i][:id]にすることで、お気に入りレコードのIDが取得できるので、これでレコードを削除するパスが生成されます。(実は@favorite[i]でもいけます)

  • app/views/users/show.html.erb
<h3>お気に入りの本</h3>
<table>
  <thead>
    <tr>
      <th>削除</th>
      <th>タイトル</th>
      <th>価格</th>
    </tr>
  </thead>
  <tbody>
    <!-- 削除リンクの為にindex付きのメソッドを使用 -->
    <% @user.books.each_with_index do |book, i| %>
      <tr>
        <td><%= link_to "削除",favorite_path(@favorites[i][:id]), method: :delete %></td>
        <td><%= book.name %></td>
        <td><%= book.price %></td>
      </tr>
    <% end %>
  </tbody>
</table>

これでひとまずログインしたユーザが本をお気に入りを登録・削除できる機能が実装できました。

以上。

参考 ルーティングを極める (後編) | TechRacho

お気に入り機能サンプル①

Ruby on Railsで、ログインしたユーザが本一覧の中から気になる本をお気に入りに登録・削除するというサンプルを作っていきたいと思います!

ちょっと長くなりそうなので二回に分けて書いていきたいと思います。

Rails 4.25 ruby 2.23

今回の機能はこちらの記事からの続きになっています。

テーブル設計

中間テーブルを作る事で、テーブル同士は多対多の関係を結ぶことができます。
関係を結んだ後は、お互いのテーブルから他方にアクセスできるようになります。
一人のユーザは複数のお気に入りの本を持ち、一冊の本は複数のユーザにお気に入り登録されています。

1 多 / 多 1
User Favorite(中間テーブル) Book

モデル

  • Favoriteモデル作成

    Favoriteモデルを作成します。コマンドラインで下記コマンドを入力します。
    カラムの設定が複雑なので、後ほどマイグレーションファイルに直接書き込みます。

rails g model favorite
  • app/models/favorites.rb

    belongs_toでusers、booksテーブルとそれぞれ1対多の関係を結びます。

class Favorite < ActiveRecord::Base
    belongs_to :user #User:Favorite => 1:多
    belongs_to :book #Book:Favorite => 1:多
end
  • app/models/users.rb

    has_manyでfavaritesテーブルと1対多の関係を結びます。
    has_manythrough:を使い、favoritesテーブルを通してbooksテーブルと多対多の関係を結びます。
    ここでbooks複数なことに注意しましょう。

class User < ActiveRecord::Base
    has_many :favorites #User:Favorite => 1:多
    has_many :books, through: :favorites
    #クラスメソッド省略
end
  • Bookモデル作成

    Bookモデルを作成します。コマンドラインで下記コマンドを入力します。

rails g model book name:string price:integer
  • app/models/books.rb

    先ほどと同様に、has_manyでfavaritesテーブルと1対多の関係を結びます。
    has_manythrough:を使い、favoritesテーブルを通してusersテーブルと多対多の関係を結びます。
    ここでもusers複数なことに注意しましょう。

class Book < ActiveRecord::Base
    has_many :favorites #User:Favorite => 1:多
    has_many :users, through: :favorites
end

これでひとまずモデル同士の紐づけは完了しました。

マイグレーションファイル/テーブル作成

favoritesテーブルを作っていきます。

  • db/migrate/yyyymmddhhmmss_create_favorites.rb

    カラムの設定をしてなかったので、マイグレーションファイルに直接記述していきます。

class CreateFavorites < ActiveRecord::Migration
  def change
    create_table :favorites do |t|
      t.references :user, null:false #外部キー
      t.references :book, null:false #外部キー
      t.timestamps null: false
    end

    add_index :favorites, :user_id #インデックス
    add_index :favorites, :book_id #インデックス
  end
end

次にマイグレーションファイルを実行してテーブルを作成します。

rake db:migrate

これでfavoritesテーブルができました。テーブル同士の紐づけも完了しました。

シードデータ

  • db/seeds.rb

    シードデータに適当なデータを準備しておきます。

Book.create!(name: "マギ", price: 500)
Book.create!(name: "シンドバッドの冒険", price: 600)
Book.create!(name: "海皇紀", price: 580)

User.create!(name: "アラジン", email: "magi@hoge.com", password: "magimagi")

Favorite.create!(user_id: 1, book_id: 1)
Favorite.create!(user_id: 1, book_id: 3)

テーブルに反映します。

rake db:reset

ルーティング

ユーザへのルーティングを行います。

  root "top#index"
  resource :sessions, only: [:new, :create, :destroy]
  resources :users #追加分

コントローラ

コントローラを作成していきます。

  • app/controllers/users_controller.rb

    今回は特定のユーザの詳細ページにアクセスすると、ユーザの登録したお気に入り一覧が出る所まで実装します。
    ※今回の機能とは直接関係ないので、ユーザ一覧のコード等は省略します。

def show
    @user = User.find(params[:id])
    #特定のユーザーが登録したお気に入りを全て取得する
    @favorites = Favorite.where("user_id = ?", @user)
end

ビュー

  • app/views/user/show.html.erb

    ユーザ詳細ページにユーザが登録したお気に入りの本一覧を表示します。
    @user.booksで特定のユーザが持つお気に入りの本にアクセスできます。

    <h3>お気に入りの本</h3>
    <table>
      <thead>
          <tr>
            <th>削除</th>
            <th>タイトル</th>
            <th>価格</th>
          </tr>
      </thead>
      <tbody>
        <% @user.books.each do |book| %>
          <tr>
            <td><%= book.name %></td>
            <td><%= book.price %></td>
          </tr>
        <% end %>
      </tbody>
    </table>

これで各ユーザ別お気に入りリストが見れるようになりました。 次回はお気に入りの登録・削除を実装していきます。

参考

Rails4で多対多のリレーションをモデルに実装する - Rails Webook

ログイン & ログアウト機能サンプル

WEBサービスで確実にお世話になるログイン&ログアウト機能のサンプルを作ってみたのでメモします。

Rails 4.25 ruby 2.23

データベース

  • usersテーブル

    こんな感じのテーブルを用意してください。値は何でもいいです。
    作り方は下記を参照。

id name email password
1 アラジン magi@hoge.com magimagi

ルーティング

  • /config/routes.rb
#ルートにアクセスするとtopコントローラーのindexアクションを呼び出す。
root "top#index"
#CRUD操作のnew/create/destroyのみ使用する。リソースが単数形なのに注意。
resource :sessions, only: [:new, :create, :destroy]

#個別にルーティングする方法もある。
#get "signin", to: "sessions#new"
#post "signin", to: "sessions#create"
#delete "signout", to: "sessions#destroy"

f:id:Labyrinth_of_Wisdom:20160225160208p:plain 個別ルーティングの場合はこうなる。 f:id:Labyrinth_of_Wisdom:20160225115708p:plain

モデル

  • /app/models/user.rb
#クラスメソッド
class << self
  def check(email, password)
    user = self.find_by(email: email)
    user_pass = self.find_by(password: password)
    #変数userとuser_passのidが一致すれば、変数userを返す
    if user == user_pass
      user
    else
      nil
    end
  end
end

コントローラー

  • /app/controllers/top_controller.rb

    中身は空で大丈夫です。

  • /app/controllers/sessions_controller.rb

#ログイン画面を呼び出す為のアクションなので中身は何もない
def new
end

#セッションを取得/ログイン
def create
  #user.rbに書いたクラスメソッドcheckを使う。フォームからの値をparamsメソッドで受け取る。
  @user = User.check(params[:session][:email], params[:session][:password])

  #@userがtrueなら、@userのnameをハッシュsessionにキー[:name]でセットする。
  if @user
    session[:name] = @user.name
    #メッセージをハッシュflashにキー[:success]でセットする。
    flash[:success] = "ログインに成功しました。"
    #rootのページに遷移する。
    redirect_to root_path
  else
    flash.now[:error] = "メールアドレスとパスワードが一致しません。"
    render "new"
  end
end

#セッションを破棄/ログアウト
def destroy
  #session[:name]に入れた値をdeleteメソッドで削除する。
  session.delete(:name)
  redirect_to new_sessions_path
  #個別ルーティングの場合
  #redirect_to signin_path
end

ビュー

  • /app/views/sessions/new.html.erb
<h1>Sign in</h1>

<!-- ログイン失敗のメッセージを出す -->
<%= flash[:error] %>

<%= form_for(:session, url: sessions_path) do |f|  %>
<!-- 個別ルーティングの場合 -->
<%#= form_for(:session, url: signin_path) do |f|  %>
  <%= f.label :email %>
  <%= f.text_field :email %>

  <%= f.label :password %>
  <%= f.password_field :password %>

  <%= f.submit "Sign in" %>
<% end %>
  • /app/views/top/index.html.erb
<!-- ログアウトリンク -->
<%= link_to "Log Out", :sessions, method: :delete %>
<!-- 個別ルーティングの場合 -->
<%# link_to "Log Out", signout_path, method: :delete %>

<!-- ログイン成功のメッセージを出す -->
<p><%= flash[:success] %></p>

<!-- ログインしたユーザーの名前を表示する -->
<p>Welcom<%= session[:name] %>さん</p>

とても簡易的ですが、ひとまずログインしたのがどのユーザーかを認識できるようになりました。

マイグレーションファイルとは?/モデルの基本

Railsを勉強していると、最初の内はチュートリアル等の言われるがままに作業して中の動きが分からないまま・・・なんてことが多いと思います。筆者も分からないまま作業を進めていることが多くて悩んでいます。。
今回はモデルにおける簡単な内部の動きが少し分かったので、基本的なコマンドと一緒に記載しておきます。

マイグレーションファイルとは

まずモデルを作成する上で、マイグレーションファイルという名称が聞き慣れない言葉で頭に中々入って来ずに苦労しました。

モデルを作る際に同時に作成されるスクリプトファイルなのですが、このマイグレーションファイルを使うことでSQL文を直接書かずにデータべースにテーブルを作成・変更することができます。

つまりデータベースの種類(MySQL,PostgreSQL...)に左右されなくて済みます。(もっと言えばSQLが分からなくてもある程度大丈夫。)

f:id:Labyrinth_of_Wisdom:20160223155351p:plain

また、テーブルの作成・変更をする毎にマイグレーションファイルが作成されるので、そのファイルを使用してマイグレーションファイル実行前の状態にも戻せます。

モデルとテーブル

頻繁に使うと思われるモデルとテーブルの操作方法について記載していきます。

rails g model モデル名 カラム名:データ型

例:
rails g model user name:string age:integer gender:integer
rake db:migrate
rails destroy model モデル名

例:
rails destroy model user
rake db:rollback

シードデータ

開発されたアプリの、データベースにおける初期状態のレコードを扱うファイルのことで、このファイルに記載したものが初期レコードになります。

f:id:Labyrinth_of_Wisdom:20160223144633p:plain

  • シードデータの書き込み

    /db/seeds.rbに初期化用のデータ(シードデータ)を記述します。

例:
User.create!(name: "田中太郎", age: 28, gender: 0)
  • シードデータの投入

    コマンドラインで下記コマンドを入力すると、先ほど記述したシードデータがデータべースに投入されます。

rake db:seed
  • シードデータのリセット

    コマンドラインで下記コマンドを入力すると、ファイルの記述内容でデータベースがリセットされる。
    開発途中でデータベースを消したり増やしたりしても、これを使えば初期状態までデータベースを戻すことができます。

rake db:reset

とりあえず今回はここまで。今後も追記予定です。

以上

参考 ruby-rails.hatenadiary.com

ルーティングの基本

Railsにおいてルーティングは非常に大事かつ、ややこしくて混乱しやすいと思うので、理解できた範囲をまとめていきます。(実際筆者は大分混乱しました)

root画面の変更

アプリケーション作成直後、URLにlocalhost:3000と打つと写真の様にRailsのWelcome画面が出ますが、Welcome画面以外を表示したいときの記述。
これでコントローラーに記述されたアクションに紐付いたViewが呼び出されるようになります。

f:id:Labyrinth_of_Wisdom:20160210093158p:plain

# /config/routes.rb

root "コントローラー名#アクション名"

#例
#topコントローラーのindexアクションに紐づくView、/top/index.html.erb が呼び出される
root "top#index"

リソースベースのルーティング - CRUD操作の追加

1つのコントローラーに対してCRUD操作のアクションを自動的に紐づけてくれる記述。(index/show/new/create/edit/update/destroy が設定されます。便利!)

# /config/routes.rb

resources :コントローラー名
#CRUDの中の一部のアクションのみ使いたい場合は、限定した書き方もできる
resources :コントローラー名, only: [:アクション名, :アクション名]

#例
resources :books
#usersコントローラーのnewとdestroyアクションのみ使える
resources :users, only: [:new, :destroy]

リソースに任意のアクション追加

リソースベースのルーティングで任意のアクションを追加する記述。(searchなど)

# /config/routes.rb

resources :コントローラー名 do
    #collectionメソッドのブロック内のアクションは複数のデータを扱う
    collection {HTTPメソッド名 "アクション名"}
    #コントローラー名(単数)メソッドのブロック内のアクションは単数のデータを扱う
    コントローラー名(単数) {HTTPメソッド名 "アクション名"}
end

#例
resources :books do
    collection {get "search"} #本の検索
    book {patch "suspend","restore"} #本の公開停止・再開
end

URLをカスタマイズする方法

任意の名称でURLをカスタマイズする記述。(localhost:3000/users/newlocalhost:/signupというURLに変更したい時など)

# /config/routes.rb

match "任意のパス", to: "コントローラー名#アクション名", via: "HTTPメソッド名"

#例
resources :users
#localhost:3000/signupにアクセスすると、usersコントローラーのnewアクションをHTTPメソッドのgetで呼び出す
match "/signup", to: "users#new", via: "get"

RailsにBootstrapを適用時モーダルウィンドウがうまく動いてくれなかった件

Rails(4.25)で作成したアプリにBootstrap3を適用して開発を進めていました。
サインイン画面をモーダルウィンドウで実装していたんですが、モーダルウィンドウを呼びだしたら一瞬出てきてすぐ引っ込む、という挙動で大ハマりしたので記録。


View設定

まずサンプルとしてapplication.html.erbの中身から見ていきます。
headタグ内のstylesheet_link_tagjavascript_include_tagに注目。
それぞれがapplication.cssapplication.jsの設定ファイルを読み込む。

/app/viewa/layouts/application.html.erb

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Sampleだよ</title>

    <!-- /app/assets/stylesheets/application.css の読み込み -->
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>

    <!-- /app/assets/javascripts/application.js の読み込み -->
    <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>

    <%= csrf_meta_tags %>

    <!-- IE9未満時用の読み込み -->
    <!--[if lt IE 9]>
        <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
        <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
    <![endif]-->
  </head>
  <body>
    <!-- 省略 -->
  </body>
</html>

CSS設定

下記の様にコードを変更する事で/assets/stylesheets内のbootstrap.min.jsを読み込むことができます。
=の前に*を付ければ設定を有効、消せば無効となります。

app/assets/stylesheets/application.css

/* 追加 */
*= require bootstrap.min
/* 初期設定は有効 => 他のCSSファイルも適用したいなら無効 */
= require_tree .
*= require_self

Javascript設定

同様に初期設定から下記の様に変更します。

=の前に//を付ければ設定を有効、消せば無効となります。

app/assets/javascripts/application.js

//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require bootstrap.min # 追加
= require_tree . //初期設定は有効 => 無効

require_tree .とは一体何か?

先ほどからcssやjsファイルで有効化したり無効化したりしているrequire_tree .とは一体どういう意味を持つのでしょうか?
それはapp/assets/javascripts(stylesheets)/ 配下にある全ファイルを読み込む という意味を持ちます。(勉強していたらSassの項目で出てきました)
これによって不具合が起こっていたみたいです。(内部的な動きはまだ理解できていませんが、おそらく全てのファイルを読み込むことで設定がバッティングしてるんじゃないかと推測されます。)

今回はCSSにBootstrap以外の設定を読み込ませていなかったので、require_tree .を無効化しなくても大丈夫でしたが、別の設定も読み込ませたい場合は無効化する必要が出てくると思われます。

参考サイトを見る限りconfig/application.rbにも記述をしないといけないみたですが、今の所よく分かっていないので今後も調査が必要です。

redirect_to と render の違いについて

Rails-4.25

上記のメソッドの使い方の違いが曖昧だったので調べてみました。
ざっくり結論から言うと、

  • redirect_toはアクションそのものを呼び出してページを遷移する
  • renderはアクションはそのままだけどviewだけ指定ものを使用する(controllerの変数などはそのまま)

という具合です。
例えばindex.html.erbindexアクションでMemberモデルから全件取得された下記のレコードが表示されているとする。

名前 年齢
田中一郎 25
鈴木一郎 22
田中花子 24
<!-- index.html.erb -->

<table>
    <tr>
        <th>名前</th><th>年齢</th>
    </tr>
    <% @members.each do |member| %>
    <tr>
        <td><%= member.name %></td><td><%= member.age %></td>
    </tr>
    <% end %>
</table>

ここから田中という名前の人のみを検索したい、となった時にsearchというアクションを呼び出し、search.html.erbに遷移し検索結果を表示する。

# members_controller.rb
# 検索フォームが空なら全件取得、そうでないなら名前をあいまい検索して search.html.erb に遷移

def search
    if params[:name] == ""
        @members = Member.all
    else
        @members = Member.where("name like ?", "%"+params[:name]+"%")
    end
end
名前 年齢
田中一郎 25
田中花子 24
<!-- search.html.erb -->

<table>
    <tr>
        <th>名前</th><th>年齢</th>
    </tr>
    <% @members.each do |member| %>
    <tr>
        <td><%= member.name %></td><td><%= member.age %></td>
    </tr>
    <% end %>
</table>

ただこの方法だと、ほとんどviewの表示形式が同じなのにも関わらず、search.html.erbというファイルを作成しなければなりません。
サイトの形式にもよりますが、index.html.erbのファイルをそのまま使えるとしたら、先ほどのコードは下記のように修正できます。

# 検索フォームが空ならindexアクションを呼び出し、index.html.erbに遷移
# members_pathはビューヘルパー表記 今回の場合は /members もしくは /members/index を表す
# 検索フォームが空でないなら名前をあいまい検索し、searchアクションを呼び出し、index.html.erb に遷移

def search
    if params[:name] == ""
        redirect_to members_path
    else
        @members = Member.where("name like ?", "%"+params[:name]+"%")
        render action: "index"
    end
end

こうすると余計なファイル(今回でいうとsearch.html.erb)を作成しなくても検索結果が表示可能に ります。

以上

参考

[Rails] renderとredirect_toの違い - 拝啓、シーシュポス Railsのリダイレクト(redirect_to)でよく使う5つの方法 | NESTonline Blog