20200923のRailsに関する記事は17件です。

Rails6 Couldn't find User with 'id' = sign_outとなりログアウトができない

エラー内容

スクリーンショット 2020-09-22 13.02.47.png

参考記事

https://qiita.com/chisaki0606/items/f1f03a6c226e49b5f7b3

ログアウトができない

今までできていたはずのログアウトが突如できなくなる事態が発生してしまう。このエラーで1日を費やしてしまったので、同じようなエラーとなっている人の助けになったらと思い、本稿を投稿することにした。

解決方法

参考記事の通りに

config/initializers/devise.rb
config.sign_out_via = :delete

を下記のように変更する。

config/initializers/devise.rb
config.sign_out_via = :get

しかし、このまま再びログアウトしてもエラーは解決しない。
というのも、devise.rbのコードは再起動しなければ反映されないため、そのままではエラーのままとなる。
そのため、

$ rails s 

として、localhost:3000に再びアクセスする必要がある。
そうしてあげると、エラーから脱出することができ、正常にログアウトすることができる。

終わりに

最後の再起動が重要であり、私はこれをしていないため、沼にハマることになってしまいました。同じようなエラーが悩んでいる方々の助けになれば幸いです。
また、記述に誤りがあるようでしたら、是非ご指摘いただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsアプリケーションで投稿検索機能を実装する(whereメソッド)

アプリ: ラーメン屋の写真や情報を友達と共有できるSNS
Ruby: 2.6.5
Rails: 5.2.0

viewに検索フォームを設置する

form_withを使って、検索ワードをposts_controllerのsearchアクションに渡します。

app/views/layouts/_header.html.erb
・
・
・
<div class="post_search">
  <%= form_with url: search_posts_path, method: :get, local: true do |f| %>
    <%= f.text_field :search, class: 'form-control', placeholder: "キーワード検索"  %>
    <%= f.button :type => "submit" do %>
      <i class="fas fa-search"></i>
    <% end %>
  <% end %>
</div>
・
・
・

スクリーンショット 2020-09-23 18.54.03.jpg

ルーティング

routes.rb を下記のようにすることで、
/posts/searchというURL(search_posts_path)にgetリクエストを送ることでposts_controllerのsearchアクションにルーティングされます。

config/routes.rb
~
~
resources :posts, only: [:new, :create, :edit, :show, :update, :destroy] do
  get :search, on: :collection
end
~
~

posts_controller

検索ワードに合致する投稿を@postsで定義。

app/controllers/posts_controller.rb
~
~
def search
  @section_title = "「#{params[:search]}」の検索結果"
  @posts = if params[:search].present?
             Post.where(['shop_name LIKE ? OR nearest LIKE ?',
                        "%#{params[:search]}%", "%#{params[:search]}%"])
                 .paginate(page: params[:page], per_page: 12).recent
           else
             Post.none
           end
end
~
~

params[:search]でform_withから渡された検索ワードを受け取ります。
whereメソッドはテーブル内から条件に一致したレコードをすべて返します。
(検索ワードがない場合はPost.noneとし、投稿を取得しないようにしました。)

whereメソッド

whereメソッドでは下記のように

モデル名.where('カラム名 = ?', "値")

第一引数に ? を(プレースホルダーと呼ぶ)、第二引数に条件の値を入れることがあります。
このような書き方をすることで、SQLインジェクションというデータベースのデータを不正に操作する攻撃を防ぐことができるそうです。

whereの第二引数には下記のように

"%#{params[:search]}%"

検索ワードの前後に % を置いています。
こうすることで、「空白文字を含む任意の複数文字列」が検索ワードの前後に含まれても
その文字列を持つレコードを返すことができます。

%二郎%
→ 「二郎」「ラーメン二郎」「つけ麺二郎」「ラーメン二郎八王子店」「二郎歌舞伎町店」
 どれも該当する。

%二郎
→ 「二郎」「ラーメン二郎」「つけ麺二郎」は該当するが、
 「ラーメン二郎八王子店」「二郎歌舞伎町店」は該当しない。

下記のように書くことでshop_nameとnearestという2つのカラムから
検索ワードに該当する文字列を検索し、レコードを返します。

Post.where(['shop_name LIKE ? OR nearest LIKE ?',
            "%#{params[:search]}%", "%#{params[:search]}%"])

あとは@postsをapp/views/posts/search.html.erbで表示させるようにしてください。

こんな感じ

スクリーンショット 2020-09-23 21.33.34.jpg

スクリーンショット 2020-09-23 21.34.05.jpg

参考

【Rails】1つの検索フォームで複数カラムをまたいで検索する方法
【Rails】whereメソッドを使って欲しいデータの取得をしよう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[初心者]Rails6 Vue Postgres開発環境をDocker-Composeを使って構築する方法

はじめに

Dockerを用いてRails,Vue,Posgre環境下で開発したいなと思った時に、
Rails,Vueの環境作りに手こずりましたので自分のメモとして保存します。

この記事で分かること

・Rails6とVueとPostgresのDocker環境が構築できる
・RailsとVueの連動ができる
・PryにてインスタンスとDBの操作ができる

環境

MacOS Mojave
Ruby 2.6.4
Rails 6.0.3.3
Vue 2.6.12
Webpack 4.44.2
yarn 1.22.5
Docker 2.3.0.5
VScode

Githubのリンク

僕のGithubのDocker-Start-Kitです。よろしければご活用ください。
Whiro0501/Docker_Rails6_Vue

Docker環境をMacに構築

ターミナルを開く
mkdirで任意の名前のディレクトリを作成
cdで作成したディレクトリに移動
code.でVScodeを開く

terminal
mkdir qiita
cd qiita
code .

VScodeが起動
VScode上のターミナルで作業していくので
ターミナルが起動していなければ⬆︎+Control+@でターミナルを開く

VScodeのターミナル上で以下コマンドを入力してリモートリポジトリのファイルをローカルにコピー

VScode
git clone https://github.com/Whiro0501/Docker_Rails6_Vue.git

cdでDocker_Rails6_Vue/ディレクトリに移動

VScode
cd Docker_Rails6_Vue/

以下コマンドを実行してDockerイメージをビルド

VScode
docker-compose build

以下を実行して、必要なノードモジュールを取得

VScode
docker-compose run web yarn install --check-files

以下を実行して DB(Postgres)を作成

VScode
docker-compose run web rake db:create

別のターミナルを開く
以下を実行してレールズアプリケーションを起動

VScode
#別ターミナルが億劫なら、docker-compose up -dでも良い 
docker-compose up

さらに別のターミナルを開く
Viewを更新するたび毎回コンパイルが発生し、時間がかかるため以下を実行

VScode
docker-compose exec web ./bin/webpack-dev-server

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

Railsアプリケーションが起動することを確認

スクリーンショット 2020-09-23 20.42.47.png

VueとRailsの連動ってどうやるの?

初期構築時点ではRailsとVueが連動されておりませんので連動させていく。

以下でhomeコントローラを作成する(コントローラー名はなんでも良い)。

VScode
docker-compose exec web rails g controller home index

index.html.erbが作成

index.html.erb
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

以下ファイルに"root to: 'home#index'"を追加

routes.rb
Rails.application.routes.draw do
  root to:  'home#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

まずはindex.html.erbに記述されている内容が表示

スクリーンショット 2020-09-23 21.07.31.png

Rails側で下地ができたので、次にVueとの連動の設定
app.vueの初期設定

app.vue
<template>
  <div id="app">
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

hello_vue.jsの初期設定

hello_vue.js
import Vue from 'vue'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)

  console.log(app)
})

app.vueが単一ファイルコンポーネントであり、
hello_vue.jsにオブジェクトとして渡される。
それをindex.html.erbに表示させるよう設定する。

index.html.erbを以下の通り設定

index.html.erb
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

<%= javascript_pack_tag 'hello_vue.js' %>
<%= stylesheet_pack_tag 'hello_vue.js' %>

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

Vueとの連動が完了!!

スクリーンショット 2020-09-23 21.23.06.png

Githubののファイルの説明

Whiro0501/Docker_Rails6_Vue
まず上記リンクのGithubの状態から説明する
端的に説明すると以下2つを設定した後の状態となる
従ってRails6とVueとPostgresが使用できる環境を整えることができるという理屈である。

VScode
docker-compose run web rails new . --force --database=postgresql --webpack=vue

