20201119のRailsに関する記事は28件です。

【初心者向け】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を選択する。
スクリーンショット 2020-11-18 235646.png
任意のALBの名前を入力します。
Listenerは一旦デフォルトのままでいいです。
スクリーンショット 2020-11-19 232033.png
その後、自動作成されたVPC、サブネットを選択し、Nextをクリック。
警告が出てきますが、かまわずNextをクリックします。
スクリーンショット 2020-11-19 232224.png
次にALBのセキュリティグループを新に作成します。任意の名前と説明を入力します。
インバウンドルールはHTTPプロトコルで、すべてのソースを許可します。(画像参照)
スクリーンショット 2020-11-19 232450.png
次にターゲットグループ(ALBに来たアクセスのリダイレクト先)を新しく作成します。
ターゲットグループにはEC2インスタンスを指定していきます。

New Target groupを選択し任意のターゲットグループの名前を入力します。
後はデフォルトでOKです。
スクリーンショット 2020-11-19 232547.png
ヘルスチェック(ALBからサーバーが正常に動いているかどうかチェックする機構)もデフォルトでOK
デフォルトだとヘルスチェックのパスはルートパスになってます。
スクリーンショット 2020-11-19 232606.png
次にターゲットグループのインスタンスを登録します。
自動生成されたEC2インスタンスが二つあるはずなのでチェックマークをいれ、赤丸のinclude as pending below をクリックします。
その後、Register pending targetsをクリックします!
スクリーンショット 2020-11-19 234348.png

最後に、確認画面が出るので、設定があっているか確認出来たらCreateをクリックして作成完了です!
お疲れ様でした!

最後まで読んでいただきありがとうございます!

今回はALB作成編をやっていきました!次回はいよいよDockerイメージをECRへpushしていきます!
今週中にはすべての記事を完成させていくつもりですので、よろしくお願い致します!!

何かご指摘やご質問などあればコメントいただけますと嬉しいです。

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

【Rails6】ActionCableでマルチチャット

はじめに

初投稿です!
今回はRailsのActionCableを用いて、リアルタイムチャットアプリを作ります。
マルチチャットとは、ユーザごとに複数のチャット部屋があり、それらを行き来できるイメージです。

デモ

chat.gif

ソースコードはこちらです。
https://github.com/yamori-masato/chat-app

動作環境

  • Ruby 2.6.3
  • Rails 6.0.3.4

下準備

$ rails new chat-app
$ cd chat-app

モデルの作成

table.png

今回はこのようなモデル設計にしました。
それでは一通りモデルを作成します。

$ 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:migrate
app/models/user.rb
class User < ApplicationRecord
    has_many :user_rooms, dependent: :destroy
    has_many :rooms, through: :user_rooms
end
app/models/room.rb
class Room < ApplicationRecord
    has_many :messages, dependent: :destroy
    has_many :user_rooms, dependent: :destroy
    has_many :users, through: :user_rooms
end
app/models/user_room.rb
class UserRoom < ApplicationRecord
    belongs_to :user
    belongs_to :room
end
app/models/message.rb
class Message < ApplicationRecord
  belongs_to :user
  belongs_to :room

  validates_presence_of :content
end

コントローラーの作成

$ rails g controller rooms index show
config/routes.rb
Rails.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.rb
class 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

index.png

show.png

先ほど登録したメッセージがroom1に表示されているはずです。

メッセージ送信

現時点では送信ボタンを押してもメッセージが送られないので、これを送れるようにしたいと思います。
Ajaxで送信できるようにします。

まず、メッセージを新規追加できるようにコントローラーを修正します。

$ rails g controller message create
config/routes.rb
  Rails.application.routes.draw do
    resouces :usersn, only: [] do
      resources :rooms, only: [:index, :show]
    end
+   resources :messages, only: [:create]
  end
app/controllers/messages_controller.rb
class 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.rb
  class 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.erb
document.getElementById("message_content").value = ''

