20210112のRubyに関する記事は24件です。

iOS「自動更新サブスクリプション」のサーバー通知をRubyで受け取る

iOS「自動更新サブスクリプション」のサーバー通知をRubyで受け取る

iOS「自動更新サブスクリプション」のサーバー通知をRubyで受け取る必要があり、いろいろ調べたので備忘録としてまとめてみます!

構成

マルチプラットフォーム型のサービス
iOSアプリ:Swift
サーバーサイド:Ruby (Ruby on Rails)
※サーバーサイドでの決済システムは他のサービスを利用

おさらい:サーバー通知とは

サーバーサイドのURLをAppStoreConnectに登録しておくことで、自動更新サブスクリプションのステータスが変更したときにAppleから通知を取得することができます。

この機能があることで、入会・解約・アップグレード・ダウングレードなども配信者側はオンタイムで確認・知ることができます。

サーバー通知を受信するURL(サーバー)の登録

AppStoreConnect > 該当アプリページ を開きます。
該当アプリページのApp情報(名前などを登録するところ)の右下にある「App Storeサーバー通知のURL」にURLを登録すると、後述のタイミングでAppleStoreからの通知が登録したURLに送られてくるようになります!

サーバー通知が送られてくるタイミング

ユーザーのイベント 通知タイプ
初回購入 INITIAL_BUY
アップグレード CANCEL,DID_CHANGE_RENEWAL_STATUS, INTERACTIVE_RENEWAL
ダウングレード DID_CHANGE_RENEWAL_PREF
期限が切れた後に再購読 DID_CHANGE_RENEWAL_STATUS
期限が切れた後に別のサブスクを購読 INTERACTIVE_RENEWAL, DID_CHANGE_RENEWAL_STATUS
購読をキャンセル DID_CHANGE_RENEWAL_STATUS
Appleによる返金 CANCEL,DID_CHANGE_RENEWAL_STATUS
決済の問題でサブスクリプションの更新に失敗 DID_FAIL_TO_RENEW
ユーザーへ払い戻し REFUND
サブスクリプションの値上げに同意した PRICE_INCREASE_CONSENT
自動更新が成功 DID_RENEW

送られてくるJSONの形式

{
    "auto_renew_product_id": " ",
    "auto_renew_status": "false",
    "auto_renew_status_change_date": "2000-00-00 00:00:00 Etc/GMT",
    "auto_renew_status_change_date_ms": "0000000000000",
    "auto_renew_status_change_date_pst": "2000-00-00 00:00:00America/Los_Angeles",
    "environment": "Sandbox",
    "latest_receipt": " ", // Base64エンコードされたレシート 
    "latest_receipt_info": {
        "bid": " ",
        "bvrs": "1",
        "cancellation_date": "0000000000000",
        "expires_date": "0000000000000",
        "expires_date_formatted": "2000-00-00 00:00:00 Etc/GMT",
        "expires_date_formatted_pst": "2000-00-00 00:00:00 America/Los_Angeles",
        "is_in_intro_offer_period": "false",
        "is_trial_period": "false",
        "item_id": "1486750613",
        "original_purchase_date": "2000-00-00 00:00:00 Etc/GMT",
        "original_purchase_date_ms": "0000000000000",
        "original_purchase_date_pst": "2000-00-00 00:00:00 America/Los_Angeles",
        "original_transaction_id": "000000000000000",
        "product_id": " ",
        "purchase_date": "2000-00-00 00:00:00 Etc/GMT",
        "purchase_date_ms": "0000000000000",
        "purchase_date_pst": "2000-00-00 00:00:00America/Los_Angeles",
        "quantity": "1",
        "subscription_group_identifier": "00000000",
        "transaction_id": "000000000000000",
        "unique_identifier": "3bf0388bbca73e22cbfbce8e7b5b4c8181047dbc",
        "unique_vendor_identifier": "D89BF8D6-3B46-4F1E-895A-1061ECD31178",
        "version_external_identifier": "0",
        "web_order_line_item_id": "000000000000000"
    },
    "notification_type": "INITIAL_BUY",
    "password": " "
}

送られてくるJSONの各プロパティの意味

基本はこちらの公式サイトを見ることでわかります!
公式参考サイト

主要で利用したい値だけ今回まとめてみます
※オフィシャルサイトの英文をグーグル翻訳しただけの情報になります。正確な情報は公式サイトを確認してください。

フィールド名 意味
auto_renew_status 自動更新可能なサブスクリプション製品の現在の更新ステータス
auto_renew_status_change_date ユーザーが自動更新可能なサブスクリプションの更新ステータスをオンまたはオフにした時刻。
original_transaction_id 最初の購入のトランザクションID。この値は、ユーザーが購入を復元するか、サブスクリプションを更新する場合を除いて、同じです。
environment AppStoreがレシートを生成した環境。
latest_receipt Base64でエンコードされた最新のトランザクションレシート
latest_receipt_info 値のJSON表現。このフィールドはレシートの配列ですが、サーバー間通知では単一のオブジェクトです
cancellation_date アップルカスタマーサポートがトランザクションをキャンセルした時刻。このフィールドは、返金されたトランザクションにのみ表示されます。
cancellation_reason 返金された取引の理由。顧客がトランザクションをキャンセルすると、App Storeは顧客に返金を行い、このキーの値を提供します。の値は“1” 、アプリ内の実際の問題または認識された問題が原因で顧客がトランザクションをキャンセルしたことを示します。の値は“0” 、トランザクションが別の理由でキャンセルされたことを示します。たとえば、顧客が誤って購入した場合です。
expires_date サブスクリプションの有効期限が切れる時間、またはサブスクリプションが更新される時間
is_trial_period サブスクリプションが無料試用期間内にあるかどうかの指標。
is_upgraded ユーザーがアップグレードしたためにシステムがサブスクリプションをキャンセルしたことを示すインジケーター。このフィールドは、アップグレードトランザクションの場合にのみ表示されます。
product_id 購入した製品の一意の識別子。この値は、App Store Connectで製品を作成するときに指定し、トランザクションのプロパティに格納されているオブジェクトのプロパティに対応します。
purchase_date App Storeが、ISO 8601標準と同様の日時形式で、サブスクリプションの購入または更新に対してユーザーのアカウントに請求した時刻。
quantity 購入した消耗品の数。
subscription_group_identifier サブスクリプションが属するサブスクリプショングループの識別子。このフィールドの値は、SKProductのプロパティと同じです。
transaction_id 購入、復元、更新などのトランザクションの一意の識別子
notification_type 通知をトリガーしたサブスクリプションイベント
password レシートを検証するときにrequestBodyのpasswordフィールドに送信する共有シークレットと同じ値

送られてくるJSONをRubyで処理するコード

一言で言うと、上記のJSON形式を処理し、タイミング・各プロパティの値に合わせてアプリの処理を記載します。

#例としてreceive_appserver_notificationメソッドとして作成します
def receive_appserver_notification
    unified_receipt = params['unified_receipt'].as_json
    latest_receipt = unified_receipt['latest_receipt']
    latest_receipt_info = unified_receipt['latest_receipt_info']

   @mTestObj = TestObj.new()  #保存用のモデル、オブジェクトを用意
   @mTestObj.auto_renew_product_id = unified_receipt['auto_renew_product_id']
   @mTestObj.expires_date = latest_receipt_info['expires_date']
   #==以下省略==

   #処理があれば記載
   if @mTestObj.notification_type == 'INITIAL_BUY'
       #例えば、初期購入だった場合の処理・・・  
   end

   #保存
   begin 
      @mTestObj.save
      puts "保存完了!!"
   rescue => exception

   end

end


まとめ

1.AppStoreConnectで受信するURLを設定しましょう
2.送られてくる形式、パラメーターを理解しましょう
3.サーバーで受け取り、jsonを処理しましょう
4.値に合わせて、必要な処理を実装しましょう

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

フォームオブジェクトで複数のモデルを操作する[脱 accepts_nested_attributes_for]

かなり丁寧に書いたのでボリューム多めです。
分かっている部分はガンガン読み飛ばしてください:thumbsup:

やりたいこと

accepts_nested_attributes_forを使わず複数のモデルを操作したい。
・データの新規作成だけでなく、更新もしたい。

事前準備

accepts_nested_attributes_forとは?

関連付けられたモデルのレコードを一度に更新できるメソッドです。

例)メッセージに複数の画像をつけて送信する場合
er_for_message.png

上記の場合だと、メッセージとそれに紐づいた画像を保存できます。便利ですね。
ただ、実はこのメソッドはあまり評判が良くありません:joy:(詳しくはこちら)

そこで、代替案のフォームオブジェクトを紹介していきます。

フォームオブジェクトとは?

モデルから切り離され、フォームの処理用に独立したクラスです。

これを使うとaccepts_nested_attributes_forなしで複数のレコードを更新できます。
その他メリットや原理について詳しく知りたい方はこちらの記事を。説明がめちゃくちゃ分かりやすいです:pray:

本記事の目標

ユーザー(親)がメッセージ(子)に画像(孫)を紐づけて送信できる機能を実装します。

親、子、孫モデルは皆さんの状況に合わせて変えてください。

割愛した部分

・ルーティング設定
・画像投稿に必要なcarrierwaveの設定
・各モデルのテーブル作成

実装例

それでは見ていきましょう!

モデル

user.rb
class User < ApplicationRecord
  has_many :messages
end
message.rb
class Message < ApplicationRecord
  has_many :pictures
  belongs_to :user
  # ここに書いていたフォームのバリデーションは、フォームオブジェクトに移ります。
end
picture.rb
class Picture < ApplicationRecord
  belongs_to :message
  # 画像投稿しない方は、以下の記述は不要です。
  mount_uploader :picture, PictureUploader
end

バリデーションに関する記述がなくなりました。
スッキリして読みやすいですね:relaxed:

コントローラ

ここで注目して欲しいのは、下記の2点だけです。

assigns_attributesでパラメータから渡された値をもとに@messageの情報を更新。
MessageFormは、後ほど解説するフォームオブジェクト用のクラス。

