20210301のRailsに関する記事は25件です。

bootstrap_ドロップダウンが表示されない

rails tutorial 8章をしていたところ、
ドロップダウンを表示させるのに手こずりましたので、メモに残しておきます。

解決前

_header.html.erb
<header class="navbar navbar-fixed-top navbar-inverse">
  <div class="container">
    <%= link_to "sample app", root_path, id: "logo" %>
    <nav>
      <ul class="nav navbar-nav navbar-right">
        <li><%= link_to "Home", root_path %></li>
        <li><%= link_to "Help", help_path %></li>
        <% if logged_in? %>
          <li><%= link_to "Users", '#' %></li>
          <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
              Account <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
              <li><%= link_to "Profile", current_user %></li>
              <li><%= link_to "Settings", '#' %></li>
              <li class="divider"></li>
              <li>
                <%= link_to "Log out", logout_path, method: :delete %>
              </li>
            </ul>
          </li>
        <% else %>
          <li><%= link_to "Log in", login_path %></li>
        <% end %>
      </ul>
    </nav>
  </div>
</header>
application.js
//= require rails-ujs
//= require jquery
//= require bootstrap
//= require turbolinks
//= require_tree .

チュートリアルに記載されているようにしたのにドロップダウンが表示されない。。。
jqueryとbootstrap読み込んでいるのに。

解決後

apprication.html.erb
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>

apprication.html.erbのhead部分に上記を加えるといけた。

ドロップダウンを使うなら、jquery、 popper.js、 bootstrap.min.js が必要ですよってことらしく、これはCDNから読み込んでいるらしい。

apprication.heml.erb でjqueryとbootstrapを読み込んだけど、アセットパイプラインに指示するのではだめだったのか?

難しい。

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

polymorphic: trueとなっているポリモーフィックなモデルを結合する方法

概要

ActiveRecordを用いて内部結合、外部結合をするためのjoins, left_joins, eager_load, includes, preload...などなど、さまざまな種類がありますね。

これらの細かい違いについては、ここでは解説を省略させていただきます。

下記に掲載したQiita記事は、僕も実際に参考にさせていただき、非常にわかりやすいと感じたので共有させていただきます。

では、本題に入りたいと思います。

今回やりたいこと

RailsのActiveRecordでポリモーフィックにしているモデルと別のモデルとを結合させて、両モデルからデータを取得したい

結論

ポリモーフィックなモデル(polymorphic: trueにしている)と別のモデルとを結合させたい時は、どうやらpreloadメソッド一択らしいです。

下記のページには

This error is raised when trying to eager load a polymorphic association using a JOIN. Eager loading olymorphic associations is only possible with ActiveRecord::Relation#preload.

といった記述があります。

簡単に翻訳すると、「ポリモーフィックなモデルのアソシエーションを読み込みは、preloadでのみ可能です」といった感じです。

まとめ

今回はActive Recordのポリモーフィックのモデルの結合について書きました。

ポリモーフィックやらモデル結合らへんの話は、結構よくでてくると思うのでぜひ参考にしてください。

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

Rails Slackへメッセージを送信

前提条件

・ruby 2.6.6
・Rails 6.0.3.2
・macOS Catalina バージョン10.15.7

概要

 webアプリ内で特定のアクションが起きた時にSlackへ通知する機能を実装したいと思い
実装の備忘録としてここに記す。
 思ったよりかなり簡単だったので、ぜひご自身のwebアプリに活用していただけると幸いです。

gem 'slack-notifier'の導入

 slackへ通知を送るにはgemを導入する必要があります。

Gemfile
gem 'slack-notifier'
ターミナル
bundle

これでgemの導入が完了します。

Slack側準備

 まずチャンネルのWebhook URLを取得するために、下記のURLにアクセスします。 

 https://slack.com/services/new/incoming-webhook

 ここでRailsからの通知を受け取るチャンネルを設定します。既存のチャンネル、もしくは新規にチャンネルを作ってもどちらでも可能です。
 チャンネルを入力後、Incoming Webhook インテグレーションの追加をクリック。
その後に表示される、Webhook URLを取得します。

Slackに通知を飛ばす

 飛ばしたいアクションの中に

notifier = Slack::Notifier.new(
  'WEBHOOK_URL', 
  channel: '通知を送りたいチャンネル名',
  username: 'notifier',
)
notifier.ping 'ユーザーが投稿を作成しました!'

と書き込みます。
最後に先ほど取得したWebhook URLを'WEBHOOK_URL'に入力します。
これで実装が完了となります。実際にアクションを起こし確認してみてください。
また、そのままWEBHOOK_URLを書き込むより、gemのdotenv-railsを使用したりして
環境変数として設定するといいと思います。

参考文献

・RailsプロジェクトでSlack通知を実装する
https://tech.mof-mof.co.jp/blog/rails-slack-notifier/

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

【Rails】 バッチ処理でのエラー「sudo: systemctl: command not found」

はじめに

今回は、とあるスクールのカリキュラムを参考に、初めてバッチ処理を実装していた時の話です。
cronを利用して削除プログラムを定時処理として実行させようとしていたところ、エラーが発生してしまいました。
自身の備忘録として、また同じようなエラーに遭遇された方の参考になればと思い投稿させて頂いております。

(参考)
・crontabコマンド
https://it-mikeiken.com/crontab-command

・CentOSのバージョン確認
https://ja.stackoverflow.com/questions/66504/centos%E3%81%AE%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%A2%BA%E8%AA%8D

開発環境

ruby 2.6.3
Rails 5.2.4.4
AWS Cloud9(Amazon Linux)

エラーの内容

推奨されていたコマンドがこちらですが、どちらも同じエラーで弾かれてしまいます。

terminal.
# cronが起動しているか確認するコマンド
$ sudo systemctl status crond
sudo: systemctl: command not found

# cronを起動するコマンド
$ sudo systemctl start crond
sudo: systemctl: command not found


解決策

以下のコマンドを実行することで、cronを起動、そして起動しているかを確認することが出来ました。

terminal.
# cronが起動しているか確認するコマンド(どちらも可)
$ sudo service crond status
            or
$ sudo /etc/rc.d/init.d/crond status

# cronを起動するコマンド(どちらも可)
$ sudo service crond start
            or
$ sudo /etc/rc.d/init.d/crond start


エラーが起きた原因

CentOS6以前とCentOS7以降からでは、コマンドが変わっているという記事を発見しました。
そこで、自身のバージョンを確認してみようと思い、以下のようにコマンドを入力すると、「No such file or directory」、つまり「そのようなファイルやディレクトリはありません」と言われてしまいます。

terminal.
# CentOSのバージョンを確認するコマンド
$ cat /etc/redhat-release
No such file or directory

この時点で、そもそもOSってなんだっけ?と思い始めたわけです。
OSについて検索をしてみると、ある記事に「システム全体を管理し、様々なアプリケーションソフトを動かすための最も基本的なソフトウェア」と定義されていました。
更に調べてみると、私が開発環境で使用しているAWSのCloud9、「Amazon Linux」もCentOS/RHEL系に分類されるLinux OSの一つだということが分かりました。OSの一つではあるが、CentOSとは異なるためファイルがないと言われてしまったということです。
Amazon Linuxのバージョンを調べるコマンドも見つけたので、一応下記に掲載しておきます。

terminal.
# Amazon Linuxのバージョン確認コマンド
$ cat /etc/system-release
Amazon Linux AMI release 2018.03


結論

今回自身の結論としましては、OSの種類が違うとコマンドが変わるということで一度納得をすることにしました。まだまだ知識不足で、これらのコマンドがどう違い、どのOSにどのコマンドが正しいかというところまでは理解することが出来ませんでしたので、今後の課題としたいと思います。
もし詳しい方がいらっしゃいましたら、ご教授いただければ嬉しく思います。

終わり

今回は以上になります。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になればと思っています。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

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

Railsで通知機能を作る(②既読管理編)

やったこと

Railsでアプリを作る課題を行っています。通知機能を作りましょうというタスクがありました。
初めて作る&覚えておくと応用が効きそうなので、以下にやり方をまとめます。

なお、実行環境は以下の通りです。

  • Rails 5.2.3
  • Ruby 2.6.4

仕様

以下のような仕様になっています。

  • userがフォローされた時
  • userの投稿したpostにコメントがついた時
  • userの投稿に「いいね!」がされた時

なお、基本的な機能の実装は、こちらの記事で解説しています。

Railsで通知機能を作る(①基本機能編)

DB設計

DB設計はこちらの通りです。
Image from Gyazo

activitiesテーブルのreadカラムで既読の管理をしていますが、それ以外のDB設計の詳細については、先の「①基本機能編」の記事と、こちらの記事を参照してください。

Routing

今回、既読管理はreadというメソッドをactivityコントローラー下に作って実装します。そのため、ルーティングを下記のように実装します。