加えて上記の状態だとPostgresがうまくRailsと連動できないため以下のように設定する。
こちらも公式ドキュメントを参考とした。

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test


production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

Dockerfile

公式ドキュメントのベストプラクティスを参照にした。
DockerはFROM, RUN, COPY毎にレイヤーが作成されるため
RUNやCOPYはできるだけまとめると少ないレイヤーで収めることができるとのこと。

Dockerfile
FROM ruby:2.6

# `apt-get install yarn`とするとエラーになる
# プロジェクトに必要なツールをインストール
# &&で繋げてコマンドを実行することによりレイヤーを1つとする
#apt-get update と apt-get installは同一RUN上で行う(分けると最新版を使用できない)
#RUNはイメージの作成次に実行(CMDはコンテナ起動時に実行)
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update -qq && apt-get install -y nodejs postgresql-client vim && \
    apt-get install -y yarn

# ディレクトリの作成
RUN mkdir /myapp

# 作業ディレクトリの指定
#RUN ,  COPY,  ADD 命令のみレイヤを作成するためWORKDIRは気にしなくて良い
# 絶対パスとする
WORKDIR /myapp

# Gemfileが更新された時のみ、レイヤを再構築
#先にプロジェクト全体をコピーしないのはレイヤーを分けるため
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
# ライブラリの依存関係をインストール
RUN bundle install

# プロジェクト全体をコピー(Gemfile/Gemfile.lockはコピーされない)
COPY . /myapp

#コンテナを起動する毎に実行されるスクリプトを追加
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

#コンテナの公開ポート番号の指定
EXPOSE 3000

#指定しなければコンテナ起動時にデフォルトで実行する処理
#Dockerfile では CMD 命令を 1 つしか記述できない
#ENTRYPOINT 命令に対するデフォルト引数としてCMDを使用可能
CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose

docker-compose.ymlもDocker公式ドキュメントを参考にした。

docker-compose.yml
version: '3'
services:
    db:
        # DBにpostgresを使用
        image: postgres
        # ホストの./tmp/dbと/var/lib/postgresql/dataを同期させる
        volumes:
            - ./tmp/db:/var/lib/postgresql/data
        # 環境変数の指定
        environment:
            POSTGRES_PASSWORD: password
    web:
        build: .
        # コンテナ起動時にserver.pidを削除し、rails sを実行する
        command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
        # ホストのカレントディレクトリをコンテナの/myappと同期させる
        volumes:
            - .:/myapp
        # ホストとコンテナ間をポートフォワードする
        ports:
            - '3000:3000'
        # サービス間の依存関係
        depends_on:
            - db
        # Docker環境でByebugを使用
        stdin_open: true
        tty: true

Gemfile

Gemfileに関してはデフォルトで入っているもの主となる。
追加したパッケージはコメントしているため不要であれば削除して構わない

Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }


ruby '2.6.6'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3', '>= 6.0.3.3'
# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  #デバックツールの導入
  gem 'pry-rails'
  gem 'pry-byebug'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  #自動補完用ツールの導入
  gem 'solargraph'
  #静的コード解析ツールの導入
  gem 'rubocop'
  gem 'rubocop-rails'
  #erbのフォーマットツールの導入
  gem 'htmlbeautifier'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

package.json

package.jsonについてはvue-routerとvuexを使いたいため追加している
Vuetify等、UIフレームワークを使用したいようであれば追加する。

package.json
{
    "name": "myapp",
    "private": true,
    "dependencies": {
        "@rails/actioncable": "^6.0.0",
        "@rails/activestorage": "^6.0.0",
        "@rails/ujs": "^6.0.0",
        "@rails/webpacker": "4.3.0",
        "turbolinks": "^5.2.0",
        "vue": "^2.6.12",
        "vue-loader": "^15.9.3",
        "vue-template-compiler": "^2.6.12",
        "vue-router": "^3.0.1",
        "vuex": "^3.0.1"
    },
    "version": "0.1.0",
    "devDependencies": {
        "webpack-dev-server": "^3.11.0"
    }
}

Railsでデバッグする

VScode上のターミナルを開く
以下をターミナルで実行

VScode
docker-compose exec web rails console

pry が起動

VScode
[1] pry(main)> 

postコントローラーを作成

docker-compose exec web rails g controller post index

post_controller.rbを修正

post_controller.rb
class PostController < ApplicationController
  def index
    @post = Post.all
  end
end

postのモデルを作成

VScode
docker-compose exec web rails g model post name:string age:integer

DBにモデルを反映させる

VScode
docker-compose exec web rails db:migrate

index.html.erbを以下に書き換え

index.html.erb
<%= @post.name %>
<%= @post.age %>

routes.rbを以下に修正

routes.rb
Rails.application.routes.draw do
  root to:  'post#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

pryでPostモデルのインスタンスを作成

VScode
@post = Post.new
=> #<Post:0x00005589bc4beb78 id: nil, name: nil, age: nil, created_at: nil, updated_at: nil>

#まだnameやageにはデータを入れていない
#DBに保存もされていない
Post.all
=>   Post Load (2.2ms)  SELECT "posts".* FROM "posts"
[]

#DBにインスタンスを保存する
@post.save
   (0.7ms)  BEGIN
  Post Create (5.9ms)  INSERT INTO "posts" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"  [["created_at", "2020-09-23 13:06:47.962085"], ["updated_at", "2020-09-23 13:06:47.962085"]]
   (3.1ms)  COMMIT
=> true

#もう一度Post.allをしてデータをDBから取得すると保存したインスタンスが呼び出される
Post.all
=>   Post Load (2.2ms)  SELECT "posts".* FROM "posts"
[#<Post:0x00005589bceec5f0
  id: 1,
  name: nil,
  age: nil,
  created_at: Wed, 23 Sep 2020 13:06:47 UTC +00:00,
  updated_at: Wed, 23 Sep 2020 13:06:47 UTC +00:00>]

#@postインスタンスにデータを入れてみる
@post.name = "Hiro"
=> "Hiro"

@post.age = "29"
=> "29"

#再び保存
@post.save
Post Update (4.0ms)  UPDATE "posts" SET "name" = $1, "updated_at" = $2 WHERE "posts"."id" = $3  [["name", "Hiro"], ["updated_at", "2020-09-23 13:10:26.486888"], ["id", 1]]
Post Update (2.0ms)  UPDATE "posts" SET "age" = $1, "updated_at" = $2 WHERE "posts"."id" = $3  [["age", 29], ["updated_at", "2020-09-23 13:10:56.785029"], ["id", 1]]
   (1.0ms)  COMMIT
=> true

#データが保存されている
Post.all
=>   Post Load (1.4ms)  SELECT "posts".* FROM "posts"
[#<Post:0x00007f1ddc7a77a8
  id: 1,
  name: "Hiro",
  age: 29,
  created_at: Wed, 23 Sep 2020 13:06:47 UTC +00:00,
  updated_at: Wed, 23 Sep 2020 13:10:56 UTC +00:00>]

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

以下のようにDBからデータを取得できればOK

スクリーンショット 2020-09-23 22.27.01.png

Binding.pryを試してみる

post_controller.rbを修正

class PostController < ApplicationController
  def index
    binding.pry
    @post = Post.all
  end
end

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

以下の画面でフリーズする(正しい挙動)
スクリーンショット 2020-09-23 22.30.19.png

pryのターミナルに戻る

pry(main)> 

以上で、pryでデバッグする環境が整った

参考

以下のサイトを参考にさせていただきました。
Docker ドキュメント日本語化プロジェクト
クィックスタート: Compose と Rails
Dockerfile のベストプラクティス
Webpacker の基本的な仕組み
Dockerを使って「Rails / PostgreSQL」の開発環境を作ろう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6 Vue Postgres開発環境をDocker-Composeを使って構築する方法

はじめに

Dockerを用いてRails,Vue,Posgre環境下で開発したいなと思った時に、
Rails,Vueの環境作りに手こずりましたので自分のメモとして保存します。

この記事で分かること

・Rails6とVueとPostgresのDocker環境が構築できる
・RailsとVueの連動ができる
・PryにてインスタンスとDBの操作ができる

環境

MacOS Mojave
Ruby 2.6.4
Rails 6.0.3.3
Vue 2.6.12
Webpack 4.44.2
yarn 1.22.5
Docker 2.3.0.5
VScode

Githubのリンク

僕のGithubのDocker-Start-Kitです。よろしければご活用ください。
Whiro0501/Docker_Rails6_Vue

Docker環境をMacに構築

