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

【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で続きを読む

アラビア数字からローマ数字に変換してみた

はじめに

今回は課題としてアラビア数字からローマ数字に変換するという課題が出たので、その課題を解いてみる事にする

解答

プログラム

  • 正直全く良い方法分からない、、、、、
num = ARGV[0]

def divide_num(num)
  ans_list,count = [],0
  while num > 0
    tmp = num % 10
    ans_list.push(tmp * (10 ** count))
    num = num / 10
    count = count + 1
  end
  return ans_list.reverse
end

ans_list,count = divide_num(num.to_i),0

def num_to_roman_hundred(num)
  if num == 900
    print "CM"
  elsif num <= 800 && num >= 500
    num = num - 500
    if num != 0
      print "D" + "C" * (num / 100)
    else
      print "D"
    end

  elsif num == 400
    print "CD"

  elsif num <= 300 && num >= 100
    num = num - 100
    if num != 0
      print "C" + "C" * (num / 100)
    else
      print "C"
    end
  end
end

def num_to_roman_ten(num)
  if num == 90
    print "XC"
  elsif num <= 80 && num >= 50
    num = num - 50
    if num != 0
      print "L" + "X" * (num / 10)
    else
      print "L"
    end

  elsif num == 40
    print "XL"

  elsif num <= 30 && num >= 10
    num = num - 10
    if num != 0
      print "X" + "X" * (num / 10)
    else
      print "X"
    end
  end
end

def num_to_roman(num)
  roman_first = ['I','II','III','IV','V','VI','VII','VIII','IX','X']

  if num >= 1000
    tmp = num / 1000
    x = "M" * tmp
    print x

  elsif num <= 900 && num >= 100
    num_to_roman_hundred(num)
  elsif num <= 90 && num >= 10
    num_to_roman_ten(num)
  elsif num <= 10 && num >= 1 
    print roman_first[num-1]

  end
end

for i in ans_list do
  num_to_roman(i)
end

print "\n"

自分で書いてて思います。頭悪い実装です(笑)

(深夜にやっていて頭回ってないという言い訳をしときます(笑))

個別の関数毎の説明

  • divide_num 関数の中で受けとった数字を分解
  • num_to_roman_hundred, num_to_roman_ten はそれぞれの桁数に合わせてローマ数字に変換を行う
  • num_to_roman で全ての数字をローマ数字に変換

答え合わせ

では作成したプログラムが正しいかどうかの答え合わせをしましょう!!

  • 以下のようなプログラム(a.sh)を作成します

    for i in $(seq 1 3999);do
      ruby a.rb $i # a.rb はソースコードの名前なので何でもいいです
    done
    
  • bash a.sh > tmp.txt

  • エクセルから 3999 までのローマ数字を作成(1 分以内で可能)し、tmp2.txt として保存

  • diff tmp.txt tmp2.txt

何も出力されなければ成功

GitHub

以下の URL に全てのプログラムと txt file を置いておきます。


  • source ~/Downloads/git/grad_members_20f/members/taiseiyo/memos/roman.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

マルチスケールシミュレーション特論: roman numerals

はじめに

今回は課題としてアラビア数字からローマ数字に変換するという課題が出たので、その課題を解いてみる事にする

解答

プログラム

  • 正直全く良い方法分からない、、、、、
num = ARGV[0]

def divide_num(num)
  ans_list,count = [],0
  while num > 0
    tmp = num % 10
    ans_list.push(tmp * (10 ** count))
    num = num / 10
    count = count + 1
  end
  return ans_list.reverse
end

ans_list,count = divide_num(num.to_i),0

def num_to_roman_hundred(num)
  if num == 900
    print "CM"
  elsif num <= 800 && num >= 500
    num = num - 500
    if num != 0
      print "D" + "C" * (num / 100)
    else
      print "D"
    end

  elsif num == 400
    print "CD"

  elsif num <= 300 && num >= 100
    num = num - 100
    if num != 0
      print "C" + "C" * (num / 100)
    else
      print "C"
    end
  end
end

def num_to_roman_ten(num)
  if num == 90
    print "XC"
  elsif num <= 80 && num >= 50
    num = num - 50
    if num != 0
      print "L" + "X" * (num / 10)
    else
      print "L"
    end

  elsif num == 40
    print "XL"

  elsif num <= 30 && num >= 10
    num = num - 10
    if num != 0
      print "X" + "X" * (num / 10)
    else
      print "X"
    end
  end
