1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

#5 Rails × Vue.jsで動的なページをSPA化させる

Last updated at Posted at 2021-08-13

前回の続き
恐らく最大の難関であったであろう、json形式で画像を受け渡しました。
RailsとVue.jsを組み合わせてSPAを実装した記事はこちらです。

今回ははVue.jsのほう。フロント、Viewの部分の実装をしていきたいと思います。

[追記&質問]

railsでactive hashというgemでカテゴリーを実装しました。
ですが、gemでカテゴリーを実装したので、当然のことながらVueのほうで上手くカテゴリーが表示されません。。。

avtive hasuはどのようにカテゴリーを実装するかと言うと、例えば自分の場合
drinksといった、コーヒーの感想を投稿するようなモデルがあります。
そこに、コーヒーの産地といったカテゴリを実装したい。となった時に
active hashを導入して、
drinksテーブルにregion_idカラムを追加して

region.rbに

class Region < ActiveHash::Base
  self.data = [
    { id: 1, name: '---' },
    { id: 2, name: 'マルチリージョン' },
    { id: 3, name: 'ラテンアメリカ' },
    { id: 4, name: 'アフリカ' },
    { id: 5, name: 'アジア、太平洋' }
  ]
end

と記述します。
drink.rbとregion.rbでアソシエーションを組む。(アソシエーションの組み方はactive hash特有の既述がある)

そうすると、dirnksテーブルのregion_idが2だったら
dirnk.region.nameと書けば、マルチリージョンが表示されるといった具合です。

今回、

json.(@drink, :name , :price , :explain ,
:region_id , :body_id ,
:acidity_id , :processing_id ,
:likes_count , :user_id)

このような感じで、region_idをjson形式で、vueに受け渡す感じなので、
app.vueで{{drink.region.name}}と書いても当然上手くいきません。

ただ、region_idは渡されるので、それをどう利用して、カテゴリーを実装するかが今回の課題です。
結論自分は、


                  <td v-if="drink.region_id === 1" class="region-name">--- </td>
                  <td v-if="drink.region_id === 2" class="region-name">マルチリージョン </td>
                  <td v-if="drink.region_id === 3" class="region-name">ラテンアメリカ </td>
                  <td v-if="drink.region_id === 4" class="region-name">アフリカ</td>
                  <td v-if="drink.region_id === 5" class="region-name">アジア、太平洋 </td>   

このようなクソコードで実装できましたが、多分酷いコードなのでいい方法があれば教えていただければ幸いです。

また、そのような条件分岐のような処理はサーバーサイドの方で書いた方がいいと思ったので、
サーバーサイドでの実装を以下に既述しております。
お手数ですがご確認いただければ幸いです。

実装

今回はdrinks/show.html.erbの


<% provide(:title,"Details")%>
<div class='show-main'>

  <%# 商品一覧 %>
  <div class='item-contents'>
    <h2 class='title'></h2>
    <ul class='item-lists'>
      <%# 商品のインスタンス変数になにか入っている場合、中身のすべてを展開できるようにしましょう %>
      <%if @drink%>
      <li class='list'>
        <%= link_to drink_path(@drink.id) do %>
        <div class='item-img-content'>
          <%= image_tag @drink.image , class: "item-img" if @drink.image.attached? %>
          
          <%# if drink.trade%>
          
   
          
          <%# end %>
        </div>
        <div class='item-info'>
          <h3 class='item-name'>
            <%= @drink.name %>
          </h3>
          <div class='item-price'>
            <span><%= @drink.price %><br>(税込み)</span>
          </div>
          <div class='item-explain'>
             <%= render_with_hashtags(@drink.explain) %>
          </div>

          <%# ここから%>
          <table class="item-category">
            <tbody>
              <tr class="item-region">
                <% if @drink.region %>
                  <th class="region-title">産地 </th>
                   <td class="region-name"><%= @drink.region.name%> </td>             
                <% end %>
              </tr>
                <tr class="item-body">
                  <% if @drink.body%>
                  <th class="body-title">コク</th> 
                  <td class="body-name"><%= @drink.body.name %></td>
                  <% end %>
                </tr>
                <tr class="item-acidity">
                  <% if @drink.acidity %>
                    <th class="acidity-title">酸味</th> 
                    <td class="acidity-name"><%= @drink.acidity.name%></td>
                  <% end %>
                </tr>
                <tr class="item-processing">
                  <% if @drink.processing%>
                    <th class="processing-title">加工法</th>
                     <td class="processing-name"><%= @drink.processing.name%></td>
                  <% end %>
                </tr>
              
            </tbody>
          </table>
           <%= render "likes/like",drink: @drink%>
        </div>
        <% if logged_in? && current_user.id == @drink.user_id %>
        <div class="item-delete">
          <%= link_to "削除する",drink_path(@drink),method: :delete %>
        </div>

        <% end %>
            <%if @drink.user.id == 6%>
            <%= link_to "商品を購入する",drink_trades_path(@drink.id),class: "buy-link"%>
            <%end%>
      </li>
      <%end%>
    </ul>

    <div class="comment-container">
      <div id="comments-create">
        <%= render partial: 'comments/create', locals: {comment: @comment, drink: @drink} %>
      </div>
    </div>

    <div id="comments-index">
       <%= render partial: 'comments/index', locals: {comments: @comments,drink: @drink} %>
    </div>  

  </div>
  
  <%end%>

  </div>