ターミナルを開く
mkdirで任意の名前のディレクトリを作成
cdで作成したディレクトリに移動
code.でVScodeを開く

terminal
mkdir qiita
cd qiita
code .

VScodeが起動
VScode上のターミナルで作業していくので
ターミナルが起動していなければ⬆︎+Control+@でターミナルを開く

VScodeのターミナル上で以下コマンドを入力してリモートリポジトリのファイルをローカルにコピー

VScode
git clone https://github.com/Whiro0501/Docker_Rails6_Vue.git

cdでDocker_Rails6_Vue/ディレクトリに移動

VScode
cd Docker_Rails6_Vue/

以下コマンドを実行してDockerイメージをビルド

VScode
docker-compose build

以下を実行して、必要なノードモジュールを取得

VScode
docker-compose run web yarn install --check-files

以下を実行して DB(Postgres)を作成

VScode
docker-compose run web rake db:create

別のターミナルを開く
以下を実行してレールズアプリケーションを起動

VScode
#別ターミナルが億劫なら、docker-compose up -dでも良い 
docker-compose up

さらに別のターミナルを開く
Viewを更新するたび毎回コンパイルが発生し、時間がかかるため以下を実行

VScode
docker-compose exec web ./bin/webpack-dev-server

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

Railsアプリケーションが起動することを確認

スクリーンショット 2020-09-23 20.42.47.png

VueとRailsの連動ってどうやるの?

初期構築時点ではRailsとVueが連動されておりませんので連動させていく。

以下でhomeコントローラを作成する(コントローラー名はなんでも良い)。

VScode
docker-compose exec web rails g controller home index

index.html.erbが作成

index.html.erb
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

以下ファイルに"root to: 'home#index'"を追加

routes.rb
Rails.application.routes.draw do
  root to:  'home#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

まずはindex.html.erbに記述されている内容が表示

スクリーンショット 2020-09-23 21.07.31.png

Rails側で下地ができたので、次にVueとの連動の設定
app.vueの初期設定

app.vue
<template>
  <div id="app">
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

hello_vue.jsの初期設定

hello_vue.js
import Vue from 'vue'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)

  console.log(app)
})

app.vueが単一ファイルコンポーネントであり、
hello_vue.jsにオブジェクトとして渡される。
それをindex.html.erbに表示させるよう設定する。

index.html.erbを以下の通り設定

index.html.erb
<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

<%= javascript_pack_tag 'hello_vue.js' %>
<%= stylesheet_pack_tag 'hello_vue.js' %>

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

Vueとの連動が完了!!

スクリーンショット 2020-09-23 21.23.06.png

Githubののファイルの説明

Whiro0501/Docker_Rails6_Vue
まず上記リンクのGithubの状態から説明する
端的に説明すると以下2つを設定した後の状態となる
従ってRails6とVueとPostgresが使用できる環境を整えることができるという理屈である。

VScode
docker-compose run web rails new . --force --database=postgresql --webpack=vue

加えて上記の状態だとPostgresがうまくRailsと連動できないため以下のように設定する。
こちらも公式ドキュメントを参考とした。

database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test


production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

Dockerfile

公式ドキュメントのベストプラクティスを参照にした。
DockerはFROM, RUN, COPY毎にレイヤーが作成されるため
RUNやCOPYはできるだけまとめると少ないレイヤーで収めることができるとのこと。

Dockerfile
FROM ruby:2.6

# `apt-get install yarn`とするとエラーになる
# プロジェクトに必要なツールをインストール
# &&で繋げてコマンドを実行することによりレイヤーを1つとする
#apt-get update と apt-get installは同一RUN上で行う(分けると最新版を使用できない)
#RUNはイメージの作成次に実行(CMDはコンテナ起動時に実行)
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update -qq && apt-get install -y nodejs postgresql-client vim && \
    apt-get install -y yarn

# ディレクトリの作成
RUN mkdir /myapp

# 作業ディレクトリの指定
#RUN ,  COPY,  ADD 命令のみレイヤを作成するためWORKDIRは気にしなくて良い
# 絶対パスとする
WORKDIR /myapp

# Gemfileが更新された時のみ、レイヤを再構築
#先にプロジェクト全体をコピーしないのはレイヤーを分けるため
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
# ライブラリの依存関係をインストール
RUN bundle install

# プロジェクト全体をコピー(Gemfile/Gemfile.lockはコピーされない)
COPY . /myapp

#コンテナを起動する毎に実行されるスクリプトを追加
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

#コンテナの公開ポート番号の指定
EXPOSE 3000

#指定しなければコンテナ起動時にデフォルトで実行する処理
#Dockerfile では CMD 命令を 1 つしか記述できない
#ENTRYPOINT 命令に対するデフォルト引数としてCMDを使用可能
CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose

docker-compose.ymlもDocker公式ドキュメントを参考にした。

docker-compose.yml
version: '3'
services:
    db:
        # DBにpostgresを使用
        image: postgres
        # ホストの./tmp/dbと/var/lib/postgresql/dataを同期させる
        volumes:
            - ./tmp/db:/var/lib/postgresql/data
        # 環境変数の指定
        environment:
            POSTGRES_PASSWORD: password
    web:
        build: .
        # コンテナ起動時にserver.pidを削除し、rails sを実行する
        command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
        # ホストのカレントディレクトリをコンテナの/myappと同期させる
        volumes:
            - .:/myapp
        # ホストとコンテナ間をポートフォワードする
        ports:
            - '3000:3000'
        # サービス間の依存関係
        depends_on:
            - db
        # Docker環境でByebugを使用
        stdin_open: true
        tty: true

Gemfile

Gemfileに関してはデフォルトで入っているもの主となる。
追加したパッケージはコメントしているため不要であれば削除して構わない

Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }


ruby '2.6.6'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.3', '>= 6.0.3.3'
# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.7'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use Active Model has_secure_password
# gem 'bcrypt', '~> 3.1.7'

# Use Active Storage variant
# gem 'image_processing', '~> 1.2'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

group :development, :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
  #デバックツールの導入
  gem 'pry-rails'
  gem 'pry-byebug'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
  gem 'web-console', '>= 3.3.0'
  gem 'listen', '~> 3.2'
  # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
  #自動補完用ツールの導入
  gem 'solargraph'
  #静的コード解析ツールの導入
  gem 'rubocop'
  gem 'rubocop-rails'
  #erbのフォーマットツールの導入
  gem 'htmlbeautifier'
end

group :test do
  # Adds support for Capybara system testing and selenium driver
  gem 'capybara', '>= 2.15'
  gem 'selenium-webdriver'
  # Easy installation and use of web drivers to run system tests with browsers
  gem 'webdrivers'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

package.json

package.jsonについてはvue-routerとvuexを使いたいため追加している
Vuetify等、UIフレームワークを使用したいようであれば追加する。

package.json
{
    "name": "myapp",
    "private": true,
    "dependencies": {
        "@rails/actioncable": "^6.0.0",
        "@rails/activestorage": "^6.0.0",
        "@rails/ujs": "^6.0.0",
        "@rails/webpacker": "4.3.0",
        "turbolinks": "^5.2.0",
        "vue": "^2.6.12",
        "vue-loader": "^15.9.3",
        "vue-template-compiler": "^2.6.12",
        "vue-router": "^3.0.1",
        "vuex": "^3.0.1"
    },
    "version": "0.1.0",
    "devDependencies": {
        "webpack-dev-server": "^3.11.0"
    }
}

Railsでデバッグする

VScode上のターミナルを開く
以下をターミナルで実行

VScode
docker-compose exec web rails console

pry が起動

VScode
[1] pry(main)> 

postコントローラーを作成

docker-compose exec web rails g controller post index

post_controller.rbを修正

post_controller.rb
class PostController < ApplicationController
  def index
    @post = Post.all
  end
end

postのモデルを作成

VScode
docker-compose exec web rails g model post name:string age:integer

DBにモデルを反映させる

VScode
docker-compose exec web rails db:migrate

index.html.erbを以下に書き換え

index.html.erb
<%= @post.name %>
<%= @post.age %>

routes.rbを以下に修正

routes.rb
Rails.application.routes.draw do
  root to:  'post#index'
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

pryでPostモデルのインスタンスを作成

VScode
@post = Post.new
=> #<Post:0x00005589bc4beb78 id: nil, name: nil, age: nil, created_at: nil, updated_at: nil>

