20200921のRailsに関する記事は15件です。

Rails 6 + MySQLの環境構築をDocker composeで作る

Dockerの公式サイトにはRailsアプリケーション用のDocker composeチュートリアルがあるが、少し情報が古くRails 6ではうまく動かなかったので、Rails 6で動かすための方法を載せておく。基本は公式チュートリアルの手順に従っているため、Rails 6用に変更したところを中心に補足を入れている。

DBはPostgresではなくMySQLを使う方法を載せておく。

プロジェクトディレクトリの準備

mkdir myrailsapp
cd myrailsapp

設定ファイルの準備

Dockerfile
FROM 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は使用しないため除外。

Gemfile
source '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.yml
version: '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:
      - db

MySQLを使う形に設定を変更。

Railsプロジェクトの作成

docker-compose run web rails new . --force --no-deps --database=mysql

--database=mysqlでDB設定をMySQLにした状態でプロジェクト作成。

docker-compose build

DB接続設定

database.yml
development:
  <<: *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

developmenttestがdocker-composeで起動したMySQLイメージdbを使うように設定を記述。

DB作成

docker-compose run web rails db:create

Webpackerの導入

docker-compose run web rails webpacker:install 

Rails 6からSprocketsに代わりWebpackerが使われるようになったためこのステップが必要に。

イメージの起動

docker-compose up

Scaffold

docker-compose run web rails g scaffold article title:string body:text published_at:timestamp
docker-compose run web rails db:migrate

docker-composeを使って開発をする場合は、scaffold含めgenerate系のコマンド、マイグレーションもdocker-compose run webで実行する。

docker-compose up

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

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

Rails 6で認証認可入り掲示板APIを構築する #16 policyの設定

Rails 6で認証認可入り掲示板APIを構築する #15 pundit導入

post_policyの編集

まずはspec/spec_helper.rbを以下のように変更します。
punditの公式にあるように、以下の通り追加すればpundit用のrspecメソッドが使えるようになります。

spec/spec_helper.rb
 RSpec.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
  end

index?と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
  end

not_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.rb
 module 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.rb
class ApplicationPolicy
...

+  private
+
+  def mine?
+    @record.user == @user
+  end

   #
   # scope
   #
  class Scope
...

post_policyに反映してみます。

app/policies/post_policy.rb
   def update?
-    @record.user == @user
+    mine?
   end

   def destroy?
-    @record.user == @user
+    mine?
   end

スッキリしましたね。
これで、今後はuserの関連を持つmodelが自身が所有している場合のみ実行するactionは、policyファイルを作ってmine?メソッドを配置するだけ。超お手軽ですね。

続き

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

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

"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.0

1.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に対応しておらず、プラグインでの対応です。
スクリーンショット 2020-09-05 16.22.48.png

スクリーンショット 2020-09-05 16.26.12.png

早速ですが、公式サイトに記載ある通りプラグインのインストールと実行を行ってみます。
公式サイト:plugin-ruby

私の環境はRails6ですので、yarnでインストールしました。

