- 投稿日:2020-09-23T22:30:14+09:00
Rails6 Couldn't find User with 'id' = sign_outとなりログアウトができない
エラー内容
参考記事
https://qiita.com/chisaki0606/items/f1f03a6c226e49b5f7b3
ログアウトができない
今までできていたはずのログアウトが突如できなくなる事態が発生してしまう。このエラーで1日を費やしてしまったので、同じようなエラーとなっている人の助けになったらと思い、本稿を投稿することにした。
解決方法
参考記事の通りに
config/initializers/devise.rbconfig.sign_out_via = :deleteを下記のように変更する。
config/initializers/devise.rbconfig.sign_out_via = :getしかし、このまま再びログアウトしてもエラーは解決しない。
というのも、devise.rbのコードは再起動しなければ反映されないため、そのままではエラーのままとなる。
そのため、$ rails sとして、
localhost:3000
に再びアクセスする必要がある。
そうしてあげると、エラーから脱出することができ、正常にログアウトすることができる。終わりに
最後の再起動が重要であり、私はこれをしていないため、沼にハマることになってしまいました。同じようなエラーが悩んでいる方々の助けになれば幸いです。
また、記述に誤りがあるようでしたら、是非ご指摘いただけると幸いです。
- 投稿日:2020-09-23T21:39:42+09:00
Railsアプリケーションで投稿検索機能を実装する(whereメソッド)
アプリ: ラーメン屋の写真や情報を友達と共有できるSNS
Ruby: 2.6.5
Rails: 5.2.0viewに検索フォームを設置する
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> ・ ・ ・ルーティング
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で表示させるようにしてください。
こんな感じ
↓
参考
【Rails】1つの検索フォームで複数カラムをまたいで検索する方法
【Rails】whereメソッドを使って欲しいデータの取得をしよう!
- 投稿日:2020-09-23T21:30:54+09:00
[初心者]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
VScodeGithubのリンク
僕のGithubのDocker-Start-Kitです。よろしければご活用ください。
Whiro0501/Docker_Rails6_VueDocker環境をMacに構築
ターミナルを開く
mkdirで任意の名前のディレクトリを作成
cdで作成したディレクトリに移動
code.でVScodeを開くterminalmkdir qiita cd qiita code .
VScodeが起動
VScode上のターミナルで作業していくので
ターミナルが起動していなければ⬆︎+Control+@でターミナルを開くVScodeのターミナル上で以下コマンドを入力してリモートリポジトリのファイルをローカルにコピー
VScodegit clone https://github.com/Whiro0501/Docker_Rails6_Vue.gitcdでDocker_Rails6_Vue/ディレクトリに移動
VScodecd Docker_Rails6_Vue/以下コマンドを実行してDockerイメージをビルド
VScodedocker-compose build以下を実行して、必要なノードモジュールを取得
VScodedocker-compose run web yarn install --check-files以下を実行して DB(Postgres)を作成
VScodedocker-compose run web rake db:create別のターミナルを開く
以下を実行してレールズアプリケーションを起動VScode#別ターミナルが億劫なら、docker-compose up -dでも良い docker-compose upさらに別のターミナルを開く
Viewを更新するたび毎回コンパイルが発生し、時間がかかるため以下を実行VScodedocker-compose exec web ./bin/webpack-dev-serverWebブラウザで以下にアクセス
Webブラウザhttp://localhost:3000Railsアプリケーションが起動することを確認
VueとRailsの連動ってどうやるの?
初期構築時点ではRailsとVueが連動されておりませんので連動させていく。
以下でhomeコントローラを作成する(コントローラー名はなんでも良い)。
VScodedocker-compose exec web rails g controller home indexindex.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.rbRails.application.routes.draw do root to: 'home#index' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html endWebブラウザで以下にアクセス
Webブラウザhttp://localhost:3000まずはindex.html.erbに記述されている内容が表示
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.jsimport 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:3000Vueとの連動が完了!!
Githubののファイルの説明
Whiro0501/Docker_Rails6_Vue
まず上記リンクのGithubの状態から説明する
端的に説明すると以下2つを設定した後の状態となる
従ってRails6とVueとPostgresが使用できる環境を整えることができるという理屈である。VScodedocker-compose run web rails new . --force --database=postgresql --webpack=vue加えて上記の状態だとPostgresがうまくRailsと連動できないため以下のように設定する。
こちらも公式ドキュメントを参考とした。database.ymldefault: &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はできるだけまとめると少ないレイヤーで収めることができるとのこと。DockerfileFROM 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.ymlversion: '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: trueGemfile
Gemfileに関してはデフォルトで入っているもの主となる。
追加したパッケージはコメントしているため不要であれば削除して構わないGemfilesource '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上のターミナルを開く
以下をターミナルで実行VScodedocker-compose exec web rails consolepry が起動
VScode[1] pry(main)>postコントローラーを作成
docker-compose exec web rails g controller post indexpost_controller.rbを修正
post_controller.rbclass PostController < ApplicationController def index @post = Post.all end endpostのモデルを作成
VScodedocker-compose exec web rails g model post name:string age:integerDBにモデルを反映させる
VScodedocker-compose exec web rails db:migrateindex.html.erbを以下に書き換え
index.html.erb<%= @post.name %> <%= @post.age %>routes.rbを以下に修正
routes.rbRails.application.routes.draw do root to: 'post#index' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html endpryで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
Binding.pryを試してみる
post_controller.rbを修正
class PostController < ApplicationController def index binding.pry @post = Post.all end endWebブラウザで以下にアクセス
Webブラウザhttp://localhost:3000pryのターミナルに戻る
pry(main)>以上で、pryでデバッグする環境が整った
参考
以下のサイトを参考にさせていただきました。
Docker ドキュメント日本語化プロジェクト
クィックスタート: Compose と Rails
Dockerfile のベストプラクティス
Webpacker の基本的な仕組み
Dockerを使って「Rails / PostgreSQL」の開発環境を作ろう!
- 投稿日:2020-09-23T21:30:54+09:00
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
VScodeGithubのリンク
僕のGithubのDocker-Start-Kitです。よろしければご活用ください。
Whiro0501/Docker_Rails6_VueDocker環境をMacに構築
ターミナルを開く
mkdirで任意の名前のディレクトリを作成
cdで作成したディレクトリに移動
code.でVScodeを開くterminalmkdir qiita cd qiita code .
VScodeが起動
VScode上のターミナルで作業していくので
ターミナルが起動していなければ⬆︎+Control+@でターミナルを開くVScodeのターミナル上で以下コマンドを入力してリモートリポジトリのファイルをローカルにコピー
VScodegit clone https://github.com/Whiro0501/Docker_Rails6_Vue.gitcdでDocker_Rails6_Vue/ディレクトリに移動
VScodecd Docker_Rails6_Vue/以下コマンドを実行してDockerイメージをビルド
VScodedocker-compose build以下を実行して、必要なノードモジュールを取得
VScodedocker-compose run web yarn install --check-files以下を実行して DB(Postgres)を作成
VScodedocker-compose run web rake db:create別のターミナルを開く
以下を実行してレールズアプリケーションを起動VScode#別ターミナルが億劫なら、docker-compose up -dでも良い docker-compose upさらに別のターミナルを開く
Viewを更新するたび毎回コンパイルが発生し、時間がかかるため以下を実行VScodedocker-compose exec web ./bin/webpack-dev-serverWebブラウザで以下にアクセス
Webブラウザhttp://localhost:3000Railsアプリケーションが起動することを確認
VueとRailsの連動ってどうやるの?
初期構築時点ではRailsとVueが連動されておりませんので連動させていく。
以下でhomeコントローラを作成する(コントローラー名はなんでも良い)。
VScodedocker-compose exec web rails g controller home indexindex.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.rbRails.application.routes.draw do root to: 'home#index' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html endWebブラウザで以下にアクセス
Webブラウザhttp://localhost:3000まずはindex.html.erbに記述されている内容が表示
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.jsimport 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:3000Vueとの連動が完了!!
Githubののファイルの説明
Whiro0501/Docker_Rails6_Vue
まず上記リンクのGithubの状態から説明する
端的に説明すると以下2つを設定した後の状態となる
従ってRails6とVueとPostgresが使用できる環境を整えることができるという理屈である。VScodedocker-compose run web rails new . --force --database=postgresql --webpack=vue加えて上記の状態だとPostgresがうまくRailsと連動できないため以下のように設定する。
こちらも公式ドキュメントを参考とした。database.ymldefault: &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はできるだけまとめると少ないレイヤーで収めることができるとのこと。DockerfileFROM 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.ymlversion: '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: trueGemfile
Gemfileに関してはデフォルトで入っているもの主となる。
追加したパッケージはコメントしているため不要であれば削除して構わないGemfilesource '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上のターミナルを開く
以下をターミナルで実行VScodedocker-compose exec web rails consolepry が起動
VScode[1] pry(main)>postコントローラーを作成
docker-compose exec web rails g controller post indexpost_controller.rbを修正
post_controller.rbclass PostController < ApplicationController def index @post = Post.all end endpostのモデルを作成
VScodedocker-compose exec web rails g model post name:string age:integerDBにモデルを反映させる
VScodedocker-compose exec web rails db:migrateindex.html.erbを以下に書き換え
index.html.erb<%= @post.name %> <%= @post.age %>routes.rbを以下に修正
routes.rbRails.application.routes.draw do root to: 'post#index' # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html endpryで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
Binding.pryを試してみる
post_controller.rbを修正
class PostController < ApplicationController def index binding.pry @post = Post.all end endWebブラウザで以下にアクセス
Webブラウザhttp://localhost:3000pryのターミナルに戻る
pry(main)>以上で、pryでデバッグする環境が整った
参考
以下のサイトを参考にさせていただきました。
Docker ドキュメント日本語化プロジェクト
クィックスタート: Compose と Rails
Dockerfile のベストプラクティス
Webpacker の基本的な仕組み
Dockerを使って「Rails / PostgreSQL」の開発環境を作ろう!
- 投稿日:2020-09-23T17:08:56+09:00
Rails ページング機能 [gem kaminari]を導入する流れについてまとめてみた。
kaminari導入の仕方
1.Gemfileにkaminariを追加して保存
Gemfilegem '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件まで表示される。
4.kaminariにBootstrapを適用させる
Bootstrapをすでに導入していれば簡単にkaminariに適用させることができる。
$ rails g kaminari:views bootstrap3
これでapp/views/kaminariフォルダにBootstrap用のViewが生成され、Bootstrap用のテンプレートに適用される。
- 投稿日:2020-09-23T16:31:33+09:00
select_tag 選択値設定(include_blank => true)
select_tag 空白行設定(include_blank => true)時、
DBから検索された値をVALに設定しても、空白テキストが表示されている
対策:$("#selectbox").val(DBから検索値).trigger('change')include_blank => falseなら、
$("#selectbox").val(DBから検索値)
- 投稿日:2020-09-23T15:51:23+09:00
【解説】ポートフォリオ(NotePro)
URL
概要
デバイス内に散らばった情報を一つにまとめることできる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で吐き出すことが可能
投稿に対するいいね
・他ユーザーのいいと思った投稿にいいねができる
ゴミ箱機能
・不要投稿はゴミ箱に入れることができる
プロフィールの画像設定
・アイコンの設定ができる
追加実装予定
使用技術
・言語: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作成するにあたり定義したものたち
- 投稿日:2020-09-23T15:51:23+09:00
ポートフォリオ(NotePro)の解説
URL
概要
デバイス内に散らばった情報を一つにまとめることできる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で吐き出すことが可能
投稿に対するいいね
・他ユーザーのいいと思った投稿にいいねができる
ゴミ箱機能
・不要投稿はゴミ箱に入れることができる
プロフィールの画像設定
・アイコンの設定ができる
追加実装予定
使用技術
・言語: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'なるべく少なくとは思いつつ想定より多くなってしまいました、、
- 投稿日:2020-09-23T15:04:37+09:00
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 thewebpack -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.したがって自分で作ったら起動完了した。
- 投稿日:2020-09-23T14:37:52+09:00
Rails Kaminariでページネーション機能を作る
バージョン
・ruby 2.6.6
・rails 5.2.4.1gem 'kaminari' を実装
投稿機能を持ったアプリを作った際には、ページネーション機能はUX的にも必須です。
そこで、gemのKaminariを使って、ページネーション機能の実装を行います。まずGemfileにkaminariを追加して、bundle installを実行
Gemfilegem 'kaminari', '~> 1.2.0'ターミナルbundle installControllerを修正
すでに投稿機能を作成し、投稿一覧ページがあるのであれば、Controlerを修正する必要があります。
元々の自分の投稿一覧ページにおけるControllerは以下の通りです。controllers/posts_controller.rbdef index @posts = Post.all endこれらを次のように変更する。
controllers/posts_controller.rbdef index @posts = Post.page(params[:page]).per(10) endKaminariを追加したことで、各モデル上で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.ymlja: views: pagination: first: '最初' last: '最後' previous: '前' next: '次' truncate: '...'ここまでで日本語が適用されていないときは「config/application.rb」の中に config.i18n.default_locale = :ja を追加する。
全体的に見ると、以下のようになります。config/application.rbrequire_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以上で実装完了です。
うまくいかない時は再度サーバーの再起動をしてみてください。
皆さんの参考になれば幸いです。
- 投稿日:2020-09-23T13:38:34+09:00
気軽な.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.rbclass Article < ActiveRecord::Base has_many :comments endapp/models/comment.rbclass 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.rb
でincludes
をすることなのですが、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退治に役立てれば幸いです。
- 投稿日:2020-09-23T13:05:32+09:00
nilブロックなるものを知った
この記事について
独学でrailsを勉強しております。
新たに学べたことをアウトプットするだけの記事ですので、初学者の方にとっての記事です。nilブロックとは何か?
nilになる可能性のあるものにメソッドを使う場合はnilブロックを行うことで、エラーが発生しないようになる。
実際のコード
例えば、先に
title
を投稿できるようにしてすでにデータが作成された後に、expired_at
カラムを追加して新たにtitle
とexpired_at
を投稿してviewを確認しようとすると、前に作られたデータには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の可能性のあるカラム(今回の場合は
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-23T12:59:37+09:00
vhと%の違い
※主に自分の振り返り用です。
クレジットカード登録画面の下部に空白が生じるのをなんとかしたい。
写真ではわかりにくいですが、黄色で囲んだところに生じている空白を
無くしたいと思い奮闘しました。いろいろ調べて、htmlやbodyに
height: 100%;
を当てる方法を試したところ、他のページのビューにも影響が出てしまい
この方法は断念。。。このあと2時間ほど奮闘しましたが、
card_addクラスに
height: 100vh;
これであっさり解決。要素の高さ指定で%とvhの違いがよく理解できていなかったので
ここにたどり着くのに時間がかかったようです。vhと%の違い
【vh】
ページの表示領域に依存する。height: 100vh;と指定すれば、高さがページの表示領域いっぱいに表示される
【%】
親要素に依存する。height: 100%;と指定すれば親要素の大きさまで表示される。
- 投稿日:2020-09-23T12:29:53+09:00
No Method Errorについて
自分用のエラー解決録です。
フリマアプリ作成のチーム実装もいよいよ終盤。
各メンバーが開発したものをマスターにマージし、
自分のローカルにプルすると度々エラーが発生します。マイページからログアウトページ遷移しようとしたところ発生。
undefined method `each' for nil:NilClassという記述です。
場所はどこか?というと
user#showです。早速コントローラーを確認すると
class UsersController < ApplicationController def index @parents = Category.where(ancestry: nil) end def show end endshowの中身が空、、、
こちらに記述を追加class UsersController < ApplicationController def index @parents = Category.where(ancestry: nil) end def show @parents = Category.where(ancestry: nil) end endエラーを重ねることによって、エラー文から原因を自分で推測して
解決できるようになってきました。
自走力が少し身についてきたのかな?まだまだこれからですが、引き続き頑張ります!
- 投稿日:2020-09-23T12:03:06+09:00
MVCについて(自分なりにアウトプット)
そもそも何ってことから振り返ってみます!
MVCとは、モデル(Model)・ビュー(View)・コントローラー(Controller)の頭文字をとったもです。で、それ自体は何かと問われると、【Webフレームワークで一般的に取り入れられるアプリケーション設定を整理するための概念の一つ】とある記事で書かれていましたが、要は、railsなどのアプリケーションの処理を行うシステムの構造のことを言うと覚えておけば良いのかと思います。
では、なんでそれを使用するのかを調べました。
MVCの概念がない場合、あるプログラムを書いたディレクトリをどこで対応しているかなどがわからなくなります。そのディレクトリで記述したコードの破綻等が起き不具合やバグの原因にも繋がります。そのため、役割ごとに意味のある単位で機能を実装すると管理しやすくなるので、MVCに分割して実装することでそれらを防ぎ円滑にアプリケーションを構築できるので便利であると言う理由から使用されたとのことです。
要は、それらを機能している箱に、コードを書くだけでアプリケーションが動きますよ的なことだと認識してます?【MVC】のそれぞれの役割を調べていきます!
まずは、MVCの M から見ていきます!
Mとは、モデルのことを指します。
モデルは何の機能をもったところかと言うと、データベースとデータのやり取りを行う機能を実装したり、データベースからデータをプログラムで扱いやすい形式でデータを変換する役割を担っています。
データベースに、新規のデータを入れたい時や、データの更新を行いたい時、削除したい時などに機能の実装を行う箇所です。次にMVCの V を見ていきます!
Vとは、ビューのことを指します。
ビューは、データを文字上でレスポンスを行う機能をいい、HTMLを動的に生成する部分です。
要は、リクエストされた情報を、HTMLなどを利用し、可視化してコントローラーに渡す役割を持った箇所を指します。
※HTMLとは、webページ上で表示されている文字などを表示させるマークアップ言語です。ラストにMVCの C を見ていきます!
Cとは、コントローラーを指します。
コントローラーは、ルーティングから来たリクエストをモデルを介して情報の取得及び保存を行いビューからもらった(見た目が整った)ものをレスポンスとしてクライアントに返す役割を持った箇所です。
要は、モデルとの橋渡し・ビュートの橋渡し・クライアントの橋渡しの部分がコントローラーに当たります。
※ルーティングとは、クライアントからくる情報をコントローラに道筋を決めて渡す箇所を言います。これを画像にするとこうです(鬼の手書きなのでわかりにくいと思います?♂️)↓
まとめ
MVCの概念はアプリケーションを作る上では必須と言っても過言ではないもので、それらを理解するとスムーズにアプリケーションを作成することができるものだと言った認識でいます!
- 投稿日:2020-09-23T11:31:26+09:00
任意の場所から文字を取り出せるメソッド作成
【概要】
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!"が抜き出される点は注意しないといけません。
- 投稿日:2020-09-23T09:27:52+09:00
Rails 6で認証認可入り掲示板APIを構築する #18・終 user controllerの実装
←Rails 6で認証認可入り掲示板APIを構築する #17 管理者権限の追加
全18回に渡る連載も今回で終了です。
今回はuser controllerを作ります。
今までの集大成なので、例示するコードを見ずに作ってみることをオススメします。仕様
主に管理者がユーザー管理をする用途と、ユーザーが自分自身の更新削除をするための機能群として想定しています。
- #index 管理者のみ閲覧可能
- #show 自分自身か管理者のみ閲覧可能
- #create 管理者のみ作成可能
- #update 自分自身か管理者のみ更新可能
- #destroy 管理者のみ削除可能
手順
- users_controllerの作成
- user_policyの作成
- user_policyテストの実装
- user_policyの実装
- users_controllerテストの実装
- users_controllerの実装
という手順で進めてみます。
1. users_controllerの作成
実装例
コマンド叩くだけなので簡単ですね。$ rails g controller v1/usersrubocopに怒られないように微修正したファイル群がこちら。
app/controllers/v1/users_controller.rb# frozen_string_literal: true module V1 # # users controller # class UsersController < ApplicationController end endspec/requests/v1/users_request_spec.rb# frozen_string_literal: true require "rails_helper" RSpec.describe "V1::Users", type: :request do end2. user_policyの作成
実装例
コマンド叩いてファイル作ります。
$ rails g pundit:policy userrubocopに怒られないように微修正を加えたらとりあえず完成。
app/policies/user_policy.rb# frozen_string_literal: true # # userのポリシークラス # class UserPolicy < ApplicationPolicy # # scope # class Scope < Scope def resolve scope.all end end endspec/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 end3. 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.rbRails.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回に渡りご覧いただきありがとうございました。ぜひこれを元に、コメント機能やソーシャルログイン等の機能拡張していってください。
目次
【連載目次へ】