#まだnameやageにはデータを入れていない
#DBに保存もされていない
Post.all
=>   Post Load (2.2ms)  SELECT "posts".* FROM "posts"
[]

#DBにインスタンスを保存する
@post.save
   (0.7ms)  BEGIN
  Post Create (5.9ms)  INSERT INTO "posts" ("created_at", "updated_at") VALUES ($1, $2) RETURNING "id"  [["created_at", "2020-09-23 13:06:47.962085"], ["updated_at", "2020-09-23 13:06:47.962085"]]
   (3.1ms)  COMMIT
=> true

#もう一度Post.allをしてデータをDBから取得すると保存したインスタンスが呼び出される
Post.all
=>   Post Load (2.2ms)  SELECT "posts".* FROM "posts"
[#<Post:0x00005589bceec5f0
  id: 1,
  name: nil,
  age: nil,
  created_at: Wed, 23 Sep 2020 13:06:47 UTC +00:00,
  updated_at: Wed, 23 Sep 2020 13:06:47 UTC +00:00>]

#@postインスタンスにデータを入れてみる
@post.name = "Hiro"
=> "Hiro"

@post.age = "29"
=> "29"

#再び保存
@post.save
Post Update (4.0ms)  UPDATE "posts" SET "name" = $1, "updated_at" = $2 WHERE "posts"."id" = $3  [["name", "Hiro"], ["updated_at", "2020-09-23 13:10:26.486888"], ["id", 1]]
Post Update (2.0ms)  UPDATE "posts" SET "age" = $1, "updated_at" = $2 WHERE "posts"."id" = $3  [["age", 29], ["updated_at", "2020-09-23 13:10:56.785029"], ["id", 1]]
   (1.0ms)  COMMIT
=> true

#データが保存されている
Post.all
=>   Post Load (1.4ms)  SELECT "posts".* FROM "posts"
[#<Post:0x00007f1ddc7a77a8
  id: 1,
  name: "Hiro",
  age: 29,
  created_at: Wed, 23 Sep 2020 13:06:47 UTC +00:00,
  updated_at: Wed, 23 Sep 2020 13:10:56 UTC +00:00>]

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

以下のようにDBからデータを取得できればOK

スクリーンショット 2020-09-23 22.27.01.png

Binding.pryを試してみる

post_controller.rbを修正

class PostController < ApplicationController
  def index
    binding.pry
    @post = Post.all
  end
end

Webブラウザで以下にアクセス

Webブラウザ
http://localhost:3000

以下の画面でフリーズする(正しい挙動)
スクリーンショット 2020-09-23 22.30.19.png

pryのターミナルに戻る

pry(main)> 

以上で、pryでデバッグする環境が整った

参考

以下のサイトを参考にさせていただきました。
Docker ドキュメント日本語化プロジェクト
クィックスタート: Compose と Rails
Dockerfile のベストプラクティス
Webpacker の基本的な仕組み
Dockerを使って「Rails / PostgreSQL」の開発環境を作ろう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails ページング機能 [gem kaminari]を導入する流れについてまとめてみた。

kaminari導入の仕方

1.Gemfileにkaminariを追加して保存

Gemfile
gem 'kaminari','~> 1.2.1'

2.kaminariをインストールする

Gemfileに追記できましたら、以下コマンドを実行して、kaminariをインストールします。
$ bundle install
さらに、kaminariの設定ファイルを作成します。
以下のコマンドを実行しましょう。
$ rails g kaminari:config
最後に、kaminariがページャで利用するテンプレートを作成します。
以下のコマンドを実行しましょう。
$ rails g kaminari:views default

3.ページャを実装する

app/views/books/index.html.erb
 <%= @books.each do |book| %>
        :
        :
 <% end %>
 <%= paginate @books %> #このコードを追加
app/controllers/books_controller.rb
@books = Books.all.page(params[:page]).per(10) #.page以降追加

この場合1ページの表示件数が10件まで表示される。

10件以上投稿するとこのように次のページへと分かれる。
アイコン

4.kaminariにBootstrapを適用させる

Bootstrapをすでに導入していれば簡単にkaminariに適用させることができる。

$ rails g kaminari:views bootstrap3

これでapp/views/kaminariフォルダにBootstrap用のViewが生成され、Bootstrap用のテンプレートに適用される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

select_tag 選択値設定(include_blank => true)

select_tag 空白行設定(include_blank => true)時、
DBから検索された値をVALに設定しても、空白テキストが表示されている
対策:$("#selectbox").val(DBから検索値).trigger('change')

include_blank => falseなら、
$("#selectbox").val(DBから検索値)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【解説】ポートフォリオ(NotePro)

URL

NotePro

概要

デバイス内に散らばった情報を一つにまとめることできるWEBアプリケーション

制作背景

日頃から私自身で困っていたことを解決したいという考えから作成いたしました。

1.以前Twitterいいねした投稿がいいねしすぎてすぐに見つからない
2.YouTubeでためになったことをスクショしたはいいものの、フォルダのどこにあるのかすぐに見つからない
3.参照リンクもまとめたい
4.某投稿サイトなどはあるが何かによりすぎて気軽な投稿はできそうにない雰囲気がある
5.まとめたものにいいねをもらいたい!!!!

1.以前Twitterいいねした投稿がいいねしすぎてすぐに見つからない

Twitterでは気軽にいいねができすぎて、本当に必要な時、いいねした投稿が見つからないことが皆さんも結構あるのではないでしょうか。
また、Twitterアカウント自体複数持っている方も結構いるのではないでしょうか。そうなると尚更、見つかりずらくなります。
noteとしてまとめる時間はかかるものの、
各アカウントのいいね欄を探しに行く時間を考えたら圧倒的に時間削減できると考えたので作ろうと考えました。

2.YouTubeでためになったことをスクショしたはいいものの、フォルダのどこにあるのかすぐに見つからない

私自身結構な頻度でスクショを撮ります。
動画を見ている際、コメントで出てくるところを撮ったりするのですがフォルダには日常生活で撮影した写真などもあり「あの時スクショした画像どこかな?」と探すことが結構な頻度であります。
なのでこういったことも解決できたらいいなと考えておりました。

3.参照リンクもまとめたい

いいねしたツイート、スクリーンショットなどで、
参照元のリンクやページ内にある別サイトのリンクなども「まとめて管理できないかなあ」と考えておりました。

4.某投稿サイトなどはあるが何かによりすぎて気軽な投稿はできそうにない雰囲気がある

某緑(これ)のやつだったりなどいろいろありますが、
私生活の豆知識なども一緒に投稿できる雰囲気ではないので、
もう少しラフで気軽に投稿できるWEBサービスが欲しかったのです。

5.まとめたものにいいねをもらいたい!!!!

単にnoteの作成であればeveronteやiPhoneのメモなどで事足ります。
ですが、投稿するならまとめたものにいいねとかもらいたいと考えておりました。
しかし、SNSぽくしてしまうと、最近では悲しい事件が起きたりしているで
SNSの使い方、モラルなどが強く必要となり、noteツールにそういうことまではもたせたくないと考えていたりしてました。
なのでSNSのいいところだけを取り、誹謗中傷などはないけど、程よく承認欲求も満たされるものを作ろうじゃないか!

という上記の理由から今回のNoteProを作成いたしました。

機能

リッチテキストによる投稿
idのtoken化
タグ機能
Twitterログイン機能
CSVのエクスポート機能
投稿に対するいいね
ゴミ箱機能
プロフィールの画像設定

リッチテキストによる投稿

summernoteを使用し制作
※日本語の解説などがあまりなく結構時間とられました。

idのtoken化

・user_idやpost_idを乱数保存
皆さんが良く利用するサービスでも

user/12(id)/edit

上記みたいな簡単なIDで表示されているのはあまりないですよね。
簡単なIDにしてしまうと簡単に推測できてしまい悪用されかねないので
乱数(いろいろ種類がある)でトークン化して保守化しました。

タグ機能

・投稿にタグを紐づけることができる

Twitterログイン機能

・Twitterからのログインをすることができる

CSVのエクスポート機能

・自分の投稿のみSCVで吐き出すことが可能

投稿に対するいいね

・他ユーザーのいいと思った投稿にいいねができる

ゴミ箱機能

・不要投稿はゴミ箱に入れることができる

プロフィールの画像設定

・アイコンの設定ができる

追加実装予定

issues

使用技術