</div>

この部分をvueに置き換えて実装していきたいと思います。

drink部分はjsonのデータを取得できているので、そのまま置き換えても問題なさそうなコードは
一旦置き換えて、問題がありそうなコード、例えばlink_toとかは、いったん飛ばしていきたいと思います。

<template>

<div class='show-main'>

  <div class='item-contents'>
    <h2 class='title'></h2>
    <ul class='item-lists'>

      <li class='list'>

        <div class='item-img-content'>
          <img class= "item-img" v-bind:src="drink.image" >

        </div>
        <div class='item-info'>
          <h3 class='item-name'>
            {{drink.name}} 
          </h3>
          <div class='item-price'>
            <span>{{drink.price}}<br>(税込み)</span>
          </div>
          <div class='item-explain'>
             {{drink.explain}}
          </div>


          <table class="item-category">
            <tbody>
              <tr class="item-region">
                  <th class="region-title">産地 </th>
                   <td v-if="drink.region" class="region-name">{{drink.region.name}} </td>             
              </tr>
                <tr class="item-body">
                  <th class="body-title">コク</th> 
                  <td v-if="drink.body" class="body-name">{{drink.body.name}}</td>
                </tr>
                <tr class="item-acidity">
                    <th class="acidity-title">酸味</th> 
                    <td v-if="drink.acidity" class="acidity-name">{{drink.acidity.name}}</td>
                </tr>
                <tr class="item-processing">
                    <th class="processing-title">加工法</th>
                     <td v-if="drink.processing" class="processing-name">{{drink.processing}}</td>
                </tr>
              
            </tbody>
          </table>

        </div>
        <div class="item-delete">
        </div>

      </li>

    </ul>

    <div class="comment-container">
      <div id="comments-create">
        <!-- <%= render partial: 'comments/create', locals: {comment: @comment, drink: @drink} %> -->
      </div>
    </div>

    <div id="comments-index">
       <!-- <%= render partial: 'comments/index', locals: {comments: @comments,drink: @drink} %> -->
    </div>  

  </div>
  
</div>

</template>

こんな感じで置き換えることができました。

単純に置き換えたが問題点発生、カテゴリーの表示が上手くいかない

ただ問題点は


            <tbody>
              <tr class="item-region">
                  <th class="region-title">産地 </th>
                   <td v-if="drink.region" class="region-name">{{drink.region.name}} </td>             
              </tr>
                <tr class="item-body">
                  <th class="body-title">コク</th> 
                  <td v-if="drink.body" class="body-name">{{drink.body.name}}</td>
                </tr>
                <tr class="item-acidity">
                    <th class="acidity-title">酸味</th> 
                    <td v-if="drink.acidity" class="acidity-name">{{drink.acidity.name}}</td>
                </tr>
                <tr class="item-processing">
                    <th class="processing-title">加工法</th>
                     <td v-if="drink.processing" class="processing-name">{{drink.processing}}</td>
                </tr>
              
            </tbody>

ここらへんのdrink.region.nameとかの
drinkテーブルに紐付けた情報が表示されていないのが問題ですね。

drinkにはregion_idといったものは取得できてます。
例えば、drinksテーブルのregion_idが2だったら「ラテンアメリカ」とか
drinkテーブルのbody_idが3だったら「コク強め」とか
ここらへんは、Active hashとかいうgemでカテゴリーを実装しました。

ちなみにavtive hashのほうで作成したモデルはこんな感じ

models/region.rb

class Region < ActiveHash::Base
  self.data = [
    { id: 1, name: '---' },
    { id: 2, name: 'マルチリージョン' },
    { id: 3, name: 'ラテンアメリカ' },
    { id: 4, name: 'アフリカ' },
    { id: 5, name: 'アジア、太平洋' }
  ]
end

のおかげで、drink.region.nameとerbに書いていても
マルチリージョンと表示されていましたが、
もちろんrailsのgemのおかげでそのように表示されていたので、
vueにdrink.region.nameと書いても上手くいきません。

なのでvueのほうにdirnkのregion_idが1だったら「ラテンアメリカ」
を表示するとかいう関数を書きたいと思います。

それがだめなら、
controllers/api/drinks_controller.rbで

class Api::DrinksController < ApplicationController
  def show
    @drink = Drink.find(params[:id])
    if @drink.region_id = 3
       @drink.region_name == "ラテンアメリカ"
    end  
  end
end

json.(@drink, :name , :price , :explain ,
:region_id , :body_id ,
:acidity_id , :processing_id ,
:likes_count , :user_id, :region_name)

とかで受け渡せたらいいなと思う

まずはvueにカテゴリ機能を実装


    setRegion: function(){
      switch(this.drink.region_id){
        case 1:
          this.drink.region.name = "---";
        case 2:
          this.drink.region.name = "マルチリージョン";
        case 3: 
          this.drink.region.name = "ラテンアメリカ";
                    console.log(this.drink.region.name)
        case 4:
          this.drink.region.name = "アフリカ";
        case 5:
          this.drink.region.name = "アジア、太平洋"
      }
    }