end

def num_to_roman(num)
  roman_first = ['I','II','III','IV','V','VI','VII','VIII','IX','X']

  if num >= 1000
    tmp = num / 1000
    x = "M" * tmp
    print x

  elsif num <= 900 && num >= 100
    num_to_roman_hundred(num)
  elsif num <= 90 && num >= 10
    num_to_roman_ten(num)
  elsif num <= 10 && num >= 1 
    print roman_first[num-1]

  end
end

for i in ans_list do
  num_to_roman(i)
end

print "\n"

自分で書いてて思います。頭悪い実装です(笑)

(深夜にやっていて頭回ってないという言い訳をしときます(笑))

個別の関数毎の説明

  • divide_num 関数の中で受けとった数字を分解

  • num_to_roman_hundred, num_to_roman_ten はそれぞれの桁数に合わせてローマ数字に変換を行う

  • num_to_roman で全ての数字をローマ数字に変換

答え合わせ

では作成したプログラムが正しいかどうかの答え合わせをしましょう!!

  • 以下のようなプログラム(a.sh)を作成します

    for i in $(seq 1 3999);do
      ruby a.rb $i # a.rb はソースコードの名前なので何でもいいです
    done
    
  • bash a.sh > tmp.txt

  • エクセルから 3999 までのローマ数字を作成(1 分以内で可能)し、tmp2.txt として保存

  • diff tmp.txt tmp2.txt

何も出力されなければ成功!!

逆に出力されたら失敗してます。。。

GitHub

以下の URL に全てのプログラムと txt file を置いておきます。


  • source ~/Downloads/git/grad_members_20f/members/taiseiyo/memos/roman.org
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

heroku run rails db:migrateできなかった時の話

herokuでデプロイしようとした際、データベースにマイグレーションの情報を入力

% heroku run rails db:migrate

入力、、、なんか違和感を感じる

logを遡っていくと

Mysql2::Error: Table 'heroku_XXXXXXXXXXXXXXXX.users' doesn't exist

 〜省略〜

とエラーが出てる

heroku上のuserテーブルが存在していないってこと?

当然、urlを指定しても画面には

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

と表示される。

データベースのステータス確認

% heroku run rails db:migrate:status 

Status   Migration ID    Migration Name
--------------------------------------------------
   (2.2ms)  SELECT `schema_migrations`.`version` FROM `schema_migrations` ORDER BY `schema_migrations`.`version` ASC
  down    202010XXXXXXXX  Create posts
  down    202010XXXXXXXX  Create active storage tablesactive storage
  down    202010XXXXXXXX  Devise create users
  down    202010XXXXXXXX  Create comments
  down    202011XXXXXXXX  Add columns to users

全ファイルupされてない。。。

試したこと

ローカルリポジトリでは問題なく反映されていたものの、一度こちらをリセットしてみようと試みる。

% rails db:migrate:reset

すると

Mysql2::Error: Table 'power_spot_development.users' doesn't exist

  〜省略〜

えー
ローカルでもエラー発生

herokuでの状況と全く一緒で全ファイルがdown

マイグレイトできない

解決策

rails db:migrate:up VERSION=Migration ID

で一つづつファイルupしてみた。
すると

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20201021042500  Create posts
   up     20201023030611  Create active storage tablesactive storage
   up     20201023054938  Devise create users
   up     20201030032407  Create comments
   up     20201101015416  Add columns to users

全部マイグレーションできた!

herokuの方でも同じ動作を行う

heroku run rails db:migrate:up VERSION=Migration ID

全部upになった。

挙動も正常を確認

疑問点

なぜ、一つづつならマイグレートできたのか分からない

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

【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

この時点で
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で続きを読む

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で続きを読む

【Ruby】基本文法を個人的にまとめた。

Rubyについて、理解を深めるためにまとめる。
随時更新していく。

参考文献
☆伊藤潤一『プロを目指す人のためのRuby入門』

[コマンド]
irb(Interactive Ruby)➡︎REPL(Read-eval-print loop、対話型評価環境)の1つ
             ターミナルで動作確認ができる

$ irb
irb(main):001:0> 1 + 2
=> 3
irb(main):002:0> a = 'Hello, World'
=> "Hello, World"
irb(main):003:0> exit

ruby➡︎同じくターミナルで動作確認ができる

