- 投稿日:2021-03-01T23:53:29+09:00
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を読み込んだけど、アセットパイプラインに指示するのではだめだったのか?
難しい。
- 投稿日:2021-03-01T22:48:12+09:00
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のポリモーフィックのモデルの結合について書きました。
ポリモーフィックやらモデル結合らへんの話は、結構よくでてくると思うのでぜひ参考にしてください。
- 投稿日:2021-03-01T22:42:53+09:00
Rails Slackへメッセージを送信
前提条件
・ruby 2.6.6
・Rails 6.0.3.2
・macOS Catalina バージョン10.15.7概要
webアプリ内で特定のアクションが起きた時にSlackへ通知する機能を実装したいと思い
実装の備忘録としてここに記す。
思ったよりかなり簡単だったので、ぜひご自身のwebアプリに活用していただけると幸いです。gem 'slack-notifier'の導入
slackへ通知を送るにはgemを導入する必要があります。
Gemfilegem '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/
- 投稿日:2021-03-01T21:48:17+09:00
【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にどのコマンドが正しいかというところまでは理解することが出来ませんでしたので、今後の課題としたいと思います。
もし詳しい方がいらっしゃいましたら、ご教授いただければ嬉しく思います。
終わり
今回は以上になります。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になればと思っています。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。
- 投稿日:2021-03-01T21:40:22+09:00
Railsで通知機能を作る(②既読管理編)
やったこと
Railsでアプリを作る課題を行っています。通知機能を作りましょうというタスクがありました。
初めて作る&覚えておくと応用が効きそうなので、以下にやり方をまとめます。なお、実行環境は以下の通りです。
- Rails
5.2.3
- Ruby
2.6.4
仕様
以下のような仕様になっています。
user
がフォローされた時user
の投稿したpost
にコメントがついた時user
の投稿に「いいね!」がされた時なお、基本的な機能の実装は、こちらの記事で解説しています。
DB設計
activities
テーブルのread
カラムで既読の管理をしていますが、それ以外のDB設計の詳細については、先の「①基本機能編」の記事と、こちらの記事を参照してください。Routing
今回、既読管理は
read
というメソッドをactivity
コントローラー下に作って実装します。そのため、ルーティングを下記のように実装します。config/routes.rbresources :activities, only: [] do patch :read, on: :member end
only: []
なんて書き方は初めて見ましたが、Railsの基本の7アクションがなくて、かつオリジナルのメソッドだけある時は、これで書けるようです。知らなかった。Model
続いてモデルです。モデルには、
enum
でread
とunread
を定義します。models/activity.rbenum read: { unread: false, read: true, }enumって、boolean型にも使えるのですね。
true
とfalse
にそれぞれ別の名前がつけられて、文脈がわかりやすくなりますね。また、モデルには以下のようなメソッドも定義しました。
models/activity.rbdef 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.rbclass 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では既読の時だけ色が変わるように設定して...。
クリックすると色が変わり、ビジュアルに既読管理ができるようになりました。
最終的には、こちらに「未読の件数」表示のバッジをつけて完成しましたが、以上で、既読管理の完成です。
まとめ
初めは、実装についてあまりイメージのついていなかった「通知」機能ですが、
- 「通知」テーブルを別途作る
- 通知対象のアクションが行われたら、「通知」レコードを一つ作る
- 通知を確認するためのリンクを設定し、リンクをクリックしたらステータスを「既読」にする
というのが大まかなポイントなのかなと思いました。
ポリモーフィック関連付けについては、使い所をもう少し抽象的に理解したいです。それでは、引き続き頑張っていきます。
- 投稿日:2021-03-01T21:29:40+09:00
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' %>無事反映されました!
- 投稿日:2021-03-01T21:17:49+09:00
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."を削除
これで解決。
- 投稿日:2021-03-01T19:02:43+09:00
【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)に変更したところ、解決しました。
同様のエラーに悩んでいる人の助けになれば幸いです。
- 投稿日:2021-03-01T18:04:33+09:00
作品を評価し合うwebアプリ作ってみた[個人開発]
今回作ったもの
タイトルにもある通り、絵や音楽、プログラムなどの作品を評価し合うコミュニティサイト「PRORP」です。
なぜ作ったか
ヤフー知恵袋でこんな質問を見つけました。
塾に通ってるなら他人からの評価は簡単に受けれますが、独学の場合なかなかしっかりと評価を受けるときってありません。
以外に他人の評価って欲しがってる人が多いのかなと思い作りました。工夫した点
投稿一覧ページをなくした
公開したばかりでユーザーも投稿も少ないのでユーザーが投稿が見る方法は検索だけにしました。
使いずらいですが最初はしょうがないんです。すみません(__)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
- 投稿日:2021-03-01T17:41:40+09:00
【超かんたん】Fakerを使ってダミーデータを作成しよう!
Fakerを利用してアプリにダミーデータ生成します。
今回は、deviseで作成したusersテーブルにダミーデータを投入していきます。Fakerとは
Fakerとはランダムな値を生成してくれるGemです。人名、メールアドレス、パスワードの生成はもちろんのことゲームのタイトルやアニメのキャラクター名なども生成してくれる遊び心がいっぱい詰まったGemです。公式ドキュメント
では、さっそく使って行きましょう!
Fakerの導入
gamのインストール
Gemfilegem 'faker'ターミナルbundle install導入完了。
ダミーデータの生成
今回は、deviseで生成されるemailとpasswordの他にnameカラムを追加しています。
deviseで生成されるemailカラムには一意性制約があるのでFaker::Internetとfree_emailの間にuniqueと記述しています。
passwordもデフォルトでは6文字以上でないと登録できないのでmin_length: 6と記述しています。この辺りはご自身が制作するアプリの仕様によって適宜変更していきましょう。
db/seeds.rb50.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ダミーデータが生成されています。
日本語化してみよう
Fakerは一部、日本語対応しています。
日本語化はとても簡単です。
config/application.rbにconfig.i18n.default_locale = :jaという記述を追加するだけです。config/application.rbmodule アプリ名 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番外編
ここからは番外編です。
ダミーデータの名前を変更してみましょう。
詳しくは公式ドキュメントを参照しましょう。まずはja.ymlファイルを作成します。
ターミナルtouch config/locales/ja.ymlこちらの記述を作成したja.ymlにコピペし日本語化しましょう。
※日本語化できるのは一部だけです。ポケモンの場合
db/seeds.rb50.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スーパーマリオの場合
db/seeds.rb50.times do name = Faker::Games::SuperMario.character #変更 email = Faker::Internet.unique.free_email password = Faker::Internet.password(min_length: 6) #以下略ターミナルrails db:seedドラゴンボールの場合
db/seeds.rb50.times do name = Faker::JapaneseMedia::DragonBall.character #変更 email = Faker::Internet.unique.free_email password = Faker::Internet.password(min_length: 6) #以下略ターミナルrails db:seed以上になります。
- 投稿日:2021-03-01T15:42:14+09:00
[Rails] モデルが使用しているデータベースを調べる
方法
ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#current_database を使用する。
DemonSlayer.superclass #=> ApplicationRecord(abstract) DemonSlayer.connection.current_database #=> 'kimetsu_production'
- 投稿日:2021-03-01T14:03:16+09:00
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
これで本番環境へ反映します。
- 投稿日:2021-03-01T13:39:49+09:00
【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の一番下に下記を記述します。
Gemfilegem '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/models
にuser.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
というマイグレーションファイルが生成されているので編集が必要であれば編集します。例えば、元々はユーザー登録に
password
しか必要ないようになっているので、下記のようにstring型
のname
を追加したりできます。db/migrate/2021×××_devise_create_user.rbclass 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.rbclass 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カラムにデフォルトの
password
以外を追加した場合は、それも保存できるように許可の記述をしないと弾かれてしまいます。
app/controllers/application_controller.rb
に以下のように記述します。app/controllers/application_controller.rbclass 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
という名前は慣習的なものらしいので特に意味はありません。以上です。
- 投稿日:2021-03-01T13:34:45+09:00
いいね機能の実装について
はじめに
今回は、オリジナルアプリに実装した「いいね機能」について
振り返りながら記事を書きたいと思います。オリジナルアプリでいいね機能は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コントローラーの設定
まずは、コントローラーを作成します。
% 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) endfind_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が一致したものがあれば、削除のボタンを表示し、
一致しない時は、いいねボタンが表示されるようにしています。以上がいいね機能実装の手順になります。
初心者の私にはかなり難しく感じました。
実装できたものの、理解できていない部分もあります。もし何か間違いや聞きたいことがありましたら、コメント
していただければ幸いです。
- 投稿日:2021-03-01T12:41:09+09:00
【Rails】link_toのxxx_pathが長い時はpolymorphic_pathを使うと綺麗に書けるお話
Railsにはよく使うものとして
link_to
やbutton_to
があります。これらはリンクやボタンを生成するヘルパーです。更に、遷移先のURLやpathを指定することが出来て、以下のように書けます。
<%= link_to '詳細へ', user_path(user.id) %>では、URLのネストをした場合、
xxx_path
はどうなるでしょうか?例えば、ユーザーは記事を投稿でき、その記事たちをお気に入りに追加できる機能があったとします。
ルーティングはこちら
routes.rbRails.application.routes.draw do # ユーザーは記事を投稿できる resources :users do resources :articles do # 記事をお気に入りすることができる resources :favorites end end endrails 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の記述を簡潔に書くことができます。
ぜひお試しください!
- 投稿日:2021-03-01T11:19:31+09:00
【Rails】acts_as_listで並べ替え機能を実装
導入
Gemfilegem 'acts_as_list'$ bundle installapp/models/task.rbclass Task < ApplicationRecord acts_as_list endrails g migration AddPositionToTask position:integerclass AddPositionToTask < ActiveRecord::Migration[6.0] def change add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない end end$ rails db:migrate
config/routes.rbresources :tasks do member do get :move_higher get :move_lower end end同期処理で動かす
/app/contollers/user_controller.rbdef 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 endapp/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: true
とjs.erb
を使って実装。詳細は割愛。参考: 【Ruby on Rails】部分テンプレート(js.erb)を用いた非同期通信について(基礎/開発) - Qiita
注意点
positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。参考
- 投稿日:2021-03-01T11:19:31+09:00
【Rails】acts_as_listで並べ替え機能を同期/非同期それぞれで実装
導入
Gemfilegem 'acts_as_list'$ bundle installapp/models/task.rbclass Task < ApplicationRecord acts_as_list endrails g migration AddPositionToTask position:integerclass AddPositionToTask < ActiveRecord::Migration[6.0] def change add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない end end$ rails db:migrate
config/routes.rbresources :tasks do member do get :move_higher get :move_lower end end同期処理で動かす
app/contollers/user_controller.rbdef 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 endapp/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.rbdef 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) endapp/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.rbclass Task < ApplicationRecord acts_as_list scope: [:kind] end注意点
positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。参考
- 投稿日:2021-03-01T11:19:31+09:00
【Rails】acts_as_listを使った並べ替え機能を同期/非同期それぞれで実装
導入
Gemfilegem 'acts_as_list'$ bundle installapp/models/task.rbclass Task < ApplicationRecord acts_as_list endrails g migration AddPositionToTask position:integerclass AddPositionToTask < ActiveRecord::Migration[6.0] def change add_column :tasks, :position, :integer #カラム名はpositionじゃなきゃいけない end end$ rails db:migrate
config/routes.rbresources :tasks do member do get :move_higher get :move_lower end end同期処理で動かす
app/contollers/user_controller.rbdef 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 endapp/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.rbdef 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) endapp/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.rbclass Task < ApplicationRecord acts_as_list scope: [:kind] end注意点
positionカラムのデータがnullだと動かない。
既存のテーブルに後からpositionカラムを追加した際には、なんらかの方法で全てのデータのpositionカラムに数値を入力する必要がある。参考
- 投稿日:2021-03-01T11:06:37+09:00
Custom Validator
最初に
みなさん、Custom Validator使ってますか?
rails guideのここにも記載されていますが
ActiveModel::Validator
やActiveModel::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など使えるところは色々あるのでお試し下さい!
- 投稿日:2021-03-01T11:06:37+09:00
RailsでCustom Validatorの実装例
最初に
みなさん、Custom Validator使ってますか?
rails guideのここにも記載されていますが
ActiveModel::Validator
やActiveModel::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など使えるところは色々あるのでお試し下さい!
- 投稿日:2021-03-01T09:18:31+09:00
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 endclass 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.html4.ビューを編集する
<%= link_to favorite_movie_path do %> <% if current_user.likes?(movie) %> お気に入り解除 <% else %> お気に入り登録 <% end %> <% end %>先程、指定したルーティングを
link_to
で使用しています。
current_user.likes?(movie)
これは、ユーザーがお気に入り登録しているとtrueを返します。
シンプルですが、基本的な機能の実装は以上です。
しかし、Ajax(非同期処理)でお気に入り登録をしたり、
お気に入りの総数を表示したいと思うかもしれません。
この辺りの実装は補足として別記事で行おうと考えています。
何か、至らない点があれば、ご指摘下さい。
- 投稿日:2021-03-01T04:00:28+09:00
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のことでしたのでそのままやっていきます。
しかし、なんでも略されるとわけわからなくなる時ありますよね。
・・・まじ最初サッカーやんって⚽️。
- 投稿日:2021-03-01T02:28:36+09:00
[rails6] fullcalendar 攻略ガイド(予定追加、表示編)
はじめに
この記事はjqueryの外部ライブラリである「fullcalendar」をrailsで使いこなそうという記事です。
前回の投稿をまだご覧になっていない方は下記の記事を一度ご覧になってからこちらの記事を読まれることをお勧めします。
過去の投稿
・ [rails6] fullcalendar 攻略ガイド(導入、表示編)
今回の内容の概要
今回は前回表示させたカレンダーの上に実際の予定を追加し、表示を実装してみたいと思います。
jqueryでajaxを使用するのでajaxの知識が必要になります。
前提知識
ajax,jbuilderの知識
参考
Railsでajaxを実装する。基本の構造から、controller・jbuilderの書き方など。[Rails]
予定の追加
実装の流れ
まず最初の流れを説明するので、迷ったらここをみてもらえれば今どこにいるかが分かってもらえるかと思います。
- カレンダーの予定を追加したい日付の余白をクリックし、予定追加フォームが記載されたmodalを表示
- modal内で追加操作、ボタンで確定
- 追加成功modalを表示
- カレンダーに予定が反映される
この流れでやっていきます。
事前準備
model設計
今回は
Event
という名前のモデルを作成します
カルムは予定の詳細(title)、開始時間(start)、終了時間(end)としました。consolerails g model Event title:string start:datetime end:datetime
validate
models/event.rbclass 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 end1. カレンダーの予定を追加したい日付の余白をクリック、予定追加フォームが記載されたmodalを表示
controller
controllerは
resources :events
としてnewでフォームを出していきます。events_controller.rbdef 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の時のコーディングで紐づいてくるのでいったん先に進みます。 endview
次は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">×</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">×</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.jsdocument.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.rbdef 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) end3. 追加成功、失敗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.rbdef index @events = Event.all #変数名は絶対に「@events」にすること!! endjsに追加
fullcalendarの内容を記載しているjsファイル内にデータの受信をする設定を記載します。
app/javascript/packs/calendar/event.jsdocument.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通信でイベント登録)
- 投稿日:2021-03-01T02:28:36+09:00
[rails6] fullcalendar 攻略ガイド(予定追加、予定表示編)
はじめに
この記事はjqueryの外部ライブラリである「fullcalendar」をrailsで使いこなそうという記事です。
前回の投稿をまだご覧になっていない方は下記の記事を一度ご覧になってからこちらの記事を読まれることをお勧めします。
過去の投稿
・ [rails6] fullcalendar 攻略ガイド(導入、表示編)
今回の内容の概要
今回は前回表示させたカレンダーの上に実際の予定を追加し、表示を実装してみたいと思います。
jqueryでajaxを使用するのでajaxの知識が必要になります。
前提知識
ajax,jbuilderの知識
参考
Railsでajaxを実装する。基本の構造から、controller・jbuilderの書き方など。[Rails]
予定の追加
実装の流れ
まず最初の流れを説明するので、迷ったらここをみてもらえれば今どこにいるかが分かってもらえるかと思います。
- カレンダーの予定を追加したい日付の余白をクリックし、予定追加フォームが記載されたmodalを表示
- modal内で追加操作、ボタンで確定
- 追加成功modalを表示
- カレンダーに予定が反映される
この流れでやっていきます。
事前準備
model設計
今回は
Event
という名前のモデルを作成します
カルムは予定の詳細(title)、開始時間(start)、終了時間(end)としました。consolerails g model Event title:string start:datetime end:datetime
validate
models/event.rbclass 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 end1. カレンダーの予定を追加したい日付の余白をクリック、予定追加フォームが記載されたmodalを表示
controller
controllerは
resources :events
としてnewでフォームを出していきます。events_controller.rbdef 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の時のコーディングで紐づいてくるのでいったん先に進みます。 endview
次は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">×</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">×</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.jsdocument.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.rbdef 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) end3. 追加成功、失敗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.rbdef index @events = Event.all #変数名は絶対に「@events」にすること!! endjsに追加
fullcalendarの内容を記載しているjsファイル内にデータの受信をする設定を記載します。
app/javascript/packs/calendar/event.jsdocument.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通信でイベント登録)
- 投稿日:2021-03-01T00:42:53+09:00
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のインストールは以下のコマンドでできる
terminaldocker-compose run コンテナ名 rails active_storage:install docker-compose run コンテナ名 rails db:migrate用意するもの
S3バケット情報
- バケット名
- リージョン
IAMユーザー情報
- アクセスキー
- シークレットアクセスキー
手順
1. root dirにて以下のコマンドを実行
terminaldocker exec -it コンテナID sh /app # EDITOR=vi rails credentials:editコンテナIDは以下のコマンドで確認できる
terminaldocker ps2. credentialsを編集
credentialsをターミナル上で編集する
awsは最初コメントアウトされているので外す (それに気付かずハマってしまいました)
aws: access_key_id: 取得したアクセスキー secret_access_key: 取得したシークレットアクセスキー入力し保存する.
Esc→:wqでセーブして保存する.
:wq3. config/storage.ymlの編集
regionとbucketを作成したものに書き換える.
config/storage.ymlamazon: 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を編集
Gemfilegem 'aws-sdk-s3', require: falseGemfileを書き換えたので,コンテナをbuildし直す.
terminaldocker-compose build コンテナ名5. config/environments/development.rbでActive Storageの参照先を:localから:amazonへと変更
config/environments/development.rbconfig.active_storage.service = :amazon以上で,S3のバケットに画像が保存された.