config/routes.rb
resources :activities, only: [] do
  patch :read, on: :member
end

only: []なんて書き方は初めて見ましたが、Railsの基本の7アクションがなくて、かつオリジナルのメソッドだけある時は、これで書けるようです。知らなかった。

Model

続いてモデルです。モデルには、enumreadunreadを定義します。

models/activity.rb
enum read: {
  unread: false,
  read: true,
}

enumって、boolean型にも使えるのですね。truefalseにそれぞれ別の名前がつけられて、文脈がわかりやすくなりますね。

また、モデルには以下のようなメソッドも定義しました。

models/activity.rb
def transition_path
  case action_type.to_sym
  when :commented_to_own_post
    post_path(subject.post, anchor: "js-comment-#{subject.id}")
  when :liked_to_own_post
    post_path(subject.post)
  when :followed_me
    user_path(subject.follower)
  end
end

こちらは、この後すぐ利用します^^

Controller

controllerのreadメソッドはこのように実装します。

controllers/activities_controller.rb
class ActivitiesController < ApplicationController
  # beforeフィルターなどは省略

  def read
    activity = current_user.activities.find(params[:id])
    activity.read! if activity.unread?
    redirect_to activity.transition_path
  end
end

readメソッドが実行されたら、readカラムの中身がfalse(unread)の時に、readにして、先に設定したtransition_pathに遷移する仕組みです。

仕上げ&完成

あとは、詳細省きますが、Viewでは既読の時だけ色が変わるように設定して...。

Image from Gyazo

クリックすると色が変わり、ビジュアルに既読管理ができるようになりました。

最終的には、こちらに「未読の件数」表示のバッジをつけて完成しましたが、以上で、既読管理の完成です。

まとめ

初めは、実装についてあまりイメージのついていなかった「通知」機能ですが、

  • 「通知」テーブルを別途作る
  • 通知対象のアクションが行われたら、「通知」レコードを一つ作る
  • 通知を確認するためのリンクを設定し、リンクをクリックしたらステータスを「既読」にする

というのが大まかなポイントなのかなと思いました。
ポリモーフィック関連付けについては、使い所をもう少し抽象的に理解したいです。

それでは、引き続き頑張っていきます。

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

Top画面でヘッダーとフッターしか表示されない rails備忘録

備忘録として、、、

初歩的なことですが、、

application.html
<body>
    <%= render 'layouts/header' %>
      <main>
        <p id="notice"><%= notice %></p>
      </main>
    <%= render 'layouts/footer' %>

これだけだと、トップページが呼び出されていないため
<%= render 'layouts/header' %>
<%= render 'layouts/footer' %>しか反映されない

そこで
<%= yield %>(渡されているブロックと同じ働きをするメソッドのようなものだそうで)
を記述すると

application.html
<body>
    <%= render 'layouts/header' %>
      <main>
        <p id="notice"><%= notice %></p>
     <%= yield %>
      </main>
    <%= render 'layouts/footer' %>

無事反映されました!

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

A server is already running. 対処方法

前提条件

・ruby 2.6.6
・Rails 6.0.3.2
・macOS Catalina バージョン10.15.7

概要

ローカル環境でrails sを走らせようとしたが
"A server is already running"
というエラーが発生した。その対処法を記述する。

1.再起動をする

ターミナルを全て閉じ再起動する。
これで解決する場合もあるみたい。私はやったことはない。

2.tmp/pids/server.pid.を削除する

私はこの方法で毎回解決している。
ターミナルで"A server is already running"の後に
Check /Users/・・・・/tmp/pids/server.pid.
と出ている。
サーバーを落とす際にこのファイルが消されるはずなのだが、何らかの不具合で
消されていない状況が発生するようだ。
Finderからアクセスし、直接"server.pid."を削除
これで解決。

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

【Rspec】Error: raise WrongScopeError の解消【原因:rspec_railsのバージョン】

Rspecを初めて書こうと思い、色んな記事を参考にrspec_rails をインストールしてテストを書きました。いざRspecを走らせてみると、以下の様なエラーが発生しました。

Failure/Error:
       raise WrongScopeError,
             "`#{name}` is not available from within an example (e.g. an " \
             "`it` block) or from constructs that run in the scope of an " \
             "example (e.g. `before`, `let`, etc). It is only available " \
             "on an example group (e.g. a `describe` or `context` block)."

       `name` is not available from within an example (e.g. an `it` block) or from constructs that run in the scope of an example (e.g. `before`, `let`, etc). It is only available on an example group (e.g. a `describe` or `context` block).

どうやらnameがスコープの範囲外にあるのがダメと言っています。しかしnameに全く身に覚えがなかったので途方にくれていました。

色々調べてみると、rspec_railsのバージョンが古いと同様なエラーが発生する様です。

そこでrspec_railsのバージョンを現在の最新バージョン(4.0.2)に変更したところ、解決しました。
同様のエラーに悩んでいる人の助けになれば幸いです。

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

作品を評価し合うwebアプリ作ってみた[個人開発]

今回作ったもの

タイトルにもある通り、絵や音楽、プログラムなどの作品を評価し合うコミュニティサイト「PRORP」です。

4598734.png

なぜ作ったか

ヤフー知恵袋でこんな質問を見つけました。

塾に通ってるなら他人からの評価は簡単に受けれますが、独学の場合なかなかしっかりと評価を受けるときってありません。
以外に他人の評価って欲しがってる人が多いのかなと思い作りました。

工夫した点

投稿一覧ページをなくした
公開したばかりでユーザーも投稿も少ないのでユーザーが投稿が見る方法は検索だけにしました。
使いずらいですが最初はしょうがないんです。すみません(__)

★評価機能
25478.png

Raty.jsというものを使い実装しました。まあまあ簡単です。
平均値も求められます。

開発効率化のために

今回、初めて土台アプリを作ってみました。
というのもwebアプリ開発って結構同じことの繰り返しじゃないですか。CRUD、コメント機能、いいね機能、通知機能など
どんなアプリを作るにしろ必要な機能ってありますよね?
それを毎回毎回作ってたら非効率的だなと思いある程度、完成してる土台アプリを先に作りそれをコピーしてPRORPを作りました。

土台アプリでの開発の感想としてはとにかく早いです
当たり前ですが新しく1から作るよりカスタムするほうが早いので、土台アプリは作ってよかったなーと思ってます。

土台アプリに実装した機能としては

  • CRUD
  • コメント機能
  • いいね機能
  • 通知機能
  • ヘッダー、フッター、2カラムレイアウトなどのCSS
  • 新規登録、ログイン機能
  • ユーザーIDとの紐付け

くらいです。もちろん土台アプリなので汎用性重視です。

使ったgem

(省略)

gem 'ridgepole'
gem 'slim-rails'
gem 'html2slim'
gem 'pry-rails'
gem 'devise'
gem 'kaminari'
gem 'activeadmin'
gem 'rack-attack'
gem 'rails-i18n'
gem 'devise-i18n'
gem 'devise-i18n-views'
gem 'carrierwave'
gem 'fog-aws'
gem 'dotenv-rails'
gem 'rmagick'

まとめ

土台アプリは今度からも使っていこうと思います。
作りたいものがない、という方は土台アプリをまず作ってみるといいかもしれません。

まぁとにかくこのPRORP使ってくれ!(誘導下手)

ツイッターもやってます!
https://twitter.com/yamada1531

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

【超かんたん】Fakerを使ってダミーデータを作成しよう!

Fakerを利用してアプリにダミーデータ生成します。
今回は、deviseで作成したusersテーブルにダミーデータを投入していきます。

Fakerとは

Fakerとはランダムな値を生成してくれるGemです。人名、メールアドレス、パスワードの生成はもちろんのことゲームのタイトルやアニメのキャラクター名なども生成してくれる遊び心がいっぱい詰まったGemです。公式ドキュメント

では、さっそく使って行きましょう!

Fakerの導入

gamのインストール

Gemfile
gem 'faker'
ターミナル
bundle install

導入完了。

ダミーデータの生成

今回は、deviseで生成されるemailとpasswordの他にnameカラムを追加しています。

deviseで生成されるemailカラムには一意性制約があるのでFaker::Internetfree_emailの間にuniqueと記述しています。

passwordもデフォルトでは6文字以上でないと登録できないのでmin_length: 6と記述しています。この辺りはご自身が制作するアプリの仕様によって適宜変更していきましょう。

db/seeds.rb
50.times do
  name = Faker::Name.name
  email = Faker::Internet.unique.free_email
  password = Faker::Internet.password(min_length: 6)

  User.create(
    name: name,
    email: email,
    password: password,
    password_confirmation: password
  )
end
ターミナル
rails db:seed

それでは確認してみましょう。
user.jpg

ダミーデータが生成されています。

日本語化してみよう

Fakerは一部、日本語対応しています。

日本語化はとても簡単です。
config/application.rbにconfig.i18n.default_locale = :jaという記述を追加するだけです。