messages_controller.rb
  # 新規作成画面
  def new
    @message = MessageForm.new
  end

  # 新規作成
  def create
    @message = MessageForm.new
    @message.assign_attributes(message_form_params)
    if @message.save
      # 成功・失敗の処理
    end
  end

  # 編集画面
  def edit
    # params[:id]の部分は、編集するオブジェクトを特定できる値を入れてください
    @message = MessageForm.new(message = Message.find(params[:id]))
  end

  # 編集
  def update
    @message = MessageForm.new(message = Message.find(params[:id]))
    @message.assign_attributes(message_form_params)
    if @message.save
      # 成功・失敗の処理
    end
  end

  private
    # ストロングパラメータ
    def message_form_params
      params.require(:message_form).permit(:body, pictures_attributes: [:picture]).merge(user_id: current_user)
    end

ストロングパラメータのpictures_attributesについては、次に説明していきます。

ビュー

メッセージの新規作成画面です。

new.html.erb
<%= form_with model: @message, url: メッセージ作成用のpath, local: true do |f| %>

  <%= f.text_area :body %>

  <%= f.fields_for :pictures do |picture| %>
    <%= picture.file_field :picture, multiple: "multiple", name: "message_form[pictures_attributes][][picture]" %>
  <% end %>

  <%= f.submit "送信" %>

<% end %>

パラメータを中心に解説します。

まず最初に、テキストエリアに「OK」と入力して送信したとします。
このときのパラメータは下記のようになります。(ターミナルで確認してみてください。)

Parameters: {"authenticity_token"=>"略", "message_form"=>{"body"=>"OK"}, "button"=>""}

bodyカラムに入力した値が入っていますね。

ここで、"message_form"となっているのは@messageMessageFormクラスから生成しているからです。(自動でこうなります。)

次に、これに画像情報を追加するなら、下記のような形にしたいですよね。

Parameters: {"authenticity_token"=>"略", "message_form"=>{画像情報, "body"=>"OK"}, "button"=>""}

そこで、file_fieldのname属性を使ってみます。

name: "message_form[pictures_attributes][][picture]"

こう書くと、画像を2枚投稿した場合のパラメータは下記になります。
(画像情報は長いので省略しています。)

 Parameters: {"authenticity_token"=>"略", "message_form"=>{"pictures_attributes"=>[{"picture"=>1枚目の画像情報}, {"picture"=>2枚目の画像情報}], "body"=>"OK"}, "button"=>""}

"message_form"下に画像情報が入ったのが分かると思います。

