【完全版?】RailsとVue.jsを用いて動的なページを部分SPA化(画像もjson形式でやり取り)

https://qiita.com/divclass123/items/09496753be14b892e4f0
https://qiita.com/divclass123/items/a3b632d32ace31cc44ea
https://qiita.com/divclass123/items/aba1cfb21177c3471eff
https://qiita.com/divclass123/items/2801a160be312ea4ce6e

これらの記事のまとめです。
これらの記事で何をやっていたかというと、Railsのほうにあるデータをjson形式でVue.jsのほうに受け渡して、Vue.jsでビューを書いていく。ということをやろうとしてました。

このようなRailsとVue.jsの組み合わせの方法は、以下の教材をやればある程度事足りるのですが、
実際に今自分が作成してるポートフォリオに落とし込んだときに大きく２つのポイントにつまずいたので、
そこを重点的に解説していきたいと思います。
[参考にさせていただいた教材](https://www.techpit.jp/courses/123)

そのうち一つのポイントを先にネタバレすると、railsの画像データをjson形式でVue.jsの方に受け渡すことに苦労しました。
ググって色々な記事が出てきましたが、初学者の自分には難しく、自作アプリに応用できる気がしませんでした。
そこで色々試行錯誤して簡単にできたので、それを解説していきたいと思います。

# 前提

よくある投稿テーブルにActive Strageでimageテーブルを紐付けてるようなアプリケーションを自分は作成しています。

drinksというものが度々書いてありあますが、これは投稿に関するものだと思ってください。
postとかtweetとかに置き換えて読んでいただければと思います。

rails new したときに最初からオプションにAPIモードを指定していません。
あとからVueを取り込んでも部分SPA化できたので、ご安心ください。

初学者故、色々間違ってるとこもあると思いますが、ご了承いただければ幸いです。

また、ほんの一部、[参考にさせていただいた教材](https://www.techpit.jp/courses/123)
の文言を引用させて頂いてます。
万が一問題があれば即座に修正させていただくので、お手数ではございますが、編集リクエストを頂ければ幸いです。

(最近、某高額Web制作教材Z○NEが著作権違反疑惑で炎上したから怖いめう。。。。)

引用部分は
>このようにハイライトをしています。

# いざ実装


**config/routes.rb**

```rb
  namespace :api, format: 'json' do
    resources :drinks, only: [:show]
  end
```

namespace を使ってる理由は、URLやファイル構成を指定のパスにしたいらしい。
指定のパスにならない場合ってどんなときやろ。。。
指定のパスにすることで今後の実装が容易になるとか。

  namespace :api　と記載することで、コントローラーを作成する際は
apiディレクトリに作成します。

format: 'json'でコードjson形式に指定できる。
多分やりとりするデータがjson形式だとSPAができるイメージ。




```
$ rails g controller api/drinks

```

を実行。

```app/controller/api/drinks_controller.rb

class Api::DrinksController < ApplicationController
  def show
    @drink = Drink.find(params[:id])
  end
end

```


>json形式でデータのやりとりをします。
そんなときには、このjson作成を簡単にしてくれる、jbuilderというものがRailsには備わっているので、それを使ってjsonを返したいと思います。

```
touch app/views/api/drinks/show.json.jbuilder

```

を実行します。

```app/views/api/drinks/show.json.jbuilder

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

json.image rails_blob_url(@drink.image) if @drink.image.attached?
```

このように書くことにより、json形式でデータをVue.jsの方に送信することができます。

drinks#showの部分、つまり、投稿の詳細ページを受け渡していこうと思うので、

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

といった、単一データを扱っています。

:name,:priceは投稿モデルの属性というか、カラムです。


また投稿に紐付いてる画像のデータをjson形式でVueに受け渡す、送信するために、
json.image rails_blob_url(@drink.image) if @drink.image.attached?
このように書いています。

rails_blob_urlとは

引数に渡されたActive Recordオブジェクト(ここでいう@drink.image)のURLを生成するメソッドです。

.attached?は、Active Recordオブジェクトがあるかどうか確かめるメソッドです。
画像があるかどうかバリデーションをかけているので、基本的には画像がないということはありえないとおもいますが、一応書いておきましょう。


このように書いたら、

localhost:3000/api/drinks/10

にアクセスしてみると、(dirnks/10は皆さんのアプリによって変わってきます。とにかく投稿の詳細ページにアクセスできるURLに/api/を上記のように追加すれば問題ないです。)

{"name":"ddd","price":3000,"explain":"dddddd","region_id":1,"body_id":1,"acidity_id":1,"processing_id":1,"likes_count":null,"user_id":7,"image":"http://localhost:3000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBGQT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--a5cbc36b9182e1ffa204bbf8cf5678f1790eec3f/anastasiia-chepinska-lcfH0p6emhw-unsplash.jpg"}

このように表示されます。

drink(投稿)に関するデータがAPIとしてjson形式で取得できています。

```app/views/drinks/show.html.erb

<%= javascript_pack_tag 'hello_vue' %>
```

このように書きます。ここにvueファイルが表示されます。

jsを介してvueファイルを表示するらしいので、このような書き方らしい。
app.vueファイルが実際には表示されます。すこしややこしいですね。。。。

```app.vue

<template>

<div id="id">
  {{drink}}
  <img v-bind:src="drink.image">
</div>

</template>

<script>
import axios from 'axios';


export default {
  data: function (){
    return{
      drink: "drink"
    }
  },
  mounted () {
    this.setDrink();
  },
  methods: {
    setDrink: function(){
      axios.get('/api' + location.pathname)
        .then(response => (
          this.drink = response.data
        ))
    }
  }
}
</script>

```

このように記述します。

```

yarn add axios
```
を実行して、
非同期通信を実装するためのモジュールのaxiosをインストールします。

>axiosをインストールできたかは、ルートディレクトリにあるpackage.jsonファイルを確認します。




```
import axios from 'axios';
```

非同期通信を実装するためのモジュールのaxiosをインポートしています。

```js

  methods: {
    setDrink: function(){
      axios.get('/api' + location.pathname)
        .then(response => (
          this.drink = response.data
        ))
    }
  }
```

上記のコードではsetDrinkという関数を定義しています。
どのような関数かというと

```js
axion.get('/api/' + location.pathname)

```

でapi　を呼び出しています。

route.rbの方で

```routes.rb

  namespace :api, format: 'json' do
    resources :drinks, only: [:show]
  end
```

このように記述していたとおもいます。
なのでこのように記述することで、APIを呼び出せるわけです。

location.pathnameはjsのメソッドで、

console.log(location)とすると

Location
  ancestorOrigins: DOMStringList {length: 0}
  assign: ƒ assign()
  hash: ""
  host: "localhost:3000"
  hostname: "localhost"
  href: "http://localhost:3000/drinks/10"
  origin: "http://localhost:3000"
  pathname: "/drinks/10"
  port: "3000"
  protocol: "http:"
  reload: ƒ reload()
  replace: ƒ replace()
  search: ""
  toString: ƒ toString()
  valueOf: ƒ valueOf()
  Symbol(Symbol.toPrimitive): undefined


いろんなURLの情報が詰まってるオブジェクトを取得というかアクセスできます。

```js
location.pathname
```

とすることにより、じぶんはdrinks/9が取得できました。
**つまりどんな投稿の詳細ページにアクセスしようがlocation.pathnameで現在いるURLのパスを取得できるので、
axios.getが上手く動作するんです。。。！！**
(うまいこと伝わりますかね。。。。。)
これを思いついたとき自分は天才かと思いました。正直。。。

```js
      axios.get('/api' + location.pathname)
```

の.getは、HTTP動詞のgetです。
ここらへんの解説はめんどい。json形式のデータを取得したいだけなのでgetです。

```js

        .then(response => (
          this.drink = response.data
        ))
```


.then()で通信が成功したときの処理をかきます。
response.dataは、
さっき、localhost:3000/api/drinks/9にアクセスしたときのデータがつまってます。
それをdrinksに代入します。
同一オブジェクト内のデータを同一オブジェクト内のメソッドに記述する場合はthis.を書かないといけないです。

オブジェクトのデータのdrinkをまだ定義してないので、定義します。

```js

  data: function (){
    return{
      drink: "drink"
    }
  },
```

dataにはそのファイルで保持しておきたいデータを書きます。


最後に、関数を定義したはいいものの、関数を呼び出すコードがないのでそれも書いときましょう。

```js
  mounted () {
    this.setDrink();
  },
```

mounted()はこのファイルが読み込まれた時点、vueオブジェクトが最初に作成された時点で
指定されたメソッドを呼び込みます。。。。多分。vueのライフサイクルの理解があやふやで正直この説明に自信がないです。。
とにかく、一番最初に実行されるくらいの認識でいます。

```app.vue

<template>

<div id="id">
  {{drink}} 
  {{drink.price}}
  {{drinl.name}}
  <img v-bind:src="drink.image">
</div>

</template>
```

で、

localhost:3000/drinks/9(投稿の詳細ページ)にアクセスしたら

画像や、投稿のjsonデータ。(コーヒーに関する)投稿の名前、値段が表示されているとおもいます。

アクセスするurlは
localhost:3000/api/drinks/9ではなく
localhost:3000/drinks/9(投稿の詳細ページ)
です。



# 処理の流れを簡単におさらい

https://qiita.com/x5dwimpejx/items/c459450d3294d8c437aa
この方の記事がわかりやすいです。

投稿の詳細ページにアクセス
例えば、localhost:3000/drinks/9
↓
route.rbに記述してある
  resources :drinks, only: [:index,:new,:show,:create,:destroy] do
end

 のパスが読み込まれる。

↓

drinks#show起動する

↓

drinks/show.html.erb
が表示される

ここまでは一般的なrailsの処理の流れですね。
ここからが大事

↓

drinks/show.html.erbに記述されている
<%= javascript_pack_tag 'hello_vue' %>
が読み込まれる

↓

hello_vue.jsの
主に render: h => h(App)
この部分の記述により、app.vueファイルが表示される

↓

app.vue
が読み込まれた瞬間、
setDrink関数が呼び出される。

↓

axios.get('/api' + location.pathname)

で、

route.rbに書かれてる

```
  namespace :api, format: 'json' do
    resources :drinks, only: [:show]
  end
```

の処理が走り、

api/drinks_controller.rb

のshowメソッドが呼び出される


↓

app/views/api/drinks/show.json.jbuilder
　が読み込まれ
json形式のデータをレスポンス、Vueに返す

↓

```js

        .then(response => (
          this.drink = response.data
        ))
```

で、通信に成功した場合、取得したjsonデータをdrinkに格納

↓

```vue

<template>

<div id="id">
  {{drink}}
  {{drink.name}}
  {{drink.price}}
  <img v-bind:src="drink.image">
</div>

</template>
```

取得データを表示


まあざっくりとした説明でしたが、以上のような感じで
画像も含めた動的なページをSPAライクに表示することができました。

わかりにくい箇所があれば質問も受け付けておりますので、ぜひ！！！

いやー、とにかくjson形式で動的なデータを取得できてテンション上がりました！！！