config/application.rb
module アプリ名
 class Application < Rails::Application
   # Initialize configuration defaults for originally generated Rails version.
   config.load_defaults 6.0
   config.i18n.default_locale = :ja #追加
   # 省略
 end
end
ターミナル
rails db:seed

user2.jpg
日本語化完了。

番外編

ここからは番外編です。
ダミーデータの名前を変更してみましょう。
詳しくは公式ドキュメントを参照しましょう。

まずはja.ymlファイルを作成します。

ターミナル
touch config/locales/ja.yml

こちらの記述を作成したja.ymlにコピペし日本語化しましょう。
※日本語化できるのは一部だけです。

ポケモンの場合

db/seeds.rb
50.times do
  name = Faker::Games::Pokemon.name #変更
  email = Faker::Internet.unique.free_email
  password = Faker::Internet.password(min_length: 6)

  User.create(
    name: name,
    email: email,
    password: password,
    password_confirmation: password
  )
end
ターミナル
rails db:seed

p.jpg

スーパーマリオの場合

db/seeds.rb
50.times do
  name = Faker::Games::SuperMario.character #変更
  email = Faker::Internet.unique.free_email
  password = Faker::Internet.password(min_length: 6)

 #以下略
ターミナル
rails db:seed

ま.jpg

ドラゴンボールの場合

db/seeds.rb
50.times do
  name = Faker::JapaneseMedia::DragonBall.character #変更
  email = Faker::Internet.unique.free_email
  password = Faker::Internet.password(min_length: 6)

 #以下略
ターミナル
rails db:seed

d.jpg
こちらは日本語対応していないみたいです。

以上になります。

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

[Rails] モデルが使用しているデータベースを調べる

方法

ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#current_database を使用する。

DemonSlayer.superclass #=> ApplicationRecord(abstract)
DemonSlayer.connection.current_database #=> 'kimetsu_production'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

basic認証を導入する

basic認証をアプリケーションへ導入する

controllers/application_controller.rb にて以下の記述を行う。

class ApplicationController < ActionController::Base
  before_action :basic_auth


  private

  def basic_auth
    authenticate_or_request_with_http_basic do |username, password|
      username == ENV["BASIC_AUTH_USER"] && password == ENV["BASIC_AUTH_PASSWORD"]  # 環境変数を読み込む記述に変更
    end
  end
end

環境変数を使って、ユーザー名とパスワードの設定を行う。
heroku上で管理を行うため以下の記述でセットする。

% heroku config:set BASIC_AUTH_USER='aaaa'
% heroku config:set BASIC_AUTH_PASSWORD='1111'

正しく設定できているかはheroku configでしっかり確認します。
変更したコードをコミットし、Herokuへデプロイします。

% git add .
% git commit -m "Basic認証を導入"
% git push heroku master

これで本番環境へ反映します。

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

【Ruby on Rails】deviseの導入

Ruby on Railsの便利機能であるdeviseの導入手順をまとめます。

環境

Version
MacOS Catalina 10.15.7
Ruby 2.6.5
Rails 6.0.3.5

Gemfile

Gemfileの一番下に下記を記述します。

Gemfile
gem 'devise'

ターミナルで下記コマンドを実行しgemを読み込みさせます。

ターミナル
bundle install

ターミナルで下記コマンドを実行しdeviseを導入します。

ターミナル
rails g devise:install

下記のようなメッセージが出てそれぞれファイルが生成されます。

create  config/initializers/devise.rb
create  config/locales/devise.en.yml

モデル作成

Userモデルを作成します。
ターミナルで下記コマンドを実行します。

ターミナル
rails g devise user

下記のような実行結果がでます。
メッセージにあるようにapp/modelsuser.rbというモデルファイルが生成されます。

ターミナル
Running via Spring preloader in process 1632
      invoke  active_record
      create    db/migrate/2021×××_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

マイグレーションファイル編集

db/migrateに新しく2021×××_devise_create_users.rbというマイグレーションファイルが生成されているので編集が必要であれば編集します。

例えば、元々はユーザー登録にemailpasswordしか必要ないようになっているので、下記のようにstring型nameを追加したりできます。

db/migrate/2021×××_devise_create_user.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

     # 下記を追加
      t.string :name,               null: false

編集したら下記コマンドでマイグレーションを実行します。

ターミナル
rails db:migrate

ビュー作成

devise用のビューを作成します。
下記コマンドを実行します。

ターミナル
rails g devise:views

下記のような実行結果が出てdeviseに関する様々なファイルが生成されたことが分かります。

ターミナル
Running via Spring preloader in process 1841
      invoke  Devise::Generators::SharedViewsGenerator
      create    app/views/devise/shared
      create    app/views/devise/shared/_error_messages.html.erb
      create    app/views/devise/shared/_links.html.erb
      invoke  form_for
      create    app/views/devise/confirmations
      create    app/views/devise/confirmations/new.html.erb
      create    app/views/devise/passwords
      create    app/views/devise/passwords/edit.html.erb
      create    app/views/devise/passwords/new.html.erb
      create    app/views/devise/registrations
      create    app/views/devise/registrations/edit.html.erb
      create    app/views/devise/registrations/new.html.erb
      create    app/views/devise/sessions
      create    app/views/devise/sessions/new.html.erb
      create    app/views/devise/unlocks
      create    app/views/devise/unlocks/new.html.erb
      invoke  erb
      create    app/views/devise/mailer
      create    app/views/devise/mailer/confirmation_instructions.html.erb
      create    app/views/devise/mailer/email_changed.html.erb
      create    app/views/devise/mailer/password_change.html.erb
      create    app/views/devise/mailer/reset_password_instructions.html.erb
      create    app/views/devise/mailer/unlock_instructions.html.erb

簡易的なビューがこれだけで簡単に生成できてしまいます。

おまけ

バリデーションはモデルに記述します。
下記が例です。

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  # 下記を追加
  validates :name, presence: true
end

カラムにデフォルトのemailpassword以外を追加した場合は、それも保存できるように許可の記述をしないと弾かれてしまいます。
app/controllers/application_controller.rbに以下のように記述します。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  private
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:name])
  end
end

before_action :configure_permitted_parameters, if: :devise_controller?でdeviseに関わる動作については全て起動するように設定します。
そしてその処理としてconfigure_permitted_parametersで今回作成したnameも許可するように記述します。
configure_permitted_parametersという名前は慣習的なものらしいので特に意味はありません。

以上です。

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

いいね機能の実装について

はじめに

今回は、オリジナルアプリに実装した「いいね機能」について
振り返りながら記事を書きたいと思います。

オリジナルアプリでいいね機能は2箇所実装しました。
1.投稿に対するいいね
2.コメントに対するいいね
この2つです。
別テーブルで作成しました。

今回は、コメントに対するいいね機能の記事を書きたいと思います。
なお、ユーザー管理機能、投稿機能、コメント機能は実装済みです。

1.テーブル設計
2.モデル作成(テーブル作成、バリデーション、アソシエーション)
3.ルーティング設定
4.コントローラー設定
5.ビューの設定

バージョン

・Ruby 2.6.5
・Rails 6.0.0

テーブル設計

・likesテーブル

Column Type Options
user references null: false, foreign_key: true
post references null: false, foreign_key: true
comment references null: false, foreign_key: true

Association

  • belongs_to :user
  • belongs_to :post
  • belongs_to :comment

user、post、commentは紐づいているので、references型でオプションでforeign_key: true
をつけています。
commentにいいねしたい場合、投稿も紐づいているためpostカラムも必要になります。
どのユーザーがどの投稿のどのコメントにいいねをしたのかわかるようにしてます。
アソシエーションはユーザー、投稿、コメントは1に対してlikeは多の関係になります。

モデルの作成

テーブル設計ができましたら、次にモデルを作成します。

% rails g model like

モデルを作成したら、マイグレーションファイルに記述します。

2021xxxxxxx_create_likes.rb

class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      t.references :user,    null: false, foreign_key: true
      t.references :post,    null: false, foreign_key: true
      t.references :comment,    null: false, foreign_key: true
      t.timestamps
    end
  end
end

先ほどのテーブルのカラムを記述したら、

% rails db:migrate

マイグレーションを行いテーブルを作成完了です!!

次にモデルにアソシエーションとバリデーションの記述を行います。

models/like.rb

class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
  belongs_to :comment

  validates_uniqueness_of :comment_id, scope: :user_id
end

ここでのバリデーションは、1つのコメントに対して1度しかいいねできないように
組んでいます。

ここにコードは載せませんが、User、Post、Commentのモデルに
has_many:likesの記述を行っています。

ルーティングの設定

routes.rb

Rails.application.routes.draw do
  devise_for :users

  root to: 'posts#index'
  resources :users, only: [:show, :edit, :update, :destroy]
  resources :posts do
    resources :comments, only: :create do
      resources :likes, only: [:create, :destroy]
    end
  end