ajaxリクエスト後はデフォルトでviews/messages/create.html.erbが呼ばれます。
ここでは送信後にフォームの値を空にしています。

ここで、idに"message_content"を指定しています。
これはform_withヘルパーによって自動生成されたinput要素に付与されるidです。
実際にChromeの検証ツールで見てみましょう。

developer_tool.png

このようにform_withで自動生成されるフォームにはそれぞれ決まったクラス名やid名がついていることがわかります。今回の場合は、inputタグのidにmessage_contentが付与されているのでこれを指定しています。

これでAjaxでメッセージ送信することができるはずなので実際に確認してみましょう!

<送信前>
before.png
<送信後>
after.png

※この時点ではまだリロードしないと描画に反映されません。

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.rb
class RoomChannel < ApplicationCable::Channel
  def subscribed
    # stream_from "some_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end
app/javascript/channels/room_channel.js
import 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.js
  import 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.rb
  class 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.rb
class 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.js
import 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.js
  import 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.js
  import 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.rb
  class 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.rb
  class 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.js
import 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']のようにして受け取れます。
これをチャットログの末尾に挿入してあげれば完成です!!

参考文献

おわりに

次回は、このチャットアプリにログイン機能を追加したいと思います!
ご意見アドバイス等ぜひお願いいたします。

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

【Rails6】ActionCableでマルチルームチャット

はじめに

初投稿です!
今回はRailsのActionCableを用いて、リアルタイムチャットアプリを作ります。
マルチルームチャットとは、ユーザごとに複数のチャット部屋があり、それらを行き来できるイメージです。

デモ

chat.gif

ソースコードはこちらです。
https://github.com/yamori-masato/chat-app

動作環境

  • Ruby 2.6.3
  • Rails 6.0.3.4

下準備

$ rails new chat-app
$ cd chat-app

モデルの作成

table.png

今回はこのようなモデル設計にしました。
それでは一通りモデルを作成します。

$ 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:migrate
app/models/user.rb
class User < ApplicationRecord
    has_many :user_rooms, dependent: :destroy
    has_many :rooms, through: :user_rooms
end
app/models/room.rb
class Room < ApplicationRecord
    has_many :messages, dependent: :destroy
    has_many :user_rooms, dependent: :destroy
    has_many :users, through: :user_rooms
end
app/models/user_room.rb
class UserRoom < ApplicationRecord
    belongs_to :user
    belongs_to :room
end
app/models/message.rb
class Message < ApplicationRecord
  belongs_to :user
  belongs_to :room

  validates_presence_of :content
end

コントローラーの作成

$ rails g controller rooms index show
config/routes.rb
Rails.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.rb
class 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

index.png

show.png

先ほど登録したメッセージがroom1に表示されているはずです。

メッセージ送信

現時点では送信ボタンを押してもメッセージが送られないので、これを送れるようにしたいと思います。
Ajaxで送信できるようにします。

まず、メッセージを新規追加できるようにコントローラーを修正します。

$ rails g controller message create
config/routes.rb
  Rails.application.routes.draw do
    resouces :usersn, only: [] do
      resources :rooms, only: [:index, :show]
    end
+   resources :messages, only: [:create]
  end
app/controllers/messages_controller.rb
class 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.rb
  class 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.erb
document.getElementById("message_content").value = ''

ajaxリクエスト後はデフォルトでviews/messages/create.html.erbが呼ばれます。
ここでは送信後にフォームの値を空にしています。

ここで、idに"message_content"を指定しています。
これはform_withヘルパーによって自動生成されたinput要素に付与されるidです。
実際にChromeの検証ツールで見てみましょう。

developer_tool.png

このようにform_withで自動生成されるフォームにはそれぞれ決まったクラス名やid名がついていることがわかります。今回の場合は、inputタグのidにmessage_contentが付与されているのでこれを指定しています。

これでAjaxでメッセージ送信することができるはずなので実際に確認してみましょう!

<送信前>
before.png
<送信後>
after.png