・言語:Ruby2.5.7
・フレームワーク:Ruby on Rails5.2.4.3
・テスト:RSpec
・フロント:slim(BEM)、Sass、JavaScript(jQuery)、Bootstrap4
・インフラ:AWS(VPC | ELB | EC2 | SES | Route53 )
・ソースコード管理:GitHub
・その他:Capistranoによる自動デプロイ

環境

開発環境

Vagrant 2.2.4
SQLite 3.7.17

本番環境

MySQL2 5.5.62
Nginx 1.16.1
Puma

使用したgem

gem 'faker'
gem 'pry-byebug'
gem 'rubocop-airbnb'
gem 'slim-rails'
gem 'devise'
gem 'summernote-rails', '~> 0.8.10.0'
gem 'omniauth'
gem 'omniauth-twitter'
gem 'google-analytics-rails'
gem 'meta-tags'
gem 'aws-ses'
gem "refile"
gem 'rails-i18n'
gem 'kaminari', '~> 1.2.1'

なるべく少なくとは思いつつ想定より多くなってしまいました、、

NotePro作成するにあたり定義したものたち

ワイヤーフレーム
ER図
テーブル定義書

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポートフォリオ(NotePro)の解説

URL

NotePro

概要

デバイス内に散らばった情報を一つにまとめることできるWEBアプリケーション

制作背景

日頃から私自身で困っていたことを解決したいという考えから作成いたしました。

1.以前Twitterいいねした投稿がいいねしすぎてすぐに見つからない
2.YouTubeでためになったことをスクショしたはいいものの、フォルダのどこにあるのかすぐに見つからない
3.どうせなら参照リンクもまとめたい
4.某投稿サイトなどはあるが何かによりすぎて気軽な投稿はできそうにない雰囲気がある
5.まとめたものにいいねをもらいたい!!!!

1.以前Twitterいいねした投稿がいいねしすぎてすぐに見つからない

Twitterでは気軽にいいねができすぎて、本当に必要な時、いいねした投稿が見つからないことが皆さんも結構あるのではないでしょうか。
また、Twitterアカウント自体複数持っている方も結構いるのではないでしょうか。そうなると尚更、見つかりずらくなります。
noteとしてまとめる時間はかかるものの、
各アカウントのいいね欄を探しに行く時間を考えたら圧倒的に時間削減できると考えたので作ろうと考えました。

2.YouTubeでためになったことをスクショしたはいいものの、フォルダのどこにあるのかすぐに見つからない

私自身結構な頻度でスクショを撮ります。
動画を見ている際、コメントで出てくるところを撮ったりするのですがフォルダには日常生活で撮影した写真などもあり「あの時スクショした画像どこかな?」と探すことが結構な頻度であります。
なのでこういったことも解決できたらいいなと考えておりました。

3.どうせなら参照リンクもまとめたい

いいねしたツイート、スクリーンショットなどで、
どうせなら参照元のリンクやページ内にある別サイトのリンクなども「まとめて管理できないかなあ」と考えておりました。

4.某投稿サイトなどはあるが何かによりすぎて気軽な投稿はできそうにない雰囲気がある

某緑(これ)のやつだったりなどいろいろありますが、
私生活の豆知識なども一緒に投稿できる雰囲気ではないので、
もう少しラフで気軽に投稿できるWEBサービスが欲しかったのです。

5.まとめたものにいいねをもらいたい!!!!

単にnoteの作成であればeveronteやiPhoneのメモなどで事足ります。
ですが、どうせならまとめたものにいいねとかもらいたいと考えておりました。
しかし、SNSぽくしてしまうと、最近では悲しい事件が起きたりしているで
SNSの使い方、モラルなどが強く必要となり、noteツールにそういうことまではもたせたくないと考えていたりしてました。
なのでSNSのいいところだけを取り、誹謗中傷などはないけど、程よく承認欲求も満たされるものを作ろうじゃないか!

という上記の理由から今回のNoteProを作成いたしました。

機能

リッチテキストによる投稿
タグ機能
Twitterログイン機能
CSVのエクスポート機能
投稿に対するいいね
ゴミ箱機能
プロフィールの画像設定

リッチテキストによる投稿

summernoteを使用し制作
※日本語の解説などがあまりなく結構時間とられました。

タグ機能

・投稿にタグを紐づけることができる

Twitterログイン機能

・Twitterからのログインをすることができる

CSVのエクスポート機能

・自分の投稿のみSCVで吐き出すことが可能

投稿に対するいいね

・他ユーザーのいいと思った投稿にいいねができる

ゴミ箱機能

・不要投稿はゴミ箱に入れることができる

プロフィールの画像設定

・アイコンの設定ができる

追加実装予定

issues

使用技術

・言語:Ruby2.5.7
・フレームワーク:Ruby on Rails5.2.4.3
・テスト:RSpec
・フロント:slim(BEM)、Sass、JavaScript(jQuery)、Bootstrap4
・インフラ:AWS(VPC | ELB | EC2 | SES | Route53 )
・ソースコード管理:GitHub
・その他:Capistranoによる自動デプロイ

環境

開発環境

Vagrant 2.2.4
SQLite 3.7.17

本番環境

MySQL2 5.5.62
Nginx 1.16.1
Puma

使用したgem

gem 'faker'
gem 'pry-byebug'
gem 'rubocop-airbnb'
gem 'slim-rails'
gem 'devise'
gem 'summernote-rails', '~> 0.8.10.0'
gem 'omniauth'
gem 'omniauth-twitter'
gem 'google-analytics-rails'
gem 'meta-tags'
gem 'aws-ses'
gem "refile"
gem 'rails-i18n'
gem 'kaminari', '~> 1.2.1'

なるべく少なくとは思いつつ想定より多くなってしまいました、、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webpacker::Manifest::MissingEntryError in モデル名 のエラーを解決

Webpacker::Manifest::MissingEntryError in が出た時の対処

Rails アプリを作った。Rails s でサーバー起動後に出た。

自分の環境

Rails 6.0.3.3
ruby 2.6.5

エラー

Webpacker can't find application in /Users/user/acne/public/packs/manifest.json. Possible causes:
1. You want to set webpacker.yml value of compile to true for your environment
unless you are using the webpack -w or the webpack-dev-server.
2. webpack has not yet re-run to reflect updates.
3. You have misconfigured Webpacker's config/webpacker.yml file.
4. Your webpack configuration is not creating a manifest.
Your manifest contains:
{
}

 対処

Webpacker can't find application in /Users/user/acne/public/packs/manifest.json. Possible causes:

って書いているので探してみたら、対象のフォルダ/ファイルがない。
packs/manifest.json.

したがって自分で作ったら起動完了した。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails Kaminariでページネーション機能を作る

バージョン

・ruby 2.6.6
・rails 5.2.4.1

gem 'kaminari' を実装

 投稿機能を持ったアプリを作った際には、ページネーション機能はUX的にも必須です。
そこで、gemのKaminariを使って、ページネーション機能の実装を行います。

まずGemfileにkaminariを追加して、bundle installを実行

Gemfile
gem 'kaminari', '~> 1.2.0'
ターミナル
bundle install

Controllerを修正

 すでに投稿機能を作成し、投稿一覧ページがあるのであれば、Controlerを修正する必要があります。
 元々の自分の投稿一覧ページにおけるControllerは以下の通りです。

controllers/posts_controller.rb
def index
 @posts = Post.all
end

これらを次のように変更する。

controllers/posts_controller.rb
def index
 @posts = Post.page(params[:page]).per(10)
end

 Kaminariを追加したことで、各モデル上でpageとperというメソッドが使えるようになります。
pageメソッドの引数に現在のページ数を渡し、perの引数には何件でページを分割するかを渡します。
ここではperの引数に10を入れましたが、8件ずつページを区切りたいときは引数を8にします。

Viewの変更

 投稿一覧ページを表示する際には、each文を使うことが大体であると思いますが、rubyで用いる
<% end %> の後に <%= pagenate @posts %> と付け加えます。
 hamlを使った場合は、listの最後に = paginate @posts のように表記すると良いようです。
詳しくは、hamlを使って記事を書いている方がいらっしゃるので、そちらを参考にしてください。

views/posts/index.html.erb
 <% @posts.each do |post| %>
           .
           .
           .
 <% end %>
 <%= pagebate @posts %>

 ここまででミスがなければ、ページネーション用のリンクが自動で表示され、ページネーション機能が実装できます。
 しかし、見やすいデザインとまではなってないと思います。そこで、Bootstrapを利用して、デザインを整えます。すでにBootstrapを導入している方は、次のステップは飛ばしてください。