end

ここで、ルーティングをネストさせます。
まず、postsコントローラー(親)→commentsコントローラー(子)の関係性があります。
そこに、commentsコントローラー(親)→likesコントローラー(子)の関係性でネストさせます。
ネストをさせる理由は、アソシエーション先のレコードのidをparamsに追加してコントローラーに送るためです。
今回だと、いいねに結びつくコメント投稿のidをparamsに追加します。

これで、ターミナルでルーティングを確認する。

% rails routes

Image from Gyazo

コントローラーの設定

まずは、コントローラーを作成します。

% rails g controller likes

次にコントローラーにコードを記述します。

controllers/likes_controller.rb

class LikesController < ApplicationController

  def create
    post = Post.find(params[:post_id])
    comment = Comment.find(params[:comment_id])
    @like = Like.create(user_id: current_user.id, post_id: post.id, comment_id: comment.id)
    redirect_to post_path(comment.post)
  end

  def destroy
    post = Post.find(params[:post_id])
    comment = Comment.find(params[:comment_id])
    Like.find_by(user_id: current_user.id, post_id: post.id, comment_id: comment.id).destroy
    redirect_to post_path(comment.post)
  end
end

ここでは、いいねの保存と削除のアクションを記述しています。

createアクション

post = Post.find(params[:post_id])
comment = Comment.find(params[:comment_id])

いいねされたコメントに紐づく投稿を変数postに格納する。
いいねされたコメントのidとPostテーブルのidが一致するものをfindで見つけて変数postに格納。

@like = Like.create(user_id: current_user.id, post_id: post.id, comment_id: comment.id)

こちらでいいねを保存する処理を記述します。

destroyアクション

def destroy
    post = Post.find(params[:post_id])
    comment = Comment.find(params[:comment_id])
    Like.find_by(user_id: current_user.id, post_id: post.id, comment_id: comment.id).destroy
    redirect_to post_path(comment.post)
  end

find_byメソッドを使用して、それぞれのidが一致したいいねを取り消す処理を記述します。

viewの記述

最後にviewの記述を行います。

views/likes/_like.html.erb

 <%if user_signed_in? %>
    <% if Like.find_by(user_id: current_user.id, post_id: @post.id, comment_id: comment.id) %>
        <%= link_to  post_comment_like_path(@post, comment, comment.likes), {class: "like-link", method: :delete } do %>
          <i class="fas fa-grin-squint-tears unlike-btn"></i>
        <% end %>
        <p class="count"><%= comment.likes.count %></p>
    <% else %>
        <%= link_to post_comment_likes_path(@post, comment), {class: "like-link", method: :post } do %>
          <i class="far fa-grin-squint-tears like-btn"></i>
        <% end %>
        <p class="count"><%= comment.likes.count %></p>
    <% end %>
  <% else %>
      <i class="fas fa-grin-squint-tears unlike-btn"></i><p class="count"><%= comment.likes.count %></p>
  <% end %>

user_signed_in?メソッドを使用しログインしていないユーザーには、カウント数だけ表示
されるようにしています。
そして、if文でfind_byメソッドを使用し、idが一致したものがあれば、削除のボタンを表示し、
一致しない時は、いいねボタンが表示されるようにしています。

以上がいいね機能実装の手順になります。
初心者の私にはかなり難しく感じました。
実装できたものの、理解できていない部分もあります。

もし何か間違いや聞きたいことがありましたら、コメント
していただければ幸いです。

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

【Rails】link_toのxxx_pathが長い時はpolymorphic_pathを使うと綺麗に書けるお話

Railsにはよく使うものとしてlink_tobutton_toがあります。これらはリンクやボタンを生成するヘルパーです。

更に、遷移先のURLやpathを指定することが出来て、以下のように書けます。

<%= link_to '詳細へ', user_path(user.id) %>

では、URLのネストをした場合、xxx_pathはどうなるでしょうか?

例えば、ユーザーは記事を投稿でき、その記事たちをお気に入りに追加できる機能があったとします。

ルーティングはこちら

routes.rb
Rails.application.routes.draw do
  # ユーザーは記事を投稿できる
  resources :users do
    resources :articles do
      # 記事をお気に入りすることができる
      resources :favorites
    end
  end
end

rails routesを叩いてxxx_pathの部分を見てみましょう。

お気に入りの新規保存に使えるであろうfavorites#createはuser_article_favoritesになってます。
これはまだマシですが、もっとネストされたら長くなり、xxx_pathの部分が分かり辛くなるでしょう。

$ rails routes

user_article_favorites     GET    /users/:user_id/articles/:article_id/favorites(.:format)          favorites#index
                           POST   /users/:user_id/articles/:article_id/favorites(.:format)          favorites#create
new_user_article_favorite  GET    /users/:user_id/articles/:article_id/favorites/new(.:format)      favorites#new
edit_user_article_favorite GET    /users/:user_id/articles/:article_id/favorites/:id/edit(.:format) favorites#edit
user_article_favorite      GET    /users/:user_id/articles/:article_id/favorites/:id(.:format)      favorites#show
                           PATCH  /users/:user_id/articles/:article_id/favorites/:id(.:format)      favorites#update
                           PUT    /users/:user_id/articles/:article_id/favorites/:id(.:format)       favorites#update
                           DELETE /users/:user_id/articles/:article_id/favorites/:id(.:format)       favorites#destroy

本題(polymophic_path)を使おう!

Railsにはモデルから生成されたインスタンスに対して、ルーティングをスマートに書ける方法が提供されています。

公式ドキュメントはこちら

結論から言うと、こう書いていたものが、

# user    = User.find(1)
# article = Article.find(1)

<%= button_to 'お気に入りする', user_article_favorites_path(user.id, article.id), method: :post %>

こう書けます

<%= link_to 'お気に入りする', polymorphic_path([user, article, :favorites]), method: :post %>

何をしているかと言うと、polymorphic_pathの引数においたモデルインスタンスを元にリンクを生成してくれています。
今回の場合 users/1/articles/1/favoritesが生成されています。

ポイントは以下の3つです。

  • 基本的に引数にはModelのインスタンスを置く
  • 引数を複数置きたい場合は配列で囲む
  • インスタンスの生成が行われていない場合は、:favoritesのようにシンボル型で引数に置く

今回の例よりさらに複雑になったpathであれば可読性はさらに向上すると思います。
(逆にusers_pathといった短いものにpolymorphic_pathを使うと変に見えるかもしれません。)

polymorphic_pathの応用

先ほどとは別にnewアクションやeditアクションへのリンクを生成したい場合は以下のように書くこともできます。

# user    = User.find(1)
# article = Article.find(1)

<%= link_to '編集へ', edit_polymorphic_path([user, article ]) %>

<%= link_to '新規作成へ', new_polymorphic_path([user, article ]) %>

edit_polymorphic_path([user, article ]) の場合、users/1/articles/1/editのようなリンクが生成されます。

まとめ

RailsにはDRYの法則に則ったヘルパーやメソッドがたくさん用意されています。
今回扱ったpolymorphic_pathは長くなりがちなpathの記述を簡潔に書くことができます。
ぜひお試しください!

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

【Rails】acts_as_listで並べ替え機能を実装

導入

Gemfile
gem 'acts_as_list'
$ bundle install
app/models/task.rb
class Task < ApplicationRecord
  acts_as_list
end
rails g migration AddPositionToTask position:integer
class AddPositionToTask < ActiveRecord::Migration[6.0]
  def change
    add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない
  end
end
$ rails db:migrate
config/routes.rb
resources :tasks do
  member do
    get :move_higher
    get :move_lower
  end
end

同期処理で動かす

/app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position) # positionカラムに従いソート
end

def move_higher
  Task.find(params[:id]).move_higher #move_higherメソッドでpositionを上に
  redirect_to action: :index
end

def move_lower
  Task.find(params[:id]).move_lower #move_lowerメソッドでpositionを下に
  redirect_to action: :index
end
app/views/tasks/index.html.erb
<h2>タスク一覧</h2>
<table>
  <% @tasks.each do |task| %>
    <tr>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', user %></td>
      <td><%= link_to "↑", move_higher_task_path(task) %></td>
      <td><%= link_to "↓", move_lower_task_path(task) %></td>
    </tr>
  <% end %>
</table>

非同期処理で動かす

remote: truejs.erbを使って実装。詳細は割愛。

参考: 【Ruby on Rails】部分テンプレート(js.erb)を用いた非同期通信について(基礎/開発) - Qiita

注意点

positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。

参考

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

【Rails】acts_as_listで並べ替え機能を同期/非同期それぞれで実装

導入

Gemfile
gem 'acts_as_list'
$ bundle install
app/models/task.rb
class Task < ApplicationRecord
  acts_as_list
end
rails g migration AddPositionToTask position:integer
class AddPositionToTask < ActiveRecord::Migration[6.0]
  def change
    add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない
  end
