20200721のRubyに関する記事は16件です。

【Rails】renderメソッドの引数のカッコの有無について

renderメソッドの引数にはカッコはつける?つけない?

結論から言うと、どちらでもOKです。
Rubyにおけるメソッドの呼び出しではカッコを省略することができます。

なので以下の2つはどちらの書き方でも問題ありません。

posts_controller.rb
render("posts/new")
posts_controller.rb
render "posts/new"
※ただし、複雑なコードを書く場合は、区切りの位置をわかりやすくする為にカッコをつけたほうがいいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

A server is already running. Check /[WORKDIR]/tmp/pids/server.pid.の解決方法

状態

docker-compose upしたら、

A server is already running. Check /[WORKDIR]/tmp/pids/server.pid.
 Exiting

と出てきてしまいサーバーが立ち上がらないことがありました。

原因

サービスを起動したままコンテナを無理やり消してしまうとpidファイルが残ってしまうことが原因です。

解決方法

指示通り

tmp/pids/server.pid.

をみに行くと、ここに数字が書かれています。
server.pid.ファイルを消して、docker-compose upすれば解決します。

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

マークダウン記法でDB設計【残り55日】

使用したマークダウン記法
[#]
見出しとして使用
数を増やすとfont-sizeが小さくなっていく
HTMLでいうh1~h6タグ

[|文字列|]
表の作成として使用
コロンを文字列の左、左右、右につけることで左寄せ、中央揃え、右寄せにできる

[-]
箇条書きとして使用
ハイフンの後に半角スペースを入れること

マークダウン記法でDB設計.png

テーブル設計

users テーブル

Column Type Option
name string null: false
email string null: false
password string null: false

Association

  • has_many :room_users
  • has_many :rooms, through :room_users
  • has_many :messages

カラム、アソシエーションが一目瞭然なのでアプリケーション開発には必須の作業。

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

[Rails]Administrateでメインアプリで使っていたヘルパーメソッドを動作させる方法

困ったこと、やりたいこと

\\\メインアプリのヘルパーメソッドが使えない///

Railsアプリで管理画面を作成するために、Administrateというgemを使用した時のことです。
メインアプリのビューで使うためにhelperファイルに定義したhelperメソッドを、admin以下のビューでも使おうとしたところ、下記のエラーが…

スクリーンショット 2020-07-21 21.00.03.png

image_present?は、メインアプリのビューで使用しているヘルパーメソッド(自分でhelpers/shops_helper.rbに定義したもの)です。
メインアプリではNoMethodErrorにならないのに…どうすればいいの???

環境

  • Ruby 2.5.1
  • Rails 5.0.7.2
  • Administrate 0.14.0

結論

1. config/application.rbファイルに、下記の設定を記述する。
2. アプリケーション(サーバー)を再起動する

以上。簡単でした笑

config/application.rb
module Hoge
  class Application < Rails::Application
    # 他のconfigが書かれてるかも

    # ここから
    config.to_prepare do
      Administrate::ApplicationController.helper Hoge::Application.helpers
    end
    # ここまで
  end
end

Hogeはアプリ名です。通常ならば、1行目のmoduleの後ろにrails newした時のアプリ名が記載されているはずです。

Administrate::ApplicationController.helper Hoge::Application.helpers

Hogeの部分を、ご自身のアプリ名に置き換えてください。

しっかりGitHubのIssuesに挙げられていて、解決済みだった

Best way to include main app helper modules in administrate? #334
スクリーンショット 2020-07-21 20.51.34.png

余談

参考記事に挙げたGitHubのIssuesを見ていると、他の方法もあるよう。

スクリーンショット 2020-07-21 20.52.13.png

こちらの方法は、
1. helpersディレクトリの下にadministrateディレクトリを作成
2. そこにapplication_helper.rbファイルを作成
3. そこにヘルパーメソッドを再度定義する
という方法のようです。

試しにやってみた

helpers/administrate/application_helper.rb
module Administrate::ApplicationHelper
  def image_present?(shop)
    if shop.shop_images.present?
      image_tag "#{shop.shop_images[0].shop_image}", class: 'top-cover-img shop-img'
    else
      image_tag asset_path('no-image.png'), class: 'top-cover-img shop-img'
    end
  end
end

ポイントは、moduleの宣言の際に、クラスの継承?(Administrate::ApplicationHelper)を記述することだそうです。
これがないとRouting Errorになります。
スクリーンショット 2020-07-21 21.10.13.png

実現できるが、DRY原則に反する

一応上記の方法でも実現できましたが、同じヘルパーメソッドを2回定義することになるので、DRY原則に反するなと思いました。

無駄なファイルも作成しなくていいので、冒頭の方法をおすすめします。

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

【ポートフォリオを作成する方へ】ミスを減らして、読みやすいコードへ -Rubocop Airbnb-

Rubocop Airbnbとは

書かれたコードをコーディング規約に沿っているかチェックしてくれるライブラリです。構文解析ツールとしてRubocopが有名ですが、色々な設定があるので、今回は開発現場用にカスタマイズされたrubocop-airbnbを導入していきます。

導入

gemをインストール
Gemfile
 group :development, :test do
   gem 'rubocop-airbnb'
 end
 $ bundle install
ファイル作成

Gemfileと同じディレクトリに.rubocop.yml と .rubocop_airbnb.ymlを新規作成します。

.rubocop.yml
inherit_from:
  - .rubocop_airbnb.yml

AllCops:
  Exclude:
  - 'bin/*'
  - 'config/**/*'
  - 'db/**/*'
  - 'spec/spec_helper.rb'
.rubocop_airbnb.yml
require:
   - rubocop-airbnb

呼び出した際にエラーが出る時はここのファイルの"."や"_"のつけ忘れや入力ミスの可能性があるので気をつけてください。

rubocopの実行
$ rubocop --require rubocop-airbnb
自動修正方法
$ rubocop --require rubocop-airbnb -a

まとめ

修正は後になるほど大変になるので、ぜひコードを書き始める前に導入してみてください。

参考

https://github.com/airbnb/ruby/tree/master/rubocop-airbnb

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

【Ruby on Rails】Fat Controllerの解消〜まずはロジックをモデルへ〜

はじめに

今回初投稿となります。
スクールでRuby on Railsを学習し、リファクタリングのカリキュラムもあったのですが、その時点では完成させることに必死で、ロクにリファクタリングもしていなかったので今回ControllerのロジックをModelへ移し、コントローラーをすっきりさせる方法を書いていきます。

そもそもFat Controllerとは?

コントローラー自体の行数が多く、処理の流れを追いにくくなってしまっているControllerの状態を指します(簡単に言えば見通しが悪い)。
スクールでは全く意識せずにControllerに全てのロジックを記述していたが、「実務レベルのコードに近づけたい!」という思いから現在も個人アプリのFat Controllerと闘っています(笑)

切り分ける手順

まずは膨大に膨れ上がったControllerのロジックを全てModelへ移してしまうくらいの気持ちでいきましょう。
Controllerに記述するpublicメソッドはアクションだけで十分です。

そしてロジックを移す先は基本的にModelで問題ないと思います。

例:ユーザーの持つアイテムのみを引っ張ってきたい場合

切り分け前↓

users.controller.rb
def show
  @useritems = Item.includes(:images).where(user_id:(current_user.id)).order(id: "DESC") if user_signed_in?
end

deviseを使用して認証機能を実装しているため、「current_user.id」で現在ログイン中のユーザーのidが取得できます。つまり現在ログイン中のユーザーのマイページ(showアクション)にユーザーのアイテムの情報を引っ張ってきたいわけです。
しかしこのデータの取得、実は「items.controller.rb」のindexアクションでも使用しています。ということでModelにメソッドを定義し、ユーザーのアイテムを取得する記述をまとめたいと思います。

まずはController

users.controller.rb
def show
  @useritems = Item.user_items_get(current_user.id) if user_signed_in?
end

Model側で「current_user」は使用できないので引数で渡します。

続いてModel

Item.rb
def self.user_items_get(user_bigint)
  Item.includes(:images).where(user_id:(user_bigint)).order(id: "DESC")
end

この様にする事でItemControllerのindexアクションでも

items.cotroller.rb
def index
  @useritems = Item.user_items_get(current_user.id).limit(10) if user_signed_in?
end

この様に使用することができます。

Modelにメソッドを書いてControllerで呼び出すとなると、初学者は大層なことに感じてしまいがちですが、Controllerをすっきりさせる上では重要なので是非活用してみて下さい。(しまったこの例行数減ってないや、、、笑)

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

Rubyのパターンマッチを使って簡単なプログラミング問題を解いてみた

業務で必要になった簡単な文字列処理をRuby 2.7で導入されたパターンマッチを使って解いてみました。

今回のお題はこちら

"1, 5, 10-12, 15, 18-20"

という文字列から

[1, 5, 10, 11, 12, 15, 18, 19, 20]

という配列を作りたい。どう書く?

僕の解答例

こんな感じで解いてみました。
Minitestによるテストも一緒に書いてます。

require 'minitest/autorun'

def parse_as_array(str)
  str
    .scan(/\d+(?:-\d+)?/)
    .flat_map{|s|
      case s.split('-').map(&:to_i)
      in [n]    then n
      in [n, m] then [*n..m]
      end
    }
end

class ParseAsArrayTest < Minitest::Test
  def test_parse_as_array
    str = "1, 5, 10-12, 15, 18-20"
    expected = [1, 5, 10, 11, 12, 15, 18, 19, 20]
    assert_equal expected, parse_as_array(str)
  end
end

ざっくり解説

上のparse_as_arrayメソッドのロジックを上から順に分解しながら説明してみます。

まず、正規表現を使って文字列から数字とハイフンだけを配列として抜き出します。

str = "1, 5, 10-12, 15, 18-20"
str.scan(/\d+(?:-\d+)?/)
#=> ["1", "5", "10-12", "15", "18-20"]

正規表現の読み方がわからない!という人はこちらの記事を読めばわかるはずです。

さらに、その配列をそれぞれハイフンでsplitします。

arr = ["1", "5", "10-12", "15", "18-20"]
arr.map{|s| s.split('-')}
#=> [["1"], ["5"], ["10", "12"], ["15"], ["18", "20"]]

ただし、そのままだと文字列になってしまうので、to_iメソッドで整数に変換します。

arr = ["1", "5", "10-12", "15", "18-20"]
arr.map{|s| s.split('-').map(&:to_i)}
#=> [[1], [5], [10, 12], [15], [18, 20]]

そうすると、"-"が含まれる文字列は要素が2個に、それ以外は要素が1個になります。
この「1個か、2個か」をパターンマッチで条件分岐して、なおかつ変数に代入します。

arr = [[1], [5], [10, 12], [15], [18, 20]]
arr.each {|a|
  case a
  in [n]
    # 要素が1個の場合の処理
    pp n
  in [n, m]
    # 要素が2個の場合の処理
    pp [n, m]
  end
}
#=> 1
#   5
#   [10, 12]
#   15
#   [18, 20]

Ruby 2.7のパターンマッチ構文の使い方についてはこちらの記事をご覧ください。

さらに、"10-12"のような文字列は[10, 11, 12]のような配列に変換する必要があります。
これは範囲(Range)を使って実現します。

n = 10
m = 12
[*n..m]
#=> [10, 11, 12]

ハイフンを含まない数字(1や5)はそのまま数字として使えばOKです。

ここまでのアイデアを組み合わせてメソッドチェーンにすると、次のようなコードが書けます。(パターンマッチ構文はthenを使ってinを1行にまとめています)

str = "1, 5, 10-12, 15, 18-20"
str
  .scan(/\d+(?:-\d+)?/)
  .map{|s|
    case s.split('-').map(&:to_i)
    in [n]    then n
    in [n, m] then [*n..m]
    end
  }
#=> [1, 5, [10, 11, 12], 15, [18, 19, 20]]

ただし、上の結果を見るとわかるように、"10-12"や"18-20"の変換結果はネストした配列になってしまっています。
そこで、mapの代わりにflat_mapを使ってフラットな配列が返るようにします。

str = "1, 5, 10-12, 15, 18-20"
str
  .scan(/\d+(?:-\d+)?/)
  .flat_map{|s|
    case s.split('-').map(&:to_i)
    in [n]    then n
    in [n, m] then [*n..m]
    end
  }
#=> [1, 5, 10, 11, 12, 15, 18, 19, 20]

これをメソッド化すれば完成です!

def parse_as_array(str)
  str
    .scan(/\d+(?:-\d+)?/)
    .flat_map{|s|
      case s.split('-').map(&:to_i)
      in [n]    then n
      in [n, m] then [*n..m]
      end
    }
end

str = "1, 5, 10-12, 15, 18-20"
parse_as_array(str)
#=> [1, 5, 10, 11, 12, 15, 18, 19, 20]

パターンマッチを使わない場合

ほぼ同じ考え方でパターンマッチを使わない書き方もできます。

def parse_as_array(str)
  str
    .scan(/\d+(?:-\d+)?/)
    .flat_map{|s|
      n, m = s.split('-').map(&:to_i)
      m ? [*n..m] : n
    }
end

あれ、こっちの方がシンプルかも・・・???

その他の解答例

弊社内で出てきたその他の解答例です。

# evalを使うパターン
def parse_as_array(str)
  str
    .gsub('-', '..')
    .split(',')
    .flat_map {|x| Array(eval(x)) }
end
# evalを使うパターンその2
def parse_as_array(str)
  str
    .gsub(/(\d+)-(\d+)/, '*\1..\2')
    .then{|s| eval("[#{s}]")}
end

いろいろな解き方があって面白いですね!

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

いいね機能(Ajax)の実装

自分用にまとめます。

実装

コントローラー・モデル作成済み

routes.erb
  Rails.application.routes.draw do
    resources :posts do
      resources :comments, only: [:create, :destroy]
      resource :likes, only: [:create, :destroy]
    end
  end

likesはIDの付与不必要のためresourceになる


post.rb
  def liked_by?(user)
    likes.where(user_id: user.id).exists?
  end

いいねしたかどうかを確認できる


likes_controller.rb
class LikesController < ApplicationController

  def create
    @post = Post.find_by(id: params[:post_id])
    # 連続クリック防止のための条件
    unless @post.liked_by?(current_user)
      @like = current_user.likes.new(post_id: @post.id)
      @like.save
    end
  end

  def destroy
    @post = Post.find_by(id: params[:post_id])
    @like = current_user.likes.find_by(post_id: @post.id)
    @like.destroy
  end

end

create,destroyそれぞれリダイレクトは不要になるので、記載している場合は削除


posts/show.html.slim
#Ajax化するために必要になるのでidを付与する
div id='like_#{ @post.id }'
   # いいねアイコンをパーシャル化
   = render 'like', post: @post
posts/_like.html.slim
# いいね削除
- if post.liked_by?(current_user)
  = link_to post_likes_path(post), method: :delete, remote: true do
    i.fas.fa-heart style='color: red;'
    = post.likes.count

# いいね
- else
  = link_to post_likes_path(post), method: :post, remote: true do
    i.far.fa-heart
    = post.likes.count

remote: trueをつけることでJavaScriptファイルを呼び出す


likes/create.js.erb
$("#like_<%= @post.id %>").html("<%= j(render 'posts/like', post: @post ) %>");
likes/destroy.js.erb
$("#like_<%= @post.id %>").html("<%= j(render 'posts/like', post: @post ) %>");

完成!

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

Rails環境構築 Rails5.2.1 ruby2.5.1 Catalina

久しぶりの投稿です。

2019年の9月からテックキャンプに通い、2020年の7月からRails自社開発企業で働き始めました!
時間は掛かってしまいましたが、念願のエンジニアデビュー初日、支給されたMacBookの環境構築で苦労したので記録しておきます。

開発環境

Rails 5.2.1
Ruby 2.5.1
MacOS Catalina10.15.6

Dockerは使ってないので完全に初期状態のPCにエディタからRubyから詰め込んで行きました。
これだけで1日終わりました。

 苦労したこと

  • command line toolsの導入
  • Rubyのバージョン設定
  • MacOSの互換性(個人ではmojaveを使用)
  • rbenvの理解

学習の基礎段階ではテックキャンプのカリキュラムで環境を構築していたので、0から自力で環境構築するのに結構時間がかかってしまいました。かなり復習になりました。

command line tools

command line toolとは「キーボードだけで操作するプログラム」です。
要はターミナルから打ち込むコマンドですね。

Xcodeの「command line tools」をインストールします。

ターミナル
$ xcode-select --install

Xcode本体を入れなくても良いという記事がありますが、私の環境ではXcode自体もインストールが必要でした。

こちらのコマンドでバージョン確認が取れればインストール成功

ターミナル
$ xcodebuild -version

 遭遇したエラー

xcode-select: error: tool 'xcodebuild' requires Xcode, but active developer directory 
'/Library/Developer/CommandLineTools' is a command line tools instance

こちらがXcode本体をインストールする前に遭遇したエラー

Xcode本体をインストール後に↓のコマンドで解決しました。

ターミナル
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

Xcodeのcommand line toolsに切り替えるコマンドみたいです。

参考記事1
参考記事2

Rubyのバージョン設定

学習時はあまり意識していませんでしたが、Rubyのバージョン管理はrbenvで行います。
複数のRubyのバージョンを管理するツールです。

homebrewでインストールします。
※homebrewも初期状態ではインストールされていないので導入が必要です。
homebrewとはMac OSのパッケージマネジャです。

ターミナル
# rbenvインストール
$ brew install rbenv ruby-build

次にbashで使えるようにするのですがMacOS Catalinaではデフォルトのログインシェルが zsh です。
mojaveでは bash でした。

まだ資料もbashのほうが多いのでここでbashにログインシェルを切り替えました。

ログインシェルとはログイン直後に設定されているシェルのことです。

切替コマンド

ターミナル
# 使用できるシェルを確認
$ cat /etc/shells

# 切替
$ chsh

# vimが開くのでファイルを修正します。

参考記事

Catalinaではターミナルが %表記 になっています。
mojaveでは  $表記 です。

地味に焦りました。

いままでbashしか使ったことがなく、bashの方が日本語の資料が豊富なのでzshをbashに切り替えた訳ですが、zshはbashの上位互換という記事を目にしました。
ターミナルでも警告が出た気がします。

早くzshにして慣れた方が良さそうな気がします。

続いてbashでコマンドを使えるようにパスを通します。

ターミナル
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc

$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

$ source ~/.bash_profile

これでrbenvが使えるようになります。

その後いよいよRubyやRails,DBをインストールしていくのですが、特に詰まるポイントもなかったので割愛します。
こちらの記事を参考にさせて頂きました。

以上です。
お付き合い頂きありがとうございました!

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

Railsで日時を日本語表記に変更

はじめに

テーブルのカラム created_at を使えば、データを作成した日時を取り出せます。
ところが、そのままの表記では使いづらいです。

今回は、 2020/7/21 9:05:50 の様な表記で取り出せるようにしたいと思います。

作成日時を表示する

まず posts テーブルのデータ @postcreated_at をそのまま取り出してみましょう。

posts/show.html.erb
<p>イベント: <%= @post.event %></p>
<p>場所: <%= @post.place %></p>
<p>作成日時: <%= @post.created_at %></p>
place event created_at
東京 花火大会 2020-07-21 00:05:50 UTC

目標としている 2020/7/21 9:05:50 の表記と大いに違うことが分かると思います。

具体的には以下の3点が異なります。

  1. UTC (世界標準時) になっている
  2. / のところが - になっている
  3. の表記が 07 になっている

順番に修正していきたいと思います。

1.世界標準時から日本時間に変更する

プログラミングは世界中で使われている為、そのままだと 世界標準時 で出力されてしまいます。
その為、日本時間 に変更しましょう。

config/application.rb
class Application < Rails::Application
  # ***** 次の1行を追加 *****
  config.time_zone = "Asia/Tokyo"
end

この1行を入れることで以下のように、世界標準時日本時間 に変更することができました。

place event created_at
東京 花火大会 2020-07-21 09:05:50 +0900

+0900 がついていますが、これは、日本時間と世界標準時との時差が+9時間であることを表しています。

2.フォーマットを変更する

日本時間にはなりましたが、フォーマットが 2020-07-21 09:05:50 +0900 では分かりにくいです。変更していきましょう。

まず、多言語に対応する為の gem をインストールし、日本語に設定します。

Gemfile
# Rails6 の場合
gem 'rails-i18n', '~> 6.0'
# Rails5 の場合
gem 'rails-i18n', '~> 5.1'
ターミナル
bundle install
config/application.rb
class Application < Rails::Application
  # 略
  config.time_zone = "Asia/Tokyo"
  # ***** 次の1行を追加 *****
  config.i18n.default_locale = :ja
end

次に、 rails-i18n に用意されている lメソッドを使用します。

posts/show.html.erb
<p>イベント: <%= @post.event %></p>
<p>場所: <%= @post.place %></p>
<!-- ***** l を追加 ***** -->
<p>作成日時: <%= l @post.created_at %></p>

さらに、config/locales/ja.ymlというファイルを作成し、フォーマットを設定しましょう。

config/locales/ja.yml
ja:
  time:
    formats:
      default: "%Y/%m/%d %H:%M:%S"

これにより、表記が以下のように変わり、+0900も消えます。

place event created_at
東京 花火大会 2020/07/21 09:05:50

フォーマットは自由に変更することができ、例えば、 が不要なら :%S を省くことで対応出来ます。

config/locales/ja.yml
      default: "%Y/%m/%d %H:%M"
place event created_at
東京 花火大会 2020/07/21 09:05
記号 意味
%Y 年(YEAR)
%m 月(MONTH)
%d 日(DAY)
%H 時(HOUR)
%M 分(MINUTE)
%S 秒(SECOND)

[参考ページ] Ruby 2.7.0 リファレンスマニュアル(strftime)

3.ゼロ(0)埋めをなくす

例えば、 7月07 と表記されるのは不自然なので 0 を削りましょう。

config/locales/ja.yml
ja:
  time:
    formats:
      # ***** %の後ろに-を追加 *****
      default: "%Y/%-m/%-d %-H:%M:%S"
place event created_at
東京 花火大会 2020/7/21 9:05:50

-を入れることで以上の様に、0埋め をなくすことが出来ました。

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

標準入力・まとめ/python,ruby

自分用メモとしてpaizaのスキルチェックやAtCoderなどでよく使いそうな標準入力をまとめました。

基本

文字

input.py
s = input()
input.rb
s = gets.chomp

数値

python
n = int(input())
ruby
n = gets.chomp.to_i

スペースで区切る

スペースで区切られた値を個別の変数に数値として入力

input
1 2 3 
python
a,b,c = map(int,input().split())
#int カンマ input ドット split に注意
ruby
a,b,c = gets.chomp.split.map(&:to_i)

スペースで区切られた値を個別の変数に文字として入力

input
red blue yellow 
python
a,b,c = input().split()
ruby
a,b,c = gets.chomp.split

スペース区切りの値をリストに入力(文字・数値)

input
spring summer autumn winter
101 102 103 104
python
x = input().split()
y = list( map(int,input().split()) )
ruby
x = gets.chomp.split
y = gets.chomp.split.map(&:to_i)
output
#x
["spring","summer","autumn","winter"]
#y
[101,102,103,104]

複数行

入力:n(行数)
   i1
   .
   .
   in

n行の値をリストに入力 [i1,i2,i3,.....,in]

python
n = int(input())
I = [ input() for i in range(n) ]
ruby
n = gets.chomp.to_i
array = []
n.times do
 i = gets.chomp
 array.push(i)
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

動画の再生時間(HH:MM:SS)を秒数にしたいし、その逆もしたい

HH:MM:SSを秒に

# rails c または以下をrequire
require "active_support/duration"

duration_text = "00:01:00"
Time.parse(duration_text) - Time.parse("00:00:00") # => 60.0

秒をHH:MM:SSに

Time.at(60).utc.strftime("%H:%M:%S") # => "00:01:00"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS SQS】SQSを構築し、EC2からメッセージをポーリングする

目標

AWS SQSを構築しメッセージをキューに投入(AWSコンソール上から投入する)、その後投入したメッセージをAWS SDKを利用してEC2から取得(ポーリング)する。

SQSとは

アプリケーション間でやりとりされるメッセージを送受信するためのAWSが提供するキューサービスのこと。
受信側がメッセージをSQSに問い合わせてメッセージを取得するPull型のサービスです。

SQSに関する基本情報は以下記事がわかりやすくまとめられていると思いました。
Amazon SQS

前提

・AWS SDK(AWS SDK for Rubyを本記事では利用)を使用可能なEC2インスタンスが構築済みであること(※)。

※AWS SDK for Rubyを構築した際の参考手順
【AWS SDK】EC2自動構築用スクリプト(1.AWS SDK for Rubyのセットアップ)

作業の流れ

項番 タイトル
1 SQSの構築
2 SQSにメッセージを送信
3 EC2からメッセージをポーリングする

手順

1.SQSの構築

SQSコンソールを開く

キューを作成をクリック

③作成するキューの設定
今回はスタンダードキューを利用します(※1)
キューの名前は任意のものを記載します。

※1 SQSには以下2つのタイプのキューが存在します。特徴を簡単にまとめます。
スタンダードキュー
 ⇒最低1回のメッセージ配信を保証するが、リクエストのタイミングによっては同一メッセージが複数回配信される可能性がある。
 ⇒メッセージ配信の順序はベストエフォート型(メッセージ送信と配信の順序が異なる可能性がある。)

FIFOキュー
 ⇒同一メッセージが複数回配信されることはない。
 ⇒メッセージ配信の順序は先入れ先出し型(メッセージ送信と配信の順序が同一となる。)
 ⇒高性能なため、スタンダードキューよりも少し料金が高い。

tempsnip.png

キューの詳細設定(※2)をします。
今回はメッセージ受信待機時間のみ20秒に設定し、それ以外はデフォルトのままとしています。

※2 以下簡単にキューの詳細設定の内容を記載致します。
可視性タイムアウト
メッセージを受信した後に一定時間、該当のメッセージを他システムから見えなくさせる機能のこと。
可視性タイムアウトとして指定した時間内にメッセージ処理を完了させ、メッセージ削除することで、
同一メッセージに対する複数回処理を防ぐことが可能となります。

配信遅延(遅延キュー)
キューにメッセージを送信した後、指定した時間が経過した後にメッセージを表示させるようにする機能

メッセージ受信待機時間
ロングポーリングorショートポーリングの設定を行うことが出来ます。
デフォルトはショートポーリング(メッセージ受信待機時間が0秒)となっており、キューが空の場合でもすぐ再リクエストを行います。
SQSはメッセージのリクエスト回数によって料金が判断されるため、空メッセージのリクエストを繰り返すと料金がかさんでしまいます。
その対策として有効なのが、ロングポーリング(メッセージ受信待機時間が0秒より上)となります。
ロングポーリングを設定すると空メッセージを受け取った場合、指定した秒数分待機する(キュー接続を持続する)ため、
SQSのリクエスト回数を減らし料金を節約することが可能となります。
要件にもよりますが、基本的にはロングポーリングを使用するのがAWSの推奨となっているようです。

tempsnip.png

キューへのアクセス設定はデフォルトのままとします。

image.png

最後に暗号化、デッドレターキュー(※3)、タグの設定もありますが今回は設定なしとします。
キューの作成をクリック

※3 デッドレターキュー
処理結果がエラーとなったメッセージを何回かリトライ後、自動的に別のキューに移動する機能
問題のあるメッセージが別キューに集中するため識別しやすくなり、当該メッセージが正規のキューに残存することを防ぎます。

tempsnip.png

2.SQSにメッセージを送信

作成したSQSキューにAWSコンソール上からメッセージを送信してみます。

①作成したSQSキューの詳細画面からメッセージの送受信をクリック
tempsnip.png

メッセージ本文を記載後メッセージ送信をクリック
この手順を何回か繰り返します。
tempsnip.png

③送信したメッセージの内容確認
メッセージ送信欄の下段にあるメッセージ受信欄からメッセージをポーリングをクリック
tempsnip.png

メッセージが表示されればOKです

tempsnip.png

3.EC2からメッセージをポーリングする

①AWS SDK for Rubyが利用可能なEC2インスタンスにポーリング実行用スクリプトを配備

# **********************************************************************************
# <機能概要>
# 指定したSQSキューのメッセージをポーリングする
#
# <機能詳細>
# 常駐プロセスとして稼働し、指定したSQSキューのメッセージをポーリングし取得時刻及びメッセージ本文を標準出力する。
# 取得したメッセージは出力後削除する。
# メッセージが空の場合は指定した時間(wait_time_seconds)分待機した後、キューが空であることを伝えるメッセージを出力する。
#
# <スクリプト用法>
# ruby <スクリプトパス>
# **********************************************************************************

require 'aws-sdk'

# キュー名
queue_name = "MyTestQueue"

# SQS操作用インスタンス作成
sqs = Aws::SQS::Client.new

# キューURLの取得
begin
  queue_url = sqs.get_queue_url(queue_name: queue_name).queue_url
rescue Aws::SQS::Errors::NonExistentQueue
  puts "A queue named '#{queue_name}' does not exist."
  exit(false)
end

loop do
  # キューからメッセージを取得
  receive_message_result = sqs.receive_message({
    queue_url: queue_url, 
    message_attribute_names: ["All"], # 全属性のメッセージを取得
    max_number_of_messages: 5,        # 最大でも5メッセージの取得
    wait_time_seconds: 20             # メッセージが空の場合は20秒待機
  })

  # メッセージを取得した時刻を取得
  timestamp = Time.new

  # メッセージが空のときはemptyメッセージ出力
  if receive_message_result.messages.nil?
    puts "#{timestamp.strftime("%Y-%m-%d %H:%M:%S")}: Message is empty."
  end


  receive_message_result.messages.each do |message|
    # 取得したメッセージ本文を表示
    puts "#{timestamp.strftime("%Y-%m-%d %H:%M:%S")}: #{message.body}" 

    # メッセージをキューから削除
    sqs.delete_message({
      queue_url: queue_url,
      receipt_handle: message.receipt_handle    
    })
  end
end

②スクリプト実行し、メッセージをポーリングする。
AWSコンソールから事前投入した3つメッセージは即座に処理され、メッセージが空の場合は指定した時間分待機することを確認できました。
またメッセージ待機中に新規メッセージをキューにプットした場合、そのメッセージは即座に処理されることも確認できました。

[ec2-user@basehost ~]$ ruby queue.rb
2020-07-21 04:13:32: test2          # AWSコンソールから事前投入したメッセージその1
2020-07-21 04:13:33: test3          # AWSコンソールから事前投入したメッセージその2
2020-07-21 04:13:33: test1          # AWSコンソールから事前投入したメッセージその3
2020-07-21 04:13:53: Message is empty.    # wait_time_secondsで指定した時間分(20秒)待機後、emptyメッセージ出力
2020-07-21 04:14:01: This message is the message sent during the waiting time.   # メッセージ待機時間中にキューにプットしたメッセージ、プット後即座に処理された。
2020-07-21 04:14:21: Message is empty.
2020-07-21 04:14:41: Message is empty.
2020-07-21 04:15:01: Message is empty.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rubocopに追加された Style/HashLikeCase Consider replacing case-when with a hash lookup

rubocopに追加された Style/HashLikeCase

case文で記述しなくても良い場合は配列で定義してしまいましょうというルール。

rubocop 0.88.0 で追加、非常に良いですね。

悪い例
type = 'test'
case type
when 'interview'
  'インタビュー'
when 'test'
  'テスト'
when 'standard'
  '通常'
end
良い例
type = 'test'
lists = {
  'interview' => 'インタビュー',
  'test' => 'テスト',
  'standard' => '通常',
}
lists[type]

公式リポジトリの情報

issueと経緯

https://github.com/rubocop-hq/rubocop/issues/8247

PRと実装

https://github.com/rubocop-hq/rubocop/pull/8280

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

redirect_toでflashメッセージが表示されない

事象

下記のように記述してもflashメッセージが表示されず、msgがクエリパラメータに含まれてしまう。

redirect_to action: :new, alert: 'msg'

解決策

下記のように記述する。

redirect_to({:action => :new} , {:alert => 'msg'})

原因

redirect_toは下記のように使用する。

redirect_to(options = {}, response_options = {})

冒頭のように記述すると、flashメッセージも第一引数として扱われてしまう。

redirect_to action: :new, alert: 'msg'
↓
redirect_to({:action => :new, :alert => 'msg'}, { })
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

redirect_toとrenderの使い分け

概要

Railsでscaffoldを使ったらcreateアクションは以下のようになる。
保存に成功した時はredirect_toでページ遷移するのに失敗したときはrenderを使っている。
この違いを解説する。

user_cocntroller.rb
def create
    @user = User.new(user_params)

    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'User was successfully created.' }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

結論

redirect_toはHTTPリクエストが走る。
renderはviewをただ表示するだけ(=URLは変化しない)

redirect_toを使用したい時

データの更新が成功したときは、HTTPリクエストを走らせて別のページに遷移する。
そうすることで、リロードで同じデータが登録されないようにする。

renderを使用したい時

保存に失敗したときは、エラーメッセージを添えてviewを表示してやるだけでOK。
無駄にリクエストを増やさないようにする。
データの登録は行われていないので、リロードされても問題ない。

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