- 投稿日:2020-09-21T23:12:31+09:00
Rails 6 + MySQLの環境構築をDocker composeで作る
Dockerの公式サイトにはRailsアプリケーション用のDocker composeチュートリアルがあるが、少し情報が古くRails 6ではうまく動かなかったので、Rails 6で動かすための方法を載せておく。基本は公式チュートリアルの手順に従っているため、Rails 6用に変更したところを中心に補足を入れている。
DBはPostgresではなくMySQLを使う方法を載せておく。
プロジェクトディレクトリの準備
mkdir myrailsapp cd myrailsapp設定ファイルの準備
DockerfileFROM ruby:2.5 ## nodejsとyarnはwebpackをインストールする際に必要 # yarnパッケージ管理ツールをインストール RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ 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 && apt-get install -y yarn RUN apt-get update -qq && apt-get install -y nodejs yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]Rails 6ではWebpackerの実行のためにyarnが必要になるのでDockerfileにyarnのインストールの手順を加える。
ruby:2.5
のイメージで普通にapt-get yarn
でインストールすると0.32+git
という変なバージョンがインストールされてしまい後にエラーになるので、yarn公式サイトの指示にしたがってレポジトリを追加した後でapt-get install yarn
する。参考: https://qiita.com/shunichi_com/items/4dca141d8b9342c51a04
postgresは使用しないため除外。
Gemfilesource 'https://rubygems.org' gem 'rails', '~>6'GemfileでRails 6系を指定。このGemfileはRailsプロジェクト作成後にRailsプロジェクトの内容で上書きされることになる。
touch Gemfile.lock
entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"この辺りは公式のサンプルのままなので説明省略。
docker-compose.ymlversion: '3' services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root ports: - "3306:3306" volumes: - ./tmp/db:/var/lib/mysql web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - dbMySQLを使う形に設定を変更。
Railsプロジェクトの作成
docker-compose run web rails new . --force --no-deps --database=mysql
--database=mysql
でDB設定をMySQLにした状態でプロジェクト作成。docker-compose buildDB接続設定
database.ymldevelopment: <<: *default database: myapp_development host: db username: root password: password # Warning: The database defined as "test" will be erased and # re-generated from your development database when you run "rake". # Do not set this db to the same as development or production. test: <<: *default database: myapp_test host: db username: root password: password
development
とtest
がdocker-composeで起動したMySQLイメージdb
を使うように設定を記述。DB作成
docker-compose run web rails db:createWebpackerの導入
docker-compose run web rails webpacker:installRails 6からSprocketsに代わりWebpackerが使われるようになったためこのステップが必要に。
イメージの起動
docker-compose upScaffold
docker-compose run web rails g scaffold article title:string body:text published_at:timestamp docker-compose run web rails db:migratedocker-composeを使って開発をする場合は、scaffold含めgenerate系のコマンド、マイグレーションも
docker-compose run web
で実行する。docker-compose up
- 投稿日:2020-09-21T22:04:08+09:00
Rails 6で認証認可入り掲示板APIを構築する #16 policyの設定
←Rails 6で認証認可入り掲示板APIを構築する #15 pundit導入
post_policyの編集
まずは
spec/spec_helper.rb
を以下のように変更します。
punditの公式にあるように、以下の通り追加すればpundit用のrspecメソッドが使えるようになります。spec/spec_helper.rbRSpec.configure do |config| ... + require "pundit/rspec" end次に
spec/policies/post_policy_spec.rb
にテストを組み込んでいきます。spec/policies/post_policy_spec.rb# frozen_string_literal: true require "rails_helper" RSpec.describe PostPolicy, type: :policy do let(:user) { create(:user) } let(:post) { create(:post) } subject { described_class } permissions :index?, :show? do it "未ログインの時に許可" do expect(subject).to permit(nil, post) end end permissions :create? do it "未ログインの時に不許可" do expect(subject).not_to permit(nil, post) end it "ログインしている時に許可" do expect(subject).to permit(user, post) end end permissions :update?, :destroy? do it "未ログインの時に不許可" do expect(subject).not_to permit(nil, post) end it "ログインしているが別ユーザーの時に不許可" do expect(subject).not_to permit(user, post) end it "ログインしていて同一ユーザーの時に許可" do post.user = user expect(subject).to permit(user, post) end end endなんとなく読み解けると思いますが、念の為解説を。
permissions :index?, :show? do it "未ログインの時に許可" do expect(subject).to permit(nil, post) end endindex?とshow?は条件が同じのためまとめてテストをしています。
permit(nil, post)
は第1引数にログインユーザーmodelを、第2引数に対象modelを指定します。
すると第1引数のユーザーが第2引数のオブジェクトのindex?やshow?の権限があるかテストをします。permissions :update?, :destroy? do ... it "ログインしているが別ユーザーの時に不許可" do expect(subject).not_to permit(user, post) end it "ログインしていて同一ユーザーの時に許可" do post.user = user expect(subject).to permit(user, post) end endnot_toは見たままですが、許可されていないことのテストですね。
そして、postの所有ユーザーを一致したことで最後のテストはパスします。request specの修正
一旦rspecを動かしてみます。
するとspec/requests/v1/posts_request_spec.rb
が結構コケます。原因は上記と同じく、#updateや#destoryが所属ユーザーのログインじゃないと403になるからです。
ですが、posts_request_spec.rb
で使っているauthorized_user_headers
ヘルパは内部でcreate(:user)
をしているため、ログインユーザーとpostユーザーを一致できません。そのため、以下のように修正を加えます。
spec/support/authorization_spec_helper.rbmodule AuthorizationSpecHelper - def authorized_user_headers - user = create(:user) + def authorized_user_headers(user = nil) + user = create(:user) if user.nil? post v1_user_session_url, params: { email: user.email, password: "password" }これで、
authorized_user_headers
に引数無しで渡した場合は内部でuserが作られ、引数でuserを渡した場合はそれを利用します。ただし
authorized_user_headers
が少し複雑になってしまいrubocopのAbcSizeに引っかかるので、以下の対応をします。.rubocop.yml... + +# AbcSize デフォルト15はキツいので20に上げる +Metrics/AbcSize: + Max: 20
さて、ようやくrequest specの修正です。
spec/requests/v1/posts_request_spec.rb... it "正常レスポンスコードが返ってくる" do - put v1_post_url({ id: update_param[:id] }), params: update_param + post = Post.find(update_param[:id]) + put v1_post_url({ id: update_param[:id] }), params: update_param, headers: authorized_user_headers(post.user) expect(response.status).to eq 200 end it "subject, bodyが正しく返ってくる" do - put v1_post_url({ id: update_param[:id] }), params: update_param + post = Post.find(update_param[:id]) + put v1_post_url({ id: update_param[:id] }), params: update_param, headers: authorized_user_headers(post.user) json = JSON.parse(response.body) expect(json["post"]["subject"]).to eq("update_subjectテスト") expect(json["post"]["body"]).to eq("update_bodyテスト") end it "不正パラメータの時にerrorsが返ってくる" do - put v1_post_url({ id: update_param[:id] }), params: { subject: "" } + post = Post.find(update_param[:id]) + put v1_post_url({ id: update_param[:id] }), params: { subject: "" }, headers: authorized_user_headers(post.user) json = JSON.parse(response.body) expect(json.key?("errors")).to be true end @@ -106,13 +109,13 @@ RSpec.describe "V1::Posts", type: :request do create(:post) end it "正常レスポンスコードが返ってくる" do - delete v1_post_url({ id: delete_post.id }) + delete v1_post_url({ id: delete_post.id }), headers: authorized_user_headers(delete_post.user) expect(response.status).to eq 200 end it "1件減って返ってくる" do delete_post expect do - delete v1_post_url({ id: delete_post.id }) + delete v1_post_url({ id: delete_post.id }), headers: authorized_user_headers(delete_post.user) end.to change { Post.count }.by(-1) endそこまで大きな変更は無いですね。
authorized_user_headers
にpostの所有ユーザーを渡すことで、認可を通過します。所有ユーザー一致判定メソッドの作成
ここをもう少し直感的に変えていきます。
自分自身のものか判定する処理はpostに限らず、今後もいろいろなmodelで流用しそうですよね。def update? @record.user == @user end def destroy? @record.user == @user endそのため、application_policy.rbに自分自身のものか判定するプライベートメソッドを作ります。
app/policies/application_policy.rbclass ApplicationPolicy ... + private + + def mine? + @record.user == @user + end # # scope # class Scope ...post_policyに反映してみます。
app/policies/post_policy.rbdef update? - @record.user == @user + mine? end def destroy? - @record.user == @user + mine? endスッキリしましたね。
これで、今後はuserの関連を持つmodelが自身が所有している場合のみ実行するactionは、policyファイルを作ってmine?
メソッドを配置するだけ。超お手軽ですね。続き
- 投稿日:2020-09-21T21:05:27+09:00
"Rails"でのフォーマット環境を整える(VScode)
この記事を書いたきっかけ
Railsで開発を進めるにあたり、VScodeで開発を進めていたのですが、
Rubocopだけではなかなか思っていた通りにフォーマットが動作せず、四苦八苦しました。
Railsでの環境下では最低限.rbファイルと.erbファイルのフォーマットが必要となります。
さらに他の言語(JavaScript,yaml等)へフォーマッター対応も拡張できる方が便利です。他エディターはどうか知りませんが、VScodeでの構築はポイントがあるように感じます。
初心者ゆえに間違えがありましたが、やさしくご指摘頂ければと思います。
この記事の目的
- 安定したRailsのコーディング環境(Ruby,erbのフォーマット環境)を整える
- 解析ツール・フォーマッターであるRubocopと共に使用できる
- 他の言語の拡張も可能なフォーマッターの導入と安定稼働
検証環境
Rails v6.0.3.3
Ruby v2.6.6
Rubocop v0.89.1・拡張機能
Ruby v0.27.0
Prettier v5.6.0
Prettier+ v4.2.2
Beautify v1.5.0・インストール
prettier/plugin-ruby v0.20.01.VScodeの設定(setting json)
フォーマットを整える前にエディターに下記の設定を入れてあります。
エディターの設定画面はCommand + ,
で表示できます。
settings.jsonのファイルを開いて下記の設定を入れ込みます。
settings.jsonは設定画面右上にあるアイコンに合わせると"設定(JSON)"と表示されるアイコンです。
アイコンをクリックして下記設定を入れると
- 末尾の空白を除去し、最終行に新しい行を入れる設定
- 自動フォーマット
が反映されます。
settings.json{ "files.trimFinalNewlines": true, #ファイルの保存時に最終行以降の新しい行をトリミング "files.insertFinalNewline": true, #ファイルの保存時に最新の行を末尾に追加 "files.trimTrailingWhitespace": true, #末尾の空白をトリミング "editor.formatOnSave": true, #ファイル保存時に自動フォーマット }1.Rubyのフォーマット環境
手順としては、まずgemでrubocopがインストールされている前提で、
VScodeの拡張機能で
- Prettier
- Prettier+
をインストールします。*忘れず2つともインストールしてください。その後にrailsの開発プロジェクト内でprettierのプラグインをインストールします。
フォーマッターのPrettier,Prettier+は標準ではRubyに対応しておらず、プラグインでの対応です。
早速ですが、公式サイトに記載ある通りプラグインのインストールと実行を行ってみます。
公式サイト:plugin-ruby私の環境はRails6ですので、yarnでインストールしました。
yarn add --dev prettier @prettier/plugin-ruby`Rails環境下の一斉実行は下記のコマンドで実行できます。
./node_modules/.bin/prettier --write '**/*.rb'これで既存のRubyファイルのフォーマットが完了しました。
保存後、エディター、Dockerなど関連ソフトは全て再起動を行ってください。再起動後に何らかのrbファイルを開いてエディター右下にPrettier,Prettier+のチェックが入っている事を確認ください。
その上で、ファイルに空白文字やいくつか改行などを入れてから保存しフォーマットが実行されるか確認してみてください。
うまく実行できればRubyのフォーマット設定は完了です。2.erbのフォーマット
拡張機能のbeautifyをインストールします。
Prettierがあるのになんで? という意見があると思うのですが、
実際にそれでも構わないのですが、下記のように設定した場合、一部のレイアウトが削除されてerbが分かりづらくなります。
これはerbがhtmlで認識されている為に発生します。Prettier サンプル例
settings.json"files.associations": { "*.html.erb": "html" }Prettier での表示例(ハイライトが消えてわかりづらい)
beautifyを拡張機能でインストールしてsettings.jsonで下記の通りに設定を入れます。
"beautify.language": { "html": ["htm", "html", "erb"] },これでhtmlのフォーマットがerbにも適応されつつ、erbのハイライトが表示されてわかりやすく作業ができます。
3.JavaScriptのフォーマット
JavaScriptのフォーマット設定はPrettierの設定をjsonに反映するだけでOKです。
settings.json"[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" },追記事項
VScodeでのRailsフォーマット環境の一例を紹介しました。
各種設定詳細は割愛しましたので、細かな設定については公式サイト等を参照頂ければと思います。
上記設定でうまく動かない場合は他の拡張機能が邪魔をしている場合がありますので、
拡張機能をいったん無効にして動作確認してみて下さい。最後に
私の構築した環境がベストな構築方法とは思いませんが、
Rails環境を構築の一例として、構築したフォーマット環境が他の方に少しでも参考になれば幸いです。
- 投稿日:2020-09-21T21:01:29+09:00
Ruby on Rails バリデーションまとめ
はじめに
主にRails Tutorialの備忘録として、例などをまとめてみます。
間違い等ありましたらご指摘いただけますと幸いです。バリデーションとは
オブジェクトをデータベースに保存する前に、オブジェクトの状態を検証することです。
入力された値が無効ではないことを検証します。
例えば、空のデータが保存されないようにしたり、文字数に制限を儲けたりすることができます。基本的な書き方
基本的な記入方法は以下です。
modelvalidates :カラム名, ヘルパー #複数のカラムに適用したい場合 validates :カラム名, :カラム名, :カラム名, ヘルパー主なバリデーションのヘルパー
presence
指定された属性が空でないことを確認します。
modelvalidates カラム名, presence: truemodelvalidates :name, presence: true #複数のカラムに適用したい場合 validates :name, :login, :email, presence: truelength
属性の値の長さを検証します。多くのオプションがあり、さまざまな長さ制限を指定できます。
modelvalidates カラム名, length: { 制限 }modelvalidates :passward, length: { minimum: 5 } #5文字以上 validates :name, length: { maximum: 50 } #20文字以下 validates :passward, length: { in: 3..10 } #3文字以上10文字以下 validates :registration_number, length: { is: 6 } #6文字のみ許可uniqueness
属性の値が一意(unique)であり重複していないことを検証します。
modelvalidates :カラム名, uniqueness: truemodelvalidates :name, uniqueness: trueacceptance
フォームが送信された時に、チェックボックスがオンになっているかどうかを検証します。
「サービスの利用規約に同意する」にチェックする必要がある場合などに利用されます。modelvalidates :カラム名, acceptance: truemodelvalidates :terms_of_service, acceptance: trueconfirmation
複数のフォームで入力された値が完全に一致するかどうかを検証します。
メールアドレスと確認用メールアドレスの値の一致などに利用されます。modelvalidates :カラム名, confirmation: truemodelvalidates :email, confirmation: truenumericality
属性に数値のみが使われていることを検証します。
modelvalidates カラム名, numericality: truemodelvalidates :points, numericality: truemodel#整数のみ許可 validates :age, numericality: { only_integer: true }主なオプション
オプション 概要 :only_integer 整数でなけえればならない :equal_to 指定された値と等しくなければならない format
withオプションで与えられた正規表現と属性の値がマッチするかどうかのテストによる検証を行います。
modelvalidates :カラム名, format: { with: 制限 }model#英数字のみ許可 validates :password, format: { with: /\A[a-zA-Z]+\z/ } #有効なメールアドレスのみ許可 VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i validates :email, format: { with: VALID_EMAIL_REGEX }上記のような、有効なメールアドレスのみ許可するための正規表現は以下のような表に基づいています。
正規表現 概要 z\d-.]+.[a-z]+\z/i 完全な正規表現 / 正規表現の開始を示す \A 文字列の先頭 [\w+-.]+ 英数字、アンダースコア(_)、プラス(+)、ハイフン(-)、ドット(.)のいずれかを少なくとも1文字以上繰り返す @ アットマーク [a-z\d-.]+ 英小文字、数字、ハイフン、ドットのいずれかを少なくとも1文字以上繰り返す . ドット [a-z]+ 英小文字を少なくとも1文字以上繰り返す \z 文字列の末尾 / 正規表現の終わりを示す i 大文字小文字を無視するオプション 共通のオプション
1 2 :message バリデーション失敗時に表示されるエラーメッセージを指定できます。指定しない場合、デフォルトのメッセージが表示されます。 :allow_nill 対象の値がnilの場合にバリデーションをスキップします。 :allow_blank 属性の値がblank?に該当する場合(nilや空文字など)にバリデーションがパスします。 model# メッセージを直書きする場合 validates カラム名, presence: { message: “カスタムエラーメッセージ” }modelvalidates :name, presence: { message: “必須項目です” }参考
Active Record バリデーション - Railsガイド v6.0
https://railsguides.jp/active_record_validations.html
- 投稿日:2020-09-21T19:15:39+09:00
【Reactで】小説投稿サイトなどに良くある「全○話」みたいなのを表示させる方法
はじめに
今回は小説投稿サイトなどによくある「全○話」を自作アプリにて表示させるのに結構苦労した(3日かかった)ので、戒めとして残しておきたいと思います。
実現したかったこと
用意したのは
「シリーズ」
というフォルダ的な役割を持つモデルと、「アイテム」
というシリーズに複数個格納されるモデルの2つ(1対多の関係)
ルートページ
にて「シリーズ」全件を表示させ、その「シリーズ」が所有する「アイテム」を全て取得し、その総数をカウント
させ「全〜件」という形で表示
させたい。苦労した理由
- 表示させたいのが
ルートページ
だったからです。普通なら各シリーズが所有するアイテム
を取得しようとする場合、例えばURLが"/series/104"
なら、シリーズのパラメータ(この場合なら104)を取得して、そのパラメータを頼りにアイテムを取得します。なので、パラメータが存在しないルートページでどうやって各シリーズのパラメータを取得すりゃええんじゃいとと半ばキレかけながら考えていたわけです(今思えば単純な話でした)
環境・前提等
環境
フロントエンド
- React(v16.8以上)
- React Hooks(カスタムフックを使う)
- axios
バックエンド
- Rails(5.2系)
前提
- CORSの設定、モデル作成などの工程は省略します。
- PUMAでRails側のローカルホストをデフォルトで3001に指定しています。
Rails側
- モデル
コントローラ
Api::V1::SeriesController
- このコントローラにてシリーズ全件を返すアクションと、アイテムのカウントを返すアクションを作成する。
ルーティング
- ルート:
"/"
→"api/v1/series#index"
- アイテム取得:
"api/v1/item_count/:series_id"
→"api/v1/series#item_count"
React側
- 用意するコンポーネント
Homeコンポーネント
: シリーズを全件取得し、Seriesというコンポーネントに各データを順繰り渡す役割りを持たせる。Seriesコンポーネント
: このコンポーネントにて各シリーズを表示させる。ItemCountコンポーネント
: 各シリーズが持つアイテムの総数だけを表示させる。useFetchカスタムフック
: Railsからデータを取得する。Rails側のコード
ルーティング
routes.rbRails.application.routes.draw do # ルート root to: 'api/v1/series#index' # アイテムのカウント get 'api/v1/item_count/:id', to: 'api/v1/series#item_count' endコントローラ
app/controller/api/v1/series_controller.rbclass Api::V1::SeriesController < ApplicationController # item_countアクションに、パラメータから取得したシリーズをコールバック before_action :set_series, only: [:item_count] def index @series =Series.all render json: { status: 200, series: @series, keyword: "index_of_series" # React側で使う } end def item_count @items = @series.items.all # シリーズに関連付けられているアイテムの取得 @items_count = @items.count # アイテムの総数をカウント render json: { status: 200, item_count: @item_count, # カウントをJSONとしてReactへ送信 keyword: "item_count" # React側で使う } end private # パラメータを頼りにシリーズを取得 def set_series @series = Series.find(params[:id]) end endReact側のコード
// 階層 //src // ├ Home.js // ├ Series.js // ├ ItemCount.js // └ useFetch.jsuseFetchカスタムフック
src/useFetch.jsimport { useState, useEffect } from "react" import axios from 'axios' // カスタムフックでは文頭はuseが必須 // useFetchの引数に、methodとurlを渡す // これは、HomeとItemCountコンポーネントにて、Railsとの通信に使う // HTTPリクエストと、ルーティングを指定するため export default function useFetch({method, url}) { // 初期値の定義。 const [items, setItems] = useState("") useEffect(() => { const getItems = () => { // ここのmethodとurlにて、Home・ItemCountコンポーネントから // 送られてくるメソッドとルーティングを代入することになる。 axios[method](url) .then(response => { let res = response.data let ok = res.status === 200 // シリーズ全件取得 // Rails側で指定したkeywordはここで使う。 // そうしてカウントとの区別を付けている。 if (ok && key === 'index_of_series') { setItems({ ...res.series }) // シリーズごとのアイテムの総数を取得 } else if (ok && key === 'item_count') { setItems(res.item_count) } }) .catch(error => console.log(error)) } getItems() }, [method, url, items]) return { items // items変数を他のコンポーネントで使えるようにする。 } }Homeコンポーネント
src/Home.jsimport React from 'react' import Series from './Series' import useFetch from './useFetch' function Home() { // ここでは、useFetchからRailsで取得したシリーズのデータを受け取っている。 // methodはget、urlはRailsのルートのURLを指定。これにより、 // useFetchからRailsのルートのルーティングへリクエストが送信され、 // その後Railsから受け取ったデータをitemsへ格納します。 const { items } = useFetch({ method: "get", url: 'http://localhost:3001' }) return ( <div> {/* Object.keys()メソッドを使い、JSONで送られてくるitemsを */} {/* ループ処理で1個ずつSeriesコンポーネントに渡している。 */} {/* JSONは、{ {...}, {...}, {...} }のようなものであると想定 */} {Object.keys(items).map(key => ( <Series key={key} items={items[key]} /> ))} </div> ) } export default HomeSeriesコンポーネント
src/Series.jsimport React from 'react' import ItemCount from './ItemCount' function Series(props) { // Homeから送られてくるpropsを頼りに、各シリーズのidをここで取得しています。 // このidをパラメータとして使うことで、各シリーズの所有するアイテムにアクセスすることができます。 const seriesId = props.items.id const seriesTitle = props.items.title return ( <div> <div>{seriesTitle}</div> {/* ItemCountコンポーネントに、シリーズのidを渡す。 */} <ItemCount {...props} seriesId={seriesId} /> </div> ) } export default SeriesItemCountコンポーネント
src/ItemCount.jsimport React from 'react' import useFetch from './useFetch' function SeriesCount(props) { // useFetchを使いRailsと通信。 // methodはget、urlはRailsの`api/v1/item_count/${props.seriesId}`を指定。 // id部分にSeriesコンポーネントから渡ってくる各シリーズのidを嵌め込むことで、 // Railsの"api/v1/item_count/:id"というルーティングへリクエストが送信され、 // その後Railsから各シリーズの持つアイテムのカウント数を受け取り、最後にitemsへ格納されます。 const { items } = useFetch({ method: 'get', url: `http://localhost:3001/api/v1/item_count/${props.seriesId} ` }) return ( <div> {/* Railsから送られてくるアイテムの総数をここにレンダリングします。 */} (このシリーズは全部で {items} 個のアイテムを所有しています) </div> ) } export default SeriesCount
- 投稿日:2020-09-21T18:24:32+09:00
rails チュートリ
Rubyのバージョン番号
ところで、Herokuのデプロイするとき、もしかしたら次のような警告メッセージを目にしたことがあるかもしれません。WARNING:
You have not declared a Ruby version in your Gemfile. To set your Ruby version add this line to your Gemfile: ruby '2.1.5'(これは「Rubyのバージョンを明示的に指定してください」というメッセージですが) 経験的には、本書のようなチュートリアルの段階では明示的に指定しない方がスムーズに進むことが多いので、この警告は現時点では無視してしまった方がよいでしょう。というのも、サンプルアプリケーションでRubyのバージョンを常に最新に保っておくと、多大な不都合に繋がりかねないからです15 。また、本書のサンプルアプリケーションにおいては、ローカルで使っているバージョンと本番環境のバージョンが異なっていても、違いが生じることはほぼ無いでしょう。とは言うものの、次の点は頭の片隅に置いておいてください。それは、仕事でHerokuを使ったアプリケーションを動かす場合はGemfileでRubyのバージョンを明示しておいた方が賢明である、という点です。これによって開発環境と本番環境の互換性を最大限に高めることができるので、(バージョンの差異による誤作動やエラーなどが無くなり) オススメです。
- 投稿日:2020-09-21T15:47:32+09:00
font-awesomeとbootstrap(4.5.1)の導入
各種バージョン
Ruby: 2.6.5
Rails: 6.0.0
Bootstrap: 4.5.1
Font-Awesome(Free): 5.7.2app/views/layouts/application.html.erb<!DOCTYPE html> <html> <head> <title>boot_and_fontawesome_app</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/3.18.1/build/cssreset/cssreset-min.css"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> <script src="https://kit.fontawesome.com/d9fcea61b7.js" crossorigin="anonymous"></script> </head> <body> <%= yield %> <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.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script> </body> </html>上記を貼るだけ
以下はサンプルコード。app/views/index.html.erb<div class="container"> <div class="jumbotron m-3"> <h1 class="display-4">Hello, Bootstrap!</h1> <hr class="my-4"> <p>This is the template of Ruby on Rails using Bootstrap.</p> <button type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#exampleModal"> <i class="far fa-window-maximize"></i> Click here </button> </div> <!-- Modal --> <div class="modal fade" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="exampleModalLabel">Modal title</h5> <button type="button" class="close" data-dismiss="modal" aria-label="Close"> <span aria-hidden="true">×</span> </button> </div> <div class="modal-body"> Hello! </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> </div> </div> </div> </div> </div>すると以下のようなページを表示できます。
https://gyazo.com/2c476830157ce55b86000591254edc33
ブートストラップとfont-awesomeが適用できていることがわかります。参考 https://getbootstrap.jp/docs/4.5/getting-started/introduction/
- 投稿日:2020-09-21T15:18:11+09:00
[Rails]flashメッセージの使い方
はじめに
flashとは、ページ遷移した時に簡単なメッセージを一時的に表示させる機能です。
例えば、ユーザーがログインに成功した時に『ログインできました。』と表示させることで、ユーザーが進行具合を確認できるというものです。目次
1 基本的な書き方
2 flashとflash.nowの違い基本的な書き方
コントローラーの編集
flashはハッシュのような形で扱います。
flash[:キー名] = “表示さたいメッセージ”キー名はあらかじめ用意せれているnoticeかalertオプションを用います(自分で好きな名前をつけることも可能です)。
例
controlle.rbif @outgo.update(outgo_params) flash[:alert] = ‘メッセージが’登録されました。’ redirect_to root_path else ~ 略 ~ビューの編集
フラッシュメッセージを表示させたい箇所に下記を記述します。
<%= flash[:キー名] %>例
html.erb<%= flash[:alert] %>flashとflash.nowの違い
両者の使い分け
- flashは次のアクションが動いた後のビューファイルにflashメッセージを表示する時(redirect_toを用いた時)
- flash.nowは現在のアクションで表示するビューファイルのみ有効なflashメッセージを表示させたい時(renderを用いた時)
renderとredirect_toの挙動
- render : controller → view
コントローラーの直後にビューを表示しています。- redirect_to : controller → URL → route → controller → view
コントローラーの直後に一度ルーティングを経由してビューを表示しています。
なので
flashは最初のアクションが実行された後の1回のみflashメッセージが表示されるのでredirect_toメソッドと一緒に用います。
flash.nowは次のアクションが動く間表示されるのでrenderメソッドと一緒に用います。参考リンク
https://pikawaka.com/rails/flash
https://qiita.com/dice9494/items/2a0e92aba58a516e42e9
- 投稿日:2020-09-21T14:57:23+09:00
addEventListenerにより重複している処理を防ぐ方法
動作環境
Ruby 2.6.5
Rails 6.0.3.2以前、addEventListenerによって同じ処理が何度も発生していたことにより、エラーが起きてしまい、思うような処理ができないことがあったので、投稿してみました。
addEventListenerにより重複している処理が発生してしまう例
index.html.erb<% @hugas.each do |huga| %> <div class="huga" > <%= huga.content %> </div> <% end %>上記のコードは、hugaのcontentカラムを繰り返し表示させており、その表示されたcontentのclassはhugaであるということを表しています。
huga.jsfunction hoge() { const hugas = document.querySelectorAll(".huga"); hugas.forEach(function (huga) { huga.addEventListener("click", () => { //クリックすることで発生する処理 }); }); }; setInterval(hoge, 1000);function hoge()を1行目として、2行目からこのコードの解説をしていきます。
2行目でclassがhugaである要素をすべてhugasに代入しています。
3行目でhugasを1つずつに分けて、それらの名前をhugaとしています。
4行目でhugaをクリックすると5行目の処理を発生するようにしています。
最終行により、以上の動作を毎秒発生させています。つまり、上記の2つのコードはcontentをクリックすると、huga.jsの5行目に書かれた処理が発生するということを表しています。
しかし、このままではエラーが起きてしまいます。なぜなら、huga.jsの最終行により毎秒2行目と3行目の動作が発生しているからです。それによって何が起こるのかというと、例えばそのページに遷移してから10秒後にcontentをクリックした場合、5行目の処理が10回同時に発生してしまうというようなことが起きてしまいます。
この問題を解決するために、この記事のタイトルである「addEventListenerにより重複している処理を防ぐ方法」が必要となります。
※そもそもsetIntervalではなく、window.addEventListener('load',hoge);にすれば良いのではという意見が出ると思いますが、その通りです。しかし、index.html.erbのhuga.contentにおいて非同期通信が使われている場合はsetIntervalを使う必要があります。非同期通信だとページのロードが行われないからです。
addEventListenerにより重複している処理を防ぐ方法
huga.jsfunction hoge() { const hugas = document.querySelectorAll(".huga"); hugas.forEach(function (huga) { if (huga.getAttribute("baz") != null) { return null; } huga.setAttribute("baz", "true"); huga.addEventListener("click", () => { //クリックすることで発生する処理 }); }); }; setInterval(hoge, 1000);重複している処理は、上記のコードの4行目から7行目を追記することで防ぐことができる。なぜなら、この追記によって何秒経過してからcontentをクリックしても、1つのhugaには1回の処理しか行わないという意味になるからです。
追記した部分を詳しく解説していきます。
まず、1秒経過すると1回目の処理が行われ、if文によって条件分岐が起きます。hugaはbazという属性(Attribute)は持っていないのでnullとなり、条件式はfalseとなりますが、falseの場合の処理は記載されていないため、そのまま次の処理に移ります。7行目によって、hugaにbazという属性が与えられ、それはtrueとなります。つまり、1回目の処理は記載する前と変わっていません。2秒経過した場合を見ていきます。2秒経過すると2回目の処理が行われ、再度if文によって条件分岐が起きます。1回目と違い、hugaにはbazという属性を持っているため、nullではありません。そのため、条件式はtrueとなり、trueの場合の処理が行われ、return nullが実行されます。return nullとは、処理を抜け出すという意味なので、この記載以降の処理は行われなくなります。つまり、2秒経過してから、contentをクリックしても、処理が1回しか行われないようになります。
当然ですが3秒後以降も同じであるため、重複している処理を防ぐことができます。
- 投稿日:2020-09-21T14:37:53+09:00
railsでhamlを使えるようにする
背景
railsのチーム開発でhamlを使っていたが便利だったので個人開発でもを使いたくなったので試しに使えるようにしました。
やった事
1. gemをインストール
使用するgem
①haml-rails
<= hamlを使えるようにするgem
②erb2haml
<= erbファイルをhamlに一括変換してくれるgem1.1 Gemfileに以下を追加
# Gemfile gem 'haml-rails' gem 'erb2haml'1.2
bundle install
を実行2. erbファイルをhamlへ変換
rake haml:replace_erbs
をコンソールで実行する。すると以下のようにコンソールに表示されて、hamlへファイルが変換される。
user@samole:/myapp# bundle exec rake haml:replace_erbs Looking for ERB files to convert to Haml... Converting: app/views/hellos/index.html.erb... Done! Removing: app/views/hellos/index.html.erb... Removed!3.再起動
2.の段階でブラウザ表示してみるとエラーが出てしまう.
このエラーはrails s
でサーバを再起動すると解消されました。4. erb2haml を削除
erb2haml
はもう使わないのでGemfileから削除でOK.#Gemfile から以下を削除 gem 'erb2haml'
- 投稿日:2020-09-21T13:36:37+09:00
Ruby 配列やハッシュオブジェクトについてのざっくりメモ
Rubyの配列やハッシュオブジェクトについてのメモです。
配列の基礎的なことは、知っているのでruby特有の書き方をメモしていきたいと思います。配列に要素を追加するとき
num = ["one"] num << "two" p num # ["one", "two"]ハッシュオブジェクトを使っての、キーと値の定義
numKey = { one: 1, two: 2 } p numKey # {:one=>1, :two=>2} p numKey[:two] # 2%記法で配列の定義
# numbers = ["one", "two", "three"] 同じ意味 numbers = %W( one two three ) p numbers # ["one", "two", "three"]配列の中の要素を一つずつ取り出す
%w[one two three].each do |num| p num end # "one" # "two" # "three"元の配列に値を追加して、新しい配列を返す。
nums = %w[one two three four].map{ |num| "数字: #{num}" } p nums # ["数字: one", "数字: two", "数字: three", "数字: four"]
- 投稿日:2020-09-21T11:40:13+09:00
Rubyのエラーメッセージについて
1.はじめに
プログラミング初学者はエラーメッセージの読み解きにどうしても時間がかかってしまいます。とりあえず一番手っ取り早いエラーメッセージを全文コピーしてググる、というやり方をしがちです。しかし、ググればそれらしい情報にがいくつか引っかかりますが、絶対にそれが正しい情報だとは言い切れません。「プロを目指す人のためのRuby入門」で指摘されていますが、ググった結果を実行して、一見うまく解決できたように見えるものでも、実はセキュリティ的に重大な欠陥を生み出していたなんてことがあるそうです。なのでまずはエラーメッセージを読めるようになり、自身で問題点を抽出できるようになることが必要だと考えます。そうすればある程度すばやくバグやエラーを取り除ける上に、Rubyの構造を理解できるようになると考え、この記事を作成しました。
参考にしました→ プロを目指す人のためのRuby入門
2.バックトレースの読み方
プログラム実行中にエラーが発生すると、メソッドの呼び出し状況を表したデータを出力します。これをバックトレースと呼びます。ここではあえて間違った構文を入力しバックトレースを呼び出します。
irb(main):001:0> puts hoge Traceback (most recent call last): 5: from /(Rubyがインストールされているパス)/irb:23:in `<main>' 4: from /(Rubyがインストールされているパス)/irb:23:in `load' 3: from /(Rubyがインストールされているパス)/irb:11:in `<top (required)>' 2: from (irb):1 1: from (irb):2:in `rescue in irb_binding' NameError (undefined local variable or method `hoge' for main:Object)上記はirb(ターミナル上で実行できるRuby)で
puts hoge
という間違った構文を入力したために出力されたバックトレースです。hoge
をシングルクォート(')やダブルクォート(")で囲む等で文字列として認識できなかった為に出た至極単純なエラーです。一つ一つ丁寧に見ていきます。まず
Traceback (most recent call last):ですが、これはエラーメッセージではなく、訳するとトレースバック(最後の最新の呼び出し)、つまりRubyを実行してエラーまでの最新の実行過程を次に表示するよー、という文章です。そして次に
5: from /(Rubyがインストールされているパス)/irb:23:in `<main>' 4: from /(Rubyがインストールされているパス)/irb:23:in `load' 3: from /(Rubyがインストールされているパス)/irb:11:in `<top (required)>' 2: from (irb):1 1: from (irb):2:in `rescue in irb_binding'ですが、エラーまでの実行過程を5:〜1:の流れで表示しています。下に行くほどエラーに近くなっています(言い換えると最新に近くなる)。ここで5:〜3:までは、「irbのプログラムの実行過程」、つづく2:では「irbの1行目を実行」という意味になります。1:は調べましたが
rescue in irb_binding
の意味がわかりませんでした。「irbの2行目で補足がされているか」ということですかね?
つづいて、NameError (undefined local variable or method `hoge' for main:Object)こちらがエラーメッセージになります。まず
NameError
ですが、エラーメッセージの種類です。エラーメッセージの種類については後ほど説明します。続くundefined local variable or method `hoge' for main:Object
ですが、hoge
を文字列と認識できていないため、ローカル変数か定数から探しており、それが定義されていないという意味になります。
今回は単純なミスによるバックトレースなので5行ですんでいますが、複雑なものになると何十行もの実行課程を表示されることがあります。そういった場合でも、冷静に内容をよく読むことが必要です。3.主なエラーメッセージの種類
エラー名 概要 NameError 未定義のローカル変数や定数を使用したときに発生する NoMethodError 存在しないメソッドを呼び出そうとしたときに発生する SyntaxError ソースコードに文法エラーがあったときに発生する TypeError メソッドの引数に期待される型ではないオブジェクトや、期待される振る舞いを持たないオブジェクトが渡された時に発生する SystemStackError システムスタックがあふれたときに発生します。典型的には、メソッド呼び出しを無限再帰させてしまった場合に発生する LoadError require や load が失敗したときに発生する その他のエラーについては、公式リファレンス参照します。
4.エラーメッセージで出会ったときの対処方法
(1)行った手順を確認し、バックトレースを読み込む。
(2)デバッガを使用する。
(3)irbで簡単なコードを動かしてみる。
(4)ログを調べる。
(5)公式ドキュメントを読む。
(6)issueを検索する。
(7)外部ライブラリのコードを読む。
(8)誰かに聞く。
5.まとめ
初学者の私は大量のエラーメッセージを目にするとかなりやる気が削がれてしまいます。エラーメッセージに対しては冷静かつ堅実な対応が必要ですね。エラーに遭遇したときのために、事前に対処法は考えといたほうが良いかもしれないです。
- 投稿日:2020-09-21T11:39:11+09:00
入力した引数に対して、複数の戻り値を返したい
【概要】
1.結論
2.どのように記述するか
3.ここから学んだこと
1.結論
変数を複数用意し、returnで複数用意すること!
2.どのように記述するか
def calculation(num) no1 = (num + 100) * 100 no2 = (num + 10) * 200 return no1,no2 end num = gets_to.i a,b = calculation(num)上記のようにすると変数を複数用意(➡︎a,b)して、returnで複数用意(➡︎no1,no2)しました。そうすると、入力した引数(➡︎num)に対して、複数の戻り値(➡︎no1,no2)を返すことがきます。注意点としては変数は複数個用意した分の個数(左辺)分だけ代入する個数分を用意しないと"nil"になります。
参考にしたURL:
戻り値が複数ある場合の変数代入
3.ここから学んだこと
変数を複数代入できることは知っていましたが、変数の代入の仕方でもこんなに種類があると思いませんでした。
下記URLから多重代入のことについて載せておきます!参考にしたURL:
Rubyの多重代入あれこれまとめ
- 投稿日:2020-09-21T01:59:53+09:00
Access denied for user 'root'@'localhost' (using password: YES)のエラー
状況
Ruby on Rails 6.0.3.2
EC2へのデプロイ作業中のエラー
MySQLへのログインはできるので正しいパスワードは把握できている。DBの作成時にエラーが出る
本番環境で以下のコマンドでDB作成を試みる。
rails db:create RAILS_ENV=productionしかし以下のようなエラーが発生。
rails aborted! Mysql2::Error::ConnectionError: Access denied for user 'root'@'localhost' (using password: YES) /var/www/<アプリ名>/bin/rails:9:in `<top (required)>' /var/www/<アプリ名>/bin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' Tasks: TOP => db:create (See full trace by running task with --trace)何が問題なのか
MySQLへ接続時のパスワードが間違っている。
Access denied for user 'root'@'localhost' (using password: YES)上記の文の意味はパスワードは入力されているがパスワードが間違っているという意味である。
解決方法
環境変数の設定が間違っている可能性がある。
特にenvironmentファイルに記入した環境変数とパスワードを囲むシングルクォートが全角だとエラーになる。(シングルクォートを含んだパスワードと見なされるため)
環境変数とパスワードを表示するコマンドを入力した時、パスワードと環境変数がシングルクォートで囲まれているかどうかで確認できる(半角だとシングルクォートが出ない)。まとめ
タイトルのようなエラー文が表示された時はパスワードが間違っているので要確認。
- 投稿日:2020-09-21T00:15:54+09:00
Rails ルーティングの基礎
備忘録のために投稿。
環境
ruby 2.7.1
Rails 6.0.3.2Rails の導入は済んでいる前提です。
generate コマンドで必要なファイルを作成
bundle exec rails g controller <controller_name> <method_name> ↓ bundle exec rails g controller home index必要なファイルが作成される。削除したいときは delete コマンド
bundle exec rails d controller home indexroutes.rb
bundle exec rails routes or bundle exec rails routes | grep xxx (絞り込み)ディレクトリ内のルーティングの設定が見れる。
見るべきポイント
URI Pattern Controller#Action /articles/index(.:format) articles#index/articles/index にアクセスすると Controller articles の Action index に飛べる。rails s でサーバ立ち上げて localhost::3000 にアクセスすれば見れる。
Rails のルールとして Controller 名はそのまま View 側の階層構造になっている。例:HomeController(コントローラー名)
View のディレクトリ → home(ディレクトリ名)/index.html.erb(ファイル名)Controller から View への値の渡し方
インスタンス変数を使用する
class HomesController < ApplicationController def index # インスタンス変数 @message = "message" end end変数の前に @ を付ける事で、Controller 内で値をどこでも呼べる。View に変数の値を渡すこともできる。今回の例の場合、インスタンス変数 @message に入った文字列 "message" が渡される。
<h1>Homes#index</h1> <%= @message %>HTML で Ruby を呼び出したい場合、<% %> で使用できる。何か出力したい場合、<%= %> とイコールを付けると出力される。インスタンス変数 @message を渡しているので、文字列 "message" が HTML で表示される。