--sample.rb--
a = 'こんにちは'
puts a

--ターミナル--
$ ruby sample.rb
こんにちは
【FizzBuzz】
def fizz_buzz(n)
    if n % 15 == 0
        "Fizz Buzz"
    elsif n % 3 == 0
        "Fizz"
    elsif n % 5 == 0
        "Buzz"
    else
        n.to_s
    end
end

puts fizz_buzz(1) --1
puts fizz_buzz(2) --2
puts fizz_buzz(3) --Fizz
puts fizz_buzz(4) --4
puts fizz_buzz(5) --Buzz
puts fizz_buzz(6) --6
puts fizz_buzz(15) --Fizz Buzz
【unless文】
status = "error"
message =
    unless status == "ok" --「if status != "ok"」と同義
        "異常発生"
    else
        "正常"
    end
puts message
【case文】
country = "イタリア"
say =
    case country
    when "日本"
         "こんにちは"
    when "アメリカ"
         "Hello"
    when "イタリア"
         "Ciao"
    else
         "???"
    end
puts say
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

RubyでFirebase Authenticationが発行したID Tokenを検証する

https://firebase.google.com/docs/auth/admin/verify-id-tokens

上記URLにやり方が記載されているのですが、Rubyに関してはSDKが存在しないため自前で実装する必要があります。
https://github.com/fschuindt/firebase_id_token

こういうgemもあるのですが、Firebaseが公開している証明書をキャッシュするためだけにRedisが必要なので、Redisを使っていないプロジェクトではこのためだけにRedisを用意するのは過剰です。

https://satococoa.hatenablog.com/entry/2018/10/05/210933

JWTの検証に関してはこの記事が非常に参考になりますが、証明書のキャッシュ部分だけはRials.cacheに依存していてイマイチ。またHTTPクライアントはFaradayを利用したかったこともありFaradayを使ってなんとかいい感じにできないかと考えていたところ、いい感じのFaraday Middlewareがあったのでその紹介をします(本当はJWTの検証部分も解説しようと思ったんですが、ぐぐってみたら上記の記事を発見して書きたいことが書いてあったのでやめました)

faraday-http-cache

https://github.com/sourcelevel/faraday-http-cache

HTTPのレスポンスのCache-Controlヘッダーに含まれるmax-ageの値を利用していい感じにキャッシュしてくれるMiddlewareです。キャッシュの保存先としてキャッシュ用のクラスのインスタンスを渡すことができ、任意の場所にキャッシュできます。サンプルではRails.cacheを渡していますが、ファイルやDBやRedisなどに保存したい場合は同じインターフェースをもつクラスのインスタンスを作成して渡すこともできます(もちろんRails.cacheのストレージをを変更しても良いです)
証明書や公開鍵に関しては、Cache-Controlヘッダーに含まれるmax-ageの値を利用してキャッシュすることが推奨されているので、このgemはうってつけです。

connection = Faraday.new do |builder|
  builder.use :http_cache, store: Rails.cache, logger: Rails.logger
  builder.request :url_encoded
  builder.response :json, parser_options: { symbolize_names: true }, content_type: 'application/json'

  builder.adapter Faraday.default_adapter
end

connection.get('https://www.googleapis.com/robot/v1/metadata/x509/securetoken@system.gserviceaccount.com')

使い方としては上記のような形です。
特段変わったことをせず普通にFaradayを使っていればキャッシュされるようになるので非常に便利です。
またloggerを設定することで、キャッシュから取得しているのかどうかがわかるようになるので、loggerも合わせて設定することをおすすめします。ログレベルはdebugなので、本番環境でやたらログが出ることもありません(本番環境のログレベルがinfo以上である前提ですが…)

FirebaseはJWKsを公開しているわけではない罠

最初公式のドキュメントにある証明書がJWKsだと勘違いをしていてハマりました。実際は独自のフォーマットで、しかも公開鍵ではなく証明書なので、証明書から公開鍵に変換する必要があるする必要があります。
GCPではJWksが公開されてるので、Firebaseも同様にJWKsだろうと思いこんでしまうと罠にはまります。
JWKsであれば、JWTのdecodeの際にjwksのHashを渡すことでJWT.decodeがいい感じに公開鍵を使って検証してくれて楽なのに残念です…。

と、記事をここまで書いて、「もしかしたらJWKsも公開されてるのでは?」と思ってぐぐってみたところ、公式サイトにはなかったJWKsを発見しました。
https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com

