とんてき

Ruby, Ruby on Railsやその周辺技術を中心に。ときどき趣味のことも。

CarrierWaveを使用している時のテストデータの準備法(FactoryBot編)

FactoryBotの基本的な使い方と同じですが、文字列と少しだけ異なります。

FactoryBot.define do
  factory :post do
    title "Sample Title"
    description "This is a sample description"
    # CarrierWageで使用しているカラム名をキーにする
    # 画像ファイルは「spec/support」配下に格納
    image { Rack::Test::UploadedFile.new(Rails.root.join('spec/support/sample_image.jpg'), 'image/jpeg') }
  end
end

これで出来上がり!

【CarrierWave + Heroku】でAWS S3に画像を保存しようとすると「HTTP 403」が返却される

HTTP 403とは

HTTP の 403 Forbidden クライアントエラーレスポンスコードは、サーバーがリクエストを理解したものの、認証が拒否されたことを示します。

つまり、リクエスト自体は送られているけど、何らかの理由でアクセスが拒否されたということですね。

対処法

ググったら以下がヒットしたので試してみました。
qiita.com

上記の記事によると、
新しく発表されたパブリックアクセス設定機能によりデフォルトでは管理者しかアクセスできず、IAMユーザーのアクセスがブロックされてしまうということです。

だから少し古いS3のセットアップ方法だけだと、403を返却されてしまうのですね。

解決できてよかったです。
@yukiyukimiyaiwaさん、ありがとうございます!

【Rspec】Shoulda Matchersを使用してspecをシンプルに書く

はじめに

「Shoulda Matchers」というGemを使うと、specをDRYに書けるという話です。

例えば以下のようなnameのpresenceのバリデーションを確認するspecがあるとします。

# nameが未入力の時、無効な状態であること
it "is not valid without a name" do
  user = FactoryBot.build(:user, name: nil)
  user.valid?
  expect(user.errors[:name]).to include("can't be blank")
end

テストする対象が少しの場合は良いのですが、例えばフォームの入力欄がたくさんあり、
それらに対してpresenceのバリデーションをテストしなければならないような場合はやや冗長になります。
しかし、「Shoulda Matchers」というGemを使えば、これがワンライナーで書けてしまいます。

1. 「Shoulda Matchers」のインストール

Gemfileに以下を追記し、bundle install

group :test do
  gem 'shoulda-matchers', '4.0.0.rc1'
  # 省略
end

rails_helper.rbに以下を追記

Shoulda::Matchers.configure do |config|
  config.integrate do |with|
    with.test_framework :rspec
    with.library :rails
  end
end

2. 「Shoulda Matchers」の利用

先ほど冒頭で書いたサンプルは以下のように書き換えることが可能です。

# nameが未入力の時、無効な状態であること
it { is_expected.to validate_presence_of :name }

これだけです。ものすごいシンプルに書けます。
上記はpresenceをバリデートする例ですが、他にも以下のようなメソッドが用意されています。

# アソシエーション
it { is_expected.to have_many :posts }
it { is_expected.to belong_to :user }
# 一意性
it { is_expected.to validate_uniqueness_of :email }
# length
it { should validate_length_of(:password).is_at_least(6).with_message("は6文字以上で入力してください") }

終わりに

他にも色々なメソッドが用意されているので、興味があればその都度調べて見るのが良いと思います。
github.com

【Ruby on Rails】バリデーションメッセージが表示されない時の対処法

問題点

フォームから情報を入力し送信する際に、入力情報に不備があればバリデーションメッセージが表示されるはずが、
なぜか急に表示されなくなったので、その時の対処した内容のメモ。
デバッグやらなんやらで解決するまで丸1日はまってしまいました。とほほ

原因

以前はRailsのヘルパーメソッドである「form_for」を使用していました。
しかし、Rails5.1から「form_with」とやらが登場し、それ以前の「form_tag」、「form_for」は非推奨になったらしい。
ということで早速「form_with」に切り替えたのですが、どうやらここに原因があったようです。

解決法

「form_with」は、デフォルトで「remote: true」すなわちAjax通信が行われるように設定されているようで、
Ajax通信を行わないようにするには、以下のように「local: true」を付与する必要があるようです。

# 省略

<%= form_with model: @post, local: true |f| %>

# 省略

Railsのヘルパーメソッド「simple_format」について調べて見た

はじめに

「simple_format」とは、与えられた文字列を以下の条件で整形するメソッド。(らしい。。。)

・文字列を<p>で括る
・改行は<br />を付与
・連続した改行は、</p><p>を付与

1. simple_formatなし

# 入力値
simple_formatのテストをします。 
まずは改行を1度挟みます。

# 出力値
simple_formatのテストをします。 まずは改行を1度挟みます。

本当は改行したのですが、出力値では改行されていません。

2. simple_formatあり(オプションなし)

simple_format(入力値)

# 入力値
simple_formatのテストをします。 
まずは改行を1度挟みます。

# 出力値
simple_formatのテストをします。 
まずは改行を1度挟みます。
# ここが1文空く

出力値でも改行されていることがわかります。
全体を<p>で括っているため、最後の行にスペースが入っているように見えます。

3. simple_formatあり(オプションなし)

simple_format(入力値)

# 入力値
simple_formatのテストをします。 
<p>htmlタグを記述して見ます。</p>

# 出力値
simple_formatのテストをします。 
htmlタグを記述して見ます。
# ここが1文空く

改行は問題なくされていますが、入力した<p>が出力値に反映されていません。
htmlタグを全て除去したい場合はこれでもいいですが、残したい場合はこれはダメですね。

4. simple_formatあり(オプションあり)

