【rails】複数のjsonデータを返す方法

発生経緯

railsアプリを作る時、Vue.jsを使うと基本的にrailsのデータはAPIで返すことになる。 ということは最後の出力をjson形式で返すことになるわけだが、ここでかなりつまづいたので忘れないようにメモしておく。

単体jsonデータの場合

class QuestionsController < ApplicationController
  def index
    @question = Question.new
    @questions = Question.all.order(created_at: "DESC")
    respond_to do |format|
      format.html
      format.json
    end
  end
end

QuestionテーブルはUserテーブルと紐づいており、Questions(多):User(1)の関係になっている。この時 index.json.jbuilder を使うとこんな感じになる。

json.array! @questions, :title, :content, :user

一応これで@questionsに関連したUserもjson形式で送ることはできた。が、これだけのためにjbuilderを使うのはもったいない。

そこで、Controllerの方でまとめてみる。

class QuestionsController < ApplicationController
  def index
    @question = Question.new
    questions = Question.all.order(created_at: "DESC")
    respond_to do |format|
      format.html
      format.json { render json: questions, include: [:user] }
    end
  end
end

includeを使うことで、関連したテーブルのデータも送ることができるようになる。jsonの中身を確認すべくhtml端子を .json にしてみる。

[{"id":1,"title":"hoge","content":"huga","user_id":1,"created_at":"2019-10-14T10:33:14.247+09:00","updated_at":"2019-10-14T10:33:14.247+09:00","user":{"id":1,"email":"hoge@gmail.com","created_at":"2019-10-13T16:58:27.516+09:00","updated_at":"2019-10-13T16:58:27.516+09:00","name":"foo"}}]

ちょっと見辛いが、questionのuser_idに紐づいたUserのデータが一緒に入っているのがわかる。

複数のjsonデータの場合

では、showメソッドでquestionに紐づいたanswerも一緒に送るにはどうすれば良いか?

class QuestionsController < ApplicationController
  def show
    @question = Question.find(params[:id])
    @answers = Answer.where(question_id: params[:id]).order(created_at: "DESC")
    @answer = Answer.new
    respond_to do |format|
      format.html
      format.json
    end
  end
end

ここで得たいのはquestionのデータとanswersのデータ。 ひとまず show.json.jbuilder に書いてみる。

json.question @question, :title, :content, :user
json.answers do
  json.array! @answers, :text, :user
end

複数の時も上のようにcontrollerにまとめるには .to_jsonを使うとできなくはないが、Vueで受け取ったデータに \ が死ぬほど入ってきた…

まとめ

単体jsonデータはcontrollerにまとめることができたけど、複数のデータのまとめかたがいまいちわからなかった…ここは忘れずに改善する必要がある。

【heroku】railsをデプロイした時に出たエラー

発生経緯

Vueの勉強がてらローカルで作っていたrailsアプリをherokuにデプロイしようとコマンドを叩いた。

$ git push heroku origin

すると見たことのないエラーが発生

remote: -----> Detecting rake tasks
remote: 
remote:  !
remote:  !     Could not detect rake tasks
remote:  !     ensure you can run `$ bundle exec rake -P` against your app
remote:  !     and using the production group of your Gemfile.
remote:  !     Activating bundler (2.0.1) failed:
remote:  !     Could not find 'bundler' (2.0.1) required by your /tmp/build_6b1f695b994476ef74ee9e7d5cc6b6f0/Gemfile.lock.
remote:  !     To update to the latest version installed on your system, run `bundle update --bundler`.
remote:  !     To install the missing version, run `gem install bundler:2.0.1`
remote:  !     Checked in 'GEM_PATH=vendor/bundle/ruby/2.5.0', execute `gem env` for more information
remote:  !     

出てくることは少ないかもしれないけど、忘れないようにメモ。

とりあえずエラーの中身確認

どうもbundlerをインストールせいと言っているので、先ずはバージョン確認。

$ bundler -v
Bundler version 2.0.1

あれ?あってる… ってことはrailsのバージョンとbundlerのバージョンがheroku推奨とアンマッチの可能性がある。

解決手法

というわけで、railsとbundler両方ともアップデートをかけることにする。

$ brew update && brew upgrade ruby-build
$ rbenv install 2.6.3
# railsは他でも使っているので、ひとまず適用させたいフォルダのみバージョン変更
$ rbenv local 2.6.3
# bundlerも最新のやつを入れておく
$ gem install bundler -v 2.0.2
# このまま入れるとgemfileが書き変わらないので、一旦削除
$ rm Gemfile.lock
$ bundle install

ここまで行うと .ruby-version が新たに作られるので、忘れずに .gitignore に追加しておく。 gitにpushし終えたら再びデプロイトライ。

$ git push heroku origin
remote: Verifying deploy... done.
 * [new branch]      master -> master

無事デプロイ完了!