end
$ rails db:migrate
config/routes.rb
resources :tasks do
  member do
    get :move_higher
    get :move_lower
  end
end

同期処理で動かす

app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position) # positionカラムに従いソート
end

def move_higher
  Task.find(params[:id]).move_higher #move_higherメソッドでpositionを上に
  redirect_to action: :index
end

def move_lower
  Task.find(params[:id]).move_lower #move_lowerメソッドでpositionを下に
  redirect_to action: :index
end
app/views/tasks/index.html.erb
<h2>タスク一覧</h2>
<table>
  <% @tasks.each do |task| %>
    <tr>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', user %></td>
      <td><%= link_to "↑", move_higher_task_path(task) %></td>
      <td><%= link_to "↓", move_lower_task_path(task) %></td>
    </tr>
  <% end %>
</table>

非同期処理で動かす

app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position)
end

def move_higher
  @task = Task.find(params[:id])
  @task.move_higher
  @tasks = Task.all.order(:position)
end

def move_lower
  @task = Task.find(params[:id])
  @task.move_lower
  @tasks = Task.all.order(:position)
end
app/views/tasks/index.html.erb
<div id='task-list'>
  <table>
    <% @tasks.each do |task| %>
      <tr class='body-row'>
        <td><%= task.name %></td>
        <td><%= link_to '詳細', task %></td>
        <td><%= link_to '↑', tasks_path(task), remote: true %></td>
        <td><%= link_to '↓', tasks_path(task), remote: true %></td>
      </tr>
    <% end %>
  </table>
</div>
app/views/_tasks.html.erb
<table>
  <% tasks.each do |task| %>
    <tr class='body-row'>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', task %></td>
      <td><%= link_to '↑', tasks_path(task), remote: true %></td>
      <td><%= link_to '↓', tasks_path(task), remote: true %></td>
    </tr>
  <% end %>
</table>
app/views/move_higher.html.erb
$('#task-list').html("<%= j(render 'admin/tasks/tasks', tasks: @tasks) %>");
app/views/move_lower.html.erb
$('#task-list').html("<%= j(render 'admin/tasks/tasks', tasks: @tasks) %>");

scope

taskテーブルのkindカラムの中で並び替えをしたい場合(kindで絞り込みをした状態でもうまく動くようにしたい場合)、以下のように書く。

app/models/task.rb
class Task < ApplicationRecord
  acts_as_list scope: [:kind]
end

注意点

positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。

参考

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

【Rails】acts_as_listを使った並べ替え機能を同期/非同期それぞれで実装

導入

Gemfile
gem 'acts_as_list'
$ bundle install
app/models/task.rb
class Task < ApplicationRecord
  acts_as_list
end
rails g migration AddPositionToTask position:integer
class AddPositionToTask < ActiveRecord::Migration[6.0]
  def change
    add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない
  end
end
$ rails db:migrate
config/routes.rb
resources :tasks do
  member do
    get :move_higher
    get :move_lower
  end
end

同期処理で動かす

app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position) # positionカラムに従いソート
end

def move_higher
  Task.find(params[:id]).move_higher #move_higherメソッドでpositionを上に
  redirect_to action: :index
end

def move_lower
  Task.find(params[:id]).move_lower #move_lowerメソッドでpositionを下に
  redirect_to action: :index
end
app/views/tasks/index.html.erb
<h2>タスク一覧</h2>
<table>
  <% @tasks.each do |task| %>
    <tr>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', user %></td>
      <td><%= link_to "↑", move_higher_task_path(task) %></td>
      <td><%= link_to "↓", move_lower_task_path(task) %></td>
    </tr>
  <% end %>
</table>

非同期処理で動かす

app/contollers/user_controller.rb
def index
  @tasks = Task.all.order(:position)
end

def move_higher
  @task = Task.find(params[:id])
  @task.move_higher
  @tasks = Task.all.order(:position)
end

def move_lower
  @task = Task.find(params[:id])
  @task.move_lower
  @tasks = Task.all.order(:position)
end
app/views/tasks/index.html.erb
<div id='task-list'>
  <table>
    <% @tasks.each do |task| %>
      <tr class='body-row'>
        <td><%= task.name %></td>
        <td><%= link_to '詳細', task %></td>
        <td><%= link_to '↑', tasks_path(task), remote: true %></td>
        <td><%= link_to '↓', tasks_path(task), remote: true %></td>
      </tr>
    <% end %>
  </table>
</div>
app/views/_tasks.html.erb
<table>
  <% tasks.each do |task| %>
    <tr class='body-row'>
      <td><%= task.name %></td>
      <td><%= link_to '詳細', task %></td>
      <td><%= link_to '↑', tasks_path(task), remote: true %></td>
      <td><%= link_to '↓', tasks_path(task), remote: true %></td>
    </tr>
  <% end %>
</table>
app/views/move_higher.html.erb
$('#task-list').html("<%= j(render 'admin/tasks/tasks', tasks: @tasks) %>");
app/views/move_lower.html.erb
$('#task-list').html("<%= j(render 'admin/tasks/tasks', tasks: @tasks) %>");

scope

taskテーブルのkindカラムの中で並び替えをしたい場合(kindで絞り込みをした状態でもうまく動くようにしたい場合)、以下のように書く。

app/models/task.rb
class Task < ApplicationRecord
  acts_as_list scope: [:kind]
end

注意点

positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。

参考

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

Custom Validator

最初に

みなさん、Custom Validator使ってますか?

rails guideここにも記載されていますがActiveModel::ValidatorActiveModel::EachValidatorを使って自作のvalidatorを作成することができます。

異なるmodelで同じようなvalidationを実行する場合は以下のようなメリットがあるので是非使っていきましょう。
・modelのコードを減らせる
・specの行数を減らせる
(Validator Classへspecを書けば良いので同じようなspecを減らせます)

実装例

よくあるパターンだと思うのですが異なるmodelでメールアドレスカラムを持っており、同じようなvalidationを実装する場合の実装例を記載します。

  • 以下のようなEmailのValidator Classを作成します。
app/validators/email_validator.rb
# frozen_string_literal: true

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)
      record.errors.add(attribute, options[:message] || "の形式が不正です")
    end
  end
end
  • deviseを使っていてdeviseと同じEmailのvalidationを使いたい場合は以下のような書き方もできます。
unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)

unless value.match(Devise.email_regexp)
  • modelでの使い方は以下になります。
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end
  • エラーメッセージは以下のような感じになると思います。
メールアドレスの形式が不正です
  • エラーメッセージを変えたい場合もあると思います。
  • その場合は以下のようにメッセージを個別で設定することができます。
class User < ApplicationRecord
    validates :email, allow_nil: true, email: { message: "正しいメールアドレスの形式で入力してください" }
end
  • 次にspecの書き方のサンプルを記載します。
# frozen_string_literal: true

require "rails_helper"

describe EmailValidator do
  let(:model_class) do
    Struct.new(:mail_address) do
      include ActiveModel::Validations

      def self.name
        "DummyModel"
      end

      validates :mail_address, allow_nil: true, email: true
    end
  end

  describe "#validate" do
    subject { model_class.new(email) }

    describe "登録可能な形式" do
      context "nil は登録できる" do
        let(:email) { nil }
        it { is_expected.to be_valid }
      end

      context "「abc@example.com」は登録できる" do
        let(:email) { "abc@example.com" }
        it { is_expected.to be_valid }
      end
    end

    describe "登録不可能な形式" do
      context "「abc」は登録できない" do
        let(:email) { "abc" }
        it { is_expected.not_to be_valid }
      end

      context "「abcd.@example.com」は登録できない" do
        let(:email) { "abcd.@example.com" }
        it { is_expected.not_to be_valid }
      end
    end
  end
end

最後に

email以外にも画像やURLのvalidationなど使えるところは色々あるのでお試し下さい!

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

RailsでCustom Validatorの実装例

最初に

みなさん、Custom Validator使ってますか?

rails guideここにも記載されていますがActiveModel::ValidatorActiveModel::EachValidatorを使って自作のvalidatorを作成することができます。

異なるmodelで同じようなvalidationを実行する場合は以下のようなメリットがあるので是非使っていきましょう。
・modelのコードを減らせる
・specの行数を減らせる
(Validator Classへspecを書けば良いので同じようなspecを減らせます)

実装例

よくあるパターンだと思うのですが異なるmodelでメールアドレスカラムを持っており、同じようなvalidationを実装する場合の実装例を記載します。

  • 以下のようなEmailのValidator Classを作成します。
app/validators/email_validator.rb
# frozen_string_literal: true

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)
      record.errors.add(attribute, options[:message] || "の形式が不正です")
    end
  end
end
  • deviseを使っていてdeviseと同じEmailのvalidationを使いたい場合は以下のような書き方もできます。
unless value.match(/\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i)

unless value.match(Devise.email_regexp)
  • modelでの使い方は以下になります。