※この時点ではまだリロードしないと描画に反映されません。

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.rb
class RoomChannel < ApplicationCable::Channel
  def subscribed
    # stream_from "some_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end
app/javascript/channels/room_channel.js
import 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.js
  import 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.rb
  class 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.rb
class 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.js
import 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.js
  import 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.js
  import 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.rb
  class 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.rb
  class 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.js
import 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']のようにして受け取れます。
これをチャットログの末尾に挿入してあげれば完成です!!

参考文献

おわりに

次回は、このチャットアプリにログイン機能を追加したいと思います!
ご意見アドバイス等ぜひお願いいたします。

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

link_toを使って、一つ前のページに遷移させる方法

link_toでもどるボタン

<%= link_to 'もどる', :back %>

:backとすると、一つ前のページに遷移させることができる。

ちなみに

<%= link_to 'TOPページへ', root_path  %>

これで、routesで設定した、トップページに遷移させることができる。

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

Rails 日付カラムのバリデーション設定

はじめに

Railsを使ってオリジナルアプリを開発しています。日付カラムに今日の日付より後(明日以降)は保存できないように、バリデーションを設定しました。忘れないように書き記します。

目次

1.日付カラムのバリデーション
2.モデルへの追加記述

1.日付カラムのバリデーション

今回suggestionテーブルにlast_cleaned_dateカラム(最後に掃除した日付)を作成した。下記のように空の場合、保存できないバリデーションは設定した。しかし、他カラムと異なり、空以外のバリデーションは別で行う必要がある。

app/models/suggestion
class Suggestion < ApplicationRecord
  with_options presence: true do
#----------------中略-------------------    
    validates :last_cleaned_date #←対象のカラム
  end
  belongs_to :user
end

2.モデルへの追加記述

同ファイルにバリデーションを追記する。ここではlast_cleaned_dateカラムが空でなかった場合、day_after_todayの処理が行われる。errors.addでエラーの種類を追加する。ここでもif文で条件を定義した。last_cleaned_dateが今日の日付(Date.today)より大きい場合、明日以降を示す。そして条件を満たした場合、保存されずエラーを表示する。

app/models/suggestion.rb
validate :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)で保存しようとした場合の、エラー文である。

image.png

以上

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

【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-18 23.58.35.png

するとブラウザにルーティングが表示される!
スクリーンショット 2020-11-19 22.27.27.png

ルートが増えるとターミナルでは確認し辛くなってくるのでコチラの方法はかなりオススメです!
ぜひお試しください!

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

