simple_formを使って簡単に入力フォームを作成
simple_formとは
最低限の記述でformを生成することができるgemです。
simple_formを使うための設定
まずGemfileにsimple_formのgemを追加してbundle install
します。
gem 'simple_form'
generatorを起動させるコマンド実行します。
$ rails generate simple_form:install
bootstrapを適用させたい場合はbootstrapオプションをつけてコマンド実行します。
$ rails generate simple_form:install --bootstrap
simple_formの書き方
実際にsimple_formを使ってビューに書いてみます。
<%= simple_form_for @user do |f| %> <%= f.input :username %> <%= f.input :password %> <%= f.button :submit %> <% end %>
この記載だけでラベルやエラーなどの入力が含まれています。
下記のRailsのデフォルトのformヘルパーを使った書き方と比べるとコード量の違いが一目瞭然です。
<%= form_with @user do |f| %> <% if object.errors.any? %> <div id="error_messages" class="alert alert-danger"> <ul class="mb-0"> <% object.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %> <div class="form-group"> <%= f.label :username %> <%= f.text_field :username, class: "form-control" %> </div> <div class="form-group"> <%= f.label :password %> <%= f.password_field :password, class: "form-control" %> </div> <%= f.submit (t 'defaults.register'), class: 'btn btn-primary' %> <% end %>
入力フォームを変更する
simple_formではデフォルトでついてしまうラベルの表記などを変更したり、つけなかったり、またformヘルパーのように新たなオプションをつけることもできます。
- ラベル名の変更をしてみる
<%= f.input :username, label: '名前' %>
- フォームに初期値を入れてみる
<%= f.input :username, placeholder: '田中太郎' %>
- バリデーションエラーメッセージを表示しない
<%= f.input :username, error: false %>
ラベルの名前はlocals配下にあるja/ymlファイルに日本語表記を準備しておけば何も定義せずに日本語に表記が変わります。これはformヘルパーの時と同じですね!
as:を使って型変更をする
またsimple_formはデータベース内の列タイプ(string型など)を調べ、列に適切な入力を使用します。たとえば、text型で作成された列は、デフォルトでtextarea入力を使用します。この入力フォームに関してもオプションをつけて変更することが可能です。
例としてarticleのtitle(string型)をtextarea入力することができるように変更してみました。
<%= simple_form_for @article do |f| %> <%= f.input :title, as: :text %> <%= f.button :submit %> <% end %>
ラジオボタンの実装で躓いたので備忘録
画像の横幅を選択するラジオボタンをsimple_formを使って実装してみます。
画像の横幅のカラム名は:image_areaのinteger型に設定しました。
$ rails g migration AddImageAreaToArticles image_area:integer $ rails db:migrate
enumを使って「左、中央、右」の列挙型にします。
article.rb enum :image_area { left: 0, center: 1, right: 2 }
articleの新規作成ビューに次のように記述しました。
new.html.erb <%= simple_form_for @article do |f| %> <%= f.input :image_area, as: :radio_button %> <% end %>
ラジオボタンで選択する仕様に変わるかと思ったら、フォーム入力の設定になっていたので、検証ツールをみてみました。するとclass名に'form-control enum_radio_buttons optional'
という記述があったので'form-control'
について調べてみました。
input, select, textareaのようなテキスト形式のコントロールは form-control が付与されています。
Forms - Bootstrap 4.1 日本語リファレンス
と書いてあり、as: :radio_button
で型変更を行ったけど機能していないのかなと思いました。なので公式に書いてあったinput_fieldを使うと無事にラジオボタンを実装することができました。
<%= simple_form_for @article do |f| %> <%= f.input_field :image_area, as: :radio_button %> <% end %>
またenum_helpを定義しておくとラベルを日本語表記にしてくれます。
config/locals/enum.ja.yml ja: enums: article image_area: left: '左寄せ' center: '中央' right: '右寄せ'
応用編課題9で参照したサイト
画像を複数枚アップロードする方法
【Rails6.0】Active Storageを用いた単数枚、複数枚画像投稿の実装手順をそれぞれ解説|TechTechMedia
simple_formを使ってる時のmultipule: trueの書き方
【Rails5】carrierwaveとsimple_formで複数の画像をアップロードする | ニートエンジニア
variantの説明
Active StorageのVariantの指定方法いろいろ - Qiita
画像を複数枚受け取る方法(バリデーション)
Rails カスタムバリデーション(validator, EachValidator) - Qiita
Active Record バリデーション - Railsガイド
複数枚の画像の中から選択したものを削除する
Deleting ActiveStorage Attachments From the Controller, 3 Ways
Swiperの書き方
【Rails5】「Swiper」を使ってスライダー、カルーセルを作る方法 - Qiita
swiperをyarnで導入して、画像をスライダー形式にする! - Qiita
RailsでSwiperを導入する方法(Swiperは2020年7月にバージョンアップし、従来と設定方法が変わりました!) - Qiita
Active Storage
Actrive Storageとは
Actrive Storageとは、ファイルアップロードを行うための機能です。この機能を使うことでフォームで画像の投稿機能などを簡単に作ることができます。
また画像などのファイルのアップロードを簡単にするメソッドが使用でき、画像を保存するテーブルを簡単に作成できます。
Amazon S3、Google Cloud Storage、Microsoft Azure Storageなどの クラウドストレージサービスへのファイルのアップロードや、ファイルをActive Recordオブジェクトにアタッチする機能を提供します。development環境とtest環境向けのローカルディスクベースのサービスを利用できるようになっており、ファイルを下位のサービスにミラーリングしてバックアップや移行に用いることもできます。
Active Storageの使用方法
$ rails active_storage:install $ rails db:migrate
このマイグレーションによって、active_storage_blobs
とactive_storage_attachments
という2つのテーブルが生成されます。
active_storage_blobs
はファイル名、ファイルの種類、バイト数、誤り検出符号などのメタデータを保持するモデルactive_storage_attachments
はBlobオブジェクトとActive Recordオブジェクトを紐付けるための中間テーブル
なおここで作成された2つのモデルはActive Storageを使う際に触れることはありません。なのでカラムの追加など行うこともありません。
事前にTweetモデルを作成しておき、このTweetモデルに画像を添付できるような実装を行っていきます。
※ Active Storageでは、モデルを作成する際に画像用のカラムを用意する必要はありません。
次のコードのようにhas_one_attached
を使うことで、Tweetモデルに画像を一枚添付できるようになりました。
class Tweet < ApplicationRecord has_one_attached :image end
:image
はファイルの名前で、好きなように変更することもできます。(例えば :avatar
、:photo
など)
以下のように書くことでTweetに画像をつけることができるようになります。
tweet_controller.rb class TweetController < ApplicationController def create user = Tweet.create!(tweet_params) redirect_to root_path end def show @tweet = Tweet.find(params[:id]) end private def tweet_params params.require(:tweet).permit(:body, :image) end end
tweet投稿フォーム。フォームのfile_field
で選択された画像をTweetモデルと紐付けています。
new.html.erb <%= form_with model: @tweet, local: true do |f| %> <%= f.text_area :body %> <%= f.file_field :image %> <%= f.submit %> <% end %>
tweet詳細画面。image.attached?
で特定のtweetがimageを持っているかどうかを調べられます。
show.html.erb <% if @tweet.image.attached? %> <%= image_tag @tweet.image %> <% end %>
Actice Storageの保存先の設定
ファイルの保存先は各環境の設定ファイルに記載します。利用するサービスをActive Storageに認識させるには必要な記述です。
config/environments/development.rb Rails.application.configure do config.active_storage.service = :local end
config/environments/test.rb Rails.application.configure do config.active_storage.service = :test end
Active Storageのサービスはconfig/storage.ymlで宣言します。アプリケーションが使うサービスごとに、名前と必要な構成を指定します。
開発環境(local)、テストともにDiskサービス(ローカルのディスク)を使うと宣言します。またtmp/storageディレクトリがファイルの保存先に指定されています。
config/storage.yml local: service: Disk root: <%= Rails.root.join("tmp/storage") %> test: service: Disk root: <%= Rails.root.join("tmp/storage") %>
RSpecテストで画像を添付する
事前にテストで使う画像をspec/fitures/image
にセットしておきます。
それでは実際に「作成済みのtweetに画像を添付できるか」というテストを書いてみます。
※ FactoryBotにtweetとuser、またLoginモジュールを作成している前提です。
tweets_spec.rb require 'rails_helper' RSpec.describe "Tweets", type: :system do let!(:tweet) { create :tweet } let(:user) { create :user } describe '画像投稿機能' do context '画像を添付する' do it '正常に添付することができる' do Login_as(user) visit tweet_path(tweet) attach_file 'tweet[image]', "#{Rails.root}/spec/fixtures/image/hoge.jpg" click_on '投稿する' expect(page).to have_selector("img[src$='hoge.jpg']") end end end
attach_file
を使うことで画像を添付するという操作をすることができます。
Punditを使って権限の管理をする
Punditとは
リソースに対して、どのユーザーであれば処理が許可されるのかを定義するgemです。
参考 : GitHub - varvet/pundit: Minimal authorization through OO design and pure Ruby classes
コントローラの各アクションで authorizeリソースオブジェクト を呼ぶと対象のリソースに対して権限があるかを確認してくれます。そのポリシーをapp/policiesにあるポリシーファイルで細かく定義することができます。
Punditの使用方法
まずGemfileにPunditのgemを追加してbundle install
します。
gem "pundit"
継承元のコントローラ(application_controller.rb)にPunditをincludeします。
app/controllers/application_controller.rb class ApplicationController < ActionController::Base include Pundit end
generatorを使ってapplication_policy.rbファイルを作成します。
$ rails g pundit:install
app/policies/配下にポリシーファイルというものをオブジェクト別に作成していきます。
例題 : 記事(Article)アプリで管理者だけが記事の一覧画面、また記事の作成、削除、更新ができるように設定する。※ユーザーには管理者(admin)と一般(general)をenumで分けてある。
Policyファイルに認可を与えるユーザー(管理者)を設定していきます。
app/policies/article_policy.rb class ArticlePolicy < ApplicationPolicy def index? user.admin? end def create? user.admin? end def update? user.admin? end def destroy? user.admin? end end
articles_controller.rbの各アクションの実行前にauthorize
を実行して認可の許可を判断します。
class ArticlesController < ApplicationController def index authorize(Article) end def create authorize(Article) end def edit authorize(@article) end def update authorize(@article) end def destroy authorize(@article) end end
authorize(@article)
とauthorize(Article)
は、特定のarticleがあるかないかで使い分けます。記事一覧と記事新規作成画面では特定のarticleはないのでauthorize(Article)
を使っています。
view/articles/index.html.erbでこのようにビューファイルで権限によってリンクを表示するかしないか判定することができます。
<% if policy(Article).index? %> <%= link_to "記事一覧", articles_path %> <% end %>
403 Forbiddenのエラー画面を表示
403 Forbiddenのステータスコードは、要求されたリソースはサーバー上にありますが、アクセス権やIPアドレス制限などの問題でアクセス拒否されたことを表します。
今回はgeneralでアクセスするとこの403のエラー画面が出力されるはずです。
まずpublicディレクトリ配下に403.htmlファイルを作成しておきます。
<html> <head> <title>権限がありません(403)</title> <meta name="viewport" content="width=device-width,initial-scale=1"> </head> <body> <p>権限がありません。</p> </body> </html>
Pundit::NotAuthorizedError例外を403HTTPステータスにします。
config/application.rb config.action_dispatch.rescue_responses["Pundit::NotAuthorizedError"] = :forbidden
development環境でアプリの動作を確認するときは、config.consider_all_requests_local
の値をtrue
から一時的にfalse
に変更します。
config/environments/development.rb
config.consider_all_requests_local = false
true
のままだと開発用のエラーページが表示されています。
false
に変えることで本番環境の画面を表示することができます。
参考 : カスタム例外を小さな exceptions_app でハンドリングする - Qiita
私は最初application_controller.rb内に以下のような権限のないユーザーがアクセスすると403エラーページが表示される実装をしていました。
class ApplicationController < ActionController::Base include Pundit rescue_from Pundit::NotAuthorizedError, with::user_not_authorized private def user_not_authorized flash[:alert] = '許可されていないユーザーです' redirect_to(request.refferrer || root_path) end end
しかしこのように記載していると開発用のエラーページが表示されないのでこの記載は開発には向いていません。
RSpecでステータスコードを確認する
ステータスコード確認のためにシステムスペック内にてdriven_by(:rack_test)
を使用します。この記述がないと、Capybara::NotSupportedByDriverError
が現れます。
before do driven_by(:rack_test) end
以下はエラーページが出力されていることを確認するテストの書き方です。
expect(page).to have_http_status(403)
RailsAdminを使って管理者機能を実装
RailsAdminとは
管理画面をテンプレート的に作成できるgemの一つです。
railsにはたくさんの管理画面を実装できるgemがありますが、中でも簡単でデザインがとてもスタイリッシュだったので、RailsAdminのgemを使ってみることにしました。
参考 : rails管理画面系gem比較してみた - Qiita
RailsAdminの使用方法
まず最初にGemfileにrails_adminを追加してbundle install
を行います。
gem 'rails_admin'
generatorを起動させます。
$ rails g rails_admin:install
? Where do you want to mount rails_admin? Press <enter> for [admin] >
と聞かれるのでenter
を入力します。するとルーティングとrails_admin.rbファイルが追加されます。rails s
を起動して、localhost:3000/admin
を開くと管理画面に遷移します。
Hiroshiというアプリに事前にAlbums、Books、Dramas、Movies、Stagesというテーブルを作成してあります。それぞれいくつかデータが入っている状況です。
ここでデータの追加や編集、削除などの操作をすることができます。
しかし今のままでは、誰でも管理画面にアクセスできてしまうので、セキュリティ的に問題があります。なのでBASIC認証(ユーザー名、パスワード認証)をかけていきます。
次のコードをrails_admin.rbファイルの一番上に追加します。
config/initializers/rails_admin.rb config.authenticate_with do authenticate_or_request_with_http_basic('Site Message') do |username, password| username == 'admin' && password == 'password' end end
username == 'admin' && password == 'password'
の部分は実際に使用するユーザー名とパスワードになります。
再度rails s
でサーバーを起動して、localhost:3000/admin
にアクセスすると、パスワード入力画面になります。
FactoryBotのtransientとコールバック
transientとは
transientは実際に作成するデータと直接関係無い新しいattributeを定義する機能です。そこで定義されたものは実際のmodelにはセットされません。追加データとして利用するのが一般的です。
FactoryBotで使うコールバックとは
下記4つのコールバックがあり、各処理の前後で特定の処理を行うことができます。コールバックを使えば、生成したインスタンスがcreate、buildされた直後や実行前に自由にインスタンスを修正することができます。
after(:build) - インスタンス生成後に呼び出し before(:create) - インスタンス生成&保存前に呼び出し after(:create) - インスタンス生成&保存後に呼び出し after(:stub) - スタブオブジェクト生成後に呼び出し
使用例
BlogモデルのFactoryBot作成時に、ユーザーの名前を追加データとして投入してみたいと思います。※ BlogとUserモデルにはアソシエーションが組まれていることを前提としています。
FactoryBot define do factory :blog do sequence(:title) { |n| "title_#{n}" } sequence(:body) { |n| "body_#{n}" } transient do sequence(:user_name) { |n| "user_name_#{n}" } end after(:build) do |blog, evaluator| blog.user = build(:user, name: evaluator.user_name) end end end
上記で定義したFactoryBotをテストで呼び出してみます。
let(:blog) { create(:blog) }
このように記載するとblogが呼び出された時に作られるデータは、ブログのタイトル、本文そしてユーザー名です。
after(:build)
を使用しているので、blogが作られた後にユーザーの名前がUserモデルにも作成されます。
CSVを使ってrails db:seedで初期データを登録
CSVとは
CSVとは「カンマで値を区切ったもの」が入っているファイルで、カンマ区切りファイルともいいます。ファイルの拡張子は .csvです。実際のファイルの中身は次のようになっています。
このファイルとrails: db:seedを使って大量の初期データをデータベースに登録する方法を説明していきます。
使用方法
NumbersやExcelなどのテキストエディタを使用して、データを挿入するテーブルを作成します。私はNumbersを使用しています。
ファイルの一番上の行ではカラム名を入れます。 IDは必ず設定しましょう。データはコピーペーストでテーブルに入れることができるのでとても簡単です!
データの入力が終わったらファイルを下記の手順でcsv形式にして保存します。ファイルの保存先はrailsアプリのdbディレクトリ配下に保存します。
次に作成したCSVファイルを使ってseeds.rbファイルを編集していきます。
seeds.rb require "csv" CSV.foreach('db/dramas.csv', headers: true) do |row| Drama.create( # カラム名: row['CSVファイルの列名'], published_at: row['published_at'], name: row['name'], ) end
CSVファイルを作成しseeds.rbファイルに記述ができたらdデータベースにseedsファイルの情報を入れるためにターミナルで下記のコマンドを入力します。
$ rails db:seed
rails console
を行うことで、データベースに大量のデータが保存されたのがわかります。