これを用いれば以下のようにシンプルに書くことができます。

connection = Faraday.new do |builder|
  builder.use :http_cache, store: Rails.cache, logger: Rails.logger
  builder.request :url_encoded
  builder.response :json, parser_options: { symbolize_names: true }, content_type: 'application/json'

  builder.adapter Faraday.default_adapter
end

res = connection.get('https://www.googleapis.com/service_accounts/v1/jwk/securetoken@system.gserviceaccount.com
')

payload, =  JWT.decode(id_token, nil, true, { algorithms: ['RS256'], jwks: res.body })

シンプルに書けるようになりますが、公式ドキュメントに書かれているURLではないため、利用する際には注意してください。

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

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で続きを読む

roman numbers

課題

https://qiita.com/daddygongon/items/2d0a73a51ddab2d9da1b

問題

アラビア数字(arabic numerals)を受け取って,ローマ数字(roman numerals)を返すmethodを書きなさい.

解答

#!/usr/bin/env ruby
# frozen_string_literal: true

class Integer
  def to_roman()
    symbols = %w[I V X L C D M]
    roman = digits.each_with_index.map do |n, i|
      one = symbols[i * 2]
      five = symbols[i * 2 + 1]
      ten = symbols[(i + 1) * 2]
      case n
      when 1..3 then one * n
      when 4 then one + five
      when 5 then five
      when 6..8 then five + one * (n - 5)
      when 9 then one + ten
      end
    end
    roman.reverse.join
  end
end

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100].each do |i|
  puts(i.to_roman)
end

学んだこと

Integerクラス

数値に対して 999.to_roman を実装したいなら、Integer クラスを拡張する。

class Integer
  def to_roman()
    # ここに書く
  end
end

冗長な self

self を書かなくてもいい。

roman = self.digits.each_with_index.map do |n, i|

ではなく

roman = digits.each_with_index.map do |n, i|

  • source ~/go/src/github.com/TeamNishitani/grad_members_20f/members/iPolyomino/roman_numbers.org
  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

配列とif文を使って文字列をleet文字に変換

概要

  1. 結論
  2. 前提条件
  3. どのようにコーディングするか
  4. 開発環境

結論

文字列とそれに対応するleet文字列をそれぞれ配列にすることで、変換プログラムを作ることができる。

前提条件

Rubyで文字列「A、I、U、E、O」をleet文字列「4、1、(_)、3、0」に変換する。

どのようにコーディングするか

leet.rb
input = gets.split("")    #入力された文字列を配列化する

input.each {|s|
    string = ["A", "I", "U", "E", "O"]    #変換元の文字列を配列にする
    leet = ["4", "1", "(_)", "3", "0"]    #変換後の文字列も配列にする
    a = string.index(s)    #配列inputの値が配列stringの何番目にあるか探す

    if a == nil
        print s    #該当がなければそのまま出力
    else
        print leet[a]    #該当があれば配列leetの同じインデックス番号の値を出力
    end
}

if文やcase文のみで変換を行っていく方法もありますが、上記のやり方で行数を少なくすることができました(処理スピードはあまり変わらないようですが)。

開発環境

Mac catalina 10.15.7
Vscode
Ruby 2.6.5

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

Google recruit problem (exp and prime)

概要

ネイピア数 e の連続する 10 桁の数のうち、最初の素数を Ruby で求める

ただし、e は 200 桁まででよい

2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274274663919320030599218174135966290435729003342952605956307381323286279434907632338298807531952510190

ref:

(Ruby 勉強中)

コード

 1  def is_prime(num)
 2    warden = true
 3    for i in Range.new(2, Math::sqrt(num - 1))
 4      if num % i == 0
 5        warden = false
 6        break
 7      end
 8    end
 9    return warden
10  end
11  
12  exp = gets.chomp
13  exp = exp.delete('.')
14  
15  for i in Range.new(0, exp.length - 10)
16    ten_digits = exp.slice(i .. i + 9).to_i
17    if is_prime(ten_digits)
18      puts ten_digits
19      break
20    end
21  end

line1~10:

  • 素数かどうかを判定する method を作成する

ine12~13:

  • 以下のテキストファイルを読み込む

ファイル:

2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274274663919320030599218174135966290435729003342952605956307381323286279434907632338298807531952510190
  • 小数点を取り除く