Bootstrapの導入

Bootstrapの導入には、yarnを利用してインストールするやり方、Bootstrapの公式サイトからダウンロードするやり方等ありますが、今回はCDNの方法で説明します。
ここの説明だけでは不安という方はhttps://www.youtube.com/watch?v=YY0mEyggH1E&t=713s
の動画で導入方法を説明しているものがありますので、そちらをご覧ください。

行うステップは2つ。
1つ目はheadタグの最後に、公式サイトにあるコードを記述。

layouts/application.html.erb
<head>
 <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous">
</head>

2つ目はbodyタグの最後に、公式サイトにあるコードを記述

layouts/application.html.erb
<body>

 <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
 <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
 <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script>

</body>

以上で導入が完了します。

Bootstrap4用のViewテンプレートを生成

 kaminariにはBootstrapやfoundationなどで使えるテーマが用意されています。
以下のコードをターミナルで実行すると「app/views/kaminari」配下にBootstrap4用のViewテンプレートが生成されます。このViewはkaminariデフォルトのViewより優先して適用されます。

ターミナル
rails g kaminari:views bootstrap4

ここまで完了すると見た目も良いデザインとなります。
うまくいかない時はサーバーを再起動してみてください。

また、英語の表記のままでは嫌だという人は日本語表記にする操作を行います。
「config/locales/kaminari_ja.yml」を追加し、以下のように記述します。

config/locales/kaminari_ja.yml
ja:
  views:
      pagination:
        first: '最初'
        last: '最後'
        previous: '前'
        next: '次'
        truncate: '...'

ここまでで日本語が適用されていないときは「config/application.rb」の中に config.i18n.default_locale = :ja を追加する。
全体的に見ると、以下のようになります。

config/application.rb
require_relative 'boot'

require 'rails/all'

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module Theclo
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
    config.i18n.default_locale = :ja
    config.time_zone = 'Tokyo'
  end
end

以上で実装完了です。
うまくいかない時は再度サーバーの再起動をしてみてください。
皆さんの参考になれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

気軽な.countに気をつけよう(N+1問題)

はじめに

Ruby on Railsを使っているとサクサクコードが書けて楽しいですね。
文法さえいくつか覚えてしまえばまるでプログラミングを意識せずに書きたいことをサクサク書いていけます。
Databaseに対してどういう命令を出しているかも意識せずに・・・

この代表的な問題点がいわゆるN+1問題です。

N+1問題の解決策などはこのあたりに詳しく書いてありますが、今回はcountメソッドを使った場合に起きる問題に焦点を当てて解説します。

問題点: Array?ActiveRecord::Associations?

配列の数を数えるメソッドとして、

.length
.size
.count

などがあります。
これらを使うことで、簡単に要素の数を返してくれます。

今回の問題点は .count を使った際にせっかく includes などを利用してもN+1問題が発生してしまうことの詳細です。

例えば、記事に対するコメント数を出す場合に

app/models/article.rb
class Article < ActiveRecord::Base
  has_many :comments
end
app/models/comment.rb
class Comment < ActiveRecord::Base
  belongs_to :article
end

というモデルがあったとします。

とある一覧画面に記事の一覧とコメントの数を列挙しようとした際に、何も考えずに書くと

app/controllers/articles_controller.rb
~~

  def index
    @articles = Article.all
  end

~~

※ページングも何も考慮してません

app/views/articles/index.html.erb
~~略

<%= @articles.each do |article| %>
<div class="article_panel">
  <div class="article_title"><%= article.title %></div>
  <div class="comment_count"><%= article.comments.count %></div>
</div>
<% end %>

~~略

おめでとうございます。コレであなたもN+1問題へ無事突入しました。

一般的な解決策としては app/controllers/articles_controller.rbincludes をすることなのですが、

app/controllers/articles_controller.rb
~~

  def index
    @articles = Article.includes(:comments).all
  end

~~

これだけだと app/views/articles/index.html.erb.count メソッドを使っているため、解決しません。

.count メソッドは ActiveRecord::Associations::CollectionProxy にも定義されており、対象のテーブルに COUNT(*) 句のクエリを発行してしまいます。

残念ながらここまででは、N+1は未解決です。

解決策

app/controllers/articles_controller.rb
~~

  def index
    @articles = Article.includes(:comments).all
  end

~~

includes に加えて、

app/views/articles/index.html.erb
~~略

<%= @articles.each do |article| %>
<div class="article_panel">
  <div class="article_title"><%= article.title %></div>
  <div class="comment_count"><%= article.comments.size %></div>
</div>
<% end %>

~~略

.count.size (もしくは .length )に書き換えることで、N+1問題は解決します。

結論

せっかくincludeしたActiveRecord::Associationsに対しての数を数えるときは、 .size もしくは .length を使いましょう。
そうすることで COUNT(*) 句のクエリは発行されず、要素の数だけを数えて返してくれます。

※ちなみにですが、 comments.sum(:like) などを使った際にも SUM(comments.like) などでクエリが発行されますので注意が必要です。

おわりに

Ruby on Rails は本当に気軽にかける素晴らしいフレームワークです。
我々の会社でも数多くのプロダクトでこのフレームワークを使うことによる開発速度の恩恵を受けてきました。

正しく理解し、正しく使うことが非常に重要です。

何に対してどんな命令を出すことで、どこのリソースに対してどんな負荷がかかるのかを意識し、この記事がN+1退治に役立てれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

nilブロックなるものを知った

この記事について

独学でrailsを勉強しております。
新たに学べたことをアウトプットするだけの記事ですので、初学者の方にとっての記事です。

nilブロックとは何か?

nilになる可能性のあるものにメソッドを使う場合はnilブロックを行うことで、エラーが発生しないようになる。

実際のコード

例えば、先にtitleを投稿できるようにしてすでにデータが作成された後に、expired_atカラムを追加して新たにtitleexpired_atを投稿してviewを確認しようとすると、前に作られたデータにはexpired_atの情報が入っておらず、画像のエラーが表示される。

スクリーンショット 2020-09-23 13.00.18.png

#-----  controller -------
  def index
    @to_dos = ToDo.all
  end


#----- view ----- 
<% @to_dos.each do |to_do| %>
    <h2><%= to_do.title %></h2>
    <h3><%= to_do.expired_at.strftime('%Y/%m/%d %H:%M') %></h3>
<% end %>



nilブロックを使ってみると

nilの可能性のあるカラム(今回の場合はexpired_at)の後ろに&をつける

#-----  controller -------
  def index
    @to_dos = ToDo.all
  end


#----- view ----- 
<% @to_dos.each do |to_do| %>
    <h2><%= to_do.title %></h2>
    <h3><%= to_do.expired_at&.strftime('%Y/%m/%d %H:%M') %></h3>
<% end %>



画像のようにnilがあったとしても、nilブロックによってエラーにならずに表示させる事ができる。

スクリーンショット 2020-09-23 13.00.08.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vhと%の違い

※主に自分の振り返り用です。

クレジットカード登録画面の下部に空白が生じるのをなんとかしたい。
写真ではわかりにくいですが、黄色で囲んだところに生じている空白を
無くしたいと思い奮闘しました。

スクリーンショット 2020-09-23 10.21.22.png

いろいろ調べて、htmlやbodyに

height: 100%;

を当てる方法を試したところ、他のページのビューにも影響が出てしまい
この方法は断念。。。

このあと2時間ほど奮闘しましたが、
card_addクラスに

height: 100vh;

これであっさり解決。

要素の高さ指定で%とvhの違いがよく理解できていなかったので
ここにたどり着くのに時間がかかったようです。

vhと%の違い

【vh】
ページの表示領域に依存する。

height: 100vh;

と指定すれば、高さがページの表示領域いっぱいに表示される

【%】
親要素に依存する。

height: 100%;

と指定すれば親要素の大きさまで表示される。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

No Method Errorについて

自分用のエラー解決録です。

フリマアプリ作成のチーム実装もいよいよ終盤。
各メンバーが開発したものをマスターにマージし、
自分のローカルにプルすると度々エラーが発生します。

今回のエラーはこちら
NoMethodError.png

マイページからログアウトページ遷移しようとしたところ発生。

