- 投稿日:2021-01-12T23:01:22+09:00
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.値に合わせて、必要な処理を実装しましょう
- 投稿日:2021-01-12T22:32:14+09:00
フォームオブジェクトで複数のモデルを操作する[脱 accepts_nested_attributes_for]
かなり丁寧に書いたのでボリューム多めです。
分かっている部分はガンガン読み飛ばしてくださいやりたいこと
・
accepts_nested_attributes_for
を使わず複数のモデルを操作したい。
・データの新規作成だけでなく、更新もしたい。事前準備
accepts_nested_attributes_forとは?
関連付けられたモデルのレコードを一度に更新できるメソッドです。
上記の場合だと、メッセージとそれに紐づいた画像を保存できます。便利ですね。
ただ、実はこのメソッドはあまり評判が良くありません(詳しくはこちら)そこで、代替案のフォームオブジェクトを紹介していきます。
フォームオブジェクトとは?
モデルから切り離され、フォームの処理用に独立したクラスです。
これを使うと
accepts_nested_attributes_for
なしで複数のレコードを更新できます。
その他メリットや原理について詳しく知りたい方はこちらの記事を。説明がめちゃくちゃ分かりやすいです本記事の目標
ユーザー(親)がメッセージ(子)に画像(孫)を紐づけて送信できる機能を実装します。
親、子、孫モデルは皆さんの状況に合わせて変えてください。
割愛した部分
・ルーティング設定
・画像投稿に必要なcarrierwaveの設定
・各モデルのテーブル作成実装例
それでは見ていきましょう!
モデル
user.rbclass User < ApplicationRecord has_many :messages endmessage.rbclass Message < ApplicationRecord has_many :pictures belongs_to :user # ここに書いていたフォームのバリデーションは、フォームオブジェクトに移ります。 endpicture.rbclass Picture < ApplicationRecord belongs_to :message # 画像投稿しない方は、以下の記述は不要です。 mount_uploader :picture, PictureUploader endバリデーションに関する記述がなくなりました。
スッキリして読みやすいですねコントローラ
ここで注目して欲しいのは、下記の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"
となっているのは@message
がMessageForm
クラスから生成しているからです。(自動でこうなります。)次に、これに画像情報を追加するなら、下記のような形にしたいですよね。
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"=>
がコントローラのストロングパラメータに対応しています。ひとまず、これでコントローラに値を渡せそうですね
ちなみに、編集画面の場合は注意が必要です。
理由はこちらの記事に書いていますので、時間があれば参考にしてください。フォームオブジェクト用クラス
ここが一番ややこしい部分です。
4つに分けて解説しますので、気楽に読んでくださいmessage_form.rbclass 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 endpart-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
)という理由から少し改造しています
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?最後に、画像情報をメッセージにセットして終了です。
お疲れ様でした環境
ruby: 2.7.1
rails: 6.0.3.3
- 投稿日:2021-01-12T22:27:31+09:00
ActiveStorageを使う!
アウトプットに時間を使わなきゃと思いつつ中々やらずにいたので、重い腰をあげようかと…
今回は、毎度使っていてあれ?と振り返っていることなので自分が使っていることこちらに書き出してみようと思います。概要
- ActiveStorageとは
- 導入方法
- 使用方法
- 保存した画像を表示
- 画像加工のツールについて
- 感想
ActiveStorageとは
Active StorageとはAmazon S3、Google Cloud Storage、Microsoft Azure Storageなどの クラウドストレージサービスへのファイルのアップロードや、ファイルをActive Recordオブジェクトにアタッチする機能を提供します。development環境とtest環境向けのローカルディスクベースのサービスを利用できるようになっており、ファイルを下位のサービスにミラーリングしてバックアップや移行に用いることもできます。
アプリケーションでActive Storageを用いることで、ImageMagickで画像のアップロードを変換したり、 PDFやビデオなどの非画像アップロードの画像表現を生成したり、任意のファイルからメタデータを抽出したりできます。
画像アップロード機能が簡単に実装できるGemです。
ActiveStorageはRails5.2から標準で搭載されるようになりました。導入方法
- Active Record モデルを用意する まず、導入したいアプリケーションのディレクトリで
rails active_storage:installrails 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 :ファイル名 endhas_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 installgemの更新をしたので、サーバーの立ち上げを忘れずに。
こちらを実装すると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
- 投稿日:2021-01-12T22:22:44+09:00
【Rails】created_atを使って「〇〇分前」という表記にしたい!
概要
本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
開発環境
- Ruby 2.6.5p114
- Rails 6.0.3.4
方法
① 日本語設定をする
まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してくださいconfig/application.rbconfig.i18n.default_locale = :ja② 日本語のyamlファイルを作成する
次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。config/locales/ja.ymlja: 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
にて修正してください。
- 投稿日:2021-01-12T22:22:44+09:00
【Rails】created_atを使って「〇〇分前」と表示させたい!
概要
本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
開発環境
- Ruby 2.6.5p114
- Rails 6.0.3.4
手順
① 日本語設定をする
まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してくださいconfig/application.rbconfig.i18n.default_locale = :ja② 日本語のyamlファイルを作成する
次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。config/locales/ja.ymlja: 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
にて修正してください。
- 投稿日:2021-01-12T22:22:44+09:00
【Rails】created_atを使って「〇〇分前」と表示したい!
概要
本記事ではRailsのcreated_atで作成日時を表示する際に、従来の時間表記ではなく「〇〇分前」のように表示する方法を記載しています。
完成イメージは以下の通りです。
開発環境
- Ruby 2.6.5p114
- Rails 6.0.3.4
手順
① 日本語設定をする
まずは、日本語設定をします。
configのapplication.rbファイルに以下を記述してくださいconfig/application.rbconfig.i18n.default_locale = :ja② 日本語のyamlファイルを作成する
次に、どのような日本語表記にするかyamlファイルに設定を書いていきます。
日本語に関するja.ymlはRailsにデフォルトで入っていないため、以下を参考に作成してください。
ja.ymlファイルはconfig/locales配下に作成します。config/locales/ja.ymlja: 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
にて修正してください。
- 投稿日:2021-01-12T21:28:43+09:00
またしても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に注意しなければならないということを再確認しました。
この記事は以上です。
- 投稿日:2021-01-12T17:27:52+09:00
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.02.ログイン
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 cleardbRuby 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 master7.Heroku上でマイグレーションファイルを実行
データベースにはマイグレーションの情報が反映されていませんので、heroku runを頭につけて、マイグレーションを実行してください。
% heroku run rails db:migrate8.アプリケーションの情報を確認
これまでの作業で、Herokuに反映したアプリケーションの情報を確認するには、以下のコマンドを入力します。
% heroku apps:info9.アプリケーションを開く
% 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
- 投稿日:2021-01-12T17:23:55+09:00
【Rails6】Rubocop警告一覧
はじめに
転職活動用ポートフォリオ作成中です。
今回、CircleCiを導入したことにより、push時にRubocopでコードのスタイルチェックが行われるようになり、そこで指摘されるエラーをまとめていきたいと思いました。環境
Ruby on Rails '6.0.0'
Ruby '2.6.5'①Layout/ExtraSpacing: Unnecessary spacing detected.
不必要な、スペースが確認されたためです
app/models/company.rbvalidates_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上記コマンドを使用すれば、勝手に修正してくれるみたいですが、
なぜか意味を調べたくなったので、投稿しました。気が向けば新たな警告文が出た場合は、投稿していきます。。。。
- 投稿日:2021-01-12T16:31:11+09:00
ActiveHashの導入〜使用まで
Railsの勉強をしていてActiveHashというGemを使うことがあったので備忘録として残しておきます。
・ActiveHashとは
・ActiveHashの導入
・ActiveHashの使い方(長いですが手順に沿っていけば使えます)この3つについて触れておきます。まだプログラミング初心者なので深くは語れないです。
ActiveHashとは
Active_Hashとは、変更されないデータをモデルファイル内に直接記述することで、データベースへ保存せずに読み取り専用のデータを取り扱うことができるGemのことです。
分かりやすく言うと、、、
基本的に変更されないデータ(静的データ)、「都道府県」や「市区町村」などのデータを擬似モデルにハッシュ形式で格納して、呼び出して使う事ができる便利なや〜つです。
何が便利かって言うとテーブルを作らなくて済む事です!!!!
データの中身が変わらないときは基本的にActiveHashを使いましょう!
最初は難しく感じますがすぐに慣れます!ActiveHashの導入
まずGemfileに以下の一文を記述します
Gemfilegem '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.rbclass 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.rbclass Prefecture < ApplicationRecord end②の解説
・まず元々書かれていた「class Prefecture < ApplicationRecord」
は「ApplicationRecord」と言うクラスを継承してくださいと言う記述になるので消しました(ActiveHash::Baseと言うクラスを継承してほしいため)。・ActiveHashの基本的な格納方法は以下になります
activehashの格納方法class モデルクラス名 < ActiveHash::Base self.data = [ {カラム名: 値, カラム名: 値}, {カラム名: 値, カラム名: 値} ] endなので
idとnameがカラム名
整数と都道府県が値となります。③アソシエーションを定義
今回は「一つの商品は一つの都道府県を持っている」と言う形を取りたいので
「Item belongs to Prefecture」になります。
item.rbに以下の記述しましょう!models/item.rbextend 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.rbclass 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」する
使い方は頑張る最後までありがとうございました!
- 投稿日:2021-01-12T16:06:16+09:00
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: exitMore 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]成功。
- 投稿日:2021-01-12T14:26:21+09:00
Rubyのevalについて
- 投稿日:2021-01-12T14:24:52+09:00
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]ボタンをクリックします。次の画面でProject名を入力します。
[Quickstart]のままでも問題ないので入力したら[NEXT]をクリックします。
次の画面では作成するOAuth clientを選びます。
[Desktop app]を選択して[CREATE]ボタンをクリックします。
次の画面では[DOWNLOAD CLIENT CONFIGURATION]ボタンが表示されるのでクリックして
credentials.json
ファイルをダウンロードします。
続いてrubyのプログラムを作成していきます。
まずプログラムを格納するためのディレクトリを作成していきます。
今回は「sample_google_spreadsheet_api」ディレクトリを作成しました。(ディレクトリ名はなんでも大丈夫です)cd ~ mkdir sample_google_spreadsheet_api
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.rbquickstart.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が表示されるのでそれにアクセスします。
3: 認可コードが表示されるので実行画面へ貼り付けReturn
実行結果へ貼り付けて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スプレッドシートの内容が表示されれば成功です。
- 投稿日:2021-01-12T13:44:06+09:00
ar
See the Pen #genuary 10 Tree by Johan Karlsson (@DonKarlssonSan) on CodePen.
- 投稿日:2021-01-12T12:33:44+09:00
NoMethodError: undefined method `setup' for Devise:Module
- 投稿日:2021-01-12T11:50:05+09:00
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が入り
ログインした画面へリダイレクトされます。
- 投稿日:2021-01-12T11:31:43+09:00
【Rails6】SNS認証による新規登録機能実装(Facebook, Google)
はじめに
現在Raisを使用しアプリケーションの作成を行なっています。今回は既に実装しているログイン機能にSNSアカウントでログインできるよう追加実装していきます。備忘録及び復習のため記述していきます。
環境
Ruby on Rails '6.0.0'
Ruby '2.6.5'前提
- devise(gem)を使用し、ユーザーのログイン機能実装済み。
- facebook for developersと Google Cloud Platformから外部APIの設定済み。
①Gemの導入と環境変数の設定
omniauth
・Google,Facebook,Twitter等のSNSアカウントを用いてユーザー登録やログインなどを実装できるgemです。今回はFacebookとGoogleのomniauthをインストールします。Gemfilegem 'omniauth-google-oauth2' gem 'omniauth-facebook'ターミナル% bundle installまた、外部API設定時に取得したIDやシークレットキーを環境変数に設定します。環境変数の設定方法はいくつかあると思いますが、私の場合は、「gem 'dotenv-rails'」により「.envファイル」を設定済みのため、そこに記述しました。
.envFACEBOOK_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_credentialdb/migrate/XXXXXXXXXXXXXXXX_create_sns_credentials.rbclass 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 enduserモデルとのアソシエーションのため、外部キーとして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.rbclass SnsCredential < ApplicationRecord belongs_to :user end③deviseの再設定
deviseのコントローラーを再設定するため、コントローラーを作成しています。
ターミナル% rails g devise:controllers users生成したコントローラーを使用させるために、deviseのルーティングを変更します。
config/routesRails.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.rbclass 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.rbclass 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 endUserモデルから返ってきた値を@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 endpersisted?で登録済みのユーザーと判断を行い、「if文」の中が呼ばれます。新規登録時は、SnsCredentialモデルが保存されるタイミングで、user_idが確定していなかったので、SnsCredentialモデルとUserモデルは紐づいていません。ログインの際に、sns.userを更新して紐付けを行います。
最後にログイン画面のviewファイルも編集して完了です!!
終わりに
もしこの記事を参考に、実装してみて、うまくできない場合は教えてください。。初めての実装のためお許しください。。
- 投稿日:2021-01-12T09:25:16+09:00
配列
配列とは
一つの変数で複数の値を順番で管理することができるというものです。
配列は角括弧[ ]で囲い生成します。例
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が出力される 以上です。
- 投稿日:2021-01-12T09:01:31+09:00
[Rails]date_selectにBootstrapのform-controlが適用されない
課題
Rails + Boostrap でdate_selectにform-controlが適用されない
_form.html.erb<%= form.date_select :date, class: "form-control" %>結論
以下のような書き方で適用されます。
_form.html.erb<%= form.date_select :date, {}, {class: 'form-control', style: 'display: inline-block;width: auto;'} %>ちなみにこの style: 'display: inline-block;width: auto; をサボると想定外にダッさい表示になります。
地味に初学者が躓くので再度メモしました。
技術背景も含めて以下記事が完全回答版です。
過去の先人たちに感謝いたします。参考情報
Rails date_selectをBootstrapを使っていい感じにする
https://qiita.com/t_oginogin/items/519fb52e1708e26a8b73
- 投稿日:2021-01-12T07:19:16+09:00
ローカルに保存されている画像データをZIPダウンロードしたいとき
解決したいこと
ローカルに保存されている、main.jpg、top_map_off.jpgをzipダウンロードしたい。
解決策
画像はバイナリモードでしか書き出せないので、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
- 投稿日:2021-01-12T01:25:52+09:00
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
こちらを参考にさせていただきました。
- 投稿日:2021-01-12T01:02:43+09:00
enumの使い方(日本語表記の導入)
今回はECサイトの制作をしていた際の注文ステータスのカラムにenumを使用し、プルダウン表記を活用しました。enumを使用することが初めてであったので、忘れない様に自身のメモとして記事を残したいと思います。
以下の記事は実装の際に参考にさせていただきました。(参考記事)
・【初心者向け】i18nを利用して、enumのf.selectオプションを日本語化する[Rails]
https://qiita.com/tanutanu/items/d44a92425188a4489ec6
・[初学者]Railsのi18nによる日本語化対応
https://qiita.com/shimadama/items/7e5c3d75c9a9f51abdd5Gemfile
Gemfile.gem 'enum_help'terminal.bundle installまずは、enumを使用するのに必要なgemをGemfileに記述します。
上記のように記述ができたら、ターミナルでbundle installのコマンドを実行します。model.rb
order_item.rbenum 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.rbmodule 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.ymlja: 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に必要な記述を少し書き加えるだけで、アプリケーション全体に変更を行うことが可能です。
今回は以上になります。
私自身もプログラミング初心者ですが、同じ様な立場の方に少しでも参考になれば幸いです。
また、もし内容に誤りなどがございましたら、ご指摘いただけますと幸いです。
- 投稿日:2021-01-12T00:09:41+09:00
【Rails AWS Docker】既存Ruby on Rails + MySQLアプリをDockerで構築し、AWSにデプロイする(1)
ポートフォリをとして作ったRuby on RailsアプリをDockerコンテナ化し本番環境をAWSで構築するまでの道のりです。
ポートフォリオ自体はこちらとなります。
【ポートフォリオ】転職活動時に作成したポートフォリオの概要(テッ◯キャンプ)かなり苦しめられたので、どなたかのお役に立てれば。
タイトル 1 ローカル環境のRailsアプリをDockerコンテナ化 参考にさせていただいた記事は、(6)の末尾い記載しています。
ローカル環境のRailsアプリをDockerコンテナ化
Dockerコンテナ環境構成(ローカル版)
構成は上記です。
webサーバーとしてnginxを配置します。
appサーバーはpumaを設置します。まず既存のRailsアプリをコンテナ化します。
前提
AWSにアカウント作成済
docker hubにアカウント作成済
docker インストール済
ローカル環境で動作するRailsアプリ構築
※fitO2の部分は、ご自身のアプリ名に変更してください。
現状のアプリのフォルダ構成に、下記のファイルを追加します。
docker-compose.yml Dockerfile nginx_docker ├── Dockerfile └── nginx.conf config └── puma.rbdocker-compose.ymlversion: '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.confupstream 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.rbthreads_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", truepumaの設定ファイルです。
bind "unix://#{app_root}/tmp/sockets/puma.sock"の部分は、nginx.confのserverと一致するようにしなければなりません。参考
config/database.ymldefault: &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にアクセスすると、サイトにアクセスすることができます。
エラーがー生じた際は、docker-compose upターミナルにログが出ていますので、確認してください。
とりあえず既存のアプリをDockerコンテナ化できました。
次回(2)へ続く
[AWSにVPCを作成する。パブリックサブネットを作成する。]
- 投稿日:2021-01-12T00:09:41+09:00
【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コンテナ環境構成(ローカル版)
構成は上記です。
webサーバーとしてnginxを配置します。
appサーバーはpumaを設置します。まず既存のRailsアプリをコンテナ化します。
前提
AWSにアカウント作成済
docker hubにアカウント作成済
docker インストール済
ローカル環境で動作するRailsアプリ構築
※fitO2の部分は、ご自身のアプリ名に変更してください。
現状のアプリのフォルダ構成に、下記のファイルを追加します。
docker-compose.yml Dockerfile nginx_docker ├── Dockerfile └── nginx.conf config └── puma.rbdocker-compose.ymlversion: '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.confupstream 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.rbthreads_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", truepumaの設定ファイルです。
bind "unix://#{app_root}/tmp/sockets/puma.sock"の部分は、nginx.confのserverと一致するようにしなければなりません。参考
config/database.ymldefault: &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にアクセスすると、サイトにアクセスすることができます。
エラーがー生じた際は、docker-compose upターミナルにログが出ていますので、確認してください。
とりあえず既存のアプリをDockerコンテナ化できました。
次回(2)へ続く
AWSにVPCを作成する。パブリックサブネットを作成する。