- 投稿日:2020-12-04T23:31:55+09:00
【Rails】Gemを初公開しました
Gemを初公開しました
ask_year_monthという、
レシーバが何月なのか、
スマートにチェックするメソッドを生やしただけのGemを公開しました。
例えば、
Time.current.month == 12 #=> trueよりも、
Time.current.december? #=> trueのが見やすいよなーという所から。
ご興味がある方は良かったら使ってみてください。
RubyGems
https://rubygems.org/gems/ask_year_month
GitHub
https://github.com/mah666hhh/ask_year_month
参考記事
RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方
- 投稿日:2020-12-04T23:31:55+09:00
【Rails】Gemを初公開しました。何月なのかを月名で確認するメソッドを生成
Gemを初公開しました
ask_year_monthという、
レシーバが何月なのか、
月名でスマートにチェックできるメソッドを生やすGemを公開しました。
月名で確認できなくて地味にモヤッとしたんですよね。
例えば、
Time.current.month == 12 #=> trueよりも、
Time.current.december? #=> trueのが見やすいよなーという。
ご興味がある方は良かったら使ってみてください。
RubyGems
https://rubygems.org/gems/ask_year_month
GitHub
https://github.com/mah666hhh/ask_year_month
参考記事
RailsプラグインGemの作成方法、RSpecテストまで含めたrails pluginの作り方
- 投稿日:2020-12-04T22:58:42+09:00
DockerでRails6(MySQL, Webpacker)を動かす。
Docker練習第二弾。Rails5は結構やっていたのでRails6を動かしてみようと思います。
環境
- Docker 19.03.13
- docker-compose 1.27.4
- Windows10 Pro
ディレクトリ構成
rails_test/ ┝ Dockerfile ┝ docker-compose.yml ┝ Gemfile ┝ Gemfile.lock ┝ environments/ └ db.envDockerfile
DockerfileFROM ruby:2.6 RUN apt-get update -qq && \ apt-get install -y build-essential \ nodejs 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 && apt-get install yarn RUN mkdir /rails_test WORKDIR /rails_test ADD Gemfile /rails_test/Gemfile ADD Gemfile.lock /rails_test/Gemfile.lock RUN bundle install ADD . /rails_test
- FROM Ruby2.6をプルします。
- RUN Railsに必要なNode.jsとyarnをインストール。 作業ディレクトリの作成。
- WORKDIR 作業ディレクトリの指定をします。
- ADD ローカルのGemfileとGemfile.lockをコンテナにコピーします。
- RUN バンドルインストールをします。
- ADD Gemfileをローカルに反映します。
今回webpackerを使うためにyarnをインストールするのポイントでした。
docker-compose.yml
今回のメインのdocker-compose.ymlの設定です。
docker-compose.ymlversion: '3' services: app: build: . volumes: - .:/rails_test command: bash -c "rm -f tmp/pids/server.pid && rails s -b 0.0.0.0" ports: - 3000:3000 environment: WEBPACKER_DEV_SERVER_HOST: webpacker env_file: - ./environments/db.env depends_on: - db webpacker: build: . environment: NODE_ENV: development RAILS_ENV: development WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 volumes: - .:/rails_test command: ./bin/webpack-dev-server ports: - 3035:3035 db: image: mysql:5.7 volumes: - rails-db:/var/lib/mysql env_file: - ./environments/db.env volumes: rails-db:各コンテナの設定を説明します。
appapp: build: . volumes: - .:/rails_test command: bash -c "rm -f tmp/pids/server.pid && rails s -b 0.0.0.0" ports: - 3000:3000 environment: WEBPACKER_DEV_SERVER_HOST: webpacker env_file: - ./environments/db.env depends_on: - dbRailsの設定です。
- volumes ローカルのディレクトリとマウントします。これによりコンテナ作成時にローカルでの変更点が反映されます。
- command サーバーを立ち上げています。ポイントとしてdocker-compose downでコンテナを削除した際にserver.pidがローカルに残るため再度コンテナを作成した際にサーバーが立ち上げられなくなるため最初にserver.pidを削除します。
- depends_on MySQLのコンテナとの起動順序を定義します。Railsが先に立ち上がるとDBと接続できないと怒られます。
- env_file ここではDBのユーザーネームなど定義します。今回はrootユーザーで行うためルートユーザーのパスワードだけ設定しておきます。
- environment webpackerの設定です。
webpackerwebpacker: build: . environment: NODE_ENV: development RAILS_ENV: development WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 volumes: - .:/rails_test command: ./bin/webpack-dev-server ports: - 3035:3035Webpackerの設定です。ほとんど公式に書いてある通り(Github)にしただけなので、特に言うこともありませんがここでもvolumesをRailsと合わせておかないとwebpack-dev-serverが見つけられなくなります。
dbdb: image: mysql:5.7 volumes: - rails-db:/var/lib/mysql env_file: - ./environments/db.env volumes: rails-db:MySQLの設定です。rails-dbという名前付きボリュームにてDBのデータを永続化しています。また設定は別に用意(db.env)しています。もしrootユーザーでなく新しいユーザーを作りたい場合にはdb.envに書き加えます。
/environments/db.envMYSQL_ROOT_PASSWORD=passwordとりあえずこれだけ書いておきます。
Gemfile
この二行だけ書き加えます。Gemfile.lockは何も書きません。
Gemfilesource 'https://rubygems.org' gem 'rails', '6.0'アプリ作成
まずはrails newをしてアプリを作ります。
$ docker-compose run --rm app rails new . --force -d mysqlディレクトリを新たに作らずにDBはMySQLを使い、Gemfileのオーバーライドをするコマンドを入力しています。ここで自分はyarnを入れていなかったためにwebpackerが途中でインストールが止まりました。
database.ymlの設定
ローカルにできたdatabase.ymlファイルにdocker-compose.ymlで設定したものに書き換えます。
database.ymldefault: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: <%= ENV.fetch('MYSQL_USER') { 'root' } %> password: <%= ENV.fetch('MYSQL_PASSWORD') { 'password' } %> host: db development: <<: *default database: rails_test_development test: <<: *default database: rails_test_test production: <<: *default database: rails_test_production username: rails_test password: <%= ENV['RAILS_TEST_DATABASE_PASSWORD'] %>defaultのところを書き換えています。今回MYSQL_USERとMYSQL_PASSWORDは設定していないのでrootとpasswordになりますが、db.envにて設定できるようにしています。
コンテナ起動
これでRailsがMySQLに接続できるようになりました。コンテナを全て起動しましょう。
$ docker-compose up -dコンテナが3つ(app, db, webpacker)停止していなければ成功かと思われます。
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ab56f7caf110 rails_test_app "bash -c 'rm -f tmp/…" 2 minutes ago Up 2 minutes 0.0.0.0:3000->3000/tcp rails_test_app_1 eb345957801c mysql:5.7 "docker-entrypoint.s…" 3 minutes ago Up 2 minutes 3306/tcp, 33060/tcp rails_test_db_1 a64c29f979c1 rails_test_webpacker "./bin/webpack-dev-s…" 13 minutes ago Up 2 minutes 0.0.0.0:3035->3035/tcp rails_test_webpacker_1DB作成
最後にdb:createして http://localhost:3000 にてデフォルト画面が見れたら成功です。
$ docker-compose exec app rails db:create所感
Qiitaの記事を見ながらコピペでやってできた気になっていましたが、改めて自分で書いてみるとMySQLに接続できなかったりWebpackerが起動できなかったりとトラブル続出でした。
また、node_modulesをコンテナの中に置いておくのってセンスが無い気もしました。何かいいアイデアがありそう。とはいえひと月前までコピペでなんとなくやっていたところも大分自分の言葉で説明できるようになりました。
次は本番環境想定してnginxとの連携とかですかね...。
- 投稿日:2020-12-04T22:58:19+09:00
ActiveStorageのバリデーション
はじめに
Rails5.2から導入されたActive Storageは設定が簡単でとても導入しやすく、シンプルな機能を備えていて、少し使ってみた感じだとよい機能だと思っています。
ただ、実運用で使おうとすると、バリデーションの機能がなくて、惜しい感じです…。そこで、自分が最低限欲しいと感じた、いくつかのバリデーションを作ったので紹介します。
サンプルアプリケーションのコードはこちらです。
Rails5.2で書いていますが、ほぼ同じものを6.0に持ってきても動きます。必須
入力フォームで、ファイルの添付を必須強制したいときはごく普通にあると思います。
ただ、普通のpresenceのバリデーションは使えなかったので、別途用意しました。設置例
attached_file_presence
で設定できるようにしています。class Post < ApplicationRecord has_one_attached :main_image has_many_attached :other_images validates :main_image, attached_file_presence: true validates :other_images, attached_file_presence: true endコード
ファイルが添付されているかどうか検査できる
attached?
を使います。app/validators/attached_file_presence_validator.rbclass AttachedFilePresenceValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) record.errors.add(attribute, :blank) unless value.attached? end endファイル数
has_many_attached
を使うと、複数のファイルを添付できますが、個数の制限をしたいときがあると思います。設置例
attached_file_number
とmaximum
オプションで最大個数を設定できるようにしています。class Post < ApplicationRecord has_many_attached :other_images validates :other_images, attached_file_number: { maximum: 3 } endコード
ファイル数はsizeで取れるので、それを使って検証しています。
app/validators/attached_file_number_validator.rbclass AttachedFileNumberValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return true unless value.attached? file_number = value.size if (limit = options[:maximum]).present? && file_number > limit record.errors.add(attribute, :too_many_files, count: limit) end if (limit = options[:minimum]).present? && file_number < limit record.errors.add(attribute, :too_few_files, count: limit) end end endエラーメッセージで指定している、
too_many_files
やtoo_few_files
はconfig/locales
で設定しています。config/locales/ja.ymlja: errors: messages: too_many_files: は%{count}個以内で入力してください too_few_files: は%{count}個以上で入力してくださいファイルサイズ
ユーザに画像を登録できるようにすると、本格的なカメラで撮ったような高精細の画像が添付されてくることがしばしばありますが、リソース上の制約から受け取らないようにしたいときもあると思います。
設置例
attached_file_size
とmaximum
オプションで最大ファイルサイズを設定できるようにしています。class Post < ApplicationRecord has_one_attached :main_image has_many_attached :other_images validates :main_image, attached_file_size: { maximum: 5.megabytes } validates :other_images, attached_file_size: { maximum: 5.megabytes } endコード
ファイルのサイズを得る
attachement.byte_size
を使っています。
ファイルの単複で微妙に処理が変わるのが嫌で、単数のときは配列に詰めて複数と同じように処理できるようにしています。app/validators/attached_file_size_validator.rbclass AttachedFileSizeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return true unless value.attached? return true unless options&.dig(:maximum) maximum = options[:maximum] attachements = value.is_a?(ActiveStorage::Attached::Many) ? value.attachments : [value.attachment] if attachements.any? { |attachment| attachment.byte_size >= maximum } record.errors.add(attribute, :less_than, { count: maximum.to_s(:human_size) }) end end endファイルタイプ
例えば画像を登録してもらうつもりのところに、間違ってテキストファイルを登録しないようになど、アップロードするファイルの形式を保存する前にチェックしたいことは多いと思います。
設置例
attached_file_type
とpattern
オプションで最大ファイルサイズを設定できるようにしています。
pattern
は正規表現で設定します。class Post < ApplicationRecord has_one_attached :main_image has_many_attached :other_images validates :main_image, attached_file_type: { pattern: /^image\// } validates :other_images, attached_file_type: { pattern: /^image\// } endコード
content_typeを指定のパターンに合致しているかチェックしています。
ファイルの単複で微妙に処理が変わるのが嫌で、単数のときは配列に詰めて複数と同じように処理できるようにしています。class AttachedFileTypeValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return true unless value.attached? return true unless options&.dig(:pattern) pattern = options[:pattern] attachments = value.is_a?(ActiveStorage::Attached::Many) ? value.attachments : [value.attachment] if attachments.any? { |attachment| !attachment.content_type.match?(pattern) } record.errors.add(attribute, :invalid_file_type) end end endエラーメッセージで指定している、
invalid_file_type
はconfig/locales
で設定しています。config/locales/ja.ymlja: errors: messages: invalid_file_type: は不正なファイル形式ですValidatorのテスト
RSpecで書いたものがありますが、全部載せるととても長いので、こちらを参照してください。
ここでのポイントは、アプリケーションで実際に使っているテーブルを使ったモデルを定義して、そこにバリデーションを設置してテストに使うところです。
最初は使いそうなものをスタブやモックを使って作ろうとしていたのですが、ファイルの単複を扱うのでとても記述量が多くなり、わかりにくくなってしまったのでやめました。最後に
バリデータもテストを書いたりすると、結構楽しいですね。これのおかげでActiveStorageと少し仲よくなれた気がします。
書きながらあれこれ探してたら、もっとスマートなactivestorage-validatorというGemがあったので、こちらを使ってもらってもいいかもしれません。参照
- 投稿日:2020-12-04T22:53:10+09:00
PDF出力してみたいんじゃ! 【Thinreports + Rails】
どうも。とある六本木一丁目にある会社のエンジニアです。
というわけで SmartHR Advent Calendar 2020 の5日目だぜ。目的
弊社プロダクトのPDF出力に Thinreports を使用しているのでお友達になりたかった。
使うもの
Thinreports + Rails でやっていきます
ゴール
タイトルと本文がある書類をPDFで出力できるようにする!
簡単!やっていこう
とりあえず rails new
rails new thinreports_demo --skip-testgem 追加
gem 'thinreports'bundle installサンプルPDF出力する準備
ThinreportsでのPDF出力には
tlf
というフォーマットのデータが必要なので後々用意する必要があるので先にrails側の準備を整えるコントローラーを用意
rails g controller pdfs index sample_docsample_doc用にroutesを修正
routes.rbRails.application.routes.draw do root 'pdfs#index' resources :pdfs, only: :index do get :sample_doc, on: :collection end endviews を修正
views/pdfs/index.html.erb<h1>PDFにしてくれる君</h1> <p> <%= link_to 'サンプル書類がみたい', sample_doc_pdfs_path %> </p>リンクにアクセスするとPDFを見れるようにしていく
Thinreports Editor で TLFファイル 作る
公式を見て Thinreports Editorのインストール(必要なら Generatorの方も)
Thinreports インストールガイド(公式)何はともあれ
Thinreports Editor
を起動新規作成から新しくA4の書類を作ります
後々のことを考えて
- タイトル
- 本文
- 著者
の3つを
テキストブロックツール
を使って作り出しますapp/pdfs 以下に保存(ファイル名は任意で)
PDFとして出力する設定をしていく
pdfsコントローラーにPDFを出力するための設定をする
詳しくはここら辺のクイックスタート を参考にするpdfs_controller.rbclass PdfsController < ApplicationController : : def sample_doc report = Thinreports::Report.new(layout: "#{Rails.root}/app/pdfs/sample_doc.tlf") report.start_new_page # さっき作った title に value つっこんでる report.page.item(:title).value('PDFやで') # title 以外はあとでやるのでここではスルーします file = report.generate send_data( file, filename: 'filename_sample.pdf', type: 'application/pdf', disposition: 'inline' ) end end先ほどのリンクからアクセスすると PDF出てくる
やったね✌️
書類らしきものを作る
ここら辺は scaffold しましょう
- Doc
- title
- content
- author
rails g scaffold doc title content:text authorrails db:migrate新しく TLFファイル を複製
内容は
sample_doc.tlf
と同じでよいので もう一つ TLF作る(特に意味はないのでsample_doc.tlfを使い回しでいいと思います)書類らしきものごとにPDF出力できるようにする
routes の修正
routes.rbRails.application.routes.draw do : : resources :docs do get :show_pdf end : : endコントローラー
docs_controller.rbclass DocsController < ApplicationController : : def show_pdf @doc = Doc.find(params[:doc_id]) report = Thinreports::Report.new(layout: "#{Rails.root}/app/pdfs/doc.tlf") report.start_new_page # 以下で各カラムごとのデータを入れる report.page.item(:title).value(@doc.title) report.page.item(:content).value(@doc.content) report.page.item(:author).value(@doc.author) file = report.generate send_data( file, filename: "doc_#{@doc.id}.pdf", type: 'application/pdf', disposition: 'inline' ) end : : endリンクも忘れず
views/docs/index.html.erb<p id="notice"><%= notice %></p> <h1>Docs</h1> <table> : <tbody> <% @docs.each do |doc| %> <tr> : <td><%= link_to 'ShowPDF', doc_show_pdf_path(doc) %></td> # ☝️追加 : </tr> <% end %> </tbody> </table> <br> <%= link_to 'New Doc', new_doc_path %>データを入れる
ダミーテキストジェネレータ とかを使ってデータ入れる
http://localhost:3000/docs/new から適当に
http://localhost:3000/docs はこんな感じ
完成!!(見た目は整える時間なかった!)
やったね✌️まとめ
お友達になれました
![]()
- 投稿日:2020-12-04T21:59:51+09:00
DockerでRailsの環境構築をする
初心者がDockerでRailsの環境構築をしたのでメモとして残します。
Dockerのインストール
Dockerの公式ホームページからget startedをクリックしてDocker Desktopをクリックしダウンロードします。
Dockerの公式ホームページ https://www.docker.com/
ダウンロード後インストールが完了したらターミナルで
docker run hello-worldと打ちます。
以下のように表示されたらインストール成功です。ターミナルHello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ファイル設定
新規ディレクトリを作成しそこに以下の4つのファイルをコピーします。
https://github.com/sekine617/Rails-file新しいRailsプロジェクトのファイル作成
ターミナルで以下のコマンドを打ちます。
ターミナルdocker-compose run web rails new . --force --database=mysqldocker-compose run webはDockerのwebサービスコンテナで右のコマンドを実行するためのものです。
rails new .で新しいRailsプロジェクトのファイル作成し、
--forceは既存のファイルの上書き、
--database=mysqlはデータベースにMySQLを使用するコマンドです。Gemのインストールや新規作成されたファイルをDockerに取り込むために以下のコマンドを打ちます。
ターミナルdocker-compose buildファイル設定2
作成したファイルの/config/database.ymlを編集します。
database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: 5 username: root password: ******** host: *****17~18行目にあるpasswordとhostを/docker-compose.ymlのpasswordとhostの値と一致させます。
/Gemfileも編集します。
gem 'mysql2', '>= 0.3.18', '<= 8.0.22'
の部分の'<= 8.0.22'を自身のMySQLのバージョンに合わせてください。/Gemfilesource 'https://rubygems.org' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.0.0', '>= 5.0.0.1' # Use mysql as the database for Active Record gem 'mysql2', '>= 0.3.18', '<= 8.0.22' # Use Puma as the app server gem 'puma', '~> 3.0'コンテナの起動
以下のコマンドで現在のディレクトリでコンテナの起動します。
ターミナルdocker-compose up -d起動の確認を次のコマンドで行います。
ターミナルdocker-compose ps以下のように表示されれば正しく起動されています。
ターミナルName Command State Ports ----------------------------------------------------------------------------- rails_db_1 docker-entrypoint.sh mysqld Up 3306/tcp, 33060/tcp rails_web_1 bundle exec rails s -p 300 ... Up 0.0.0.0:3000->3000/tcpデータベースの作成
以下のコマンドでデータベースの作成します。
ターミナルdocker-compose run web bundle exec rake db:createrake db:createでデータベースが作成されていない場合新規に作成されます。
これでRailsサーバーにアクセスできます。サーバーへアクセス
ブラウザのURLからlocalhost:3000と入力します。
Yay!You're on Rails!
と表示されればうまくアクセスできています。
- 投稿日:2020-12-04T21:55:42+09:00
[Rails]Stripe Checkoutを利用して定期決済機能を実装する
はじめに
この記事では、Stripeを用いた定期決済機能を実装します。
決済には、Ruby on RailsとCheckout Sessionを利用します。Stripeとは
決済サービスです。
WebサイトやWebアプリケーション、ネイティブアプリに決済システムを導入できます。似たようなサービスには、PayPal, Pay.JP, Omiseなどがあります。
Stripeは機能がとにかく豊富で、多彩な決済システムを構築できるのが特徴です。
用語整理
Checkout
Stripeの決済ページには、Checkout と Elements の2つのタイプが用意されています。Checkout:Stipeが用意した決済専用ページ。
Elements:決済フォームとシステムだけ用意され、決済ページはカスタマイズできる。サクッと決済ページを作成したいときはCheckoutを利用するといいですね。定期決済、都度決済どちらにも対応しています。
Session ID
今回はセッションを用いた決済システムを構築します。こちらの決済システムでは、レスポンスにSession_id
が返ってきます。
このSession_id
にはcusntomer_id(顧客ID)
やsubscription_id(定期決済ID)
など、さまざまな決済情報が入っています。注意点
Stripeドキュメントに書かれているRubyコードの例は、主にSinatraで書かれているのでRailsではありません。
Sinatraとは、Rubyのフレームワークです。RubyのフレームワークはRailsだけではなくて、Sinatraもあります。以前StripeはRailsを使って例を出していましたが、、、現在ではSinatraに鞍替えしています。
RailsにSinatraを導入することはできないので、今回の実装では一部JavaScriptを利用しています。
この記事でできること
- チェックアウトを用いた決済ページの作成
- 決済システムの構築
- session_idを活用
準備
商品を作成するためにStripe Dashboardに入る必要があります。Stripeアカウントを持っていない方は、ここで作成しましょう。
本番環境ではなく、テスト環境で行うだけでしたらアカウントを有効にさせる必要はありません。今回作成する商品や、APIキーはテスト用のものですので、今回の実装も本番環境を使用することはありません。
すでに本番環境を有効にしている方
もしすでに有効にしている場合は、注意点があります。ダッシュボードの左側に View test data もしくは Viewing test data と表示されている切り替えボタンがあると思います。View test dataとなっている時は本番モード、Viewing test dataとなっている時はテストモードです。
テストモードで実装を行いたい場合は、商品の作成からテストモードで行わなければなりません。本番モードの時に作成した商品は本番環境でしか、テストモードの時に作成した商品はテスト環境でしか使用できません。こちらはご注意ください。
商品の作成
商品の作成にはダッシュボード左側のProductsを選択します。
Add productを選択します。
最低限、名前と金額さえ選択できていれば、商品は作成できます。
One timeは一回限りの都度決済、Recurringは定期決済となります。
今回は毎月の定期決済を実装するので、Recurringを選択し、Billing perio(期間)はMonthlyと設定します。
設定が完了したら、右上のSave productをクリックします。
そうすると、PricingのところにAPI IDという蘭があり、そちらには
price_id
が発行されています。こちらが発行されていれば準備は完了です。※補足
2020年8月27日以前のStripe APIのバージョンでは、定期決済の実装には
plan_id
を用いていました。現在のバージョンではprice_id
を用いるので、ご注意ください。実装
Gemをインストール
Gemfilegem 'stripe'APIのシークレットキーを設定
StripeダッシュボードのHomeに行くと、Get your test API keysという欄があります。そちらにあるSecret keyをRailsのディレクトリに記述します。
シークレットキーを記述するのには
credentials.yml
を利用してもいいですが、今回はenv
ファイルを使って行います。.envSTRIPE_TEST_SECRET_KEY = sk_test_xxxxxxまた、
config/initializers
の中にstripr.rb
というファイルを作成し、その中にシークレットキーを設定します。stripe.rbStripe.api_key = ENV['STRIPE_TEST_SECRET_KEY']Paymentsコントローラーを作成する
名前はなんでもいいですが、今回はPaymentsと命名します。
メソッドはnew_subscription
とcreate_subscription
の2つを作成することにします。payments_controller.rbdef new_subscription @session = Stripe::Checkout::Session.create({ payment_method_types: ['card'], line_items: [{ price: 'price_XXXX', quantity: 1, }], mode: 'subscription', success_url: request.base_url + '/payments/create_subscription?session_id={CHECKOUT_SESSION_ID}', cancel_url: request.base_url + '/payments/subscription1', }) end def create_subscription endドキュメントにはsessionに@はついていませんが、viewにsession_idを渡す必要があるので、@を付けます。
priceの欄に、先ほどダッシュボード上で商品を作成したときに発行されたprice_idを入力します。今は
create_subscription
メソッドに何も書かなくていいです。ただ、今の設定ですと決済完了した時にはこのメソッドに遷移するようになっているので、viewファイルにcreate_subscription.html.erb
を作成し、決済完了したことがわかるように何か書くといいかなと思います。create_subscription.html.erb<p>できたよ</p>決済画面を実装
new_subscription.html.erb<script src="https://js.stripe.com/v3"></script> <script> var stripe = Stripe('pk_test_XXXX'); stripe.redirectToCheckout({ sessionId: '<%= @session.id %>' }).then(function (result) { }); </script>
Stripe('pk_test_XXXX')
のカッコ内には、公開可能APIキーを入れてください。シークレットキーとは別のものなので、注意してください。先ほどシークレットキーを取得したStripeダッシュボードからPublishable keyをコピーし、貼り付けましょう。ドキュメントでは決済画面に遷移する前にボタンが実装されていますが、ボタンが不要だと思ったので今回は省いています。ボタンが必要な方はドキュメントを参考にしてください。
試しに決済
これで決済画面ができたので、遷移してみましょう。
こちらの画面に、テストカードを利用して決済してみましょう。
テストカードには、カード番号に4242 4242 4242 4242と入力すれば、後の記入欄はなんでもいいです。決済完了し、この画面になるとおそらく決済できたと思います。Stripeダッシュボードでも確認できます。
create.subscription.html.erbの画面↓
session_id
レスポンスにはsession_idが返ってきています。これは、決済ごとに発行されるIDです。
こちらのsession_idを取り出すと、色々な情報が出てきます。取り出し方は以下です。
session_idを取り出すStripe::Checkout::Session.retrieve( 'cs_test_XXXX', )レスポンス{ "id": "cs_test_XXXX", "object": "checkout.session", "allow_promotion_codes": null, "amount_subtotal": null, "amount_total": null, "billing_address_collection": null, "cancel_url": "https://example.com/cancel", "client_reference_id": null, "currency": null, "customer": null, "customer_email": null, "livemode": false, "locale": null, "metadata": {}, "mode": "payment", "payment_intent": "pi_XXXX", "payment_method_types": [ "card" ], "payment_status": "unpaid", "setup_intent": null, "shipping": null, "shipping_address_collection": null, "submit_type": null, "subscription": null, "success_url": "https://example.com/success", "total_details": null }この情報のどれかをDBに保存したい場合は、こちらから保存するといいかなと思います。例えば、customer_idを保存したい場合は以下のようになると思います。
payments_controller.rbdef create_subscription session = Stripe::Checkout::Session.retrieve(params[:session_id]) Payment.create(customer_id: session.customer) endまとめ
StripeのAPIバージョンは2020年8月末で更新されています(おそらく)。このバージョンから仕組みが大きく変わっているところがあり、この日以前のQiitaの記事があまり参考にならない場合があります。例えば、price_idの登場だとか、ですね。
このバージョンの日本語記事が少なく、Stripeのドキュメントも英語で、かつドキュメントにあるRubyコードのサンプルはSinatra用となっているので、、Railsで実装をしようとすると難しい部分がありますね。
これからStripe系の記事もいくつか投稿していこうと思います。
参考資料
Stripeドキュメント: (Dynamic subscriptions の After 部分)
Stripe APIリファレンス: セッションを取得する
Qiita: Rails5.2でStripeを使う(v3)
- 投稿日:2020-12-04T20:38:36+09:00
RailsアプリのCI/CDを"GitHub Actions"で作ってみた
記事のきっかけ
初学者ながら自身のRailsアプリにCI/CDを入れるぞ❗️と考えました。
最近話題になっているGitHub Actionsってなんだろう❓
GitHubでCI/CDも管理できるらしい。凄い❗️
その気持ちから、他のCI/CDサービスと自分なりに比較選択をし、
GitHub Actionsを選択。実装に突き進みました。
本記事はその時のコード記録と実施内容の忘備録を兼ねて記事にしました。アプリ開発環境
- Rails v6.0.3.4
- Ruby v2.6.6
- PostgreSQL v12.4
- Docker-compose v1.26.2
- Docker v19.03.12
GitHub Actionsとは
言わずも知れた開発管理必須ツールであるGitHub。
そのGitHubが提供するCI/CDサービス。
GitHubと高度に連携されており、設定によりGitHub上のコードを
自動でビルド・テスト・デプロイが可能。なぜGitHub Actionsなのか?
私なりにCIを選ぶにあたって他のサービスと検討しました。
他のサービスもありますが、私が比較したのは下記の2つのサービスです。
Circle CI
SaaS型のCI/CDサービス。FacebookやCyberAgentでの活用事例がありCI/CDサービス大手
QiitaでもCI/CDの記事が豊富にあり、アプリ作成事例での活用も多いイメージがあります。Code Pipeline
AWSのCI/CDサービス。各種AWSとの連携ができるのが強み私が選択した理由
①:GitHub Actionsの圧倒的なメリット点
GitHubのリポジトリの場所でCI/CDが実行・確認できる事です。
アプリ開発はGitHubは開発では必然的に使うのでこれは非常に便利です。
またAWSのリソースを触る場合においてアクセスキーを登録する必要がありますが
リポジトリ管理環境とAWSのアクセスキーを一度に管理できるのは、
AWSを使用したデプロイを想定している中で
リポジトリと外部SaaSでCI/CDの管理を分けるより、
進捗及び危機管理しやすい点があると考えました。②:他のCI/CDとのサービス比較
上記でも触れましたが、Circle CIは外部SaaSへの権限移譲の問題などの下記資料も検証し
*GitHub Actions の self-hosted runner と Amazon EKS を使った Docker の Build Pipeline
(freee株式会社)
今後の実務の中でもこの事は問題になる可能性があるなら、CI/CDとして最初に使うのは
GitHub Actionsでもいいのではないか?と考えました。Codepipelineとの比較では今回はAPI側の変更作業が多くなる事が予想されたので
GitHub上でCI/CDを随時実行し確認できた方が作業効率がいいと思われました。以上を総合判断し、最終的にGitHub Actionsを使用する事としました。
ただこれは初心者が考えた事ですので、間違いやそれぞれのCI/CD側の言い分があるかもしれません。
Circle CIはGithub Actionsより優れていると宣伝してますし(そんな堂々と・・。)
CodepipelineもAWSとの連携をする中では大きなメリットを享受出来る事と思います。GitHub Actions 作成手順・コード
前置きが長くなりました。早速GitHubActionsでのCI/CDを作成して行きます。
公式サイトは下記のリンク先となります。
GitHub Actionsのドキュメント1. 作成リスト
今回RailsでのアプリでCI/CDを組み立てます。
- .github/workflows ディレクトリ配下(新規作成)
- linter.yml
- rspec_security.yml
- main.yml
- database.yml (修正)
2.CIセッティング
今回のCIは2種類でセッティングしました。
1. Linter (トリガー:GitHubにpush
した時)
2. RSpec, security (トリガー:GitHubにpush
した時 及びpull_request
時)linter.ymlname: Linter on: [push] jobs: linters: name: Linters runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - name: Ruby Bundler uses: actions/cache@v2 with: path: vendor/bundle key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-gems- - name: Bundle install run: | bundle config path vendor/bundle bundle install --jobs 4 --retry 3 - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - name: yarn install run: yarn install - uses: actions/cache@v2 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - name: Run linters run: | bundle exec rubocop --parallelrspec_security.ymlname: Rails RSpec and security on: [push, pull_request] env: RAILS_ENV: test CI_HOST: localhost jobs: build: runs-on: ubuntu-latest services: postgres: image: postgres:12 ports: - 5432:5432 env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 chrome: image: selenium/standalone-chrome ports: - "4444:4444" volumes: - /dev/shm:/dev/shm steps: - name: Checkout code uses: actions/checkout@v2 - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 2.6 - name: Ruby Bundler uses: actions/cache@v2 with: path: vendor/bundle key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-gems- - name: Bundle install run: | bundle config path vendor/bundle bundle install --jobs 4 --retry 3 - name: Get yarn cache directory path id: yarn-cache-dir-path run: echo "::set-output name=dir::$(yarn cache dir)" - name: yarn install run: yarn install - uses: actions/cache@v2 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | ${{ runner.os }}-yarn- - name: Setup test database env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres run: | bundle exec rake db:create db:schema:load --trace - name: run rspec run: bundle exec rspec env: SELENIUM_REMOTE_URL: http://localhost:4444/wd/hub - name: Archive rspec result screenshots if: failure() uses: actions/upload-artifact@v2 with: name: rspec result screenshots path: tmp/screenshots/ - name: security check run: | bundle exec bundle-audit check --update bundle exec brakeman -q -w2CD (ECR自動デプロイ)
CDは
ECR
へのコンテナbuild&push
を行うセッティングです。
今回の設定でのトリガーはtag push
のversion(v)
で実行しております。main.ymlname: Build and Push on: push: tags: - v* jobs: build-and-push: runs-on: ubuntu-18.04 timeout-minutes: 300 steps: - uses: actions/checkout@v1 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ap-northeast-1 - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build, tag, and push image to Amazon ECR env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }} run: | IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g") docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG . docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAGdatabase.yml
環境変数のセッティングが必要となります。
テスト部にENV.fetch
で第一引数で指定した環境変数を確認し、
なければブロックで定義したデフォルト値が帰るようにしています。database.ymldefault: &default adapter: postgresql encoding: utf8 min_messages: WARNING host: db port: 5432 username: postgres password: postgres pool: 5 timeout: 5000 stats_execution_limit: 10 development: <<: *default database: development test: <<: *default database: test host: <%= ENV.fetch('CI_HOST') { 'db' } %> production: <<: *default database: myworkdb host: <%= ENV['DB_URL'] %> username: <%= ENV['DB_USERNAME'] %> password: <%= ENV['DB_PASSWORD'] %>CI/CD内容
CI
Linter.yml
コード内容としてはrubocop
でのLinterチェックとしました。
ローカル開発環境ではrubocop
prettier
beautify
の3種を併用しておりますが、
push
前のLinter整形忘れチェックとしてpush
時にチェックする対応で対応しました。
*ローカル環境下でのLinterセッティングについては下記記事にまとめております。
よろしければご参照下さい。
"Rails"でのフォーマット環境を整える(VScode)
RSpec, security
- テストはRailsの定番RSpecを走らせました。
- セキュリティは Gemの脆弱性診断
bundle-audit
と総合セキュリティbrakeman
を活用しました。
*Railsのセキュリティに関しては別記事でまとめております。
よろしければご参照下さい。
Rails6のセキュリティチェック環境を整えるCD
セッティングコードはClassmethodさんの下記の記事を参考にしました。
この記事のおかげで以後の開発が凄く捗りました。ありがとうございます。
- 参考元リンク:GitHub ActionでDockerコンテナをビルドしてAmazon ECRに保存する
(Developers.io Classmethod株式会社)最後に
最後まで読了頂きありがとうございます。
少しでも資料がどなたかの役に立てれば幸いです。
また今回GitHub Actionsの詳細な設定内容(コード)については触れておりませんので
別記事でGitHub Actionsの設定について個人的にまとめたいと思っております。
- 投稿日:2020-12-04T20:15:22+09:00
Mysql2::Error: Field '****' doesn't have a default valueに二回もハマった話
Mysql2::Error: Field 'tag_name' doesn't have a default value
とは、NOT NULL制約をかけているにも関わらず値が入っていないよーといった感じのエラーですclass CreateTags < ActiveRecord::Migration[6.0] def change create_table :tags do |t| t.string :name,null:false t.timestamps end end endとかでカラムに,null: falseで必ず値が入るように設定していたと思います
なので、rails側でも
validates :name,presence: trueと、バリデーションをかけていたと思います。
というわけで、まず考えられる原因が
マイグレーションファイルでnull: falseとNOT NULL制約を設けていたにも関わらず、Rails側でバリデーション をかけていなくて、form_withとか何とかで、値を保存しようとした!!
といったことが考えられます。
しかし自分は違いました、バリデーション もかけている
[しくじった原因1: ストロングパラメーターの記述が間違ってる]
{"authenticity_token"=>"T8/aOv7fmGDk6UO/GHiYkODYvpMTH/3tFP6sCW0QPVVaAVh6ZHcAd2xaTQzcuioVXdOOWYAFNHO8u3S0OmnG6Q==", "drink_tag"=> {"tag_name"=>"酸味", "image"=> #<ActionDispatch::Http::UploadedFile:0x00007f8a24b3d318 @content_type="image/jpeg", @headers="Content-Disposition: form-data; name=\"drink_tag[image]\"; filename=\"ethiopia.jpg\"\r\n" + "Content-Type: image/jpeg\r\n", @original_filename="ethiopia.jpg", @tempfile=#<File:/var/folders/34/pbcy_n7j1q79hnpm7sbcgl0m0000gn/T/RackMultipart20201204-1452-13ocgpk.jpg>>, "name"=>"エチオピア", "explain"=>"酸味があってアイスもおすすめ!!", "price"=>"1000"}, "commit"=>"投稿する"}と、パラメーターがとんでいて
private def drink_params params.require(:drink_tag).permit(:name,:price,:explain,:image,:tag_name).merge(user_id: current_user.id) endとストロングパラメーターを書くのが正しいですが
自分の場合、ストロングパラメータくらい分かったつもりになって教材をコピペしたせいで,
private def drink_params params.require(:tweet_tag).permit(:name,:price,:explain,:image,:tag_name).merge(user_id: current_user.id) endと、
params.require(:drink_tag)を
params.require(:tweet_tag)にしていました、、、。
ストロングパラメーターが間違っていたら
正しい値を許容できていないのでdef create @drink = DrinkTag.new(drink_params) if @drink.save redirect_to drinks_path else render 'new' end endの@drinkは空,nilなので、
nilな物を保存すんな!って怒られてしまいます、、、。
form_withの使い方はあってたので、しっかりとしたパラメーターが作られて
railsのバリデーションの網はかいくぐったようですが、MySQLちゃんがしっかりと値の保存を防いでくれました、、、。[しくじった原因2: Formオブジェクトを用いた値の保存で、一度に保存するためのmodelの記述が間違ってる!]
Formオブジェクトとは、一つのビューで複数の値を保存したいときに用いられるテクニック?的なものです。
( 詳しくは検索、検索♪)class UserDonation include ActiveModel::Model attr_accessor :name, :name_reading, :nickname, :postal_code, :prefecture, :city, :house_number, :building_name, :price with_options presence: true do validates :name, format: { with: /\A[ぁ-んァ-ン一-龥]/, message: "は全角で入力してください。"} validates :name_reading, format: { with: /\A[ァ-ヶー-]+\z/, message: "は全角カタカナで入力して下さい。"} validates :nickname, format: { with: /\A[a-z0-9]+\z/i, message: "は半角英数で入力してください。"} end def save user = User.create(name: name, name_reading: name_reading, nickname: nickname) Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number, building_name: building_name, user_id: user.id) Donation.create(price: price, user_id: user.id) end endと、一つのビューで複数のテーブルに値を保存したい時に、こういったモデルをよく作りますが
saveメソッドでのキー、バリューの書き忘れ!!!
で自分はつまずきました、、、。
この定義したsaveメソッドはあとでコントローラーで
def create @donation = UserDonation.new(donation_params) if @donation.valid? @donation.save # バリデーションをクリアした時 return redirect_to root_path else render "new" # バリデーションに弾かれ時 end endとsaveメソッドを用いますが、
例えば、
def save user = User.create(name: name, name_reading: name_reading, nickname: nickname) Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number, building_name: building_name, user_id: user.id) Donation.create(price: price, user_id: user.id) endで、
Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number,
building_name: building_name, user_id: user.id)
の
postal_code: postal_code
が抜け落ちてたら、このエラーが起きます、、、。
なので、しっかりsaveメソッドが定義できてるかみましょう!!これもフォームで入力して、しっかりパラメーターが作られるので、railsのバリデーション の
網を抜けたのでしょうが、データベース側でも「nilはだめ!!」としていたので、助かりました、。まとめ
バリデーション をかけているにもかかわらず、このエラーが起きるということは、モデルかコントローラーに
何かしらの問題がある。もっというと、コントローラーは、createで値を保存するので、
ストロングパラメーターに問題があったり、formオブジェクトはmodelにsaveメソッドを定義してるので、saveメソッドに問題が
ないか確かめましょう、、、!!!他にもこのエラーに対する考えられる原因と、それの対処法があればコメントお願いします!!
- 投稿日:2020-12-04T19:13:50+09:00
railsのdeviceではhas_secure_passwordはいらない
deviceを使用しており、password_digestを使おうとuserモデルにhas_secure_passwordを追加したら、「undefined method `password_digest=' for #<> Did you mean? password_digest」というエラーが出た。
結論
deviseはhas_secure_password の代わりにencrypt_passwordを使用している。
よって、userモデルにhas_secure_passwordを追加する必要はない。マイグレーションファイルを見てみると、
t.string :encrypted_password, null: false, default: ""と確かに書いてあった。
- 投稿日:2020-12-04T19:13:50+09:00
railsのdeviseではhas_secure_passwordはいらない
deviseを使用しており、password_digestを使おうとuserモデルにhas_secure_passwordを追加したら、「undefined method `password_digest=' for #<> Did you mean? password_digest」というエラーが出た。
結論
deviseはhas_secure_password の代わりにencrypt_passwordを使用している。
よって、userモデルにhas_secure_passwordを追加する必要はない。マイグレーションファイルを見てみると、
t.string :encrypted_password, null: false, default: ""と確かに書いてあった。
- 投稿日:2020-12-04T18:56:40+09:00
【Rails】ActionMailer のテストで Mail::Matchers を使う
はじめに
CBcloud Advent Calendar 2020 の2日目の記事です。
本記事では、メール送信の単体テストの際、ActionMailer が依存している Mail に含まれている
Mail::Matchers
を RSpec のマッチャとして利用する方法を紹介します。また、比較対象として、以下の2つも同時に記載します。
- Rails Guides に記載されているテスト方法
- RSpec のドキュメントに記載されているテスト方法
最後に、
Mail::Matchers
を使った例の紹介と、実際に動くサンプルコードを添付します。動作確認環境
- ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]
- actionmailer (6.0.3.4)
- rspec (3.10.0)
テストコード
Rails Guides に記載されているテスト方法
https://guides.rubyonrails.org/testing.html#testing-your-mailers
require 'test_helper' class UserMailerTest < ActionMailer::TestCase test "invite" do # Create the email and store it for further assertions email = UserMailer.create_invite('me@example.com', 'friend@example.com', Time.now) # Send the email, then test that it got queued assert_emails 1 do email.deliver_now end # Test the body of the sent email contains what we expect it to assert_equal ['me@example.com'], email.from assert_equal ['friend@example.com'], email.to assert_equal 'You have been invited by me@example.com', email.subject assert_equal read_fixture('invite').join, email.body.to_s end endRSpec のドキュメントに記載されているテスト方法
https://relishapp.com/rspec/rspec-rails/v/3-9/docs/mailer-specs/mailer-spec
require "rails_helper" RSpec.describe NotificationsMailer, :type => :mailer do describe "notify" do let(:mail) { NotificationsMailer.signup } it "renders the headers" do expect(mail.subject).to eq("Signup") expect(mail.to).to eq(["to@example.org"]) expect(mail.from).to eq(["from@example.com"]) end it "renders the body" do expect(mail.body.encoded).to match("Hi") end end endMail::Matchers を使ったテスト方法
ActionMailer の依存で Mail gem は既にインストール済みなので、導入手順は簡単です。
- RSpec の設定で、
Mail::Matchers
を include する- ActionMailer のテストで
Mail::Matchers
が提供するマッチャを使う以下が実際に
Mail::Matchers
を使って書いたコードです。
比較のため、最初のコンテキストでは先述した RSpec のドキュメントに記載されたサンプルを踏襲したコードを実装しています。RSpec.configure do |config| config.include Mail::Matchers, type: :mailer end RSpec.describe NotificationsMailer, type: :mailer do before do ActionMailer::Base.deliveries.clear end describe '#signup' do # @see: https://relishapp.com/rspec/rspec-rails/v/3-9/docs/mailer-specs/mailer-spec context 'when using RSpec mailer examples' do subject(:mail) { described_class.signup } it 'renders the headers' do expect(mail.subject).to eq('Signup') expect(mail.to).to eq(['to@example.org']) expect(mail.from).to eq(['from@example.com']) end it 'renders the body' do expect(mail.body.encoded).to match('Hi') end it 'sends the mail' do expect { mail.deliver_now }.to change { ActionMailer::Base.deliveries.count } end end # @see: https://github.com/mikel/mail#using-mail-with-testing-or-specing-libraries context 'when using Mail::Matchers' do subject(:mail) { described_class.signup.deliver_now } it { is_expected.to have_sent_email } it { is_expected.to have_sent_email.from('from@example.com') } it { is_expected.to have_sent_email.to('to@example.org') } it { is_expected.to have_sent_email.with_subject('Signup') } it { is_expected.to have_sent_email.with_body('Hi') } end end endメール送信後のマッチャが全て
have_sent_email
に集約されているのがわかりますね。
ここで紹介したもの以外にも、添付ファイルに対するマッチャなど、メールのテストに必要なものが一通り揃っているので、ぜひドキュメントを確認して使ってみてください。
https://github.com/mikel/mail#using-mail-with-testing-or-specing-libraries最後に
ここまで読んで、実際に自分で試してみたいと思われた方のために、動くサンプルコードを Gist で公開しています。
https://gist.github.com/tomohiro/dfa7362cd066dd87f25f40bdd6856513余談
個人的には
--format documentation
で出力したときの表現が冗長に感じています。参考資料
- 投稿日:2020-12-04T17:29:08+09:00
Sprockets::DoubleLinkError
初心者です。備忘録のため。残します。
超基本的な内容です。エラー内容
Sprockets::DoubleLinkErrorRails にて erb ファイルを作成して、表示しようとしたら上記発生。ターミナル確認すると以下記述あり。
ActionView::Template::Error (Multiple files with the same output path cannot be linked ("style.css") In "/Users/○○/git/photo_submission/app/assets/config/manifest.js" these files were linked: - /Users/○○/git/photo_submission/app/assets/stylesheets/style.css - /Users/○○/git/photo_submission/app/assets/stylesheets/style.scss ):原因
調べてみたら、Rails は rails new したときに自動で作成される views/layauts/application.html.erb を参照して HTML ファイルを作るようです。
assets ファイル以下のデータ群を一つに圧縮する(アセットパイプライン)。
views/layauts/application.html.erb
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>あとで erb ファイルに変更しようと思って、先に普通の HTML ファイルで作成しており、深く考えずに style.scss を作成していた。VSCode の拡張機能の Live Sass Compiler でstyle.css も自動で作成されており、これがエラーの原因。
assets/stylesheets/application.css に style.scss の記述を写し、stylesheets 直下には application.css だけ残すようにファイルの構造を調整して、再度出力すると問題なく表示できた。
- 投稿日:2020-12-04T17:17:07+09:00
RSpec FactoryBotのアソシエーション
はじめに
FactoryBotのアソシエーションについてメモしていきます
アソシエーションの仕方
例えば、
userモデル
と紐付いてるpostモデル
のテストデータが欲しい場合FactoryBot.define do factory :post do content {"Ruby楽しい"} association :user, factory: :user #アソシエーション end endこの場合は
user
のFactoryBotも作成してる必要がある。またこのように省略することも出来る。
FactoryBot.define do factory :post do content {"Ruby楽しい"} user #省略 end end最後に
まだまだ勉強中なので訂正などありましたらご指摘いただけると幸いですm(__)m
- 投稿日:2020-12-04T16:44:24+09:00
【AmazonLinux2】Railsアプリケーション新規作成 rake db:createまで
初めに
AmazonLinux2でRailsアプリケーションを新規作成した際に、いろいろとエラーにハマったため、黙示録として書いています。
環境
OS:AmazonLinux2
Ruby:ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
Rails:Rails 5.0.0
DB:Mariadb手順
前提として今回は、AWS(cloud9)でAmazonLinux2での開発を想定してます。またAWSのenvironmentの作成はできているものとします。
Ruby, Railsのバージョンの確認(AWSでは環境構築がされているため)
$ ruby -v ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux] $ rails -v Rails 5.0.0RubyのライブラリであるGemを管理するためのGem(bundler)をinstallする
$ gem install bundler Fetching bundler-2.1.4.gem Successfully installed bundler-2.1.4 Parsing documentation for bundler-2.1.4 Installing ri documentation for bundler-2.1.4 Done installing documentation for bundler after 3 seconds 1 gem installedMarinadbを使用するために必要なパッケージをインストール
DBサーバーインストール
$ sudo yum install mariadb-server Loaded plugins: extras_suggestions, langpacks, priorities, update-motd amzn2-core | 3.7 kB 00:00:00 220 packages excluded due to repository priority protections Resolving Dependencies --> Running transaction check ---> Package mariadb-server.x86_64 3:10.2.10-2.amzn2.0.3 will be installed --> Processing Dependency: mariadb-tokudb-engine(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 --> Processing Dependency: mariadb-server-utils(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 --> Processing Dependency: mariadb-rocksdb-engine(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 --> Processing Dependency: mariadb-gssapi-server(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 --> Processing Dependency: mariadb-errmsg(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 --> Processing Dependency: mariadb-cracklib-password-check(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 --> Processing Dependency: mariadb-backup(x86-64) = 3:10.2.10-2.amzn2.0.3 for package: 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 --> Running transaction check ---> Package mariadb-backup.x86_64 3:10.2.10-2.amzn2.0.3 will be installed ---> Package mariadb-cracklib-password-check.x86_64 3:10.2.10-2.amzn2.0.3 will be installed ---> Package mariadb-errmsg.x86_64 3:10.2.10-2.amzn2.0.3 will be installed ---> Package mariadb-gssapi-server.x86_64 3:10.2.10-2.amzn2.0.3 will be installed ---> Package mariadb-rocksdb-engine.x86_64 3:10.2.10-2.amzn2.0.3 will be installed ---> Package mariadb-server-utils.x86_64 3:10.2.10-2.amzn2.0.3 will be installed --> Processing Dependency: perl(DBI) for package: 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64 --> Processing Dependency: perl(DBI) for package: 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64 --> Processing Dependency: perl(DBD::mysql) for package: 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64 ---> Package mariadb-tokudb-engine.x86_64 3:10.2.10-2.amzn2.0.3 will be installed --> Processing Dependency: libjemalloc.so.1()(64bit) for package: 3:mariadb-tokudb-engine-10.2.10-2.amzn2.0.3.x86_64 --> Running transaction check ---> Package jemalloc.x86_64 0:3.6.0-1.amzn2.0.1 will be installed ---> Package perl-DBD-MySQL.x86_64 0:4.023-6.amzn2 will be installed ---> Package perl-DBI.x86_64 0:1.627-4.amzn2.0.2 will be installed --> Processing Dependency: perl(RPC::PlServer) >= 0.2001 for package: perl-DBI-1.627-4.amzn2.0.2.x86_64 --> Processing Dependency: perl(RPC::PlClient) >= 0.2000 for package: perl-DBI-1.627-4.amzn2.0.2.x86_64 --> Running transaction check ---> Package perl-PlRPC.noarch 0:0.2020-14.amzn2 will be installed --> Processing Dependency: perl(Net::Daemon) >= 0.13 for package: perl-PlRPC-0.2020-14.amzn2.noarch --> Processing Dependency: perl(Net::Daemon::Test) for package: perl-PlRPC-0.2020-14.amzn2.noarch --> Processing Dependency: perl(Net::Daemon::Log) for package: perl-PlRPC-0.2020-14.amzn2.noarch --> Processing Dependency: perl(Compress::Zlib) for package: perl-PlRPC-0.2020-14.amzn2.noarch --> Running transaction check ---> Package perl-IO-Compress.noarch 0:2.061-2.amzn2 will be installed --> Processing Dependency: perl(Compress::Raw::Zlib) >= 2.061 for package: perl-IO-Compress-2.061-2.amzn2.noarch --> Processing Dependency: perl(Compress::Raw::Bzip2) >= 2.061 for package: perl-IO-Compress-2.061-2.amzn2.noarch ---> Package perl-Net-Daemon.noarch 0:0.48-5.amzn2 will be installed --> Running transaction check ---> Package perl-Compress-Raw-Bzip2.x86_64 0:2.061-3.amzn2.0.2 will be installed ---> Package perl-Compress-Raw-Zlib.x86_64 1:2.061-4.amzn2.0.2 will be installed --> Finished Dependency Resolution Dependencies Resolved ================================================================================================================================================================================================ Package Arch Version Repository Size ================================================================================================================================================================================================ Installing: mariadb-server x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 17 M Installing for dependencies: jemalloc x86_64 3.6.0-1.amzn2.0.1 amzn2extra-lamp-mariadb10.2-php7.2 109 k mariadb-backup x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 5.9 M mariadb-cracklib-password-check x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 36 k mariadb-errmsg x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 221 k mariadb-gssapi-server x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 39 k mariadb-rocksdb-engine x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 4.0 M mariadb-server-utils x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 1.6 M mariadb-tokudb-engine x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 818 k perl-Compress-Raw-Bzip2 x86_64 2.061-3.amzn2.0.2 amzn2-core 32 k perl-Compress-Raw-Zlib x86_64 1:2.061-4.amzn2.0.2 amzn2-core 58 k perl-DBD-MySQL x86_64 4.023-6.amzn2 amzn2-core 141 k perl-DBI x86_64 1.627-4.amzn2.0.2 amzn2-core 804 k perl-IO-Compress noarch 2.061-2.amzn2 amzn2-core 260 k perl-Net-Daemon noarch 0.48-5.amzn2 amzn2-core 51 k perl-PlRPC noarch 0.2020-14.amzn2 amzn2-core 36 k Transaction Summary ================================================================================================================================================================================================ Install 1 Package (+15 Dependent packages) Total download size: 31 M Installed size: 136 M Is this ok [y/d/N]: y Downloading packages: (1/16): jemalloc-3.6.0-1.amzn2.0.1.x86_64.rpm | 109 kB 00:00:00 (2/16): mariadb-cracklib-password-check-10.2.10-2.amzn2.0.3.x86_64.rpm | 36 kB 00:00:00 (3/16): mariadb-errmsg-10.2.10-2.amzn2.0.3.x86_64.rpm | 221 kB 00:00:00 (4/16): mariadb-backup-10.2.10-2.amzn2.0.3.x86_64.rpm | 5.9 MB 00:00:00 (5/16): mariadb-gssapi-server-10.2.10-2.amzn2.0.3.x86_64.rpm | 39 kB 00:00:00 (6/16): mariadb-rocksdb-engine-10.2.10-2.amzn2.0.3.x86_64.rpm | 4.0 MB 00:00:00 (7/16): mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64.rpm | 1.6 MB 00:00:00 (8/16): mariadb-tokudb-engine-10.2.10-2.amzn2.0.3.x86_64.rpm | 818 kB 00:00:00 (9/16): mariadb-server-10.2.10-2.amzn2.0.3.x86_64.rpm | 17 MB 00:00:00 (10/16): perl-Compress-Raw-Bzip2-2.061-3.amzn2.0.2.x86_64.rpm | 32 kB 00:00:00 (11/16): perl-Compress-Raw-Zlib-2.061-4.amzn2.0.2.x86_64.rpm | 58 kB 00:00:00 (12/16): perl-DBD-MySQL-4.023-6.amzn2.x86_64.rpm | 141 kB 00:00:00 (13/16): perl-IO-Compress-2.061-2.amzn2.noarch.rpm | 260 kB 00:00:00 (14/16): perl-Net-Daemon-0.48-5.amzn2.noarch.rpm | 51 kB 00:00:00 (15/16): perl-PlRPC-0.2020-14.amzn2.noarch.rpm | 36 kB 00:00:00 (16/16): perl-DBI-1.627-4.amzn2.0.2.x86_64.rpm | 804 kB 00:00:00 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Total 32 MB/s | 31 MB 00:00:00 Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : 3:mariadb-errmsg-10.2.10-2.amzn2.0.3.x86_64 1/16 Installing : jemalloc-3.6.0-1.amzn2.0.1.x86_64 2/16 Installing : perl-Compress-Raw-Bzip2-2.061-3.amzn2.0.2.x86_64 3/16 Installing : perl-Net-Daemon-0.48-5.amzn2.noarch 4/16 Installing : 1:perl-Compress-Raw-Zlib-2.061-4.amzn2.0.2.x86_64 5/16 Installing : perl-IO-Compress-2.061-2.amzn2.noarch 6/16 Installing : perl-PlRPC-0.2020-14.amzn2.noarch 7/16 Installing : perl-DBI-1.627-4.amzn2.0.2.x86_64 8/16 Installing : perl-DBD-MySQL-4.023-6.amzn2.x86_64 9/16 Installing : 3:mariadb-tokudb-engine-10.2.10-2.amzn2.0.3.x86_64 10/16 Installing : 3:mariadb-rocksdb-engine-10.2.10-2.amzn2.0.3.x86_64 11/16 Installing : 3:mariadb-backup-10.2.10-2.amzn2.0.3.x86_64 12/16 Installing : 3:mariadb-cracklib-password-check-10.2.10-2.amzn2.0.3.x86_64 13/16 Installing : 3:mariadb-gssapi-server-10.2.10-2.amzn2.0.3.x86_64 14/16 Installing : 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 15/16 Installing : 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64 16/16 Verifying : 1:perl-Compress-Raw-Zlib-2.061-4.amzn2.0.2.x86_64 1/16 Verifying : 3:mariadb-tokudb-engine-10.2.10-2.amzn2.0.3.x86_64 2/16 Verifying : perl-Net-Daemon-0.48-5.amzn2.noarch 3/16 Verifying : 3:mariadb-rocksdb-engine-10.2.10-2.amzn2.0.3.x86_64 4/16 Verifying : perl-DBD-MySQL-4.023-6.amzn2.x86_64 5/16 Verifying : 3:mariadb-backup-10.2.10-2.amzn2.0.3.x86_64 6/16 Verifying : 3:mariadb-server-utils-10.2.10-2.amzn2.0.3.x86_64 7/16 Verifying : 3:mariadb-cracklib-password-check-10.2.10-2.amzn2.0.3.x86_64 8/16 Verifying : perl-IO-Compress-2.061-2.amzn2.noarch 9/16 Verifying : 3:mariadb-gssapi-server-10.2.10-2.amzn2.0.3.x86_64 10/16 Verifying : perl-Compress-Raw-Bzip2-2.061-3.amzn2.0.2.x86_64 11/16 Verifying : jemalloc-3.6.0-1.amzn2.0.1.x86_64 12/16 Verifying : perl-DBI-1.627-4.amzn2.0.2.x86_64 13/16 Verifying : perl-PlRPC-0.2020-14.amzn2.noarch 14/16 Verifying : 3:mariadb-errmsg-10.2.10-2.amzn2.0.3.x86_64 15/16 Verifying : 3:mariadb-server-10.2.10-2.amzn2.0.3.x86_64 16/16 Installed: mariadb-server.x86_64 3:10.2.10-2.amzn2.0.3 Dependency Installed: jemalloc.x86_64 0:3.6.0-1.amzn2.0.1 mariadb-backup.x86_64 3:10.2.10-2.amzn2.0.3 mariadb-cracklib-password-check.x86_64 3:10.2.10-2.amzn2.0.3 mariadb-errmsg.x86_64 3:10.2.10-2.amzn2.0.3 mariadb-gssapi-server.x86_64 3:10.2.10-2.amzn2.0.3 mariadb-rocksdb-engine.x86_64 3:10.2.10-2.amzn2.0.3 mariadb-server-utils.x86_64 3:10.2.10-2.amzn2.0.3 mariadb-tokudb-engine.x86_64 3:10.2.10-2.amzn2.0.3 perl-Compress-Raw-Bzip2.x86_64 0:2.061-3.amzn2.0.2 perl-Compress-Raw-Zlib.x86_64 1:2.061-4.amzn2.0.2 perl-DBD-MySQL.x86_64 0:4.023-6.amzn2 perl-DBI.x86_64 0:1.627-4.amzn2.0.2 perl-IO-Compress.noarch 0:2.061-2.amzn2 perl-Net-Daemon.noarch 0:0.48-5.amzn2 perl-PlRPC.noarch 0:0.2020-14.amzn2 Complete!DBクライアントインストール
$ sudo yum install mariadb-devel Loaded plugins: extras_suggestions, langpacks, priorities, update-motd 220 packages excluded due to repository priority protections Resolving Dependencies --> Running transaction check ---> Package mariadb-devel.x86_64 3:10.2.10-2.amzn2.0.3 will be installed --> Finished Dependency Resolution Dependencies Resolved ================================================================================================================================================================================================ Package Arch Version Repository Size ================================================================================================================================================================================================ Installing: mariadb-devel x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 1.0 M Transaction Summary ================================================================================================================================================================================================ Install 1 Package Total download size: 1.0 M Installed size: 4.5 M Is this ok [y/d/N]: y Downloading packages: mariadb-devel-10.2.10-2.amzn2.0.3.x86_64.rpm | 1.0 MB 00:00:00 Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : 3:mariadb-devel-10.2.10-2.amzn2.0.3.x86_64 1/1 Verifying : 3:mariadb-devel-10.2.10-2.amzn2.0.3.x86_64 1/1 Installed: mariadb-devel.x86_64 3:10.2.10-2.amzn2.0.3 Complete!データベースサーバーの起動
$ sudo systemctl start mariadbサーバーの起動状況を確認
$ sudo systemctl status mariadb ● mariadb.service - MariaDB 10.2 database server Loaded: loaded (/usr/lib/systemd/system/mariadb.service; disabled; vendor preset: disabled) Active: active (running) since Thu 2020-12-03 15:35:26 UTC; 8s ago Process: 860 ExecStartPost=/usr/libexec/mysql-check-upgrade (code=exited, status=0/SUCCESS) Process: 638 ExecStartPre=/usr/libexec/mysql-prepare-db-dir %n (code=exited, status=0/SUCCESS) Process: 582 ExecStartPre=/usr/libexec/mysql-check-socket (code=exited, status=0/SUCCESS) Main PID: 778 (mysqld) Status: "Taking your SQL requests now..." Tasks: 45 Memory: 169.8M CGroup: /system.slice/mariadb.service └─778 /usr/libexec/mysqld --basedir=/usr Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: MySQL manual for more instructions. Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: Please report any problems at http://mariadb.org/jira Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: The latest information about MariaDB is available at http://mariadb.org/. Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: You can find additional information about the MySQL part at: Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: http://dev.mysql.com Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: Consider joining MariaDB's strong and vibrant community: Dec 03 15:35:25 ip-172-31-19-99.us-east-2.compute.internal mysql-prepare-db-dir[638]: https://mariadb.org/get-involved/ Dec 03 15:35:26 ip-172-31-19-99.us-east-2.compute.internal mysqld[778]: 2020-12-03 15:35:26 140397484732224 [Note] /usr/libexec/mysqld (mysqld 10.2.10-MariaDB) starting as process 778 ... Dec 03 15:35:26 ip-172-31-19-99.us-east-2.compute.internal mysqld[778]: 2020-12-03 15:35:26 140397484732224 [Warning] Changed limits: max_open_files: 1024 max_connections: 151 ta...ache: 431 Dec 03 15:35:26 ip-172-31-19-99.us-east-2.compute.internal systemd[1]: Started MariaDB 10.2 database server. Hint: Some lines were ellipsized, use -l to show in full.OS起動時に自動的に起動するように設定
$ sudo systemctl enable mariadb Created symlink from /etc/systemd/system/multi-user.target.wants/mariadb.service to /usr/lib/systemd/system/mariadb.service.アプリケーション新規作成
ここでようやくアプリケーションを新規作成します。今回データベースはmysqlを指定するので、以下コマンドを打ち込みます。例としてアプリケーション名は"shift-management"としています。
$ rails new shift-management -d mysql create create README.md create Rakefile create config.ru create .gitignore create Gemfile create app create app/assets/config/manifest.js create app/assets/javascripts/application.js create app/assets/javascripts/cable.js create app/assets/stylesheets/application.css create app/channels/application_cable/channel.rb create app/channels/application_cable/connection.rb create app/controllers/application_controller.rb create app/helpers/application_helper.rb create app/jobs/application_job.rb create app/mailers/application_mailer.rb create app/models/application_record.rb create app/views/layouts/application.html.erb create app/views/layouts/mailer.html.erb create app/views/layouts/mailer.text.erb create app/assets/images/.keep create app/assets/javascripts/channels create app/assets/javascripts/channels/.keep create app/controllers/concerns/.keep create app/models/concerns/.keep create bin create bin/bundle create bin/rails create bin/rake create bin/setup create bin/update create config create config/routes.rb create config/application.rb create config/environment.rb create config/secrets.yml create config/cable.yml create config/puma.rb create config/spring.rb create config/environments create config/environments/development.rb create config/environments/production.rb create config/environments/test.rb create config/initializers create config/initializers/application_controller_renderer.rb create config/initializers/assets.rb create config/initializers/backtrace_silencers.rb create config/initializers/cookies_serializer.rb create config/initializers/cors.rb create config/initializers/filter_parameter_logging.rb create config/initializers/inflections.rb create config/initializers/mime_types.rb create config/initializers/new_framework_defaults.rb create config/initializers/session_store.rb create config/initializers/wrap_parameters.rb create config/locales create config/locales/en.yml create config/boot.rb create config/database.yml create db create db/seeds.rb create lib create lib/tasks create lib/tasks/.keep create lib/assets create lib/assets/.keep create log create log/.keep create public create public/404.html create public/422.html create public/500.html create public/apple-touch-icon-precomposed.png create public/apple-touch-icon.png create public/favicon.ico create public/robots.txt create test/fixtures create test/fixtures/.keep create test/fixtures/files create test/fixtures/files/.keep create test/controllers create test/controllers/.keep create test/mailers create test/mailers/.keep create test/models create test/models/.keep create test/helpers create test/helpers/.keep create test/integration create test/integration/.keep create test/test_helper.rb create tmp create tmp/.keep create tmp/cache create tmp/cache/assets create vendor/assets/javascripts create vendor/assets/javascripts/.keep create vendor/assets/stylesheets create vendor/assets/stylesheets/.keep remove config/initializers/cors.rb run bundle install [DEPRECATED] `Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` (called at /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/railties-5.0.0/lib/rails/generators/app_base.rb:374) The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies.... Fetching rake 13.0.1 Installing rake 13.0.1 Using concurrent-ruby 1.1.7 Fetching i18n 1.8.5 Installing i18n 1.8.5 Fetching minitest 5.14.2 Installing minitest 5.14.2 Using thread_safe 0.3.6 Fetching tzinfo 1.2.8 Installing tzinfo 1.2.8 Fetching activesupport 5.0.7.2 Installing activesupport 5.0.7.2 Using builder 3.2.4 Using erubis 2.7.0 Using mini_portile2 2.4.0 Using nokogiri 1.10.10 Using rails-dom-testing 2.0.3 Using crass 1.0.6 Fetching loofah 2.8.0 Installing loofah 2.8.0 Using rails-html-sanitizer 1.3.0 Fetching actionview 5.0.7.2 Installing actionview 5.0.7.2 Using rack 2.2.3 Using rack-test 0.6.3 Fetching actionpack 5.0.7.2 Installing actionpack 5.0.7.2 Fetching nio4r 2.5.4 Installing nio4r 2.5.4 with native extensions Using websocket-extensions 0.1.5 Using websocket-driver 0.6.5 Fetching actioncable 5.0.7.2 Installing actioncable 5.0.7.2 Using globalid 0.4.2 Fetching activejob 5.0.7.2 Installing activejob 5.0.7.2 Using mini_mime 1.0.2 Using mail 2.7.1 Fetching actionmailer 5.0.7.2 Installing actionmailer 5.0.7.2 Fetching activemodel 5.0.7.2 Installing activemodel 5.0.7.2 Using arel 7.1.4 Fetching activerecord 5.0.7.2 Installing activerecord 5.0.7.2 Fetching bindex 0.8.1 Installing bindex 0.8.1 with native extensions Using bundler 2.1.4 Fetching byebug 11.1.3 Installing byebug 11.1.3 with native extensions Fetching coffee-script-source 1.12.2 Installing coffee-script-source 1.12.2 Fetching execjs 2.7.0 Installing execjs 2.7.0 Fetching coffee-script 2.4.1 Installing coffee-script 2.4.1 Using method_source 1.0.0 Using thor 1.0.1 Fetching railties 5.0.7.2 Installing railties 5.0.7.2 Fetching coffee-rails 4.2.2 Installing coffee-rails 4.2.2 Fetching ffi 1.13.1 Installing ffi 1.13.1 with native extensions Fetching jbuilder 2.10.1 Installing jbuilder 2.10.1 Fetching jquery-rails 4.4.0 Installing jquery-rails 4.4.0 Fetching rb-fsevent 0.10.4 Installing rb-fsevent 0.10.4 Fetching rb-inotify 0.10.1 Installing rb-inotify 0.10.1 Fetching listen 3.0.8 Installing listen 3.0.8 Fetching mysql2 0.4.10 Installing mysql2 0.4.10 with native extensions Fetching puma 3.12.6 Installing puma 3.12.6 with native extensions Fetching sprockets 3.7.2 Installing sprockets 3.7.2 Using sprockets-rails 3.2.2 Fetching rails 5.0.7.2 Installing rails 5.0.7.2 Fetching sass-listen 4.0.0 Installing sass-listen 4.0.0 Fetching sass 3.7.4 Installing sass 3.7.4 Fetching tilt 2.0.10 Installing tilt 2.0.10 Fetching sass-rails 5.0.7 Installing sass-rails 5.0.7 Fetching spring 2.1.1 Installing spring 2.1.1 Fetching spring-watcher-listen 2.0.1 Installing spring-watcher-listen 2.0.1 Fetching turbolinks-source 5.2.0 Installing turbolinks-source 5.2.0 Fetching turbolinks 5.2.1 Installing turbolinks 5.2.1 Fetching uglifier 4.2.0 Installing uglifier 4.2.0 Fetching web-console 3.7.0 Installing web-console 3.7.0 Bundle complete! 15 Gemfile dependencies, 62 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. Post-install message from i18n: HEADS UP! i18n 1.1 changed fallbacks to exclude default locale. But that may break your application. If you are upgrading your Rails application from an older version of Rails: Please check your Rails app for 'config.i18n.fallbacks = true'. If you're using I18n (>= 1.1.0) and Rails (< 5.2.2), this should be 'config.i18n.fallbacks = [I18n.default_locale]'. If not, fallbacks will be broken in your app by I18n 1.1.x. If you are starting a NEW Rails application, you can ignore this notice. For more info see: https://github.com/svenfuchs/i18n/releases/tag/v1.1.0 Post-install message from sass: Ruby Sass has reached end-of-life and should no longer be used. * If you use Sass as a command-line tool, we recommend using Dart Sass, the new primary implementation: https://sass-lang.com/install * If you use Sass as a plug-in for a Ruby web framework, we recommend using the sassc gem: https://github.com/sass/sassc-ruby#readme * For more details, please refer to the Sass blog: https://sass-lang.com/blog/posts/7828841 run bundle exec spring binstub --all * bin/rake: Spring inserted * bin/rails: Spring insertedデータベースの作成
$ rake db:create The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Created database 'shift-management_development' Created database 'shift-management_test'今回はここまで。
よくあるエラー
- 投稿日:2020-12-04T16:18:11+09:00
rbenvのインストールについて(Checking for rbenv shims in PATH: not found)
結論
シェルの確認
↓
.zshrcファイルの作成
↓
eval "$(rbenv init -)"
export PATH="~/.rbenv/bin:$PATH"
export PATH="~/.rbenv/bin:~/.rbenv/shims:$PATH"
の三行を追加
↓
source ~/.zshrc環境
- Mac
問題点
$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bashを実行すると、
Checking for `rbenv' in PATH: /usr/local/bin/rbenv Checking for rbenv shims in PATH: not found The directory `/Users/ユーザー名/.rbenv/shims' must be present in PATH for rbenv to work. Please run `rbenv init' and follow the instructions. Checking `rbenv install' support: /usr/local/bin/rbenv-install (ruby-build 20201118) Counting installed Ruby versions: none There aren't any Ruby versions installed under `/Users/ユーザー名/.rbenv/versions'. You can install Ruby versions like so: rbenv install 2.2.4 Checking RubyGems settings: OK Auditing installed plugins: OKとなり、
Checking for rbenv shims in PATH: not foundこの部分のパスが通っていないこと。
解決法
rbenv shimsの環境パスの設定が正しくできていない、とのことなので
.bash_profile内で、export RBENV_ROOT="$HOME/.rbenv" if [ -d "${RBENV_ROOT}" ]; then export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)" fiを記述する。これで再度確認するとOK!になるのだが、ターミナルを再起動するとまた
Checking for rbenv shims in PATH: not foundのエラーになる。
ここからが具体的な解決法。
今現在シェルがzshになっている。その上で、bash_profileファイル内に記述したのだがそれではターミナルを起動するごとにsource ~/.bash_profileをする必要がある。
結論として新たにzshrcファイル(zsh_profileファイルでもいい)を作成し、同じような記述をするとターミナルを再起動してもパスが通っている状態になっている。
- 投稿日:2020-12-04T16:05:30+09:00
RailsでWeb APIを実装しよう!
はじめに
PORT Advent Calendar 2020 の4日目の記事です!
はじめまして! PORT新卒エンジニアのしゅんいち(shxun6934) です!
業務でのとあるタスクにて、Railsを使用してWebAPIを実装することになりました。
今までは、WebAPI(QiitaとかTwitterとかお世話になりました。)を使用する側でしたが、WebAPIを作成する側になったので、いろいろわからないところが多かったです。簡単にですが、そのタスクに臨むにあたって学んだこと、意識していたことを皆さんに共有しておきたいと思います。
そもそも、APIって何?
API(Application Programming Interface) は、アプリケーションとアプリケーション、人、組織をつなげてくれるものです。
APIと言っても種類はいくつかあります。
(例:Twitterから「いいね」の情報を取得するためのもの、iOSアプリでカメラの機能を使用するためのもの、画像の色を反転させるためのもの、などなど。。。)一般的に言われている「API」は、WebAPIを指すことが多いです。
WebAPIは、ネットワークを使用して他のアプリケーションと通信する APIのことです。
RESTful API
RESTの原則に従ったAPI。
ここで思うことは、「RESTって何???」
簡単に言うと、RESTは複数のソフトウェアを連携させるための設計原則の考え方のことです。
RESTの原則は以下の4つ。URIを通して提供できる情報が表現できていること = アドレス可能性
インターフェースが統一されていること = 統一インターフェース
やり取りする情報で全てを完結させること = ステートレス性
情報の内部に別の情報や状態へのリンクを含めることができること = 接続性これらに従って設計すれば、使いやすくAPIになります。
では、実際にWebAPIで意識することを見ていきます。
URI
WebAPIなので、URIは必須になります。
ここでは、REST原則のアドレス可能性が鍵になります。
URIはユーザーが実際に入力するものなので、わかりやすく、どんな機能を持つURIがひと目でわかるようにしたほうがベストです。例として、犬の種別の一覧を取得するAPIを設計します。
GET https://example.com/api/get-dogs-type-all-listこれでは、読みにくく、直感的ではありません。(何の機能なのかはなんとなくわかる気がする。。。)
dogs
とtype
が同列に並んでいるので、階層的にわかりづらいですし、list
とall
では意味がほぼ同じなので、重複してます。
また、WebAPIを使用するユーザーが想定なので、URIにわざわざget
という単語を入れなくてもいいです。(後述のHTTPメソッドより)なので、階層的にわかりやすく、シンプルにしたほうが使いやすそうですね。
以下のように修正します。GET https://example.com/api/dogs/type/list
dogs
のtype
のlist
を取得できると直感的にわかるようになったと思います。(多分)HTTPメソッド
REST原則の統一インターフェースに一番密な要素です。
標準化されたHTTPメソッドの個々の役割を理解して、それらをどのAPIでも適用するものです。基本的なHTTPメソッドは、以下。
HTTPメソッド 役割 GET リソースを取得する、検索する POST リソースを作成する、追加する PUT リソースのデータを更新する DELET リソースを削除する もし、POSTで「リソースのデータを更新する」役割をも持たせたとして、
記事を投稿するAPI
と記事の内容を更新するAPI
があったとします。2つのAPIを以下のように設計します。
POST /api/articles POST /api/articles/:article_id上記の設計だと、同じアプリケーションに「リソースを作成する」
POST
と「リソースのデータを更新する」POST
が存在してしまいます。これでは、
POST
に関して同じ意味をなさなくなるので、インターフェースが統一されていないことになり、使いづらいAPIになってしまいます。この例では、HTTPメソッドの役割に沿って、
記事の内容を更新するAPI
はPUTで行うようにしたほうがいいと思います。HTTPレスポンス
APIを使用して返ってくるものにもしっかりと制約をつけ、インターフェースを統一しましょう。
(HEADは割愛させていただきます。すいません。)ステータスコード
200
とか、404
とか、500
とかのあれです。
ステータスコード単体でも意味を成しているので、HTTPメソッド同様、理解する必要があります。よく目にするステータスコードは以下。
ステータスコード 意味 200 リクエストが成功した 201 リクエストが成功し、リソースが新規作成された 400 リクエストパラメータに不足・不備があった 401 アクセストークンが無効、認証されていない 404 リクエストされたリソースが存在しなかった 500 何らかのエラーがサーバー側で発生している WebAPIの設計において、基本的には想像しうる状態を全て網羅しなくてはいけません。
網羅した上で、必要なステータスコードはなんなのかを考えていきましょう。ボディ
ユーザーが実際に扱うデータのフォーマットや構造を考えて置く必要があります。
最近のWebAPIは、JSONで返すことが多いです。
JSON
のフォーマットにしたがっていけば問題なさそうですね。レスポンスのボディは2種類あると思います。
リクエストが成功した場合
とリクエストが失敗した場合
です。リクエストが成功した場合
ユーザーが欲しい情報を
JSON
のフォーマットに沿って構造化していきます。今回、個人的に意識したことは、階層構造です。
例として、ユーザー一覧を取得するAPIの
JSON
を考えます。
ユーザーを取得する際に、住所の情報も欲しいとします。もし、住所の情報に、
prefecture
とcity
が欲しいとき、
以下のように階層ごとにグループ化することで、わかりやすく、データもまとまります。users.json{ { "id": 1 "first_name": "AAAA", "middle_name": null, "last_name": "BBBB", "sex": "man", // 住所として一つのグループにする。 "address": { "prefecture": "東京都", "city": "新宿区" }, "favorite_tags": ["Ruby", "Rails", "WebAPI"] "url": "https://example.com/users/1" }, { "id": 2 "first_name": "CCCC", "middle_name": "1111", "last_name": "DDDD", "sex": "woman", // 同列だとわかりづらい "prefecture": "東京都" "city": "渋谷区" "favorite_tags": [] "url": "https://example.com/users/2" }, }リクエストが失敗した場合
ユーザーに何のエラーなのか、どうして起きたのかを正しく提供しなくてはいけません。
ユーザー一覧を取得するAPIのパラメーターに、ソートをかけるとします。(例:favorites_tagsで"Ruby"を持っているユーザーの一覧)
ここで間違えて、"Ruby"ではなく、数字を入れてしまったときのレスポンスは、
以下のようにする必要があると思います。https://example.com/api/users?favorites_tags=65847{ "message": "Invalid Request", "errors": [ { "type": "bad_type", "message": "favorites_tags is String, is not Integer" } ] }エラーが複数存在する場合は、グループ化して、わかりやすくするのも一つの手ですね。
Railsでは、、、
Webアプリを作成するために最適な
Rails
でも当然、WebAPIの実装はできます。RailsでのAPI実装
Railsには、APIモードなるものが存在していて、プロジェクトを新規作成する場合は、
$ rails new my_api --apiでAPIモードになります。
また、既存のアプリをAPI専用にしたい場合は、
config/application.rb
にてconfig/application.rbconfig.api_only = trueを設定するとできるみたいです。(超便利!!)
ただ、今回は既存のプロジェクトの一部でAPIが実装されているので、上二つのやり方はできませんでした。
RailsのAPI実装の特徴は、APIモードではない、いつも通りのRailsとあまり変わらずに実装することができることです。
MVC
のView層が使用されないくらいで、あとは同じ感じでコードを書けるので、楽にかけます。
ただ、APIではいつもよりも制限がかかっているので、仕様については理解しないといけないところがあります。今回の実装で詰まった、
ActionController::API
を簡単に説明します。ActionController::API
APIモードにする際に、
app/application.rb
を以下のように変更する必要があります。app/application.rb# ActionController::Base → ActionController::APIに変更する。 class ApplicationController < ActionController::API endこれでAPI専用になるのですが、、、
このActionController::API
は、ActionController::Base
をAPI専用にしたものであることを認識しないといけませんでした。上記でいつも通りに開発できると言いましたが、厳密には少し違います。
このActionController::APIのリファレンスのこの部分。
Keep in mind that templates are not going to be rendered
APIモードでも、
render
メソッドでhtmlやxmlのフォーマットを指定すれば、読み出せます。しかし、template
の読み出しはできないため、jsonをviewとして扱うことが結構難しくなっています。まとめ
APIを実装する上では、ユーザー目線に立つことが何よりも大事だと思いました。
実際に使う人の目線に立って、使いづらくないか、わかりやす構造になっているかを気にしてAPIを組み立てていく必要があると思います。RailsでWebAPIを実装するときは、ドキュメントを読んで仕様を理解する必要があります。
便利な機能が揃っていて、ブラウザ向けアプリケーションの開発とあまり大差ないので楽にかける分、ブラウザ向けアプリケーションでできていたことができないことみたいな制約もあるので、注意が必要です。参考
以下、参考にしたもの。
書籍
サイト
Rails API
API設計
PORTについて
「世界中に、アタリマエとシアワセを。」 を目的に、就活や金融領域などの各産業領域に特化したメディアを展開しています。
- 投稿日:2020-12-04T15:58:46+09:00
$rails s でサーバーが動かない
開発環境
Ruby 2.5.0
Ruby on Rails 6.0.3.4
rbenv 1.1.2
macOS Big Sur v11.0.1エラー内容
rails s
とコマンドを打つと[WARNING] Could not load command "rails/commands/server/server_command". Error: uninitialized constant URI::Generic /Users/user/.rbenv/versions/2.5.0/lib/ruby/gems/2.5.0/gems/bootsnap-1.5.1/lib/bootsnap/load_path_cache/core_ext/active_support.rb:00in 'block in load_missing_constant' (同様の文が何行も出るため中略) /User/user/Desktop/rails_testbin/spring:15:in `<top (required)>' bin/rails:3:in `load' bin/rails:3:in `<main>' (See full trace by running task with --trace)上記のようなメッセージが出て、http://localhost:3000 が起動できない
解決方法
結論から言うと、Rubyのバージョンを2.5.0から2.7.0に変更したら動きました。
参考記事:
https://info-wcn.com/ruby-changeversion-mac/
https://qiita.com/kamillle/items/5a7befd0ebad47378832
https://qiita.com/chihiro/items/efdf8b88865b7a93971f1.Rubyのバージョン変更
rbenvを最新バージョンへとアップデート
$ brew upgrade rbenv現在のRubyのバージョンを確認
$ ruby -v ruby 2.5.0インストール済みのRubyのバージョンを確認
$ rbenv versions system * 2.5.0 (set by /Users/user/.rbenv/version)これで、現在自分が使っているRubyのバージョンは2.5.0であることが分かりました。
これを2.7.0へとアップデートしていきます。インストールできるRubyのバージョンを確認
$ rbenv install -l バージョンがずらっと表示されるRubyのインストール
$ rbenv install 2.7.0 # インストールするバージョンを2.7.0に指定 $ rbenv rehash $ rbenv versions system 2.5.0 * 2.7.0 (set by /Users/user/.rbenv/version)2.7.0が追加されたことを確認。
Rubyのバージョン切り替え(ローカル)
$ rbenv local 2.7.0 バージョンを指定 $ ls -a .ruby-versionが作成されているか確認 $ less .ruby-version 2.7.0と表示されるバージョンの確認から戻るときは、qを押して戻る
Rubyのバージョン切り替え(グローバル)
$ rbenv global 2.7.0 # バージョンを指定 2.7.0と表示される確認から戻るときは、ローカル同様qを押す
これで、使用するRubyのバージョン切り替えが完了しました。
2.Railsのバージョン変更
1の終了後、
$rails sと打つと(真似しなくてよい)、
rbenv: rails: command not found The `rails' command exists in these Ruby versions: 2.5.0このようになり、上手く動きません。
そこで、https://qiita.com/kamillle/items/5a7befd0ebad47378832 の「対処」を参考にして、新しくインストールしたruby環境にrailsをインストールする
$ gem update --system $ gem install bundler $ gem install rails # 各コマンド間、色々なものがインストールされるため時間がかかるかも $ rails --version # Rails 6.0.3.4と表示された(2020-12-04時点)これでRailsのバージョン変更が完了しました。
3.サーバーが動くか確認
試しに、デスクトップへrails_appという名前のアプリケーションを作成してみます。
$ cd Desktop $ rails new rails_appインストールが完了したら、
$ cd rails_appで移動し、
$ bundle installを実行。
最後に
$ rails sを実行すると
=> Booting Puma => Rails 6.0.3.4 application starting in development => Run `rails server --help` for more startup options Puma starting in single mode... * Version 4.3.7 (ruby 2.7.0-p0), codename: Mysterious Traveller * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://127.0.0.1:3000 * Listening on tcp://[::1]:3000 Use Ctrl-C to stopとなり、無事http://localhost:3000 にアクセスすることが出来ました!!
- 投稿日:2020-12-04T14:49:46+09:00
投稿データとユーザー情報の結びつけ方法
まず用意したのはpost.rb(models/post)
class Post < ApplicationRecord def user return User.find_by(id: self.user_id) end endそしてposts_controller
def show @post = Post.find_by(id: params[:id]) @user = @post.user end・ポイント
post(投稿)のところにuserインスタンスメソッドを用意してあげる。
そしてposts_controllerの処理したいアクションにさっき用意したuserのインスタンスメソッドを入れてあげる。
- 投稿日:2020-12-04T14:18:20+09:00
【Rails】注文・カート機能のER図について(初学者向け)
はじめに
本記事は、 駆け出しエンジニアの第一歩!AdventCalendar2020 4日目の記事です。
この記事では、注文・カート機能を実装する上で欠かせないER図の考え方について、自分が理解できなかったことをまとめてみました。
Railsを学習し始めて3ヶ月で、まだまだ理解が浅い部分があると思いますが、もし間違っているところ等ありましたら、ご指摘いただけると幸いです。対象
・注文・カート機能にはどんなテーブルが必要なのか分からない人
・それぞれのテーブルの役割が分からない人概要
ご紹介する注文機能の基本的な流れは、通販と同様ですが、商品をカートに入れる → 注文情報を入力する → 注文確定する、です。
それぞれの工程で自分が分からなかったことについて、解説します。
1. カートに商品を入れる
1-1.カートテーブルの成り立ち
まず、通販というのはユーザーが商品を購入する行為をおこなう場所です。そのため、どのユーザーがどんな商品を購入するのか、という風にユーザーと商品の間で関係性を持たせることが必要になります。しかし、ユーザーテーブルと商品テーブルは直接結ぶことができません。なぜなら、ユーザーはどの商品にも関係性がないからです。直接結び付けられる例として、ユーザーとブログの関係が挙げられます。
でも、ユーザーと商品を関連づけなければ、商品を買うことはできません。そこで、ユーザーと商品の間を取り持つテーブルが必要になってきます(ここではカートテーブルとします)。カートテーブルは、ユーザーと商品を結びつける上で必然的に発生した中間テーブルです。1-2. カートとは?
カートを理解するには、まず1つのカートは複数種類の商品をいっぱい入れておけるもの、というイメージを捨てましょう。上記のER図のカートをよく見ると、カートID1つに対して、商品IDやユーザーIDはそれぞれ1つずつ対応しています。なので、1つのカートには、1種類の商品が入ります。
2. 注文情報を入力する
2-1. 注文テーブルに商品情報を直接入れる
注文情報の入力では、主に配送先住所、送料、支払い方法などを決定します。ここで注意したいのが、商品も注文情報の一部だからという考えから、注文テーブルの中に商品を入れてしまわないことです。
2-2. 注文テーブルと商品テーブルを直接関連づける
注文テーブルと商品テーブルを直接関連づければいいという考えから、以下のようにしてみるのもよくありません。よくない点は、いくつかありますが、一例をあげると、サイト管理者が商品を登録する際に、注文idを設定しいなければいけないことです。注文idは、注文が完了した後に、事後的に決定するものであり、サイト管理者が事前に設定するのは、少しおかしな話です。
そこで、注文テーブルと商品テーブルを結びつけるためには、間に中間テーブル(注文詳細テーブル)を作る必要があります。
3. 注文確定ボタンをクリックする
カートと注文の仕組みについて解説してきましたが、この2つのテーブル同士の関係はまだありません。しかし、結論から言うと関係づけるというよりは、カートの情報を注文詳細へ移動させるということです。
カートの情報は、移動後に削除されるようにすれば、注文確定後にカート内は空にできます。通販でも注文確定後のカート内は、空になっていますよね。カートと注文詳細の決定的な違いは、注文が未確定のものか確定済みのものかということです。まとめ
自分は当初この仕組みが全く理解できていませんでした。初学者の方で理解に苦しんでいる人の力に少しでもなったら幸いです。
- 投稿日:2020-12-04T14:14:30+09:00
ユーザーと投稿の結びつけ「@を使ったlink_toの使い方」
<%=link_to(@user.name, "/users/#{@user.id}") %>
・概要説明
目的:ユーザーの投稿詳細を表示したいのでユーザーと投稿を結びつけたい。
ポイント:link_toのやり方は様々だが今回はpostsコントローラで定義した@userを使ってのことだったので少しややこしかった。
指定のcontroller(僕はcontrollers/posts_controller)
def show @user = User.find_by(id: @post.user_id) end解説
User.find_by(id: @post.user_id)のUserはMysquelなどで作ったデータベースのusers。
find_byはその指定した(User)データから入手するものを定義。
@postはデータベースの投稿要素。
userのidとpostのidを結びつけたいので@user = User.find_by(id: @post.user_id)とする。指定のview(僕はviews/posts/show.html.erb)に
<%= link_to("表示したいもの", "表示したい内容の場所") %>
例えば: <%=link_to(@user.name, "/users/#{@user.id}") %>とすればできます。
- 投稿日:2020-12-04T14:10:02+09:00
prawnで請求書っぽいPDFを作る
本記事はiCARE Advent Calender 2020 の5日目です。
はじめに
本記事ではprawnを用いた請求書っぽいPDFの作成方法を解説します。
RailsでPDF作成する場合だとwicked_pdfも使えますが、wicked_pdfがhtmlライクにPDF作成ができるのに対して、prawnはゴリゴリのDSLなので、学習コストがかかりますが、wicked_pdfよりも柔軟にPDFを作ることができるので、個人的にはprawnの方が好きだったりします。
セットアップ
今回の請求書PDFではテーブル表示を行いたいので、prawn-tableを使います。
# Gemfile gem 'prawn' gem 'prawn-table'でbundle install。
your_app $ bundle install主要なメソッドの紹介
請求書の作成の前に、今回用いる主要なメソッドを紹介したいと思います。
Prawn::Document#generate
https://prawnpdf.org/docs/0.11.1/Prawn/Document.html#method-c-generate
公式の説明文を引用します。
Creates and renders a PDF document.
When using the implicit block form, Prawn will evaluate the block within an instance of Prawn::Document, simplifying your syntax. However, please note that you will not be able to reference variables from the enclosing scope within this block.
PDFドキュメントを作成してレンダリングします。
暗黙的なブロックフォームを使用する場合、PrawnはPrawn :: Documentのインスタンス内のブロックを評価し、構文を簡素化します。 ただし、このブロック内の囲んでいるスコープから変数を参照することはできないことに注意してください。
PDFドキュメントの雛形を作成するメソッドですね。
オプションとして、ページサイズ(A4, B5等)や余白(上下左右)を指定することが出来ます。
# 例 Prawn::Document.generate( 'sample.pdf', page_size: 'A4', top_margin: 35, bottom_margin: 35, left_margin: 35, right_margin: 35 ) do |pdf| # 処理 endPrawn::Document#bounding_box
https://prawnpdf.org/docs/0.11.1/Prawn/Document.html#method-i-bounding_box
こちらも公式を引用
A bounding box serves two important purposes:
・ Provide bounds for flowing text, starting at a given point
・ Translate the origin (0,0) for graphics primitives
A point and :width must be provided. :height is optional. (See stretchyness below)bounding_box には、次の2つの重要な目的があります。
・ 特定のポイントから開始して、流れるテキストの境界を指定します
・ グラフィックスプリミティブの原点(0,0)を変換します
ポイントと:widthを指定する必要があります。 :heightはオプションです。PDF内にボックス要素を作成するのに用いるメソッドですね。
# 例 Prawn::Document.generate( 'sample.pdf', page_size: 'A4', top_margin: 35, bottom_margin: 35, left_margin: 35, right_margin: 35 ) do |pdf| pdf.bounding_box([50, 75], width: 200, height: 300) do # 処理 end end後ほど紹介しますが、テキストを書き込んだり、tableを作成して表示したりできます。
Prawn::Text#text
https://prawnpdf.org/docs/0.11.1/Prawn/Text.html#method-i-text
公式引用
If you want text to flow onto a new page or between columns, this is the method
to use. If, instead, if you want to place bounded text outside of the flow of a ?document (for captions, labels, charts, etc.), use Text::Box or its convenience method text_box.Draws text on the page. Prawn attempts to wrap the text to fit within your current bounding box (or margin_box if no bounding box is being used). Text will flow onto the next page when it reaches the bottom of the bounding box. Text wrap in Prawn does not re-flow linebreaks, so if you want fully automated text wrapping, be sure to remove newlines before attempting to draw your string.
テキストを新しいページまたは列間で流したい場合は、これが使用する方法です。 代わりに、ドキュメントのフローの外側に境界付きテキストを配置する場合(キャプション、ラベル、グラフなど)、Text :: Boxまたはその便利なメソッドtext_boxを使用します。
ページにテキストを描画します。 Prawnは、現在のバウンディングボックス(またはバウンディングボックスが使用されていない場合はmargin_box)内に収まるようにテキストを折り返そうとします。 テキストは、バウンディングボックスの下部に到達すると、次のページに流れます。 Prawnでのテキストの折り返しは改行をリフローしないため、完全に自動化されたテキストの折り返しが必要な場合は、文字列を描画する前に必ず改行を削除してください。
小難しいことが書いてありますが、要は文字をPDF内に記述したい時に使うメソッドになります。
# 例 Prawn::Document.generate( 'sample.pdf', page_size: 'A4', top_margin: 35, bottom_margin: 35, left_margin: 35, right_margin: 35 ) do |pdf| pdf.bounding_box([50, 75], width: 200, height: 300) do pdf.text 'サンプルだよ' pdf.text 'サンプル 左寄りだよ', align: :left pdf.text 'サンプル 右寄りだよ', align: :right pdf.text 'サンプル 文字大きいよ', size: 30 end end例のように、文字の開始位置を決められたり、文字サイズを変更したりすることができます。
Prawn::Document#move_down
引用
https://prawnpdf.org/docs/0.11.1/Prawn/Document.html#method-i-move_down
Moves down the document by n points relative to the current position inside the current bounding box.
現在のbounding_box内の現在の位置を基準にして、ドキュメントをnポイント下に移動します。
ドキュメントを下に移動したい時に用いるメソッドです。
# 例 Prawn::Document.generate( 'sample.pdf', page_size: 'A4', top_margin: 35, bottom_margin: 35, left_margin: 35, right_margin: 35 ) do |pdf| pdf.bounding_box([50, 75], width: 200, height: 300) do pdf.text 'サンプルだよ' pdf.move_down(5) pdf.text 'サンプル 左寄りだよ', align: :left pdf.move_down(10) pdf.text 'サンプル 右寄りだよ', align: :right pdf.move_down(20) pdf.text 'サンプル 文字大きいよ', size: 30 end endテキストとテキストの間を空けたいときなんかに使います。
Prawn::Table#table
Quote from the official Doc
If a block is passed to methods that initialize a table (Prawn::Table.new, Prawn::Document#table, Prawn::Document#make_table), it will be called after cell setup but before layout. This is a very flexible way to specify styling and layout constraints. This code sets up a table where the second through the fourth rows (1-3, indexed from 0) are each one inch (72 pt) wide:
pdf.table(data) do |table| table.rows(1..3).width = 72 end表形式でデータを表示したい場合に使えるメソッドになります。
# 例 Prawn::Document.generate( 'sample.pdf', page_size: 'A4', top_margin: 35, bottom_margin: 35, left_margin: 35, right_margin: 35 ) do |pdf| pdf.table( [['小計', "11000円"], ['消費税', "1000円"], ['合計金額', "11000円"]], column_widths: [50, 100], position: :right ) do |table| table.cells.size = 10 end end請求書を作成する
お待たせしました。今まで紹介したメソッドを用いて、
簡単な請求書めいたPDFを作成したいと思います。invoice_pdf_exporter.rbの作成
app/services下にinvoice_pdf_exporter.rbを作成し、
以下のコードを記述します。require 'prawn' class InvoicePdfExporter FONT_PATH = Rails.root + 'public/fonts/任意のフォントファイル.ttf' def initialize # Prawnドキュメントを生成 # ページサイズやマージンを指定 Prawn::Document.generate( Rails.root + 'invoice.pdf', page_size: 'A4', top_margin: 35, bottom_margin: 35, left_margin: 35, right_margin: 35 ) do |pdf| # フォントを指定しないと Prawn::Errors::IncompatibleStringEncoding 例外が発生する pdf.font FONT_PATH # 本文の生成 self.create_contents pdf end end endfontメソッドは初出なので公式Docから引用します。
Prawn::Document#font
https://prawnpdf.org/docs/0.11.1/Prawn/Document.html#method-i-font
Without arguments, this returns the currently selected font. Otherwise, it sets the current font. When a block is used, the font is applied transactionally and is rolled back when the block exits.
引数がない場合、これは現在選択されているフォントを返します。 それ以外の場合は、現在のフォントを設定します。 ブロックが使用されると、フォントはトランザクションで適用され、ブロックが終了するとロールバックされます。
ドキュメントに記載がありませんが、fontの指定をしないで実行すると、
Prawn::Errors::IncompatibleStringEncoding
の例外が発生します。サンプルコードではpublicディレクトリに配置していますが、app/assets/fontsでもいいと思います。
フォントファイルは、
https://fontfree.me/
に無料フォントがありますので、お好きなものをお使いください。今回はほのか明朝を使います。
create_contentsメソッドを実装する
では具体的な処理を記述します。
コード全晒しです。
def create_contents(doc) # bunding_boxメソッドでボックスを生成 # 引数にはボックス生成位置、横、縦のサイズを指定 doc.bounding_box([50, 750], width: 300, height: 150) do # textメソッドでテキストを挿入。引数には文字サイズとalignを指定できる。:left, :right, :center doc.text "〒123-4567", size: 10, align: :left # move_downメソッドで次のテキストの書き出し位置を下げている doc.move_down 10 doc.text "東京都新宿区新宿1-1-1", size: 10, align: :left doc.move_down 10 doc.text "ご担当者 様", size: 12, align: :left end doc.bounding_box([300, 750], width: 300, height: 150) do doc.text 'HogeHoge株式会社', size: 12, align: :left doc.move_down 5 doc.text '〒103-0021', size: 10, align: :left doc.move_down 5 doc.text '大阪府大阪市中央区中央1-1-1', size: 10, align: :left doc.move_down 5 doc.text '大阪中央ビル4F 経理部経理課', size: 10, align: :left doc.move_down 5 doc.text 'TEL: 03-1234-5678', size: 10, align: :left end doc.text '請求書', size: 20, align: :center doc.bounding_box([0, 550], width: 300, height: 60) do doc.text '下記の通りご請求申し上げます。', size: 10, align: :left doc.move_down 10 doc.text "合計金額 11,000円", size: 16, align: :left end doc.bounding_box([320, 550], width: 300, height: 60) do doc.text "日付: 2020年10月01日", size: 10, align: :left end rows = [['詳細', '数量', '単価', '金額'], ['雑費', '1', '10000', '10000']] # tableメソッドでテーブルを生成する # rowsは多重配列 # 多重配列でない場合 Prawn::Errors::InvalidTableData 例外が発生する doc.table(rows, column_widths: [370, 30, 60, 60], position: :center) do |table| # セルのサイズの指定 table.cells.size = 10 # 1行目のalignを真ん中寄せにしている table.row(0).align = :center end doc.bounding_box([0, 300], width: 300, height: 100) do doc.text "振込期限 202X年10月31日", size: 10, align: :left doc.move_down 10 doc.text '振込先 日本銀行 本店 0000', size: 10, align: :left doc.move_down 10 doc.text ' 普通: 1234567', size: 10, align: :left end doc.bounding_box([373, 300], width: 150, height: 100) do doc.table [['小計', "11000円"], ['消費税', "1000円"], ['合計金額', "11000円"]], column_widths: [50, 100], position: :right do |table| table.cells.size = 10 end end endサンプルコードなので金額や配列の中身をベタ書きにしていますが、
initializeメソッドの引数にデータを渡してやれば、動的なPDFを作成することができます。あとはコンソール上で実行してやりましょう。
your_app $ rails c $ InvoicePdfExporter.new紹介していないけど便利なメソッド
Prawn::Document#start_new_page
Creates and advances to a new page in the document.
Page size, margins, and layout can also be set when generating a
new page. These values will become the new defaults for page creationドキュメント内の新しいページを作成して進みます。
ページサイズ、余白、およびレイアウトは、生成時に設定することもできます。
新しいページ。 これらの値は、ページ作成の新しいデフォルトになります次のページを作成するメソッドです。
Prawn::Document.generate( 'sample.pdf', page_size: 'A4', top_margin: 35, bottom_margin: 35, left_margin: 35, right_margin: 35 ) do |pdf| pdf.table( [['小計', "11000円"], ['消費税', "1000円"], ['合計金額', "11000円"]], column_widths: [50, 100], position: :right ) do |table| table.cells.size = 10 end pdf.start_new_page pdf.text '次のページですよ' endPrawn::Image#image
Add the image at filename to the current page. Currently only
JPG and PNG files are supported. (Note that processing PNG
images with alpha channels can be processor and memory intensive.)ファイル名の画像を現在のページに追加します。
JPGおよびPNGファイルがサポートされています。 (PNGの処理に注意してください
アルファチャネルを備えた画像は、プロセッサとメモリを大量に消費する可能性があります。)PDF内に写真を貼るメソッドです。横縦の幅や寄せる位置などを指定できます。
Prawn::Document.generate("image2.pdf", :page_layout => :landscape) do pigs = "#{Prawn::BASEDIR}/data/images/pigs.jpg" image pigs, :at => [50,450], :width => 450 dice = "#{Prawn::BASEDIR}/data/images/dice.png" image dice, :at => [50, 450], :scale => 0.75 endまとめ
今回紹介したメソッドの他にも様々な機能がありますので、
詳しく知りたい方はドキュメントをご覧ください!https://prawnpdf.org/manual.pdf
iCAREテックブログもよろしくね!!
- 投稿日:2020-12-04T14:01:05+09:00
これから投稿していきます。
Web開発エンジニアを目指すためにいまから頑張ります。
まずは、Ruby、Railsを用いたポートフォリオの作成に挑みます。
事前知識がないので少しアウトプット
ブラウザにプログラムを反映するためにルーティング→コントローラ→ビューの順を踏む
▼ルーティング
コントローラー、ビューがどういった定義で順で実行されていくか
定める役割▼コントローラー
ルーティングを参照しどのビューを実行するか決める役割▼ビュー
具体的なHTMLファイルなど実行されるプログラムを格納する場所
- 投稿日:2020-12-04T13:11:34+09:00
Railsで新規アプリケーションを作成した際、mysql2のインストール中にエラーが発生した時の解決策
環境
OS:AmazonLinux2
Ryby:ruby 2.6.3p62 (2019-04-16 revision 67580) [x86_64-linux]
Rails:Rails 5.0.0問題
データベースをmysqlに指定してRailsアプリケーションを新規作成した際、mysql2のGemのインストール中にエラーが発生した。
結論
DBクライアントというパッケージをインストールしていなかったことが原因だった。
やったこと
以下コマンドで、データベースをmysqlに指定して、新規アプリケーションを作成
$ rails new (アプリケーション名) -d mysql
$ rails new shift-management -d mysql create create README.md create Rakefile create config.ru create .gitignore create Gemfile create app create app/assets/config/manifest.js create app/assets/javascripts/application.js create app/assets/javascripts/cable.js create app/assets/stylesheets/application.css create app/channels/application_cable/channel.rb create app/channels/application_cable/connection.rb create app/controllers/application_controller.rb create app/helpers/application_helper.rb create app/jobs/application_job.rb create app/mailers/application_mailer.rb create app/models/application_record.rb create app/views/layouts/application.html.erb create app/views/layouts/mailer.html.erb create app/views/layouts/mailer.text.erb create app/assets/images/.keep create app/assets/javascripts/channels create app/assets/javascripts/channels/.keep create app/controllers/concerns/.keep create app/models/concerns/.keep create bin create bin/bundle create bin/rails create bin/rake create bin/setup create bin/update create config create config/routes.rb create config/application.rb create config/environment.rb create config/secrets.yml create config/cable.yml create config/puma.rb create config/spring.rb create config/environments create config/environments/development.rb create config/environments/production.rb create config/environments/test.rb create config/initializers create config/initializers/application_controller_renderer.rb create config/initializers/assets.rb create config/initializers/backtrace_silencers.rb create config/initializers/cookies_serializer.rb create config/initializers/cors.rb create config/initializers/filter_parameter_logging.rb create config/initializers/inflections.rb create config/initializers/mime_types.rb create config/initializers/new_framework_defaults.rb create config/initializers/session_store.rb create config/initializers/wrap_parameters.rb create config/locales create config/locales/en.yml create config/boot.rb create config/database.yml create db create db/seeds.rb create lib create lib/tasks create lib/tasks/.keep create lib/assets create lib/assets/.keep create log create log/.keep create public create public/404.html create public/422.html create public/500.html create public/apple-touch-icon-precomposed.png create public/apple-touch-icon.png create public/favicon.ico create public/robots.txt create test/fixtures create test/fixtures/.keep create test/fixtures/files create test/fixtures/files/.keep create test/controllers create test/controllers/.keep create test/mailers create test/mailers/.keep create test/models create test/models/.keep create test/helpers create test/helpers/.keep create test/integration create test/integration/.keep create test/test_helper.rb create tmp create tmp/.keep create tmp/cache create tmp/cache/assets create vendor/assets/javascripts create vendor/assets/javascripts/.keep create vendor/assets/stylesheets create vendor/assets/stylesheets/.keep remove config/initializers/cors.rb run bundle install The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies.... Fetching rake 13.0.1 Installing rake 13.0.1 Using concurrent-ruby 1.1.7 Fetching i18n 1.8.5 Installing i18n 1.8.5 Fetching minitest 5.14.2 Installing minitest 5.14.2 Using thread_safe 0.3.6 Fetching tzinfo 1.2.8 Installing tzinfo 1.2.8 Fetching activesupport 5.0.7.2 Installing activesupport 5.0.7.2 Using builder 3.2.4 Using erubis 2.7.0 Using mini_portile2 2.4.0 Using nokogiri 1.10.10 Using rails-dom-testing 2.0.3 Using crass 1.0.6 Fetching loofah 2.8.0 Installing loofah 2.8.0 Using rails-html-sanitizer 1.3.0 Fetching actionview 5.0.7.2 Installing actionview 5.0.7.2 Using rack 2.2.3 Using rack-test 0.6.3 Fetching actionpack 5.0.7.2 Installing actionpack 5.0.7.2 Fetching nio4r 2.5.4 Installing nio4r 2.5.4 with native extensions Using websocket-extensions 0.1.5 Using websocket-driver 0.6.5 Fetching actioncable 5.0.7.2 Installing actioncable 5.0.7.2 Using globalid 0.4.2 Fetching activejob 5.0.7.2 Installing activejob 5.0.7.2 Using mini_mime 1.0.2 Using mail 2.7.1 Fetching actionmailer 5.0.7.2 Installing actionmailer 5.0.7.2 Fetching activemodel 5.0.7.2 Installing activemodel 5.0.7.2 Using arel 7.1.4 Fetching activerecord 5.0.7.2 Installing activerecord 5.0.7.2 Fetching bindex 0.8.1 Installing bindex 0.8.1 with native extensions Using bundler 1.17.3 Fetching byebug 11.1.3 Installing byebug 11.1.3 with native extensions Fetching coffee-script-source 1.12.2 Installing coffee-script-source 1.12.2 Fetching execjs 2.7.0 Installing execjs 2.7.0 Fetching coffee-script 2.4.1 Installing coffee-script 2.4.1 Using method_source 1.0.0 Using thor 1.0.1 Fetching railties 5.0.7.2 Installing railties 5.0.7.2 Fetching coffee-rails 4.2.2 Installing coffee-rails 4.2.2 Fetching ffi 1.13.1 Installing ffi 1.13.1 with native extensions Fetching jbuilder 2.10.1 Installing jbuilder 2.10.1 Fetching jquery-rails 4.4.0 Installing jquery-rails 4.4.0 Fetching rb-fsevent 0.10.4 Installing rb-fsevent 0.10.4 Fetching rb-inotify 0.10.1 Installing rb-inotify 0.10.1 Fetching listen 3.0.8 Installing listen 3.0.8 Fetching mysql2 0.4.10 Installing mysql2 0.4.10 with native extensions Gem::Ext::BuildError: ERROR: Failed to build gem native extension. current directory: /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/mysql2-0.4.10/ext/mysql2 /home/ec2-user/.rvm/rubies/ruby-2.6.3/bin/ruby -I /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0 -r ./siteconf20201203-7896-3nmivb.rb extconf.rb checking for rb_absint_size()... yes checking for rb_absint_singlebit_p()... yes checking for ruby/thread.h... yes checking for rb_thread_call_without_gvl() in ruby/thread.h... yes checking for rb_thread_blocking_region()... no checking for rb_wait_for_single_fd()... yes checking for rb_hash_dup()... yes checking for rb_intern3()... yes checking for rb_big_cmp()... yes checking for mysql_query() in -lmysqlclient... yes checking for mysql.h... no checking for mysql/mysql.h... no ----- mysql.h is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again. ----- *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary libraries and/or headers. Check the mkmf.log file for more details. You may need configuration options. Provided configuration options: --with-opt-dir --without-opt-dir --with-opt-include --without-opt-include=${opt-dir}/include --with-opt-lib --without-opt-lib=${opt-dir}/lib --with-make-prog --without-make-prog --srcdir=. --curdir --ruby=/home/ec2-user/.rvm/rubies/ruby-2.6.3/bin/$(RUBY_BASE_NAME) --with-mysql-dir --without-mysql-dir --with-mysql-include --without-mysql-include=${mysql-dir}/include --with-mysql-lib --without-mysql-lib=${mysql-dir}/lib --with-mysql-config --without-mysql-config --with-mysql-dir --without-mysql-dir --with-mysql-include --without-mysql-include=${mysql-dir}/include --with-mysql-lib --without-mysql-lib=${mysql-dir}/lib --with-mysqlclientlib --without-mysqlclientlib To see why this extension failed to compile, please check the mkmf.log which can be found here: /home/ec2-user/.rvm/gems/ruby-2.6.3/extensions/x86_64-linux/2.6.0/mysql2-0.4.10/mkmf.log extconf failed, exit code 1 Gem files will remain installed in /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/mysql2-0.4.10 for inspection. Results logged to /home/ec2-user/.rvm/gems/ruby-2.6.3/extensions/x86_64-linux/2.6.0/mysql2-0.4.10/gem_make.out An error occurred while installing mysql2 (0.4.10), and Bundler cannot continue. Make sure that `gem install mysql2 -v '0.4.10' --source 'https://rubygems.org/'` succeeds before bundling. In Gemfile: mysql2 run bundle exec spring binstub --all bundler: command not found: spring Install missing gem executables with `bundle install`mysql2がインストールできないと言われる
原因
どうやらDBクライアントをインストールしていないことが原因だった
参考:AmazonLinux2にMariaDBをインストールする解決策
DBクライアントをインストール(パッケージインストール)
$ sudo yum install mariadb-devel Loaded plugins: extras_suggestions, langpacks, priorities, update-motd 220 packages excluded due to repository priority protections Resolving Dependencies --> Running transaction check ---> Package mariadb-devel.x86_64 3:10.2.10-2.amzn2.0.3 will be installed --> Finished Dependency Resolution Dependencies Resolved ================================================================================================================================================================================================ Package Arch Version Repository Size ================================================================================================================================================================================================ Installing: mariadb-devel x86_64 3:10.2.10-2.amzn2.0.3 amzn2extra-lamp-mariadb10.2-php7.2 1.0 M Transaction Summary ================================================================================================================================================================================================ Install 1 Package Total download size: 1.0 M Installed size: 4.5 M Is this ok [y/d/N]: y Downloading packages: mariadb-devel-10.2.10-2.amzn2.0.3.x86_64.rpm | 1.0 MB 00:00:00 Running transaction check Running transaction test Transaction test succeeded Running transaction Installing : 3:mariadb-devel-10.2.10-2.amzn2.0.3.x86_64 1/1 Verifying : 3:mariadb-devel-10.2.10-2.amzn2.0.3.x86_64 1/1 Installed: mariadb-devel.x86_64 3:10.2.10-2.amzn2.0.3 Complete!インストール完了(mysql-develでも同じみたい)
もう一度bundle installすると
$ bundle install The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`. Fetching gem metadata from https://rubygems.org/............ Fetching gem metadata from https://rubygems.org/. Resolving dependencies.... Using rake 13.0.1 Using concurrent-ruby 1.1.7 Using i18n 1.8.5 Using minitest 5.14.2 Using thread_safe 0.3.6 Using tzinfo 1.2.8 Using activesupport 5.0.7.2 Using builder 3.2.4 Using erubis 2.7.0 Using mini_portile2 2.4.0 Using nokogiri 1.10.10 Using rails-dom-testing 2.0.3 Using crass 1.0.6 Using loofah 2.8.0 Using rails-html-sanitizer 1.3.0 Using actionview 5.0.7.2 Using rack 2.2.3 Using rack-test 0.6.3 Using actionpack 5.0.7.2 Using nio4r 2.5.4 Using websocket-extensions 0.1.5 Using websocket-driver 0.6.5 Using actioncable 5.0.7.2 Using globalid 0.4.2 Using activejob 5.0.7.2 Using mini_mime 1.0.2 Using mail 2.7.1 Using actionmailer 5.0.7.2 Using activemodel 5.0.7.2 Using arel 7.1.4 Using activerecord 5.0.7.2 Using bindex 0.8.1 Using bundler 1.17.3 Using byebug 11.1.3 Using coffee-script-source 1.12.2 Using execjs 2.7.0 Using coffee-script 2.4.1 Using method_source 1.0.0 Using thor 1.0.1 Using railties 5.0.7.2 Using coffee-rails 4.2.2 Using ffi 1.13.1 Using jbuilder 2.10.1 Using jquery-rails 4.4.0 Using rb-fsevent 0.10.4 Using rb-inotify 0.10.1 Using listen 3.0.8 Fetching mysql2 0.4.10 Installing mysql2 0.4.10 with native extensions Fetching puma 3.12.6 Installing puma 3.12.6 with native extensions Fetching sprockets 3.7.2 Installing sprockets 3.7.2 Using sprockets-rails 3.2.2 Fetching rails 5.0.7.2 Installing rails 5.0.7.2 Fetching sass-listen 4.0.0 Installing sass-listen 4.0.0 Fetching sass 3.7.4 Installing sass 3.7.4 Fetching tilt 2.0.10 Installing tilt 2.0.10 Fetching sass-rails 5.0.7 Installing sass-rails 5.0.7 Fetching spring 2.1.1 Installing spring 2.1.1 Fetching spring-watcher-listen 2.0.1 Installing spring-watcher-listen 2.0.1 Fetching turbolinks-source 5.2.0 Installing turbolinks-source 5.2.0 Fetching turbolinks 5.2.1 Installing turbolinks 5.2.1 Fetching uglifier 4.2.0 Installing uglifier 4.2.0 Fetching web-console 3.7.0 Installing web-console 3.7.0 Bundle complete! 15 Gemfile dependencies, 62 gems now installed. Use `bundle info [gemname]` to see where a bundled gem is installed. Post-install message from sass: Ruby Sass has reached end-of-life and should no longer be used. * If you use Sass as a command-line tool, we recommend using Dart Sass, the new primary implementation: https://sass-lang.com/install * If you use Sass as a plug-in for a Ruby web framework, we recommend using the sassc gem: https://github.com/sass/sassc-ruby#readme * For more details, please refer to the Sass blog: https://sass-lang.com/blog/posts/7828841無事mysql2がインストールされました。
以下に、Railsアプリケーションの作成時の手順を書いているので参考にしてみてください
- 投稿日:2020-12-04T12:56:27+09:00
Vue.js チュートリアル for Rails エンジニア(Vue3 version)
この記事は クラウドワークス Advent Calendar 2020 の9日目の記事です。
はじめに
この記事は、普段 Rails を使用して開発を行なっているエンジニアが、 Vue.js (Vue3) を触り始めようとする時に見て役に立ったら嬉しいものです。
今回は Vue3 での開発を想定したものになります。 Vue2 での開発に関して知りたい方は下記の記事をご覧ください。
Vue.js チュートリアル for Rails エンジニア(Vue2 version)また、 Vue.js 自体を触るのは今回が初めてという方は、先に下記のようなチュートリアル記事を参照されることをお勧めします。
Vue.js を vue-cli を使ってシンプルにはじめてみる対象読者
- Rails に関しての知識がある程度ある方(Rails チュートリアルをやっていれば OK)
- Vue.js に関しての知識がある程度ある方(なんらかのチュートリアルなどをやっていれば OK。Vue3 の知識は必須でないです)
- Rails + Vue3 のアプリケーションをどうやって開発すればいいかを知りたい方
作るもの
今回は Webpacker を使用して Rails + Vue3 の簡単なアプリケーションを作っていきます。
そもそも Webpacker を使用しない手法だったり、Docker を使ったり、 TypeScript を使ったりといったことは本稿では扱わないのでご了承ください。
(Vue.js を使った開発のとっかかりとしてのチュートリアルという位置付けにしたいため)作成予定の画面は ↓ にあるような、簡単なテーブル構造を持つ画面になります。
サンプルコードこちらに置いています: https://github.com/t0yohei/rails-vue3-app
では、早速環境構築からチュートリアルに入っていきましょう。
環境構築
環境構築には、
homebrew
,rbenv
を使用します。インストールがまだな方は、各自インストールをお願いします。
- rbenv: https://github.com/rbenv/rbenv#installation
- homebrew: https://brew.sh/index_ja
使用する環境
- macOS: 10.15.7 (Catalina)
- Ruby: 2.7.2
- Rails: 6.0.3
- Webpacker: 4.3.0
- yarn: 1.22.10
- Vue: 3.0.3
※ macOS を除いて、記事作成時点で最新のバージョンになります。
アプリケーションを作成するディレクトリを作成
はじめに、これからアプリケーションを実装していくディレクトリを
rails-vue3-app
という名前で作成します。今回はこのrails-vue3-app
がアプリケーションの名前になります。$ mkdir rails-vue3-app $ cd rails-vue3-apprbenv を使用して Ruby 2.7.2 をインストール
Ruby 2.7.2 をインストールします。
$ brew update && brew install ruby-build // または brew update && brew upgrade ruby-build $ rbenv install 2.7.2 $ rbenv local 2.7.2 $ rbenv rehash $ ruby -v ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]yarn 1.22.10 のインストール
yarn をインストールします。
今回は 1.22.10 を前提に進めていきますが、 1.22.10 以上であれば基本問題ないはずです。$ brew install yarn // または brew upgrade yarn $ yarn -v 1.22.10Vue.js devtools (Vue3 用)のインストール
Installation | Vue Devtools から devtools をインストールします。
以前に Vue.js devtools をインストールしている場合(Vue2 の Vue.js devtools を使っていた場合)、Vue3 の Vue.js devtools とは併用できなくなっています(2020 年 12 月時点)。
そのため、拡張機能の設定から既存の Vue.js devtools (Vue2 用) を OFF にする必要があります。
Chrome の場合はchrome://extensions/
を開き、 Vue2 の Vue.js devtools を OFF にしてください。以上で環境構築は終わりです。次からはプロジェクトの作成を進めていきます。
プロジェクトの作成
カレントディレクトリ(rails-vue3-app)を起点に Rails プロジェクトを作成します。
$ bundle init $ echo 'gem "rails", "~> 6.0.3"' >> Gemfile $ bundle install --path vendor/bundle $ bundle exec rails new . --skip-turbolinks --skip-coffee Overwrite (略)/rails-vue3-app/Gemfile? (enter "h" for help) [Ynaqdhm] y※
--skip-turbolinks --skip-coffee
のオプションをつけて、今回は不要となるものを削ぎ落としています。 1Vue2 では、
rails new . --webpack=vue
として実行していたのですが、Vue3 ではオプションが提供されていないため--webpack=vue
のオプションを付けずに実行します。 2Vue3 のセットアップ
--webpack=vue3
といった形での Vue3 のセットアップができないため、独自で Vue3 のセットアップを行っていきます。Vue3 用の各種パッケージをインストール
$ yarn add --dev vue@next vue-loader@next @vue/compiler-sfcインストール完了後、
package.json
は以下のようになっているはずです(細かいバージョンはインストールのタイミングで違ってきます)package.json{ "name": "rails_vue3_app", "private": true, "dependencies": { "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "4.3.0", "@vue/compiler-sfc": "^3.0.3", "vue": "^3.0.3", "vue-loader": "^16.0.0" }, "version": "0.1.0", "devDependencies": { "webpack-dev-server": "^3.11.0" } }Webpack の設定
次に
.vue
の拡張子がついた Vue ファイルを、 Webpack でコンパイルするための設定を追加します。
config/webpack/environment.js
を開いてみると、現状は次のような状態になっているはずです。config/webpack/environment.jsconst { environment } = require('@rails/webpacker') module.exports = environment以下のように書き変えて、 Vue ファイルに対して
vue-loader
を使用するように指定します。config/webpack/environment.jsconst { environment } = require('@rails/webpacker') + const { VueLoaderPlugin } = require('vue-loader') + + environment.plugins.prepend( + 'VueLoaderPlugin', + new VueLoaderPlugin() + ) + + environment.loaders.prepend('vue', { + test: /\.vue$/, + use: [{ + loader: 'vue-loader' + }] + }) module.exports = environment
補足: ファイルを分けて定義することも可能
config/webpack/loaders/vue.js
を作成して、 loader の定義ファイルを切り出すことも可能です。config/webpack/loaders/vue.jsmodule.exports = { test: /\.vue(\.erb)?$/, use: [{ loader: 'vue-loader' }] }config/webpack/environment.jsconst { environment } = require('@rails/webpacker') + const { VueLoaderPlugin } = require('vue-loader') + const vue = require('./loaders/vue') + environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin()) + environment.loaders.prepend('vue', vue) module.exports = environment
config/webpacker.yml
のextensions
に.vue
を追加します。config/webpacker.ymldefault: &default # ... extensions: # ... + - .vue以上で Webpack の設定は終わりです。
一番シンプルな Rails + Vue を使った画面の実装
環境構築や設定周りの変更が完了したので、ここからは実際にアプリケーションの実装に入っていきます。
最初に、 Rails + Vue を使った一番シンプルな画面を実装をしていきます。コントローラーの作成
まずは
rails g controller
コマンドでコントローラーやルーティングを作成します。$ bundle exec rails g controller HelloVue index --no-helper --no-assets※ 今回は、ヘルパーやアセットファイルなど不要なものを生成しないオプションを指定しています。 3
一旦サーバーを起動して、画面が表示するか見てみましょう。
$ bundle exec rails shttp://localhost:3000/hello_vue/index にアクセスして、下記のページが表示されると ok です。
javascript_pack_tag を使用して、 js ファイルを読み込めるようにする
javascript_pack_tag
を使用することでapp/javascript/packs
配下にあるファイルの読み込みが可能になります(この辺は Webpacker の設定によるもの)。
今回は、読み込む対象としてこの後作成するhello_vue.js
を指定します。app/views/hello_vue/index.html.erb<h1>HelloVue#index</h1> <p>Find me in app/views/hello_vue/index.html.erb</p> + <%= javascript_pack_tag 'hello_vue.js' %>
index.html.erb から読み込む js ファイルを作成
先ほど作成した
index.html.erb
から読み込む js ファイルを作成します。app/javascript/packs/hello_vue.jsdocument.addEventListener("DOMContentLoaded", () => { console.log('this is hello_vue.js') });再度 http://localhost:3000/hello_vue/index にアクセスして開発者ツールを開くと、 Console に
this is hello_vue.js
と出力されます。Vue ファイルを作成
次に画面表示に使用するシンプルな Vue ファイル(Vue コンポーネント)を作成します。
app/javascript/app.vue<template> <p> {{ message }} </p> </template> <script> export default { data() { return { message: "Hello Vue!" } } } </script> <style scoped> p { font-size: 2em; text-align: left; } </style>
補足: Composition API を使って書く場合
Composition APIは、 Vue3 で新たに提供された API です。 Composition API を使うことで、大規模な Vue アプリケーションにおけるコンポーネントの実装をシンプルに保つことができます。今回はそもそもコンポーネントが大きくないので、Composition API を使うメリットはそんなにありませんが、 Composition API を使用した実装をする場合は下記のようになります。
app/javascript/app.vue<template> <p> {{ message }} </p> </template> <script> import { ref } from 'vue' export default { setup() { const message = ref('Hello World') return { message } } } </script> <style scoped> p { font-size: 2em; text-align: left; } </style>余談ですが、
data
関数など使用した従来の Vue コンポーネントの実装方法は Options API を使用した実装と呼ばれています。
app コンポーネントを
index.html.erb
にマウントする最後に、先ほど作成した
app.vue
のコンポーネントをindex.html.erb
にマウントします。まずはマウント対象の要素を
index.html.erb
に作成します。app/views/hello_vue/index.html.erb<h1>HelloVue#index</h1> <p>Find me in app/views/hello_vue/index.html.erb</p> <%= javascript_pack_tag 'hello_vue.js' %> + <div id='vue-app'></div>
この要素に対して、 App コンポーネントをマウントする実装を
hello_vue.js
に追加します。app/javascript/packs/hello_vue.js+ import { createApp } from "vue"; + import App from "../app.vue"; document.addEventListener("DOMContentLoaded", () => { - console.log('this is hello_vue.js') + const app = createApp(App); + app.mount("#vue-app"); });http://localhost:3000/hello_vue/index にアクセスしてみると、下記のような画面が表示されるはずです。
この時点で、一番の基本となる Rails + Vue のアプリケーション実装が完了しました
。
bin/webpack を使用した js, vue ファイルの事前ビルド
今までのやり方では、 http://localhost:3000/hello_vue/index にアクセスがあった時点で、毎回 js, vue ファイルのビルドを行うため、画面が表示されるのに少し時間がかかってしまいます。
下記のコマンドを実行することで、app/JavaScript
配下の js, vue ファイルを事前にビルドしておくことができます。$ bin/webpack
rails server を立ち上げ直した後に http://localhost:3000/hello_vue/index に再度アクセスすると、先ほどよりも早く画面が表示されるようになるはずです。
※ 必ず rails server を立ち上げ直してください
bin/webpack
コマンドの詳細について知りたい方はこちら: https://qiita.com/chimame/items/8d3d6f4afea675cffa7dwebpack の自動ビルド
先ほどは、
bin/webpack
のビルドを行いましたが、これでは js, vue ファイルを変更するたびに再度コマンドを叩いてビルドをする必要があります。
それでは面倒なので、開発中はbin/webpack
の代わりに下記コマンドを実行して、ファイルを保存するたびに自動ビルドが走るようにしておくといいでしょう(rails server
を実行しているタブとは別のタブで実行してください)。$ bin/webpack-dev-server
Vue.js devtools
せっかくなので
Vue.js devtools
の使い方をここで確認しておきます。
使い方は簡単で、 chrome の developer ツールを開いて、 Vue のタブを選択するだけです。
Vue のタブを選択し、コンポーネントを選択してみると、内部の data などを確認できます。
※ Vue.js devtools がうまく表示されない場合は、
chrome://extensions/
で複数の Vue.js devtools が ON になっていないかを確認してください。あるいは、 chrome の developer ツールを開き直してみてください。実践的な実装
先ほど実装では、本番アプリケーションを実装する上で必須になってくるバックエンド(controller など)からのデータの受け渡しを行なっていなかったので、ここからはその点を深掘っていきます。
Webpacker を使用した、 Rails + Vue のアプリケーションを作成する際、データの渡し方には色々あるのですが、今回は個人的にデータフローがわかりやすかった以下の 2 通りの方法でサンプルアプリケーションを作っていきます。
- 1. HTML のデータ属性に値を設定して渡す方法
- 2. API を使用して渡す方法
1. HTML のデータ属性に値を設定して渡す方法
まずはページを表示するためのコントローラーを、
HomeController
という名前で作成します。$ bundle exec rails g controller Home index --no-helper --no-assets次にコントローラー内部の実装を進めていきます。
index
メソッドのインスタンス変数として、title
description
そして Hash 形式のcontents
を用意します。app/controllers/home_controller.rbclass HomeController < ApplicationController def index + @title = 'Home#index' + @description = 'トップページ' + @contents = get_contents end + + private + + def get_contents + { + outer_links: [ + { + name: 'Qiitaページ', + text: 'Qiita', + url: 'https://qiita.com/t0yohei/items/cd11b15642fbb26f71e2' + }, + { + name: 'ソースコード', + text: 'GitHub', + url: 'https://github.com/t0yohei/rails-vue3-app' + } + ], + } + end end続いて
view
の実装です。ここでポイントとなるのが、 Rails のcontent_tag
ヘルパーを利用して、div
タグのdata
属性に vue 側へ受け渡したいデータを設定している点です。 vue へ受け渡すデータはjson
形式に変換しておきます。app/views/home/index.html.erb- <h1>Home#index</h1> - <p>Find me in app/views/home/index.html.erb</p> + <%= javascript_pack_tag 'home/index.js' %> + <%= content_tag :div, + id: "homeIndex", + data: { + title: @title, + description: @description, + contents: @contents + }.to_json do %> + <% end %>※
data
属性に設定した情報は、 developer tool などを使うことで閲覧可能です。そのため API 同様にユーザーのプライペート情報など、秘匿情報は公開しないよう注意してください。data 属性に設定した値を読み込む js ファイルを作成
先ほど view で設定したデータを、 js 側から読み取ってみましょう。
app/javascript/packs/home/index.js
を下記の通り実装します。app/javascript/packs/home/index.jsdocument.addEventListener("DOMContentLoaded", () => { const node = document.getElementById("homeIndex"); const initialData = JSON.parse(node.getAttribute("data")); console.log(initialData); });http://localhost:3000/home/index にアクセスして、 developer tool の console を開けてみます。
このように、 Rails の view ファイルで設定したデータが、 object として取得できていることがわかります。
この object のデータを使用して、実装を進めていきましょう。読み取った値を表示する Vue コンポーネントの実装
props として
initialData
を受け取る Index コンポーネントを作成します。app/javascript/components/home/Index.vue<template> <div> <h1>{{ initialData.title }}</h1> <p>{{ initialData.description }}</p> <table class="contents-table"> <tr> <th>名前</th> <th>リンク</th> </tr> <tr v-for="outer_link in initialData.contents.outer_links" v-bind:key="outer_link.name"> <td>{{ outer_link.name }}</td> <td> <a v-bind:href="outer_link.url">{{ outer_link.text }}</a> </td> </tr> </table> </div> </template> <script> export default { props: { initialData: { type: Object, default: () => {} } }, }; </script> <style scoped> .contents-table { border: 1px solid gray; margin: 10px; } .contents-table th, .contents-table td { border: 1px solid gray; } </style>
補足: data プロパティを設定する場合
今回の実装では、 props で受け取った値をそのまま画面に表示するだけなので今の実装で十分ですが、props の値は immutable (書き換え不可)なので任意のタイミングで値を変更したりできません。
(どこかのボタンを押すとdescription
の値を変更させる、といった処理はできません。)そう言った場合は、 props で受け取った値を初期値として data 属性を設定することで、 mutable な state (書き換え可能な値)を設定することができます。
app/javascript/components/home/Index.vue<template> <div> - <h1>{{ initialData.title }}</h1> - <p>{{ initialData.description }}</p> + <h1>{{ title }}</h1> + <p>{{ description }}</p> <table class="contents-table"> <tr> <th>名前</th> <th>リンク</th> </tr> - <tr v-for="outer_link in initialData.contents.outer_links" v-bind:key="outer_link.name"> + <tr v-for="outer_link in contents.outer_links" v-bind:key="outer_link.name"> <td>{{ outer_link.name }}</td> <td> <a v-bind:href="outer_link.url">{{ outer_link.text }}</a> </td> </tr> </table> </div> </template> <script> export default { props: { initialData: { type: Object, default: () => {} } }, + data() { + return { + title: this.initialData.title, + description: this.initialData.description, + contents: this.initialData.contents + } + }, }; </script>
補足: data プロパティの設定を spread 構文を使って書く場合
spread 構文を使うことで、 data プロパティ設定の処理をよりシンプルに書くことができます。
app/javascript/components/home/Index.vue... <script> export default { props: { initialData: { type: Object, default: () => {} } }, data() { return { ...this.initialData } }, }; </script> ...
補足: data プロパティの設定を Composition API を使って書く場合
toRefs をでの分割代入を使用して実装します。
app/javascript/components/home/Index.vue... <script> import { toRefs } from 'vue' export default { props: { initialData: { type: Object, default: () => {} } }, setup(props) { const { title, description, contents } = toRefs(props.initialData) return { title, description, contents } } }; </script> ...
Index コンポーネントをマウントする
index.js
で Index コンポーネントに props として initialData を渡し初期化し4、index.html.erb
の div 要素にマウントします。app/javascript/packs/home/index.js+ import { createApp } from "vue"; + import Index from "../../components/home/Index.vue"; document.addEventListener("DOMContentLoaded", () => { const node = document.getElementById("homeIndex"); const initialData = JSON.parse(node.getAttribute("data")); + const app = createApp(Index, { initialData: initialData }) + app.mount("#homeIndex"); - console.log(initialData); });ページの表示
app/javascript/packs
配下に追加した js ファイルを読み込ませるためには、webpack-dev-server
の再起動が必要です。webpack-dev-server
を実行中の場合は一度停止し、再度下記コマンドを実行しましょう。$ bin/webpack-dev-server
http://localhost:3000/home/index にアクセスした時、下記のようなページが表示されていると ok です。
実装のリファクタリング
とりあえず表示させることを優先で、
Index.vue
に全てを書いていたので、簡単にコンポーネントの分割します。分割のイメージは下記になります。
ちょっとずつ実践的な実装になってきましたね。
HeaderView.vue の作成
Index コンポーネントの
title
,description
部分を切り出した HeaderView コンポーネントを作成します。このコンポーネントは、他のコンポーネントでも使いやすいように、1 階層上 (app/javascript/components
直下) に配置します。app/javascript/components/HeaderView.vue<template> <div> <h1>{{ title }}</h1> <p>{{ description }}</p> </div> </template> <script> export default { props: { title: { type: String, default: () => "" }, description: { type: String, default: () => "" } } }; </script> <style></style>Contents.vue の作成
次に、 Index コンポーネントの
table
部分を切り出した Contents コンポーネントを作成します。app/javascript/components/home/Contents.vue<template> <div> <table class="contents-table"> <tr> <th>名前</th> <th>リンク</th> </tr> <tr v-for="outer_link in contents.outer_links" v-bind:key="outer_link.name"> <td>{{ outer_link.name }}</td> <td> <a v-bind:href="outer_link.url">{{ outer_link.text }}</a> </td> </tr> </table> </div> </template> <script> export default { props: { contents: { type: Object, default: () => {} } } }; </script> <style scoped> .contents-table { border: 1px solid gray; margin: 10px; } .contents-table th, .contents-table td { border: 1px solid gray; } </style>Index.vue の修正
最後に、 Index コンポーネントで HeaderView コンポーネント、 Contents コンポーネントを使用する形に修正します。
app/javascript/components/home/Index.vue<template> <div> - <h1>{{ initialData.title }}</h1> - <p>{{ initialData.description }}</p> - <table class="contents-table"> - <tr> - <th>名前</th> - <th>リンク</th> - </tr> - <tr v-for="outer_link in initialData.contents.outer_links" v-bind:key="outer_link.name"> - <td>{{ outer_link.name }}</td> - <td> - <a v-bind:href="outer_link.url">{{ outer_link.text }}</a> - </td> - </tr> - </table> + <HeaderView v-bind:initialData.title="title" v-bind:description="initialData.description" /> + <Contents v-bind:contents="initialData.contents" /> </div> </template> <script> + import HeaderView from "../HeaderView.vue"; + import Contents from "./Contents.vue"; export default { + components: { + HeaderView, + Contents + }, props: { initialData: { type: Object, default: () => {} } }, }; </script> - <style scoped> - .contents-table { - border: 1px solid gray; - margin: 10px; - } - .contents-table th, - .contents-table td { - border: 1px solid gray; - } - </style> + <style scoped></style>Index コンポーネントがだいぶスッキリしましたね。
念の為、再度 http://localhost:3000/home/index にアクセスして、画面がちゃんと表示されることを確認しておきましょう。2. API を使用して渡す方法での実装
次に、API を使用して渡す方法での実装を進めていきましょう。
下記の手順で整数リテラルの分類表を作成してみます。
- 2-1. API を叩くコンポーネントの実装
- 2-2. API エンドポイントの実装
- 2-3. コンポーネントから API を叩いてデータを取得
- 2-4. 取得したデータをコンポーネント内で描画
2-1. API を叩くコンポーネントの実装
まずはページを表示するためのコントローラーを作成します。
$ bundle exec rails g controller IntegerLiteralDescriptions index --no-helper --no-assetsコントローラーの実装
今回は何もしないです。
View の実装
javascript_pack_tag
と、コンポーネントをマウントするための div 要素を追加します。app/views/integer_literal_descriptions/index.html.erb- <h1>IntegerLiteralDescriptions#index</h1> - <p>Find me in app/views/integer_literal_descriptions/index.html.erb</p> + <%= javascript_pack_tag 'integerLiteralDescriptions/index.js' %> + <div id="integerLiteralDescriptionsIndex"></div>コンポーネントの実装
index.html.erb
にマウントする Index コンポーネントと Contents コンポーネントを追加します。app/javascript/components/integerLiteralDescriptions/Index.vue<template> <div> <HeaderView v-bind:title="title" v-bind:description="description" /> <Contents v-bind:contents="contents" /> </div> </template> <script> import HeaderView from "../HeaderView.vue"; import Contents from "./Contents.vue"; export default { components: { HeaderView, Contents }, data() { return { title: "title", description: "description", contents: [] }; } }; </script> <style scoped> </style>app/javascript/components/integerLiteralDescriptions/Contents.vue<template> <div> <table class="contents"> <tr> <th>名前</th> <th>英語訳</th> <th>表記例</th> <th>用途</th> </tr> <tr v-for="content in contents" v-bind:key="content.name"> <td>{{ content.name }}</td> <td>{{ content.english }}</td> <td>{{ content.sample }}</td> <td>{{ content.usage }}</td> </tr> </table> </div> </template> <script> export default { props: { contents: { type: Array, default: () => [] } } }; </script> <style scoped> .contents { border: 1px solid gray; } .contents th, .contents td { border: 1px solid gray; } </style>Vue コンポーネントをマウントする実装
Index コンポーネントと Contents コンポーネントを
index.html.erb
にマウントします。app/javascript/packs/integerLiteralDescriptions/index.jsimport { createApp } from "vue"; import Index from "../../components/integerLiteralDescriptions/Index.vue"; document.addEventListener("DOMContentLoaded", () => { const app = createApp(Index) app.mount("#integerLiteralDescriptionsIndex"); });再び
bin/webpack-dev-server
を実行し直し、 http://localhost:3000/integer_literal_descriptions/index にアクセスすると、下記画像のようなページが表示されます。これで API を叩いて取得したデータを受け取り、表示するためのコンポーネントが完成しました。
次は、先ほど作成したコンポーネントからリクエストを受け、レスポンスを返す処理を実装していきます。
2-2. API エンドポイントの実装
コントローラーを、 Api::V1::IntegerLiteralDescriptionsController という名前で作成します。
$ bundle exec rails g controller api/v1/integer_literal_descriptions index --no-helper --no-assets --no-view-specsコントローラーの実装
整数リテラルの分類表の json を返却する index メソッドを実装します。
app/controllers/api/v1/integer_literal_descriptions_controller.rbclass Api::V1::IntegerLiteralDescriptionsController < ApplicationController def index + title = 'IntegerLiteralDescriptions#index' + description = '整数リテラルの分類表' + contents = get_integer_literals + result_values = { + title: title, + description: description, + contents: contents + } + render json: result_values end -end + + private + + def get_integer_literals + [ + { + name: '10進数', + english: 'decimal', + sample: '42', + usage: '数値' + }, + { + name: '2進数', + english: 'binary digits', + sample: '0b0001', + usage: 'ビット演算など' + }, + { + name: '8進数', + english: 'octal', + sample: '0o777', + usage: 'ファイルのパーミッションなど' + }, + { + name: '16進数', + english: 'hexadecimal, hex', + sample: '0xEEFF', + usage: '文字のコードポイント、RGB値など' + } + ] + end +endアクセスの確認
この状態で、 http://localhost:3000/api/v1/integer_literal_descriptions/index にアクセスしてみると、下記のような画面が表示されるはずです。
一旦これで、 API のエンドポイントが完成しました。
2-3. コンポーネントから API を叩いてデータを取得
コンポーネントと API の Ajax 通信は axios というライブラリを使用します。5
まずはaxios
をインストールします。$ yarn add --dev axiosinstall に成功していると、
package.json
axios
の項目が追記されているはずです。package.json"devDependencies": { + "axios": "^0.21.0", "webpack-dev-server": "^3.11.0" }
次に
integerLiteralDescriptions/Index.vue
を書き換えて、 axios でのデータ取得を実装します。app/javascript/components/integerLiteralDescriptions/Index.vueimport Contents from "./Contents.vue"; +import Axios from "axios"; export default { components: { HeaderView, Contents }, data: function() { return { title: "title", description: "description", contents: [] }; + }, + + created: function() { + this.updateContents(); + }, + + methods: { + updateContents() { + Axios.get("/api/v1/integer_literal_descriptions/index.json").then( + response => { + const responseData = response.data; + console.log(responseData); + } + ); + } }
補足: Composition API を使って書く場合
app/javascript/components/integerLiteralDescriptions/Index.vue<script> import HeaderView from "../HeaderView.vue"; import Contents from "./Contents.vue"; import Axios from "axios"; import { ref, onMounted } from "vue"; export default { components: { HeaderView, Contents }, setup() { const title = ref('') const description = ref('') const contents = ref([]) const updateContents = () => { Axios.get("/api/v1/integer_literal_descriptions/index.json").then( response => { const responseData = response.data; console.log(responseData); } ); } onMounted(updateContents) return { title, description, contents } } }; </script>
created
で vue コンポーネントが作成されたタイミングでaxios
によるデータ取得を走らせるようにしています。
console.log
によって、取得したデータが出力されているはずなので、 http://localhost:3000/integer_literal_descriptions/index を見てみましょう。
↓ のようなログが出て、データ取得に成功しているはずです。
Tips: JavaScript のデバッグ
ご存知な方も多いと思いますが、 JavaScript では debugger を仕込むことで、デバッグ実行が可能になります。
debugger ステートメント | MDN具体的には下記のように仕込むことができます。
app/javascript/components/integerLiteralDescriptions/Index.vueAxios.get("/api/v1/integer_literal_descriptions/index.json").then( response => { const responseData = response.data; - console.log(responseData); + debugger; } );開発者ツールを開きながら、再度先ほどの http://localhost:3000/integer_literal_descriptions/index にアクセスしてみると、
debugger
を仕込んだ部分で処理が止まり、Console
からその時点の各種データを覗くことができます。(↑ 画像の場合、画像下部でresponseData
の値を確認しています。)
2-4. 取得したデータをコンポーネント内で描画
先ほど
axios
で取得したデータを、画面に反映させます。
今回の場合、下記の部分を書き換えるだけです。app/javascript/components/integerLiteralDescriptions/Index.vueAxios.get("/api/v1/integer_literal_descriptions/index.json").then( response => { const responseData = response.data; - console.log(responseData); + this.title = responseData.title; + this.description = responseData.description; + this.contents = responseData.contents; } );http://localhost:3000/integer_literal_descriptions/index にアクセスしてみると、整数リテラル分類表が出てくるはずです。
Tips: 整数リテラルとは
JavaScript で表現できる 整数リテラル には 10 進数、2 進数、8 進数、16 進数があります。
JavaScript では 0 で始まる数値の直後にb, o, x
をつけることで、それぞれ 2 進数、8 進数、16 進数が表現できます。
b, o, x
は英語訳を見てみると、それぞれ binary digits, octal, hex となっており、それぞれの頭文字から来ていることがわかります。今回の実装で整数リテラル分類表をサンプルに組み込んだ理由はただの思いつきです。
リンクボタンを追加
せっかくなので、 home/index から http://localhost:3000/integer_literal_descriptions/index に飛べるよう、リンクボタンを追加しておきましょう。
app/controllers/home_controller.rburl: 'https://github.com/t0yohei/rails-vue-app' } ], + inner_links: [{ + label: '整数リテラル分類表', + url: url_for(action: 'index', controller: 'integer_literal_descriptions') + }] }
app/javascript/components/home/Contents.vue... </td> </tr> </table> + <div v-for="inner_link in contents.inner_links" v-bind:key="inner_link.label"> + <button v-on:click="changeLocation(inner_link.url)" class="btn-push">{{ inner_link.label }}</button> + </div> </div> </template> <script> export default { props: { contents: { type: Object, default: () => {} } + }, + methods: { + changeLocation(url) { + window.location.href = url; + } } }; </script> <style scoped> ... +.btn-push { + margin: 10px; + max-width: 180px; + text-align: left; + background-color: rgb(24, 174, 238); + font-size: 14px; + color: #fff; + text-decoration: none; + font-weight: bold; + padding: 10px 24px; + border-radius: 4px; + border-bottom: 4px solid rgb(24, 174, 238); +} +.btn-push:active { + transform: translateY(4px); + border-bottom: none; +} </style>
補足: Composition API を使って書く場合
app/javascript/components/home/Contents.vue<script> import { ref } from "vue"; export default { props: { contents: { type: Object, default: () => {} } }, setup(props) { const changeLocation = url => { window.location.href = url; } return { changeLocation } } }; </script>
http://localhost:3000/home/index を開いて、こんな感じのボタンができていたら ok です。
最後に
今回のチュートリアルは以上で終了です。
このチュートリアルで学習したのは Webpacker を使用した Vue + Rails アプリケーションの基本となる部分で、大きなアプリケーションに取り組むときにも、これからアプリケーションを作り始める時にも大きな武器になるはずです。
このチュートリアルが、 Rails や Vue を学習される方の助けに少しでもなっていれば幸いです。
では。参考
- rails/webpacker: Use Webpack to manage app-like JavaScript modules in Rails
- Vue.js 3 · Issue #2751 · rails/webpacker
- Vue3 on Rails - DEV
- はじめに | Vue.js
- メンテ不能になったフロントエンド環境を立て直す話
注釈
今回の実装では CoffeeScript と turbolinks のセットアップを省いています。既存の rails プロジェクトで Vue.js を使用する場合は、 turbolinks と戦う必要がありそうです。 ↩
Webpacker 6.0 でそもそも
--webpack=vue
といったオプション自体が廃止されるとのこと。ちなみの --webpack のオプションを指定しない場合でも、 rails new 時に default で webpacker:install が実行されます。 ↩createApp の第二引数に root props を渡せる機能を活用しています。 ↩
axios を利用した API の使用。 Ajax 通信をする手段としては、他のも fetch API などを使用する方法が有名です。 ↩
- 投稿日:2020-12-04T11:56:12+09:00
[Rails] アプリケーションの型作成 データベース作成 サーバー起動 の具体的方法
- 投稿日:2020-12-04T11:03:33+09:00
RailsのDBをmysqlに変える(メモ)
RailsでデフォルトのDB'SQlite'から'mysql'(ver8.0.22)に変更する際に詰まったので、
mysqlインストールから実際にDB作成をするところまでの手順をメモ。1.mysqlインストーラでmysqlをインストール。
http://dev.mysql.com/downloads/installer/
からインストーラをダウンロード。
2.ダウンロードしたインストーラを起動。
あとは流れに沿ってインストールしていく。(rootのパスワード設定等。)
そして、この流れの中で後にRailsからmysqlを起動しようとするとErrorになってしまう設定項目があった。それがこの設定。
ここで上の認証方式を選択してしまうと、認証方式が'caching_sha2_password'になるが、Railsではこの認証方式に対応していないらしい。(この認証方式はmysql8で使用可能。)
なので、下の設定を選択して認証方式を'mysql_native_password'にする必要があった。3.gemファイルでmysqlをinstall。
Gemfile# Use mysql as the database for Active Record gem "mysql2"bundel install4.railsアプリケーションのDB設定
config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: host: localhost timeout: 5000 development: adapter: mysql2 encoding: utf8 reconnect: false database: <%= ENV['DATABASE_DEV_NAME'] %> pool: 5 username: <%= ENV['DATABASE_DEV_USER'] %> password: <%= ENV['DATABASE_DEV_PASSWORD'] %> host: <%= ENV['DATABASE_DEV_HOST'] %> # 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: adapter: mysql2 encoding: utf8 reconnect: false database: アプリケーション名_test pool: 5 username: root password: <%= ENV['DATABASE_TST_PASSWORD'] %> host: localhost production: adapter: mysql2 encoding: utf8 reconnect: false database: アプリケーション名_production pool: 5 username: アプリケーション名 password: <%= ENV['アプリケーション名_DATABASE_PASSWORD'] %> host: localhostのように変更。
5.パスワードを環境変数で管理する。
dotenvというgemをinstall。Gemfilegem "dotenv-rails"bundle installアプリケーションのルートディレクトリに.envファイルを作成し、その中に環境変数を設定する。
.envDATABASE_DEV_NAME = 'アプリケーション名_development' DATABASE_DEV_PASSWORD = 'パスワードを記入' DATABASE_DEV_USER = 'MySQLユーザー名を記入' DATABASE_DEV_HOST = 'localhost' DATABASE_TST_PASSWORD = 'パスワードを記入'.gitignoreファイルに.envを追加する。
.gitignore# Ignore env config /.env※mysqlユーザを作成しておく。
ここで
rails db:create完了。
※mysqlユーザを作成する。
1.mysqlに接続する。
コマンドプロンプトを管理者権限で開き、以下を実行する。net start mysql80mysql -u root -pmysqlインストール時に設定したパスワードを入力。
2.実際にmysqlユーザを作成する。
mysql> create user 'test-user'@'localhost' identified by 'password';3.mysqlユーザに権限を与える。
mysql> grant all on *.* to 'test-user'@'localhost';
- 投稿日:2020-12-04T11:02:24+09:00
RailsのSystem SpecでJavaScriptエラーを検出する方法
はじめに
最近のWebサイトであれば、JavaScriptを利用していないページを探すほうが難しいかと思います。
そんな中で単純にRailsのSystem Specによる検証だけだと、テストを通ったのでリリースしたら実は以下のようなJavaScriptエラーが発生していたみたいな見落としが起こることが時々ありますよね。解決方法としての理想はJavaScriptが影響する全ての箇所を網羅するようなテストを書くことかもしれませんが、現実的にはそこまで書けないというケースが多いかと思います。
また、全てを網羅していたつもりでも実際には漏れがあって本番環境のコンソールにJavaScriptエラーが表示されていたみたいなことは起こりえます。そこで今回の記事ではRailsのSystem SpecでJavaScriptエラーを検出する方法を紹介します。
設定例
spec/support/system_test.rbmodule Capybara module Matchers class JsNoErrorMatcher def matches?(page) errors = page.driver.browser.manage.logs.get(:browser) errors.find_all { |error| error.level == 'WARNING' }.each do |error| STDERR.puts 'WARN: javascript warning' STDERR.puts error.message end @severe_errors = errors.find_all { |error| error.level == 'SEVERE' } @severe_errors.empty? end def description 'have no js errors' end def failure_message @severe_errors.map(&:message).join("\n") end end def have_no_js_errors JsNoErrorMatcher.new end end end RSpec.configure do |config| config.include Capybara::Matchers, type: :system driven_by :selenium, using: :headless_chrome config.after(:each, type: :system) do |example| if example.metadata[:skip_js_error] # ログリセット page.driver.browser.manage.logs.get(:browser) else expect(page).to have_no_js_errors end end end※説明を簡単にする為に1ファイルにまとめて書いていますが実際は分けたほうが綺麗だと思います。
使い方
これまで通りSystem Specを実行することで、JavaScriptエラーが発生した場合には、発生したエラーメッセージが表示されてテストが失敗するようになります。
もしも外部のスクリプトに依存している等の理由により、エラーを無視するしかないみたいなケースでは以下のように書くことで特定のテストだけJavaScriptエラーの検証をスキップすることが出来ます。
it 'ログインできること', skip_js_error: true do visit root_path # ... endまとめ
というわけで、この記事では「System SpecでJavaScriptエラーを検出する方法」を紹介しました。
既存のテストに設定を足すだけで、JavaScriptエラーに気付けるようになったり、不安定なテストの原因が特定しやすくなったりと役立つことが多いかと思います。
- 投稿日:2020-12-04T10:48:40+09:00
【Rails】 備忘録:Deviseのモデル名を間違えて生成してしまったときの対処法
タイトルの通り、Rails Deviseのモデル名を間違えて生成してしまったときの対処法をメモします。
$rails generate devise MODEL #任意のモデル名このモデル名を間違えて生成してしまいました。
結果、以下のように複数ファイルが、間違えたモデル名で生成されました。Running via Spring preloader in process 12067 invoke active_record create db/migrate/20201204011415_devise_create_models.rb create app/models/model.rb invoke test_unit create test/models/model_test.rb create test/fixtures/models.yml insert app/models/model.rb route devise_for :models
ではこれを削除するにはどうするかというと
$rails destroy devise MODEL #任意のモデル名先ほどのgenerateの部分をdestroyにすればいいだけです。
これで先ほど尽かされたファイルや追記されたコードが削除されます。
ターミナルの結果にて確認できます↓Running via Spring preloader in process 12224 invoke active_record remove db/migrate/20201204011415_devise_create_models.rb remove app/models/model.rb invoke test_unit remove test/models/model_test.rb remove test/fixtures/models.yml route devise_for :models
もし、
$rails db:migrate
コマンドでデータベースを反映させてしまった場合はrollbackを実行して、データベースを元に戻してください。
と、その前にまずはどこまでmigrateされているか現在の状況を把握するところから始めます。
(現在の状況を知らずにとにかくrollbackを行うと、何をrollbackしてしまったのかわからなくなるからです。特にチームでやる場合は必須。)←だそうです(実務未経験です)このコマンドで確認します。
$rails db:version Current version: 2020112909502820201129095028(migrateした最新の日付)までmigrateされていることが確認できます。それでrollbackしても問題なければ、以下を実行します。
$ rails db:rollbackもう一度同じ手順で、正しいモデル名でモデルを生成し、$rails db:migrate したら完了です。
- 投稿日:2020-12-04T10:48:40+09:00
【備忘録】Rails Deviseのモデル名を間違えて生成してしまったときの対処法
タイトルの通り、Rails Deviseのモデル名を間違えて生成してしまったときの対処法をメモします。
$rails generate devise MODEL #任意のモデル名このモデル名を間違えて生成してしまいました。
結果、以下のように複数ファイルが、間違えたモデル名で生成されました。Running via Spring preloader in process 12067 invoke active_record create db/migrate/20201204011415_devise_create_models.rb create app/models/model.rb invoke test_unit create test/models/model_test.rb create test/fixtures/models.yml insert app/models/model.rb route devise_for :models
ではこれを削除するにはどうするかというと
$rails destroy devise MODEL #任意のモデル名先ほどのgenerateの部分をdestroyにすればいいだけです。
これで先ほど尽かされたファイルや追記されたコードが削除されます。
ターミナルの結果にて確認できます↓Running via Spring preloader in process 12224 invoke active_record remove db/migrate/20201204011415_devise_create_models.rb remove app/models/model.rb invoke test_unit remove test/models/model_test.rb remove test/fixtures/models.yml route devise_for :models
もし、
$rails db:migrate
コマンドでデータベースを反映させてしまった場合はrollbackを実行して、データベースも削除してください。
と、その前にまずはどこまでmigrateされているか現在の状況を把握するところから始めます。
(現在の状況を知らずにとにかくrollbackを行うと、何をrollbackしてしまったのかわからなくなるからです。特にチームでやる場合は必須。)←だそうです(実務未経験です)このコマンドで確認します。
$rails db:version Current version: 2020112909502820201129095028(migrateした最新の日付)までmigrateされていることが確認できます。それでrollbackしても問題なければ、以下を実行します。
$ rails db:rollbackもう一度同じ手順で、正しいモデル名でモデルを生成し、$rails db:migrate したら完了です。