line15~21:

  • ループを回して、前から順に連続する10桁の数字が素数かどうかを判定する
  • 素数が見つかれば終了し、見つかった素数を出力する

結果は以下の通り

7427466391

  • source ~/doc/lecture/multi-scale/grad_members_20f/members/yukiue/docs/google_recruit.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby で文字列を置換する方法 gsub

Rubyで文字列を置換:Stringクラスのgsub

Ruby初学者である自分のための、Ruby 2.7.0についての記事です。

文字列の一部を特定の文字列に置き換える方法です。

  • 基本的な使い方 gsub(pattern, replace)

例えば、"def"を"!!"に置き換えたい場合

# 公式ドキュメントより
'abcdefg'.gsub(/def/, '!!')  # => "abc!!g"

# 変数を使う場合(自分の解釈)
S = "abcdefg" # 文字列を変数Sに代入します
puts S.gsub(/def/, '!!')

# 出力される文字列 abc!!g

一番簡単な部分だけ抜粋しています。詳細は公式ドキュメントを確認します。
https://docs.ruby-lang.org/ja/latest/method/String/i/gsub.html

  • このエントリーをはてなブックマークに追加
  • 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で続きを読む

コピペで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で続きを読む

記事一つでわかるRuby

言語の種類

まず初めにプログラミング言語の種類について深ぼっていきます。
プログラミング言語は大きく分けて下記の二つに分かれます。

フロントエンド言語

フロントエンド言語とはwebサイトの見た目の部分を司る言語となります。
これも、厳密に言うと大きく二つに分類されます。
マークアップ言語
見た目の部分を作り込む言語。HTMLとCSSのことを指します。
プログラミング言語
フロントエンド言語の中のプログラミング言語は主にJavascriptがあります。
Javascriptは、動的なwebサイトを実現するときによく採用される言語です。

バックエンド言語(サーバーサイド言語)

フロントエンドが見た目の部分を司るのに対し、こちらは内部の構造を司るものになる。例えば、自動販売機にお金を入れオレンジジュースのボタンを押すとお金が計算され、ジュースとお釣りが出てくるような物です。
こちらは、フロントエンド言語と違い全てプログラミング言語となります。
それぞれ、言語には特化したものがありニーズに合わせて使い分けるのが良い。
例)python、ruby、java、C、C#、C++、PHP等

フレームワークとは

フレームワークとは、簡単に言えばプログラミングの言語を使うための便利ツールのような物です。本来であれば、膨大な量のコードを書かなければいけないのに対し、フレームワークを使用することで、簡単にシステムを開発することができます。

Ruby on Rails

Rubyのフレームワーク。開発を効率化する仕組みが多数用意されているため生産性が高い。

Laravel

PHPのフレームワーク。手軽さや扱いやすさから人気を集め最近ではよく使用されるようになっている。

上記以外にも多数のフレームワークが存在するがここでは省略します。

今回学んだ言語

題名の通り今回学んだ言語はRubyとなります。Rubyは習得が比較的簡単かつ最近注目されてきている言語の一つである。

文字,数字の出力、計算

・文字の出力

puts "文字が出力される"

文字が出力される #出力結果 

・数字の出力

puts 10

10 #出力結果

・計算の出力

puts 5 + 5
10 #出力結果(足し算を行う)

puts 5 - 5
0 #出力結果(引き算を行う)

puts 5 * 5
25 #出力結果(掛け算を行う)

puts 5 / 5
1 #出力結果(割り算を行う)

puts 5 % 5
0 #出力結果(割り算の余りを出す)

# 組み合わせも可能
puts (5 + 5) * 5
50 #出力結果

変数

変数とは簡単に言えば数学における代入と同じです。
例えば、aに数字の10を代入したいときは以下のようなコードで記します。

a = 10
puts a
10 #出力結果

変数には文字列であったり四則計算も代入することができます。変数における=は等しいではなく、代入を指します。

条件分岐処理(if文)

条件分岐の処理を行いたいときはif文を用いて行います。

#条件分岐がないの場合
if "条件"
  "処理の内容"
end

#条件分岐がある場合
if "条件"
  "処理の内容"
else
  "処理の内容"
end

#条件分岐が複数の場合
if "条件"
  "処理の内容"
elsif "条件"
  "処理の内容"
else
  "処理の内容"
end

ここで重要なのが必ずendをつけること。付けないと、どこで処理が終わって良いのかわからず、エラーが発生してしまいます。
条件には下記のような内容を記載します。