class Person < ApplicationRecord
  validates :email, presence: true, email: true
end
  • エラーメッセージは以下のような感じになると思います。
メールアドレスの形式が不正です
  • エラーメッセージを変えたい場合もあると思います。
  • その場合は以下のようにメッセージを個別で設定することができます。
class User < ApplicationRecord
    validates :email, allow_nil: true, email: { message: "正しいメールアドレスの形式で入力してください" }
end
  • 次にspecの書き方のサンプルを記載します。
# frozen_string_literal: true

require "rails_helper"

describe EmailValidator do
  let(:model_class) do
    Struct.new(:mail_address) do
      include ActiveModel::Validations

      def self.name
        "DummyModel"
      end

      validates :mail_address, allow_nil: true, email: true
    end
  end

  describe "#validate" do
    subject { model_class.new(email) }

    describe "登録可能な形式" do
      context "nil は登録できる" do
        let(:email) { nil }
        it { is_expected.to be_valid }
      end

      context "「abc@example.com」は登録できる" do
        let(:email) { "abc@example.com" }
        it { is_expected.to be_valid }
      end
    end

    describe "登録不可能な形式" do
      context "「abc」は登録できない" do
        let(:email) { "abc" }
        it { is_expected.not_to be_valid }
      end

      context "「abcd.@example.com」は登録できない" do
        let(:email) { "abcd.@example.com" }
        it { is_expected.not_to be_valid }
      end
    end
  end
end

最後に

email以外にも画像やURLのvalidationなど使えるところは色々あるのでお試し下さい!

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

Railsでお気に入り機能を実装する

自己紹介

9月から独学でプログラミング学習を開始し、
11月からスクールを使って学習をしています。
現在はポートフォリオの作成し転職活動中です。
知識を定着させるために、学びをアウトプットしています。
また、これから学び始める方の参考になることを願っています。


開発環境

  • Ruby 3.0
  • Ruby on Rails 6.0.3.4

お気に入り機能を実装する

今回はsocializationというgemを使用します。

▼詳しくは公式のGithubはご覧ください。
https://github.com/cmer/socialization

現在、Userテーブルと映画情報を保存するMovieテーブルがあるとします。
まずは、Gemfileに下記を追加します。

gem 'socialization'

追加したら、bundle installを実行し、gemを追加してください。

次に、rails generate socialization -sを実行します。
実行すると、like.rb follow.rb mention.rbのように
モデルが3つとマイグレーションファイルが3つ作成されます。

- class CreateFollows < ActiveRecord::Migration
+ class CreateFollows < ActiveRecord::Migration[6.0]

上記のようにそれぞれのマイグレーションファイルにバージョンを追加します。
そしてrails db:migrateを実行してください。

1.モデルを編集する

それぞれのモデルに下記を追加します。

class Movie < ApplicationRecord

  act_as_likeable

end
class User < ApplicationRecord

  act_as_liker

end

他にお気に入り機能を追加したいテーブルがある場合は、
同様に、act_as_likeableを追加して下さい。


2.コントローラーを編集する

/controllers/movies_controller.rbを編集します。

class MoviesController < ApplicationController

def favorite
  @movie = Movie.find(params[:id])
  current_user.toggle_like!(@movie)
end

上記のようにfavoriteを追加しています。

current_userを使用するにはdeviseというgemが必要になります。
今回、deviseについては、省略致します。

socializationを導入したことでuser.toggle_like!(movie)というメソッドが使えるようになりました。
これは、

  • お気に入りされていなければ、お気に入り登録をする
  • お気に入りされていれば、お気に入りを解除する

このようなメソッドです。
その他のメソッドについては、公式Githubを参照下さい。

3.ルーティングを編集する

favoriteを追加したので/config/routes.rbを編集しましょう。

Rails.application.routes.draw do

  resources :movies do
    member do
      post 'favorite'
    end
  end

end

メンバールーティングについてはRailsガイドを参照下さい。
https://railsguides.jp/routing.html

4.ビューを編集する

<%= link_to favorite_movie_path do %>
  <% if current_user.likes?(movie) %>
    お気に入り解除
  <% else %>
    お気に入り登録
  <% end %>
<% end %>

先程、指定したルーティングをlink_toで使用しています。
current_user.likes?(movie)
これは、ユーザーがお気に入り登録しているとtrueを返します。


シンプルですが、基本的な機能の実装は以上です。
しかし、Ajax(非同期処理)でお気に入り登録をしたり、
お気に入りの総数を表示したいと思うかもしれません。
この辺りの実装は補足として別記事で行おうと考えています。
何か、至らない点があれば、ご指摘下さい。

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

Rails PK FK ってなんじゃ?

テーブル定義でいきなりPK・FKが。。

今回、課題として「このようなテーブルを作成してください」ということで出てきたPK・FK.

結論

PK = primary_key
FK = foreign_key

のことでした。
ただ、このFKは過去にいわゆる外部キーとして出会ったことがあったものの、PKって何?となり申した。

結論、主キーのことでした。
これは、テーブルを作成した時必ず出てくるidがまさにそれでした。
ただ、これを変える方法もあるんだとか。。

参考資料
https://www.kaqiita.com/entry/2019/02/17/104923

今回の課題ではそのままidのことでしたのでそのままやっていきます。

しかし、なんでも略されるとわけわからなくなる時ありますよね。
・・・まじ最初サッカーやんって⚽️。

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

[rails6] fullcalendar 攻略ガイド(予定追加、表示編)

はじめに

この記事はjqueryの外部ライブラリである「fullcalendar」をrailsで使いこなそうという記事です。

前回の投稿をまだご覧になっていない方は下記の記事を一度ご覧になってからこちらの記事を読まれることをお勧めします。

過去の投稿

・ [rails6] fullcalendar 攻略ガイド(導入、表示編)

今回の内容の概要

今回は前回表示させたカレンダーの上に実際の予定を追加し、表示を実装してみたいと思います。

jqueryでajaxを使用するのでajaxの知識が必要になります。

前提知識

ajax,jbuilderの知識

参考

Railsでajaxを実装する。基本の構造から、controller・jbuilderの書き方など。[Rails]

予定の追加

実装の流れ

まず最初の流れを説明するので、迷ったらここをみてもらえれば今どこにいるかが分かってもらえるかと思います。

  1. カレンダーの予定を追加したい日付の余白をクリックし、予定追加フォームが記載されたmodalを表示
  2. modal内で追加操作、ボタンで確定
  3. 追加成功modalを表示
  4. カレンダーに予定が反映される

この流れでやっていきます。

事前準備

model設計

今回はEventという名前のモデルを作成します
カルムは予定の詳細(title)、開始時間(start)、終了時間(end)としました。

console
 rails g model Event title:string start:datetime end:datetime

validate

models/event.rb
class Event < ApplictionRecord

   default_scope -> { order(start_time: :asc) }

   validate  :start_end_check

   #時間の矛盾を防ぐ
   def start_end_check
     if self.start_time.present? && self.end_time.present?
       errors.add(:end, "が開始時刻を上回っています。正しく記入してください。") if self.start > self.end 
     end
   end
end


1. カレンダーの予定を追加したい日付の余白をクリック、予定追加フォームが記載されたmodalを表示

controller

controllerはresources :eventsとしてnewでフォームを出していきます。

events_controller.rb
def new
    @event = events.new
    render plain: render_to_string(partial: 'form_new', layout: false, locals: { event: @event })
    #views/eventsディレクトリのなかに_form_new.html.erb というファイルを作り
    #そのファイルの中のhtmlコードを文字として返してくれます。
    #これはjsの時のコーディングで紐づいてくるのでいったん先に進みます。
end

view

次はmodal用のhtml,登録フォーム用のhtmlを追加します。

views/events/index.html.erb
<div id="calendar"></div>

<%# ここから追加 %>
<div id="modal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">

        </div>
      </div>
    </div>
  </div>
</div>  

<div id="response-modal" tabindex="0" role="dialog">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="error" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="response-modal-body">
        </div>
      </div>
    </div>
</div>

次は新規追加フォームのレイアウトです

views/events/_form_new.html.erb
<div class="row">
      <%= form_with(model: @event, url: events_path, remote: true) do |f| %>
        <%= render 'shared/error_messages', object: f.object %>

        <%= f.label :plan, "内容"%>
        <%= f.text_field :plan, class:'form-control mb-3', required: true %> 
        <%# form_with で required: trueを追加すると空白登録を回避してくれます %> 

        <div class="form-inline text-left">
         <%= f.label :start,"開始時刻", {class:'control-label'}%>
         <%= f.datetime_select :start, {default: Date.today + 0.hours + 00.minutes, minute_step: 60}, class: 'form-control' %>
        </div>
         <br>
        <div class="form-inline text-left"> 
         <%= f.label :end,"終了時刻", {class:'control-label'}%>
         <%= f.datetime_select :end, {default: Date.today + 0.hours + 00.minutes, minute_step: 60}, class: 'form-control' %>
        </div>
         <%# 自動入力をするためにはdatetime_selectにしておく必要があります %>

        <%= f.submit "登録",   class: "btn btn-primary js-event-create-btn" %>
      <% end %>