simple_format(h(入力値), {}, wrapper_tag: "div")

# 入力値
simple_formatのテストをします。 
<p>htmlタグを記述して見ます。</p>

# 出力値
simple_formatのテストをします。 
<p>htmlタグを記述して見ます。</p>

改行はされていますし、htmlタグも出力されています。
また、「wrapper_tag」を追加することで<p>ではなく、別の指定したタグで囲むことができます。

まとめ

状況に応じてですが、simple_formatで文字列を囲むだけでと意図しない結果に繋がることもあるので、
文字列をどのように扱いたいか考えてから使うのがいいですね!

画像アップロード機能「Active Storage」を使ってみた!

「Active Storage」とは

Rails5.2から登場したgemで、ActiveRecordモデルへの紐付けや、
クラウドストレージサービス(AWSGCP、Azure)へアップロードが非常に容易に行えるライブラリ。
現在は、画像アップロードには「CarrierWave」というgemが一番よく使われているが、
今後はこの「ActiveStorage」が使われることになると言われている。。。(本当?)

1. ActiveStorageを準備する

gemをインストールするためには以下のコマンドを実行します。

rails active_storage:install

そうすると、以下のようなマイグレーションファイルが作成されます。

xxxxxxxxxxxxxxxxx_create_active_storage_tables.active_storage.rb

このマイグレーションファイルの中身は以下のようになっています。

# This migration comes from active_storage (originally 20170806125915)
class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
  def change
    create_table :active_storage_blobs do |t|
      t.string   :key,        null: false
      t.string   :filename,   null: false
      t.string   :content_type
      t.text     :metadata
      t.bigint   :byte_size,  null: false
      t.string   :checksum,   null: false
      t.datetime :created_at, null: false

      t.index [ :key ], unique: true
    end

    create_table :active_storage_attachments do |t|
      t.string     :name,     null: false
      t.references :record,   null: false, polymorphic: true, index: false
      t.references :blob,     null: false

      t.datetime :created_at, null: false

      t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
    end
  end
end

この中身を見ると、テーブルが2つ作成されていることがわかります。
それぞれどういう役割を果たすかを簡単に説明すると、以下のようになります。
active_storage_blobs ・・・ ActiveStorage::Blobというモデルと紐づいており、ファイルの実態以外の情報(識別キー、ファイル名、Content-type、ファイルのメタデータ、サイズ等)を管理するもの
active_storage_attachments ・・・ ActiveStorage::Attachmentというモデルと紐づいており、ActiveStorage::Blobとその他のモデルを繋げる中間的な役割を果たすもの

作成されたマイグレーションファイルは特にいじらずにマイグレートを実施します。

rails db:migrate

次にファイルの実態をどこで管理するか設定している箇所を見てきます。
config/environments/development.rb

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

「root: <%= Rails.root.join("storage") %>」という記述がありますが、
この部分がファイルの実態の保管場所を意味しています。(root直下のstorageディレクトリ)

2. 画像アップロードを実装する

models/post.rbの中に「 has_one_attached :image」を追記します。
例では、Postモデルに紐付けをしたかったので、post.rbの中に記述しました。
このように「ActiveStorage」をインストールし、テーブルを作成してしまえば、
他のテーブルは修正することがなく、下記のような一文でテーブル間の連携ができてしまいます。(めちゃ楽)
1ポスト1画像を紐付けたかったため、「has_one_attached」としましたが、
もし複数の画像を紐付けたい場合は「has_many_attached」を使えば良いです。

class Post < ApplicationRecord
  has_one_attached :image
end

アップロード機能の実装は普通の画像アップロードと変わりません。

<% f.label :image %>
<%= f.file_field :image, class: 'form-control' %>
end

あとは、アップロードした画像を表示させます。

<% if @post.image.attached? %>
  <%= image_tag @post.image %>
<% end %>
end

「image.attahed?」メソッドを使うことで、画像が添付されていない場合でもエラーを回避することができます。
(image_tagは画像がない場合エラーを表示する。)

おわりに

今回は、ローカル環境だけでの実装でしたが、これがAWS等のクラウドストレージサービスでもとても簡単に実装できてしまいます。
まだまだ新しい機能であり、バリデーションやキャッシュといった機能は現在備わっていないようです。
とても簡単に実装できますが、これらのことも踏まえて導入を検討したいですね!

ではまた!

Railsで論理削除を実装したい時は「paranoia」Gemを使おう!

はじめに

このデータ(レコード)要らないと思って削除したはいいが、あとで戻したいなんてことありますよね?
Railsチュートリアル等を見ると、destroyメソッドでレコードを物理削除したりしていますが、
完全に消すのは怖いという場合、論理削除をすることをおすすめします。
paranoia」というGemを使用するとこれが簡単に実装できてしまいます!
それでは見ていきましょう!

1. Gemをインストール

Gemfileに以下を追記する

gem 'paranoia'

bundle installする

2. 論理削除用のカラムを追加

migrationファイルの作成
※ 以下では、migrationファイルを生成するときにカラム名と型を指定しています。

rails g migration add_column_to_テーブル名 deleted_at:datetime

migrationを行う

rails db:migrate

3. モデルに独自のメソッドを追記

class Post < ApplicationRecord
    acts_as_paranoid
end

終わりに

これでdestroyメソッドで削除しようとすると、先ほど追加した「deleted_at」に日時が入り、物理的にレコードは削除されません。
※ 本当に物理削除したい場合は、「really_destroy」メソッドを使用すると物理削除をすることができます。

めちゃくちゃ簡単でしたね!
本当にRailsのGemは豊富で実装が簡単です!