yarn add --dev prettier @prettier/plugin-ruby`

Rails環境下の一斉実行は下記のコマンドで実行できます。

./node_modules/.bin/prettier --write '**/*.rb'

これで既存のRubyファイルのフォーマットが完了しました。
保存後、エディター、Dockerなど関連ソフトは全て再起動を行ってください。

再起動後に何らかのrbファイルを開いてエディター右下にPrettier,Prettier+のチェックが入っている事を確認ください。
スクリーンショット 2020-09-05 17.01.41.png

その上で、ファイルに空白文字やいくつか改行などを入れてから保存しフォーマットが実行されるか確認してみてください。
うまく実行できればRubyのフォーマット設定は完了です。

2.erbのフォーマット

拡張機能のbeautifyをインストールします。
Prettierがあるのになんで? という意見があると思うのですが、
実際にそれでも構わないのですが、下記のように設定した場合、一部のレイアウトが削除されてerbが分かりづらくなります。
これはerbがhtmlで認識されている為に発生します。

Prettier サンプル例

settings.json
"files.associations": {
   "*.html.erb": "html"
}

Prettier での表示例(ハイライトが消えてわかりづらい)
スクリーンショット 2020-09-05 17.29.34.png


beautifyを拡張機能でインストールしてsettings.jsonで下記の通りに設定を入れます。

  "beautify.language": {
    "html": ["htm", "html", "erb"]
  },

スクリーンショット 2020-09-05 17.30.32.png

これでhtmlのフォーマットがerbにも適応されつつ、erbのハイライトが表示されてわかりやすく作業ができます。

3.JavaScriptのフォーマット

JavaScriptのフォーマット設定はPrettierの設定をjsonに反映するだけでOKです。

settings.json
"[javascript]": {
  "editor.defaultFormatter": "esbenp.prettier-vscode"
},

追記事項

VScodeでのRailsフォーマット環境の一例を紹介しました。
各種設定詳細は割愛しましたので、細かな設定については公式サイト等を参照頂ければと思います。
上記設定でうまく動かない場合は他の拡張機能が邪魔をしている場合がありますので、
拡張機能をいったん無効にして動作確認してみて下さい。

最後に

私の構築した環境がベストな構築方法とは思いませんが、
Rails環境を構築の一例として、構築したフォーマット環境が他の方に少しでも参考になれば幸いです。

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

Ruby on Rails バリデーションまとめ

はじめに

主にRails Tutorialの備忘録として、例などをまとめてみます。
間違い等ありましたらご指摘いただけますと幸いです。

バリデーションとは

オブジェクトをデータベースに保存する前に、オブジェクトの状態を検証することです。
入力された値が無効ではないことを検証します。
例えば、空のデータが保存されないようにしたり、文字数に制限を儲けたりすることができます。

基本的な書き方

基本的な記入方法は以下です。

model
validates :カラム名, ヘルパー

#複数のカラムに適用したい場合
validates :カラム名, :カラム名, :カラム名, ヘルパー

主なバリデーションのヘルパー

presence

指定された属性が空でないことを確認します。

model
validates カラム名, presence: true
model
validates :name, presence: true

#複数のカラムに適用したい場合
validates :name, :login, :email, presence: true

length

属性の値の長さを検証します。多くのオプションがあり、さまざまな長さ制限を指定できます。

model
validates カラム名, length: { 制限 }
model
  validates :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)であり重複していないことを検証します。

model
validates :カラム名, uniqueness: true
model
validates :name, uniqueness: true

acceptance

フォームが送信された時に、チェックボックスがオンになっているかどうかを検証します。
「サービスの利用規約に同意する」にチェックする必要がある場合などに利用されます。

model
validates :カラム名, acceptance: true
model
validates :terms_of_service, acceptance: true

confirmation

複数のフォームで入力された値が完全に一致するかどうかを検証します。
メールアドレスと確認用メールアドレスの値の一致などに利用されます。

model
validates :カラム名, confirmation: true
model
validates :email, confirmation: true

numericality

属性に数値のみが使われていることを検証します。

model
validates カラム名, numericality: true
model
validates :points, numericality: true
model
#整数のみ許可
validates :age, numericality: { only_integer: true }
主なオプション
オプション 概要
:only_integer 整数でなけえればならない
:equal_to 指定された値と等しくなければならない

format

withオプションで与えられた正規表現と属性の値がマッチするかどうかのテストによる検証を行います。

model
validates :カラム名, 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: “カスタムエラーメッセージ” } 
model
validates :name, presence: { message: “必須項目です” }

参考

Active Record バリデーション - Railsガイド v6.0
https://railsguides.jp/active_record_validations.html

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

【Reactで】小説投稿サイトなどに良くある「全○話」みたいなのを表示させる方法

はじめに

今回は小説投稿サイトなどによくある「全○話」を自作アプリにて表示させるのに結構苦労した(3日かかった)ので、戒めとして残しておきたいと思います。

実現したかったこと

  • 用意したのは「シリーズ」というフォルダ的な役割を持つモデルと、「アイテム」というシリーズに複数個格納されるモデルの2つ(1対多の関係)

  • ルートページにて「シリーズ」全件を表示させ、その「シリーズ」が所有する「アイテム」を全て取得し、その総数をカウントさせ「全〜件」という形で表示させたい。

苦労した理由

  • 表示させたいのがルートページだったからです。普通なら各シリーズが所有するアイテムを取得しようとする場合、例えばURLが"/series/104"なら、シリーズのパラメータ(この場合なら104)を取得して、そのパラメータを頼りにアイテムを取得します。

  なので、パラメータが存在しないルートページでどうやって各シリーズのパラメータを取得すりゃええんじゃいとと半ばキレかけながら考えていたわけです(今思えば単純な話でした)

環境・前提等

環境

  • フロントエンド

    • React(v16.8以上)
    • React Hooks(カスタムフックを使う)
    • axios
  • バックエンド

    • Rails(5.2系)

前提

  • CORSの設定、モデル作成などの工程は省略します。
  • PUMAでRails側のローカルホストをデフォルトで3001に指定しています。

Rails側

  • モデル

スクリーンショット 2020-09-21 16.29.12.png

  • コントローラ

    • 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.rb
 Rails.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.rb
class 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

end

React側のコード

// 階層

//src
//  ├ Home.js
//  ├ Series.js
//  ├ ItemCount.js
//  └ useFetch.js

useFetchカスタムフック

src/useFetch.js
import { 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.js
import 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 Home

Seriesコンポーネント

src/Series.js
import 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 Series

ItemCountコンポーネント

src/ItemCount.js
import 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

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

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のバージョンを明示しておいた方が賢明である、という点です。これによって開発環境と本番環境の互換性を最大限に高めることができるので、(バージョンの差異による誤作動やエラーなどが無くなり) オススメです。

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

font-awesomeとbootstrap(4.5.1)の導入

各種バージョン

Ruby: 2.6.5
Rails: 6.0.0
Bootstrap: 4.5.1
Font-Awesome(Free): 5.7.2

app/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">&times;</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/

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

[Rails]flashメッセージの使い方

はじめに

flashとは、ページ遷移した時に簡単なメッセージを一時的に表示させる機能です。
例えば、ユーザーがログインに成功した時に『ログインできました。』と表示させることで、ユーザーが進行具合を確認できるというものです。

目次

1 基本的な書き方
2 flashとflash.nowの違い

基本的な書き方

コントローラーの編集

flashはハッシュのような形で扱います。

flash[:キー名] = “表示さたいメッセージ”

キー名はあらかじめ用意せれているnoticeかalertオプションを用います(自分で好きな名前をつけることも可能です)。

controlle.rb
if @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

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

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.js
function 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.js
function 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秒後以降も同じであるため、重複している処理を防ぐことができます。

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

railsでhamlを使えるようにする

背景

railsのチーム開発でhamlを使っていたが便利だったので個人開発でもを使いたくなったので試しに使えるようにしました。

やった事

1. gemをインストール

使用するgem
haml-rails <= hamlを使えるようにするgem
erb2haml <= erbファイルをhamlに一括変換してくれるgem

1.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.の段階でブラウザ表示してみるとエラーが出てしまう.

スクリーンショット 2020-09-21 14.31.25.png
このエラーはrails sでサーバを再起動すると解消されました。

4. erb2haml を削除

erb2hamlはもう使わないのでGemfileから削除でOK.

#Gemfile から以下を削除
gem 'erb2haml'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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"]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.まとめ

初学者の私は大量のエラーメッセージを目にするとかなりやる気が削がれてしまいます。エラーメッセージに対しては冷静かつ堅実な対応が必要ですね。エラーに遭遇したときのために、事前に対処法は考えといたほうが良いかもしれないです。

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

入力した引数に対して、複数の戻り値を返したい

【概要】

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の多重代入あれこれまとめ

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

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ファイルに記入した環境変数とパスワードを囲むシングルクォートが全角だとエラーになる。(シングルクォートを含んだパスワードと見なされるため)
環境変数とパスワードを表示するコマンドを入力した時、パスワードと環境変数がシングルクォートで囲まれているかどうかで確認できる(半角だとシングルクォートが出ない)。

まとめ

タイトルのようなエラー文が表示された時はパスワードが間違っているので要確認。
 

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

Rails ルーティングの基礎

備忘録のために投稿。

環境

ruby 2.7.1
Rails 6.0.3.2

Rails の導入は済んでいる前提です。

generate コマンドで必要なファイルを作成

bundle exec rails g controller <controller_name> <method_name>

↓

bundle exec rails g controller home index

必要なファイルが作成される。削除したいときは delete コマンド

 bundle exec rails d controller home index

routes.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 で表示される。

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