</div>      

calendarのjs

では最後にjs側で日付クリックでajaxする処理をしていきます

app/javascript/packs/calendar/event.js
document.addEventListener('turbolinks:load', function() {
    var calendarEl = document.getElementById('calendar');

    var calendar = new Calendar(calendarEl, {
        plugins: [ monthGridPlugin, interactionPlugin, googleCalendarApi ],
        //~省略~//

        //---ここから---//
        //日付をクリックした時に発生させるイベント
        dateClick: function(info){
            //クリックした日付の情報を取得
            const year  = info.date.getFullYear();
            const month = (info.date.getMonth() + 1);
            const day   = info.date.getDate();

            //ajaxでevents/newを着火させ、htmlを受け取ります
            $.ajax({
                type: 'GET',
                url:  '/events/new',
            }).done(function (res) {
                // 成功処理
                // 受け取ったhtmlをさっき追加したmodalのbodyの中に挿入します
                $('.modal-body').html(res);

                //フォームの年、月、日を自動入力
                $('#event_start_1i').val(year);
                $('#event_start_2i').val(month);
                $('#event_start_3i').val(day);

                $('#event_end_1i').val(year);
                $('#event_end_2i').val(month);
                $('#event_end_3i').val(day);

                //ここのidはevents/newのurlにアクセスするとhtmlがコードとして表示されるので、
                //開始時間と終了時間のフォームを表しているところのidを確認してもらうことが確実です

                $('#modal').fadeIn();

            }).fail(function (result) {
                // 失敗処理
                alert("failed");
            });
        },

    });
});


2. modal内で追加操作、ボタンで確定

予定の追加はcreateで登録していきます

events_controller.rb
def new
#省略
end

def create
    @event = events.new(params_event)
    if @event.save
      respond_to do |format|
        format.html { redirect_to root_path } 
        format.js  #create.js.erbを探してその中の処理を実行する
      end
    else
      respond_to do |format|
        format.js {render partial: "error" }
        #登録にエラーが起きたときはerror.js.erbを実行する
      end
    end
end

def params_event
    params.require(:event).permit(:title, :start, :finish)
end

3. 追加成功、失敗modalを表示

コメントにも記入したようにajaxでcreateを処理した後は

成功時にcreate.js.erb(新規作成)、失敗時にerror.js.erb(新規作成)に処理が移るのでそこに成功、失敗を表すmodalを表示するようにします

views/events/create.js
$('#modal').fadeOut();
$('.response-modal-body').html('<h1>登録が完了しました!</h1>');
$('#response-modal').fadeIn();
<%# 一度フォームのmodalを閉じて、成功の文面をもう一つのmodalの中に入れてそのmodalを表示する %>
views/events/error.js
$('.response-modal-body').html('<h1>時間設定が間違っています!</h1>');
$('#response-modal').fadeIn();
<%# 登録フォームを残したまま、エラーの内容(今回の場合時間設定しかあり得ない仕様なので)を入れる %>

4. カレンダーに予定が反映される

ここまでできたら追加処理は完了です!
でも、、予定が表示されてねぇ!となっていると思います。

実はまだrails側とjquery側でデータのやりとりをする処理をしていないからです。
それを次でやって今度こそ完成です。

予定の表示

rubyとjavascript間でデータを交換したりする際に立ちはだかるのが言語の壁です。
その言語の壁を取り払ってくれるのが.jsonというファイル形式です。

今回はこいつを使ってrails(ruby)側のデータをjquery(javascript)に送りたいと思います。

controllerでデータの選定

まずはindexのアクション内で表示したい予定を選びましょう。
今回は無難に全てのデータを表示します。

events_controller.rb
def index
   @events = Event.all 
   #変数名は絶対に「@events」にすること!!
end

jsに追加

fullcalendarの内容を記載しているjsファイル内にデータの受信をする設定を記載します。

app/javascript/packs/calendar/event.js
document.addEventListener('turbolinks:load', function() {
    var calendarEl = document.getElementById('calendar');

    var calendar = new Calendar(calendarEl, {
        plugins: [ monthGridPlugin, interactionPlugin, googleCalendarApi ],
        //~省略~//

        events: '/events.json', // <=これを追加
        // 書き方のルールとしては['/コントローラー名.json']としてください

    });

    calendar.render();

    //この下からも追加
    //成功、失敗modalを閉じたときに予定を再更新してくれます
    //これがないと追加しても自動更新されません
    $(".error").click(function(){
        calendar.refetchEvents();
    });
});

jbuilder

次はrails側がデータを送信するファイルを追加します。
view/events/のなかにindex.json.jbuilderというファイルを新規作成します。

※問題ないとは思いますが、gemのjbuilderが入っていることを一応確認しましょう。
ちなみにjbuilderは.rb.jsonに変換してくれるものです。

では、その中に渡すデータの詳細を記載します

view/events/index.json.jbuilder
   #さっきcontrollerに追加した@eventsから情報を配列化する
   json.array!(@events) do |event|
    json.id event.id
    json.title event.title
    json.start event.start  
    json.end event.end 
   end

  #json.〇〇は送るデータの型
  #event.〇〇はそれに対応するモデルのカルム

解説:json.〇〇側のid,title,start,endというのはfullcalendarで定められたeventsが持っているパラメータになります。それに該当する情報を当てはめていくといった形になります。

上記のjbuilderは必要最低限の情報だけを記載しました。
eventsが持つパラメータの詳細は下記のドキュメントをみていただければもっとたくさんの情報を送ることも可能です。

ということでこれでリロードしてjavascriptをコンパイルすれば
今度こそデータが反映されているはずです!!
お疲れ様でした!

次回!

次回は表示されているeventをクリックしたときに起こるアクションの解説をしたいと思います。
乞うご期待!

参照

公式ドキュメント(Event Object)
Railsでfullcalendarを使ってみる(Ajax通信でイベント登録)

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

[rails6] fullcalendar 攻略ガイド(予定追加、予定表示編)

はじめに

この記事はjqueryの外部ライブラリである「fullcalendar」をrailsで使いこなそうという記事です。

前回の投稿をまだご覧になっていない方は下記の記事を一度ご覧になってからこちらの記事を読まれることをお勧めします。

過去の投稿

・ [rails6] fullcalendar 攻略ガイド(導入、表示編)

今回の内容の概要

今回は前回表示させたカレンダーの上に実際の予定を追加し、表示を実装してみたいと思います。

jqueryでajaxを使用するのでajaxの知識が必要になります。

前提知識

ajax,jbuilderの知識

参考

Railsでajaxを実装する。基本の構造から、controller・jbuilderの書き方など。[Rails]

予定の追加

実装の流れ

まず最初の流れを説明するので、迷ったらここをみてもらえれば今どこにいるかが分かってもらえるかと思います。

  1. カレンダーの予定を追加したい日付の余白をクリックし、予定追加フォームが記載されたmodalを表示
  2. modal内で追加操作、ボタンで確定
  3. 追加成功modalを表示
  4. カレンダーに予定が反映される

この流れでやっていきます。

事前準備

model設計

今回はEventという名前のモデルを作成します
カルムは予定の詳細(title)、開始時間(start)、終了時間(end)としました。

console
 rails g model Event title:string start:datetime end:datetime

validate

models/event.rb
class Event < ApplictionRecord

   default_scope -> { order(start_time: :asc) }

   validate  :start_end_check

   #時間の矛盾を防ぐ
   def start_end_check
     if self.start_time.present? && self.end_time.present?
       errors.add(:end, "が開始時刻を上回っています。正しく記入してください。") if self.start > self.end 
     end
   end
end


1. カレンダーの予定を追加したい日付の余白をクリック、予定追加フォームが記載されたmodalを表示

controller

controllerはresources :eventsとしてnewでフォームを出していきます。

events_controller.rb
def new
    @event = events.new
    render plain: render_to_string(partial: 'form_new', layout: false, locals: { event: @event })
    #views/eventsディレクトリのなかに_form_new.html.erb というファイルを作り
    #そのファイルの中のhtmlコードを文字として返してくれます。
    #これはjsの時のコーディングで紐づいてくるのでいったん先に進みます。
end

view

次はmodal用のhtml,登録フォーム用のhtmlを追加します。

views/events/index.html.erb
<div id="calendar"></div>

<%# ここから追加 %>
<div id="modal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="modal-body">

        </div>
      </div>
    </div>
  </div>
</div>  

<div id="response-modal" tabindex="0" role="dialog">
    <div class="modal-dialog" role="document">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="error" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div class="response-modal-body">
        </div>
      </div>
    </div>
</div>

