- 投稿日:2020-12-16T23:56:04+09:00
[Ruby on Rails] macで環境構築をしよう
環境
macOS Catalina バージョン 10.15.7
今回インストールするバージョン
Ruby 2.6.6
Ruby on Rails 6.1.0
手順
Homebrew
のインストールHomebrewは、macOSオペレーティングシステム上のパッケージ管理システムのひとつです。
Rails
で必要なものをインストールするためにHomebrew
を使うので入っていない場合はインストールしましょう。$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"インストールされたか確認するコマンド
$ brew help
Homebrew
で使えるコマンド一覧が表示されればインストール成功です。
rbenv
のインストール
rbenv
は、複数のRubyのバージョンを管理し、プロジェクトごとにRubyのバージョンを指定して使うことを可能としてくれるツールです。$ brew install rbenv ruby-buildインストールされたか確認するコマンド
$ rbenv --version
rbenv 1.1.2
などと表示されればインストール成功です。(数字はバージョンによって異なります)次にターミナルを起動した際にrbenvの初期化を自動で行うように設定をします。
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile $ echo 'if which rbenv > /dev/null; then eval "$(rbenv init -)"; fi' >> ~/.bash_profile $ source ~/.bash_profile
Ruby
のインストールインストール可能なRubyのバージョン確認をします。
$ rbenv install -lインストールしたいバージョン(今回は
2.6.6
)が一覧に表示されていたらインストールを続けていきます。
他のバージョンをインストールする場合は2.6.6
の部分を置き換えればそのバージョンがインストールされます。rubyのインストールは少し時間がかかるので注意!
$ rbenv install 2.6.6インストールされたか確認するコマンド
$ rbenv versions
system
* 2.6.6このように表示されればインストール成功です。
次に
ruby
のバージョンを確認してみます。$ ruby -vrubyのバージョンを確認してみて
2.6.6
になっていない場合は、先程インストールした2.6.6
に切り替える作業をしていきます。$ rbenv global 2.6.6 $ rbenv rehash使用するバージョンを切り替えることができたのかを確認するコマンドを実行してみましょう。
$ ruby -v
ruby 2.6.6p146
このように表示されれば成功です。
bundlerをインストール
Gemとは?
ruby
のパッケージ管理ツールです。gemを容易に管理でき、gemを配布するサーバの機能も持ちます。例えばWebアプリケーションにユーザー登録やログイン機能などのユーザー認証機能を実装したい時、これらを一からコードを書くのはとても時間がかかってしまいますが、「devise」と呼ばれるgemをインストールするだけで簡単にユーザー認証で必要な機能が使えます。
bundler とは
bundlerとは、gemのバージョンやgemの依存関係を管理してくれるgemの一つです。 bundlerを使うことで、複数人での開発やgemのバージョンが上がってもエラーを起こさずに開発できます。
以下を実行することによって、bundlerをインストールできます。
インストールしていない場合はインストールしましょう。$ gem install bundlerインストールされたか確認するコマンド
$ bundler -vRailsのインストール
次に、
Rails
のインストールです。まだしていない場合はインストールしましょう。$ gem install railsインストールされたか確認するコマンド
$ rails -vRailsアプリケーションの開発
作成するアプリケーション用のディレクトリを作成します。今回はsampleという名前で作成します。
作成したらそのディレクトリの中に移動しましょう。$ mkdir sample $ cd sampleRailsで開発を始めるには、
rails new アプリケーション名
というコマンドを実行します。
このコマンドを実行することで、入力したアプリケーション名と同名のフォルダが作成され、その中に開発に必要なフォルダやファイルが用意されます
今回はsample_appという名前のアプリケーションを作成していきます。$ rails new sample_app開発中のアプリケーションをブラウザで表示するためには、サーバーを起動する必要があります。サーバーの起動は、以下のコマンドを実行するだけで完了です。
(サーバーを起動する場所に注意!!cd sample_app
でカレントディレクトリを変更して起動)$ rails sサーバーを起動した後、ブラウザで
localhost:3000
というURLにアクセスすると、Railsの初期画面が表示されるようになります。
- 投稿日:2020-12-16T23:47:25+09:00
[Rails] renderとredirect_toの違い
render
・部分テンプレートファイル
_form.html.erb
のように_から始まります。・ビューファイル内で部分テンプレートを呼び出すとき
<%= render partial: "form" %>
_と拡張子を除いた形で呼び出します。
partialは部分テンプレートを指定するオプションです。・部分テンプレート内で変数を使いたい時
<%= render partial: ‘form’, locals: { form: @form } %>
のように、localsオプションを用いて、
locals: { 部分テンプレート内で使いたい変数: 持っていきたい値 }
と記述します。・部分テンプレート内で繰り返し処理を行いたい時
<%= render partial: ‘form’, collection: @forms %>
collectionオプションに@formsと複数形の変数を渡すと、部分テンプレートでformが個別のインスタンスとして呼ばれる変数となり、さらにeach文を使用せずに繰り返し処理も行ってくれます。<%= render @forms %>
と略して書くことも可能です。redirect_to
redirect_to root_path
のように使います。違い
render → ビュー
redirect_to → ルーティング → コントローラー → ビュー表示されるまでのプロセスが違います。
- 投稿日:2020-12-16T23:42:39+09:00
FormObjectをうまく使いこなしたい
はじめに
Rails Advent Calendar 2020 16日目の記事です!
業務で毎日、Railsを触れていいコードが書けるように精進しています。
そんな中、今年一扱いに苦戦した(今も苦戦している)FormObjectについて、自分が考えることを共有しておきたいと思います!!
FormObjectとは
そもそもFormObjectについて、「なにそれ????」と思う方もいると思うでの簡単に紹介します!
簡単に言うと、FormObjectは、Railsのデザインパターン設計手法の1つです。
MVC
Railsの基本設計は、MVC(Model, View, Controller)です。
View
は、ユーザーからの入力を受け付けます。その情報をController
にわたし、Controller
はその情報を整形・制御します。
そして、Controller
は整形・制御した情報をModel
に対して渡してあげます。
Model
は、Controller
から受け取った情報をもとにDBにアクセスし、データの保存・変更・削除を行い、
その結果をController
やView
に返します。
返ってきた結果は、Controller
で整形・制御しView
に表示させたり、制御・整形せずそのまま表示したりします。これが基本的な
MVC
の流れですが、、、以下のようなことがあると思います。
- 一つの
View
で複数のModel
の属性を入力してもらい、DBを更新したい。Model
やDBの制限とは別にView
側で制限をかけたい。Controller
でのView
から受け取った情報の整形が複雑化してしまって、見辛くなった。 ...これらの実装をMVCだけで行おうとすると、
Controller
かModel
に責務が偏ってしまいます。
これでは、コードが追いづらくなったり、影響範囲が大きくなったりしてしまいます。そうすると、アプリの保守性が低いことになってしまうので、
新しい機能を追加したり、、、
修正したり、、、
DBの構造を変えたり、、するときに大変苦労な思いをすることになってしまいます。
それらを防ぐための手法の一つとして、
FormObject
という概念が存在します。メリット・デメリット
FormObject
を使用する一番の目的は、責務を切り離すことだと思います。
Controller
の肥大化の原因のひとつである、View
からの情報を整形するロジックをFormObject
でカプセル化する事で、
Controller
の責務が一つ減ることになります。
これでController
は、肥大化せずに済みますし、単一責務の状態になるので依存関係が少なくなり、保守性は高くなります。また、Modelの状態に関係ないロジックを
Model
にかくと、Model
が肥大化してしまいます。そういったロジックをFormObject
に書くことで、
Model
の責務が一つ減り、肥大化を防ぐことができます。このように、
Controller
やModel
に偏りがちなロジックをFormObject
にまとめることで、過剰責務にならずに済みます!!ただ、上記のことはデメリットにもなり得ると思います。
やたら滅多に、
FormObject
にロジックを集約させると、今度はFormObjectが肥大化してしまいます。
これでは本末転倒です。。。
Controller
とModel
のView
に関するロジックをFromObject
に任せて、責務を切り離すことを目的に使用しているにもかかわらず、
FormObject
の責務がでかくなってしまって、そこの保守性が低くなるのは良くありません。そのため、使用する前はしっかりと
FormObject
の責務を明確にしてから使用しましょう!FormObjectの責務とは???
FormObject
がなんなのか大体わかってきました。かなり便利な
FromObject
ですが、、、いろいろな記事をみて、実際に書いていく中で、ふと思ったことがあります。。。「ここってFomObjectでやるべき???」
「ControllerやModelのロジックとかぶってない???」
「FormObjectの責務って、、、結局どこまで持てばいいの???」これらを思ったきっかけを実際のコードを例に説明していきたいと思います。
FormObjectのvalidate
FormObjectを使用するとき、
ActiveModel::Model
をincludeしてればModel
同様にvalidateを書けることができます。
例えば、記事の登録でタグ情報も入力するフォームがあったとします。フォームでは、記事のタイトルと内容、タグの名前が必須であった場合、以下のようにかけます。
class ArticlePostForm include ActiveModel::Model attr_accessor :article_body, :article_title, :tag_name validates :article_body, presence: true validates :article_title, presence: true validates :tag_name, presence: true endこれでFormの入力に対して制限を設けることができました。ただ、記事とタグの
Model
にも以下のようなvalidateが設けてありました。class Article < ApplicationRecord validates :title, presence: true, uniqueness: true validates :body, presence: true validates :is_display, inclusion: [true, false] end class Tag < ApplicationRecord validates :name, presence: true, uniqueness: true end
FormObject
でのvalidateとModel
でのvalidateでは、意味は違います。
FormObject
でのvalidateでは入力された値に対する制限なのに対して、Model
でのvalidateではDBに保存する際の制限です。ただ、今回では検証している属性が
FormObject
とModel
でかぶってしまっているので、FormObjectのvalidateは必要なのか?
という疑問があります。
FormObjectでのsave・update
記事を見ているとよく見かけるものです。
以下のようにFormObject
でActiveRecordの作成を行うsave
メソッドがあります。class ConractForm include ActiveModel::Model attr_accessor :first_name, :last_name, :email, :body def save return false if invalid? contact = Contact.new(first_name: first_name, last_name: last_name, email: email, body: body) if contact.save true else errors.add(:base, contact.errors.full_messages) false end end end
Controller
では、以下のように書けるはずです。class ContactController < ApplicationController def new @contact = ContactForm.new end def create @contact = ContactForm.new(set_params) if @contact.save flash.now = 'お問い合わせを承りました' redirect_to :contacts else flash.alert = '送信に失敗しました' render :new end end private def set_params params.require(:contact).permit(:first_name, :last_name, :email, :body) end endここで、
FormObject
はModel
の生成・更新の役割を持っていいのかどうかに疑問を持ちました。
FormObject
は、入力に対してロジックを持たせるのが一般的だと思います(間違っていたらすいません。。。)ここで僕は
FomObject
に対しての責務で、「Viewの入力に対しての制御のみで
Model
の生成は別」と「validateをかけているからModel
の生成までがいい」の2通りの考え方できると思ったので、どちらに寄せるべきなのかを迷いました。
FormObjectのエラーハンドリング
Model
の生成において、validateによって発生したエラーをFormObject
で取り扱うかどうかがどうなのか疑問に思いました。
Model
のエラーとFormObject
のエラーは分けたほうがいいのか、一緒にすべきなのかが一番悩ましいところです。。。(ここは僕もまだ考えがまとまっていないので、知見が増えたらまた共有します。)
def save # ここはFormObjectの入力のエラー return false if invalid? # ここはModel生成時のエラー contact = Contact.new(first_name: first_name, last_name: last_name, email: email, body: body) if contact.save true else errors.add(:base, contact.errors.full_messages) false end endまとめ
FormObject
に対しての責務は、その都度考え、その責務以上のことをさせないように実装することが大事だと思っています。ただ、僕自身も
FormObject
を完璧に把握・理解しているわけではなく、他にも設計思想があるので、そこらへんと組み合わせた
FormObject
の考えもあるのかなと感じています。参考サイト
- 投稿日:2020-12-16T23:26:32+09:00
rails:db:migrateを実行すると「Directly inheriting from ActiveRecord::Migration is not supported.」と出た
<この記事について>
自作のアプリ製作中にカラムを追加する流れで「rails:db:migrate」を実行すると「Directly inheriting from ActiveRecord::Migration is not supported.」と表示されエラーに。
初歩的なミスでしたが備忘録として投稿![環境]
・Ruby 2.6.5,
・Rails 6.0.0
・macOSrake aborted! StandardError: An error has occurred, all later migrations canceled: Directly inheriting from ActiveRecord::Migration is not supported. Please specify the Rails release the migration was written for:調べてみると、原因はマイグレーションファイルがrailsのバージョンに対応していないことが原因のようでした。。
対象のマイグレーションファイルの1行目末尾にrailsのバージョンを追加。
class AddAttachmentImageToPosts < ActiveRecord::Migration[6.0] #←[6.0]を追加 def self.up change_table :posts do |t| t.attachment :image end end def self.down remove_attachment :posts, :image end endその後、再度以下を実行すると解決しました。
$ rails db:migarate<最後に>
実装してエラーが出る、思うような結果が得られないことは多々あると思います。
そんな時は冷静な気持ちでエラー文と丁寧に向き合うようにしましょう!
- 投稿日:2020-12-16T23:17:08+09:00
エラーメッセージの日本語化(rails)
rails-i18n(Gem)
日本語に対応できるようになるGemのこと。
localeファイル
エラーメッセージを全て日本語にするために必要なファイルで、さまざまな言語に対応できるファイルのこと。この中に日本語化用のファイルを作成することで、英語を日本語に翻訳できる。
実装方法
①日本語の言語設定
config/application.rbrequire_relative 'boot' require 'rails/all' Bundler.require(*Rails.groups) module Asakatu5757 class Application < Rails::Application config.load_defaults 6.0 config.i18n.default_locale = :ja config.time_zone = 'Tokyo' end end今回追加した記述は「config.i18n.default_locale = :ja」で、標準言語の日本語にすることができる。
②rails-i18nの導入
Gemfilegem 'rails-i18n'ターミナル% bundle install③locales内に日本語化用のファイルを作成し、編集する。
例)Userモデルのnicknameカラムを日本語化する場合。
config/locales/ja.ymlja: activerecord: attributes: user: nickname: ニックネーム
- 投稿日:2020-12-16T22:41:17+09:00
Railsでオブジェクト配列をハッシュ化するあれこれ(injectとかindex_byとか)
例えばUserモデルがあり、nameやらemailやらageやらを各オブジェクトが持っていたとする。
ある特定のageの人たちだけに施したい処理があって、どの年齢が対象となるかは既に配列のグループがあったとする。(若干例え悪いけどそんな感じだった)
最初、以下の様な処理を書いていた。
sample.rb# 対象としたい年齢群。 targets = [1,4,5,6,10,23] targets.each do |target| User.all.each do |user| # 一致するものがあれば次の処理へ next if target != user.age user.nextaction end endこれだとeachで二重に回すことになるので、もっと簡単にできないものかと考えた時に、ageをキーとするハッシュつくればいいのでは??となった。
そして最初、injectでハッシュを生成したのがこちら。
sample.rb# 対象としたい年齢群。 targets = [1,4,5,6,10,23] # ageをキーとしてUser.allをハッシュ化 users_sort_age = User.all.inject({}) {|hash,user| hash[user.age] = user; hash } targets.each do |target| # targetをキーとした(=targetと同じ値のageが存在した)場合次の処理に飛ばす next if users_sort_age[target].blank? user.nextaction endこれでもできるんだけど、index_byを使うともっとみやすくなりました。
sample.rb# 対象としたい年齢群。 targets = [1,4,5,6,10,23] # ageをキーとしてUser.allをハッシュ化をなんとこれだけで表している。 users_sort_age = User.all.index_by(&:age) targets.each do |target| next if users_sort_age[target].blank? user.nextaction end非常にみやすくなった。
ただしindex_byはActiveRecordでサポートされているものなので、デフォルトのRubyではinjectionを使いましょうという話。
- 投稿日:2020-12-16T22:11:35+09:00
Rails + Grape + Grape SwaggerでちゃんとOpenAPI/Swaggerドキュメンテーションしようとしたらハマったこと
この記事はCBcloud Advent Calendar 2020 の12日目の記事です。
Rails + Grape + Grape Swaggerで、実際に業務でOpenAPIドキュメントを生成してメンバーに共有しようとしたときにハマったことについて紹介します。
まずはおさらい: OpenAPI、Swaggerってなんだっけ?
OpenAPI
OpenAPIのリポジトリには下記のように記述されています。
https://github.com/OAI/OpenAPI-SpecificationThe OpenAPI Specification (OAS) defines a standard, programming language-agnostic interface description for HTTP APIs, which allows both humans and computers to discover and understand the capabilities of a service without requiring access to source code, additional documentation, or inspection of network traffic.
つまりOpenAPIは、プログラミング言語に依存しないHTTP APIのインタフェースを記述するための仕様です。
その仕様に従い記述されたドキュメントは、人が読んでも理解できるし、それを解析していい感じのUIを提供することもできるようになっている、ということです。Swagger
Swaggerのページには下記のように記述されています。
https://swagger.io/about/Swagger is a powerful yet easy-to-use suite of API developer tools for teams and individuals, enabling development across the entire API lifecycle, from design and documentation, to test and deployment.
〜略〜
Swagger started out as a simple, open source specification for designing RESTful APIs in 2010. Open source tooling like the Swagger UI, Swagger Editor and the Swagger Codegen were also developed to better implement and visualize APIs defined in the specification.
〜略〜
In 2015, the Swagger project was acquired by SmartBear Software. The Swagger Specification was donated to the Linux foundation and renamed the OpenAPIつまりSwaggerはAPI開発者のためのツール群を提供するプロジェクトということですね。そして、SwaggerのAPI定義のための仕様がLinux foundationに寄付され、OpenAPIに改名されたとのことです。
この記事が対象とする範囲
上記を踏まえ、この記事でやろうとしていること(そしてハマったこと)は、Rails + Grapeの構成で開発しているプロジェクトで、Grape SwaggerのDSLを使ってOpenAPI/Swagger仕様に沿ったJSONを生成することです。
また、その生成したJSONを読み込んで見やすくしてくれるツールで見たときに、意図した構成として表示されることを目指します(そしてハマります)使用したgem
ドキュメント生成にあたり使用したgemは下記の通りです。
gem 'grape' # RESTful APIを開発するためのDSLを備えたフレームワーク gem 'grape-swagger' # Grape APIからのドキュメント生成 gem 'grape-entity' # Grapeフレームワークにレスポンス整形のツールを加える gem 'grape-swagger-entity' # grape-entityからのドキュメント生成設定と使い方は各リポジトリをご参照のこと。
https://github.com/ruby-grape/grape
https://github.com/ruby-grape/grape-swagger
https://github.com/ruby-grape/grape-entity
https://github.com/ruby-grape/grape-swagger-entityハマったこと
1. descブロックのparamsと、descと同階層のparamsは別物
descと同階層のparamsではバリデーションをしてくれる
Grapeを使っているとき、下記のような書き方をすることがあると思います。
これはuser_nameは必須パラメータであることを指しており、実際にリクエストボディを空にしてリクエストを投げると、400エラーになってuser_name is missing
として返してくれます。class Users < Grape::API resources :params_in_same_layer do desc 'descと同じ階層にparamsを書いた場合' params do requires :user_name, type: String, documentation: { desc: 'ユーザ名', type: 'string' } optional :address, type: String, documentation: { desc: '届け先情報', type: 'string' } end post do present hoge: 'fuga' end end enddescブロック内のparamsではバリデーションはしてくれない
次にGrape Swaggerでドキュメンテーションしていこうとすると、下記のような記述方法が出てきます。
これにも先程と同様にリクエストボディ無しでリクエストを投げると、今度は400エラーにはならず201が返ってきます。class Users < Grape::API resources :params_whitin_desc_block do desc 'descブロック内にのみparamsを書いた場合' do params SimpleUserParamsEntity.documentation end post do present hoge: 'fuga' end end end class SimpleUserParamsEntity < Grape::Entity expose :user_name, documentation: { desc: 'ユーザ名(エンティティで定義)', type: 'string', required: true } expose :address, documentation: { desc: '住所(エンティティで定義)', type: 'string' } endただしUIで見ると見た目はほぼ同じ
上記をSwagger UIで見たときにどのような差があるか比べてみます。
どちらもユーザ名が必須であることは表現できています。しかし2つ目のエンドポイントでは実際には必須になっていないので注意が必要です。両方にparamsを書いたら、キーが一致していればdesc内のドキュメンテーションが優先されるが、一致していないものはそれぞれ出力される
それでは両方に書くとどうなるでしょうか。さらにそれぞれでしか定義されていない、age、blood_typeというキーを追加してみました。
class Users < Grape::API resources :params_in_same_layer_and_desc_block do desc 'descブロック内と、descの同階層の両方にparamsを書いた場合' do params SimpleUserParamsEntity.documentation end params do requires :user_name, type: String, documentation: { desc: 'ユーザ名', type: 'string' } optional :address, type: String, documentation: { desc: '届け先情報', type: 'string' } optional :age, type: Integer, documentation: { desc: '年齢', type: 'string' } end post do present hoge: 'fuga' end end end class SimpleUserParamsEntity < Grape::Entity expose :user_name, documentation: { desc: 'ユーザ名(エンティティで定義)', type: 'string', required: true } expose :address, documentation: { desc: '住所(エンティティで定義)', type: 'string' } expose :blood_type, documentation: { desc: '血液型(エンティティで定義)', type: 'string' } endSwagger UIで見るとこうなります。キーが一致している場合は、ドキュメンテーションはdesc内のものが優先され、一致していない場合は、それぞれ出力されるようです。
2. 複雑な構成のリクエストをUIでちゃんと表示しようと頑張ったが、結局は使用するUIの挙動次第
複雑なパラメータのパターンでUIがどうなるか確かめてみます。またdescブロック内に記述した場合と同階層に書いたエンドポイントを用意します。
class Users < Grape::API resources :complex_params_in_same_layer do desc 'descと同じ階層にparamsを書いた場合' params do requires :user_name, type: Integer, documentation: { desc: 'ユーザ名', type: 'string' } optional :addresses, type: Array[JSON], documentation: { desc: '届け先情報', type: 'array', collectionFormat: 'multi' } do requires :name, type: String, documentation: { desc: '届け先名', type: 'string' } requires :address, type: String, documentation: { desc: '届け先住所', type: 'string' } requires :tags, type: Array[JSON], documentation: { desc: 'タグ', type: 'array', collectionFormat: 'multi' } do optional :name, type: String, documentation: { desc: 'タグ名', type: 'string'} end end end post do present hoge: 'fuga' end end resources :complex_params_in_desc_block do desc 'descブロック内にのみparamsを書いた場合' do params ComplexUserParamsEntity.documentation end post do present hoge: 'fuga' end end end class ComplexUserParamsEntity < Grape::Entity class TagEntity < Grape::Entity expose :name, documentation: { desc: 'タグ名(エンティティで定義)', type: 'string' } end class AddressEntity < Grape::Entity expose :name, documentation: { desc: '届け先名(エンティティで定義)', type: 'string' } expose :address, documentation: { desc: '届け先住所(エンティティで定義)', type: 'string' } expose :tags, documentation: { desc: 'タグ(エンティティで定義)', type: 'array', is_array: true }, using: TagEntity end expose :user_name, documentation: { desc: 'ユーザ名(エンティティで定義)', type: 'string' } expose :addresses, documentation: { desc: '届け先情報(エンティティで定義)', type: 'array', is_array: true }, using: AddressEntity endSwagger UIで確認してみます。一方は配列内が表示されず、もう一方は表示されるけど階層構造がわかりにくい表示です。
見やすくできないかあがいてみます。全パラメータにparam_type: 'body'を追加してみます。
class Users < Grape::API resources :complex_params_in_same_layer do desc 'descと同じ階層にparamsを書いた場合' params do requires :user_name, type: Integer, documentation: { desc: 'ユーザ名', type: 'string', param_type: 'body' } # 以下略再びSwagger UIで確認します。すると、Entityで定義した方は相変わらず階層構造が失われています。Entityの書き方が悪いのかもしれません。
そしてもう一方は、階層構造がわかりやすくなりましたが、説明が反映されていません。そこで、ふと他のOpenAPIを解釈できるUIを試してみます。使うのはこちら。
https://github.com/Redocly/redocEntityの方は相変わらずですが、説明がちょっとマシになっています。
そして同階層の方はなんと完全に思い通りの表示になっています!!(配列のキーにつけた「届け先情報」という説明もちゃんと表示されています)このことからわかるのは、実際の表示は使用するUIツールによるので使用するツールでちゃんと表示されるようになることを確認しましょう、ということです。
3. exampleを出力するにはワークアラウンドが必要
exampleを指定しても出力されないのでなぜだろうと思ったら下記のissuesがありました。
現時点でOpenなので解決されるまではissues内に投稿されているワークアラウンドを入れればできるようです。
https://github.com/ruby-grape/grape-swagger/issues/762おわりに
リクエストパラメータのところのエンティティの使い方なんかはまだちゃんと理解できていないので間違い等あったらご指摘いただければ助かります。
- 投稿日:2020-12-16T22:06:21+09:00
nilガードについて理解してみた
Rubyの勉強をしているなかでnilガードというものがでてきたので勉強の記録用として記事にしてみました。
認識違いがあった場合などは是非ご指摘下さい。
nilガード
Rubyにはnilになってしまう状態を防ぐためにnilガードという書き方が存在します。簡単に説明すると仮に変数がnilだった場合には値を入れるということです。
ということで分かりやすく下記に例を記載したいと思います。
# 例1 > number = nil > number ||= 6 > puts number => 6 # 例2 > number = 5 > number ||= 6 > puts number => 5初めにnilガードを実装するにパイプ演算子||と=を組み合わせます。
例1ではnumberがnilなら6を代入しています。しかし例2ではnumberがnilではないので代入が行われていません。ここでnilガードというものはnilかfalseでないと代入が行われないと分かりました。まとめ
nilガードは変数にnilが入っているかもしれない状況でnilの代わりに何らかのデフォルト値を入れておきたいという場面で、とても便利に利用できます。
参考
・ 現場で使える Ruby on rails 5 速習実践ガイド
・ https://www.sejuku.net/blog/19044
- 投稿日:2020-12-16T21:35:04+09:00
Webpacker::Manifest::MissingEntryError
概要
このようなエラーが起きた時ひとつの解決策としてお役に立てれば幸いです。
環境
Mac OS Catarina 10.15.7
rails 6.0系
ruby 2.6.5エラー文
1. You want to set webpacker.yml value of compile to true for your environment unless you are using the `webpack -w` or the webpack-dev-server. 2. webpack has not yet re-run to reflect updates. 3. You have misconfigured Webpacker's config/webpacker.yml file. 4. Your webpack configuration is not creating a manifest. Your manifest contains:試したこと
1.webpackをインストール
2.yarnが入っているかの確認
3.yarnのupgrade
4../bin/webpack-dev-serverでサーバー立ち上げ
application.html.erb
のjsを読み込む記述
を消せばエラーは消えるのですが、jsを読み込むことができなくなってしまいます。解決策
nodeのバージョンが異なっているためエラーが起きておりました。
ゴールはnodeのバージョンを合わせてサーバーが立ち上がることを確認することです。
解決の流れ
1.nodeのバージョン確認
2.nodeのダウングレードするために削除・再インストールする
3.nodeのバージョン確認
4.インストールしたnodeが反映されていなかったらPATHを通す
5.Webpackerをインストールする
6.yarnが必要というエラーが起きたらyarn install
7.再度Webpackerをインストール
8.サーバーを立ち上げて確認詳細
1.nodeのバージョン確認
2.nodeのダウングレードするために削除・再インストールする
3.nodeのバージョン確認ターミナル% node -v % brew uninstall --ignore-dependencies nodejs % brew install node@14 % node -v
4.インストールしたnodeが反映されていなかったらPATHを通す
ターミナル% vim ~/.zshrc % source ~/.zshrc
vimexport PATH="/usr/local/opt/node@14/bin:$PATH"5.Webpackerをインストールする
ターミナル% rails webpacker:install
6.yarnが必要というエラーが起きたらyarn install
ターミナル======================================== Your Yarn packages are out of date! Please run `yarn install --check-files` to update. ========================================
ターミナル% yarn install
7.再度Webpackerをインストールしてみる
ターミナル% rails webpacker:install
8.サーバーを立ち上げて確認
ターミナル% rails s
まとめ
- Webpackerをインストールするにはyarnが必要。
- バージョンの違いで読み込めないということも起こることがある。
- 投稿日:2020-12-16T21:30:55+09:00
(解決方法)NoMethodError in Devise::SessionsController#destroy
エラー文
NoMethodError in Devise::SessionsController#destroy
undefined method `remember_created_at=' for #User:0x00007f84757e1ea8 Did you mean? remember_me=Extracted source (around line #432): else match = matched_attribute_method(method.to_s) match ? attribute_missing(match, *args, &block) : super end end ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)エラー内容の詳細
NoMethodError in Devise::SessionsController#destroy
devise sessionsコントローラーのdestroyアクションが定義されていません。
仮説①deviseがおかしい?undefined method `remember_created_at=' for #User:0x00007f84757e1ea8 Did you mean? remember_me=
remember_created_at=は定義されていない。remember_me=ではないか?
remember_created_at=はマイグレーションファイルのデフォルトの記載で書いてあったような、、、
仮説②マイグレーションファイルのremember_created_at=をremember_me=へ修正したら解決されるのでは?解決方法
仮説②がかすっていました。マイグレーションファイルのデフォルトの記述を手違いで消してしまっており、今回のエラーを発生させていました。そのため、マイグレーションファイルのデフォルトの記述を書いてあげたら、エラーは解決されました。
仮説①は、無関係でした。deviseに関するエラー文が出てきても、deviseは完璧と仮定し、その他の要因から疑っていったほうが良いみたいです。
- 投稿日:2020-12-16T20:32:27+09:00
Railsアプリケーションに2要素認証を導入する
はじめに
Zeals Advent Calendar 2020の16日目の記事です。
Zealsでバックエンドエンジニアをやってる高久田です。本日はRailsアプリケーションに対して2要素認証を導入するあたって、どのような方法があるのか調査したことについて記事にしようと思います。
主にAWS cognito ユーザープールの内容になるので Amazon Cognito ユーザープール からがメインになります。背景
みなさんが普段使っている、もしくは開発しているサービスには2要素認証が導入されていますか?
email, passwordのみでログインできるものや、最近だとGoogleやFacebookを利用したSNSログインで完了できるものがほとんどではないでしょうか
最近だと不正ログインにより、大きな問題になったサービスもいくつか耳にしたことがあると思います。
その中でよく2段階認証という言葉を聞くと思うのですが、今回タイトルに書いてある2要素認証とは何が違うのでしょうか?まずは「認証要素」について説明します。
「認証要素」とは認証を行うために必要な認証方法の種類になります。
- 知識要素
知っていることや記憶していること(パスワードや秘密の質問)
- 所持要素
自分が持っているデバイスに送信されてくる情報
Google Authenticatorを使う方法やSMSなどで送信されてくるメッセージを利用する
- 生体要素
指紋認証や顔認証
この3つの認証要素のうち2つ以上の認証要素を利用したものが2要素認証、多要素認証と呼ばれます。
2段階認証はemail, passwordを入力後、秘密の質問に答えて認証を完了するようなものになり、複数の段階を踏んで認証を完了させるものになります。海外では2段階認証という言葉あまり使われないらしく、また安全性を考えるうえで必要なのは段階の数ではなく、認証要素の数になります。
2FA
それでは実際にRailsアプリケーションに対して2FAを実装していきます。
環境
- ruby 2.6.6
- Rails 5.2.4.4
devise
Railsアプリケーションでログイン機能を作成しているのであればほとんどの人がdeviseを利用しているのではないでしょうか?
deviseを利用しているのならば、deviseを拡張するgemを導入することで2FAを実装することができます。devise-two-factor 簡単に2要素認証を導入することができる
rqrcode QRコードの生成を行うまたは
google-authenticator Google Authenticatorと統合できるなど既にあるgemを利用する形で実装することができます。
Amazon Cognito ユーザープール
Amazon Cognito ユーザープール とはアプリケーションで必要な認証機能を提供してくれるサービスになります。
自前で認証機能を実装する必要がなく、クラウドで提供されているものを利用していきます。
sdk も提供されているので、これを利用していきます。クライアント作成
def initialize cognito_client = Aws::CognitoIdentityProvider::Client.new( region: ENV['COGNITO_REGION'], access_key_id: ENV['COGNITO_ACCESS_KEY_ID'], secret_access_key: ENV['COGNITO_SECRET_ACCESS_KEY'] ) endユーザープール作成
ユーザプールは Amazon Cognito のユーザディレクトリです。ユーザープールを使用すると、ユーザーは Amazon Cognito を通じてウェブまたはモバイルアプリにログインできます。また、ユーザーは Google、Facebook、Amazon、Apple などのソーシャル ID プロバイダー、および SAML ベースの ID プロバイダー経由でユーザープールにサインインすることもできます。ユーザーが直接またはサードパーティーを通じてサインインするかどうかにかかわらず、ユーザープールのすべてのメンバーには、Software Development Kit (SDK) を通じてアクセスできるディレクトリプロファイルがあります。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/cognito-user-identity-pools.htmldef create_user_pool(user_pool_name) res = client.create_user_pool( pool_name: user_pool_name, policies: { password_policy: { minimum_length: 8, require_uppercase: false, require_lowercase: false, require_numbers: false, require_symbols: false, temporary_password_validity_days: 1 } } ) res.user_pool.id endアプリクライアントの作成
パスワードの登録、サインインなどのAPI操作を呼び出すには、アプリケーションクライアントIDとクライアントシークレットが必要になります。承認されたクライアントアプリのみがこれらの未認証操作を呼び出すことができるようにします。
def create_cognito_app_client(user_pool_id) res = client.create_user_pool_client( user_pool_id: 'ap-northeast-1_XXXXXXXXX', client_name: 'cognito-app-client', explicit_auth_flows: ['ADMIN_NO_SRP_AUTH'], prevent_user_existence_errors: 'ENABLED' ) res.user_pool_client.client_id endsign_up
def sign_up(email, password, app_client_id) res = client.sign_up( client_id: app_client_id, username: email, password: password ) res.user_sub endsign_in
def sign_in(email, password, app_client_id) res = client.admin_initiate_auth( user_pool_id: 'ap-northeast-1_XXXXXXXXX', client_id: app_client_id, auth_flow: 'ADMIN_NO_SRP_AUTH', auth_parameters: { USERNAME: email, PASSWORD: password } ) res.authentication_result.access_token endだいたいの基本的な操作をするコードをこのようなものになります。
多要素認証を有効化する場合はユーザープールの設定を変更することで有効化することができます。
省略可能にチェックするとユーザー毎にMFAを適応するかどうかを設定することができます。
第2の要素としてSMSテキストメッセージかワンタイムパスワード(Google Authenticator)を設定することができます。
これで認証要素のうちの所持要素を確認することができます。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-settings-mfa.html2要素認証を設定した後は、送信されてくる/Google Authenticatorで登録した確認コードを使って認証を行うことができます。
resp = client.confirm_sign_up({ client_id: app_client_id, username: "user_name", confirmation_code: "*******", force_alias_creation: false, })cognitoでMFAを容易に設定できるだけではなく、ログインページ自体が提供されていたり、既存の認証基盤からcognitoへ移行する仕組みなども提供されています。
https://docs.aws.amazon.com/ja_jp/cognito/latest/developerguide/user-pool-lambda-migrate-user.htmlまとめ
普段Railsを触っているとどうしてもRails内でどのように問題を解決するかに意識が向いてしまっていたのですが、Cognitoなどを利用することによって認証部分などを外部サービスを利用して切り出していくことができると思います。
サービスが大きくなっていったり、複数のサービスを提供するようになった際に、それぞれのサービスで認証基盤を作成するのは工数がかかります。
その際に楽できるところは楽していきたいですね!
- 投稿日:2020-12-16T20:04:29+09:00
railsでグラフ機能を導入
はじめに
今回学習記録をとるアプリ開発をしていたところ学習状況をグラフ化したい!
と思い比較的簡単に実装ができたので備忘録として残します。
筆者はrails6を使っての実装となります。
初学者のため間違え等ありましたらご指摘頂けると幸いです!手順1 gem chartkickを導入
グラフ機能を簡単に実装するにはgemのchartkickを使います。
gemファイルに以下を追加しbundleinstallします。
groupdateはDBから様々な情報をグループ化して取り出せる便利なメソッドがあるので導入してますが、chartkickを使うだけであればgem "chartkick"のみで問題ありません。gem "chartkick" gem "groupdate"rails6の場合は以下コマンドを実行
yarn add chartkick chart.js手順2 jsファイルの読み込み
chartkickではjsを使って実装するためjsファイルをapp/javascript/packs/application.jsに記述します。
rails6の場合require("chartkick") require("chart.js")rails5の場合
//= require chartkick //= require Chart.bundle手順3 htmlにグラフを挿入
ここまで行けばあとは公式ページにあるサンプルコードをhtmlに記述するだけで簡単にグラフを作成する事ができます。
私の場合このように使いました。
<%= column_chart @tweets.group_by_day_of_week(:created_at, format: "%a").count %>
https://gyazo.com/4c24263ba93c7e3bc2ea6974bfb0143f
ここでgroup_by_day_of_weekはgemのgroupdateを導入していると使えるメソッドです。
今回使ったのは棒グラフですが他にも折れ線や円グラフも簡単に実装できるのでchartkickの公式サイト見て見てください!!
- 投稿日:2020-12-16T19:33:53+09:00
【ActiveAdmin】レコードが無い場合の、まだありません。「作成する」を非表示に【blank_slate content link】
- 投稿日:2020-12-16T18:59:22+09:00
has_manyで中間テーブルから親モデルの別のassociationを取ってきたい
概要
Railsを使用してデータを扱う際、殆どこんな機会は訪れない(別の方法などで代替できることがほとんど)ですが、どうしよもない事情で中間テーブルから関連付けを貼る必要があったので備忘録としてメモっておきます。
事前知識
class User < ApplicationRecord has_many :group_users has_many :groups, through: :group_users has_many :comments end class Group < ApplicationRecord has_many :group_users has_many :users, through: :group_users has_many :comments end class Comment < ApplicationRecord belongs_to :user belongs_to :group end class GroupUser < ApplicationRecord belongs_to :group belongs_to :user endこのようなクラスが存在しているとします。
通常であればGroupUserという中間テーブルを起点として何かすることはほとんどないですし、あったとしてもUserやGroup経由でCommentを取得することができるので、困ることはほとんどありません。一方、GraphQLを使用していたり、何かアソシエーションのタイミングで整ったデータが欲しいなどの稀な事情でGroupUserのインスタンスからgroupとuserが絞り込まれたcommentの一覧が欲しい場合があったりしました。
普通にthroughをして取得しようとすると、例えば下記のようなコードになります。
class GroupUser < ApplicationRecord belongs_to :group belongs_to :user has_many :comments, through: :user #groupでもいい endただ、この方法だとthroughしたどちらかのカラムの値では絞り込まれますが、もう片方のカラムでは絞り込みが行われません。
どちらでthroughしたとしても、userかgroupのどちらかでの絞り込みしか行われず、意図した値は取得できません。
解決策
じゃあどうするのかというと、socpeを使います。
class GroupUser < ApplicationRecord belongs_to :group belongs_to :user has_many :comments,->(group_user) { where(group_id: group_user.group_id) }, through: :user #groupならuserでwhere endhas_manyはスコープの引数として自分自身だけは取ることができます。なので、このようにすることでgroup_userインスタンスのgroup_idとuser_idを持ったコメントの一覧を取得することができます。
どうしても中間テーブルを起点にして取得しなければいけない状況になった場合に役立てば幸いです。
- 投稿日:2020-12-16T18:42:31+09:00
Pinterestのように写真を表示する
はじめに
絶賛ポートフォリオ製作中の駆け出しエンジニアです
インスタグラムのような古着専用の写真投稿サイトを作っています
その時に写真の投稿一覧画面でPinterest(https://www.pinterest.jp/) というサイトのように写真を並べるために奮闘しましたので備忘録として残したいと思います
コード
before
post.html.erb<div class = contents-row > <% @items.each do |item| %> <ul class='item-lists'> <li class='list'> <%= link_to items_path(item.id), method: :get do %> <%= image_tag(item.image, class:"item-img") %> <% end %> </li> </ul> <% end %> </div>post.scss.contents-row { display: flex; flex-wrap: wrap; justify-content: center; align-items: center; margin: 0 auto; width: 95vw; } .item-lists { margin: 5px; } .list { width:25vw; } .item-img { width: 100%; height: auto; border-radius: 30px; vertical-align: bottom; }after
post.html.erb<div class = contents-row > <ul class='item-lists'> <% @items.each do |item| %> <li class='list'> <%= link_to items_path(item.id), method: :get do %> <%= image_tag(item.image, class:"item-img") %> <% end %> </li> <% end %> </ul> </div>post.scss.contents-row { display: flex; justify-content: center; align-items: center; margin: 0 auto; width: 95vw; } .item-lists { column-count: 3; column-gap: 15px; column-fill: auto; } .list { width:25vw; } .item-img { width: 100%; height: auto; border-radius: 30px; vertical-align: bottom; margin: 5px; }行ったこと
• 【html】 ulタグがeach文の中に入っていたのでeach文の外に出した
• 【css】 item-listsにcolumn-countなどを記述し、それに合わせてそれぞれ調整した参考サイト
- 投稿日:2020-12-16T18:42:19+09:00
【Rilas】ページネーション機能を実装で勉強したこと。
一覧表示画面にページネーション機能をつけた際に勉強になった事を備忘録として残しています。
前提
ページネーション機能をつける前のビューなどです。
コントローラー
事前にソート機能をつけています。
app/controller/stocks_controller.rbclass StocksController < ApplicationController helper_method :sort_column, :sort_direction def index @calendar = Calendar.find(params[:calendar_id]) @stocks = @calendar.stocks.page.order("#{sort_column} #{sort_direction}") @products = Product.all end private def sort_direction %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc' end def sort_column Stock.column_names.include?(params[:sort]) ? params[:sort] : 'id' end endビュー
app/views/stocks/index.html.erb<table> <thead> <tr> <th scope="col"><%= sort_order "display", "陳列" %></th> <th scope="col"><%= sort_order "publisher", "出版社名" %></th> <th scope="col"><%= sort_order "magazine_name", "雑誌名" %></th> <th scope="col"><%= sort_order "num", "冊数" %></th> <th scope="col"><%= sort_order "price", "本体価格" %></th> <th scope="col"><%= sort_order "i_form", "発行形態" %></th> <th scope="col"><%= sort_order "purchased", "買切雑誌" %></th> </tr> </thead> <tbody> <% @stocks.each do |stock| %> <% if stock.num > 0 %> <tr> <td><%= stock.display %></td> <td><%= stock.publisher %></td> <td><%= stock.magazine_name %></td> <td><%= stock.num %></td> <td><%= stock.price.to_s(:delimited) %></td> <td><%= stock.i_form %></td> <td><%= stock.purchased %></td> </tr> <% end %> <% end %> </tbody> </table>ページネーションの実装
今回kaminariというページネーション用のgemを使って実装しました。
意外と簡単に実装できました〜。kaminariのインストール
Gemfileに
gem 'kaminari'
を追加し、$ bundle install
でインストールします。Gemfilegem 'kaminari'gemをインストールした後は一度サーバを再起動しましょう。
Ctrl-c
でサーバを止め、rails s
でサーバを起動できます。
また、忘れてて右往左往しました・・・。これでkaminariのインストールは完了です。
ページネーションを表示させる
お次はcontrollerです。
ページネーションを表示させたいデータに.page(params[:page])
を追加します。app/controllers/stocks_controller.rbdef index # ページネーションをつけたいデータに.page(params[:page])を追加 @stocks = @calendar.stocks.page(params[:page]).order("#{sort_column} #{sort_direction}") end余談(1.ページに表示するレコード数の変更)
1ページに表示するレコード数は初期値では25件なのだそうです。
controllerに.per(表示したいレコード数)
を追加すると変更できるそうです。例えば、30件表示したい場合は
.per(30)
と追加。app/controllers/stocks_controller.rbdef index # .per(30)を追加 @stocks = @calendar.stocks.page(params[:page]).per(30).order("#{sort_column} #{sort_direction}") endって感じです。
最後に、viewでページネーションを表示させたいところへ
<%= paginate @stocks %>
を追加します。app/views/stocks/index.html.erb<%= paginate @stocks %> <table> 〜〜 省略 〜〜 </table>余談(2.表示を日本語に変更する)
ページ番号の前後についている
<<First
とか<Prve
は判りづらいと感じたので
こちらを日本語に変更していきます。config/application.rb# 〜〜省略〜〜 module Teiki29770(アプリ名です) class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 6.0 # 日本語の言語設定 config.i18n.default_locale = :ja config.time_zone = 'Tokyo' # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading # the framework and any gems in your application. end endサーバを再起動しましょう!
config/locales
に日本語変換用のymlファイルja.yml
を作成して、
以下のコードを追加してみます。config/locales/ja.ymlja: views: pagination: first: "« 最初" last: "最後 »" previous: "‹ 前" next: "次 ›" truncate: "..."これで表示を日本語に変更できました!やった!!
と、思っていたら・・・。表示数にズレが出てしまう。
なんか少ない・・・。30件表示できるはずなのに・・・。
調べてみると他のページも表示数はまちまちでした。ビューでの条件式が原因
コントローラーで指定している通り、30件づつデータを送って
ビューのほうで条件式に合うデータのみを表示していることが原因でした。app/views/stocks/index.html.erb〜〜 省略 〜〜 <tbody> <% @stocks.each do |stock| %> <% if stock.num > 0 %>⇦ここが原因。 <tr> <td><%= stock.display %></td> <td><%= stock.publisher %></td> <td><%= stock.magazine_name %></td> <td><%= stock.num %></td> <td><%= stock.price.to_s(:delimited) %></td> <td><%= stock.i_form %></td> <td><%= stock.purchased %></td> </tr> <% end %> <% end %> </tbody> </table>なので、コントローラーの方で条件に合うデータだけを先に抽出しておいて
そちらを30件ずつビューへ送る様にします。app/controller/stocks_controller.rbdef index @calendar = Calendar.find(params[:calendar_id]) @stocks = @calendar.stocks.where("num > ?",0).page(params[:page]).per(30).order("#{sort_column} #{sort_direction}") endapp/views/stocks/index.html.erb〜〜 省略 〜〜 <tbody> <% @stocks.each do |stock| %> 〜〜 if文を削除 〜〜 <tr> <td><%= stock.display %></td> <td><%= stock.publisher %></td> <td><%= stock.magazine_name %></td> <td><%= stock.num %></td> <td><%= stock.price.to_s(:delimited) %></td> <td><%= stock.i_form %></td> <td><%= stock.purchased %></td> </tr> <% end %> </tbody> </table>これで、しっかりと条件に沿ったデータを表示することができました!
ちゃんとソートもできます!勉強になったこと
MVCの関係性の大事さと共に、役割分担をしっかりとすることの大切さを再確認できました。
ありがとう、ページネーション!参考にさせていただいたサイト様
https://qiita.com/rio_threehouse/items/313824b90a31268b0074
ありがとうございます!
- 投稿日:2020-12-16T18:42:19+09:00
【Rilas】ページネーション機能の実装で勉強したこと。
一覧表示画面にページネーション機能をつけた際に勉強になった事を備忘録として残しています。
前提
ページネーション機能をつける前のビューなどです。
コントローラー
事前にソート機能をつけています。
app/controller/stocks_controller.rbclass StocksController < ApplicationController helper_method :sort_column, :sort_direction def index @calendar = Calendar.find(params[:calendar_id]) @stocks = @calendar.stocks.page.order("#{sort_column} #{sort_direction}") @products = Product.all end private def sort_direction %w[asc desc].include?(params[:direction]) ? params[:direction] : 'asc' end def sort_column Stock.column_names.include?(params[:sort]) ? params[:sort] : 'id' end endヘルパーメソッド(追記)
大事なヘルパーメソッドの記述を抜かっておりました・・・。
app/helper/stocks_helper.rbmodule StocksHelper def sort_order(column, title) css_class = (column == sort_column) ? "current #{sort_direction}" : nil direction = (column == sort_column && sort_direction == 'asc') ? 'desc' : 'asc' link_to title, { sort: column, direction: direction }, class: "sort_header #{css_class}" end endビュー
app/views/stocks/index.html.erb<table> <thead> <tr> <th scope="col"><%= sort_order "display", "陳列" %></th> <th scope="col"><%= sort_order "publisher", "出版社名" %></th> <th scope="col"><%= sort_order "magazine_name", "雑誌名" %></th> <th scope="col"><%= sort_order "num", "冊数" %></th> <th scope="col"><%= sort_order "price", "本体価格" %></th> <th scope="col"><%= sort_order "i_form", "発行形態" %></th> <th scope="col"><%= sort_order "purchased", "買切雑誌" %></th> </tr> </thead> <tbody> <% @stocks.each do |stock| %> <% if stock.num > 0 %> <tr> <td><%= stock.display %></td> <td><%= stock.publisher %></td> <td><%= stock.magazine_name %></td> <td><%= stock.num %></td> <td><%= stock.price.to_s(:delimited) %></td> <td><%= stock.i_form %></td> <td><%= stock.purchased %></td> </tr> <% end %> <% end %> </tbody> </table>ページネーションの実装
今回kaminariというページネーション用のgemを使って実装しました。
意外と簡単に実装できました〜。kaminariのインストール
Gemfileに
gem 'kaminari'
を追加し、$ bundle install
でインストールします。Gemfilegem 'kaminari'gemをインストールした後は一度サーバを再起動しましょう。
Ctrl-c
でサーバを止め、rails s
でサーバを起動できます。
また、忘れてて右往左往しました・・・。これでkaminariのインストールは完了です。
ページネーションを表示させる
お次はcontrollerです。
ページネーションを表示させたいデータに.page(params[:page])
を追加します。app/controllers/stocks_controller.rbdef index # ページネーションをつけたいデータに.page(params[:page])を追加 @stocks = @calendar.stocks.page(params[:page]).order("#{sort_column} #{sort_direction}") end余談(1.ページに表示するレコード数の変更)
1ページに表示するレコード数は初期値では25件なのだそうです。
controllerに.per(表示したいレコード数)
を追加すると変更できるそうです。例えば、30件表示したい場合は
.per(30)
と追加。app/controllers/stocks_controller.rbdef index # .per(30)を追加 @stocks = @calendar.stocks.page(params[:page]).per(30).order("#{sort_column} #{sort_direction}") endって感じです。
最後に、viewでページネーションを表示させたいところへ
<%= paginate @stocks %>
を追加します。app/views/stocks/index.html.erb<%= paginate @stocks %> <table> 〜〜 省略 〜〜 </table>余談(2.表示を日本語に変更する)
ページ番号の前後についている
<<First
とか<Prve
は判りづらいと感じたので
こちらを日本語に変更していきます。config/application.rb# 〜〜省略〜〜 module Teiki29770(アプリ名です) class Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.load_defaults 6.0 # 日本語の言語設定 config.i18n.default_locale = :ja config.time_zone = 'Tokyo' # Settings in config/environments/* take precedence over those specified here. # Application configuration can go into files in config/initializers # -- all .rb files in that directory are automatically loaded after loading # the framework and any gems in your application. end endサーバを再起動しましょう!
config/locales
に日本語変換用のymlファイルja.yml
を作成して、
以下のコードを追加してみます。config/locales/ja.ymlja: views: pagination: first: "« 最初" last: "最後 »" previous: "‹ 前" next: "次 ›" truncate: "..."これで表示を日本語に変更できました!やった!!
と、思っていたら・・・。表示数にズレが出てしまう。
なんか少ない・・・。30件表示できるはずなのに・・・。
調べてみると他のページも表示数はまちまちでした。ビューでの条件式が原因
コントローラーで指定している通り、30件づつデータを送って
ビューのほうで条件式に合うデータのみを表示していることが原因でした。app/views/stocks/index.html.erb〜〜 省略 〜〜 <tbody> <% @stocks.each do |stock| %> <% if stock.num > 0 %>⇦ここが原因。 <tr> <td><%= stock.display %></td> <td><%= stock.publisher %></td> <td><%= stock.magazine_name %></td> <td><%= stock.num %></td> <td><%= stock.price.to_s(:delimited) %></td> <td><%= stock.i_form %></td> <td><%= stock.purchased %></td> </tr> <% end %> <% end %> </tbody> </table>なので、コントローラーの方で条件に合うデータだけを先に抽出しておいて
そちらを30件ずつビューへ送る様にします。app/controller/stocks_controller.rbdef index @calendar = Calendar.find(params[:calendar_id]) @stocks = @calendar.stocks.where("num > ?",0).page(params[:page]).per(30).order("#{sort_column} #{sort_direction}") endapp/views/stocks/index.html.erb〜〜 省略 〜〜 <tbody> <% @stocks.each do |stock| %> 〜〜 if文を削除 〜〜 <tr> <td><%= stock.display %></td> <td><%= stock.publisher %></td> <td><%= stock.magazine_name %></td> <td><%= stock.num %></td> <td><%= stock.price.to_s(:delimited) %></td> <td><%= stock.i_form %></td> <td><%= stock.purchased %></td> </tr> <% end %> </tbody> </table>これで、しっかりと条件に沿ったデータを表示することができました!
ちゃんとソートもできます!勉強になったこと
MVCの関係性の大事さと共に、役割分担をしっかりとすることの大切さを再確認できました。
ありがとう、ページネーション!参考にさせていただいたサイト様
https://qiita.com/rio_threehouse/items/313824b90a31268b0074
ありがとうございます!
- 投稿日:2020-12-16T18:29:43+09:00
Ruby on Railsプロジェクトの開発環境をDocker化する
ACCESS Advent Calendar 2020 の16日目の記事です。
概要
現在運用中のRuby on Railsプロジェクトの開発環境をDocker化する案件があり、
その際に行った作業の手順をまとめました。Dockerについて
Dockerとは、コンテナと呼ばれる仮想環境を構築・実行できるようにするためのプラットフォームです。
私は今回初めてDockerを触ったのですが、Dockerの理解には入門Dockerが大変参考になりました。
前提
バージョン
- macOS Catalina 10.15.7
- Docker 19.03.13
- docker-compose 1.27.4
- Ruby 2.4.5
- mongoDB 3.0.15
- postgres 10
システム構成
こちらのシステム構成図の通りにDocker化していきます。
(※大分簡略化しています)初めにRailsアプリケーションのDocker Containerを作成し、
その後オーケストレーションツールであるdocker-composeによって、
RailsアプリケーションをmongoDB及びpostgreSQLに接続します。手順
Dockerのインストール
Docker HubよりDocker Desktop for Macを導入します。
ターミナルで下記の2つのコマンドが実行できればOKです。
$ docker -v Docker version 19.03.13, build 4484c46d9d $ docker-compose -v docker-compose version 1.27.4, build 40524192必要なファイルの作成
Docker及びdocker-composeを動かすのに必要なファイルをプロジェクトのルートに作成します。
Dockerfile
ruby 2.4.5環境が予めインストールされている
ruby:2.4.5-slim
というDockerイメージをDocker Hubから取得し、そのイメージ上にRails環境をセットアップしています。DockerfileFROM ruby:2.4.5-slim # Dockerコンテナ上におけるプロジェクトルートを指定 ENV APP_ROOT=/app RUN mkdir $APP_ROOT WORKDIR $APP_ROOT # apt-utilsインストールの時の警告を抑制する # https://qiita.com/haessal/items/0a83fe9fa1ac00ed5ee9 ENV DEBCONF_NOWARNINGS yes # aptパッケージのインストール RUN apt-get update -y -qq && \ apt-get install -y -qq build-essential libpq-dev libmagickwand-dev # Railsのセットアップ COPY Gemfile Gemfile COPY Gemfile.lock Gemfile.lock RUN gem install bundler -v 1.17.3 && bundle install # プロジェクトディレクトリをDocker Imageにコピー COPY . $APP_ROOTdocker-compose.yml
postgres
,mongo
,web
の3つのサービスを定義し、
web
をpostgres
及びmongo
に依存させています。docker-compose.ymlversion: "3" services: # postgreSQL containerの定義 postgres: image: postgres:10 ports: # <Host Port>:<Container Port> - "5432:5432" environment: POSTGRES_USER: xxxxxx POSTGRES_PASSWORD: xxxxxx # mongoDB containerの定義 mongo: image: mongo:3.0.15 ports: - "27017:27017" # Rails app containerの定義 web: build: . env_file: .env # pid error の回避のため、server.pidを削除したのちにrails sを実行 # https://qiita.com/sakuraniumarete/items/ac07d9d56c876601748c command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" # 依存関係の定義 (webをビルドするとpostgresとmongoが同時にビルドされる) depends_on: - postgres - mongoビルド実行
これら2つのファイルを作成すると、
$ docker-compose build
でコンテナをビルドできるようになります。
DBの永続化
現在の状態ではDBがコンテナ内部のストレージに生成されており、
コンテナを削除して再ビルドすると、DBに保存されていたデータは全て消失してしまいます。DB上のデータを永続化するためには、Dockerが提供している
volume
という機能を利用します。
volumeは、Docker Containerのライフサイクルからは独立して生成されるデータ保存領域です。
volume上にDBを生成することにより、コンテナを再ビルドしてもDB上のデータが残り続けるようになります。
引用元: https://matsuand.github.io/docs.docker.jp.onthefly/storage/volumes/volumeを利用するためには、docker-compose.ymlに以下の内容を追記します。
docker-compose.ymlservices: postgres: + volumes: + - "postgres-data:/var/lib/postgresql/data" mongo: + volumes: + - "mongo-data:/data/db" + volumes: + postgres-data: + mongo-data:上記を追記した上で改めてビルドすると、Docker Volumeが作成されているはずです。
$ docker volume ls local mongo-data local postgres-dataホストとコンテナのソースコードを同期
現在の状態では、Dockerfileをビルドした時点で、ホストの全データををImageにコピーしています。
DockerfileCOPY . $APP_ROOTつまり、これより後にホスト側でソースコードを変更した場合、動作しているコンテナを一旦停止させ、
docker-compose build
からやり直す必要があります。開発環境において毎度ビルドからやり直しているのでは非常に効率が悪いので、
ホストのコード変更をコンテナに即時反映できるようにします。よくある方法は、下記のように、プロジェクトのルートディレクトリを無名volumeとしてコンテナにマウントする方法です。
docker-compose.ymlservices: # ${APP_ROOT}はDockerfileにおいてENVで定義した環境変数 web:.:${APP_ROOT}しかし、上記の方法を採った場合の問題として、
Docker for Mac特有のVolume I/Oの遅さが大きく影響してしまうということがあります。
この問題については、docker/for-macのGitHubリポジトリにおいて議論されています。
(この問題により最も影響を受けたのは、RSpecの実行でした。上記の方法でマウントした場合、
普段5分ほどで完了していたテストが30分ほどかかりました...)docker-sync
現状とりうる有効な解決策として、docker-syncというサードパーティライブラリが提供されています。
新たに
docker-sync.yml
とdocker-compose-dev.yml
を作成します。
作成にあたってはdocker-syncのドキュメントを参考にしました。docker-sync.ymlversion: "2" syncs: sync-volume: src: "." sync_excludes: - "log" - "tmp" - ".git"docker-compose-dev.ymlversion: "3" services: web: volumes: - "sync-volume:/app:nocopy" volumes: sync-volume: external: trueDBセットアップ
以下のコマンドで、postgreSQLとmongoDBをセットアップします。
$ docker-compose run --rm -e RAILS_ENV=development -T web rake db:setup $ docker-compose run --rm -e RAILS_ENV=devlopment -T web rake db:mongoid:create_indexes
docker-compose run --rm <container name> <command>
は、
指定したコンテナサービスを起動し、任意のコマンドを実行後、そのコンテナを削除するというコマンドです。
DBはvolumeで永続化されているので、セットアップのためだけにコンテナを作成し、その後削除してしまっても問題ないということです。Railsサーバーの実行
ここまでの手順を実施した上で、下記コマンドを実行することでRailsサーバーが起動します。
$ docker-sync-stack start
これは以下のコマンドを短縮したものです。
$ docker-sync start $ docker-compose -f docker-compose.yml -f docker-compose.yml up
-f
オプションを使って複数のdocker-composeファイルを指定すると、
コンテナ作成時の各種パラメーターを上書きすることができます。
参考: 設定の追加と上書き - Docker-docs-jaまた、上記はフォアグラウンドで起動するためのコマンドで、
バックグラウンドで起動する場合は以下のコマンドを実行します。# 起動 $ docker-sync start $ docker-compose -f docker-compose.yml -f docker-compose.yml up # 停止 $ docker-compose down $ docker-sync stopテスト実行
RSpecを実行するためには、サーバーを起動した状態で以下のコマンドを実行します。
$ docker-compose exec -e COVERAGE=true -T web bundle exec rspec
docker-compose exec
で、起動中のDockerコンテナに対して任意のコマンドを実行できます。もしくは、以下のようにコンテナに入って実行することもできます。
$ docker-compose exec web bash root@container:/app# bundle exec rspecCI対応
CIツールとしてJenkinsを使用しています。
テストを実行するシェルスクリプト
ビルドジョブにおいて、下記のシェルを実行することで自動テストが行われるようにしました。
# 終了時の処理 # docker-composeが失敗した際でもJenkinsビルドマシンにゴミが残らないよう後処理をかける # https://qiita.com/ryo0301/items/7bf1eaf00b037c38e2ea function finally { # Clean project docker-compose down --rmi local --volumes --remove-orphans } trap finally EXIT # 並列実行のために、プロジェクト名としてJenkinsの環境変数である$BUILD_TAGを指定 export COMPOSE_PROJECT_NAME=$BUILD_TAG # 環境変数で予めビルドするdocker-composeファイルを指定することで、 # -fオプションによる指定を省略できる # https://docs.docker.jp/compose/reference/envvars.html export COMPOSE_PATH_SEPARATOR=: export COMPOSE_FILE=docker-compose.yml:docker-compose-test.yml # Build Container docker-compose build --no-cache docker-compose up -d # Setup DB docker-compose exec -e RAILS_ENV=test -T web rake db:setup docker-compose exec -e RAILS_ENV=test -T web rake db:mongoid:create_indexes # Run RSpec docker-compose exec -e COVERAGE=true -T web bundle exec rspecビルドジョブを並列実行できるようにする
RailsプロジェクトをDocker化していない時の問題として、
2つ以上のビルドジョブを並列実行すると、同じマシン上でDBの取り合いが起こり、
エラーが発生する問題がありました。Docker化したことで、それぞれのビルドが独立したコンテナの中で実行されるようになり、
並列実行してもエラーが起こらないようになります。
ただし、並列ビルドの実行時にコンテナのポート番号が重複しないよう、
ポートフォワーディングの設定を変更する必要があります。
参考: ホスト上にコンテナのポートを割り当て - Docker-docs-jaexport COMPOSE_FILE=docker-compose.yml:docker-compose-test.ymlで指定している
docker-compose-test.yml
の中身でそれを行っています。docker-compose-test.ymlversion: "3" services: postgres: ports: - "5432" mongo: ports: - "27017" web: ports: - "3000"また、
docker-compose.yml
に記載したポート番号をdocker-compose-dev.yml
に移動する必要があります。
なぜなら、このままdocker-compose up -d
を実行すると、docker-compose.yml
とdocker-compose-test.yml
がマージされ、
結果としてポートの指定が以下のようになってしまい、docker-compose-test.yml
でわざわざポート指定した意味がなくなってしまうためです。services: postgres: ports: - "5432:5432" - "5432" mongo: ports: - "27017:27017" - "27017" web: ports: - "3000:3000" - "3000"Railsサーバーの実行の項で、
-f
オプションを使って複数のdocker-composeファイルを指定すると、
コンテナ作成時の各種パラメーターを上書きすることができます。と述べましたが、複数指定可能なパラメータの場合は設定値は上書きされずにマージされるので注意が必要です。
docker-compose.ymlservices: postgres: - ports: - - "5432:5432" mongo: - ports: - - "27017:27017" web: - ports: - - "3000:3000"docker-compose-dev.ymlservices: postgres: + ports: + - "5432:5432" mongo: + ports: + - "27017:27017" web: + ports: + - "3000:3000"おまけ: RubyMineへの対応
JetBrainsのIDEであるRubyMineは、Docker Container上のRuby on Railsの開発環境に完全対応しており、以下の手順で設定することができます。
チュートリアル : リモートインタープリターとしての Docker Compose — RubyMineまとめ
今回新たに作成したファイル
開発環境をDocker化するにあたり、新たに作成したファイルは以下の通りです。
. ├── Dockerfile ├── docker-compose.yml ├── docker-compose-dev.yml ├── docker-compose-test.yml └── docker-sync.ymlその他
今回初めてコンテナ技術に触れ、Docker化にあたっては様々な試行錯誤を重ねました。
これまでに書いた中で、もっと良い対応方法がある、或いは対応方法として正しくない箇所があるかもしれませんが、その時はご指摘いただければ幸いです。
- 投稿日:2020-12-16T18:14:47+09:00
Sprockets::DoubleLinkError を解消した方法
やんばるエキスパートというオンラインスクールにて教材に取り組んでいた時の出来事です。
Sprockets::DoubleLinkErrorの出現
Railsのメッセージ投稿アプリを作成しており、その中でstylesheetsフォルダのcssファイルをscssファイルに拡張子を変更し、動作確認を行ったところ、「Sprockets::DoubleLinkError」が出現しました。
原因はcssファイルとscssファイルの干渉
stylesheetsフォルダ内にcssファイル、min.cssファイル、scssファイルの3つが存在しており、cssファイルとscssファイルが干渉してエラーが起こっていたようです。
cssファイルとmin.cssファイルを削除し、動作確認を行った所、エラーは解消されました。
しかし、、、
拡張機能Easy Sassにも注意
VScodeを再起動すると、再度cssファイルとmin.cssファイルが生成されていました。
拡張機能にEasy Sassを入れており、どうやらこいつが悪さをしていたようです。
Easy Sassを無効化した所、cssファイルとmin.cssファイルは自動生成されなくなりました。そして、エラーも出ず、問題なく動作できるようになりました。
- 投稿日:2020-12-16T17:56:47+09:00
【Active Admin】Unable to find input class JsonInput
エラー内容
json型のDBカラムを、
active_adminの管理画面フォームで表示させようとすると、
Unable to find input class JsonInput のエラー。
- backtraceの一部
@input_class_finder.find(as) rescue Formtastic::InputClassFinder::NotFoundError raise Formtastic::UnknownInputError, "Unable to find input #{$!.message}" end # @api private
解決方法
Formtasticのクラスを継承した、
XxxxInputというクラスを作れば良い。
今回の場合は、
エラー文の通り、(Unable to find input class JsonInput)
JsonInputというクラスを作ればOK。
# app/inputs/json_input.rb class JsonInput < Formtastic::Inputs::StringInput; end
その後、
サーバーを再起動してエラーは出なくなりました。
参考
- 投稿日:2020-12-16T17:04:40+09:00
【Github Actions】Rails × PostgreSQL環境作ってHerokuにデプロイするCIとその解説
Github Actionsを使ってRails×PostgreSQL環境のCIを作りました。
Github Actionsは普段触らなかったので、作ったCIについてコードの解説をここに書いていきます。まずはCIでやっていることと、そのコードの全体像を書いていきます。そのあとにCIのコードについて説明を書いていきます。
今回のCIでやっていること
- Pull Requestが作られたタイミングで、RSpecによるテスト
- Masterブランチにマージされたタイミングで、Herokuにデプロイ
- テストが完了したら、Slackに通知する
書いたCI
name: Build And Test on: pull_request: push: branches: - master jobs: build_and_test: name: Exec RSpec rubocop brakeman runs-on: ubuntu-latest services: postgres: image: postgres:11.5 ports: - 5432:5432 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 env: RAILS_ENV: test BUNDLE_PATH: ./vendor/bundle DATABASE_NAME: covid19_line_bot_test DATABASE_USER: postgres DATABASE_PASSWORD: "" DATABASE_HOST: 127.0.0.1 DATABASE_PORT: 5432 TZ: Asia/Tokyo RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }} steps: - name: Check out code uses: actions/checkout@v2 - name: Cache Gemfile.lock uses: actions/cache@v2 with: path: ./vendor/bundle key: ${{ runner.os }}-rails-bundle-v1-${{ hashFiles('**/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-rails-bundle-v1- - name: Ruby 2.6.5のセットアップ uses: ruby/setup-ruby@v1 with: ruby-version: 2.6.5 - name: pg gem の依存関係のインストール run: | sudo apt-get update; sudo apt-get install libpq-dev - name: node v10のセットアップ uses: actions/setup-node@v1 with: node-version: '10.x' - name: yarn install run: | yarn install --check-files - name: gem install run: | gem install bundler -v 1.17.3 - name: bundler install run: | bundle check || bundle install --path vendor/bundle --jobs 4 --retry 3 - name: Setup Database run: | bundle exec rails db:create db:schema:load --trace - name: Exec RSpec run: | bundle exec rspec --format documentation --force-color --backtrace - name: Exec Brakeman run: | bundle exec brakeman -A - name: Exec rubocop run: | bundle exec rubocop --extra-details --display-style-guide --parallel --display-cop-names test_slack_notification: name: 【Test Result】Slack Notification by Github Actions runs-on: ubuntu-latest needs: build_and_test env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_USERNAME: Github Actions Result steps: - name: Slack Notification on Success uses: rtCamp/action-slack-notify@v2 if: success() env: SLACK_MESSAGE: Pass rubocop, rspec, brakeman SLACK_TITLE: Test Complete!?✨ - name: Slack Notification on Failure uses: rtCamp/action-slack-notify@v2 if: failure() env: SLACK_MESSAGE: Fail rubocop, rspec, brakeman SLACK_TITLE: Test Failure!? SLACK_COLOR: "#dc3545" deploy_production: name: Herokuにデプロイ needs: build_and_test runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' steps: - name: Check out code uses: actions/checkout@v2 - name: Deploy to Heroku Production uses: akhileshns/heroku-deploy@v3.6.8 with: heroku_api_key: ${{ secrets.HEROKU_API_KEY }} heroku_app_name: ${{ secrets.HEROKU_APP_NAME }} heroku_email: ${{ secrets.HEROKU_EMAIL }}解説
name
name: Build And Testワークフローの名前GitHub リポジトリの [Actions] タブに表示されるワークフローの名前
on
ワークフローを実行するタイミングを指定
on: pull_request: push: branches: - master上記の例では、以下のタイミングでワークフローが走ります。
- Pull Requestが作られたとき
- MasterブランチにPushされたとき
他にもデプロイがされたとき、ブランチが作られたとき、プルリクにレビューが投げられたとき、など様々なタイミングを指定することが可能。
参考:Events that trigger workflows
jobs
ワークフローは1つ以上のJobからなる。
複数のジョブはデフォルトでは、並行に実行される。jobs.job_id
JobにはJobを一意に判別するIDが必要
jobs: build_and_test: name: Exec RSpec rubocop brakeman runs-on: ubuntu-latest services: ~~~ [省略] test_slack_notification: name: 【Test Result】Slack Notification by Github Actions runs-on: ubuntu-latest needs: build_and_test env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} SLACK_USERNAME: Github Actions Result steps: ~~ [省略] deploy_production: name: Herokuにデプロイ needs: build_and_test runs-on: ubuntu-latest if: github.ref == 'refs/heads/master' [省略]上記のコードではこれらがJobのIDのキーに該当します。
- build_and_test
- test_slack_notification
- deploy_production
jobs.job_name
Jobの名前
build_and_test: name: Exec RSpec rubocop brakeman test_slack_notification: name: 【Test Result】Slack Notification by Github Actions deploy_production: name: Herokuにデプロイjobs.runs_on
Jobを実行するマシンを指定します。
build_and_test: name: Exec RSpec rubocop brakeman runs-on: ubuntu-latest2020年12月16日現在は、このような環境が用意されています
Virtual environment YAML workflow label Windows Server 2019 windows-latest or windows-2019 Ubuntu 20.04 ubuntu-20.04 Ubuntu 18.04 ubuntu-latest or ubuntu-18.04 Ubuntu 16.04 ubuntu-16.04 macOS Big Sur 11.0 macos-11.0 macOS Catalina 10.15 macos-latest or macos-10.15 参考:Github Actions Supported runners and hardware resources
jobs.services
ジョブのためのサービスコンテナをホストするために使用
[サービスコンテナ]
Jobを実行するのに必要になるDBやRedisなどのサービスを提供できるDockerコンテナjobs: build_and_test: name: Exec RSpec rubocop brakeman runs-on: ubuntu-latest services: postgres: image: postgres:11.5 ports: - 5432:5432 # ↓postgresが起動するまで待つヘルスチェックの設定 options: >- --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5jobs.env
Job中で使える環境変数を定義
Stepの中に書けば、そのStepでのみ利用可能な環境変数を定義できるjobs: build_and_test: name: Exec RSpec rubocop brakeman [省略] env: RAILS_ENV: test BUNDLE_PATH: ./vendor/bundle DATABASE_NAME: covid19_line_bot_test DATABASE_USER: postgres DATABASE_PASSWORD: "" DATABASE_HOST: 127.0.0.1 DATABASE_PORT: 5432 TZ: Asia/Tokyo RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}jobs.needs
ジョブの実行前に正常に完了する必要があるジョブを指定できる
以下の例だと、build_and_test
が正常に完了しないと、test_slack_notification
もdeploy_production
も実行されjobs: [省略] test_slack_notification: name: 【Test Result】Slack Notification by Github Actions runs-on: ubuntu-latest needs: build_and_test deploy_production: name: Herokuにデプロイ needs: build_and_testjobs.if
ジョブを実行する条件を書くことができる
deploy_production: name: Herokuにデプロイ needs: build_and_test runs-on: ubuntu-latest if: github.ref == 'refs/heads/master'github.refとは
ワークフローの実行をトリガーしたブランチまたはタグ ref。 ブランチの場合、これはrefs/heads/ の形式で、タグの場合は refs/tags/ です
参考:GitHub Actions のコンテキストおよび式の構文
steps
Jobで実行するコマンドなどを書いていく
steps: - name: Check out code uses: actions/checkout@v2 - name: Cache Gemfile.lock uses: actions/cache@v2 with: path: ./vendor/bundle key: ${{ runner.os }}-rails-bundle-v1-${{ hashFiles('**/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-rails-bundle-v1- - name: Ruby 2.6.5のセットアップ uses: ruby/setup-ruby@v1 with: ruby-version: 2.6.5 - name: pg gem の依存関係のインストール run: | sudo apt-get update; sudo apt-get install libpq-dev - name: node v10のセットアップ uses: actions/setup-node@v1 with: node-version: '10.x' - name: yarn install run: | yarn install --check-files - name: gem install run: | gem install bundler -v 1.17.3 - name: bundler install run: | bundle check || bundle install --path vendor/bundle --jobs 4 --retry 3 - name: Setup Database run: | bundle exec rails db:create db:schema:load --trace - name: Exec RSpec run: | bundle exec rspec --format documentation --force-color --backtrace - name: Exec Brakeman run: | bundle exec brakeman -A - name: Exec rubocop run: | bundle exec rubocop --extra-details --display-style-guide --parallel --display-cop-namessteps.name
GitHubで表示されるステップの名前
steps: - name: Check out code uses: actions/checkout@v2steps.uses
ステップとして実行するアクションを選択
steps: - name: Check out code uses: actions/checkout@v2 - name: Cache Gemfile.lock uses: actions/cache@v2 with: path: ./vendor/bundle key: ${{ runner.os }}-rails-bundle-v1-${{ hashFiles('**/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-rails-bundle-v1- - name: Ruby 2.6.5のセットアップ uses: ruby/setup-ruby@v1 with: ruby-version: 2.6.5使っているusersの説明
actions/checkout@v2
リポジトリをチェックアウトする
- name: Check out code uses: actions/checkout@v2参考:https://github.com/actions/checkout
actions/cache@v2
キャッシュできる
- name: Cache Gemfile.lock uses: actions/cache@v2 with: path: ./vendor/bundle key: ${{ runner.os }}-rails-bundle-v1-${{ hashFiles('**/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-rails-bundle-v1-
path: ~~
はキャッシュする、復元するファイルやディレクトリを指定する
key: ~~
はキャッシュを保存/復元するためのキー
restore-keys: ~~
はキーのキャッシュヒットが発生しなかった場合にキャッシュを復元するために使用するキーの順序付きリスト参考:https://github.com/actions/cache
ruby/setup-ruby@v1
- name: Ruby 2.6.5のセットアップ uses: ruby/setup-ruby@v1 with: ruby-version: 2.6.5
ruby-version:~~
で使用するRubyのバージョンを指定できる
参考:https://github.com/ruby/setup-rubyactions/setup-node@v1
- name: node v10のセットアップ uses: actions/setup-node@v1 with: node-version: '10.x'
node-version:~~
で使用するNodeのバージョンを指定できる
参考:https://github.com/actions/setup-nodertCamp/action-slack-notify@v2
Slackに通知を送ることができる
- name: Slack Notification on Success uses: rtCamp/action-slack-notify@v2 if: success() env: SLACK_MESSAGE: Pass rubocop, rspec, brakeman SLACK_TITLE: Test Complete!?✨
SLACK_WEBHOOK:$ {{secrets.SLACK_WEBHOOK}}
SlackのWebHockのURL設定は必須参考:https://github.com/rtCamp/action-slack-notify
akhileshns/heroku-deploy@v3.6.8
Herokuにデプロイできる
- name: Deploy to Heroku Production uses: akhileshns/heroku-deploy@v3.6.8 with: heroku_api_key: ${{ secrets.HEROKU_API_KEY }} heroku_app_name: ${{ secrets.HEROKU_APP_NAME }} heroku_email: ${{ secrets.HEROKU_EMAIL }}heroku_api_key:、heroku_app_name:、heroku_emailは必須
参考:https://github.com/AkhileshNS/heroku-deploy
steps.run
コマンドラインプログラムを実行する
- name: yarn install run: | yarn install --check-files - name: gem install run: | gem install bundler -v 1.17.3 - name: bundler install run: | bundle check || bundle install --path vendor/bundle --jobs 4 --retry 3 - name: Exec RSpec run: | bundle exec rspec --format documentation --force-color --backtracerunの中では、bundle installやyarn install、Rspecの実行などを行うようにしました。
終わりに
Rails×PostgreSQL環境を使うことが多くていままでCircleCIを利用していましたが今回GithubActionsを書いてみました。CIとしてそれほど大きな違いはなく感じたので、個人的には学習コストは低かったです。
ドキュメントも非常に分かりやすく日本語で書かれていてよかったです。
- 投稿日:2020-12-16T16:19:11+09:00
[Rails]フラッシュメッセージを一定時間で消す方法
開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina実装
Gemfilegem 'jquery-rails'ターミナルbundle installcontrollerdef create @book = Book.new(book_params) @book.user_id = current_user.id if @book.save redirect_to book_path(@book) flash[:notice] = "本が投稿されました" else @books = Book.all flash.now[:alart_flash] = "本の投稿に失敗しました" render 'index' end end本が投稿された時にはnotice、失敗した際にはalart_flashと名付けて判別。ここの名前は自分で変えてもOK!
flash[:]にすると、次のアクションまで表示させる。
flash.now[:]にすると、次のアクションに移行した時点で消える仕組みになっているので
renderは指定したviewsを呼び出すだけなので、アクションではないから気をつける。
redirect_toは次のアクションになるので、flash.nowだと表示がされないので注意が必要フラッシュメッセージ装飾
application.scss.flash{ width: 100%; height: 30px; font-size: 18px; text-align: center; padding: 0; z-index: 1; } .notice{ background-color: #65A2FF; } .alart_flash{ color: #FFFFFF; background-color: #FF0000; }一定時間でフラッシュメッセージを消す。
application.js// フラッシュメッセージ $(function(){ $('.flash').fadeOut(4000); //4秒かけて消えていく });
- 投稿日:2020-12-16T15:42:33+09:00
【保存版】Docker × React × Railsで環境構築していく方法
はじめに
本記事へのアクセスありがとうございます。
投稿主はプログラミング初心者であり、この方法が「最適解」かは分かりません。
しかし、動作は検証済みであり同様な記事も確認できたので信憑性はあると思います。記事通りにコピペしていくだけで環境構築できますので、説明がいらない人はコードだけをコピペして行ってください。
想定読者
- Dockerインストール済み
- Docker初心者
- フロントエンド側とバックエンド側の開発環境を分けて構成したい
- 現在 ( 2020年12月 )にある同様なQiita記事でエラーで詰まってしまっている
最終ファイル構成
- apiの中にRailsファイルを格納されています。
- frontの中にReactファイルを格納されています。
さっそくスタート
初期ファイルを用意する
apiの中にはDockerfile , entrypoint.sh , Gemfile , Gemfile.lockの4つを作成する。
* Gemfile.lockは何も記述しないファイルとする。docker-compose.ymlの記述
docker-compose.ymlを記述していきます
docker-compose.ymlversion: '3' services: db: image: postgres:12.3 volumes: - postgres-data:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD=password api: build: context: ./api/ dockerfile: Dockerfile command: /bin/sh -c "rm -f /myapp/tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" image: rails:dev volumes: - ./api:/myapp - ./api/vendor/bundle:/myapp/vendor/bundle - ./api/vendor/node_modules:/myapp/vendor/node_modules environment: TZ: Asia/Tokyo RAILS_ENV: development ports: - "3000:3000" depends_on: - db front: build: context: ./front/ dockerfile: Dockerfile volumes: - ./front:/usr/src/app command: sh -c "cd react-sample && yarn start" ports: - "8000:3000" volumes: postgres-data: driver: local bundle: node_modules:api / Dockerfileの記述
Dockerfileを記述していきます
FROM ruby:2.7 RUN apt-get update -qq && apt-get install -y nodejs postgresql-client yarnpkg RUN ln -s /usr/bin/yarnpkg /usr/bin/yarn RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"]api / entrypoint.shの記述
entrypoint.shを記述していきます
entrypoint.sh#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"api / Gemfileの記述
Gemfileを記述していきます
source 'https://rubygems.org' gem 'rails', '~>6'front / Dockerfileの記述
Dockerfileを記述していきます
FROM node:10-alpine RUN mkdir /myapp WORKDIR /usr/src/app* node:10以上出ないと後々にcreate-react-app出来ないので注意してください
コマンドを実行する
まずは以下の3つのコマンドをターミナルで入力してください
$ docker-compose run api rails new . --force --no-deps --database=postgresql --api $ docker-compose build $ docker-compose run --rm front sh -c "npm install -g create-react-app && create-react-app react-sample"api/config/database.ymlを下記のように書き換えてください
api/config/database.ymldefault: &default adapter: postgresql encoding: unicode # For details on connection pooling, see Rails configuration guide # https://guides.rubyonrails.org/configuring.html#database-pooling host: db username: postgres password: password pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>次に以下のコマンドをターミナルで入力してください
$ docker-compose up以下のコマンドを現在まで使用しているターミナルとは別のターミナル(新規作成)で入力してください
$ docker-compose run api rake db:create以上で環境構築が完了です。
おわりに
この状況で...
localhost:3000にアクセスすると、Rails用のページにアクセスします。
localhost:8000にアクセスすると、React用のページにアクセスします。お疲れ様でした。
少しでも役に立ったと思う方がいましたらLGTMをお願いします?♂️
おまけ
Docker内で開発するときは以下のコマンドを利用します。
docker-compose run web bundle exec rails g コマンド
- 投稿日:2020-12-16T14:36:19+09:00
【Docker】環境構築時に起きたエラー一覧
はじめに
Dockerについて学習し、既存のRailsアプリにDockerを導入しようと思い、公式のクイックスタートなどを参照しながら行いました。
その時に発生したエラーを備忘録のため、投稿しています。環境
Ruby '2.6.5'
Rails '6.0.0'
Docker for Mac導入済みエラー事例①
状況
DockerfileFROM ruby:2.6.5 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 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs yarn RUN mkdir /(アプリ名) WORKDIR /(アプリ名) COPY Gemfile /(アプリ名)/Gemfile COPY Gemfile.lock /(アプリ名)/Gemfile.lock RUN bundle install COPY . /(アプリ名) COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"]% docker-compose build で立ち上げようとすると下記のエラーが発生
エラー文
エラー文/usr/local/lib/ruby/2.6.0/rubygems.rb:283:in `find_spec_for_exe': Could not find 'bundler' (2.1.4) required by your /assist/Gemfile.lock. (Gem::GemNotFoundException) To update to the latest version installed on your system, run `bundle update --bundler`. To install the missing version, run `gem install bundler:2.1.4` from /usr/local/lib/ruby/2.6.0/rubygems.rb:302:in `activate_bin_path' from /usr/local/bin/bundle:23:in `<main>' ERROR: Service 'web' failed to build : The command '/bin/sh -c bundle install' returned a non-zero code: 1解決策
RUN gem install bundlerを挿入すると解決!
Dockerfile(中略) COPY Gemfile.lock /(アプリ名)/Gemfile.lock RUN gem install bundler RUN bundle install (中略)調べてみると、原因はlocal環境とDocker内でのbundlerのバージョンが違うため、エラーが出たそうです。gem install bundlerを入れるととりあえず解決。。。まだまだあります。。。
エラー事例②
状況
docker-compose.ymlversion: "3" services: db: image: mysql:5.6.47 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: root ports: - "3000:3000" volumes: - ./db/mysql/volumes:/var/lib/mysql web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" stdin_open: true tty: true volumes: - .:/(アプリ名) - gem_data:/usr/local/bundle ports: - "3000:3000" depends_on: - db volumes: mysql_data: gem_data:docker-compose build成功後、docker-compose up -dコマンドを実行したところ、
エラー文
ERROR: for web Cannot start service web: driver failed programming external connectivity on endpoint myapp_web_1 (ae889e882d7c9f8b72f9c9b244159d86662f4abebef7d15fac4016573fe56de4): Bind for 0.0.0.0:3000 failed: port is already allocated ERROR: Encountered errors while bringing up the project.解決策
DBサーバーとWebサーバーのポート番号を3000で同じにしていたため、Webサーバーが立ち上がらないことが原因であると考えます。単純なミスでした。。
DBのポート番号を3306に変更し、修正しました。
docker-compose.yml(中略) ports: - "3306:3306"エラー事例③
状況
先ほどのエラーを解決後、もう一度、docker-compose upコマンド実行してみると、、下記のエラーが発生。
エラー文warning Integrity check: System parameters don't match error Integrity check failed error Found 1 errors. web_1 | web_1 | web_1 | ======================================== web_1 | Your Yarn packages are out of date! web_1 | Please run `yarn install --check-files` to update. web_1 | ======================================== web_1 | web_1 | web_1 | To disable this check, please change `check_yarn_integrity` web_1 | to `false` in your webpacker config file (config/webpacker.yml). web_1 | web_1 | web_1 | yarn check v1.22.5 web_1 | info Visit https://yarnpkg.com/en/docs/cli/check for documentation about this command. web_1 | web_1 | web_1 | Exiting見たところ、yarnのupgradeを行ってくださいかのように感じたため、yarn upgradeコマンドを実行するも、変わらず。。。
解決策
エラー文をよくよく見てみると、、、
web_1 | To disable this check, please change `check_yarn_integrity` web_1 | to `false` in your webpacker config file (config/webpacker.yml).のような記述があったため、早速該当のディレクトリに行ってみると
config/webpacker.yml(中略) check_yarn_integrity: falseありました!!defaultでtrueになっていたため、falseに書き換えると解決しました!!!最後の1個いきます。。。
エラー事例④
状況
docker-compose up -dが成功し、localhost:3000でアクセスしようとすると
ActiveRecord::NoDatabaseError
が発生。
解決策
単純でしたね。db:createコマンドを忘れていました。。。
ターミナル% docker-compose exec web rails db:create % docker-compose exec web rails db:migrate終わりに
ビューファイルが思いっきり崩れていたので、原因究明してきます。。。。
- 投稿日:2020-12-16T14:26:55+09:00
【Rails】seeds.rbを使ったdbへの反映
本投稿の目的
・Railsについての議事録です。
学習に使った教材
Udemyの以下2つの教材を参考にまとめました。
・"はじめてのRuby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう"
・"フルスタックエンジニアが教える 即戦力Railsエンジニア養成講座"
○seeds.rbについて
・新規投稿view実装前に初期データを投入する際に使用
・rails db consoleに比べて,大量のデータを実装する際に便利
・(基本的には,rails console からの作成が一般的)○seeds.rbの記述方法
・プロジェクト名/db/seeds.erbにコードを記述
・今回は,数字の繰り返しパターンのユーザーデータを50個dbへ反映させるqiita.rbif Rails.env == 'development' (1..50) each do |i| model名.create(name: "ユーザー#{i}", title: "タイトル#{i}", body:"本文#{i}") end end【解説】
Rails.env == 'development'
・railsの実行環境が開発モードかどうかを判定
・env は"development(開発)" "テスト(test)" "production(本番)"の3つが存在
model名.cretate(column名: 値,column名: 値)
・データベースに値を保存するコード【createメソッドについて】
以下のようにすることで縦に並べて記述が可能qiita.rbmodel名.create([ { name: '値1' }, { name: '値2' }, { name: '値3' }, { name: '値4' }, { name: '値5' }, { name: '値6' } ]) end【解説】
・配列の中にハッシュ形式で記述する
・まとめて複数のデータを保存が可能○seeds.rbの実行
rails db:seed・seeds.rbに記載した情報をdbに反映させるためのコード
- 投稿日:2020-12-16T13:54:39+09:00
Rails:コントローラに定義する基本的7つのアクション
最初に
カレンダー企画2020の16日目
プログラミングの勉強を始めて3ヵ月程経ったので学んだことのメモをアウトプットとして記事に残します。
これからプログラミングの世界に入る人の手助けになれたら嬉しい限りです。
間違っていたり、言葉が違っていたり、誤解されるような言葉があったら教えてください^^
言葉を長々と読みづらかったら申し訳ありません。少しずつなれてがんばります。railsの7つのアクションについてちょっとまとめてみる
railsのControllerで使われる基本7つのアクションがある。
(例外として自作で名前をつけて作る事もある。)
これから紹介する7つの役割をしたい時は原則つける名前が定義されている。
一覧でどうぞ!
アクション名 役割 new データを新規作成する create データを追加する index データの一覧を表示する show データの内容を表示する edit データを編集する update データを更新する destroy データを削除する 言葉の表現の仕方は色々あると思うので自分で理解しやすいものにしてください!(意味が変わらない程度に、、、)
new
新規作成するという役割があります。
たとえば、
SNSで何か投稿しようと文字を書いたり、写真をアップロードしたりする場所
ブログなら記事を書いている場所
そこがnewアクションです!create
データを追加、登録するという役割があります。
たとえば、
newアクションで作成したもの(上記の例の続き)
「投稿」とかのボタンありますよね?
それがcreateアクションです!書いただけではどこにも表示されない。誰も見れない。残らない状態ですよね!
createをしてはじめてデータとして残ります。index
一覧表示という役割があります。
たとえば、
SNSでたくさんの人の投稿内容がズラッと並びますよね?
それがindexアクションです。show
内容、詳細を表示という役割があります。
たとえば、
indexで表示されているものをクリック、タップすると内容が全部みれたり、画面がその投稿だけになりますよね?
それはshowアクションです。edit
編集する役割があります。
たとえば、
投稿した内容で間違えあった!
情報が更新されたので投稿内容を変えたい!
そんな時に編集しますよね?編集するところがeditアクションです。
newではないところに新しく作るアクションで
editではある物を作り直すアクションです。(newに似ていますね。)update
更新する役割があります。
編集した内容を保存するイメージです。
newを保存するのにcreateアクションを使います。
editを保存するのにupdateアクションを使います。セットで覚えやすいかもしれないですね。
updateはeditを保存するだけでなく他にも変更を更新したりします。
editとだけセットという考えはやめてください。(書いといてなんですが、、、)上書き保存的なイメージです!(私は!!)
destroy
削除の役割です。
たとえば、
投稿をを削除したり、アカウントを削除したりする時に行われるアクションです。
データベースからデータを消し去ります!(物理削除というみたいです。)これは余談
論理削除というのもあり、画面上では消えているがデータは残っている状態です。たとえば、
運営としては過去に注文した履歴は残して置きたい。でも、ユーザーは退会したい。こんな時に使われます。ユーザー側からはログインできない状態(退会)管理者側の画面にはデータが残るという処理もできます。
この時に使うアクションはdestroyではなく、updateなのです!こういうupdateの使い方もあるみたいです!最後に
ザーッとまとめましたが、あくまで基本的なアクションです。
showの画面にindexを表示するようなこともあります。上記の内容が絶対ではないですが基本的にこれを使います!感じで覚えて置くといいかも!
- 投稿日:2020-12-16T13:23:13+09:00
メソッド定義における基礎知識
メソッドの定義
メソッドを定義するにあたっての基礎的な部分を
アウトプットを兼ねてこちらで共有していきます!!メソッドについて
そもそも、メソッドとはプログラミングにおいて何らかの処理をまとめたもののことを言い、そのメソッドの名称を記述することによって記述したメソッドの処理を実行することができます。
例えば、lengthメソッドを記述すれば文字列の文字数を出力することができます
※ターミナルのirbにて
irb(main):001:0> "baseball".length => 8メソッドの定義
メソッド定義の記述
このようなメソッドは予め数多く存在していますが、すでにあるもののみならず自分で定義することで作成することができます。
メソッドを作成する際には以下のように記述します。def メソッド名 # 実行する処理のコード endこれをもとに例文を作成してみると
例文↓def my_hobby puts "私の趣味はプロ野球観戦です。" end my_hobby # 出力結果 私の趣味はプロ野球観戦です。上記のように、my_hobbyというメソッドを定義したことによって
my_hobbyと記述することによって「私の趣味はプロ野球観戦です。」を出力する処理を実行することができます!コードが読まれる順番は普通では上からとなりますが、定義されたメソッドが存在する場合は、そのメソッドの記述は読まれずに一旦スルーされます。
上記の例で言うと、my_hobbyと記述してそれが読み込まれた段階で定義されたメソッドのmy_hobbyが呼び出されて処理が実行されます。実行したメソッドの最終的な値
メソッドを定義し、そのメソッドの処理が実行されて出力される値のことを戻り値または、返り値といい、上記の例文でいうと
「私の趣味はプロ野球観戦です。」という出力結果の部分が戻り値となります。またlengthメソッドを用いた場合の戻り値は、文字数として出力された数字が戻り値となります。
そして定義されたメソッドに関しては、メソッド内の処理の1番最後の行の処理の結果が戻り値となって出力されます
例文↓
def week "Sunday" "Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" # 1番最後の行が戻り値! end puts week # => Saturdayこのようにメソッド内の処理は最後の行が出力されるが
return文をメソッド内に記述すると戻り値を指定することができます!例文↓
def week "Sunday" "Monday" return "Tuesday" # これが戻り値となり、処理が終了する! "Wednesday" # 呼び出されない "Thursday" # 呼び出されない "Friday" # 呼び出されない "Saturday" # 呼び出されない end puts week # => Tuesday上記のようにreturnを指定することによって、戻り値として処理を行う部分を指定することができ、returnを記述した部分が出力されます。
値の使用可能範囲
定義した変数を使用できる範囲は限られていて、その範囲のことをスコープと言います。
①定義したメソッド外で定義された変数をそのメソッド内で使用することは出来ず
、その逆も同じように②定義したメソッド内の変数をメソッド外で使用することは出来ない
ためエラーとなります。①の場合の例文↓
def introduce puts name end name = "Kinoshita" introduce # => エラー!②の場合の例文↓
def introduce name = "Kinoshita" end puts name # => エラー!いずれも、スコープの範囲外ということで定義された変数が使えずにエラーとなってしまいます。
こういったスコープ範囲外で定義された値を使いには引数(ひきすう)という値を使用します。
引数を使用するには以下のように記述します。例文↓
def メソッド名(仮引数) # 処理の記述 end メソッド名(実引数)仮引数はメソッドを定義する際に記述し処理の際に利用する引数で、
実引数はメソッドを呼ぶ際に受け取る値を記述する引数のことを言います。
また、引数は複数用いることができ、その場合は
メソッド名(第1引数, 第2引数)
という記述をします。仮引数と実引数の名称は必ずしも一致している必要はありませんが、引数の数は必ず一致される必要があります。
上記の例文のエラーを解決するために引数を用いてみます。
例文↓def introduce(name) puts name end teacher = "Kinoshita" introduce(teacher) # => "Kinoshita"上記のように、introduceメソッド外で定義されたteacherが実引数として記述することで、メソッド外で定義された変数teacherが、introduceメソッド内の仮引数nameとして渡され、メソッド内の処理を実行する。
これによってメソッド外で定義された変数が使用できる形になります!
- 投稿日:2020-12-16T12:52:36+09:00
【Rails】デバッグ用gem 'better_errors' , 'binding_of_caller' を導入
はじめに
ポートフォリオ作成で一番苦しんだのが、デバッグ!
そこで今回は、便利で高機能なエラー画面が表示されるらしいbetter_errors
とbinding_of_caller
を導入してみました。前提
- Ruby2.7.0
- Ruby on Rails6.0.3
- MySQL8.0
better_errors
とはデフォルトのエラー画面をわかりやすく整形してくれるgem。
binding_of_caller
とは上記
better_errors
と一緒に使うことで、ブラウザ上でirbを使えるようになるgem。いざ、導入
Gemfilegroup :development do gem 'better_errors' gem 'binding_of_caller' endターミナルbundle
※Dockerを使用している場合はもうひと手間必要とのこと。
app/config/environments/development.rbBetterErrors::Middleware.allow_ip! "0.0.0.0/0"エラー箇所の画面確認
下記のように、エラーメッセージやデバッグ画面が表示され、
ターミナルを表示しなくてもブラウザからのirbでデバッグ可能。
その他、トレース情報やリクエスト情報、ローカル変数なども確認できる。
終わりに
これはめちゃくちゃ便利!デバッグが捗る!
railsでアプリを作る際は、必ず導入しようと思います!参考サイト
- 投稿日:2020-12-16T10:16:38+09:00
【Rails6】ActionMailerを用いたお問い合わせ機能の実装
はじめに
転職活動用ポートフォリオ作成中です。今回、ユーザーからのお問い合わせ機能を実装したため、備忘録及び復習のため記述します。
環境
Ruby on Rails'6.0.0'
Ruby'2.6.5'①ルーティングの記述
config/routes.rbresource :contacts, only: [:new, :create] do get "/thanks" => "contacts#thanks" end今回は、ユーザーがお問い合わせを送信後、「お問い合わせいただきありがとうございました!」のようなページを表示させる流れです。
②モデルの記述
rails g model contactコマンドでモデルを作成後、マイグレーションファイルの記述などを行っていきます。
db/migrate/20201204073627_create_contacts.rbclass CreateContacts < ActiveRecord::Migration[6.0] def change create_table :contacts do |t| t.string :name, null: false t.string :email, null: false t.text :content, null: false t.timestamps end end end今回は、名前・返信用のメールアドレス・お問い合わせ内容を指定しました。その後、rails db:migrateコマンドを実行します。
app/models/contact.rbclass Contact < ApplicationRecord validates :name, :email, :content, presence: true end名前・メールアドレス・お問い合わせ内容が空では保存できないようにバリデーションを設定します。
③ActionMailerの設定
今回はユーザーからお問い合わせが送信されると、内容が管理者へメールで届くような機能にしたいと思います。そこでActionMailerを使用しました。
まず、rails g mailer ContactMailerコマンドを実行します。すると以下のファイルが生成されるためお問い合わせが来たら管理者へメールを送信する記述をしていきます。app/mailers/contact_mailer.rbclass ContactMailer < ApplicationMailer def contact_mail(contact) @contact = contact mail to: '(管理者のメールアドレス)@gmail.com', subject: '(メールのタイトル)' end end続いては新たにviewファイルが生成されているため、メールの本文を記述していきます。
app/views/contact_mailer/contact_mail.html.erb<p>ユーザーネーム:<%= @contact.name %></p> <p>メールアドレス:<%= @contact.email %></p> <p>お問い合わせ内容:<%= @contact.content %></p>④コントローラーの記述
それではコントローラーの記述をしていきます。
app/controllers/contacts_controller.rbclass ContactsController < ApplicationController def new @contact = Contact.new end def create @contact = Contact.new(contact_params) if @contact.save ContactMailer.contact_mail(@contact).deliver redirect_to thanks_contacts_path else render :new end end def thanks end private def contact_params params.require(:contact).permit(:name, :email, :content) end endこれにより、createアクションが呼び出され、お問い合わせが正常に保存されると、メール送信処理も走り出すように設定できました。
⑤viewの記述
お問い合わせのviewファイルの記述をしていきます。
app/views/contacts/new.html.erb<div class="container"> <div class="row"> <div class="offset-sm-2 col-sm-8 offset-sm-2"> <%= form_with model: @contact, local: true do |f| %> <h5 class='form-header-text text-center'><i class="far fa-paper-plane fa-2x my-orange"></i> お問い合わせ</h5> <%= render 'layouts/error_messages', model: f.object %> <div class="form-group"> <div class='form-text-wrap'> <label class="form-text" for="name">お名前</label> <span class="badge badge-danger">必須</span> </div> <div class='input-name-wrap'> <%= f.text_field :name, class:"input-name", id:"name", placeholder:"例) 田中太郎" %> </div> </div> <div class="form-group"> <div class='form-text-wrap'> <label class="form-text" for="email">メールアドレス</label> <span class="badge badge-danger">必須</span> </div> <%= f.email_field :email, class:"input-default", id:"email", placeholder:"PC・携帯どちらでも可", autofocus: true %> </div> <div class="form-group"> <div class='form-text-wrap'> <label class="form-text" for="content">内容</label> <span class="badge badge-danger">必須</span> </div> <%= f.text_area :content, class:"article-input-default", id:"content", autofocus: true %> </div> <div class='contact-btn text-center'> <%= f.submit "送信する" ,class:"btn btn-outline-danger w-50" %> </div> <% end %> </div> </div> </div>app/views/contacts/thanks.html.erb<div class="container"> <div class="row"> <div class="offset-sm-2 col-sm-8 offset-sm-2"> <h5 class='header-text text-center'><i class="far fa-smile fa-lg my-orange"></i> お問い合わせありがとうございました</h5> <%= link_to 'トップページへ戻る', root_path %> </div> </div> </div>⑥Gmailの設定
最後に今回アドレスで使用するGmailの設定を行いました。
config/environments/development.rb(中略) config.action_mailer.perform_deliveries = true config.action_mailer.raise_delivery_errors = true config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', domain: 'gmail.com', port: 587, user_name: '(管理者のメールアドレス)@gmail.com', password: ENV["GMAIL_KEY"], authentication: 'plain', enable_starttls_auto: true } (中略)すでに他の設定も記述されているかと思うので、間に記述しました。
GmailのパスワードはGitHub上に上がってしまっては、大変なことになってしまうので、環境変数を設定します。設定する方法は様々あると思いますが、私はvimコマンドを使用して「.zshrc」ファイルに設定しました。ターミナル% vim ~/.zshrc上記のコマンドでファイルを開き、insertモードにしてから記述していきます。
.zshrcファイル(中略) export GMAIL_KEY = "Gmailのパスワード" (中略)こちらで設定が完了しました!
⑦テストコードの実装
おまけとしてRspecを使用してテストコードも実装しました。
FactoryBot
spec/factories/contacts.rbFactoryBot.define do factory :contact do name { Faker::Name.name } email { Faker::Internet.email } content { Faker::Lorem.sentence } end end単体テストコード
spec/models/contact_spec.rbrequire 'rails_helper' RSpec.describe Contact, type: :model do before do @contact = FactoryBot.build(:contact) end describe 'お問い合わせの送信' do context 'お問い合わせが送信できる場合' do it '全ての要素が存在すれば投稿できる' do expect(@contact).to be_valid end end context 'お問い合わせが送信できない場合' do it 'nameが空では送信できない' do @contact.name = nil @contact.valid? expect(@contact.errors.full_messages).to include('お名前を入力してください') end it 'emailが空では送信できない' do @contact.email = nil @contact.valid? expect(@contact.errors.full_messages).to include('メールアドレスを入力してください') end it 'contentが空では送信できない' do @contact.content = nil @contact.valid? expect(@contact.errors.full_messages).to include('お問い合わせ内容を入力してください') end end end end結合テストコード
spec/system/contacts_spec.rbrequire 'rails_helper' RSpec.describe 'お問い合わせ送信', type: :system do before do @contact = FactoryBot.build(:contact) end context 'お問い合わせの送信ができるとき' do it '正しい情報を入力すれば、お問い合わせを送信できる' do visit root_path expect(page).to have_content('お問い合わせ') visit new_contacts_path fill_in 'お名前', with: @contact.name fill_in 'メールアドレス', with: @contact.email fill_in '内容', with: @contact.content expect do find('input[name="commit"]').click end.to change { Contact.count }.by(1) expect(current_path).to eq thanks_contacts_path click_link 'トップページへ戻る' expect(current_path).to eq root_path end end context 'お問い合わせの送信ができないとき' do it '正しい情報を入力しなければ、お問い合わせは送信できない' do visit root_path expect(page).to have_content('お問い合わせ') visit new_contacts_path fill_in 'お名前', with: '' fill_in 'メールアドレス', with: '' fill_in '内容', with: '' expect do find('input[name="commit"]').click end.to change { Contact.count }.by(0) expect(current_path).to eq contacts_path end end end終わりに
https://qiita.com/mmdrdr/items/9c5dd4ca886f034fb0ef
https://qiita.com/hirotakasasaki/items/ec2ca5c611ed69b5e85e上記の記事を参考にさせていただきました。ありがとうございました。
メール送信のテストもしてみたいと思うので、調べてみます!!
誤っている点ありましたらご指摘ください。
- 投稿日:2020-12-16T09:53:56+09:00
rails_admin で alias_attribute な enum を扱えるようにする
前段
enum layout: { list: 0, grid: 1 }上記のような
top_layout
がinteger
のような enum があったとして、それをtop_layout
という名前に変えたいとします。(カラムのリネームが一番良いというのは置いておいて)アプリケーションで扱う上では、以下のように
alias_attribute
とenum
の変更で事足りました。alias_attribute :top_layout, :layout enum top_layout: { list: 0, grid: 1 }一方で、
rails_admin
が生成する UI の方は依然としてlayout
が表示され、top_layout
が現れません。rails_admin が ActiveRecord の属性をどう取得しているか
adapters/active_recored.rbmodule RailsAdmin module Adapters module ActiveRecord def properties columns = model.columns.reject do |c| c.type.blank? || DISABLED_COLUMN_TYPES.include?(c.type.to_sym) || c.try(:array) end columns.collect do |property| Property.new(property, model) end endざっと見ただけなので、他にも関連する要素はあるかもしれませんが、この properties で生成されたものをベースに扱っているように見えます。
これを確認したい場合には、console を開いて以下で確認できます。
> c = RailsAdmin.config(SampleModel.first) > c.abstract_model.properties当然 columns を取得されると、
alias_attribute
したところでどうしようもないです。カスタマイズする
config/initializer/rails_admin.rbRailsAdmin.config do |config| config.model SampleModel do include_all_fields exclude_fields :layout field :top_layout, :active_record_enum endWiki に従い、無効にしたい
layout
をexlude_fields
に入れ、新たに入れたいtop_layout
を追加し、active_record_enum
として登録しました。これで、通常の enum カラムと同じように表示されるようになります。
更新時に失敗する
このままだと、更新時になぜか
0
,1
といった値が、 enum の値として認識されず、「'0'
のような値は enum には存在しない」といったエラーが出てしまいます。ここに関しては以下が関わっています。
config/fields/types/active_record_enum.rbmodule RailsAdmin module Config module Fields module Types class ActiveRecordEnum < Enum def parse_input_value(value) abstract_model.model.attribute_types[name.to_s].deserialize(value) endこの
abstract_model.model
でアプリケーション側のモデル(例えばSampleModel
)を参照しているので、attribute_types
はSampleModel.attribute_types
などすれば確認できます。確認するとわかりますが、
alias_attribute
するだけでは、この一覧にtop_layout
は入ってきません。よって、ここでの deserialize は何も変化がなく、リクエストパラメーターとして送信された'0'
という文字列がそのままassign_attributes
に渡されてしまい、上述のエラーになってしまいます。attribute を定義する
attribute :top_layout, :integerこのような形で
top_layout
も attribute として定義を追加します。ここはActiveRecord::Enum::EnumType
を直接指定できるような type を与えたいところですが、それはできないのでinteger
にします。これは、もともとのlayout
の型と同じです。ここで attribute_types を実行するとわかりますが、alias_attribute 後も layout は ActiveRecord::Enum::EnumType として存在し続け、 top_layout は integer として定義されたような形になります。top_layout の enum 定義が、そのまま alias のオリジナルの layout の方にかかる形になるようです。
これで、更新時にも値の型変換が行われるようになったため、更新も問題なくできるようになりました。