明らかに冗長な気もするが、とりあえずこんな感じで書いてみた、

{{drink.region.name}}

そうすれば、マルチリージョンとか表示してくれるだろう。と思ったが

vue.runtime.esm.js:638 [Vue warn]: Error in render: "TypeError: Cannot read property 'name' of undefined"

found in

---> at app/javascript/app.vue

warn @ vue.runtime.esm.js:638
logError @ vue.runtime.esm.js:1921
globalHandleError @ vue.runtime.esm.js:1916
handleError @ vue.runtime.esm.js:1876
Vue._render @ vue.runtime.esm.js:3530
updateComponent @ vue.runtime.esm.js:4056
get @ vue.runtime.esm.js:4470
run @ vue.runtime.esm.js:4556
flushSchedulerQueue @ vue.runtime.esm.js:4304
(anonymous) @ vue.runtime.esm.js:2023
flushCallbacks @ vue.runtime.esm.js:1945
vue.runtime.esm.js:1927 TypeError: Cannot read property 'name' of undefined
at Proxy.render (app.vue?e7aa:46)
at VueComponent.Vue._render (vue.runtime.esm.js:3528)
at VueComponent.updateComponent (vue.runtime.esm.js:4056)
at Watcher.get (vue.runtime.esm.js:4470)
at Watcher.run (vue.runtime.esm.js:4556)
at flushSchedulerQueue (vue.runtime.esm.js:4304)
at Array. (vue.runtime.esm.js:2023)
at flushCallbacks (vue.runtime.esm.js:1945)

というエラー。
vueのエラーはどこの行でおきてるかわかりにくいってかわからない。

Error in render: "TypeError: Cannot read property 'name' of undefined"
書いてある。

まぁ確かにnameプロパティなんてない。

「vue プロパティ 定義」とかでググろう

てかv-ifとかでできるような気がしないでもない。

いや、力技だけど、


              <tr class="item-region">
                  <th class="region-title">産地 </th>
                  <td v-if="drink.region_id === 1" class="region-name">--- </td>
                  <td v-if="drink.region_id === 2" class="region-name">マルチリージョン </td>
                  <td v-if="drink.region_id === 3" class="region-name">ラテンアメリカ </td>
                  <td v-if="drink.region_id === 4" class="region-name">アフリカ</td>
                  <td v-if="drink.region_id === 5" class="region-name">アジア、太平洋 </td>           
              </tr>

こんな感じで書いたら上手くいきました。。。。。(そりゃそうだ)
クソコードにも程がある。。。

できれば、処理はtemplate内ではなく、scriptの方に書きたい。。

理想はdrink.region.nameを定義して、
drink.region_idに応じて、{{drink.region.name}}
とかでコーヒーの産地情報を表示できるのが一番な気がします。
とにかく処理はscriptの方に書きたい。

二つ目の方法、railsでカテゴリーの処理を書く

api::controllerの方でregion_nameとかを定義して、
region_idの値によって処理して、
json形式でvueに受け渡して、vueファイルで
{{drink.region_name}}とかで表示させたい。
(スネークケースでいいのか問題はあるが、、、)

とりあえず実装。

app/controller/api/drinks_controller.rb

class Api::DrinksController < ApplicationController
  def show
    attr_accessor :region_name
    @drink = Drink.find(params[:id])
    
    case @drink.region_id
    when 1
      @drink.region_name = "---"
    when 2
      @drink.region_name = "マルチリージョン"
    when 3
      @drink.region_name = "ラテンアメリカ"
    when 4
      @drink.region_name = "アフリカ"
    when 5
      @drink.region_name = "アジア、太平洋"
    end 
      

  end
end

条件分岐させるといった、根本の考え方はさっきと一緒で、これも酷いコードかもしれないけど
処理をサーバーサイドの方に書いただけさっきよりマシな気がする。

show.json.jbuilder

json.(@drink, :name , :price , :explain ,
                     :region_id , :body_id , 
                     :acidity_id , :processing_id ,
                     :likes_count , :user_id,:region_name)

:region_nameをjsonのほうに追加

app.vue

のほうに

                  <th class="region-title">産地 </th>
                  {{drink.region_name}}

このように追加。

果たして、ちゃんと産地が動的に表示されるのか

コントローラーにattr_accessor は定義されていませんと怒られた。
ので、drink.rbに

attr_accessor :region_name

と書くことに

したらいけた。
多分綺麗に書けてはいないが、何も見ずに考えて機能を実装できたのは嬉しい。。。

ただ、scriptの方にいい感じの関数をかけたら、
もっと見やすいに違いない。
だって、drink.region_nameのregion_nameがどの様に定義されてるか同一ファイルで確認できるし。
region_nameの定義場所がrailsの方だから、決してベストプラクティスではない。。。

今のところこの二つしかやり方が思い浮かばないからベストな方法をruby-jpさんの皆様に聞くしかない

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?