次は新規追加フォームのレイアウトです

views/events/_form_new.html.erb
<div class="row">
      <%= form_with(model: @event, url: events_path, remote: true) do |f| %>
        <%= render 'shared/error_messages', object: f.object %>

        <%= f.label :plan, "内容"%>
        <%= f.text_field :plan, class:'form-control mb-3', required: true %> 
        <%# form_with で required: trueを追加すると空白登録を回避してくれます %> 

        <div class="form-inline text-left">
         <%= f.label :start,"開始時刻", {class:'control-label'}%>
         <%= f.datetime_select :start, {default: Date.today + 0.hours + 00.minutes, minute_step: 60}, class: 'form-control' %>
        </div>
         <br>
        <div class="form-inline text-left"> 
         <%= f.label :end,"終了時刻", {class:'control-label'}%>
         <%= f.datetime_select :end, {default: Date.today + 0.hours + 00.minutes, minute_step: 60}, class: 'form-control' %>
        </div>
         <%# 自動入力をするためにはdatetime_selectにしておく必要があります %>

        <%= f.submit "登録",   class: "btn btn-primary js-event-create-btn" %>
      <% end %>
</div>      

calendarのjs

では最後にjs側で日付クリックでajaxする処理をしていきます

app/javascript/packs/calendar/event.js
document.addEventListener('turbolinks:load', function() {
    var calendarEl = document.getElementById('calendar');

    var calendar = new Calendar(calendarEl, {
        plugins: [ monthGridPlugin, interactionPlugin, googleCalendarApi ],
        //~省略~//

        //---ここから---//
        //日付をクリックした時に発生させるイベント
        dateClick: function(info){
            //クリックした日付の情報を取得
            const year  = info.date.getFullYear();
            const month = (info.date.getMonth() + 1);
            const day   = info.date.getDate();

            //ajaxでevents/newを着火させ、htmlを受け取ります
            $.ajax({
                type: 'GET',
                url:  '/events/new',
            }).done(function (res) {
                // 成功処理
                // 受け取ったhtmlをさっき追加したmodalのbodyの中に挿入します
                $('.modal-body').html(res);

                //フォームの年、月、日を自動入力
                $('#event_start_1i').val(year);
                $('#event_start_2i').val(month);
                $('#event_start_3i').val(day);

                $('#event_end_1i').val(year);
                $('#event_end_2i').val(month);
                $('#event_end_3i').val(day);

                //ここのidはevents/newのurlにアクセスするとhtmlがコードとして表示されるので、
                //開始時間と終了時間のフォームを表しているところのidを確認してもらうことが確実です

                $('#modal').fadeIn();

            }).fail(function (result) {
                // 失敗処理
                alert("failed");
            });
        },

    });
});


2. modal内で追加操作、ボタンで確定

予定の追加はcreateで登録していきます

events_controller.rb
def new
#省略
end

def create
    @event = events.new(params_event)
    if @event.save
      respond_to do |format|
        format.html { redirect_to root_path } 
        format.js  #create.js.erbを探してその中の処理を実行する
      end
    else
      respond_to do |format|
        format.js {render partial: "error" }
        #登録にエラーが起きたときはerror.js.erbを実行する
      end
    end
end

def params_event
    params.require(:event).permit(:title, :start, :finish)
end

3. 追加成功、失敗modalを表示

コメントにも記入したようにajaxでcreateを処理した後は

成功時にcreate.js.erb(新規作成)、失敗時にerror.js.erb(新規作成)に処理が移るのでそこに成功、失敗を表すmodalを表示するようにします

views/events/create.js
$('#modal').fadeOut();
$('.response-modal-body').html('<h1>登録が完了しました!</h1>');
$('#response-modal').fadeIn();
<%# 一度フォームのmodalを閉じて、成功の文面をもう一つのmodalの中に入れてそのmodalを表示する %>
views/events/error.js
$('.response-modal-body').html('<h1>時間設定が間違っています!</h1>');
$('#response-modal').fadeIn();
<%# 登録フォームを残したまま、エラーの内容(今回の場合時間設定しかあり得ない仕様なので)を入れる %>

4. カレンダーに予定が反映される

ここまでできたら追加処理は完了です!
でも、、予定が表示されてねぇ!となっていると思います。

実はまだrails側とjquery側でデータのやりとりをする処理をしていないからです。
それを次でやって今度こそ完成です。

予定の表示

rubyとjavascript間でデータを交換したりする際に立ちはだかるのが言語の壁です。
その言語の壁を取り払ってくれるのが.jsonというファイル形式です。

今回はこいつを使ってrails(ruby)側のデータをjquery(javascript)に送りたいと思います。

controllerでデータの選定

まずはindexのアクション内で表示したい予定を選びましょう。
今回は無難に全てのデータを表示します。

events_controller.rb
def index
   @events = Event.all 
   #変数名は絶対に「@events」にすること!!
end

jsに追加

fullcalendarの内容を記載しているjsファイル内にデータの受信をする設定を記載します。

app/javascript/packs/calendar/event.js
document.addEventListener('turbolinks:load', function() {
    var calendarEl = document.getElementById('calendar');

    var calendar = new Calendar(calendarEl, {
        plugins: [ monthGridPlugin, interactionPlugin, googleCalendarApi ],
        //~省略~//

        events: '/events.json', // <=これを追加
        // 書き方のルールとしては['/コントローラー名.json']としてください

    });

    calendar.render();

    //この下からも追加
    //成功、失敗modalを閉じたときに予定を再更新してくれます
    //これがないと追加しても自動更新されません
    $(".error").click(function(){
        calendar.refetchEvents();
    });
});

jbuilder

次はrails側がデータを送信するファイルを追加します。
view/events/のなかにindex.json.jbuilderというファイルを新規作成します。

※問題ないとは思いますが、gemのjbuilderが入っていることを一応確認しましょう。
ちなみにjbuilderは.rb.jsonに変換してくれるものです。

では、その中に渡すデータの詳細を記載します

view/events/index.json.jbuilder
   #さっきcontrollerに追加した@eventsから情報を配列化する
   json.array!(@events) do |event|
    json.id event.id
    json.title event.title
    json.start event.start  
    json.end event.end 
   end

  #json.〇〇は送るデータの型
  #event.〇〇はそれに対応するモデルのカルム

解説:json.〇〇側のid,title,start,endというのはfullcalendarで定められたeventsが持っているパラメータになります。それに該当する情報を当てはめていくといった形になります。

上記のjbuilderは必要最低限の情報だけを記載しました。
eventsが持つパラメータの詳細は下記のドキュメントをみていただければもっとたくさんの情報を送ることも可能です。

ということでこれでリロードしてjavascriptをコンパイルすれば
今度こそデータが反映されているはずです!!
お疲れ様でした!

次回!

次回は表示されているeventをクリックしたときに起こるアクションの解説をしたいと思います。
乞うご期待!

参照

公式ドキュメント(Event Object)
Railsでfullcalendarを使ってみる(Ajax通信でイベント登録)

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

DockerでRails開発時に,画像の保存先をAWS S3にする

先日,Rails, Dockerでの開発時に画像の保存先をローカルからS3に変更した際の手順を記録した.

開発環境

WSL2 (ubuntu 18.04 LTS)
Docker
- Ruby (2.7.1)
- Rails (6.0.3)

画像をS3に保存する手順

Active Storageがインストールされており,S3バケット作成まで完了していることを想定

Active Storageのインストールは以下のコマンドでできる

terminal
docker-compose run コンテナ名 rails active_storage:install
docker-compose run コンテナ名 rails db:migrate

用意するもの

  • S3バケット情報

    • バケット名
    • リージョン
  • IAMユーザー情報

    • アクセスキー
    • シークレットアクセスキー

手順

1. root dirにて以下のコマンドを実行
terminal
docker exec -it コンテナID sh
/app # EDITOR=vi rails credentials:edit

コンテナIDは以下のコマンドで確認できる

terminal
docker ps
2. credentialsを編集

credentialsをターミナル上で編集する

awsは最初コメントアウトされているので外す (それに気付かずハマってしまいました)

aws:
 access_key_id: 取得したアクセスキー
 secret_access_key: 取得したシークレットアクセスキー

入力し保存する.

Esc→:wqでセーブして保存する.

:wq
3. config/storage.ymlの編集

regionとbucketを作成したものに書き換える.

config/storage.yml
amazon:
 service: S3
 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
 secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
 region: "リージョン"
 bucket: "bucket名"
4. aws-sdk-s3というgemをインストールする

Gemfileを編集

Gemfile
gem 'aws-sdk-s3', require: false

Gemfileを書き換えたので,コンテナをbuildし直す.

terminal
docker-compose build コンテナ名
5. config/environments/development.rbでActive Storageの参照先を:localから:amazonへと変更
config/environments/development.rb
config.active_storage.service = :amazon

以上で,S3のバケットに画像が保存された.

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