デプロイまでしたら

DBを作るのを忘れがちなので、気をつける!

$ heroku run rails db:migrate

これで、無事にサイトが表示できるようになった。

【rails】PostgreSQLを使った環境構築

PostgreSQL Accountの作成

仕事でPostgreSQLを使っていたので、Accountを作るところから。

$ sudo -u postgres psql
PASSWORD= <PCのパスワード>
postgres=# create role <username> with createdb login password '<password>';
  # usernameとpasswordはあとで使う
postgres=# \q

$ rails new <appname> -d postgresql # オプションでpostgresqlを指定
$ cd <appname>

これでPostgreSQLを使ったRailsができた。

dotenv-railsの追加

PostgreSQLのdatabse.ymlを編集する前にgemを追加しておく。

gem 'dotenv-rails'

追加したら bundle install でgemを適用させる。

$ bundle install
$ touch .env.local  #今回はローカル作業用にlocalファイルを作成

今回は .env.local を作ったが、これをコピーしたければ cp .env.local .env.<好きな名前> で作れる。

config/database.ymlの編集

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  # この下に3行追加していく
  username: <%= ENV['USER'] %>
  password: <%= ENV['PASSWORD'] %>
  host: <%= ENV.fetch('HOST', 'localhost') %>

続いて先ほど作った .env.local に必要な情報を追加していく。

USER=<username>
PASSWORD=<password>
HOST=localhost  # 別環境で使うことがあればそれを指定

.gitignoreに追加

.env.local を作って環境変数を指定してあげているので、GitHub上にファイルを上げないように設定してあげる。

# dotenv
.env*

これで、.env から始まるファイルはGitHub上に上がらない。

リモートリポジトリの作成

ここまで出来たらGitHubにデータを転送していく。

$ git init  # GitHubと接続
$ git add -A  # 一回ファイルをコミットできる状態にしておく
$ git rm --cached .env.local  #さっき作った.env.localは上げないように削除
$ git commit -m"first commit"  # コメントはなんでも良い

GitHubでリポジトリを作ったら、SSHのURLをコピーしておく

$ git remote add origin <コピーしたURL>
$ git push -u origin master

これで、localで使ったPostgreSQLのusernameとpasswordをGitHub上に公開しないで住むようになった。

【Vue.js】クラスのデータバインディング

クラスのデータバインディング

クラスのデータバインディングの基本の書き方はこうなる。
例えば文字を大きくする場合
JavaScript

var vm = new Vue({
  el: '#vm',
  data: {
    isLarge: true
  }
})

HTML

<div id="vm">
   <p>
     Hello <span v-bind:class="{large: isLarge">Vue.js!</span>
   </p>
</div>

CSS

.large {
  font-size: 36px;
}

isLargeがtrueの時はVue.js!は大文字になる。
また、v-bind:classは複数適用が可能でカンマで繋げれば良い。
JavaScript

var vm = new Vue({
	el: '#vm',
  data: {
  	isLarge: true,
    hasError: true
  }
})

HTML

<div id="vm">
   <p>
     Hello <span v-bind:class="{large: isLarge, 'text-danger': hasError}">Vue.js!</span>
   </p>
</div>

CSS

.large {
  font-size: 36px;
}
.text-danger {
  color: red;
}

配列構文での記述

cssの内容をdataの中に記述して、配列構文でv-bindを書くことができる。
JavaScript

var vm = new Vue({
  el: '#vm',
  data: {
    largeClass: 'large',  // cssのクラス名
    dangerClass: 'text-danger' 
  }
})

HTML

<div id="vm">
   Hello <span v-bind:class="[largeClass, dangerClass]">Vue.js!</span>
</div>

クラスの数が増えるとわかりづらくなるので、dataオプションにclassObjectのプロパティを作ってまとめて管理する。
JavaScript

var vm = new Vue({
  el: '#vm',
  data: {
    classObject: {
      large: true,  // クラス名
      'text-danger': true
    }
  }
})

HTML

<div id="vm">
  Hello <span v-bind:class="classObject">Vue.js!</span>
</div>

随分とみやすくなった。

三項演算子を使ってみる

三項演算子を使ってクラスの条件を分けてみる。
JavaScript

var vm = new Vue({
  el: '#vm',
  data: {
    largeClass: {
      large: true,
      'bg-gray': true
    },
    dangerClass: {
      'text-danger': true
    },
    isLarge: true
  }
})

HTML

<div id="vm">
  <p>
    Hello <span v-bind:class="[isLarge ? largeClass : '', dangerClass]">Vue.js!</span>
  </p>
</div>

CSS

.large {
  font-size: 36px;
}
.text-danger {
  color: red;
}
.bg-gray {
  background-color: gray;
}

isLargeがtrueの時、largeClassが適用される。
falseの時は三項演算子で''となっているので適用から外れる。
また、dangerClassは常に適用される。