- 投稿日:2020-11-19T23:55:38+09:00
【初心者向け】Rails6で作られたWebアプリをCircleCIを使いAWS ECR・ECSへ自動デプロイする方法②-2 インフラ構築編【コンテナデプロイ】
さて、前回RDSまで作成できたので、今回はALBの作成をしていこうと思います!
この記事ではALBを作成するだけなので、今までの記事を比べるとこの記事は短めです!
タイトル ① 下準備編 ②-1 インフラ構築編 ②-2 インフラ構築編 ←今ここ! 自動デプロイ編(執筆中) ALBとは?
ここで、ALBについて説明させていただきます。
ALBとは「Application Load Balancer」の略称で、Webからのアクセスを分散してくれるものです。
一つのWebサイトにアクセスが集中してしまうと、サーバーがアクセスを処理しきれなくなり、ページが表示できなるくなるといったトラブルが起こります。ALBを導入することで複数のサーバーに負荷を振り分けることができるので、安定したサービスをユーザーに提供することができるというわけです。
これは余談ですが、ALBの他にも、CLB(Classic Load Balancer)、NLB(Network Load Balancer)というものがあり、これらのロードバランシングサービスを総称してELBと呼びます。
ALBを作成する
それではさっそく、ALBを作成していきましょう!
サービスからEC2のコンソールへ行き、Load Balancers→Create Load Balancerとクリックし、 Application Load Balancersを選択する。
任意のALBの名前を入力します。
Listenerは一旦デフォルトのままでいいです。
その後、自動作成されたVPC、サブネットを選択し、Nextをクリック。
警告が出てきますが、かまわずNextをクリックします。
次にALBのセキュリティグループを新に作成します。任意の名前と説明を入力します。
インバウンドルールはHTTPプロトコルで、すべてのソースを許可します。(画像参照)
次にターゲットグループ(ALBに来たアクセスのリダイレクト先)を新しく作成します。
ターゲットグループにはEC2インスタンスを指定していきます。New Target groupを選択し任意のターゲットグループの名前を入力します。
後はデフォルトでOKです。
ヘルスチェック(ALBからサーバーが正常に動いているかどうかチェックする機構)もデフォルトでOK
デフォルトだとヘルスチェックのパスはルートパスになってます。
次にターゲットグループのインスタンスを登録します。
自動生成されたEC2インスタンスが二つあるはずなのでチェックマークをいれ、赤丸のinclude as pending below をクリックします。
その後、Register pending targetsをクリックします!
最後に、確認画面が出るので、設定があっているか確認出来たらCreateをクリックして作成完了です!
お疲れ様でした!最後まで読んでいただきありがとうございます!
今回はALB作成編をやっていきました!次回はいよいよDockerイメージをECRへpushしていきます!
今週中にはすべての記事を完成させていくつもりですので、よろしくお願い致します!!何かご指摘やご質問などあればコメントいただけますと嬉しいです。
- 投稿日:2020-11-19T23:31:05+09:00
【Rails6】ActionCableでマルチチャット
はじめに
初投稿です!
今回はRailsのActionCableを用いて、リアルタイムチャットアプリを作ります。
マルチチャットとは、ユーザごとに複数のチャット部屋があり、それらを行き来できるイメージです。デモ
ソースコードはこちらです。
https://github.com/yamori-masato/chat-app動作環境
- Ruby 2.6.3
- Rails 6.0.3.4
下準備
$ rails new chat-app $ cd chat-appモデルの作成
今回はこのようなモデル設計にしました。
それでは一通りモデルを作成します。$ rails g model User name:string $ rails g model Room name:string $ rails g model UserRoom user:references room:references $ rails g model Message content:string user:references room:references $ rails db:migrateapp/models/user.rbclass User < ApplicationRecord has_many :user_rooms, dependent: :destroy has_many :rooms, through: :user_rooms endapp/models/room.rbclass Room < ApplicationRecord has_many :messages, dependent: :destroy has_many :user_rooms, dependent: :destroy has_many :users, through: :user_rooms endapp/models/user_room.rbclass UserRoom < ApplicationRecord belongs_to :user belongs_to :room endapp/models/message.rbclass Message < ApplicationRecord belongs_to :user belongs_to :room validates_presence_of :content endコントローラーの作成
$ rails g controller rooms index show
config/routes.rbRails.application.routes.draw do resources :users, only: [] do resources :rooms, only: [:index, :show] end end
- index... 自分が所属するRoom一覧
- show... チャットルーム閲覧
コントローラを作成して、routesファイルを編集しました。
実際にルーティングを確認してみましょう。$ rails routes ︙ user_rooms GET /users/:user_id/rooms(.:format) rooms#index user_room GET /users/:user_id/rooms/:id(.:format) rooms#show ︙2つのルーティングができています。
resourcesのonlyオプションに空の配列を渡しているので、userに対するルーティングが生成されません。それでは、コントローラーの中身を書いていきます。
app/controller/rooms_controller.rbclass RoomsController < ApplicationController before_action :set_user, only: [:index, :show] def index @rooms = @user.rooms.all end def show @room = @user.rooms.find(params[:id]) @messages = @room.messages.all end private def set_user @user = User.find(params[:user_id]) end endビューの作成
最低限ですがビューも作っていきます。
app/views/rooms/index.html.erb<h1>Chat Rooms</h1> <ul> <% @rooms.each do |room| %> <li><%= link_to room.name, user_room_path(@user,room) %></li> <% end %> </ul>app/views/rooms/show.html.erb<h1><%= @room.name %></h1> <div id="container"> <%= render @messages %> </div> <%= link_to 'Back', user_rooms_path(@user) %>app/views/messages/_message.html.erb<p> <strong><%= message.user.name %></strong> > <%= message.content %> </p>テストデータの登録
ここまでで最低限の機能が作れているはずです。
テストデータを登録して確認してみましょう!コンソールでデータを登録します。
$ rails c
> user1 = User.create!(name: "user1") > user2 = User.create!(name: "user2") > room1 = Room.create!(name: "room1") > room1.users << [user1, user2] > Message.create!(content:"hello!", room_id: room1.id, user_id: user1.id)$ rails s
先ほど登録したメッセージがroom1に表示されているはずです。
メッセージ送信
現時点では送信ボタンを押してもメッセージが送られないので、これを送れるようにしたいと思います。
Ajaxで送信できるようにします。まず、メッセージを新規追加できるようにコントローラーを修正します。
$ rails g controller message create
config/routes.rbRails.application.routes.draw do resouces :usersn, only: [] do resources :rooms, only: [:index, :show] end + resources :messages, only: [:create] end
app/controllers/messages_controller.rbclass MessagesController < ApplicationController def create @message = Message.create!(message_params) end private def message_params params.require(:message).permit(:content, :room_id, :user_id) end endフォーム送信後に、このcreateアクションが呼ばれるようにします。
app/controller/rooms_controller.rbclass RoomsController < ApplicationController ︙ def show @room = @user.rooms.find(params[:id]) @messages = @room.messages.all + @message = @room.messages.build end ︙ end
app/views/rooms/show.html.erb︙ + <%= form_with(model: @message, html:{name: "myform"}) do |f| %> + <%= f.text_field :content %> + <%= f.hidden_field :user_id, value: @user.id %> + <%= f.hidden_field :room_id, value: @room.id %> + <%= f.submit '送信' %> + <% end %> <%= link_to 'Back', user_rooms_path(@user) %>
これで送信ボタンを押すたびにajaxリクエストが送られて、messageが新規作成されるようになりました。
form_withにあるhidden_fieldは、描画はされませんが、post送信時にパラメータの1つとして送信されます。
ヘルパーと実際の出力の対応はrailsガイドをご覧ください。classやname属性も自動的に付与されます。このままでは送信後に入力した文字がそのままなので、送信したら文字が消えるようにしましょう。
app/views/messages/create.js.erbdocument.getElementById("message_content").value = ''ajaxリクエスト後はデフォルトで
views/messages/create.html.erb
が呼ばれます。
ここでは送信後にフォームの値を空にしています。ここで、idに"message_content"を指定しています。
これはform_withヘルパーによって自動生成されたinput要素に付与されるidです。
実際にChromeの検証ツールで見てみましょう。このようにform_withで自動生成されるフォームにはそれぞれ決まったクラス名やid名がついていることがわかります。今回の場合は、inputタグのidにmessage_contentが付与されているのでこれを指定しています。
これでAjaxでメッセージ送信することができるはずなので実際に確認してみましょう!
※この時点ではまだリロードしないと描画に反映されません。
WebSocketを用いた双方向通信
いちいちリロードするのは不便なので、リロードなしで描画に反映されるようにしましょう。
そこで使われるのがActionCableです。ActionCable
ActionCableとは、Rails5で追加されたWebSocketを扱えるフレームワークです。
これを用いることで、閲覧者が画面の操作を行わなくても受動的に新しい情報をリアルタイムで取得できるようになります。Channelの作成
$ rails g channel room create app/channels/room_channel.rb identical app/javascript/channels/index.js identical app/javascript/channels/consumer.js create app/javascript/channels/room_channel.js
いくつかファイルが作成されたので確認してみましょう。
app/channels/room_channel.rbclass RoomChannel < ApplicationCable::Channel def subscribed # stream_from "some_channel" end def unsubscribed # Any cleanup needed when channel is unsubscribed end endapp/javascript/channels/room_channel.jsimport consumer from "./consumer" consumer.subscriptions.create("RoomChannel", { connected() { // Called when the subscription is ready for use on the server }, disconnected() { // Called when the subscription has been terminated by the server }, received(data) { // Called when there's incoming data on the websocket for this channel } });これはRoomChannelを購読するクライアント側のコードになります。サブスクリプションを作成することでチャネルを購読することができます。
Channelの購読について
先ほど生成されたコードについて詳しく見ていきましょう。
まず、ページが読み込まれたタイミングでRoomChannelが購読されます。これは、デフォルトで
javascript/channels
配下のファイルがコンパイル対象になっているからです。先ほどのコードを少し変更してみましょう。
app/javascript/channels/room_channel.jsimport consumer from "./consumer" consumer.subscriptions.create( - "RoomChannel", + { channel: "RoomChannel", room_id: 1, user_id: 1 }, { connected() { // Called when the subscription is ready for use on the server }, disconnected() { // Called when the subscription has been terminated by the server }, received(data) { // Called when there's incoming data on the websocket for this channel } });第一引数を変更しました。
これらの引数は、サブスクリプション作成時にパラメータとしてサーバー側に渡すことができます。では、サーバー側ではどのようにパラメータを受け取るのでしょうか?
実際に確認してみましょう。Gemfile︙ + gem 'pry-rails'
app/channels/room_channel.rbclass RoomChannel < ApplicationCable::Channel def subscribed + binding.pry end def unsubscribed end end
$ bundle install $ rails s[1] pry(#<RoomChannel>)> params => {"channel"=>"RoomChannel", "room_id"=>1, "user_id"=>1}クライアント側でサブスクリプションが作成されると、購読されたチャネルのsubscribedメソッドが呼ばれます。
また、第一引数に渡した値がparamsとして受け取れることがわかります。streamとbroadcast
クライアント側から受け取ったパラメータ(params)を使うことでマルチチャットを実装することができます。
先ほどのコードを次のように変更してみましょう。app/channels/room_channel.rbclass RoomChannel < ApplicationCable::Channel def subscribed @user = User.find_by(id: params[:user_id]) reject if @user.nil? @room = @user.rooms.find_by(id: params[:room_id]) reject if @room.nil? stream_for(@room) end def unsubscribed end end今回は、あるチャットルームで発言した時、ルーム内のユーザーのみに送信を知らせるようにします。
例えば、room1で発言した内容はroom1に所属するメンバーにブロードキャストされるといった感じになります。
そこで、チャットルームをストリームと紐付けます。また、実在しないユーザーや、ユーザーが所属しないチャットルームであった場合はrejectしています。
rejectすると、チャネルの購読を拒否することができます。チャットルームとストリームを紐づける
今のままではどの部屋に対しても
user_id: 1
,room_id: 1
としてストリームが作成されてしまいます。これではどのチャットルームで発言しても、user1のroom1での発言と見されてしまうのでこれを修正していきます。app/views/rooms/show.html.erb<h1><%= @room.name %></h1> + <div id="data" data-room-id="<%= @room.id %>" data-user-id="<%= @user.id %>"></div> <div id="container"> <%= render @messages %> </div> <%= form_with(model: @message, html:{name: "myform"}) do |f| %> <%= f.text_field :content, rows: '1'%> <%= f.hidden_field :user_id, value: @user.id %> <%= f.hidden_field :room_id, value: @room.id %> <%= f.submit '送信' %> <% end %> <%= link_to 'Back', user_rooms_path(@user) %>
app/javascript/channels/room_channel.jsimport consumer from "./consumer" document.addEventListener("turbolinks:load", () => { const data = document.getElementById("data") const room_id = data.getAttribute("data-room-id") const user_id = data.getAttribute("data-user-id") consumer.subscriptions.create( { channel: "RoomChannel", room_id: room_id, user_id: user_id }, { connected() { // Called when the subscription is ready for use on the server }, disconnected() { // Called when the subscription has been terminated by the server }, received(data) { // Called when there's incoming data on the websocket for this channel } } ) })room_idとuser_idは、view側から渡してあげます。
また、DOMの読み込み完了を待ちたいので"turbolinks:load"を記述します。これにより、
/users/1/rooms/1
にアクセスすれば、room_id: 1
,user_id: 1
としてRoomChannelがサブスクライブされます。このままではチャットルーム以外のページでも読み込まれてしまう為、document.getElementById("data")が見つからずにエラーになってしまいます。なのでif文で分岐してあげます。
app/javascript/channels/room_channel.jsimport consumer from "./consumer" document.addEventListener("turbolinks:load", () => { const data = document.getElementById("data") + if (data === null) { + return + } const channel = "RoomChannel" const room_id = data.getAttribute("data-room-id") const user_id = data.getAttribute("data-user-id") ︙
また、チャットルームのページを開くたびに同じチャットルームに対するサブスクリプションが複数作成されてしまう恐れがあるのでこれも修正します。
app/javascript/channels/room_channel.jsimport consumer from "./consumer" document.addEventListener("turbolinks:load", () => { const data = document.getElementById("data") if (data === null) { return } const channel = "RoomChannel" const room_id = data.getAttribute("data-room-id") const user_id = data.getAttribute("data-user-id") + if (!isSubscribed(channel, room_id, user_id)) { consumer.subscriptions.create( ︙ ) } }) // helper + const isSubscribed = (channel, room_id, user_id) => { + const identifier = `{"channel":"${channel}","room_id":"${room_id}","user_id":"${user_id}"}` + const subscription = consumer.subscriptions.findAll(identifier) + return !!subscription.length + }チャットルームごとにストリームを紐づけることができました。
ブロードキャストする
チャットルームとストリームを紐づけることができたので、ブロードキャストする処理を書いていきます。
今回のチャットアプリでは、メッセージを新規作成したタイミングで知らせたいので、そのままコントローラに記述します。app/controllers_messages_controller.rbclass MessagesController < ApplicationController def create @message = Message.create!(message_params) @room = Room.find_by(id: message_params[:room_id]) + RoomChannel.broadcast_to(@room, message: @message.template) end private def message_params params.require(:message).permit(:content, :room_id, :user_id) end end
app/models/message.rbclass Message < ApplicationRecord belongs_to :user belongs_to :room validates_presence_of :content + def template + ApplicationController.renderer.render partial: 'messages/message', locals: { message: self } + end end
app/javascript/channels/room_channel.jsimport consumer from "./consumer" document.addEventListener("turbolinks:load", () => { ︙ consumer.subscriptions.create( { channel: "RoomChannel", room_id: room_id, user_id: user_id }, { connected() { }, disconnected() { }, received(data) { + const container = document.getElementById("container") + container.insertAdjacentHTML('beforeend', data['message']) } } ) }) ︙broadcastされるとreceived(data)が呼び出されます。
受け取ったデータは、data['message']のようにして受け取れます。
これをチャットログの末尾に挿入してあげれば完成です!!参考文献
- 描いて理解する Action Cable
- Rails5のActionCableでイカゲームもどきを作ってみた
- 【Rails6】(送信時のリロード無し!)Action CableでSlack風チャットアプリを作成
- 【Rails6.0】ActionCableを使用したライブチャットアプリを実装する手順を解説
- Rails 6: Subscribing to Multiple Channels in Action Cable
おわりに
次回は、このチャットアプリにログイン機能を追加したいと思います!
ご意見アドバイス等ぜひお願いいたします。
- 投稿日:2020-11-19T23:31:05+09:00
【Rails6】ActionCableでマルチルームチャット
はじめに
初投稿です!
今回はRailsのActionCableを用いて、リアルタイムチャットアプリを作ります。
マルチルームチャットとは、ユーザごとに複数のチャット部屋があり、それらを行き来できるイメージです。デモ
ソースコードはこちらです。
https://github.com/yamori-masato/chat-app動作環境
- Ruby 2.6.3
- Rails 6.0.3.4
下準備
$ rails new chat-app $ cd chat-appモデルの作成
今回はこのようなモデル設計にしました。
それでは一通りモデルを作成します。$ rails g model User name:string $ rails g model Room name:string $ rails g model UserRoom user:references room:references $ rails g model Message content:string user:references room:references $ rails db:migrateapp/models/user.rbclass User < ApplicationRecord has_many :user_rooms, dependent: :destroy has_many :rooms, through: :user_rooms endapp/models/room.rbclass Room < ApplicationRecord has_many :messages, dependent: :destroy has_many :user_rooms, dependent: :destroy has_many :users, through: :user_rooms endapp/models/user_room.rbclass UserRoom < ApplicationRecord belongs_to :user belongs_to :room endapp/models/message.rbclass Message < ApplicationRecord belongs_to :user belongs_to :room validates_presence_of :content endコントローラーの作成
$ rails g controller rooms index show
config/routes.rbRails.application.routes.draw do resources :users, only: [] do resources :rooms, only: [:index, :show] end end
- index... 自分が所属するRoom一覧
- show... チャットルーム閲覧
コントローラを作成して、routesファイルを編集しました。
実際にルーティングを確認してみましょう。$ rails routes ︙ user_rooms GET /users/:user_id/rooms(.:format) rooms#index user_room GET /users/:user_id/rooms/:id(.:format) rooms#show ︙2つのルーティングができています。
resourcesのonlyオプションに空の配列を渡しているので、userに対するルーティングが生成されません。それでは、コントローラーの中身を書いていきます。
app/controller/rooms_controller.rbclass RoomsController < ApplicationController before_action :set_user, only: [:index, :show] def index @rooms = @user.rooms.all end def show @room = @user.rooms.find(params[:id]) @messages = @room.messages.all end private def set_user @user = User.find(params[:user_id]) end endビューの作成
最低限ですがビューも作っていきます。
app/views/rooms/index.html.erb<h1>Chat Rooms</h1> <ul> <% @rooms.each do |room| %> <li><%= link_to room.name, user_room_path(@user,room) %></li> <% end %> </ul>app/views/rooms/show.html.erb<h1><%= @room.name %></h1> <div id="container"> <%= render @messages %> </div> <%= link_to 'Back', user_rooms_path(@user) %>app/views/messages/_message.html.erb<p> <strong><%= message.user.name %></strong> > <%= message.content %> </p>テストデータの登録
ここまでで最低限の機能が作れているはずです。
テストデータを登録して確認してみましょう!コンソールでデータを登録します。
$ rails c
> user1 = User.create!(name: "user1") > user2 = User.create!(name: "user2") > room1 = Room.create!(name: "room1") > room1.users << [user1, user2] > Message.create!(content:"hello!", room_id: room1.id, user_id: user1.id)$ rails s
先ほど登録したメッセージがroom1に表示されているはずです。
メッセージ送信
現時点では送信ボタンを押してもメッセージが送られないので、これを送れるようにしたいと思います。
Ajaxで送信できるようにします。まず、メッセージを新規追加できるようにコントローラーを修正します。
$ rails g controller message create
config/routes.rbRails.application.routes.draw do resouces :usersn, only: [] do resources :rooms, only: [:index, :show] end + resources :messages, only: [:create] end
app/controllers/messages_controller.rbclass MessagesController < ApplicationController def create @message = Message.create!(message_params) end private def message_params params.require(:message).permit(:content, :room_id, :user_id) end endフォーム送信後に、このcreateアクションが呼ばれるようにします。
app/controller/rooms_controller.rbclass RoomsController < ApplicationController ︙ def show @room = @user.rooms.find(params[:id]) @messages = @room.messages.all + @message = @room.messages.build end ︙ end
app/views/rooms/show.html.erb︙ + <%= form_with(model: @message, html:{name: "myform"}) do |f| %> + <%= f.text_field :content %> + <%= f.hidden_field :user_id, value: @user.id %> + <%= f.hidden_field :room_id, value: @room.id %> + <%= f.submit '送信' %> + <% end %> <%= link_to 'Back', user_rooms_path(@user) %>
これで送信ボタンを押すたびにajaxリクエストが送られて、messageが新規作成されるようになりました。
form_withにあるhidden_fieldは、描画はされませんが、post送信時にパラメータの1つとして送信されます。
ヘルパーと実際の出力の対応はrailsガイドをご覧ください。classやname属性も自動的に付与されます。このままでは送信後に入力した文字がそのままなので、送信したら文字が消えるようにしましょう。
app/views/messages/create.js.erbdocument.getElementById("message_content").value = ''ajaxリクエスト後はデフォルトで
views/messages/create.html.erb
が呼ばれます。
ここでは送信後にフォームの値を空にしています。ここで、idに"message_content"を指定しています。
これはform_withヘルパーによって自動生成されたinput要素に付与されるidです。
実際にChromeの検証ツールで見てみましょう。このようにform_withで自動生成されるフォームにはそれぞれ決まったクラス名やid名がついていることがわかります。今回の場合は、inputタグのidにmessage_contentが付与されているのでこれを指定しています。
これでAjaxでメッセージ送信することができるはずなので実際に確認してみましょう!
※この時点ではまだリロードしないと描画に反映されません。
WebSocketを用いた双方向通信
いちいちリロードするのは不便なので、リロードなしで描画に反映されるようにしましょう。
そこで使われるのがActionCableです。ActionCable
ActionCableとは、Rails5で追加されたWebSocketを扱えるフレームワークです。
これを用いることで、閲覧者が画面の操作を行わなくても受動的に新しい情報をリアルタイムで取得できるようになります。Channelの作成
$ rails g channel room create app/channels/room_channel.rb identical app/javascript/channels/index.js identical app/javascript/channels/consumer.js create app/javascript/channels/room_channel.js
いくつかファイルが作成されたので確認してみましょう。
app/channels/room_channel.rbclass RoomChannel < ApplicationCable::Channel def subscribed # stream_from "some_channel" end def unsubscribed # Any cleanup needed when channel is unsubscribed end endapp/javascript/channels/room_channel.jsimport consumer from "./consumer" consumer.subscriptions.create("RoomChannel", { connected() { // Called when the subscription is ready for use on the server }, disconnected() { // Called when the subscription has been terminated by the server }, received(data) { // Called when there's incoming data on the websocket for this channel } });これはRoomChannelを購読するクライアント側のコードになります。サブスクリプションを作成することでチャネルを購読することができます。
Channelの購読について
先ほど生成されたコードについて詳しく見ていきましょう。
まず、ページが読み込まれたタイミングでRoomChannelが購読されます。これは、デフォルトで
javascript/channels
配下のファイルがコンパイル対象になっているからです。先ほどのコードを少し変更してみましょう。
app/javascript/channels/room_channel.jsimport consumer from "./consumer" consumer.subscriptions.create( - "RoomChannel", + { channel: "RoomChannel", room_id: 1, user_id: 1 }, { connected() { // Called when the subscription is ready for use on the server }, disconnected() { // Called when the subscription has been terminated by the server }, received(data) { // Called when there's incoming data on the websocket for this channel } });第一引数を変更しました。
これらの引数は、サブスクリプション作成時にパラメータとしてサーバー側に渡すことができます。では、サーバー側ではどのようにパラメータを受け取るのでしょうか?
実際に確認してみましょう。Gemfile︙ + gem 'pry-rails'
app/channels/room_channel.rbclass RoomChannel < ApplicationCable::Channel def subscribed + binding.pry end def unsubscribed end end
$ bundle install $ rails s[1] pry(#<RoomChannel>)> params => {"channel"=>"RoomChannel", "room_id"=>1, "user_id"=>1}クライアント側でサブスクリプションが作成されると、購読されたチャネルのsubscribedメソッドが呼ばれます。
また、第一引数に渡した値がparamsとして受け取れることがわかります。streamとbroadcast
クライアント側から受け取ったパラメータ(params)を使うことでマルチルームチャットを実装することができます。
先ほどのコードを次のように変更してみましょう。app/channels/room_channel.rbclass RoomChannel < ApplicationCable::Channel def subscribed @user = User.find_by(id: params[:user_id]) reject if @user.nil? @room = @user.rooms.find_by(id: params[:room_id]) reject if @room.nil? stream_for(@room) end def unsubscribed end end今回は、あるチャットルームで発言した時、ルーム内のユーザーのみに送信を知らせるようにします。
例えば、room1で発言した内容はroom1に所属するメンバーにブロードキャストされるといった感じになります。
そこで、チャットルームをストリームと紐付けます。また、実在しないユーザーや、ユーザーが所属しないチャットルームであった場合はrejectしています。
rejectすると、チャネルの購読を拒否することができます。チャットルームとストリームを紐づける
今のままではどの部屋に対しても
user_id: 1
,room_id: 1
としてストリームが作成されてしまいます。これではどのチャットルームで発言しても、user1のroom1での発言と見されてしまうのでこれを修正していきます。app/views/rooms/show.html.erb<h1><%= @room.name %></h1> + <div id="data" data-room-id="<%= @room.id %>" data-user-id="<%= @user.id %>"></div> <div id="container"> <%= render @messages %> </div> <%= form_with(model: @message, html:{name: "myform"}) do |f| %> <%= f.text_field :content, rows: '1'%> <%= f.hidden_field :user_id, value: @user.id %> <%= f.hidden_field :room_id, value: @room.id %> <%= f.submit '送信' %> <% end %> <%= link_to 'Back', user_rooms_path(@user) %>
app/javascript/channels/room_channel.jsimport consumer from "./consumer" document.addEventListener("turbolinks:load", () => { const data = document.getElementById("data") const room_id = data.getAttribute("data-room-id") const user_id = data.getAttribute("data-user-id") consumer.subscriptions.create( { channel: "RoomChannel", room_id: room_id, user_id: user_id }, { connected() { // Called when the subscription is ready for use on the server }, disconnected() { // Called when the subscription has been terminated by the server }, received(data) { // Called when there's incoming data on the websocket for this channel } } ) })room_idとuser_idは、view側から渡してあげます。
また、DOMの読み込み完了を待ちたいので"turbolinks:load"を記述します。これにより、
/users/1/rooms/1
にアクセスすれば、room_id: 1
,user_id: 1
としてRoomChannelがサブスクライブされます。このままではチャットルーム以外のページでも読み込まれてしまう為、document.getElementById("data")が見つからずにエラーになってしまいます。なのでif文で分岐してあげます。
app/javascript/channels/room_channel.jsimport consumer from "./consumer" document.addEventListener("turbolinks:load", () => { const data = document.getElementById("data") + if (data === null) { + return + } const channel = "RoomChannel" const room_id = data.getAttribute("data-room-id") const user_id = data.getAttribute("data-user-id") ︙
また、チャットルームのページを開くたびに同じチャットルームに対するサブスクリプションが複数作成されてしまう恐れがあるのでこれも修正します。
app/javascript/channels/room_channel.jsimport consumer from "./consumer" document.addEventListener("turbolinks:load", () => { const data = document.getElementById("data") if (data === null) { return } const channel = "RoomChannel" const room_id = data.getAttribute("data-room-id") const user_id = data.getAttribute("data-user-id") + if (!isSubscribed(channel, room_id, user_id)) { consumer.subscriptions.create( ︙ ) } }) // helper + const isSubscribed = (channel, room_id, user_id) => { + const identifier = `{"channel":"${channel}","room_id":"${room_id}","user_id":"${user_id}"}` + const subscription = consumer.subscriptions.findAll(identifier) + return !!subscription.length + }チャットルームごとにストリームを紐づけることができました。
ブロードキャストする
チャットルームとストリームを紐づけることができたので、ブロードキャストする処理を書いていきます。
今回のチャットアプリでは、メッセージを新規作成したタイミングで知らせたいので、そのままコントローラに記述します。app/controllers_messages_controller.rbclass MessagesController < ApplicationController def create @message = Message.create!(message_params) @room = Room.find_by(id: message_params[:room_id]) + RoomChannel.broadcast_to(@room, message: @message.template) end private def message_params params.require(:message).permit(:content, :room_id, :user_id) end end
app/models/message.rbclass Message < ApplicationRecord belongs_to :user belongs_to :room validates_presence_of :content + def template + ApplicationController.renderer.render partial: 'messages/message', locals: { message: self } + end end
app/javascript/channels/room_channel.jsimport consumer from "./consumer" document.addEventListener("turbolinks:load", () => { ︙ consumer.subscriptions.create( { channel: "RoomChannel", room_id: room_id, user_id: user_id }, { connected() { }, disconnected() { }, received(data) { + const container = document.getElementById("container") + container.insertAdjacentHTML('beforeend', data['message']) } } ) }) ︙broadcastされるとreceived(data)が呼び出されます。
受け取ったデータは、data['message']のようにして受け取れます。
これをチャットログの末尾に挿入してあげれば完成です!!参考文献
- 描いて理解する Action Cable
- Rails5のActionCableでイカゲームもどきを作ってみた
- 【Rails6】(送信時のリロード無し!)Action CableでSlack風チャットアプリを作成
- 【Rails6.0】ActionCableを使用したライブチャットアプリを実装する手順を解説
- Rails 6: Subscribing to Multiple Channels in Action Cable
おわりに
次回は、このチャットアプリにログイン機能を追加したいと思います!
ご意見アドバイス等ぜひお願いいたします。
- 投稿日:2020-11-19T23:23:27+09:00
link_toを使って、一つ前のページに遷移させる方法
- 投稿日:2020-11-19T22:55:07+09:00
Rails 日付カラムのバリデーション設定
はじめに
Railsを使ってオリジナルアプリを開発しています。日付カラムに今日の日付より後(明日以降)は保存できないように、バリデーションを設定しました。忘れないように書き記します。
目次
1.日付カラムのバリデーション
2.モデルへの追加記述1.日付カラムのバリデーション
今回suggestionテーブルにlast_cleaned_dateカラム(最後に掃除した日付)を作成した。下記のように空の場合、保存できないバリデーションは設定した。しかし、他カラムと異なり、空以外のバリデーションは別で行う必要がある。
app/models/suggestionclass Suggestion < ApplicationRecord with_options presence: true do #----------------中略------------------- validates :last_cleaned_date #←対象のカラム end belongs_to :user end2.モデルへの追加記述
同ファイルにバリデーションを追記する。ここではlast_cleaned_dateカラムが空でなかった場合、day_after_todayの処理が行われる。errors.addでエラーの種類を追加する。ここでもif文で条件を定義した。last_cleaned_dateが今日の日付(Date.today)より大きい場合、明日以降を示す。そして条件を満たした場合、保存されずエラーを表示する。
app/models/suggestion.rbvalidate :day_after_today def day_after_today unless last_cleaned_date == nil errors.add(:last_cleaned_date, 'は、今日を含む過去の日付を入力して下さい') if last_cleaned_date > Date.today end end参考までに実装後のエラー表示画面を下記に示す。本日の日付(2020/11/19)で保存しようとした場合の、エラー文である。
以上
- 投稿日:2020-11-19T22:44:33+09:00
【Rails】ルーティングの確認(2パターン)
ターミナルでルートを確認する
ターミナルに $ rails routes とコマンドを打つとルーティングが表示される
ターミナル$ rails routes Prefix Verb URI Pattern Controller#Action new_user_session GET /users/sign_in(.:format) devise/sessions#new user_session POST /users/sign_in(.:format) devise/sessions#create destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy new_user_password GET /users/password/new(.:format) devise/passwords#new edit_user_password GET /users/password/edit(.:format) devise/passwords#edit user_password PATCH /users/password(.:format) devise/passwords#update PUT /users/password(.:format) devise/passwords#update POST /users/password(.:format) devise/passwords#create cancel_user_registration GET /users/cancel(.:format) devise/registrations#cancel「ターミナルだと見にくいなぁ」と思ったことはないでしょうか!
そんな方には別の方法で確認することができます!ブラウザでルートを確認する
URLに http://localhost:3000/rails/info/routes にアクセス!
ルートが増えるとターミナルでは確認し辛くなってくるのでコチラの方法はかなりオススメです!
ぜひお試しください!
- 投稿日:2020-11-19T22:08:27+09:00
[Rails]flashとflash.nowの違いについて![初心者]
flash[:notice]とは
主に
redirect_to
メソッドとセットで使用。
次のアクションまでエラーメッセージが表示されたままになる。なお、
redirect_to
メソッドとともに使用する場合、引数にnotice
というキーを渡すだけで、フラッシュメッセージを表示することができます。flash.now[:notice]とは
主に
render
とセットで使用。
次のアクションに移行した時点で、エラーメッセージが消えてしまう。使用上の注意点
render
とともにflash[:notice]
を使用してしまうと、画面遷移後のページ(renderの次のアクションで表示するページ)にも、エラーメッセージが残ったままになってしまう。
逆に、redirect_to
とセットでflash.now[:notice]
を使っても、アクションを経由しているので、エラーメッセージが表示されない。使用例
controllers/books.rbclass BooksController < ApplicationController def create @book = Book.new(book_params) @book.user_id = current_user.id if @book.save redirect_to books_path, notice: '投稿成功!!' #notice:で引数を渡しているため、flashの記載は省略 else flash.now[:alert] = "投稿失敗!!" render 'new' end end endまとめ
以下のように使い分けるべし!!!!
redirect_to パス名, notice: "エラーメッセージ"flash.now[:alert] = "エラーメッセージ" render :アクション名
- 投稿日:2020-11-19T22:00:47+09:00
[Rails][ransack] link_toで検索条件を作り、classをつける
link_toにclassをつけるのに苦戦したので備忘録もかねて。
ransackで検索機能を作っており、あるdivをクリックした時にあらかじめ決めておいた検索結果にリンクさせようとlink_toを使い苦戦。
最初に書いていたコード
<%= link_to controller: "shops", action: "index", q: { name_cont: '東京'}, class: "反映させたいクラス" do %> <div>このdivをクリックしたら「東京」の検索結果にとぶ</div> <% end %>こちらを参考に書きました。やってることはほぼ同じで、リンクテキストを踏ませるかdivを踏ませるかの違いです。
リンクテキストを指定する代わりにdo endで囲んでブロックにし、controller: ~ 東京 }
までがパスで、その後にclassを書いているというイメージです。エラーは出ずリンクは機能するものの、classが反映されず...
うまくいったコード
<%= link_to ({ controller: "shops", action: "index", q: { name_cont: '東京' }}, { class: "反映させたいクラス" }) do %> <div>このdivをクリックしたら「東京」の検索結果にとぶ</div> <% end %>最初に書いていたコードも文法的には間違いではないそうですが、classとパスの境目が曖昧でclassもURLの一部と見なされていたようなので、URL部分とその他の部分でハッシュを分けたらうまくいきました。
- 投稿日:2020-11-19T20:21:12+09:00
マイグレーションファイルのカラムを削除するには
今回はマイグレーションファイルを作成した後に、「あれ?このカラム使わないな、」と思った時にカラムを削除する方法を紹介したいと思います。
1. 仮のマイグレーションファイルを作成する
マイグレーションファイルを作る際のコマンド rails generate migration Add(カラム名)To(テーブル名) (カラム名):データ型筆者の例として
usersテーブルにstring型のnameカラムとimageカラムを作成したとします。
rails g migration AddNameToUsers name:string image:stringこのコマンドを打つと下記のようなマイグレーションファイルが作成されます。
class AddColumnsToUsers < ActiveRecord::Migration[5.2] def change add_column :users, :name, :string add_column :users, :image, :string end end2. カラムの削除
マイグレーションファイルを削除する際のコマンド rails generate migration Remove(カラム名)To(テーブル名) (カラム名):データ型とコマンドを打つとカラムを削除するための新たなマイグレーションファイルが作成されます。
開発を進めていく中でimageカラムやっぱりいらないな、と思ったら
rails g migration RemoveNameToUsers image:stringと打つことで
class RemoveColumnsToUsers < ActiveRecord::Migration[5.2] def change remove_column :users, :image, :string end endというマイグレーションファイルができます。
この後はrails db:migrateと打つことで無事usersテーブルのimageカラムは消えています。スキーマを確認してもらえるとカラムが消えているのが確認しやすいです。
番外編. 間違えて削除のコマンド打ってしまった場合の対処法(筆者の失敗例をもとに)
筆者が実際に削除用のマイグレーションのコマンドを打ち間違えてしまい
rails g migration RemoveNameToUsersと打ってしまいできてしまったマイグレーションファイルがこちらです↓
class RemoveColumnsToUsers < ActiveRecord::Migration[5.2] def change end end削除用のカラムが記載されていないファイルができてしまいました・
このファイルは削除しないといけません。まずは削除できる状態にあるか確認をします。bundle exec rake db:migrate:statusこのコマンドを打つとstatusがupになっていると思いますがこの状態だと削除することができません。
upをdownに変えてあげて削除できる状態にします。bundle exec rake db:migrate:down VERSION=MigrationIDから始まるファイル名を入れるこれでいまstatusがdownになっているのであとは
rm -rf db/migrate/MigrationIDから始まるファイル名を入れるこれでマイグレーションファイルの削除が完了したのでこの記事のカラムを削除するマイグレーションファイルを作成するところから初めて行けば大丈夫です。
いかがだったでしょうか?ぜひ参考にしていただけると幸いです!
- 投稿日:2020-11-19T20:15:34+09:00
【Rails】deviseが最低限使えるようになるためのポイント
インストール
Gemfilegem 'devise'$ bundle install $ rails g devise:install
config/initializers/devise.rb
とconfig/locales/devise.en.yml
の2つのファイルが作成され、以下のメッセージが表示される原文Depending on your application's configuration some manual setup may be required: 1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb: config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } In production, :host should be set to the actual host of your application. * Required for all applications. * 2. Ensure you have defined root_url to *something* in your config/routes.rb. For example: root to: "home#index" * Not required for API-only Applications * 3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example: <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> * Not required for API-only Applications * 4. You can copy Devise views (for customization) to your app by running: rails g devise:views * Not required *ざっくり翻訳以下の設定が必要だよ 1. config/environments/development.rbにデフォルトのURLを設定する。 書き方はこんな感じ↓ config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 上記の例は開発環境用だから、本番環境では実際のURLがセットされるようにする → 新規会員登録メール、パスワード再設定メールにサイトのURLを埋め込む時とかに使うっぽい。 2. config/route.rbにroot_urlをセットする(普通してる) 3. app/views/layouts/application.html.erbにflashメッセージの表示エリアを作る 書き方はこんな感じ↓ <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> 4. deviseが自動生成するページのデザインをカスタマイズしたい場合、rails g devise:viewsで生成されるフ ァイルを編集するユーザーテーブルを作成
$ rails g devise User # rails g devise モデル名
app/models/user.rb
の生成- マイグレーションファイルの作成
config/routes.rb
にdevise_for :users
という記述が追加され、deviseで使うルーティングが作成されるマイグレーションファイルには、デフォルトでメールアドレスやパスワードなど、いろんな項目が設定されている。
必要に応じてカラムを追加。ここではnameというカラムを追加。db/migrate/timestamp_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.2] def change create_table :users do |t| t.string :name # 追加 t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" t.string :reset_password_token t.datetime :reset_password_sent_at t.datetime :remember_created_at t.string :name t.timestamps null: false end add_index :users, :email, unique: true add_index :users, :reset_password_token, unique: true end end$ rails db:migratestrong parametersを設定
deviseのコントローラーは、ライブラリ側で用意されているので、直接修正できない。
だからdeviseのコントローラーに修正が必要なときは、application_controllerに書く。今回nameというカラムを追加したので、
configure_permitted_parameters
を上書きしてsignup時にnameを扱えるようにする。app/controllers/application_controller.rbclass ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? protected def configure_permitted_parameters # deviseで使われているstrong_parameterを上書き devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end endbefore_actionでログインチェック
application_controllerに
before_action :authenticate_user!
をセットして、ログインしないと見れないページにログインせずにアクセスした場合、ログインページにリダイレクトするように設定。app/controllers/application_controller.rbclass ApplicationController < ActionController::Base before_action :authenticate_user! # 省略 end※ログイン前に見れるページがある場合、そのページのcontrollerに
skip_before_action :authenticate_user!
を設定する。この時点で
http://開発環境のIP/users/signup
にアクセスすると、ユーザー登録画面が表示される
http://開発環境のIP/users/sign_in
にアクセスすると、ログイン画面が表示される
http://開発環境のIP/users/sign_out
にdeleteでアクセスすると、ログアウトするこれでひとまずユーザー登録、ログイン、ログアウト処理が完成。(※メール認証挟まないからまだ実運用はきつめ)
画面を編集する
deviseをインストールした時のメッセージ[4]にもあったようにviewファイルを生成してデザインや項目をカスタマイズする。
$ rails g devise:views
viewファイルがたくさん生成される。今回はユーザー登録時の画面にname項目を追加。
app/views/devise/registrations/new.html.erb<h2>Sign up</h2> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= render "devise/shared/error_messages", resource: resource %> <!-- #以下を追加 --> <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name, autofocus: true %> </div>ログインしているかどうかで表示を分ける
<ul> <% if user_signed_in? %> <li><%= link_to "ログアウト", destroy_user_session_path, method: :delete %></li> <% else %> <li><%= link_to "新規登録", new_user_registration_path %></li> <li><%= link_to "ログイン", new_user_session_path %></li> <% end %> </ul>
form_for
をform_with
に変更deviseのformはデフォルトで
form_for
を使っているのでより新しい書き方のform_with
に修正app/views/devise/registrations/new.html.erb<h2>Sign up</h2> <%= #削除 form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= form_with model: @user, url: user_registration_path, id: 'new_user', class: 'new_user', local: true do |f| %>app/views/devise/sessions/new.html.erb<h2>Log in</h2> <%= #削除 form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> <%= form_with model: @user, url: user_session_path, local: true do |f| %>ログイン後に遷移する画面を変更
デフォルトだと
root_path
に遷移するようになっているようです。メソッドを上書きしてuser_path(user)
に設定してみます。app/controllers/application_controller.rbclass ApplicationController < ActionController::Base #省略 def after_sign_in_path_for(resource) user_path(resource) end #省略 endユーザー詳細ページを作成
これはdevise無視して勝手にcontrollerやviewを作る。編集とか削除とかも同じ、deviseは気にしないで作る。
$ rails g controller users showapp/controllers/users_controller.rbclass UsersController < ApplicationController def show @user = User.find(params[:id]) end endconfig/routes.rbresources :users, only: %i[show]app/views/users/show.html.erb<h2>ユーザー詳細</h2> <p><%= @user.name %></p> <p><%= @user.email %></p>ヘルパーいろいろ
current_user # ログイン中のユーザーのオブジェクト user_signed_in? # ログインしてるか確認
- 投稿日:2020-11-19T20:04:45+09:00
MVCとは
- 投稿日:2020-11-19T19:25:31+09:00
1、Railsアプリケーション作成〜下準備
※習った範囲の最低限の情報しか書いていません。
※間違いがあればご指摘お願いします。アプリケーションの作成
ターミナル% cd ~/projects % rails _6.0.0_ new 名前 -d mysql % cd 名前 Config/datebase.yel/utf8に変更 % rails db:create新規アプリケーションのリポジトリを作成
GitHub Desktop
左上の「Current Repository」→「Add」→「Add Existing Repository...」→ディレクトリを選択し、追加
Gitで不要なファイルを除外しよう
新しいファイルを作成.gitignoreファイルの一番下の行に、.Ds_Store
設定を反映させる
ターミナル% git config --global core.excludesfile ~/.gitignore_globalturbolinksが読み込まれないように編集
アプリケーションにおいて、画面遷移を高速化するGem。
turbolinksは、アプリケーションを作成すると標準で有効になっています。
JavaScriptファイルの処理が正常に動作しないことを防ぐため、ここではturbolinksを無効化しておきましょう。app/javascript/packs/application.js// 省略 require("@rails/ujs").start() // require("turbolinks").start() //この行を削除する require("@rails/activestorage").start() require("channels") // 省略first commitする
リモートリポジトリの作成
右に青色のボタンで「Publish repository」
「Keep this code private」にチェックが入っていると非公開になってしまうので、チェックを外して、「Publish Repository」ロボコップの導入
Gemfilegroup :development do gem 'rubocop', require: false endターミナル% bundle install % touch .rubocop.ymlrubocop.ymlAllCops: # 除外するディレクトリ(自動生成されたファイル) # デフォルト設定にある"vendor/**/*"が無効化されないように記述 Exclude: - "vendor/**/*" # rubocop config/default.yml - "db/**/*" - "config/**/*" - "bin/*" - "node_modules/**/*" - "Gemfile" # 1行あたりの文字数をチェックする Layout/LineLength: Max: 130 # 下記ファイルはチェックの対象から外す Exclude: - "Rakefile" - "spec/rails_helper.rb" - "spec/spec_helper.rb" # RSpecは1つのブロックあたりの行数が多くなるため、チェックの除外から外す # ブロック内の行数をチェックする Metrics/BlockLength: Exclude: - "spec/**/*" # Assignment: 変数への代入 # Branch: メソッド呼び出し # Condition: 条件文 # 上記項目をRubocopが計算して基準値を超えると警告を出す(上記頭文字をとって'Abc') Metrics/AbcSize: Max: 50 # メソッドの中身が複雑になっていないか、Rubocopが計算して基準値を超えると警告を出す Metrics/PerceivedComplexity: Max: 8 # 循環的複雑度が高すぎないかをチェック(ifやforなどを1メソッド内で使いすぎている) Metrics/CyclomaticComplexity: Max: 10 # メソッドの行数が多すぎないかをチェック Metrics/MethodLength: Max: 30 # ネストが深すぎないかをチェック(if文のネストもチェック) Metrics/BlockNesting: Max: 5 # クラスの行数をチェック(無効) Metrics/ClassLength: Enabled: false # 空メソッドの場合に、1行のスタイルにしない NG例:def style1; end Style/EmptyMethod: EnforcedStyle: expanded # クラス内にクラスが定義されていないかチェック(無効) Style/ClassAndModuleChildren: Enabled: false # 日本語でのコメントを許可 Style/AsciiComments: Enabled: false # クラスやモジュール定義前に、それらの説明書きがあるかをチェック(無効) Style/Documentation: Enabled: false # %i()構文を使用していないシンボルで構成される配列リテラルをチェック(無効) Style/SymbolArray: Enabled: false # 文字列に値が代入されて変わっていないかチェック(無効) Style/FrozenStringLiteralComment: Enabled: false # メソッドパラメータ名の最小文字数を設定 Naming/MethodParameterName: MinNameLength: 1コピペする。
- 投稿日:2020-11-19T19:16:16+09:00
RailsでDBの値関係を修正したい時
いつも記憶が曖昧になるので、メモ用に投稿。
railsでアプリを作成していた際、カラム名を間違えた!、DBを作り直したい!などの時に使用するコマンド。
rails db:rollback
,rails db:reset
,rails db:migrate:reset
の3つ解説1
rails db:rollback
migrateの履歴を1つもどす。
migrateの実行履歴は以下のように
up ~~~~~
up ~~~~~
up ~~~~~
と古い順になっている。それを一つ戻すコマンド。実行すると、
up ~~~~
up ~~~~
down ~~~~
と一つ実行前に戻る。その後、誤ったテーブル情報などを修正し、migrateすればOK!
*migrate
はupステータスの状況で修正し、rails db:migrate
を実行しても、変化はないです。すでに所定のmigrateファイルが存在すると扱われるためです。
解説2
rails db:reset
DBを一旦dropし、schemaファイル(DBの設計図)を元にDBを再作成するコマンド。
なので、このコマンド前にmigrateionファイルを修正して、実行しても、DBの内容が書き換わらない。
解説3
rails db:migrate:reset
DBを一旦dropし(ここまではrails db:reset
と同じ)、古い順からmigrationファイルを実行する。
rails db:reset
とよく似ているが引用元が違うので、その時の希望によって使い分けが要ります。
以上、カラム名やDBを再生成したい時に使う3のコマンドです。
記憶が確かであれば、上記3つ全て格納データが消えたと思います、、、
間違っているなどあれば是非コメント下さい!
- 投稿日:2020-11-19T18:43:16+09:00
RSpecのテストにおいて期待されるエラーが出力されなかった話
はじめに
画像とテキストを投稿するRailsアプリケーションで、RSpecによるテストを行っていた。
アプリケーションの設計ではテキストがない場合はツイート(投稿)が出来ないようになっている。テスト内容
テキストなしの投稿は保存できない、という内容のテストで以下のコードを実行した。
spec/models/tweet_spec.rbit 'テキストがないとツイートは保存できない' do tweet = Tweet.new(text: "",image: "https://i.imgur.com/GL7igry.png") tweet.valid? expect(@tweet.errors.full_messages).to include("Text can't be blank") endところが、tweet.valid?はtrueを返しており、"Text can't be blank"のエラーが出力されなかった。
[1] pry(#<RSpec::ExampleGroups::Tweet::Nested::Nested_2>)> @tweet.valid? => true [2] pry(#<RSpec::ExampleGroups::Tweet::Nested::Nested_2>)> @tweet.errors.full_messages => []ここでエラーが出ないということは、アプリケーションの作成段階でミスがあったと考え、Tweetのモデルを見直したところvalidationに関する記述がなかった。
解決法
以下を書き足してもう一度テストを行ったところ、無事"Text can't be blank"のエラーが出力された。
app/models/tweet.rbvalidates :text, presence: true
- 投稿日:2020-11-19T18:42:56+09:00
Rails ゲストログイン機能
はじめに
今回はゲストログイン機能について学習していきたいと思います。
以下,
Devise
を使用している前提とし,トップページがroot 'homes#index'
で設定されていることとします。流れ
①ゲストユーザーをデータベースから取り出す
②そのユーザーをログインさせる
③フラッシュメッセージを出してトップページへリダイレクト1.ゲストログイン機能の実装方法(簡易版)
ゲストログイン用のアクションを作成
config/routes.rb# 以下を追加 post '/homes/guest_sign_in', to: 'homes#new_guest'app/controllers/homes_controller.rb# 以下を追加 def new_guest user = User.find_or_create_by!(email: 'guest@example.com') do |user| user.password = SecureRandom.urlsafe_base64 # user.confirmed_at = Time.now # Confirmable を使用している場合は必要 end sign_in user redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。' endapp/views/homes/index.html.erb# 以下を追加 <%= link_to 'ゲストログイン(閲覧用)', homes_guest_sign_in_path, method: :post %>ポイントはDeviseのsign_inメソッドを利用することです。
find_byではなく,find_or_create_byを利用しています。これにより,ゲストユーザーをあらかじめ作成する手間を省けます。また,ゲストユーザーを削除されてゲスト機能が動作しなくなるリスクも回避できます。(コードではゲストユーザーがあればそれを取り出す。なければ新しく作成する設定をしている)
パスワードを特定されると,ユーザー編集ページからメールアドレス・パスワードを変更される可能性があるため,パスワードはランダム文字列にしています。
user.password = SecureRandom.urlsafe_base64
バリデーションの影響でゲストユーザーを作成できない場合は,エラーを発生させるように設定しております。
- 例えば, name が必須であれば,user.name = "take" などを追加して下さい
2.ゲストログイン機能の実装方法
上記の実装方法は理解しやすいものの,ゲストログイン機能を
HomesController
に任せることには違和感があります。本来は,ログイン機能同様,Devise
のSessionsController
に任せるべきでしょう。例えば次のように実装すると,より自然なものになるのではないでしょうか。
ゲストログイン機能の設定
まず,
SessionsController
に新しいアクションnew_guest
を準備します。config/routes.rb# 以下を追加 devise_scope :user do post 'users/guest_sign_in', to: 'users/sessions#new_guest' endアクションnew_guestを設定するため,
app/controllers
にusers
ディレクトリを作成し,その中に次のsessions_controller.rb
を作成します。ゲストユーザーを探す or 作成する機能はUser.rb
に移動させるとコントローラーがスッキリします。app/controllers/users/sessions_controller.rbclass Users::SessionsController < Devise::SessionsController def new_guest user = User.guest sign_in user redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。' end endapp/models/user.rb# 以下を追加 def self.guest find_or_create_by!(email: 'guest@example.com') do |user| user.password = SecureRandom.urlsafe_base64 # user.confirmed_at = Time.now # Confirmable を使用している場合は必要 end endトップページにゲストログインボタンを用意する場合は,以下を追加すればOKです。(スタイルは各自で追加して下さい)
app/views/homes/index.html.erb# 以下を追加 <%= link_to 'ゲストログイン(閲覧用)', users_guest_sign_in_path, method: :post %>ゲストユーザーを削除できないようにする
上記の実装方法ならば,仮にゲストユーザーを削除されたとしても,ゲスト機能が動作しなくなることはありません。
ですが,例えば2名の方が同時にログインされている状態で,片方の方がゲストユーザーを削除しますと,もう片方の方も強制的にログアウトさせられてしまいます。ポートフォリオの場合はレアケースだと思いますが,念のためゲストユーザーを削除できないように設定しておきましょう。
ゲストユーザーが削除機能を使用できないようにするには,
registrations.rb
を編集する必要があります。まずは,ルーティングを変更します。config/routes.rb# devise_for :users を次に置き換える devise_for :users, controllers: { registrations: 'users/registrations' }
destroy
アクションの動作前に,メールアドレスがゲストユーザー用になっていないかチェックするように設定します。
ゲストユーザーならばフラッシュを出した上でトップページにリダイレクトさせるように設定しています。app/controllers/users/registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController before_action :check_guest, only: :destroy def check_guest if resource.email == 'guest@example.com' redirect_to root_path, alert: 'ゲストユーザーは削除できません。' end end endゲストユーザーがパスワードやメールアドレスを編集できないようにする
上記の実装ならば,「ユーザー編集機能」や「パスワード再設定機能」によりメールアドレス・パスワードを変更される可能性は非常に低いですし,仮に変更されたとしてもポートフォリオならば問題にならないかと思います。
それでも,「ゲストユーザーのメールアドレス・パスワードを絶対に変更されたくない!」という場合は,更に次のような設定をすればOKです。
削除機能を止めるのと同じ手法で,ゲストユーザーがメールアドレス・パスワードを編集できないように設定します。
app/controllers/users/registrations_controller.rb- before_action :check_guest, only: :destroy + before_action :check_guest, only: %i[update destroy] #変更 - redirect_to root_path, alert: 'ゲストユーザーは削除できません。' + redirect_to root_path, alert: 'ゲストユーザーの変更・削除はできません。' #変更パスワード再設定メールの送信機能を止めるには,
passwords_controller.rb
のcreate
アクションの動作前にチェックすればOKです。まずはルーティングを変更します。config/routes.rb# devise_for :users, controllers: { # registrations: 'users/registrations' # } # を次に置き換える。(,の付け忘れに注意!) devise_for :users, controllers: { registrations: 'users/registrations', passwords: 'users/passwords' }パスワード再設定ページのフォームに入力されたメールアドレスはparams[:user][:email]で受け取れるので,これを利用してゲストユーザーを特定します。
メールアドレスは大文字が小文字に変換されて保存されているため,downcaseメソッドが必要です。app/controllers/users/passwords_controller.rbclass Users::PasswordsController < Devise::PasswordsController before_action :check_guest, only: :create def check_guest if params[:user][:email].downcase == 'guest@example.com' redirect_to root_path, alert: 'ゲストユーザーの変更・削除はできません。' end end end【補足】 check_guestがほぼ同じ内容ですので,次のようにまとめてしまってもOKです。
app/controllers/application_controller.rb# 次を追加 # registrations_controller.rb と passwords_controller.rb の check_guest は削除 def check_guest email = resource&.email || params[:user][:email].downcase if email == 'guest@example.com' redirect_to root_path, alert: 'ゲストユーザーの変更・削除はできません。' end end最後に
一応これでゲストログイン機能の完成です。
何か間違っているところがあればご教授していただけると幸いです。
- 投稿日:2020-11-19T18:17:39+09:00
プログラミング初心者がRailsで日記SNS作ってみた
はじめに
日々の学びをメモして、良い感じのやつだけ共有できるようなアプリが欲しいと思ったので自作してみました.
現在、α版としてHerokuにアップしているので覗いていただけると嬉しいです。アプリ名
Output App
リンク
URL : https://outputapp.herokuapp.com/
GitHub : https://github.com/tatsuhiko-nakayama/output_app機能紹介
投稿画面
- ハッシュタグを入力することで情報源を共有できる
- デフォルトが非公開なのでメモとして使える
- closedボタンをopenに切り替えて公開する
- マークダウンが使える
詳細画面
- ハッシュタグ、参考URLへリンク
- LIKE機能
- フォロー機能
- コメント機能
ユーザーページ
- 各ステータス表示&一覧リンク
- ユーザーの投稿(open)一覧
ここまで作ってみて
所感
あまり難しい機能は実装せず、初心者でも自力でできる範囲でいろいろと実装してみました。
特にJavaScriptが勉強不足でAjaxがうまくいかなったり、改善したいところが多々あります。
次のステップ
テスト公開中なので、ユーザーさんの声を聞きながら使いたくなるサービスにどんどん改良していきます。
「アウトプット」と聞くと意識高い感じがして使いにくいのかなと思いつつも、特徴のない「日記アプリ」や「SNS」では既存サービスでええやんと思ったり。
その辺、試行錯誤しながら楽しく作り込んでいきたいなと思います。
作者スペック
- 2020年8月からプログラミングを始める
- 2ヵ月間、某プログラミングスクールで学習
- 前職は営業(8年在籍)
- 現在はニート生活を満喫しつつ、Web系自社開発企業で働きたいと思っている
- 趣味は料理とYoutube
制作期間
11/1〜現在
使用技術
- Ruby / Rails
- JavaScript / jQuery
- Amazon S3
- AWS (これから)
- Docker(これから)
- Circle CI(これから)
おわりに
テストユーザーのお願いをして使ってもらって、そこからが開発だなと思いました。
技術ももちろん大事だけど、ユーザーさんにとって使いやすかったり、感動する体験にどうやってつなげていくのか。
そこを考えるのはとても楽しいけれど、同じくらい悩ましい笑
ご覧いただけた方いらっしゃいましたら、ぜひ感想・アドバイスお願いします。
✔︎
- 投稿日:2020-11-19T18:04:02+09:00
jQueryを使ったページスクロールの作成方法まとめ
フロントエンドをよりみやすくしようと思ったところで、よく企業のページに行くと、クリックでスクロールするような動きがあり、いいなと思ったので作成してみました。
環境
ruby 2.6.5
rails 6.0.3導入の流れ
Step1 jQueryのインストール
Step2 webpackの編集
Step3 jQueryの呼び出し
Step4 スクロールをできるようにする記述を書くStep1 JQueryのインストール
railsを作成した後にターミナルにてコマンドを打ち込みます。
% yarn add jqueryStep2 webpackの編集
railsアプリケーション内にwebpackがあるので、jQueryが使えるようにするため、そこのファイルを記述し直します。
config/webpack/environment.js//元からある記述 const { environment } = require('@rails/webpacker') //ここから記述していく------------------------------ const webpack = require('webpack') environment.plugins.prepend('Provide', new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery', jquery: 'jquery', }) ) //記述ここまで----------------------------------- //元々ある記述 module.exports = environmentjQueryの呼び出し
application.jsにjQueryを呼び出します。
app/javascript/packs/application.js//コメントアウトでいろいろ書かれている require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") require('jquery') //この行を追加 //コメントアウトでいろいろ書かれているStep1からStep3でrailsにjQueryを導入しました。
Step4 スクロールをできるようにする記述を書く
scroll.jsファイルを作成し、記述していきます。
scroll.jsはjavascriptのディレクトリ内に作成します。app/javascript/scroll.js$(function(){ // #で始まるリンクをクリックしたら実行されます。 $('a[href^="#"]').click(function() { // スクロールの速度 const speed = 400; // ミリ秒で記述 ここで定義している const href= $(this).attr("href"); const target = $(href == "#" || href == "" ? 'html' : href); const position = target.offset().top; $('body,html').animate({scrollTop:position}, speed, 'swing'); return false; }); });上から3行目の部分に
$('a[href^="#"]').click(function()とありますが、JavaScriptでこのように設定しているため、HTMLでリンクを指定する場合は"#hoge"といった形で、指定してやる必要があります。
scroll.jsを作成後もう一度application.jsに記述をします。
app/javascript/packs/application.js//コメントアウトでいろいろ書かれている require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") require('jquery') //先ほど追加したやつ require('../scroll') //この行をさらに追加 //コメントアウトでいろいろ書かれているHTMLは下記のような流れになると思います。(かなり省略しています)
html.erb<a href="#hoge">一覧へ</a> <!--リンク--> <div id= 'hoge' class='fuga'>一覧</div> <!--飛んでいく先-->最後に
スクロールページの記述方法は他のサイトにも乗っていますので下記のURLを貼っておきます。
railsのバージョンが変わるごとにstep3までの導入方法が変わるのかなあと思っています。(自分が調べたサイトは古いバージョンで本当にこのやり方でいいのかと疑心暗鬼になっていました。)jQueryでスムーススクロールを実装する方法【初心者向け】
https://techacademy.jp/magazine/9532RailsでjQueryのページスクロール設定
https://qiita.com/taKassi/items/c1e2b54138744afe8b6d徹底解説!スムーススクロールをjQueryで実装する方法
https://changeup.tech/article/jquery-smooth-scroll/
- 投稿日:2020-11-19T17:39:30+09:00
【ざっくり解説】介護記録アプリを作ってみた【未経験/PF】
はじめに
前職で「こんなのあったらいいなぁ…」と思っていたアプリを、就活用のPFとして作ってみました。色々と改善点はありますが、形になったので一旦まとめてみます。
前半では私が作ったPFの詳細を、後半ではこれからPFを作る初学者の皆さんに向けたお節介を、それぞれ書いています。
これからPFを作ろうとしている初学者の皆さんの参考になれば幸いです。
目次
1.PFの概要
2.こだわったポイントと目的
3.苦労したこと・コード
4.これからPFを作る初学者へアドバイス
まとめ1.PFの概要
介護事業所向け記録アプリケーション 『Sup-App(サプアップ)』
URL : https://www.sup-app.net
GitHubRepo : https://github.com/k-kudo-hub/sup_app開発環境
- Ruby:2.6.5, Rails:6.0.0
- webpacker(css/Javascrict)
- ngix,puma(sockets通信)
- Docker(ローカル環境)
- Circleci(自動テスト)
- Circlecd(自動デプロイ)
- Rspec
制作期間
おおよそ2ヶ月、所要時間は500時間くらい…
ざっくり機能紹介
①記録機能
お客様ごとに記録を時系列で表示します。
記録はパッと見てわかるアイコンにし、実施/非実施で色が切り替わります。
②報告書作成機能
事故報告書を作成することができます。
作成した事故報告書をCSV形式で出力できるようにしました。
③チャット機能
テキストと画像で簡単なチャットを行うことができます。
タグをつけることができて、タグによってメッセージの色が変化します。
(UIがひどい…笑)
④ルーム機能
お客様ごとのチャットルームを一覧で表示します。
チャット画面に飛べるほか、お客様詳細や記録の実施/非実施切り替えなども行えます。
⑤お客様(入居者)管理機能
お客様情報をウィザード形式のフォームから登録することができます。
お客様詳細(画像)からは、お客様の情報が参照できるほか、フォローや編集なども行うことができます。
⑥ユーザー(スタッフ)管理機能
スタッフの情報を登録できます。トップページからは簡単ログインが可能です。
設定した資格や職位によって、一部の機能を制限します。
⑦お客様フォロー機能
お客様をフォローすることができます。
フォローしたお客様は「マイページ」から確認することができます。
⑧お客様検索機能
トップページの検索フォームから、「名前」「フリガナ」「居室番号」でお客様を検索することができます。
名前とフリガナは部分一致(あいまい)検索、居室番号は完全一致検索です。
2.こだわったポイントと目的
①機能の数
機能の数と各機能のボリュームには最も拘りました。
それぞれの機能が薄いものにならないよう、実際にアプリが使われる場面を想定しながら、各機能を充実させました。②ユーザビリティ
自分自身、元介護士ではありますが、独りよがりのアプリにならないよう複数人の現役介護士にフィードバックをいただきながらアプリを作成しました。
とてもシビアな意見をいただいたので、実装には苦労しました…?
しかし、その分高いユーザビリティを実現できたのではないかと思います。③モダンなインフラ技術
SIerではあまり用いられないかもしれませんが、インフラの学習としてAWS、Docker、CircleCIなどのモダンな開発環境を整えることを意識しました。
知識がないため手探りの実装になり、苦戦を強いられましたが、理解を深められたように思います。3.苦労したこと・コード
①本番環境/開発環境による動作の違い
本番環境にデプロイしたところ、記録を保存する際に時間が9時間分ずれてしまうことがわかりました。DBや環境設定を疑いましたが、結局根本的な解決には至らず…。
力業にはなってしまいましたが、 こちらの記事にまとめた内容で無理やり9時間のズレを修正し、なんとか本番環境でも問題なく稼働するようになりました。
②機能の作り込み、実用性の追求
ウィザード形式の登録フォーム
開発序盤で知識不足だったこともありますが、「sessionを用いた3ページのウィザード形式フォーム」はものすごい時間がかかりました。
(画像はbefore_actionなどを一部省略しています。)clients_controller.rb# お客様基本情報の登録(1ページ目) def create @client = Client.new(client_params) render :new and return unless @client.valid? session[:client_data] = { client: @client.attributes } @detail = @client.build_detail render :new_detail end # お客様医療情報の登録(2ページ目) def create_detail @detail = Detail.new(detail_params) render :new_detail and return unless @detail.valid? session[:detail_data] = { detail: @detail.attributes } @client = Client.new(session[:client_data]['client']) @caregiver = @client.build_caregiver render :new_caregiver end # お客様介護情報の登録(3ページ目) def create_caregiver @client = Client.new(session[:client_data]['client']) @detail = Detail.new(session[:detail_data]['detail']) @caregiver = Caregiver.new(caregiver_params) render :new_caregiver and return unless @caregiver.valid? @client.save session[:client_data].clear @detail.client_id = @client.id @detail.save session[:detail_data].clear @caregiver.client_id = @client.id @caregiver.save render :create_caregiver endCSV形式の報告書データ出力機能
ウィザードフォームの他に、CSV形式の出力機能で差別化を図っています。
これも今まで扱ったことはありませんでしたが、「現場にあったら便利かもしれない…」と思い実装してみました。index.csv.rubyrequire 'csv' require 'nkf' csv_data = CSV.generate do |csv| csv << %w[id 名前 性別 生年月日 記録者 発生時刻 発生場所 種別 単独/介助 程度 事故の内容 事故対応 連絡 通院・入院した病院 報告説明日 説明担当者 説明内容 再発防止策] @report_month.each do |report| csv << [ report.id, report.client.name, report.client.sex.name, report.client.birth, User.find(report.user_id).name, report.occ_time, report.place.name, report.genre.name, Re.find(report.res_id).name, report.level.name, report.content, report.coping, report.contact.name, report.hospital, report.desc_date, User.find(report.desc_user).name, report.desc_content, report.count_content ] end end NKF.nkf('--sjis -Lw', csv_data)③UI/UX
ビューのセンスが皆無のため、UI/UXはなかなかひどい感じになりました…。
加えてVue.jsやReactといったモダンなフロント技術をキャッチアップしていなかったため、ビューに特徴を持たせることができませんでした…現在はVue.jsを学習しつつ、MaterialDesignについて理解を深めています。
フロント技術をマスターして,イケてるビューを作れるようになりたいです4.これからPFを作る初学者へ【私みたいになるな】
①題材選びのポイント
希望の就職先に合わせて作る
この記事で最も伝えたいことです。PFは希望する企業にある程度合わせたものを作りましょう。
例えばWeb系企業を目指すのであれば、toC(カスタマー向け)で、システム感がない方が良いです。技術的にもある程度はモダンな技術を用いた方が評価につながるでしょう。
逆にシステム開発を目指すのであれば、システムやツールに寄せた方がいいでしょう。前職での経験を活かしたアプリはオリジナリティが出ますので、評価を得やすくなります。
私はPFを作り始めた頃、Web系とSIerの違いもよくわかっていませんでした。
業界や業種についての理解がないまま、「作りたいもの」を作ってしまった私は、Web系志望だったにもかかわらず業務システムを開発してしまう愚行を犯しました…。まずは志望する業種を決めて、それに合わせてPFの題材を決めていきましょう。
「作りたいもの」を作る
「いやお前、さっき業界や業種に合わせろって言うとったやないか」と突っ込まれてしまいそうですが、作りたいものを作るのも非常に大切なのです。
作りたいものがわからず既存サイトのクローンを作成し、面接で苦労した話をよく聞きます。
「なんでこのアプリを作ろうと思ったの?」という質問に、熱意を持って答えられないからです。業界・業種にターゲティングするのは重要ですが、それと同じくらいに大切なのがオリジナリティです。面接官は飽きるほどPFをみていますので、大方のPFを見飽きています。
PFを通して熱意を伝えるには、「作りたいもの」を作ることで生まれるオリジナリティが必要不可欠なのです。
【大前提】スクールの課題はPFではない
当たり前ですが、スクールの課題で作るアプリはPFになりません。
メルカリクローンや、ハリボテのチャットアプリ、画像投稿アプリなど、採用担当者は見ればわかります。みんなが作るアプリに価値はありません。
そんなアプリで就職活動を戦っても、スクールの後輩が迷惑するだけです。
スクールの課題で就活に臨むのは、恥ずかしいのでやめましょう。②見てもらえる工夫をする
【経験】PFはほとんど見てもらえない
いくつかの企業様へ応募しましたが、PFはほとんど見てもらえないのが現実です。
面接におけるPFは、あなたを構成する要素のひとつに過ぎません。
PFをいくら作り込んだところで、履歴書(志望動機、学歴、職歴)や職務経歴書などがおざなりではPFに目を通されることすらありません。PFまで到達してもらうためにも、履歴書や職務経歴書の作り込みを欠かさないようにしましょう。
最低限つけておくべき機能・実装
● 【機能編】ゲストユーザーログイン(簡単ログイン)機能
これがないとユーザー名やパスワードを入力する手間がかかり、担当者は萎えてしまいます。
必要不可欠な機能ですが、実装自体は非常に簡単なので、迷わず実装しましょう。ー私が参考にした記事ー
【学習アウトプット2】離脱率を下げる!かんたんログイン機能の実装● 【デプロイ編】herokuよりAWS
herokuにアップしている方は自分の周りにもかなり多い印象ですが、AWSへデプロイすることでEC2やVPCといったAWSの知識を習得することにつながります。
Web系自社開発企業を目指す方であれば、AWSへのデプロイは必須と言えるでしょう。
ー私が参考にした記事ー
(下準備編)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで● 【技術編】jQueryよりJavaScript
jQueryは2006年にリリースされたJavascriptのライブラリで、高いシェアを誇っていた過去があります。
しかし2020年現在、多くのWeb系自社開発企業はReactやAnguler.js、Vue.jsを採用しており、jQueryは下降の一途を辿っています。
もしjQueryを使っている or 使おうとしているのであれば、どのJSライブラリにも共通して必要なJavaScriptで実装を行い、JavaScriptの記述に慣れておくことをお勧めします。
可能であればReactやAnguler.js、Vue.jsを用いたフロント実装をしておくことで、他の方と差別化を図ることができるでしょう。
READMEの活用
就活におけるREADMEは、アプリをPRする絶好の場所です。
「ポートフォリオのレポジトリURLを送ってください」と採用担当者に言われることはよくあります。最初に目に映ることになるREADMEがショボければ、アプリもショボいんだろうなと思われてしまいかねません。
READMEはきっちりと記述しておきましょう。
大したものではありませんが、私のREADMEをひとつの参考にしてください?https://github.com/k-kudo-hub/sup_app
まとめ
長い記事になりましたが、最後までご覧いただきありがとうございました。
就職活動を控え、これからPFを作る皆さんの参考になれば幸いです。
PFに関する資料
インフラ構成図
ER図
引用・参考にさせていただいた記事
- 投稿日:2020-11-19T17:33:52+09:00
Rails いいね機能
conslerails g model Favorite user:references post:references rails db:migrate一人のユーザーはいいねを一回までにする為
favorite.rbclass Favorite < ApplicationRecord belongs_to :user belongs_to :post validates_uniqueness_of :post_id, scope: :user_id endpost.rbhas_many :favorites, dependent: :destroyuser.rbhas_many :favorites, dependent: :destroyconsolerails g controller favoritespostsにネストする
routes.rbresources :posts do resource :favorites, only: [:create, :destroy] endrails routes すると、
post_favorites
DELETE /posts/:post_id/favorites favorites#destroy
POST /posts/:post_id/favorites favorites#create
と表示される。
:post_idの部分をcontrollerで取得favorites_controller.rbclass FavoritesController < ApplicationController def create @favorite = current_user.favorites.create(post_id: params[:post_id]) redirect_back(fallback_location: root_path) end def destroy @post = Post.find(params[:post_id]) @favorite = current_user.favorites.find_by(post_id: @post.id) @favorite.destroy redirect_back(fallback_location: root_path) end endすでにいいねしてるかどうかを判定するメソッド
User.rbhas_many :favorites, dependent: :destroy def already_favorited?(post) self.favorites.exists?(post_id: post.id) endview<% if current_user.already_favorited?(@post) %> <%= link_to post_favorites_path(@post), method: :delete do %> <i class="fas fa-heart" style="color: red;"></i> <% end %> <span style="color: red;"><%= @post.favorites.count %></span> <% else %> <%= link_to post_favorites_path(@post), method: :post do %> <i class="far fa-heart"></i> <% end %> <%= @post.favorites.count %> <% end %>引数の@postはDELETE /posts/:post_id/favoritesの:post_idに対応する。
- 投稿日:2020-11-19T16:50:20+09:00
[Rails]パンくず機能
はじめに
画面遷移をわかりやすくするために、gretelというgemを使用してパンくず機能を実装しました。
目次
- 1. gretelのインストール
- 2. パンくずの設定
- 3. ビュー
1. gretelのインストール
gretelというgemを用いることで、リンクを設置したリストを画面に表示させるパンくずを実装することができます。
gemfilegem "gretel"ターミナル
bundle install2. パンくずの設定
パンくずの親子関係を設定するファイルを作成します。
ターミナル
rails g gretel:install例
config/breadcrumbs.rbcrumb "現在のページ名(表示させるビューにもページ名記述)" do link "パンくずリストでの表示名", "アクセスしたいページのパス" parent :親要素のページ名(前のページ) endconfig/breadcrumbs.rbcrumb :root do link "ホーム", root_path end crumb :posts do link "クチコミ一覧", posts_path parent :root end crumb :post_show do |post| link post.name, post_path(post) parent :posts end crumb :user do |user| link user.nickname, user_path(user) parent :root end # 親カテゴリーのパンくず crumb :parent_category do |category| category = Category.find(params[:id]).root link "#{category.name}", search_post_path(category) parent :root end # 子カテゴリーのパンくず crumb :child_category do |category| category = Category.find(params[:id]) # 表示しているページが子カテゴリーの一覧ページの場合 if category.has_children? link "#{category.name}", search_post_path(category) parent :parent_category # 表示しているページが孫カテゴリーの一覧ページの場合 else link "#{category.parent.name}", search_post_path(category.parent) parent :parent_category end end # 孫カテゴリーのパンくず crumb :grandchild_category do |category| category = Category.find(params[:id]) link "#{category.name}", search_post_path(category) parent :child_category end crumb :post_new do link "新しいクチコミ投稿", new_post_path parent :root end crumb :name_search do |search| if search == "" link "クチコミ検索結果", name_search_posts_path else link "「#{search}」のクチコミ検索結果", name_search_posts_path end parent :posts endカテゴリーのパンくずリストは、親カテゴリー > 子カテゴリー > 孫カテゴリーになるように設定しています。
子カテゴリーの処理については、孫カテゴリーのページから呼び出した場合(孫カテゴリーを選択した時)と、子カテゴリーのページから呼び出した場合(子カテゴリーを選択した時)の2つの条件で処理を変えるようにします。3. ビュー
今回はパンくずを表示させたいところだけに適応したかったので、部分テンプレートを作成しました。
app/views/shared/_breadcrumbs.html.erb<div> <%= breadcrumbs separator: " › " %> </div>separator: " › “はパンくずの区切りである「>」を示します。
投稿一覧のindexファイルだけ載せておきます。
app/views/posts/index.html.erb~略~ <% breadcrumb :posts %> <%= render "shared/breadcrumbs" %> ~略~breadcrumb :postsはbreadcrumbs.rbで設定したページ名を記述しています。
参考リンク
- 投稿日:2020-11-19T16:06:58+09:00
IMGkit での画像生成が終わらない件について
前提
この問題を解決する際に非常に時間がかかった + 日本語記事がなく英語記事でもなかなか解決策が出てこなかったので同じ問題につまづいている方にむけて共有すべく記載しています。
言葉の定義など曖昧な理解で書いている箇所があることはご理解ください。今回の実装ケースは、Controllerアクション内で画像生成を行うことを前提としています。
IMGkitとは
Railsで提供されている画像生成用のgem。 詳細は以下。
https://github.com/csquared/IMGKit今回の問題
ローカル・staging環境で画像を生成する際に生成処理が完了せず、かつログも出力されない。
stagingに至ってはサーバーダウンしてしまいサービス全体に影響が出てしまう。ローカルでの発生手順
- rails s でサーバーを立ち上げ、画像生成処理を行う(1度目はうまくいく)
- ソースを微修正して保存
- 画像生成処理を再度実行
- 現象発生(処理が完了しない)
原因
rails serverをシングルプロセスモードで立ち上げているため。
解説
- クライアントがリクエストを送信し、プロセスAがそのリクエストをハンドリング.
- プロセスAのなかで、IMGkit(wkhtmltoimage)が画像生成するためのview renderリクエストをrailsサーバーに送り、処理の完了を待つ。
- IMGkitが送ったリクエストはプロセスAの処理が完了するまで待機するが、IMGkitの処理完了を待っているためプロセスAの処理も完了しない。
- デッドロックが発生し処理が完了しない。
参考URL
IMGkitのgithubのissueに解決方法が記載
https://github.com/csquared/IMGKit/issues/84解決方法
puma.rbに下記設定を追加し、マルチプロセスで動作させる。
# config/puma.rb workers 6 preload_app!備考
マルチプロセスにする際にworkersの設定を6以上にしないとうまく動作しないが、詳しい原因やマルチプロセスにした際のデメリットなどは調査しきれていないので知っている方いたらコメントで教えてください?
- 投稿日:2020-11-19T16:03:10+09:00
Ruby がない環境で Rails プロジェクトを一撃で作る Docker コマンド
Dockerはないとダメです。
docker run --rm -it --workdir /app/ --volume $PWD:/app/ ruby:2.6.3 bash -c "gem install rails -v 5.2.4 && rails new sample_project"以上です。
- 投稿日:2020-11-19T13:35:57+09:00
未経験からのエンジニア転職を目指します。
はじめに
こんにちは。
11月より未経験からのエンジニア転職を目指すべく、プログラミング学習をスタートし始めたものです。
学習記録を残していければと思っております。自己紹介
地方国立大学卒業後、大手学習塾に新卒で就職して約1年半経ちます。
理想と仕事内容のギャップで転職を決意しました。高校はかなり勉強には力を入れた記憶があります。
が第一志望には受からず...。プログラミング自体は、大学の講義でおそらくC言語は軽く触れましたが
難しすぎて断念しました(笑)ただ「学力の規定要因分析―都道府県別のクロスセクションデータを用いて―」というテーマで卒業論文を制作し
このときにR言語を使用して、統計分析などを行いました。
1日のほとんどを分析や分析結果の執筆に費やしていた時期で大変でしたが、楽しかったですね。ですので今Progateを中心に学習を進めておりますが、まだ勉強が嫌という感じではないです。
多分ここから大変になってくるのだと思いますが...。ここまでの学習記録
11月の初週からまずはProgateを中心に学習をスタートしました。
最初はスマホ版でやっていましたが、パソコンでやってみたいと思い、先述の卒論制作に使っていたパソコンを使ってProgateに取り組んでおります。
ひとまず19日現在でHTML&CSS,Sass,Command Line,Git,Rubyを一周終わらして、Web開発パスのruby on railsコースのrails Ⅰに取り組んでいる最中です。最後に
逃げ道の無いよう、新型Mac Book Airを購入致しました。今使っているパソコンが動作モッサリしていてイライラなのとWindowsだからですね。
学習していく中での気づきや、学習記録を定期的に更新していけたらと思いますので、よろしくお願いいたします。
- 投稿日:2020-11-19T09:40:28+09:00
Herokuに「The page you were looking for doesn't exist. 」が出たがコミットできてなかったという凡ミスだった、、、
拍子抜けするほどの凡ミスをして数十分ハマってたので、共有します
まず、PCの環境を分かる範囲で書いておきます。
- Ruby 2.6.5
- Ruby on Rails 6.0.3
試作のRailsアプリケーションをHerokuへデプロイし、URLを開こうとしたところ、
The page you were looking for doesn't exist.
のエラー文が表示されました。。とりあえず、ググる、、、
ただ、調べた情報だと
root_path
を設定していなかったために、「それを設定してもう一回HerokuにpushしたらOKでした〜」というものがほとんど。僕の場合は、ローカル環境できちんとrootに対応するviewが表示されていたので、そんなはずはないと思い引続き調査続行。。。
しかし、Terminal上でHerokuのエラーログを調査すると
ActionController::RoutingError (No route matches [GET] "/")という記述があるので、やっぱりroutingはきちんと実装できてないんだなぁ、、、と。
格闘すること数十分、ふと目に止まったGitHub Desktopで異変に気づく。
Terminal上でコミットしたつもりだった「initial commit」以降の変更がcommitされていない模様、、、なななんと、、、超初歩的なミスやないかい!!!(T_T)
ということで、再度コミット後、GitHubのリモートリポジトリにpush、そして、heroku masterにもpush
めでたくきちんと表示されました!!
- 投稿日:2020-11-19T09:06:51+09:00
スクール1週目の振り返り
スクール入校
10月5日、ついにスクールへ入校。学習スタイルは短期集中オンラインです。
新型コロナの影響で在宅オンライン学習となっています。コミュニケーションはSlackとZOOMで行われます。基礎カリキュラムの学習内容
環境構築(準備)
カリキュラム通りにターミナルでコマンドを打ち続けました。今、自分が何の作業をしているのか不安になりながらも環境構築しなくては、コーディングが出来ないと改めて痛感させられました。
そう思うと、Progateは本当に初学者に優しい教材だったんだなと感謝の気持ちで満たされました。HTML&CSS
Progateで楽しく学ぶことが出来たマークアップ言語。道中コースでは自力でのコーティングに苦労しました。
スクールのカリキュラムはイラストや動画で丁寧にまとまっていて理解度が高まりました。一つのカリキュラムを読み終えるのに1時間程度はかかるので、まずは、全体像を把握した上で要点を抑え最低限のメモ書きをしました。なぜなら、プログラミングは暗記しようとしても記憶に定着しないからです。
なので、コーティングで手を動かしスクールの特徴でもある同じグループの同期に学んだことをしっかりとアウトプットすることを心がけました。
アウトプットの効果は凄まじく、人に教えるつもりでインプットして口に出すことで90%記憶に定着するとか科学的に実証されているそうです。Ruby
マークアップ言語を終えて、ついにプログラミング言語に挑戦。基礎カリキュラムでは主にRubyを学びました。
数多くあるプログラミング言語の中で、なぜRubyなのかと言うと日本人の方が開発されたことで国内でのシェア率は高いからです。
また、ネット上で日本語での投稿が多くエラーに躓き挫折してしまいがちな初学者にとっては学びやすい言語だと言えます。Ruby on Rails
Rubyが日本国内で普及した一因でもあるフレームワークのRails。フレームワークの数も多くPHPならLaravelなどその言語に応じて使いやすいフレームワークが採用させる傾向があるそうです。Railsを活用することでファイル作成等を省略できることやコーティング量を減らし可読性を高められることも大きなメリットといえます。振り返り・感想
これまで、HTML&CSS、Ruby、RailsをProgateで2周していたので、上々の滑り出しを切ることができました。基礎カリキュラムでは、最低限のマークアップ言語の知識とRailsの理解度UP、コーディング力を身に着けそこから中間試験、本試験を実施。本試験に合格し応用カリキュラムへと進みます。まず中間試験が難しすぎて自信が打ち砕かれました。そこから試験に向けて猛勉強ですね。特にCSSの配置指定やRailsのインスタンス変数問題が出来なさすぎて課題となりました。
そして、しっかりと中間試験を復習した上で本試験に臨みました。。合格点80点に対し、自己採点は84点とひと安心。そして、メンターさんからも84点と採点していただき、一発で応用カリキュラムに進むことができました。基礎カリキュラムでは、スピート感重視で理解できておらず詰まったらカリキュラムに戻り復習して記憶に定着の繰り返しすることを学びました。
不安を抱えながらも応用カリキュラムへと進みます。プログラミングはどうしても点と点が繋がらない間はこのまま進めて良いのだろうか?と不安になります。
しかし、学習を進めていくうちにどこかで必ず点と点が繋がり理解度が高まった時プログラミングが楽しくなるそうです。その日を待ち望んで日々、愚直に2週目も1日10時間をキープしながら同期と楽しくアウトプットし続けます。
- 投稿日:2020-11-19T06:22:44+09:00
RailsでPAY.JP APIを利用する 〜カード更新・削除編〜(payjp.js v2)
概要
PAY.JP APIでのカード登録/変更/削除機能実装の覚書です。
今回は登録したカード情報を更新するための手順をまとめていきます。
※簡潔にするため実装のために必要な最低限のコードのみ書いています。なにか間違い等あれば編集リクエストをいただけると幸いです。
ちなみにpayjp.jsはv1に関する記事は多いですが現行のv2とは異なる部分が多いので注意が必要です。
実装準備編
https://qiita.com/nissy7ok/items/9790ef5ee1dec2863a62
カード登録編
https://qiita.com/nissy7ok/items/cea6789fe3b99654e473
カード更新・削除編←今ここ
https://qiita.com/nissy7ok/items/0ed52954f8fdad772da3環境
# OS Version ProductName: Mac OS X ProductVersion: 10.15.7 BuildVersion: 19H2 # Ruby Version ruby: 2.6.5p114 Rails: 6.0.3.3前提条件
PAY.JP APIを用いてカード登録をするところまでできている。
前回の記事参照。手順
更新機能
API上で複数のカードを登録させることもできますが、
今回は既存のカード情報を削除し、再度登録するようにしています。
※最低限の記述に削っているので実際は条件分岐をしたほうが良いです。card_controller.rbdef edit @title = "カード情報変更" @btn ="変更" @card = Card.find(params[:id]) redirect_to "/" end def update @card = Card.find(params[:id]) Payjp.api_key = ENV['PAYJP_SECRET_KEY'] customer = Payjp::Customer.retrieve(@card.customer_id) # 既存のカード情報を削除 card = customer.cards.retrieve(@card.card_id) card.delete # カードを新しく登録 customer.cards.create( card: params['payjp_token'] ) @card.update(card_id: params['card_token']) redirect_to "/" end end削除機能
私の場合条件分岐をすべて書き換えないといけなくなってしまうため、モデル内の情報も削除しています。
カード情報は一人一枚という仕様で割り切っています。
実際は一人で複数枚登録するケースはあるし、PAY.JP側で複数枚のカード情報を管理することも当然できます。
ここはもうちょっとうまくできたかも・・・。card_controller.rbdef destroy @card = Card.find_by(user_id: current_user.id) Payjp.api_key = ENV['PAYJP_SECRET_KEY'] customer = Payjp::Customer.retrieve(@card.customer_id) customer.delete @card.destroy redirect_back(fallback_location: root_path) end参考
結局の所公式APIとガイドが最強です。
ただ読み慣れないうちはQiita等で実際の実装手順を見ながら感覚を掴むと理解が早まると思います。PAY.JP API リファレンス
https://pay.jp/docs/api/PAY.JP API 利用ガイド | PAY.JP
https://pay.jp/docs/started
公式ブログも参考になりますが、情報が古くv1準拠で書かれている場合が多いので注意が必要です。ごく基本的なPAY.JPの使い方(Ruby編) - PAY.JP Engineering Blog
https://payjp.hatenablog.com/entry/2017/11/21/191916
その他参考にした記事等Railsで Payjp.js V2 でクレジットカード登録機能実装 フリマアプリ - Qiita
https://qiita.com/ta9301/items/6b736390c49c3f40edb6[HowTo]Pay.jpを用いた商品購入機能実装から商品購入後の設定まで
https://qiita.com/Tatsu88/items/eb420e372077939a4627
- 投稿日:2020-11-19T06:10:40+09:00
コピペでwebアプリが作れる。魔法のコード。
細かいことは抜きにこのコードを自分のターミナルで打ってみてください。
rails new testapp -d postgresql; cd testapp && rails g scaffold post title:string body:string && rails g scaffold comment content:string post:references && bundle && rails db:create && rails db:migrate && rails sこのコードだけでwebアプリを作ることができます。
用途としては、
- 使ったことないgemのテストのためのアプリとして
- Railsでどんなことができるのか初めの一歩としてとりあえず動かしてみたいときなどです。
コードの詳細としては
rails new testapp -d postgresqlcd testapprails g scaffold post title:string body:stringrails g scaffold comment content:string post:referencesbundlerails db:createrails db:migraterails sです。
それぞれの意味がわからない人は是非調べてみてください!他にも便利で楽しいコードを発見したら投稿したいと思います。
- 投稿日:2020-11-19T00:59:32+09:00
Herokuの導入(自分用)
herokuのインストール
Heroku CLIをインストール
% brew tap heroku/brew && brew install herokuバリデーションの確認
% heroku --versionログイン
% heroku login --interactive hiro0308/アプリにHeroku導入
本番環境gem
Gemfile.# ファイルの一番下の行に追記する group :production do gem 'rails_12factor' end:productionで指定すると本番環境のみで使用されるように指定できる
bundle installも忘れずに。コミット
% git add . % git commit -m "gem rails_12factorの追加"Heroku上にアプリケーションを作成
% heroku create furima-00000確認
git config --list | grep herokufatal: not in a git directory以外が表示ならok
HerokuでMySQLを使用
% heroku addons:add cleardbMySQLに対応するGemについて考慮
ClearDBデータベースのURLを変数、格納
% heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`データベースのURLを再設定
% heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5} # 以下、コマンドの実行結果 DATABASE_URL: mysql2://になってることcredentials.yml.encの暗号文を中身を確認
% EDITOR="vi" bin/rails credentials:edit「escキー」→「:」→「q」で閉じる
環境変数を設定
% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`確認
% heroku configRAILS_MASTER_KEYという変数名で値が設定されたかの確認
Herokuにアプリケーションの情報を追加
% git push heroku masterHeroku上でマイグレーションファイル
% heroku run rails db:migrate % heroku run rake db:version公開を確認
% heroku apps:infoログ確認
% heroku logs --tail --app furima-00000Css,java反映
% rake assets:precompile RAILS_ENV=production