#AとBが等しいかどうか(=ではなく必ず==にすること)
A == B

#AはB以下かどうか
A <= B

#AはB未満またはAはC以上かどうか(||でまたはを意味する。複数つけることも可能)
A < B || A >= C

#AはBより大きいかつAとCが等しいかどうか(&&でかつを意味する。複数つけることも可能)
A > B && A == C

&と&&、|と| |の違い
&は共通する要素を出力し、&&はtrueかfalseを出力する。
同様に|は和集合の値を出力し、||はtrueかfalseを出力する。

繰り返し処理(timesメソッド、whileメソッド、eachメソッド等)

繰り返しの処理を行いたいときは、様々な方法があるが代表的なものをあげると、times文、while文、each文などがある。

timesメソッド

回数を指定する時に用いる

3.times do
  puts "アイウ"
end

アイウアイウアイウ #出力結果

whileメソッド

条件式を用いて、それが真で有る限り繰り返す

num = 0
while num < 5
  puts num
  num += 1
end

#出力結果
0
1
2
3
4

eachメソッド

eachメソッドは使う頻度がかなり多く、繰り返し処理の中で最も使われていると言っても過言ではないです。のちに出てくる配列を用いるため、そちらと照らし合わせて確認するのをお勧めします。

nums = [a,b,c,d,e]
nums.each do |num|
 puts num
end
# 出力結果
a
b
c
d
e

変数numsに代入した値を順に繰り返し変数numに代入していきながら、処理を繰り返し、全ての値が終了した時、処理が終わります。

配列とハッシュについて

配列

配列とは、複数の値をまとめて管理する時に用います。
配列は変数に代入することもできる。

lists = [a,b,c,d,e]

puts lists[0]
a #出力結果

puts lists[4]
e #出力結果

配列は左から順番に0から数字が割り当てられ、出力の際は上記のように記載する。

ハッシュ

ハッシュは配列と一見似ているが、簡単に言えば辞書のようなものです。
ハッシュにはキーとバリューというものが存在していて、それらで管理するこの方式をキーバリューストアといいます。こちらも変数に代入可能です。コードの記述の仕方は以下の三つとなります。

lists = { "key" => "value" }
lists = { :key => "value" }
lists = { key : "value" }

puts lists[key]

上記のように出力を行う。

※配列とハッシュは組み合わせることも可能。

メソッド、class(クラス)、インスタンスについて

メソッド

メソッドとは今までも条件分岐であったり、繰り返し処理を行う時に使用してきましたが、簡単な処理の塊と覚えると楽です。
メソッドは自分で作成することも可能でその際は、defメソッドを使用します。

def "変数名"
  "メソッドの処理"
end

def "変数名"("引数1", "引数2")
  "メソッドの処理"
end

defで指定したメソッドは変数名を記載することで呼び出すことができます。また引数というものを指定することで、スコープの外の変数を使用することができるようになります。

スコープ
スコープとは、変数を扱える範囲のことです。メソッド内で定義した変数はメソッド外で使用することができないのと同時に、メソッドを定義した後に記述した変数はメソッド内では使用できません。それらの範囲のことを専門用語としてスコープと呼びます。
引数
引数とは、スコープ外の変数を使用したい時に用います。例えば、引数を指定することで、引数の値により処理内容を変化させることができるので汎用性が上がります。

class(クラス)

クラスとは簡単に説明すると沢山のメソッドをまとめた箱のようなものです。
クラスを指定してあげることで、コードの可読性が上がったり、保守がしやすくなります。よく設計図と言い表されることがあります。

class "クラス名"{
  def "変数名"
  end
  def "変数名"
  end
}

上記のように記載します

インスタンス

簡単に説明すると、classの中に定義したメソッドを呼び出すためのもの。変数名.newをクラス外に記載することで、使用可能になる。

class "Item"{
  def "hello"
    puts "こんにちは"
  end
  def "変数名"
  end
}

item = Item.new
item.hello

こんにちは # 出力結果

上記のようにインスタンスの生成を行う。

まとめ

まだまだ、rubyで行えることはたくさんありますが、量があまりにも膨大なため今回はここまでとします。ここまでの内容は基礎の範囲なので、必ず抑えておきたいポイントだと思いました。次回はRubyのフレームワークのRuby on Railについての記事を書いていきます。

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