[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.rb
class 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 :アクション名
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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部分とその他の部分でハッシュを分けたらうまくいきました。

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

マイグレーションファイルのカラムを削除するには

今回はマイグレーションファイルを作成した後に、「あれ?このカラム使わないな、」と思った時にカラムを削除する方法を紹介したいと思います。

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
end

2. カラムの削除

マイグレーションファイルを削除する際のコマンド

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から始まるファイル名を入れる

これでマイグレーションファイルの削除が完了したのでこの記事のカラムを削除するマイグレーションファイルを作成するところから初めて行けば大丈夫です。

いかがだったでしょうか?ぜひ参考にしていただけると幸いです!

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

【Rails】deviseが最低限使えるようになるためのポイント

インストール

Gemfile
gem 'devise'
$ bundle install
$ rails g devise:install

config/initializers/devise.rbconfig/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 モデル名
  1. app/models/user.rbの生成
  2. マイグレーションファイルの作成
  3. config/routes.rbdevise_for :usersという記述が追加され、deviseで使うルーティングが作成される

マイグレーションファイルには、デフォルトでメールアドレスやパスワードなど、いろんな項目が設定されている。
必要に応じてカラムを追加。ここではnameというカラムを追加。

db/migrate/timestamp_devise_create_users.rb
class 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:migrate

strong parametersを設定

deviseのコントローラーは、ライブラリ側で用意されているので、直接修正できない。
だからdeviseのコントローラーに修正が必要なときは、application_controllerに書く。

今回nameというカラムを追加したので、configure_permitted_parametersを上書きしてsignup時にnameを扱えるようにする。

app/controllers/application_controller.rb
class 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
end

before_actionでログインチェック

application_controllerにbefore_action :authenticate_user!をセットして、ログインしないと見れないページにログインせずにアクセスした場合、ログインページにリダイレクトするように設定。

app/controllers/application_controller.rb
class 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_forform_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.rb
class ApplicationController < ActionController::Base
  #省略
  def after_sign_in_path_for(resource)
    user_path(resource)
  end
  #省略
end 

ユーザー詳細ページを作成

これはdevise無視して勝手にcontrollerやviewを作る。編集とか削除とかも同じ、deviseは気にしないで作る。

$ rails g controller users show
app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end
config/routes.rb
resources :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? # ログインしてるか確認
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MVCとは

MVCとは

Model(モデル)
View(ビュー)
Controller(コントローラー)
これら3つの役割の総称のことを指します。
RailsなどのWebアプリケーションの処理の仕組みのことです。

図解

Image from Gyazo

ざっくりと図にするとこんな感じです。
基本となるMVCですので、復習を兼ねてアウトプットしました。
説明に至らないとは思いますが、駆け出し者のアウトプットをどうか大目に見てやってください。。。

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

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_global
turbolinksが読み込まれないように編集

アプリケーションにおいて、画面遷移を高速化する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」

ロボコップの導入

Gemfile
group :development do
  gem 'rubocop', require: false
end
ターミナル
% bundle install
% touch .rubocop.yml
rubocop.yml
AllCops:
# 除外するディレクトリ(自動生成されたファイル)
# デフォルト設定にある"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

コピペする。

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

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つ全て格納データが消えたと思います、、、
間違っているなどあれば是非コメント下さい!

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

RSpecのテストにおいて期待されるエラーが出力されなかった話

はじめに

画像とテキストを投稿するRailsアプリケーションで、RSpecによるテストを行っていた。
アプリケーションの設計ではテキストがない場合はツイート(投稿)が出来ないようになっている。

テスト内容

テキストなしの投稿は保存できない、という内容のテストで以下のコードを実行した。

spec/models/tweet_spec.rb
it 'テキストがないとツイートは保存できない' 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.rb
  validates :text, presence: true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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: 'ゲストユーザーとしてログインしました。'
  end
app/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に任せることには違和感があります。本来は,ログイン機能同様,DeviseSessionsControllerに任せるべきでしょう。

例えば次のように実装すると,より自然なものになるのではないでしょうか。

ゲストログイン機能の設定

まず,SessionsControllerに新しいアクションnew_guestを準備します。

config/routes.rb
# 以下を追加
  devise_scope :user do
    post 'users/guest_sign_in', to: 'users/sessions#new_guest'
  end

アクションnew_guestを設定するため,app/controllersusersディレクトリを作成し,その中に次のsessions_controller.rbを作成します。ゲストユーザーを探す or 作成する機能はUser.rbに移動させるとコントローラーがスッキリします。

app/controllers/users/sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  def new_guest
    user = User.guest
    sign_in user
    redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。'
  end
end
app/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.rb
class 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.rbcreateアクションの動作前にチェックすれば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.rb
class 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

最後に

一応これでゲストログイン機能の完成です。
何か間違っているところがあればご教授していただけると幸いです。

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

プログラミング初心者がRailsで日記SNS作ってみた

はじめに

日々の学びをメモして、良い感じのやつだけ共有できるようなアプリが欲しいと思ったので自作してみました.
現在、α版としてHerokuにアップしているので覗いていただけると嬉しいです。

アプリ名

Output App

スクリーンショット 2020-11-19 17.28.49.png

リンク

URL : https://outputapp.herokuapp.com/
GitHub : https://github.com/tatsuhiko-nakayama/output_app

機能紹介

投稿画面

スクリーンショット 2020-11-19 17.30.34.png

  • ハッシュタグを入力することで情報源を共有できる
  • デフォルトが非公開なのでメモとして使える
  • closedボタンをopenに切り替えて公開する
  • マークダウンが使える

詳細画面

スクリーンショット 2020-11-19 17.35.36.png

  • ハッシュタグ、参考URLへリンク
  • LIKE機能
  • フォロー機能
  • コメント機能

ユーザーページ

スクリーンショット 2020-11-19 17.41.06.png

  • 各ステータス表示&一覧リンク
  • ユーザーの投稿(open)一覧

ここまで作ってみて

所感

あまり難しい機能は実装せず、初心者でも自力でできる範囲でいろいろと実装してみました。

特にJavaScriptが勉強不足でAjaxがうまくいかなったり、改善したいところが多々あります。

次のステップ

テスト公開中なので、ユーザーさんの声を聞きながら使いたくなるサービスにどんどん改良していきます。

「アウトプット」と聞くと意識高い感じがして使いにくいのかなと思いつつも、特徴のない「日記アプリ」や「SNS」では既存サービスでええやんと思ったり。

その辺、試行錯誤しながら楽しく作り込んでいきたいなと思います。

作者スペック

  • 2020年8月からプログラミングを始める
  • 2ヵ月間、某プログラミングスクールで学習
  • 前職は営業(8年在籍)
  • 現在はニート生活を満喫しつつ、Web系自社開発企業で働きたいと思っている
  • 趣味は料理とYoutube

制作期間

11/1〜現在

使用技術

  • Ruby / Rails
  • JavaScript / jQuery
  • Amazon S3
  • AWS (これから)
  • Docker(これから)
  • Circle CI(これから)

おわりに

テストユーザーのお願いをして使ってもらって、そこからが開発だなと思いました。

技術ももちろん大事だけど、ユーザーさんにとって使いやすかったり、感動する体験にどうやってつなげていくのか。

そこを考えるのはとても楽しいけれど、同じくらい悩ましい笑

ご覧いただけた方いらっしゃいましたら、ぜひ感想・アドバイスお願いします。

✔︎

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

jQueryを使ったページスクロールの作成方法まとめ

フロントエンドをよりみやすくしようと思ったところで、よく企業のページに行くと、クリックでスクロールするような動きがあり、いいなと思ったので作成してみました。

環境

ruby 2.6.5
rails 6.0.3

導入の流れ

Step1 jQueryのインストール
Step2 webpackの編集
Step3 jQueryの呼び出し
Step4 スクロールをできるようにする記述を書く

Step1 JQueryのインストール

railsを作成した後にターミナルにてコマンドを打ち込みます。

% yarn add jquery

実行後の画像は下記の通り
jQuery_Qiita1.png

Step2 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 = environment

jQueryの呼び出し

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/9532

RailsでjQueryのページスクロール設定
https://qiita.com/taKassi/items/c1e2b54138744afe8b6d

徹底解説!スムーススクロールをjQueryで実装する方法
https://changeup.tech/article/jquery-smooth-scroll/

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

【ざっくり解説】介護記録アプリを作ってみた【未経験/PF】

はじめに

前職で「こんなのあったらいいなぁ…」と思っていたアプリを、就活用のPFとして作ってみました。色々と改善点はありますが、形になったので一旦まとめてみます。

前半では私が作ったPFの詳細を、後半ではこれからPFを作る初学者の皆さんに向けたお節介を、それぞれ書いています。

これからPFを作ろうとしている初学者の皆さんの参考になれば幸いです。

目次

1.PFの概要
2.こだわったポイントと目的
3.苦労したこと・コード
4.これからPFを作る初学者へアドバイス
まとめ

1.PFの概要

介護事業所向け記録アプリケーション 『Sup-App(サプアップ)』
sup-top.jpg

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時間くらい…:innocent:

ざっくり機能紹介

①記録機能

お客様ごとに記録を時系列で表示します。
記録はパッと見てわかるアイコンにし、実施/非実施で色が切り替わります。
e548bc92f319c3b7b56d573a9ed151fa.png

②報告書作成機能

事故報告書を作成することができます。
作成した事故報告書をCSV形式で出力できるようにしました。
14b31ac0e65a0ece34b58e924c361cb6.png

③チャット機能

テキストと画像で簡単なチャットを行うことができます。
タグをつけることができて、タグによってメッセージの色が変化します。
(UIがひどい…笑)
741b13daede242cf0c112c1aec3dbf00.png

④ルーム機能

お客様ごとのチャットルームを一覧で表示します。
チャット画面に飛べるほか、お客様詳細や記録の実施/非実施切り替えなども行えます。
cfdb8d4c8c616c03912d7a24475a22eb.png

⑤お客様(入居者)管理機能

お客様情報をウィザード形式のフォームから登録することができます。
お客様詳細(画像)からは、お客様の情報が参照できるほか、フォローや編集なども行うことができます。
4a6cf8e292c48220f98107ee29f00f80.png

⑥ユーザー(スタッフ)管理機能

スタッフの情報を登録できます。トップページからは簡単ログインが可能です。
設定した資格や職位によって、一部の機能を制限します。
6d9b32b2ee78b398c7725450160b9f80.png

⑦お客様フォロー機能

お客様をフォローすることができます。
フォローしたお客様は「マイページ」から確認することができます。
771b8c8c0f8e3e699f910343f34c6d4b.png

⑧お客様検索機能

トップページの検索フォームから、「名前」「フリガナ」「居室番号」でお客様を検索することができます。
名前とフリガナは部分一致(あいまい)検索、居室番号は完全一致検索です。
9d4d8a037b17404a7b7f2b2a6bf0cd8c.png

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
  end

CSV形式の報告書データ出力機能

ウィザードフォームの他に、CSV形式の出力機能で差別化を図っています。
これも今まで扱ったことはありませんでしたが、「現場にあったら便利かもしれない…」と思い実装してみました。

index.csv.ruby
require '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といったモダンなフロント技術をキャッチアップしていなかったため、ビューに特徴を持たせることができませんでした…:sob:

現在はVue.jsを学習しつつ、MaterialDesignについて理解を深めています。
フロント技術をマスターして,イケてるビューを作れるようになりたいです:fire:

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に関する資料

インフラ構成図

sup_app.ER- infrastructure.png

ER図

sup_app.ER-ER.png

引用・参考にさせていただいた記事

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

Rails いいね機能

consle
rails g model Favorite user:references post:references
rails db:migrate

一人のユーザーはいいねを一回までにする為

favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :post

  validates_uniqueness_of :post_id, scope: :user_id
end
post.rb
  has_many :favorites, dependent: :destroy
user.rb
  has_many :favorites, dependent: :destroy
console
rails g controller favorites

postsにネストする

routes.rb
resources :posts do
  resource :favorites, only: [:create, :destroy]
end

rails routes すると、
post_favorites
DELETE /posts/:post_id/favorites favorites#destroy
POST /posts/:post_id/favorites  favorites#create
と表示される。
:post_idの部分をcontrollerで取得

favorites_controller.rb
class 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.rb
  has_many :favorites, dependent: :destroy

  def already_favorited?(post)
    self.favorites.exists?(post_id: post.id)
  end
view
<% 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に対応する。

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

[Rails]パンくず機能

はじめに

画面遷移をわかりやすくするために、gretelというgemを使用してパンくず機能を実装しました。
パンくずリスト

目次

  • 1. gretelのインストール
  • 2. パンくずの設定
  • 3. ビュー

1. gretelのインストール

gretelというgemを用いることで、リンクを設置したリストを画面に表示させるパンくずを実装することができます。

gemfile
gem "gretel"
ターミナル
bundle install

2. パンくずの設定

パンくずの親子関係を設定するファイルを作成します。

ターミナル
rails g gretel:install
config/breadcrumbs.rb
crumb "現在のページ名(表示させるビューにもページ名記述)" do
  link "パンくずリストでの表示名", "アクセスしたいページのパス"
  parent :親要素のページ名(前のページ)
end
config/breadcrumbs.rb
crumb :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: " &rsaquo; " %>
</div>

separator: " &rsaquo; “はパンくずの区切りである「>」を示します。

投稿一覧のindexファイルだけ載せておきます。

app/views/posts/index.html.erb
~~
<% breadcrumb :posts %>
<%= render "shared/breadcrumbs" %>
~略~

breadcrumb :postsはbreadcrumbs.rbで設定したページ名を記述しています。

参考リンク

https://qiita.com/Iwa_tech/items/61d1681687739faffab0

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

IMGkit での画像生成が終わらない件について

前提

この問題を解決する際に非常に時間がかかった + 日本語記事がなく英語記事でもなかなか解決策が出てこなかったので同じ問題につまづいている方にむけて共有すべく記載しています。
言葉の定義など曖昧な理解で書いている箇所があることはご理解ください。

今回の実装ケースは、Controllerアクション内で画像生成を行うことを前提としています。

IMGkitとは

Railsで提供されている画像生成用のgem。 詳細は以下。
https://github.com/csquared/IMGKit

今回の問題

ローカル・staging環境で画像を生成する際に生成処理が完了せず、かつログも出力されない。
stagingに至ってはサーバーダウンしてしまいサービス全体に影響が出てしまう。

ローカルでの発生手順
  1. rails s でサーバーを立ち上げ、画像生成処理を行う(1度目はうまくいく)
  2. ソースを微修正して保存
  3. 画像生成処理を再度実行
  4. 現象発生(処理が完了しない)

原因

rails serverをシングルプロセスモードで立ち上げているため。

解説

  1. クライアントがリクエストを送信し、プロセスAがそのリクエストをハンドリング.
  2. プロセスAのなかで、IMGkit(wkhtmltoimage)が画像生成するためのview renderリクエストをrailsサーバーに送り、処理の完了を待つ。
  3. IMGkitが送ったリクエストはプロセスAの処理が完了するまで待機するが、IMGkitの処理完了を待っているためプロセスAの処理も完了しない。
  4. デッドロックが発生し処理が完了しない。

参考URL

IMGkitのgithubのissueに解決方法が記載
https://github.com/csquared/IMGKit/issues/84

解決方法

puma.rbに下記設定を追加し、マルチプロセスで動作させる。

# config/puma.rb
workers 6
preload_app!

備考

マルチプロセスにする際にworkersの設定を6以上にしないとうまく動作しないが、詳しい原因やマルチプロセスにした際のデメリットなどは調査しきれていないので知っている方いたらコメントで教えてください?

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

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"

以上です。

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

未経験からのエンジニア転職を目指します。

はじめに

こんにちは。
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だからですね。
学習していく中での気づきや、学習記録を定期的に更新していけたらと思いますので、よろしくお願いいたします。

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

Herokuに「The page you were looking for doesn't exist. 」が出たがコミットできてなかったという凡ミスだった、、、

拍子抜けするほどの凡ミスをして数十分ハマってたので、共有します:sweat_smile:

まず、PCの環境を分かる範囲で書いておきます。

  • Ruby 2.6.5
  • Ruby on Rails 6.0.3

試作のRailsアプリケーションをHerokuへデプロイし、URLを開こうとしたところ、
The page you were looking for doesn't exist.のエラー文が表示されました。。

スクリーンショット 2020-11-19 0.18.01.png

とりあえず、ググる、、、

ただ、調べた情報だと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

めでたくきちんと表示されました!!:joy:

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

スクール1週目の振り返り

スクール入校 :writing_hand_tone1:

10月5日、ついにスクールへ入校。学習スタイルは短期集中オンラインです。
新型コロナの影響で在宅オンライン学習となっています。コミュニケーションはSlackとZOOMで行われます。

基礎カリキュラムの学習内容 :bulb:

環境構築(準備)
カリキュラム通りにターミナルでコマンドを打ち続けました。今、自分が何の作業をしているのか不安になりながらも環境構築しなくては、コーディングが出来ないと改めて痛感させられました。
そう思うと、Progateは本当に初学者に優しい教材だったんだなと感謝の気持ちで満たされました。

HTML&CSS
Progateで楽しく学ぶことが出来たマークアップ言語。道中コースでは自力でのコーティングに苦労しました。
スクールのカリキュラムはイラストや動画で丁寧にまとまっていて理解度が高まりました。

一つのカリキュラムを読み終えるのに1時間程度はかかるので、まずは、全体像を把握した上で要点を抑え最低限のメモ書きをしました。なぜなら、プログラミングは暗記しようとしても記憶に定着しないからです。

なので、コーティングで手を動かしスクールの特徴でもある同じグループの同期に学んだことをしっかりとアウトプットすることを心がけました。
アウトプットの効果は凄まじく、人に教えるつもりでインプットして口に出すことで90%記憶に定着するとか科学的に実証されているそうです。

Ruby
マークアップ言語を終えて、ついにプログラミング言語に挑戦。基礎カリキュラムでは主にRubyを学びました。
数多くあるプログラミング言語の中で、なぜRubyなのかと言うと日本人の方が開発されたことで国内でのシェア率は高いからです。
また、ネット上で日本語での投稿が多くエラーに躓き挫折してしまいがちな初学者にとっては学びやすい言語だと言えます。

Ruby on Rails
Rubyが日本国内で普及した一因でもあるフレームワークのRails。フレームワークの数も多くPHPならLaravelなどその言語に応じて使いやすいフレームワークが採用させる傾向があるそうです。Railsを活用することでファイル作成等を省略できることやコーティング量を減らし可読性を高められることも大きなメリットといえます。

振り返り・感想 :triangular_flag_on_post:

これまで、HTML&CSS、Ruby、RailsをProgateで2周していたので、上々の滑り出しを切ることができました。基礎カリキュラムでは、最低限のマークアップ言語の知識とRailsの理解度UP、コーディング力を身に着けそこから中間試験、本試験を実施。本試験に合格し応用カリキュラムへと進みます。まず中間試験が難しすぎて自信が打ち砕かれました。そこから試験に向けて猛勉強ですね。特にCSSの配置指定やRailsのインスタンス変数問題が出来なさすぎて課題となりました。

そして、しっかりと中間試験を復習した上で本試験に臨みました。。合格点80点に対し、自己採点は84点とひと安心。そして、メンターさんからも84点と採点していただき、一発で応用カリキュラムに進むことができました。基礎カリキュラムでは、スピート感重視で理解できておらず詰まったらカリキュラムに戻り復習して記憶に定着の繰り返しすることを学びました。

不安を抱えながらも応用カリキュラムへと進みます。プログラミングはどうしても点と点が繋がらない間はこのまま進めて良いのだろうか?と不安になります。
しかし、学習を進めていくうちにどこかで必ず点と点が繋がり理解度が高まった時プログラミングが楽しくなるそうです。その日を待ち望んで日々、愚直に2週目も1日10時間をキープしながら同期と楽しくアウトプットし続けます。

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

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.rb
def 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.rb
  def 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

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

コピペで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 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

です。
それぞれの意味がわからない人は是非調べてみてください!

他にも便利で楽しいコードを発見したら投稿したいと思います。

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

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 heroku

fatal: not in a git directory以外が表示ならok

HerokuでMySQLを使用

% heroku addons:add cleardb

MySQLに対応する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 config

RAILS_MASTER_KEYという変数名で値が設定されたかの確認

Herokuにアプリケーションの情報を追加

% git push heroku master

Heroku上でマイグレーションファイル

% heroku run rails db:migrate
% heroku run rake db:version

公開を確認

% heroku apps:info
ログ確認
% heroku logs --tail --app furima-00000
Css,java反映
% rake assets:precompile RAILS_ENV=production
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む