また、"pictures_attributes"=>[{"picture"=>がコントローラのストロングパラメータに対応しています。

ひとまず、これでコントローラに値を渡せそうですね:relaxed:

ちなみに、編集画面の場合は注意が必要です。
理由はこちらの記事に書いていますので、時間があれば参考にしてください。

フォームオブジェクト用クラス

ここが一番ややこしい部分です。
4つに分けて解説しますので、気楽に読んでください:ok_hand:

message_form.rb
class MessageForm

  # part-1
  include ActiveModel::Model
  include Virtus.model
  extend CarrierWave::Mount

  validates :body,   presence: true

  attribute :body, String
  attribute :user_id, Integer

  mount_uploader :picture, PictureUploader
  attr_accessor :pictures

  # part-2
  def initialize(message = Message.new)
    @message = message
    self.attributes = @message.attributes if @message.persisted?
  end

  # part-3
  def assign_attributes(params = {})
    @params = params
    pictures_attributes = params[:pictures_attributes]
    @pictures ||= []
    pictures_attributes&.map do |pictures_attribute|
      picture = Picture.new(pictures_attribute)
      @pictures.push(picture)
    end
    @params.delete(:pictures_attributes)
    @message.assign_attributes(@params) if @message.persisted?
    super(@params)
  end

  # part-4
  def save
    return false if invalid?
    if @message.persisted?
      @message.pictures = pictures if pictures.present?
      @message.save!
    else
      message = Message.new(user_id: user_id,
                            body: body)
      message.pictures = pictures if pictures.present?
      message.save!
    end
  end

end

part-1(モジュールの導入)

  include ActiveModel::Model
  include Virtus.model
  extend CarrierWave::Mount

  validates :body, presence: true

  attribute :body, String
  attribute :user_id, Integer

  mount_uploader :picture, PictureUploader
  attr_accessor :pictures

ここはインクルードしたモジュールなどをまとめています。

ActiveModel::Modelは、MessageFormオブジェクトを保存する際にバリデーションを使えるようにしています。validatesの部分。ActiveRecordを継承していなくてもバリデーションできるのが便利。

Virtus.modelは、このクラスの属性名と型を定義しています。attributeの部分。
何をパラメータとして受け取るのか明示しています。ちなみに、gemのvirtusをインストールしています。

CarrierWave::Mountは画像のアップロード用です。mount_uploaderの部分。

attr_accessorはメッセージに画像情報をセットするためのものです。(part-4で使います。)

part-2(オブジェクトの初期化)

オブジェクトにプロパティを持たせます。

  def initialize(message = Message.new)
    @message = message
    self.attributes = @message.attributes if @message.persisted?
  end

ここはinitializeの引数がポイント。
コントローラのnewメソッドに引数がある場合、それを受け取っています。

  def edit
    @message = MessageForm.new(message = Message.find(params[:id]))
  end

つまり、newでオブジェクトを生成した際に、すでに作成したメッセージの情報があれば、それをもとにフォームオブジェクトの属性を書き換えています。
(self.attributesの部分)

part-3(オブジェクトの更新)

そろそろ疲れてきた方はすみません。もう少しの辛抱です。。。

  def assign_attributes(params = {})
    @params = params
    pictures_attributes = params[:pictures_attributes]
    # picturesと書けば、画像情報が呼び出せる
    @pictures ||= []
    pictures_attributes&.map do |pictures_attribute|
      picture = Picture.new(pictures_attribute)
      @pictures.push(picture)
    end
    # パラメータから画像情報を削除
    @params.delete(:pictures_attributes)
    # すでに作成したメッセージの情報を更新
    @message.assign_attributes(@params) if @message.persisted?
    # フォームオブジェクトの情報を更新
    super(@params)
  end

ここではassign_attributes@messageにパラメータの値をセットしています。

assign_attributesメソッドはもともとデータ更新用のメソッドなのですが、

・フォームオブジェクトが持つ編集用の情報を、すでに作成したメッセージに反映させたい
picturesと書けば画像情報を取得orセットできるようにしたい(part-2のattr_accessor)

という理由から少し改造しています:sunglasses:

part-4(変換作業)

いよいよラスト!Messageモデルへの変換作業です。

  def save
    # 保存前にバリデーションをかける
    return false if invalid?
    # 編集する場合の処理
    if @message.persisted?
      @message.pictures = pictures if pictures.present?
      @message.save!
    else
    # 新規作成の場合の処理
      message = Message.new(user_id: user_id,
                            body: body)
      message.pictures = pictures if pictures.present?
      message.save!
    end
  end

編集の場合は、普通の保存と変わりないですね。
このときの@messageは、part-2のmessageと同じです。

大事なのは、下記のMessageオブジェクトへの変換。

      message = Message.new(user_id: user_id,
                            body: body)

フォームオブジェクトが持ってきた値をカラムに渡しています。
これでMessageFormからMessageオブジェクトへの変換が完了しました。

      message.pictures = pictures if pictures.present?

最後に、画像情報をメッセージにセットして終了です。
お疲れ様でした:clap:

環境

ruby: 2.7.1
rails: 6.0.3.3

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

ActiveStorageを使う!

アウトプットに時間を使わなきゃと思いつつ中々やらずにいたので、重い腰をあげようかと…
今回は、毎度使っていてあれ?と振り返っていることなので自分が使っていることこちらに書き出してみようと思います。

概要

  • ActiveStorageとは
  • 導入方法
  • 使用方法
  • 保存した画像を表示
  • 画像加工のツールについて
  • 感想

ActiveStorageとは

Active StorageとはAmazon S3、Google Cloud Storage、Microsoft Azure Storageなどの クラウドストレージサービスへのファイルのアップロードや、ファイルをActive Recordオブジェクトにアタッチする機能を提供します。development環境とtest環境向けのローカルディスクベースのサービスを利用できるようになっており、ファイルを下位のサービスにミラーリングしてバックアップや移行に用いることもできます。

アプリケーションでActive Storageを用いることで、ImageMagickで画像のアップロードを変換したり、 PDFやビデオなどの非画像アップロードの画像表現を生成したり、任意のファイルからメタデータを抽出したりできます。

Railsガイド引用

画像アップロード機能が簡単に実装できるGemです。
ActiveStorageはRails5.2から標準で搭載されるようになりました。

導入方法

  • Active Record モデルを用意する まず、導入したいアプリケーションのディレクトリで
rails active_storage:install

rails active_storage:installコマンドを実行すると、Active Storageに関連したマイグレーションが作成されます。
続けてマイグレートします。

rails db:migrate

生成されるテーブルは以下の二つになります。
画像用のカラムを用意する必要がない点もActive Storageの特徴の一つです。

active_storage_blobs

カラム名 保存内容
id ID
key ファイルを一意に識別するkey
filename アップロードしたファイルの名前
content_type ファイルの種類
metadata メタデータ(画像なら縦横の大きさなどが格納される)
byte_size ファイルサイズ(byte単位)
checksum チェックサム
created_at 作成日時

active_storage_attachments

カラム名 保存内容
id ID
name モデルの属性名(Userモデルのavatarとか)
record_type モデル名(Userとか)
record_id record_typeのモデルのID
blob_id active_storage_blogsのID
created_at 作成日時

使用方法

例としてpostテーブルをつかいます。

やることは

  • Active Storageのテーブルとpostテーブルのアソシエーションを定義
  • post_controller.rbにて、imageカラムの保存を許可

モデルに1つの画像を添付するには、has_one_attachedを使います。

class モデル < ApplicationRecord
  has_one_attached :ファイル名
end

has_one_attachedメソッド

各レコードとファイルを1対1の関係で紐づけるメソッドです。
has_one_attachedメソッドを記述したモデルの各レコードは、それぞれ1つのファイルを添付できます。
has_one_attached:ファイル名は、
:photo、:avatar、:hogeなど、ファイルの用途に合わせて好きなものを指定してください。

画像の保存を許可するストロングパラメーター

  private

  def post_params
    params.require(:post).permit(:image).merge(user_id: current_user.id)
  end

ストロングパラメーターについて割愛
これでイメージ画像の保存を許可できました。

保存した画像を表示

  • image_tagメソッド

img要素を生成するRailsのヘルパーメソッドです。

image_tagメソッドでは、複雑なRailsのディレクトリパスを指定しなくても、モデルから画像ファイルを呼び出して引数に記述するだけで、画像を表示するimg要素を生成します。つまり簡単。

# ファイルをモデルから指定する場合
<%= image_tag モデル.画像ファイル %>
<%= image_tag user.avatar %>

# app/assets/ディレクトリ下の画像ファイルパスでも指定できる
<%= image_tag 画像ファイルのパス %>
<%= image_tag "avatar.png" %>

自談(自分体験談そして余談)
image_tagの存在忘れて一生懸命画像表示しようとしていたw
この場合のpostモデルから引っ張ってもimage自体は他テーブルにあるため、下記のような表示にしかならなかった。

#<ActiveStorage::Attached::One:0x00007f8ba7b765f8>

どうようにしてオプションもある。

 <%= image_tag post.image, class: 'post-image' if post.image.attached? %>

attached?メソッド

レコードにファイルが添付されているかどうかで、trueかfalseを返すメソッドです。

モデル.ファイル名.attached?

用途としては必ず画像を表示する記述が読み込まれるため、画像が存在しない場合にはエラーが起きてしまうので、こちらのメソッドを使い
if文を使って工夫することで画像が存在する場合にのみ画像を読み込む記述を読み込まれるようにすることができる。
**クラス属性もつけられます。

画像加工のツールについて

  • ImageMagick
  • MiniMagick
  • ImageProcessing

ImageMagick

コマンドラインから画像に処理を加えることができるツールです。
処理としては、画像の作成やサイズ変更、保存形式の変更などがあります。

ImageMagickはGemではなく、ソフトウェアです。
そのため、Homebrewからインストールします。

GemではないImageMagickをRubyやRailsで扱うには、MiniMagickというGemが必要となります。

brew install imagemagick

こちらは一度入れれば問題ないのですでに入っている場合は、とばしてOK。

MiniMagick

ImageMagickの機能をRubyで扱えるようにしてくれるGemです。
RailsでImageMagickを扱うために必要となります。

ImageProcessing

MiniMagickでは提供できない、画像サイズを調整する機能を提供するGemです。

# Gemfileの一番下に記述する

gem 'mini_magick'
gem 'image_processing', '~> 1.2'
 bundle install

gemの更新をしたので、サーバーの立ち上げを忘れずに。

こちらを実装するとvariantメソッドを使用することで、ファイルの表示サイズを指定できます。

モデル.ファイル名.variant(resize: '幅x高さ')

感想

記録として残すつもりで書きました。
最後グダッてしまいましたが、文章力や伝え方について改善点があればコメントお願いします。
他、書いてあることに間違いがあれば申し訳ないです。ご指摘お願いします。
このほかにもS3を使う方法なんかをよく見かけますがこちらは実際に試したあと、追記で残そうかなと思っています。
以上、ありがとうございました。

 参考URL

https://qiita.com/hmmrjn/items/7cc5e5348755c517458a
https://qiita.com/kimuray/items/3335a87d3488be340374
https://qiita.com/sibakenY/items/e550166970e84d96e16e
https://railsguides.jp/active_storage_overview.html#active-storage%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

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

【Rails】created_atを使って「〇〇分前」という表記にしたい!

概要

本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
スクリーンショット 2021-01-12 22.01.28.png

開発環境

  • Ruby 2.6.5p114
  • Rails 6.0.3.4

方法

① 日本語設定をする

まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してください

config/application.rb
config.i18n.default_locale = :ja

② 日本語のyamlファイルを作成する

次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。

config/locales/ja.yml
ja:
  datetime:
    distance_in_words:
      about_x_hours:
        one: 1時間
        other: %{count}時間
      about_x_months:
        one: 1ヶ月
        other: %{count}ヶ月
      about_x_years:
        one: 1
        other: %{count}
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1
        other: "%{count}秒"
      x_minutes:
        one: 1
        other: "%{count}分"
      x_days:
        one: 1
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1
        other: "%{count}年"

③ ビューで表示する

最後にビューで表示します。
表示する際は、Railsが用意してくれているtime_ago_in_wordsを使用します。
引数には作成日時を渡します。

index.html.erb
<p>#{time_ago_in_words(post.created_at)}前にアウトプット</p>

※postの部分は適宜書きかえてください



以上で実装が完了しました!
表示方法を自分好みに修正したい時はja.ymlにて修正してください。

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

【Rails】created_atを使って「〇〇分前」と表示させたい!

概要

本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
スクリーンショット 2021-01-12 22.01.28.png

開発環境

  • Ruby 2.6.5p114
  • Rails 6.0.3.4

手順

① 日本語設定をする

まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してください

config/application.rb
config.i18n.default_locale = :ja

② 日本語のyamlファイルを作成する

次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。

config/locales/ja.yml
ja:
  datetime:
    distance_in_words:
      about_x_hours:
        one: 1時間
        other: %{count}時間
      about_x_months:
        one: 1ヶ月
        other: %{count}ヶ月
      about_x_years:
        one: 1
        other: %{count}
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1
        other: "%{count}秒"
      x_minutes:
        one: 1
        other: "%{count}分"
      x_days:
        one: 1
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1
        other: "%{count}年"

③ ビューで表示する

最後にビューで表示します。
表示する際は、Railsが用意してくれているtime_ago_in_wordsメソッドを使用します。
引数には作成日時を渡します。

index.html.erb
<p>#{time_ago_in_words(post.created_at)}前にアウトプット</p>

※postの部分は適宜書きかえてください



以上で実装が完了しました!
表示方法を自分好みに修正したい時はja.ymlにて修正してください。

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

【Rails】created_atを使って「〇〇分前」と表示したい!

概要

本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
スクリーンショット 2021-01-12 22.01.28.png

開発環境

  • Ruby 2.6.5p114
  • Rails 6.0.3.4

手順

① 日本語設定をする

まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してください

config/application.rb
config.i18n.default_locale = :ja

② 日本語のyamlファイルを作成する

次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。

config/locales/ja.yml
ja:
  datetime:
    distance_in_words:
      about_x_hours:
        one: 1時間
        other: %{count}時間
      about_x_months:
        one: 1ヶ月
        other: %{count}ヶ月
      about_x_years:
        one: 1
        other: %{count}
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_seconds:
        one: 1
        other: "%{count}秒"
      x_minutes:
        one: 1
        other: "%{count}分"
      x_days:
        one: 1
        other: "%{count}日"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1
        other: "%{count}年"

③ ビューで表示する

最後にビューで表示します。
表示する際は、Railsが用意してくれているtime_ago_in_wordsメソッドを使用します。
引数には作成日時を渡します。

index.html.erb
<p>#{time_ago_in_words(post.created_at)}前にアウトプット</p>

※postの部分は適宜書きかえてください



以上で実装が完了しました!
表示方法を自分好みに修正したい時はja.ymlにて修正してください。

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

またしてもFiddleの利用中にGCにメモリ領域を回収されてまった話

Fiddleの構造体のメンバーを何回も呼び出すと値が変わってしまうことがあります。
意気揚々としてisssue報告をしたのでしたが…

下、1000回呼び出すとだいたい値が変化する…

require 'fiddle/import'

module A
  extend Fiddle::Importer
  S = struct [
    'int8_t* hoge',
    'int8_t* fuga']
end

s = A::S.malloc
s.hoge = [*1..10].pack('c*')
s.fuga = [*1..10].reverse.pack('c*')

a1 = s.fuga[0,10].unpack('c*')

1000.times do
  s.fuga[0,10].unpack('c*')
end

b1 = s.fuga[0,10].unpack('c*')

if a1 == b1
  puts "OK"
else
  p a1, b1
end

ところが、これはバグではなく、文字列がGCによって回収されてしまうのだそうです。
以前にもFiddle::Closure::BlockCallerがGC回収されてしまう記事を書きましたが、これも全く同じパターンでして、

s.hoge = [*1..10].pack('c*')
s.fuga = [*1..10].reverse.pack('c*')

この = の右側の文字列は、何の変数にも代入されていないので、無慈悲にもGCに回収されてしまうのです。
回避策としては

s.hoge = memo1 = [*1..10].pack('c*')
s.fuga = memo2 = [*1..10].reverse.pack('c*')

のように変数に入れておくというのがあります。

FFIを利用してプログラミングを行う際には、よほどGCに注意しなければならないということを再確認しました。

この記事は以上です。

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

RailsアプリをHerokuにデプロイする

個人の忘備録になります。基本操作はすべて、macOSのターミナルで行います。

1.Heroku CLIをインストール

ターミナルを開き、下記のコマンドを入力します。

% brew tap heroku/brew && brew install heroku

インストールが終了したら、完了を確認するために下記のコマンドを入力します。
ここでは、heroku/x.y.z出力に表示されるはずです。

% heroku --version
heroku/7.0.0 (darwin-x64) node-v8.0.0

2.ログイン

CLIをインストールした後、heroku loginコマンドを実行します。メールアドレスやパスワードをもとめられるので、入力していきます。

% heroku login
=> Enter your Heroku credentials.
# メールアドレスを入力し、エンターキーを押す
  => Email:
# パスワードを入力して、エンターキーを押す
  => Password:

Logged in as メールアドレス と出力されるとログイン成功です。

3.Heroku上にアプリケーションを作成

% cd デプロイしたいアプリの場所

アプリ作成の際にアプリ名は一意でなければいけないので、他の人がすでに使っている名前だとエラーが表示されます。
またアプリ名がドメインの一部となるので、ドメインに使えない文字列は利用できません。
以上2点に気をつけて、以下のコマンドを入力します。

% heroku create アプリケーション名

正しく設定できたことを確認する場合は、以下を入力します。

% git config --list | grep heroku

入力後、 fatal: not in a git directory 以外が表示されていれば成功です。

4.MySQLを使用する場合

Herokuでは、使用するデータベースの設定が、デフォルトでPostgreSQLになっています。MySQLを使うためには、ClearDBというデータベースサービスが提供しているアドオンを追加することによって、HerokuでMySQLを使用できるようになります。

下記のコマンドを入力して、ClearDBアドオンを追加します。

% heroku addons:add cleardb

Ruby on Railsを使う場合は、MySQLに対応するGemについて考慮する必要があり、そちらの設定を変更します。
まず、下記のコマンドを入力してください。

% heroku_cleardb=`heroku config:get CLEARDB_DATABASE_URL`

これでClearDBデータベースのURLを変数heroku_cleardbに格納できました。
続いて、下記のコマンドを入力します。

% heroku config:set DATABASE_URL=mysql2${heroku_cleardb:5}

5.Heroku上にmaster.keyを設置

Heroku上には、環境変数(OSが提供するデータ共有機能の1つで、「どのディレクトリ・ファイルからでも参照できる変数」)としてmaster.keyの値を設置します。
まず、下記のコマンドを入力してください。

% heroku config:set RAILS_MASTER_KEY=`cat config/master.key`

設定が正しくできているか、Herokuの環境変数一覧を表示するには、以下のコマンドを入力します。

% heroku config

入力後、RAILS_MASTER_KEY: という項目がある場合、成功です。

6.アプリケーションをHerokuへ追加

Herokuへコミットをプッシュすることで、Herokuにアプリケーションの情報が追加できます。以下のコマンドを入力します。

% git push heroku master

7.Heroku上でマイグレーションファイルを実行

データベースにはマイグレーションの情報が反映されていませんので、heroku runを頭につけて、マイグレーションを実行してください。

% heroku run rails db:migrate

8.アプリケーションの情報を確認

これまでの作業で、Herokuに反映したアプリケーションの情報を確認するには、以下のコマンドを入力します。

% heroku apps:info

9.アプリケーションを開く

% heroku open

問題なく、アプリケーションが開けましたらデプロイ完了です。

おまけ1:ログのチェック

アプリが適切に機能しないような問題が発生した場合は、ログをチェックします。--tailをつけることでログの最終10行のみ表示にすることができます。

% heroku logs --tail --app アプリケーション名

おまけ2:環境変数が更新されない場合の対処法

ファイルの変更履歴が存在する場合

Herokuにプッシュしたときから少しでもファイルに差分が存在する場合は、通常通りコミットして、そのコミットをHerokuにプッシュすれば問題ありません。以下のコードを上から順に実行していきます。

% git add .
% git commit -m "後から見てわかりやすいコミット名"
% git push heroku master

ファイルの変更履歴が存在しない場合

特に変更するファイルもないがHerokuに設定した環境変数だけ本番環境に反映させたい場合は、上記の方法ですと、「Everything up-to-date(すでに最新の状態に更新されています)」と表示されます。したがって、意図的に空のコミットを作成してHerokuにプッシュする方法を行います。以下のコードを上から順に実行していきます。

% git commit --allow-empty -m "空のcommit" 
% git push heroku master
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails6】Rubocop警告一覧

はじめに

転職活動用ポートフォリオ作成中です。
今回、CircleCiを導入したことにより、push時にRubocopでコードのスタイルチェックが行われるようになり、そこで指摘されるエラーをまとめていきたいと思いました。

環境

Ruby on Rails '6.0.0'
Ruby '2.6.5'

①Layout/ExtraSpacing: Unnecessary spacing detected.

不必要な、スペースが確認されたためです

app/models/company.rb
validates_format_of :password, on: :create,  with: PASSWORD_REGEX, message: 'には英字と数字の両方を含めて設定してください'

今回は少しわかりにくいですが、「on: :create」と「with: ~」の間に1文字分余計なスペースがあったため、警告が出た模様です。

②Layout/EmptyLineBetweenDefs: Use empty lines between method definitions.

メソッドの定義文が1つの空の行で区切られているかどうかをチェックしています。

def a
end
def b
end

「def a」と「def b」の間に空白を開けていなかったため発生した模様です。

③Layout/TrailingWhitespace: Trailing whitespace detected.

該当部分の末尾に余計な空白が存在しているため、警告が出ました。

終わりに

bundle exec rubocop -a

上記コマンドを使用すれば、勝手に修正してくれるみたいですが、
なぜか意味を調べたくなったので、投稿しました。気が向けば新たな警告文が出た場合は、投稿していきます。。。。

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

ActiveHashの導入〜使用まで

Railsの勉強をしていてActiveHashというGemを使うことがあったので備忘録として残しておきます。

・ActiveHashとは
・ActiveHashの導入
・ActiveHashの使い方(長いですが手順に沿っていけば使えます)

この3つについて触れておきます。まだプログラミング初心者なので深くは語れないです。

ActiveHashとは

Active_Hashとは、変更されないデータをモデルファイル内に直接記述することで、データベースへ保存せずに読み取り専用のデータを取り扱うことができるGemのことです。

分かりやすく言うと、、、

基本的に変更されないデータ(静的データ)、「都道府県」や「市区町村」などのデータを擬似モデルにハッシュ形式で格納して、呼び出して使う事ができる便利なや〜つです。

何が便利かって言うとテーブルを作らなくて済む事です!!!!
データの中身が変わらないときは基本的にActiveHashを使いましょう!
最初は難しく感じますがすぐに慣れます!

ActiveHashの導入

まずGemfileに以下の一文を記述します

Gemfile
gem 'active_hash'

記述したらターミナルでbundle installをしましょう!

ターミナル
% bundle install

このbundle installと言うコマンドはGemfileに書かれたgemを一斉にインストールしてくれるコマンドです、新しくGemを追加したら必ず実行しましょう!

これでActiveHashの挿入は終わりです

ドチャクソ簡単ですね♪

ActiveHashの使い方

まず前提条件として今回は「都道府県(prefecture)」を作成しそれを「商品(items)」のテーブルに保存する手順で紹介していきます。
※なお「商品(items)」のテーブルは作成済みとします。
作成していない場合はrails g model itemsで作成してください

以下の手順で進めていきます。

①都道府県のデータを格納するためのモデルを作成
②データの格納
③アソシエーションを定義
④itemsのマイグレーションファイルに取得する外部キーを記述

①都道府県のデータを格納するためのモデルを作成

作成したいディレクトリ内で以下のコマンドをターミナルで実行しましょう!

ターミナル
% rails g model prefecture --skip-migration

①の解説

rails g model prefectureでprefecture(都道府県)のモデルを作成します、その時のオプションで--skip-migrationと言うオプションを使用します、このオプションはマイグレーションファイルを作成しないでくださいね、と言うオプションになります。
マイグレーションファイルとはdbディレクトリの中のmigrateディレクトリの中のファオルのことを指します。
これ→「db/migrate/2020.......」

ActiveHashではこのマイグレーションファイルは必要ないので
--skip-migrationを使用しました。

ターミナル
% rails g model prefecture --skip-migration 
Running via Spring preloader in process 26071
      invoke  active_record
      create    app/models/prefecture.rb
      invoke    rspec
      create      spec/models/prefecture_spec.rb
      invoke      factory_bot
      create        spec/factories/prefectures.rb

これらのファイルが生成されれば成功です。
もし間違えてオプションを付けずに「rails g model prefecture」のコマンドで作成したら
「rails d model prefecture」を実行して、作成したprefectureモデルを削除してからもう一度「rails g model prefecture --skip-migration」を実行しましょう!

②データの格納

先ほど作成した「app/models/prefecture.rb」のファイルに以下を記述します。

app/models/prefecture.rb
class Prefecture < ActiveHash::Base
  self.data = [
    {id: 1, name: '北海道'}, {id: 2, name: '青森県'}, {id: 3, name: '岩手県'},
    {id: 4, name: '宮城県'}, {id: 5, name: '秋田県'}, {id: 6, name: '山形県'},
    {id: 7, name: '福島県'}, {id: 8, name: '茨城県'}, {id: 9, name: '栃木県'},
    {id: 10, name: '群馬県'}, {id: 11, name: '埼玉県'}, {id: 12, name: '千葉県'},
    {id: 13, name: '東京都'}, {id: 14, name: '神奈川県'}, {id: 15, name: '新潟県'},
    {id: 16, name: '富山県'}, {id: 17, name: '石川県'}, {id: 18, name: '福井県'},
    {id: 19, name: '山梨県'}, {id: 20, name: '長野県'}, {id: 21, name: '岐阜県'},
    {id: 22, name: '静岡県'}, {id: 23, name: '愛知県'}, {id: 24, name: '三重県'},
    {id: 25, name: '滋賀県'}, {id: 26, name: '京都府'}, {id: 27, name: '大阪府'},
    {id: 28, name: '兵庫県'}, {id: 29, name: '奈良県'}, {id: 30, name: '和歌山県'},
    {id: 31, name: '鳥取県'}, {id: 32, name: '島根県'}, {id: 33, name: '岡山県'},
    {id: 34, name: '広島県'}, {id: 35, name: '山口県'}, {id: 36, name: '徳島県'},
    {id: 37, name: '香川県'}, {id: 38, name: '愛媛県'}, {id: 39, name: '高知県'},
    {id: 40, name: '福岡県'}, {id: 41, name: '佐賀県'}, {id: 42, name: '長崎県'},
    {id: 43, name: '熊本県'}, {id: 44, name: '大分県'}, {id: 45, name: '宮崎県'},
    {id: 46, name: '鹿児島県'}, {id: 47, name: '沖縄県'}
  ]
end

この時、モデル作成時にデフォルトで以下の記述がありますがそれの上から貼り付けましょう、いらないので。

app/models/prefecture.rb
class Prefecture < ApplicationRecord
end

②の解説

・まず元々書かれていた「class Prefecture < ApplicationRecord」
は「ApplicationRecord」と言うクラスを継承してくださいと言う記述になるので消しました(ActiveHash::Baseと言うクラスを継承してほしいため)。

・ActiveHashの基本的な格納方法は以下になります

activehashの格納方法
class モデルクラス名 < ActiveHash::Base
  self.data = [
      {カラム名: , カラム名: }, {カラム名: , カラム名: }
  ]
end

なので
idnameカラム名
整数都道府県となります。

③アソシエーションを定義

今回は「一つの商品は一つの都道府県を持っている」と言う形を取りたいので
「Item belongs to Prefecture」になります。
item.rbに以下の記述しましょう!

models/item.rb
 extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to_active_hash :prefecture

③の解説

・この時prefecture側は
「一つの都道府県はいくつもアイテムを持っている」
「Prefecture has many Items」になりますがprefecture側にはアソシエーションは記述する必要はありません。ActiveHashを使用した時は、いい感じに解釈してくれるので記載しなくていいです!

④itemsのマイグレーションファイルに取得する外部キーを記述

itemのマイグレーションファイルにprefectureの外部キーを取得するためのカラムを追加しましょう!
カラムを追加したらマイグレーションを実行しましょう!マイグレーションファイルを実行していたら「rails db:rollback」で停止しましょう!
またrailsサーバーを起動していたら「controll + C」でサーバーを停止し
「rails s」でサーバーも起動し直しましょう!

db/migrate/2020....create_items.rb
class CreateItems < ActiveRecord::Migration[6.0]
  def change
    create_table :items do |t|  
      #↓この1行を追加↓
      t.integer    :prefecture_id, null: false
      #↑この1行を追加↑
      foreign_key: true
      t.timestamps
    end
  end
end
ターミナル
% rails db:rollback 
   ↑マイグレーションファイルの停止コマンド

% rails db:migrate
  ↑マイグレーションファイルの実行コマンド

controll + C を実行後以下を実行

% rails s
  ↑railsサーバーの起動コマンド

④の解説

・外部キーを取得するのですがreferensesの型を使わずにinteger型を使用しています、これには理由があり,,,

そもそも今回はprefectureのテーブルを作成していないのでreferences型は使えないのです!


references型とinteger型の違いについてはそれぞれで調べてください。

・ 再起動のコマンドあれこれ、テーブルを変更したときは必ず実行する必要があるので注意してください。railsに怒られます。

⑤使用する時の記述方法

使用したいファイルに以下を記述します
今回はitemsのファイルに使用したいのでitems/index.html.erbに記述していきます。

items/index.html.erb
<%= f.collection_select(:prefecture_id, Prefecture.all, :id, :name, {include_blank: "---"}, {class:"クラス名", id:"id名"}) %>

⑤の解説

まず以下をご覧ください

<%= form.collection_select(①保存されるカラム名, ②オブジェクトの配列, ③カラムに保存される項目, ④選択肢に表示されるカラム名, ⑤{オプション}, ⑥{htmlオプション} ) %>

一つずつ解説していきます
<%=form.collection_select%>はrailsに用意されているヘルパーメソッドになります。これはプルダウン式の表示を表します。

・①の保存されるカラム名はprefectureのidを保存したいのでprefecture_idになります

・②のオブジェクトの配列は「②データの格納」でPrefectureに格納したデータのどのデータを取り出すかを指定しています「.all」と言うクラスメソッドを使用していますが他にも色々ありますが調べてください。

・③のカラムに保存される項目は「:id」にしましょう!「:name」でも大丈夫です!

・④選択肢に表示されるカラム名はパッと見で分かりやすくするために「:name」を指定しましょう

・⑤{オプション}ここのオプションは今回{include_blank: "---"}を使用しました、これは何も入力されていなければ「---」を表示することを指します。

・⑥{htmlオプション}これはHTMLのクラス名やid名を記述するところです、今回は使い方だけなので省いて書きました、必要な方はクラス名やid名を記述してください。

以上がActiveHashの使い方までの説明になります!

まとめ

ActiveHashとは読み取り専用のデータを扱うためのGemのこと
導入方法は「gem 'active_hash'」を記述してから「bundle install」する
使い方は頑張る

最後までありがとうございました!

参考記事
https://pikawaka.com/rails/active_hash

https://github.com/zilkey/active_hash

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

brew upgrade rbenv ruby-build で XcodeがmacOSをsupportしてないエラー

ruby3.0、rails6.1.0で新たにrailsアプリを開発。
atomエディタのLinterが rbenv: version `ruby-3.0.0' is not installedとのエラーを吐く。

rbenvでruby-3.0.0を入れる必要があるとのことなので、以下のコマンドを実行。

$ brew update
 (時間かかった。。)
$ brew upgrade rbenv ruby-build

参考:rbenvでmacのrubyを最新にする - Qiita

すると以下のエラー。

==> Installing ruby-build
Error: Your Xcode does not support macOS 11.
It is either outdated or was modified.
Please update your Xcode or delete it if no updates are available.
Xcode can be updated from the App Store.

Error: An exception occurred within a child process:
  SystemExit: exit

More Software Downloads - Apple Developerより最新のCommand Line Toolsを手動でインストール。

参考:Error: Your Xcode does not support macOS 11.0.が出た時の対処方法

その後、再度以下のコマンド。

$ brew upgrade rbenv ruby-build
Error:
  homebrew-core is a shallow clone.
  homebrew-cask is a shallow clone.
To `brew update`, first run: 
...

なんかError吐いたけどインストールしたよう。

$ rbenv install --list
...
3.0.0
...

 入ってる。

$ rbenv install 3.0.0
...
$ ruby -v
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]

成功。

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

Rubyのevalについて

Rubyのevalについて

経緯

いろいろなサイトで「引数として与えた文字列をRubyプログラムとして実行するメソッド」と書かれていたけど、理解力が乏しい私は「え?それじゃあいつもの文字列はRubyじゃないの?」的なこと思っちゃったので……(完全に馬鹿)

説明

入力
Ruby
1-1
1+1
1*1
1/1
10+10
コード
Ruby
puts eval gets
puts eval gets
puts eval gets
puts eval gets
eval "puts gets"
出力
Ruby
0
2
1
1
10+10

たったこれだけ……
これが分からなかった(絶望)

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

google スプレッドシートをAPIで操作する手順(ruby)

これは何?

rubyを使ってgoogleスプレッドシートをAPIで操作するまでの手順です。

基本的に https://developers.google.com/sheets/api/quickstart/ruby をやっているだけなので英語の説明でも問題ない方は公式の方を見てください。

対象者

rubyの実行環境が自分で作れる人、または既に存在する人が対象です。

手順

https://developers.google.com/sheets/api/quickstart/ruby
の[Enable the Google Sheets API]ボタンをクリックします。

01.jpg

次の画面でProject名を入力します。
[Quickstart]のままでも問題ないので入力したら[NEXT]をクリックします。
02.jpg

次の画面では作成するOAuth clientを選びます。
[Desktop app]を選択して[CREATE]ボタンをクリックします。
03.jpg

次の画面では[DOWNLOAD CLIENT CONFIGURATION]ボタンが表示されるのでクリックして credentials.jsonファイルをダウンロードします。
04.jpg

続いてrubyのプログラムを作成していきます。
まずプログラムを格納するためのディレクトリを作成していきます。
今回は「sample_google_spreadsheet_api」ディレクトリを作成しました。(ディレクトリ名はなんでも大丈夫です)

cd ~
mkdir sample_google_spreadsheet_api

続いてgemのinstallを行います。
05.jpg

GoogleのQuickstartページでは上記のように記載していますが、今回はGemfileを作成していきます。

cd sample_google_spreadsheet_api
bundle init

Gemfileが作成されるので以下のように編集します。

Gemfile
# frozen_string_literal: true

source "https://rubygems.org"

git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

--- # gem "rails"
+++ gem 'google-api-client'

bundle installします。

bundle config set --local path 'vendor/bundle'
bundle install

ダウンロードしたcredentials.json を以下へ配置します。

# fileディレクトリを作成してcredentials.jsonを格納
mkdir file

sample_google_spreadsheet_api/file/credentials.json

続いてsheets/quickstart/quickstart.rb を作成します。

mkdir sheets
cd sheets
mkdir quickstart
cd quickstart
touch quickstart.rb 

quickstart.rb へ以下のプログラムを記載します。

これはGoogleのQuickstartへ記載されているものです。

require "bundler/setup"
require "google/apis/sheets_v4"
require "googleauth"
require "googleauth/stores/file_token_store"
require "fileutils"
OOB_URI = "urn:ietf:wg:oauth:2.0:oob".freeze
APPLICATION_NAME = "Google Sheets API Ruby Quickstart".freeze
CREDENTIALS_PATH = "./file/credentials.json".freeze
# The file token.yaml stores the user's access and refresh tokens, and is
# created automatically when the authorization flow completes for the first
# time.
TOKEN_PATH = "token.yaml".freeze
SCOPE = Google::Apis::SheetsV4::AUTH_SPREADSHEETS_READONLY
##
# Ensure valid credentials, either by restoring from the saved credentials
# files or intitiating an OAuth2 authorization. If authorization is required,
# the user's default browser will be launched to approve the request.
#
# @return [Google::Auth::UserRefreshCredentials] OAuth2 credentials
def authorize
  client_id = Google::Auth::ClientId.from_file CREDENTIALS_PATH
  token_store = Google::Auth::Stores::FileTokenStore.new file: TOKEN_PATH
  authorizer = Google::Auth::UserAuthorizer.new client_id, SCOPE, token_store
  user_id = "default"
  credentials = authorizer.get_credentials user_id
  if credentials.nil?
    url = authorizer.get_authorization_url base_url: OOB_URI
    puts "Open the following URL in the browser and enter the " \
             "resulting code after authorization:\n" + url
    code = gets
        credentials = authorizer.get_and_store_credentials_from_code(
            user_id: user_id, code: code, base_url: OOB_URI
        )
    end
    credentials
end

# Initialize the API
service = Google::Apis::SheetsV4::SheetsService.new
service.client_options.application_name = APPLICATION_NAME
service.authorization = authorize

# Prints the names and majors of students in a sample spreadsheet:
# https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
spreadsheet_id = "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms"
range = "Class Data!A2:E"
response = service.get_spreadsheet_values spreadsheet_id, range
puts "Name, Major:"
puts "No data found." if response.values.empty?
response.values.each do |row|
    # Print columns A and E, which correspond to indices 0 and 4.
    puts "#{row[0]}, #{row[4]}"
end

内容としては
credentials.jsonをもとに認可コードを取得して認可コードからアクセストークンを取得し
https://docs.google.com/spreadsheets/d/1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgvE2upms/edit
の内容をコマンドラインへ出力するというものです。
以下が実行コマンドです。

ruby sheets/quickstart/quickstart.rb

実行結果

> ruby sheets/quickstart/quickstart.rb

Open the following URL in the browser and enter the resulting code after authorization:
https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&client_id=[client_id]&include_granted_scopes=true&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/spreadsheets.readonly

初回実行時はURLが表示されるのでそれにアクセスします。

1: google アカウントを選択します。
06.jpg

2:認可画面が表示されるので「許可」クリックします。
07.jpg

3: 認可コードが表示されるので実行画面へ貼り付けReturn
08.jpg

実行結果へ貼り付けてReturn

> ruby sheets/quickstart/quickstart.rb 

Open the following URL in the browser and enter the resulting code after authorization:
https://accounts.google.com/o/oauth2/auth?access_type=offline&approval_prompt=force&client_id=[client_id]&include_granted_scopes=true&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope=https://www.googleapis.com/auth/spreadsheets.readonly
# ↓へ上記で取得した認可コードを貼り付けReturn
[認可コード]

# GoogleSpreadSheetから取得した結果が表示されます。
Name, Major:
Alexandra, English
Andrew, Math
Anna, English
Becky, Art
Benjamin, English
Carl, Art
Carrie, English
Dorothy, Math
Dylan, Math
Edward, English
Ellen, Physics
Fiona, Art
John, Physics
Jonathan, Math
Joseph, English
Josephine, Math
Karen, English
Kevin, Physics
Lisa, Art
Mary, Physics
Maureen, Physics
Nick, Art
Olivia, Physics
Pamela, Math
Patrick, Art
Robert, English
Sean, Physics
Stacy, Math
Thomas, Art
Will, Math

サンプルのGoogleスプレッドシートの内容が表示されれば成功です。

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

ar

See the Pen #genuary 10 Tree by Johan Karlsson (@DonKarlssonSan) on CodePen.

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

NoMethodError: undefined method `setup' for Devise:Module

前提条件

  • AWSなどPaaS関連の設定は完了済
  • SQLも実装済
  • Herokuにデプロイする

エラー文

rake aborted!
       NoMethodError: undefined method `setup' for Devise:Module

結論

gem 'devise'をgroup develop testではなく、プロダクション環境の方に書く

原因

リポジトリのconfig/environmentで、production、deveropment、testのrbファイルがあり、gemをgroupでくくるとproduction.rbでgemが使えなくなってしまう

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

Ruby on Rails <2021年> かんたんログイン機能の実装(form_with)

前提条件

  • gem 'devise' でログイン機能を実装していること
  • 新規登録で mail: guest@guest, password: 111111で登録していること
 <%= form_with(model: User.new, url:new_user_session_path) do |f| %>
   <%= f.hidden_field :email, value: "guest@guest" %>
   <%= f.hidden_field :password, value: "111111" %>
   <%= f.submit "ゲストログイン", class:"btn btn-success btn-sm mb-3 btn-block guest"%>
<% end %>

ゲストログインがクリックされるとログイン画面へ行き、
valueの中のemailとpasswordが入り
ログインした画面へリダイレクトされます。

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

【Rails6】SNS認証による新規登録機能実装(Facebook, Google)

はじめに

現在Raisを使用しアプリケーションの作成を行なっています。今回は既に実装しているログイン機能にSNSアカウントでログインできるよう追加実装していきます。備忘録及び復習のため記述していきます。

環境

Ruby on Rails '6.0.0'
Ruby '2.6.5'

前提

①Gemの導入と環境変数の設定

omniauth
・Google,Facebook,Twitter等のSNSアカウントを用いてユーザー登録やログインなどを実装できるgemです。今回はFacebookとGoogleのomniauthをインストールします。

Gemfile
gem 'omniauth-google-oauth2'
gem 'omniauth-facebook'
ターミナル
% bundle install

また、外部API設定時に取得したIDやシークレットキーを環境変数に設定します。環境変数の設定方法はいくつかあると思いますが、私の場合は、「gem 'dotenv-rails'」により「.envファイル」を設定済みのため、そこに記述しました。

.env
FACEBOOK_CLIENT_ID='アプリID'
FACEBOOK_CLIENT_SECRET='app secret'
GOOGLE_CLIENT_ID='クライアントID'
GOOGLE_CLIENT_SECRET='クライアントシークレット'

次にアプリケーション側で環境変数を読み込む記述をします。

config/initializers/devise.rb
(省略)
  config.omniauth :facebook,ENV['FACEBOOK_CLIENT_ID'],ENV['FACEBOOK_CLIENT_SECRET']
  config.omniauth :google_oauth2,ENV['GOOGLE_CLIENT_ID'],ENV['GOOGLE_CLIENT_SECRET']

②モデルの設定

SNSのWebAPIにリクエストを送ると、API上で認証を行います。その後、SNS上に登録されている情報がアプリケーション側に返される仕組みです。
APIからのレスポンスの中の、「uid」と「provider」をアプリケーションのデータベースに、ユーザー情報(名前など)と共に保存します。
しかし、「SNS認証とユーザー登録のタイミングが異なる」仕様であるため、SNS認証時にはusersテーブルのレコードを作成することはできません。そこでSNS認証時の情報を保存する別テーブル(SnsCredentialモデル)を作成しました。

ターミナル
% rails g model sns_credential
db/migrate/XXXXXXXXXXXXXXXX_create_sns_credentials.rb
class CreateSnsCredentials < ActiveRecord::Migration[6.0]
 def change
   create_table :sns_credentials do |t|
     t.string :provider
     t.string :uid
     t.references :user,  foreign_key: true

     t.timestamps
   end
 end
end

userモデルとのアソシエーションのため、外部キーとしてuser_idを持たせています。編集がおわり次第、「rails db:migrate」にてデータベースに反映します。

アソシエーションの設定をします。

app/models/user.rb
(省略)
has_many :sns_credentials, dependent: :destroy

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable, :omniauthable, omniauth_providers: [:facebook, :google_oauth2]

omniauth_providers: [:facebook, :google_oauth2]と記述することで、FacebookとGoogleのOumniAuthを使用できます。

app/models/sns_credential.rb
class SnsCredential < ApplicationRecord
  belongs_to :user
end

③deviseの再設定

deviseのコントローラーを再設定するため、コントローラーを作成しています。

ターミナル
% rails g devise:controllers users

生成したコントローラーを使用させるために、deviseのルーティングを変更します。

config/routes
Rails.application.routes.draw do
  devise_for :users, controllers: {
    omniauth_callbacks: 'users/omniauth_callbacks',
    registrations: 'users/registrations',
    sessions: 'users/sessions',
  }
・・・(省略)・・・
end

④コントローラーの設定

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  private

  def authorization
    @user = User.from_omniauth(request.env["omniauth.auth"]
  end
end

プライベートメソッド内に記述している「authorization」を呼び出す「facebook」と「google_oauth2」というアクションを定義します。

⑤Viewファイルの編集

app/views/users/registrations/new.html.erb
(省略)
<div class="d-flex justify-content-between mt-4">
  <%= link_to user_facebook_omniauth_authorize_path, class:"btn btn-outline-primary sns-btn", method: :post do %>
    <i class="fab fa-facebook fa-2x"></i>
  <% end %>
  <%= link_to user_google_oauth2_omniauth_authorize_path, class:"btn btn-outline-danger sns-btn", method: :post do %>
    <i class="fab fa-google fa-2x"></i>
  <% end %>
</div>

⑥modelの編集

Userモデルに入るデータのため、Userモデルにクラスメソッドを作成しました。
クラスメソッドとは、クラスで共通の情報を使った処理に使用するようです。記述方法は、メソッド名の前にselfを.(ドット)で繋いで定義します。

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
 end

上記でomniauth_callbacks_controller.rbに記述した、User.from_omniauthも呼び出せるようになりました。
また、取得した情報内の「provider」と「uid」をsnsに代入して、first_or_createメソッドを用いています。
(first_or_createメソッド:保存するレコードがデータベースに存在するか検索を行い、検索した条件のレコードがあればそのレコードのインスタンスを返し、なければ新しくインスタンスを保存するメソッドです。)

次に、SNS認証を行なったかどうか確認した後のコードを記述します。

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   user = User.where(email: auth.info.email).first_or_initialize(
      first_name: auth.info.last_name,
      last_name: auth.info.first_name,
      email: auth.info.email
    )
 end

上記で追加したコードは、SNS認証を行なっていなかった場合、メールアドレスで検索をします。first_or_initializeを用いて、名前とメールアドレスを返します。
(first_or_initialize:whereメソッドとともに使うことで、whereで検索した条件のレコードがあればそのレコードのインスタンスを返し、なければ新しくインスタンスを作るメソッドです。)

first_or_create:新規レコードをデータベースに保存する
first_or_initialize:新規レコードをデータベースに保存しない

⑦controllerの編集

Userモデルから返ってきた後の処理を記述します。

app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    authorization
  end

  def google_oauth2
    authorization
  end

  private

  def authorization
    @user = User.from_omniauth(request.env['omniauth.auth'])

    if @user.persisted? #ユーザー情報が登録済みのため、新規登録ではなくログイン処理を行う
      sign_in_and_redirect @user, event: :authentication
    else #ユーザー情報が未登録のため、新規登録画面へ遷移する
      render template: 'devise/registrations/new'
    end
  end
end

Userモデルから返ってきた値を@userに代入します。これは、viewで取得した「名前」と「メールアドレス」を表示させるためです。

次に、ログイン時の記述を行います。

⑧Userモデルの編集

app/models/user.rb
(省略)
 def self.from_omniauth(auth)
   sns = SnsCredential.where(provider: auth.provider, uid: auth.uid).first_or_create
   user = User.where(email: auth.info.email).first_or_initialize(
      first_name: auth.info.last_name,
      last_name: auth.info.first_name,
      email: auth.info.email
    )

  # userが登録済みであるか判断
   if user.persisted?
     sns.user = user
     sns.save
   end
   user
 end

persisted?で登録済みのユーザーと判断を行い、「if文」の中が呼ばれます。新規登録時は、SnsCredentialモデルが保存されるタイミングで、user_idが確定していなかったので、SnsCredentialモデルとUserモデルは紐づいていません。ログインの際に、sns.userを更新して紐付けを行います。

最後にログイン画面のviewファイルも編集して完了です!!

終わりに

もしこの記事を参考に、実装してみて、うまくできない場合は教えてください。。初めての実装のためお許しください。。

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

配列

配列とは

一つの変数で複数の値を順番で管理することができるというものです。
配列は角括弧[ ]で囲い生成します。

fruits = ["apple","lemon","strawberry"]
puts fruits

# apple
  lemon
  strawberry
と出力される

配列に対してさまざまな処理を行う演算子を配列演算子と呼ぶ。
すでに生成した配列に追加で要素を追加したいときは << を使います

fruits = ["apple","lemon","strawberry"]
fruits << "orange"
puts fruits

# 配列の中身が["apple","lemon","strawberry","orange"]となり
  apple
  lemon
  strawberry
  orange
と出力される

配列の要素を取得する場合

添字という配列の各要素に振られた番号を利用して取り出します。
添字は0から始まり順番に増えていきます。

取得したい要素に対応する添字を指定することで、要素を取得することができます。
配列の取得は、配列に続けて、角括弧[ ]で取得したい要素の添字を囲います。

fruits = ["apple","lemon","strawberry"]
puts fruits[1]

# lemonが出力される

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

[Rails]date_selectにBootstrapのform-controlが適用されない

課題

Rails + Boostrap でdate_selectにform-controlが適用されない

_form.html.erb
<%= form.date_select :date, class: "form-control" %>

スクリーンショット 2021-01-12 8.53.42.png

結論

以下のような書き方で適用されます。

_form.html.erb
<%= form.date_select :date, {}, {class: 'form-control', style: 'display: inline-block;width: auto;'} %>

スクリーンショット 2021-01-12 8.56.37.png

ちなみにこの style: 'display: inline-block;width: auto; をサボると想定外にダッさい表示になります。

スクリーンショット 2021-01-12 8.58.41.png

地味に初学者が躓くので再度メモしました。
技術背景も含めて以下記事が完全回答版です。
過去の先人たちに感謝いたします。

参考情報

Rails date_selectをBootstrapを使っていい感じにする
https://qiita.com/t_oginogin/items/519fb52e1708e26a8b73

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

ローカルに保存されている画像データをZIPダウンロードしたいとき

解決したいこと

ローカルに保存されている、main.jpg、top_map_off.jpgをzipダウンロードしたい。
image.png

解決策

画像はバイナリモードでしか書き出せないので、openするときにバイナリモードを指定する。

Zip::OutputStream.open('example.zip') do |zip|
  default_images = ["main", "top_map_off"]
  default_images.each do |default_image|
    img_path = Rails.root.join(
      "app", "lib",  "output", "download_tpl", @kikaku_date.kikaku_cd, "#{target}", "images", "#{default_image}.jpg 
    ) # ローカルに保存されている画像ファイルのフルパス
    zip.put_next_entry "#{@zip_file_basename}/#{target}/images/#{default_image}.jpg"
    zip.print open(img_path, "rb").read  # 画像をバイナリモードで開く
  end
end

Rubyでバイナリデータを標準出力に出力する

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

error: src refspec main does not match anyの解決法

git pushができない

git push origin main

とすると、

error: src refspec main does not match any

のようなエラーが出るようになりました。

結論

解決策は、

git push origin HEAD:pushしたいブランチ名

で、無事にpushすることができました。

参考記事

https://qiita.com/fukkatsu3/items/c488ec9559f6cca34313
こちらを参考にさせていただきました。

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

enumの使い方(日本語表記の導入)

今回はECサイトの制作をしていた際の注文ステータスのカラムにenumを使用し、プルダウン表記を活用しました。enumを使用することが初めてであったので、忘れない様に自身のメモとして記事を残したいと思います。
以下の記事は実装の際に参考にさせていただきました。

(参考記事)
・【初心者向け】i18nを利用して、enumのf.selectオプションを日本語化する[Rails]
https://qiita.com/tanutanu/items/d44a92425188a4489ec6
・[初学者]Railsのi18nによる日本語化対応
https://qiita.com/shimadama/items/7e5c3d75c9a9f51abdd5

Gemfile

Gemfile.
gem 'enum_help'
terminal.
bundle install

まずは、enumを使用するのに必要なgemをGemfileに記述します。
上記のように記述ができたら、ターミナルでbundle installのコマンドを実行します。

model.rb

order_item.rb
 enum production_status: {cannot_be_started: 0, waiting_for_production: 1, in_production: 2, production_completed: 3}

次にモデルのファイルに記述を加えます。
enum カラム名: {名前(今回はステータス名): 0, 名前(今回はステータス名):1 ... n }
enumの後には、enumを使用するカラム名と波カッコ内にはその名目(英語表記)と各値に0から順に数字を振っていきます。
このように各名目に数字を対応させるため、テーブルを作成する際のenumのデータ型はinteger(整数型)となります。

日本語の設定に変更する

config/application.rb
module NaganoApp
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    # config.load_defaults 5.2
    config.i18n.default_locale = :ja #この1行のみ追加

    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end
end

この1行の記載がないと日本語表記に変換されません。

ja.yml
ja:
  enums:
    order_item: 
      production_status:
        cannot_be_started: "着手不可"    
        waiting_for_production: "制作待ち"
        in_production: "制作中"
        production_completed: "制作完了"

先ほどのmodelファイルの内容を日本語訳をして記述します。
断層やカラム名の記述に誤りがあると、表示されなくなってしまいます。

viewに表示する

<%= form_with model: @order_item, url: admin_order_item_path(order_item), method: :patch, local: true do |f| %>
 <%= f.select :production_status, OrderItem.production_statuses.keys.map {|k| [I18n.t("enums.order_item.production_status.#{k}"), k]} %>
 <%= f.submit "変更" %>
<% end %>

フォーム内にプルダウン表記でステータスを変更できるようにしました。
f.select以降は以下のような構成になっており、こちらもカラム名の単数、複数表記を誤ると表示がされなくなります。
:カラム名(単数形), モデル名.カラム名(複数形).keys.map {|k| [I18n.t("enums.モデル名.カラム名(単数形).#{k}"), k]} %
keys.map以降の表記により、設定されているenumの値の0から順に選択肢を入れてくれているようなイメージになります。

enumのメリット

今回のようにステータス管理など頻繁に更新が必要な際には特に有効な機能になります。
数値で管理を行うため、何か変更する必要がある時でも修正が容易に行えることが大きなメリットです。
model側とviewに必要な記述を少し書き加えるだけで、アプリケーション全体に変更を行うことが可能です。


今回は以上になります。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になれば幸いです。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。

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

【Rails AWS Docker】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(1)

ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)

かなり苦しめられたので、どなたかのお役に立てれば。

タイトル
1 ローカル環境のRailsアプリをDockerコンテナ化

参考にさせていただいた記事は、(6)の末尾い記載しています。

ローカル環境のRailsアプリをDockerコンテナ化

Dockerコンテナ環境構成(ローカル版)

IMG_CE2774B27B97-1.jpeg

構成は上記です。
webサーバーとしてnginxを配置します。
appサーバーはpumaを設置します。

まず既存のRailsアプリをコンテナ化します。

前提

AWSにアカウント作成済
docker hubにアカウント作成済
docker インストール済
ローカル環境で動作するRailsアプリ

構築

※fitO2の部分は、ご自身のアプリ名に変更してください。

現状のアプリのフォルダ構成に、下記のファイルを追加します。

docker-compose.yml

Dockerfile

nginx_docker
  ├── Dockerfile
  └── nginx.conf

config
  └── puma.rb

docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
    command: bundle exec puma -C config/puma.rb

    volumes:
      - .:/fitO2
      - public-data:/fitO2/public
      - tmp-data:/fitO2/tmp
      - log-data:/fitO2/log
    networks:
      - fitO2-network
    depends_on:
      - db

  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_DATABASE: fitO2_development
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - fitO2-network

  web:
    build:
      context: ./nginx_docker
    volumes:
      - public-data:/fitO2/public
      - tmp-data:/fitO2/tmp
    ports:
      - 80:80
    depends_on:
      - app
    networks:
      - fitO2-network

volumes:
  public-data:
  tmp-data:
  log-data:
  db-data:

networks:
  fitO2-network:
    external: true

簡単な説明

dbコンテナ
image でdockerhubからmysql:5.7をプルします。
ports でホスト側の3306とコンテナ側の3306ポートを接続します。
networks でappコンテナ側と共通のネットワークfitO2-networkを利用します。

appコンテナ
build でDockerfileのディレクトリ(コンテキスト)を指定して、Dockerfileからコンテナを作成します。
volumes で、ホスト側のdocker-compose.ymlが存在しているディレクトリと、コンテナ側の/fitO2をマウント(共通化)しています。
command で設定ファイルを指定してpuma(アプリケーションサーバー)を立ち上げています。
ports でホスト側の3000とコンテナ側の3000ポートを接続します。
depends_on でappコンテナが生成されてから、実行されるように指定しています。
networks でdbコンテナ側と共通のネットワークfitO2-networkを利用します。

webコンテナ
build で./nginx_dockerがあるディレクトリ(コンテキスト)を指定して、Dockerfileからコンテナを作成します。
depends_on でappコンテナが生成されてから、実行されるように指定しています。

networks でfitO2-networkを設定しています。

fitO2/Dockerfile.
FROM ruby:2.5.1

RUN apt-get update -qq && \
  apt-get install -y build-essential \
  nodejs\
  mysql-server\
  mysql-client

WORKDIR /fitO2

COPY Gemfile /fitO2/Gemfile
COPY Gemfile.lock /fitO2/Gemfile.lock

RUN gem install bundler
RUN bundle install

RUN mkdir -p tmp/sockets

簡単な説明

FROM でruby:2.5.1 イメージをdocker hubからインストールします。
1回目のRUN で、必要なパッケージをインストールしています。
WORKDIR で作業ディレクトリを/fitO2に設定しています。
COPYで ホスト側のgemfileとgemfile.lockをコンテナの/fitOディレクトリにコピーしています。
2回目のRUN でbundlerをインストールします。
3回目のRUN でgemfileからパッケージをインストールします。
4回目のRUN ソケットファイルを作成しています。

fitO2/nginx_docker/Dockerfile.
FROM nginx:1.15.8

RUN rm -f /etc/nginx/conf.d/*

ADD nginx.conf /etc/nginx/conf.d/fitO2.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

簡単な説明

FROM でnginx:1.15.8 イメージをdocker hubからインストールします。
RUN でインクルード用のディレクトリ内を削除してます。
ADD でNginxの設定ファイルをコンテナにコピーしてます。
CMD でビルド完了後にNginxを起動するようにしてます。

fitO2/nginx_docker/nginx.conf
upstream fitO2 {
  server unix:///fitO2/tmp/sockets/puma.sock;
}

server {
  listen 80;
# =========ローカルと本番切り替え===========
  # server_name ◯◯◯.◯◯◯.◯◯◯.◯◯◯;
  server_name localhost;
# ======================================

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  root /fitO2/public;

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @fitO2;
  keepalive_timeout 5;

  location @fitO2 {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://fitO2;
  }
}

nginxの設定ファイルです。
ローカル環境の場合は、server_name をlocalhostにします。

config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true

pumaの設定ファイルです。
bind "unix://#{app_root}/tmp/sockets/puma.sock"の部分は、nginx.confのserverと一致するようにしなければなりません。

参考

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
# PasswordとUsernameはdocker-compose.ymlと合わせます
  username: root
  password: password
  host: db

development:
  <<: *default
  database: fitO2_development

test:
  <<: *default
  database: fitO2_test

コンテナ作成、起動

アプリのディレクトリに移動します。

コンテナを作成します。

docker-compose build

ネットワークを作成します。

docker network create fitO2-network

コンテナを起動します。

docker-compose up

初回はデーターベースの初期化を行います。
ターミナルを別タブで開き、アプリ直下に移動して下記コマンドをうちます。

docker-compose exec app rails db:create 
docker-compose exec app rails db:migrate
docker-compose exec app rails db:seed (シーダーがなければ不要)

うまくいかなかった場合、試行錯誤した場合は中途半端にシーダーが入ってしまって、バリデーションの関係で弾かれる場合があるので、一度下記の様にデーターベースを削除してから再度上記コマンドを実行してください。

docker-compose exec app rails db:drop

http://localhost

にアクセスすると、サイトにアクセスすることができます。

エラーがー生じた際は、docker-compose upターミナルにログが出ていますので、確認してください。

とりあえず既存のアプリをDockerコンテナ化できました。

次回(2)へ続く
[AWSにVPCを作成する。パブリックサブネットを作成する。]

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

【Rails AWS Docker】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(1)

ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)

かなり苦しめられたので、どなたかのお役に立てれば。

タイトル
1 ローカル環境のRailsアプリをDockerコンテナ化
2 AWSにVPCを作成する。パブリックサブネットを作成する
3 プライベートサブネットを作成する
4 EC2インスタンスを作成する
5 RDSを作成する
6 DockerコンテナをAWSにアップロードする

参考にさせていただいた記事は、(6)の末尾に記載しています。

ローカル環境のRailsアプリをDockerコンテナ化

Dockerコンテナ環境構成(ローカル版)

IMG_CE2774B27B97-1.jpeg

構成は上記です。
webサーバーとしてnginxを配置します。
appサーバーはpumaを設置します。

まず既存のRailsアプリをコンテナ化します。

前提

AWSにアカウント作成済
docker hubにアカウント作成済
docker インストール済
ローカル環境で動作するRailsアプリ

構築

※fitO2の部分は、ご自身のアプリ名に変更してください。

現状のアプリのフォルダ構成に、下記のファイルを追加します。

docker-compose.yml

Dockerfile

nginx_docker
  ├── Dockerfile
  └── nginx.conf

config
  └── puma.rb

docker-compose.yml
version: '3'
services:
  app:
    build:
      context: .
    command: bundle exec puma -C config/puma.rb

    volumes:
      - .:/fitO2
      - public-data:/fitO2/public
      - tmp-data:/fitO2/tmp
      - log-data:/fitO2/log
    networks:
      - fitO2-network
    depends_on:
      - db

  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_DATABASE: fitO2_development
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - fitO2-network

  web:
    build:
      context: ./nginx_docker
    volumes:
      - public-data:/fitO2/public
      - tmp-data:/fitO2/tmp
    ports:
      - 80:80
    depends_on:
      - app
    networks:
      - fitO2-network

volumes:
  public-data:
  tmp-data:
  log-data:
  db-data:

networks:
  fitO2-network:
    external: true

簡単な説明

dbコンテナ
image でdockerhubからmysql:5.7をプルします。
ports でホスト側の3306とコンテナ側の3306ポートを接続します。
networks でappコンテナ側と共通のネットワークfitO2-networkを利用します。

appコンテナ
build でDockerfileのディレクトリ(コンテキスト)を指定して、Dockerfileからコンテナを作成します。
volumes で、ホスト側のdocker-compose.ymlが存在しているディレクトリと、コンテナ側の/fitO2をマウント(共通化)しています。
command で設定ファイルを指定してpuma(アプリケーションサーバー)を立ち上げています。
ports でホスト側の3000とコンテナ側の3000ポートを接続します。
depends_on でappコンテナが生成されてから、実行されるように指定しています。
networks でdbコンテナ側と共通のネットワークfitO2-networkを利用します。

webコンテナ
build で./nginx_dockerがあるディレクトリ(コンテキスト)を指定して、Dockerfileからコンテナを作成します。
depends_on でappコンテナが生成されてから、実行されるように指定しています。

networks でfitO2-networkを設定しています。

fitO2/Dockerfile.
FROM ruby:2.5.1

RUN apt-get update -qq && \
  apt-get install -y build-essential \
  nodejs\
  mysql-server\
  mysql-client

WORKDIR /fitO2

COPY Gemfile /fitO2/Gemfile
COPY Gemfile.lock /fitO2/Gemfile.lock

RUN gem install bundler
RUN bundle install

RUN mkdir -p tmp/sockets

簡単な説明

FROM でruby:2.5.1 イメージをdocker hubからインストールします。
1回目のRUN で、必要なパッケージをインストールしています。
WORKDIR で作業ディレクトリを/fitO2に設定しています。
COPYで ホスト側のgemfileとgemfile.lockをコンテナの/fitOディレクトリにコピーしています。
2回目のRUN でbundlerをインストールします。
3回目のRUN でgemfileからパッケージをインストールします。
4回目のRUN ソケットファイルを作成しています。

fitO2/nginx_docker/Dockerfile.
FROM nginx:1.15.8

RUN rm -f /etc/nginx/conf.d/*

ADD nginx.conf /etc/nginx/conf.d/fitO2.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

簡単な説明

FROM でnginx:1.15.8 イメージをdocker hubからインストールします。
RUN でインクルード用のディレクトリ内を削除してます。
ADD でNginxの設定ファイルをコンテナにコピーしてます。
CMD でビルド完了後にNginxを起動するようにしてます。

fitO2/nginx_docker/nginx.conf
upstream fitO2 {
  server unix:///fitO2/tmp/sockets/puma.sock;
}

server {
  listen 80;
# =========ローカルと本番切り替え===========
  # server_name ◯◯◯.◯◯◯.◯◯◯.◯◯◯;
  server_name localhost;
# ======================================

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  root /fitO2/public;

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @fitO2;
  keepalive_timeout 5;

  location @fitO2 {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://fitO2;
  }
}

nginxの設定ファイルです。
ローカル環境の場合は、server_name をlocalhostにします。

config/puma.rb
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true

pumaの設定ファイルです。
bind "unix://#{app_root}/tmp/sockets/puma.sock"の部分は、nginx.confのserverと一致するようにしなければなりません。

参考

config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
# PasswordとUsernameはdocker-compose.ymlと合わせます
  username: root
  password: password
  host: db

development:
  <<: *default
  database: fitO2_development

test:
  <<: *default
  database: fitO2_test

コンテナ作成、起動

アプリのディレクトリに移動します。

コンテナを作成します。

docker-compose build

ネットワークを作成します。

docker network create fitO2-network

コンテナを起動します。

docker-compose up

初回はデーターベースの初期化を行います。
ターミナルを別タブで開き、アプリ直下に移動して下記コマンドをうちます。

docker-compose exec app rails db:create 
docker-compose exec app rails db:migrate
docker-compose exec app rails db:seed (シーダーがなければ不要)

うまくいかなかった場合、試行錯誤した場合は中途半端にシーダーが入ってしまって、バリデーションの関係で弾かれる場合があるので、一度下記の様にデーターベースを削除してから再度上記コマンドを実行してください。

docker-compose exec app rails db:drop

http://localhost

にアクセスすると、サイトにアクセスすることができます。

エラーがー生じた際は、docker-compose upターミナルにログが出ていますので、確認してください。

とりあえず既存のアプリをDockerコンテナ化できました。

次回(2)へ続く
AWSにVPCを作成する。パブリックサブネットを作成する。

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