undefined method `each' for nil:NilClass

という記述です。
場所はどこか?というと
user#showです。

早速コントローラーを確認すると

class UsersController < ApplicationController
  def index
    @parents = Category.where(ancestry: nil)
  end

  def show

  end

end

showの中身が空、、、
こちらに記述を追加

class UsersController < ApplicationController
  def index
    @parents = Category.where(ancestry: nil)
  end

  def show
    @parents = Category.where(ancestry: nil)
  end

end

するときちんと表示が戻りました。
スクリーンショット 2020-09-23 12.27.23.png

エラーを重ねることによって、エラー文から原因を自分で推測して
解決できるようになってきました。
自走力が少し身についてきたのかな?

まだまだこれからですが、引き続き頑張ります!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MVCについて(自分なりにアウトプット)

そもそも何ってことから振り返ってみます!

MVCとは、モデル(Model)・ビュー(View)・コントローラー(Controller)の頭文字をとったもです。で、それ自体は何かと問われると、【Webフレームワークで一般的に取り入れられるアプリケーション設定を整理するための概念の一つ】とある記事で書かれていましたが、要は、railsなどのアプリケーションの処理を行うシステムの構造のことを言うと覚えておけば良いのかと思います。

では、なんでそれを使用するのかを調べました。

MVCの概念がない場合、あるプログラムを書いたディレクトリをどこで対応しているかなどがわからなくなります。そのディレクトリで記述したコードの破綻等が起き不具合やバグの原因にも繋がります。そのため、役割ごとに意味のある単位で機能を実装すると管理しやすくなるので、MVCに分割して実装することでそれらを防ぎ円滑にアプリケーションを構築できるので便利であると言う理由から使用されたとのことです。
要は、それらを機能している箱に、コードを書くだけでアプリケーションが動きますよ的なことだと認識してます?

【MVC】のそれぞれの役割を調べていきます!

まずは、MVCの M から見ていきます!
Mとは、モデルのことを指します。
モデルは何の機能をもったところかと言うと、データベースとデータのやり取りを行う機能を実装したり、データベースからデータをプログラムで扱いやすい形式でデータを変換する役割を担っています。
データベースに、新規のデータを入れたい時や、データの更新を行いたい時、削除したい時などに機能の実装を行う箇所です。

次にMVCの V を見ていきます!
Vとは、ビューのことを指します。
ビューは、データを文字上でレスポンスを行う機能をいい、HTMLを動的に生成する部分です。
要は、リクエストされた情報を、HTMLなどを利用し、可視化してコントローラーに渡す役割を持った箇所を指します。
※HTMLとは、webページ上で表示されている文字などを表示させるマークアップ言語です。

ラストにMVCの C を見ていきます!
Cとは、コントローラーを指します。
コントローラーは、ルーティングから来たリクエストをモデルを介して情報の取得及び保存を行いビューからもらった(見た目が整った)ものをレスポンスとしてクライアントに返す役割を持った箇所です。
要は、モデルとの橋渡し・ビュートの橋渡し・クライアントの橋渡しの部分がコントローラーに当たります。
※ルーティングとは、クライアントからくる情報をコントローラに道筋を決めて渡す箇所を言います。

これを画像にするとこうです(鬼の手書きなのでわかりにくいと思います?‍♂️)↓
47964874-E251-4A59-B0C4-3C792284CF36_1_201_a.jpeg

まとめ

MVCの概念はアプリケーションを作る上では必須と言っても過言ではないもので、それらを理解するとスムーズにアプリケーションを作成することができるものだと言った認識でいます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

任意の場所から文字を取り出せるメソッド作成

【概要】

1.結論

2.どのように記載するか

3.ここから学んだこと

1.結論

lengthメソッドとsliceメソッドを使用する!


2.どのように記載するか

def srt_search(strings)
  character_number = strings.length #---❶
  character_reslut = strings.slice(character_number - 3, 3) #---❷
end

上記のように記述しました!

❶lengthで、文字列を数に変更しました。

ex)"Goodjob"➡︎"8"文字に変換

❷今回は最後から3番目までの数字が欲しいので❶の文字数からslice(character_number - 3, 3)することでcharacter_numberからの文字数から"-3"した番目の"3"文字分を取得できます。

ex)"Goodjob"の8文字から"-3"すると最初から"5"番目(=最後から3番目まで)の"3"文字を抜き取る。

参考にしたURL:
【Ruby入門説明書】ruby sliceについて解説

3.ここから学んだこと

slice(character_number - 3, 3)の部分は、
slice(-3,3)とすれば、最後から3番目の3文字を常にとることができるなと思いました。ただ、文章の構成(ex:Hi, John! Nice to meet you!)になると単語区分でsliceが判断してしまい、"to meet you!"が抜き出される点は注意しないといけません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6で認証認可入り掲示板APIを構築する #18・終 user controllerの実装

Rails 6で認証認可入り掲示板APIを構築する #17 管理者権限の追加

全18回に渡る連載も今回で終了です。

今回はuser controllerを作ります。
今までの集大成なので、例示するコードを見ずに作ってみることをオススメします。

仕様

主に管理者がユーザー管理をする用途と、ユーザーが自分自身の更新削除をするための機能群として想定しています。

  • #index 管理者のみ閲覧可能
  • #show 自分自身か管理者のみ閲覧可能
  • #create 管理者のみ作成可能
  • #update 自分自身か管理者のみ更新可能
  • #destroy 管理者のみ削除可能

手順

  1. users_controllerの作成
  2. user_policyの作成
  3. user_policyテストの実装
  4. user_policyの実装
  5. users_controllerテストの実装
  6. users_controllerの実装

という手順で進めてみます。

1. users_controllerの作成

実装例

コマンド叩くだけなので簡単ですね。
$ rails g controller v1/users

rubocopに怒られないように微修正したファイル群がこちら。

app/controllers/v1/users_controller.rb
# frozen_string_literal: true

module V1
  #
  #  users controller
  #
  class UsersController < ApplicationController
  end
end

spec/requests/v1/users_request_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "V1::Users", type: :request do
end

2. user_policyの作成

実装例

コマンド叩いてファイル作ります。

$ rails g pundit:policy user

rubocopに怒られないように微修正を加えたらとりあえず完成。

app/policies/user_policy.rb
# frozen_string_literal: true

#
# userのポリシークラス
#
class UserPolicy < ApplicationPolicy
  #
  # scope
  #
  class Scope < Scope
    def resolve
      scope.all
    end
  end
end
spec/policies/user_policy_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe UserPolicy, type: :policy do
  let(:user) { User.new }

  subject { described_class }

  permissions ".scope" do
    pending "add some examples to (or delete) #{__FILE__}"
  end

  permissions :show? do
    pending "add some examples to (or delete) #{__FILE__}"
  end

  permissions :create? do
    pending "add some examples to (or delete) #{__FILE__}"
  end

  permissions :update? do
    pending "add some examples to (or delete) #{__FILE__}"
  end

  permissions :destroy? do
    pending "add some examples to (or delete) #{__FILE__}"
  end
end

3. user_policyテストの実装

実装例

仕様のおさらい。

  • #index 管理者のみ閲覧可能
  • #show 自分自身か管理者のみ閲覧可能
  • #create 管理者のみ作成可能
  • #update 自分自身か管理者のみ更新可能
  • #destroy 管理者のみ削除可能

index, #create, #destroyで共通化、#show, #updateで共通化できそうですね。

それを念頭に実装したのがこちら。

spec/policies/user_policy_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe UserPolicy, type: :policy do
  let(:user) { create(:user) }
  let(:another_user) { create(:user) }
  let(:admin_user) { create(:user, :admin) }

  subject { described_class }

  permissions :index?, :create?, :destroy? do
    it "未ログインの時に不許可" do
      expect(subject).not_to permit(nil, user)
    end
    it "adminユーザー以外でログインしている時に不許可" do
      expect(subject).not_to permit(user, user)
    end
    it "adminユーザーでログインしている時に許可" do
      expect(subject).to permit(admin_user, user)
    end
  end

  permissions :show?, :update? do
    it "未ログインの時に不許可" do
      expect(subject).not_to permit(nil, user)
    end
    it "ログインしているが別ユーザーの時に不許可" do
      expect(subject).not_to permit(user, another_user)
    end
    it "adminユーザーでログインしている時に許可" do
      expect(subject).to permit(admin_user, user)
    end
    it "ログインしていて同一ユーザーの時に許可" do
      expect(subject).to permit(user, user)
    end
  end
end

another_userという別ユーザーを定義しているのがポイント。
さて、この時点ではpolicyを設定していないのでテストがいくつかコケることを確認してください。

4. user_policyの実装

実装例
app/policies/user_policy.rb
# frozen_string_literal: true

#
# userのポリシークラス
#
class UserPolicy < ApplicationPolicy
  def index?
    admin?
  end

  def show?
    me? || admin?
  end

  def create?
    admin?
  end

  def update?
    me? || admin?
  end

  def destroy?
    admin?
  end

  private

  def me?
    @record == @user
  end

  #
  # scope
  #
  class Scope < Scope
    def resolve
      scope.all
    end
  end
end

me?というprivateメソッドをuser_policy.rbに定義しているのがポイントです。

application_policy.rbにあるmine?は、@record.user == @userと、@recordの持つuserが自分のものか比較していました。
ですが今回は@record@userが一致するか確認するので、英語の文法的にmine?ではなくme?になります。その上、user自身と比較するのは現時点ではusers_controllerでしかなさそうなので、application_policyではなくuser_policyに提議しました。

あとは特筆することもなく、テストが通過するようになったはずです。

5. users_controllerテストの実装

実装例
spec/requests/v1/users_request_spec.rb
# frozen_string_literal: true

require "rails_helper"

RSpec.describe "V1::Users", type: :request do
  before do
    @user = create(:user, name: "userテスト")
    @authorized_headers = authorized_user_headers @user
    admin = create(:user, :admin)
    @authorized_admin_headers = authorized_user_headers admin
  end
  describe "GET /v1/users#index" do
    before do
      create_list(:user, 3)
    end
    it "正常レスポンスコードが返ってくる" do
      get v1_users_url, headers: @authorized_admin_headers
      expect(response.status).to eq 200
    end
    it "件数が正しく返ってくる" do
      get v1_users_url, headers: @authorized_admin_headers
      json = JSON.parse(response.body)
      expect(json["users"].length).to eq(3 + 2) # headers用2件を含む
    end
    it "id降順にレスポンスが返ってくる" do
      get v1_users_url, headers: @authorized_admin_headers
      json = JSON.parse(response.body)
      first_id = json["users"][0]["id"]
      expect(json["users"][1]["id"]).to eq(first_id - 1)
      expect(json["users"][2]["id"]).to eq(first_id - 2)
      expect(json["users"][3]["id"]).to eq(first_id - 3)
      expect(json["users"][4]["id"]).to eq(first_id - 4)
    end
  end

  describe "GET /v1/users#show" do
    it "正常レスポンスコードが返ってくる" do
      get v1_user_url({ id: @user.id }), headers: @authorized_headers
      expect(response.status).to eq 200
    end
    it "nameが正しく返ってくる" do
      get v1_user_url({ id: @user.id }), headers: @authorized_headers
      json = JSON.parse(response.body)
      expect(json["user"]["name"]).to eq("userテスト")
    end
    it "存在しないidの時に404レスポンスが返ってくる" do
      last_user = User.last
      get v1_user_url({ id: last_user.id + 1 }), headers: @authorized_headers
      expect(response.status).to eq 404
    end
  end

  describe "POST /v1/users#create" do
    let(:new_user) do
      attributes_for(:user, name: "create_nameテスト", email: "email+create_test@example.com", admin: true)
    end
    it "正常レスポンスコードが返ってくる" do
      post v1_users_url, params: new_user, headers: @authorized_admin_headers
      expect(response.status).to eq 200
    end
    it "1件増えて返ってくる" do
      expect do
        post v1_users_url, params: new_user, headers: @authorized_admin_headers
      end.to change { User.count }.by(1)
    end
    it "name, email, adminが正しく返ってくる" do
      post v1_users_url, params: new_user, headers: @authorized_admin_headers
      json = JSON.parse(response.body)
      expect(json["user"]["name"]).to eq("create_nameテスト")
      expect(json["user"]["email"]).to eq("email+create_test@example.com")
      expect(json["user"]["admin"]).to be true
    end
    it "不正パラメータの時にerrorsが返ってくる" do
      post v1_users_url, params: {}, headers: @authorized_admin_headers
      json = JSON.parse(response.body)
      expect(json.key?("errors")).to be true
    end
  end

  describe "PUT /v1/users#update" do
    let(:update_param) do
      update_param = attributes_for(:user, name: "update_nameテスト", email: "email+update_test@example.com", admin: true)
      update_param[:id] = @user.id
      update_param
    end
    it "正常レスポンスコードが返ってくる" do
      put v1_user_url({ id: update_param[:id] }), params: update_param, headers: @authorized_headers
      expect(response.status).to eq 200
    end
    it "name, email, adminが正しく返ってくる" do
      put v1_user_url({ id: update_param[:id] }), params: update_param, headers: @authorized_headers
      json = JSON.parse(response.body)
      expect(json["user"]["name"]).to eq("update_nameテスト")
      expect(json["user"]["email"]).to eq("email+update_test@example.com")
      expect(json["user"]["admin"]).to be false # admin権限は書き換えできると困るのでfalseのまま
    end
    it "不正パラメータの時にerrorsが返ってくる" do
      put v1_user_url({ id: update_param[:id] }), params: { name: "" }, headers: @authorized_headers
      json = JSON.parse(response.body)
      expect(json.key?("errors")).to be true
    end
    it "存在しないidの時に404レスポンスが返ってくる" do
      last_user = User.last
      put v1_user_url({ id: last_user.id + 1 }), params: update_param, headers: @authorized_admin_headers
      expect(response.status).to eq 404
    end
  end

  describe "DELETE /v1/users#destroy" do
    it "正常レスポンスコードが返ってくる" do
      delete v1_user_url({ id: @user.id }), headers: @authorized_admin_headers
      expect(response.status).to eq 200
    end
    it "1件減って返ってくる" do
      expect do
        delete v1_user_url({ id: @user.id }), headers: @authorized_admin_headers
      end.to change { User.count }.by(-1)
    end
    it "存在しないidの時に404レスポンスが返ってくる" do
      last_user = User.last
      delete v1_user_url({ id: last_user.id + 1 }), headers: @authorized_admin_headers
      expect(response.status).to eq 404
    end
  end
end

いくつかpostの時とは違う考慮点があります。

  • headerを作る際にcreate(:user)をしているので、その2件分を考慮してテストを書くこと。
    • 存在しないid確認の際も同様、last_userを取得して+1
  • adminフラグは自由に書き換えれてしまうと困るので、createでadminフラグは反映されるがupdateでは反映されないこと

あたりを取り入れています。

また、レスポンスにadmin判定があるので、serializerの修正も必要ですね。

app/serializers/user_serializer.rb
 # user serializer
 #
 class UserSerializer < ActiveModel::Serializer
-  attributes :id, :name, :email
+  attributes :id, :name, :email, :admin
 end

以上で準備完了。次にcontroller実装に入ります。

6. users_controllerの実装

実装例

controllerにアクセスできるようにするため、routesの修正をします。

config/routes.rb
 Rails.application.routes.draw do
   namespace "v1" do
     resources :posts
+    resources :users
     mount_devise_token_auth_for "User", at: "auth"
   end

続いてcontrollerです。
こちらはpostのほぼ流用でいけますね。

app/controllers/v1/users_controller.rb
# frozen_string_literal: true

module V1
  #
  #  users controller
  #
  class UsersController < ApplicationController
    before_action :set_user, only: %i[show update destroy]

    def index
      users = User.order(created_at: :desc).limit(20)
      authorize users
      render json: users
    end

    def show
      authorize @user
      render json: @user
    end

    def create
      user = User.new(user_create_params)
      user[:provider] = :email
      authorize user
      if user.save
        render json: user
      else
        render json: { errors: user.errors }
      end
    end

    def update
      authorize @user
      if @user.update(user_params)
        render json: @user
      else
        render json: { errors: @user.errors }
      end
    end

    def destroy
      authorize @user
      @user.destroy
      render json: @user
    end

    private

    def set_user
      @user = User.find(params[:id])
    end

    def user_create_params
      # createの時のみadmin権限を設定できるようにする
      params.permit(:name, :email, :admin, :password)
    end

    def user_params
      params.permit(:name, :email, :password)
    end
  end
end

テストのセクションで書いたように、adminはcreateの時のみ付与できるようにしています。

以上です。
全18回に渡りご覧いただきありがとうございました。

ぜひこれを元に、コメント機能やソーシャルログイン等の機能拡張していってください。

目次

連載目次へ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む