- 投稿日:2020-11-08T23:47:32+09:00
【AWS】EC2インスタンス(ターゲットグループ)のヘルスチェックにどうしても失敗する。。
現在個人開発しているRailsアプリがやっと完成し、AWSへのデプロイ作業にチャレンジしました。(ECR × ECS × CircleCIを利用し、gitからmasterブランチへpushするとCIがビルドされ、テストが通ると自動デプロイされるといった感じの構成です)
その際、EC2インスタンスへのヘルスチェックが通らずに困り果ててしまったので、ついにプログラミング独学勢?の切り札であるMENTAを利用しました。
そしたら1~2週間程悩んでいた問題が開始わずか10分ほどで解決してしまい、あっけなく終わってしまいました。。調べてもこの解決方法はなかなか見当たらなかった(多分僕の探し方の問題)ので、僕と同じように膨大な時間を無駄にしないためにも、ここに備忘録として残そうと思います!
今回相談させていただいたメンタ―さん
ルビコン@クラウド(AWS)エンジニアさん
ルビコンさんのTwitter単発の利用だったにもかかわらず、本題とは関係ない質問にも親切丁寧に教えてくださりました。(しかも15分ぐらいお時間サービスしていただきました。)
この記事でもどうにもならない場合、是非一度相談してみてください!僕もまた困ったら是非利用したいと思える方でしたそもそもヘルスチェックとは?
ヘルスチェックは、特定のサーバー上のサービスに、作業を正常に実行できるかどうかを確認する方法です。 ロードバランサーは、各サーバーにこの質問を定期的に行い、トラフィックを転送しても安全なサーバーを判断します。
イメージ的には、定期的にロードバランサ―からターゲットグループ(EC2インスタンス)にリクエストを送り返ってきたHTTPステータスを確認しているといった感じです。
このヘルスチェックが失敗してしまうせいでタスクが強制終了してしまい、また新しいタスクが起動するといったことを20分毎ぐらいのペースで繰り返していました。(
health checks failed with these codes: [301]
というエラーが出てました)
そのせいで、アクセスするタイミングによってページが表示されたり、50xエラーが返ってきたりしているといった状況でした。解決方法
結論から言いますと、ターゲットグループの設定でHTTPステータスコードの301を許可したら治りました。
上記にも述べました通り、
health checks failed with these codes: [301]
というのはページがリダイレクトされたということを意味します。なぜリダイレクトされるかというと、Railsアプリの設定で以下のような設定をしていたためです。production.rbconfig.force_ssl = trueSSL化をしていたので、httpからhttpsにリダイレクトさせる設定をしていました。
ですので、ヘルスチェックを行ったときにHTTPステータスコードで301が返ってきて当然です。しかし、ターゲットグループの設定では200しか許可されていないので、unhealthyとなってしまいタスクが終了してしまうのです。
設定方法
まず、サービスからEC2を選択し、ターゲットグループをクリックします。すると以下のような画面が出てくるので、そこにあるGroup detailsタブのEditをクリックします。
Success codesに200と書いてあると思いますので、ここに301を追加して許可します。(カンマで区切って記述してください!)
記述したら変更を保存して終了です。
これで数分すればhealthyに変わるはずです!(ALBやセキュリティグループなどの設定が正しくできていれば)
これでもヘルスチェックに失敗する場合はセキュリティグループやALBの設定、ヘルスチェックの猶予時間などの設定に問題がある能性がありますので、見直してみてください!
それでも解決しない場合、是非ルビコン@クラウド(AWS)エンジニアさんに相談してみてください
最後まで読んでいただきありがとうございます!
日々学んだことをアウトプットしております!!ご指摘などございましたらコメントいただけますと幸いです。
- 投稿日:2020-11-08T23:43:37+09:00
Leet文字列(ruby編)
Leet文字列(ruby編)
先に言っておくと、期待通りの出力ができなかったので分かる方教えて頂けると嬉しいです?
問題
Leet ではいくつかのアルファベットをよく似た形の他の文字に置き換えて表記します。 Leet の置き換え規則はたくさんありますが、ここでは次の置き換え規則のみを考えましょう。
置き換え前 置き換え後
A 8
E 3
G 6
I 1
O 0
S 5
Z 2文字列が入力されるので、これを Leet に変換して出力するプログラムを書いてください。
入力される値
入力は以下のフォーマットで与えられます。
i
i は Leet に変換する前の文字列を表します。期待する出力
i を Leet に変換した文字列を1行に出力してください。
入力例1
APPLE
出力例1
8PPL3
私の答え(誤った答えです)
rubyarray = { "A" => "8", "E" => "3", "G" => "6", "I" => "1", "O" => "0", "S" => "5", "Z" => "2" } str = gets.gsub(/[A-Z]/, array) print str悩んだポイント
今回のポイントは「アルファベットが当てはまったら該当のアルファベットのみ数字に置換する」という事でした。ですが、gsub(/[A-Z]/)でこのままのコードでは出力時に83のみ出力されてしまいます。(おそらく条件分岐でelseを用いて当てはまらなった部分を出力できていないから。)8PPL3と出力する際の正規表現なのかそもそものgsubメソッド以外に適したメソッドがあるのか分かりませんでした。ちなみに下記に条件分岐での手段も試してみましたがうまくいきませんでした。
str = gets.chomp.split(" ") str.each { |s| case s when "A" print "8" when "E" print "3" ・ ・ ・ (省略) else print s }この問題の8PPL3という出力結果にする方法は様々あると思いますが当てはまったら部分的に置換するという方法がこれ以上できませんでした。もし分かる方、お優しい方がいらっしゃいましたらご教授頂けますと幸いです。
以上
- 投稿日:2020-11-08T23:39:50+09:00
ターミナルでRailsコマンドを確認する方法
- 投稿日:2020-11-08T23:21:41+09:00
間違ってモデルを作ってしまった時の対処法
結論
下記のようにモデルのみを指定して削除します。
ターミナルrails destroy model [モデル名]モデルを削除するときはカラムを指定する必要はないのでこのようなコマンドになります。
ただ、
rails db:migrate
をしてしまっていた場合は、テーブルを作成してしまっているので念のためにテーブルの削除をした方がいいと思います。テーブルの削除方法
マイグレーションファイルを編集する
create_table :comments
の部分をdrop_table :comments
に変更する。class CreateComments < ActiveRecord::Migration[6.0] def change create_table :comments do |t| t.string :content end end最後にターミナルで以下のコマンドを打てばデータベールからテーブルが削除されます。
rails db:migrate参考記事
- 投稿日:2020-11-08T23:05:22+09:00
RSpecとFactoryBotを使ったインスタンスの作成
はじめに
先日こんなツイートをしました。
RSpecわからん!ということで時間とって書籍で勉強。#everydayrailsjp #駆け出しエンジニア
— Shun Tagami (@programganbaru) November 6, 2020
こちらがその書籍です。単にRSpecの使い方を説明しているだけでなく、なぜテストをするのか、テストの原則、等RSpecにとらわれない考え方というのが非常に参考になりました。
https://leanpub.com/everydayrailsrspec-jp
こちらの書籍を参考にしながら以下のような自分なりには納得できるモデルのテストをパスさせることができたので学んだことをアウトプットしていきたいと思います。その前になぜテストをするのか、何をテストするのか、ということもまとめたのでこちらの記事もご覧ください。
https://qiita.com/shuntagami23/items/c4d6bd2ad282f670156d
1 インスタンスの生成(基本)
UserモデルのテストをするにはUserのインスタンスを生成する必要があります。例えばname, email, passwordのカラムを持ったUserモデルのインスタンスは以下のように生成するのが一般的です。
spec/models/user_spec.rbequire 'rails_helper' RSpec.describe User, type: :model do before do @user = User.create ( name: 'test', email: 'sample@exmple.com', password: '123456' ) end # ここからexampleを書いていく endbefore do〜endの中でインスタンスを生成しておくことで各exampleが実行される前に毎回@userを生成します。gemのFactoryBotを使うとこのインスタンスは別の場所に置いておくことができます。以下のように書きます。
spec/factories/users.rbFactoryBot.define do factory :user do name { 'TestUser' } sequence(:email) { |n| "test#{n}@example.com" } password { 'password' } end endここで生成したインスタンスは次のようにして呼び出します。
spec/models/user_spec.rbrequire 'rails_helper' describe User do before do @user = create(:user) end it '有効なファクトリを持つこと' do expect(@user).to be_valid end endたったこれだけだとあまり利点を感じられないですが、デモデータが多くなったときや関連するモデルのインスタンスを生成する際に非常に役に立ちます。
2 letとlet!について
上記の呼び出し方の他にletを使う方法があります。以下のようになります。
spec/models/user_spec.rbrequire 'rails_helper' describe User do let(:user) { create(:user) } it '有効なファクトリを持つこと' do expect(user).to be_valid end endletを使う方法だとbefore do〜endで毎回@userを作らなくていいので簡潔に書けますね!また、beforeを使った時のように、exampleが読み込まれる前に@userを生成しておきたい場合はletをlet!に変えるだけでできます。ただし、letはインスタンスの呼び出しにしか使えないのでインスタンスを呼び出すときはletとlet!を使いそれ以外の処理をするときにbeforeを使うというように使い分けるといいと思います。
3 関連するモデルのインスタンスの生成
アソシエーション先のインスタンスを生成したい時もあります。例えば、「userを削除すると、関連するpostも削除されること」を確かめるときです。
spec/models/user_spec.rbit 'ユーザーを削除すると、関連するコメントも削除されること' do post = user.create ( text: 'test' ) expect { user.destroy }.to change { Post.count }.by(-1) endこの書き方でも問題はないですがtraitとcreate_listというメソッドを使ってリファクタリングができます。先にコードを見せます。
spec/factories/users.rbFactoryBot.define do factory :user do name { 'TestUser' } sequence(:email) { |n| "test#{n}@example.com" } password { 'password' } end trait :with_posts do after(:create) do |user| create_list(:post, 1, user: user) end end endspec/models/user_spec.rbit 'ユーザーを削除すると、関連する投稿も削除されること' do user = create(:user, :with_posts) expect { user.destroy }.to change { Post.count }.by(-1) end① trait
これはクラスの継承を表します。これを使うことによってuserのname, email, passwordを継承してくれるので、再定義しなくてよいということになります。クラスを継承しつつuserにpostを持たせます。この処理をするのが次のcreate_listです。② create_list
これは複数のインスタンスを生成するメソッドです。第一引数に元となるファクトリ、第二引数にその個数、それ以降にカラムに入る値を設定します。ただし、以下のようにpostインスタンスをFactoryBot内で定義してあり、アソシエーションが組まれていることが条件です。spec/factories/posts.rbFactoryBot.define do factory :post do text { 'test' } association :user end end①、②をまとめると、user = create(:user, :with_posts)とすることでpostを1つもったuserインスタンスを生成できるということです。このuserをdestroyしたときにPost.countが1減っているか、ということをテストしています。
最後に
今回は書籍を使って学習したうちのごく一部をまとめただけになりましたが、文章を書くのに慣れていないこともあり非常に大変だなあと感じています。拙い文章で申し訳ないです。。
- 投稿日:2020-11-08T19:22:07+09:00
net/httpを利用した外部API連携クラスの作成手順
アプリケーション開発をするにあたり、外部サービスとAPI連携を行う場合があります。
RubyではHTTP通信を行う標準ライブラリとしてnet/httpが用意されています。
今回はnet/httpを利用して、API経由で外部サービスのデータを取得する方法について紹介します。今回作成するもの
今回は外部サービスの例としてQiitaを利用します。
ゴールは以下の通りです。
QiitaClientAPI
というQiita APIと連携するクラスを作成するget_items
というクラスメソッドを利用することでQiitaの記事一覧が取得できるようにする- API連携でエラーが発生した場合は、エラーレスポンスの内容を例外として出力する
localhost:3000/qiita_items
を実行するとQiitaClientAPI.get_items
の結果が取得できるなお、今回紹介するサンプルは
rails 6.0.3.4
のAPIモードで作成しています。最終的な成果物
今回は手順を追いながらソースコードの紹介をしていきます。
最終的なアウトプットは以下のようになります。ソースコード
config/routes.rbRails.application.routes.draw do resources :qiita_items, only: %i(index) endapp/controllers/qiita_items_controller.rbclass QiitaItemsController < ApplicationController def index response_json = QiitaApiClient.get_items # レスポンスを簡略化するため、titleプロパティのみ返すようにしている render json: response_json.map {|item| item.slice('title') } end endlib/qiita_api_client.rbclass QiitaApiClient class HTTPError < StandardError def initialize(response) super "code=#{response.code} body=#{response.body}" end end def initialize @token = Rails.application.credentials.qiita[:token] end def get_items request = Net::HTTP::Get.new( '/api/v2/items', 'Authorization' => "Bearer #{@token}" ) http_client.request(request) response = http_client.request(request) case response when Net::HTTPSuccess JSON.parse(response.body) else raise QiitaApiClient::HTTPError.new(response) end end class << self def client QiitaApiClient.new end def get_items client.get_items end end private QIITA_HOST = 'https://qiita.com' def http_client uri = URI.parse(QIITA_HOST) http_client = Net::HTTP.new(uri.host, uri.port) http_client.use_ssl = true http_client end end実行結果
API連携が成功した場合は以下のような結果になります。
$ curl 'http://localhost:3000/qiita_items' [{"title":"highlight.jsを動的に使ってみた - CodePen"},{"title":"飛び飛びセル順次コピペ"},{"title":"テキスト入力中の点滅するカーソルに好きなCSSを当てる方法"},{"title":"jQueryいろいろ(wrapAll, MutationObserverなど)"},{"title":"高機能なSQL開発ツール「A5:SQL Mk-2」をUbuntuで使う"},{"title":"Amazon Aurora カスタムエンドポイントの検証と考察"},... (略) ... ]API連携が失敗した場合は以下のような結果になります。
### 不正なトークンが利用されている場合 $ curl 'http://localhost:3000/qiita_items' QiitaApiClient::HTTPError (code=401 body={"message":"Unauthorized","type":"unauthorized"})下準備
実装をするにあたり、Qiita APIと連携するための準備をします。
アクセストークンの取得
Qiita APIの認証認可に必要なアクセストークンを取得します。
アクセストークンはユーザの管理画面で取得できます。
なおQiita APIのGETリクエストではアクセストークンは不要なため1、今回紹介する記事一覧取得API(
/api/v2/items
)のみを実装したい場合はこの作業は不要です。アクセストークンをRailsアプリケーションに登録する
今回はRails 6のcredentialsにトークンを保存しました。
### config/credentials/development.yml.encの編集 $ export EDITOR="vim" $ rails credentials:edit -e development → このタイミングでconfig/credentials/development.keyとconfig/credentials/development.yml.encが作成されるconfig/credentials/development.yml.encqiita: # 取得したトークンをセットする token: xxxxxxx$ rails c ### 取得したトークンが表示されればOK > Rails.application.credentials.qiita[:token] => 'xxxxxxx'ルーティングの追加
検証で利用する
localhost:3000/qiita_items
のエンドポイントを作成します。config/routes.rbRails.application.routes.draw do resources :qiita_items, only: %i(index) endlib配下のクラスを読み込むようにする
今回はQiita APIと連携するクラスを
lib
配下に作成します。
lib
配下のクラスが読み込まれるようにするため以下のように修正します。config/application.rbmodule RailsApiClient class Application < Rails::Application # 以下を追加 config.paths.add 'lib', eager_load: true end endnet/httpを利用した外部APIとの連携方法
ここからは順を追って実装について紹介していきます。
シンプルな方法
Qiita APIと連携するクラスを作成せず、ロジック2を直接記述するパターンです。
app/controllers/qiita_items_controller.rbclass QiitaItemsController < ApplicationController def index uri = URI.parse('https://qiita.com') http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true request = Net::HTTP::Get.new( '/api/v2/items', 'Authorization' => "Bearer #{Rails.application.credentials.qiita[:token]}" ) response = http.request(request) response_json = JSON.parse(response.body) # レスポンスを簡略化するため、titleプロパティのみ返すようにしている render json: response_json.map {|item| item.slice('title') } end endソースコードを見ればロジックはわかると思いますが、net/httpを利用したGETリクエストの流れについて改めてまとめると以下のようになります。
Net::HTTP.new
でHTTPのクライアントのオブジェクトを作成Net::HTTP::Get.new
でGETリクエストのオブジェクトを作成- HTTPクライアントを利用してGETのリクエスト
Qiita APIと連携する専用クラスを作成する
QiitaApiClient.get_items
を呼ぶことでデータが取得できるようにします。
lib
配下にQiitaApiClient
クラスを作成し、API連携のロジックを移行します。lib/qiita_api_client.rbclass QiitaApiClient class << self QIITA_HOST = 'https://qiita.com' def get_items uri = URI.parse(QIITA_HOST) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true # SSLを有効化する request = Net::HTTP::Get.new( '/api/v2/items', # 記事一覧を取得するエンドポイント 'Authorization' => "Bearer #{Rails.application.credentials.qiita[:token]}" # Bearer認証 ) response = http.request(request) JSON.parse(response.body) end end endAPI連携のロジックを
QiitaApiClient
に移行したので呼び出す側は以下のようになります。app/controllers/qiita_items_controller.rbclass QiitaItemsController < ApplicationController def index response_json = QiitaApiClient.get_items render json: response_json.map {|item| item.slice('title') } end endリファクタ: Net::HTTPオブジェクトの作成を共通化する
このままでも問題ないのですが、クラスメソッド(今回でいう
get_items
)でNet::HTTP.new
を実行しているため、クラスメソッドを追加するたびにNet::HTTP.new
も追加されてソースコードが少し冗長になります。そこで、
Net::HTTP.new
を実行するインスタンスメソッドを作成します。lib/qiita_api_client.rbclass QiitaApiClient def initialize # トークンはインスタンス変数として呼び出せるようにする @token = Rails.application.credentials.qiita[:token] end def get_items request = Net::HTTP::Get.new( '/api/v2/items', 'Authorization' => "Bearer #{@token}" ) # self.http_clientを呼び出す response = http_client.request(request) JSON.parse(response.body) end private QIITA_HOST = 'https://qiita.com' def http_client uri = URI.parse(QIITA_HOST) http_client = Net::HTTP.new(uri.host, uri.port) http_client.use_ssl = true http_client end end
QiitaApiClient
を呼び出す側は以下のようになります。app/controllers/qiita_items_controller.rbclass QiitaItemsController < ApplicationController def index qiita_client = QiitaApiClient.new response_json = qiita_client.get_items render json: response_json.map {|item| item.slice('title') } end endリファクタ: QiitaApiClientインスタンスを呼び出し側で作成しなくて済むようにする
インスタンスを作成しなくても
QiitaApiClient.get_items
を実行するだけでAPI連携できるようにリファクタリングした結果は以下のとおりです。lib/qiita_api_client.rbclass QiitaApiClient def initialize @token = Rails.application.credentials.qiita[:token] end def get_items request = Net::HTTP::Get.new( '/api/v2/items', 'Authorization' => "Bearer #{@token}" ) response = http_client.request(request) JSON.parse(response.body) end class << self def client QiitaApiClient.new end def get_items client.get_items end end private QIITA_HOST = 'https://qiita.com' def http_client uri = URI.parse(QIITA_HOST) http_client = Net::HTTP.new(uri.host, uri.port) http_client.use_ssl = true http_client end endこれで、以下のコードでQiita APIからデータを取得できるようになりました。
app/controllers/qiita_items_controller.rbclass QiitaItemsController < ApplicationController def index response_json = QiitaApiClient.get_items render json: response_json.map {|item| item.slice('title') } end end例外処理の追加
Qiita APIのレスポンスがエラーだった場合、APIのエラーの内容がわかるよう例外処理を追加します。
今回はエラー時のレスポンスの内容とエラーコードを例外のメッセージに追加しました。lib/qiita_api_client.rbclass QiitaApiClient def initialize @token = Rails.application.credentials.qiita[:token] end def get_items request = Net::HTTP::Get.new( '/api/v2/items', 'Authorization' => "Bearer #{@token}" ) response = http_client.request(request) case response when Net::HTTPSuccess JSON.parse(response.body) else raise "code= #{response.code}, body = #{response.body}" end end class << self def client QiitaApiClient.new end def get_items client.get_items end end private QIITA_HOST = 'https://qiita.com' def http_client uri = URI.parse(QIITA_HOST) http_client = Net::HTTP.new(uri.host, uri.port) http_client.use_ssl = true http_client end endたとえば、不正なトークンをセットしてリクエストを送った場合、以下のような例外が発生します。
$ curl 'http://localhost:3000/qiita_items' RuntimeError (code= 401, body = {"message":"Unauthorized","type":"unauthorized"}):カスタム例外を作成する
カスタム例外には例外の発生場所がわかりやすくなるというメリットがあります。カスタム例外の作成については賛否両論ありますが一応紹介しておきます。
今回は
StandardError
を継承したHTTPError
というカスタム例外を作成しました。lib/qiita_api_client.rbclass QiitaApiClient class HTTPError < StandardError def initialize(response) super "code=#{response.code} body=#{response.body}" end end def initialize @token = Rails.application.credentials.qiita[:token] end def get_items request = Net::HTTP::Get.new( '/api/v2/items', 'Authorization' => "Bearer #{@token}" ) http_client.request(request) response = http_client.request(request) case response when Net::HTTPSuccess JSON.parse(response.body) else raise QiitaApiClient::HTTPError.new(response) end end class << self def client QiitaApiClient.new end def get_items client.get_items end end private QIITA_HOST = 'https://qiita.com' def http_client uri = URI.parse(QIITA_HOST) http_client = Net::HTTP.new(uri.host, uri.port) http_client.use_ssl = true http_client end endこれにより、カスタム例外のクラスで例外処理がされるようになりました。
$ curl 'http://localhost:3000/qiita_items' QiitaApiClient::HTTPError (code=401 body={"message":"Unauthorized","type":"unauthorized"}):まとめ
以上でnet/httpを利用した外部API連携の方法の紹介を終わります。
外部APIと連携する専用のクラスを作成することで
QiitaApiClient.get_items
のような形で外部サービスのデータを取得できます。
今回はシンプルなGETメソッドのみを実装しましたが、同様の手順でクエリやリクエストボディのついたメソッドの実装もできます。今回のサンプルはあくまで一例ですので、よりよい方法があれば教えていただけるとありがたいです。
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!
- 投稿日:2020-11-08T19:20:44+09:00
【Rails】Travis CIを導入してdb:createで詰まったこと
はじめに
自動テストツール
Travis CI
を導入するときにつまづいたことを書きます。環境
・Ruby 2.6.5
・Rails 6.0.3.2
・Mysql Ver 14.14 Distrib 5.6.47問題点
以下のような
.travis.yml
に記述しているdb:create
がどうしても通らないという問題が発生しました。script: - bundle exec rake db:create RAILS_ENV=test - bundle exec rake db:migrate RAILS_ENV=test - bundle exec rspec
Travis CI
の画面ではこのようなエラーメッセージがでました。Mysql2::Error::ConnectionError: Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)いろいろ調べてみるとソケットファイルが無いということがわかりました。
database.yml
の記述のソケットファイルへのパスが誤っているということです。
今回使用したDBはMySQL
です。test: <<: *default database: (myapp)_test adapter: mysql2 encoding: utf8 username: root password: socket: (ここの記述)解決策
でもローカルではない
Travis CI
のなかのファイル構成はどうしてもわからなかったので自力で確認することにしました。まずはソケットファイルの場所を調べるコマンドは以下です。
$ mysql_config --socketローカル環境ではこれでわかるので、もしかしたら
.travis.yml
のscript:
に記述すれば「Travis CI上のmysqld.sock
の場所がわかるかもと思い、この記述をプラスしました。(正しいやり方かはわかりませんけど・・・)script: - mysql_config --socket (←ソケットファイルを確認する) - bundle exec rake db:create RAILS_ENV=test - bundle exec rake db:migrate RAILS_ENV=test - bundle exec rspecすると、
なんと値が返ってきました!515行目の記述です!
/var/run/mysql/mysqld.sockこれをdatabase.ymlに記述すれば、無事に
db:create
が通りました!ローカルの時とTravisCIの時の使い分け
このままだと逆にローカルでのテストがうまくいかないので
TravisCI
でのテストの時には上記でしらべたソケットを参照するようにします。
TravisCI
専用としてdatabase_travis.yml
を作成し以下のような記述。test: <<: *default database: (myapp)_test adapter: mysql2 encoding: utf8 username: root password: socket: /var/run/mysqld/mysqld.sockそして
.travis.yml
に以下を追記before_script: - "cp config/database_travis.yml config/database.yml"この時だけ書き換えるようにしたらローカルでも
TravisCI
でもテストが動きました。
データベースのエラーはなかなか原因にたどり着けなく苦労しました。無事にこのバッジをReadmeにつけることができました!!
- 投稿日:2020-11-08T19:15:15+09:00
【Ruby/Rails】Rails6でのjQuery導入方法
はじめに
やりたいこと
- いいね機能のAjax対応を実装したい
- そのためにjQueryを導入したい
動作環境
ruby 2.6.5 / Rails 6.0.3.4
やってみて
Rails6から標準装備されているWebpackerを利用することで簡単に実装できました。
多くの記事がRails5までの情報でとっっっても遠回りしたので、6以降の方は僕のように無駄な時間を使わないでいただけたら…参考URL
Railsガイド > Rails で JavaScript を使用する
手順
- jQueryのインストール
- Webpackの設定
- application.jsの設定
- 動作確認
jQueryのインストール
そもそもjQueryとは
JavaScriptをより簡単に記述するためのライブラリのひとつ。
したがって、jQuery(ライブラリ)を使うのであればJavaScript(元のプログラミング言語)にコンパイル(翻訳)する必要があります。
コンパイルする方法はいろいろありますが、初学者故、とりあえず簡単なWebpackerを使用していこう!というのが本記事の主旨であります。ではまず、jQueryのインストールを行います。
ターミナル% yarn add jqueryRails5以前の導入方法ではjquery-railsというGemをインストールするのが基本線のようですが、Webpackerで管理する際はyarnコマンドを使用してインストールします。
yarnとは
JavaScriptのパッケージマネージャー。Node.jsで動作するパッケージを管理する。
Node.jsとは
本来フロントサイド開発用の言語であるJavaScriptをサーバーサイドで使うための「環境」のこと
Node.jsのおかげで簡単にAjax対応ができたりする。つまり、Webpackerという翻訳機にjQueryという言語をyarnというサーバー用の説明書で登録した、という感じでしょうか(違ってたらごめんなさい)
Webpackの設定
Webpackの設定ファイルでjQueryを管理下として認定します。
config/webpack/environment.jsconst { environment } = require('@rails/webpacker') // 以下追記 const webpack = require('webpack') environment.plugins.prepend('Provide', new webpack.ProvidePlugin({ $: 'jquery/src/jquery', jQuery: 'jquery/src/jquery' }) ) // ここまで module.exports = environmentapplication.jsの設定
application.jsでjQueryを呼び出せるようにします。
javascript/packs/application.js//中略 require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") // 追記 require('jquery') //中略以上で導入は完了です。
動作確認
jQueryが問題なく動作しているか確認します。
好きなページに次のコードを転記します。好きなページ.html.erb<p>テスト</p> <script type="text/javascript"> $(document).ready(function() { $("p").text("成功!!"); }); </script>p要素のテキストに”成功!!”を代入するようになっています。
(成功例)
このように「テスト」ではなく「成功!!」と表示されていれば動作確認は完了です。おわりに
いろいろな記事を調べてgemを導入したりしていたのですが、Webpackerを使えば簡単に実装できました。
特に今回、JavaScriptとjQueryを調べていく中で、Node.jsのことを表面的にでも理解できたのはよかったかなと思います。
参考にさせていただいた記事
エンジニアの入り口 > 初心者向け!3分で理解するNode.jsとは何か?
Ruby / Rails 初学者向けの記事を書いています。
今後も週3〜4記事ペースで更新していきたいと思いますので、初学者のみなさん、ぜひフォローお願いします!!最後までお読みいただきありがとうございました!
✔︎
- 投稿日:2020-11-08T18:55:17+09:00
【ActiveAdmin】デフォルトのcreateとupdateの処理をカスタマイズしたい
- 投稿日:2020-11-08T18:18:46+09:00
【ActiveAdmin】select_boxで表示するcollectionのscopeを指定したい
- 投稿日:2020-11-08T17:56:02+09:00
deviseで使えるヘルパーメソッド
メソッド 用途 before_action :authenticate_user! コントローラに設定し、ユーザーのみアクセスを許可する。 user_signed_in? ユーザーがログインしているかどうか判定を行う。ユーザーがログインしていればtrueを、ログアウト状態であればfalseを返します。 current_user 現在ログインしているユーザー取得する user_session ユーザーのセッション情報にアクセスする before_action :authenticate_user!
ログイン状態によって表示するページを切り替えるdeviseのメソッド。
class SampleController < ApplicationController before_action :authenticate_user!, only: [:show] def index end def show end endonlyオプションを使うと、showアクションはログイン済みユーザーのみアクセス可能とし、indexアクションはログインしていなくてもアクセスできるようになります。
user_signed_in?
<% if user_signed_in? %> <div class="user_nav grid-6"> <%= link_to "ログアウト", destroy_user_session_path, method: :delete %> <%= link_to "投稿する", new_tweet_path, class: "post" %> </div> <% else %> <div class="grid-6"> <%= link_to "ログイン", new_user_session_path, class: "post" %> <%= link_to "新規登録", new_user_registration_path, class: "post" %> </div> <% end %>サインインしていれば"ログアウト"と"投稿する"のページに、サインインしていなければ"ログイン"と"新規登録"を表示といった記述も可能。
deviseの設定がまだ不慣れなため、備忘録として
- 投稿日:2020-11-08T17:55:51+09:00
Rails, RSpecの導入手順
環境
rails 6.0.3.4
ruby 2.6.5流れ
・モデルの単体テストの目的、何をテストするのか
・RSpec導入準備(必要なgemのインストール)
このような流れで説明していきます。1 テストの目的
結論、コードの保守性を保つためです。当たり前ですがリリースしたアプリケーションにバグが生じるのは望ましくありません。バグが生じないことを手作業で確かめるの思わぬ見落としがあるかもしれない、ということで信頼できるテストを書いておけばコードに変更があった際でも「テストが通ったからok」と手作業で挙動を確かめる手間を省けるということです。
2 何をテストするか
① バリデーション
データベースに値を保存する際には「空の値を保存しない」、「文字数は何文字以内」、「数字のみ保存する」、「大文字は小文字に変換する」などのルールを定義します。規則性のない値が保存されてしまうことがデータベース設計上望ましくないからです。rails側でこのルールを定義する方法がモデルのバリデーションです。そのため、モデルのテストではバリデーションが正しく動作しているかをチェックします。② メソッド
モデルにはそのモデルの振る舞いを表すメソッドを自分で定義することができます。これも手動でテストするのは大変なのでテストに組み込みます。③ その他
モデルの役割として「アソシエーションの定義」があります。アソシエーション自体はrails側で定義されているものなので特段テストしなくていいそうですが、あるモデルのデータを削除した時に関連するモデルが削除されているかということもテストに含めます。3 導入編
まず、RSpecの設定からです。以下のコマンドを実行します。
% bin/rails generate rspec:installするとジェネレータが以下のようにrspecの設定ファイルと保存フォルダを生成してくれます。
Running via Spring preloader in process 28211 create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb.rspec ファイルを開き、以下のように変更してください。これによりテストの実行結果の表示をきれいに出力できます。
.rspec--require spec_helper --format documentation次にgemのインストールです。
Gemfilegroup :development, :test do gem 'factory_bot_rails' gem 'rspec-rails', '~> 4.0.0' # 以下省略 end group :development do gem 'spring-commands-rspec' # 以下省略 end最後の'spring-commands-rspec'はRSpecテストランナーのためのbinstubです。これによりアプリケーションの起動時間を早くするspringの恩恵を受けられます。Springを使いたくない場合は無視してください。最後にもう一つ設定する項目があります。rails gコマンドを実行した際にRSec用のスペックファイルも一緒に作ってもらうようRailsを設定しましょう。また、不要なファイルを生成しないようにします。
config/application.rbを開き、以下のように編集します。config/application.rbclass Application < Rails::Application # Initialize configuration defaults for originally generated Rails version. config.generators do |g| g.test_framework :rspec, view_specs: false, helper_specs: false, routing_specs: false end endこれでRSpecを導入するための設定が完了しました!
- 投稿日:2020-11-08T17:18:51+09:00
deviseでストロングパラメーターを使えるようにする
個人的に苦手なdeviseを使用する際に記述するメソッドなど。備忘録として
devise_parameter_sanitizerメソッド
private def configure_permitted_parameters # deviseのUserモデルにパラメーターを許可 devise_parameter_sanitizer.permit(:deviseの処理名, keys: [:許可するキー]) endストロングパラメーターをコントローラーに記述するが、deviseの処理を行うコントローラーはGem内に記述されているため、編集することができない。また、deviseでログイン機能を実装した場合は、paramsの他に、paramsとは異なる形のパラメーターも受け取っています。deviseのコントローラーにストロングパラメーターを反映する方法と、devise特有のパラメーターを取得する方法が、必要になります。
devise_parameter_sanitizerメソッド
deviseのUserモデルに関わる「ログイン」「新規登録」などのリクエストからパラメーターを取得できます。
このメソッドとpermitメソッドを組み合わせることにより、deviseに定義されているストロングパラメーターに対し、自分で新しく追加したカラムも指定して含めることができます。private def configure_permitted_parameters # メソッド名は慣習 # deviseのUserモデルにパラメーターを許可 devise_parameter_sanitizer.permit(:deviseの処理名, keys: [:許可するキー]) enddeviseのpermitは、第一引数にdeviseの処理名、第二引数にkeysというキーに対し、配列でキーを指定することで、許可するパラメーターを追加します。
deviseの処理名
メソッド 用途 :sign_in ログインの処理を行うとき :sign_up 新規登録の処理を行うとき :account_update アカウント情報更新の処理を行うとき 第一引数で指定した処理に対して、第二引数のkeysで指定された名前と同じキーを持つパラメーターの取得を許可します。ビューに記述した各フォーム部品のname属性値が、フォームから送信されるパラメーターのキーである。
deviseにストロングパラメーターを追加するコードは、deviseのコントローラーが編集できないため、⇩こんな感じでapplication_controllerに記述。class ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? private def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) end end"if: :devise_controller?"はもしdeviseに関するコントローラーの処理であれば、
そのときだけconfigure_permitted_parametersメソッドを実行しまうよ。という記述。
他のtweetsコントローラーなどでは処理は読み込まれても、実行まではされません。以上備忘録。
- 投稿日:2020-11-08T16:50:05+09:00
備忘録:Railsによるコメント機能実装
はじめに
Rubu on Railsでオリジナルアプリを作成しています。そのアプリにコメント機能を実装しました。そのため忘れないように書き記します。
手順
・commentモデル、テーブルの作成
・commentsコントローラーとルーティングの設定
・各コントローラーのアクション設定commentモデル、テーブルの作成
はじめに下記のようにターミナルでcommentモデルを作成します。
rails g model commentその後マイグレーションファイルに、textカラムを追加します(このtextカラムにはコメントが保存されます)。
2020*********_create_comments.rbclass CreateComments < ActiveRecord::Migration[6.0] def change create_table :comments do |t| t.text :text, null: false t.references :user, foreign_key: true t.references :desk, foreign_key: true t.timestamps end end enduserとdeskを外部キー設定します。このコメントは誰が、どのdesk(画像)にしたものか管理するためです。同様にcommentモデルにもバリデーションの他、アソシエーションを設定する必要があります。
desk.rbvalidates :text, presence: true belongs_to :user belongs_to :deskuserモデル、deskモデルにもアソシエーションを追加します。なお、今回のバリデーションはコメントが空だと保存できないような設定です。
commentsコントローラーとルーティングの設定
ターミナルでコメントのコントローラーを作成します。この時、コントローラー名は複数形にする(いつもどっちだっけ?って悩んでます?)。
rails g controller commentsお次は作成したcommentsコントローラーにcreateアクションを設定します。このとき、中身は空で一旦OKです。
comments_controller.rbdef create end最後にルーティングを。コメントはdesk(画像)と紐付けします。なのでネスト(入れ子構造)にします。
route.rbresources :desks do resources :comments, only: :create endくどくなりますが、どの画像に対するコメントなのかをパスから判断できるようにすることが重要です。
各コントローラーのアクション設定
今回はdesks(画像)コントローラーのshowアクションに対するレスポンスページでコメント保存、表示します(元々このページは、投稿された画像の詳細ページです)。なので、まずはdeskコントローラーのshowアクションに@commentのインスタンス変数を定義します。
desks.controller.rbdef show @desk = Desk.find(params[:id]) @comment = Comment.new @comments = @desk.comments endなお画像に付けられたコメントを一覧で表示するため。@commentsでコメントを取得しています。
今度はcommentsコントローラーへの設定です。こちらではストロングパラメータを設定します。requireにモデル名、permitに保存カラムを設定します。アソシエーション関係にあるものはmergeに記述します。なおdevise Gemがインストールされているので、current_userメソッドを使用しています。これによりコメントが誰が(user_id)、何に(desk_id)にされたものか管理できます。
comments.controller.rbdef create @comment = Comment.new(comment_params) if @comment.save redirect_to desk_path(@comment.desk) else @desk = @comment.desk @comments = @desk.comments render "desks/show" end end private def comment_params params.require(:comment).permit(:text).merge(user_id: current_user.id, desk_id: params[:desk_id]) end以上
- 投稿日:2020-11-08T16:35:12+09:00
Railsでいいね機能のAjax処理を実装してみた
はじめに
Railsでいいね機能を実装していたのですが、この機能を使うと下記のような問題が発生していました。
- ページ全体をレンダリングし直すため、無駄な処理を伴ってしまう。
- ページ遷移が伴ってしまうため、ページ下部でlikeやコメントをしてもページの一番上に遷移してしまい、ユーザービリティを下げている。
そのため、Ajax機能追加し、ページ遷移を伴わないようにしてユーザービリティをあげようと思いました。
最初考えた実装方法は、JavaScript・jQueryを使って、Ajax処理を発火していく方法がベストなのかなと思ったのですが、調べていくと、Railsには、
link_to
などのメソッドにremote: true
を引数で付けるとAjax処理を使えるようになるとのことでした。最初考えてた実装方法と違って、簡潔だったので、さっそくその方法で実装してみました。前提
- 投稿機能・いいね機能は既に実装済み。
- 実装しているいいね機能は、いいねボタンを押下すると、ページ遷移が伴うタイプのもの。
- 上記に対して、Ajax処理で、非同期通信で実装を行うものとします。この記事はその実装で行った手順の紹介です。
環境・バージョン
- Ruby 2.6.3
- Rails 6.0.2.1
- jQuery 3.5.1
実装
まず、
app/views/posts/show.html.erb
にいいねボタンapp/views/likes/_like.html.erb
をrender
させます。app/views/posts/show.html.erb# ・・・省略 <div class="post-like mr-auto"> <%= render partial: 'likes/like', locals: { post: @post, like: @like } %> </div> # ・・・省略いいね機能の
delete
とpost
の引数にremote: true
を追加してAjax機能を追加します。app/views/likes/_like.html.erb<% if current_user.already_liked?(post) %> <%= link_to 'Like', post_like_path(post, like), method: :delete, class:'post-like__cancel', remote: true %> <% else %> <%= link_to 'Like', post_likes_path(post), method: :post, class:'post-like__enable', remote: true %> <% end %>追加することで、それぞれのリンク押下後に下記の処理を実行することができようになります。
delete
メソッド→app/views/likes/destroy.js.erb
のファイルを実行post
メソッド→app/views/likes/create.js.erb
のファイルを実行次に、
js.erb
ファイルは、下記のコードのように@post
や@like
などのController
のメソッドで定義されたインスタンス変数を使うことができます。これは便利ですね。app/views/likes/destroy.js.erb$('.post-like').html('<%= j(render partial: 'likes/like', locals: { post: @post, like: @like }) %>');app/views/likes/create.js.erb$('.post-like').html('<%= j(render partial: 'likes/like', locals: { post: @post, like: @like }) %>');できたもの
できたものは下記です。
ページ遷移せずに動いていることを確認できました。さいごに
他にも応用して、フォロー機能やコメントも実装できました。
今後は、検索機能も作っていくので、Ajaxも使っていこうと思います。参考記事
Railsで remote: true と js.erbを使って簡単にAjax(非同期通信)を実装しよう!(いいね機能のデモ付)
- 投稿日:2020-11-08T16:13:48+09:00
評価の平均値を星で表示する
各店舗の評価の平均値を一覧表示ページで表示する方法を記述します!
イメージはこんな感じです。
こちらの実装は
Farstep【プログラミング講座】
さんの【Ruby on Rails】星レビュー機能を作ろう(PART1)How To Create a Star Rating with Ruby on Railsを参考にしています。使用環境
ruby 2.6.5
Ruby on Rails 6.0.3.3
一部、余白の指定でBootstrap4を使用しています。実装内容
モデルの実装
modelclass Laundry < ApplicationRecord def avg_score unless self.comments.empty? comments.average(:rate_id).round(1) else 0.0 end end def avg_score_percentage unless self.comments.empty? comments.average(:rate_id).round(1).to_f*100/5 else 0.0 end end endはじめにコメントが空かどうかで場合分けをして存在している場合は平均値の計算を行います。
averageメソッドを使用してカラム名の平均値を出します。
round(1)
とすることで小数点第2位に対して四捨五入をします。
avg_score_percentage
メソッドではパーセンテージの計算をしてビューに渡します。ビューの実装
コードは以下の通りです。
html<div class="average-score mb-3"> <div class="star-rating ml-2"> <div class="star-rating-front" style="width: <%= laundry.avg_score_percentage %>%">★★★★★</div> <div class="star-rating-back">★★★★★</div> </div> <div class="average-score-display"> (<%= laundry.avg_score %>点) </div> </div>css.average-score { display: flex; // ★と点数を横並びにする justify-content: center; } .star-rating { position: relative; width: 5em; height: 1em; font-size: 17px; } .star-rating-front { position: absolute; top: 0; left: 0; overflow: hidden; // 指定幅からはみ出した部分を隠す。 white-space: nowrap; // widthが足りない時に、折り返さないようにする。 color: #ffcc33; height: 25px; } .star-rating-back { color: #ccc; }2つの5つ星(★★★★★)を用意してその2つを重ね合わせることで実装を行います。
・.star-rating-front
・・・黄色い星の部分、上に重ねて"width: <%= laundry.avg_score_percentage %>%
とすることで幅を指定する。
・.star-rating-back
・・・灰色の星の部分、下におくことで黄色くならなかった部分を埋める。ポイントとして以下の2点だと思います。
1.overflow: hidden;
を指定。
overflow: hidden;
を指定することで指定幅からはみ出した部分を隠して後ろの灰色の星を表示させます。
ここを指定しないと、せっかく幅を指定しても幅からはみ出した部分が隠れていないため必ず星5つの評価になってしまいます。
overflow: hidden;
なし
2.
white-space: nowrap;
とすることでwidthが足りない時に、折り返さないようにしてくれます。
これを指定することで小数点以下の点数も星に反映されます。
white-space: nowrap;
あり
これで評価の平均値を星で表示することができました!
- 投稿日:2020-11-08T14:53:49+09:00
stripeを使いショッピングサイトを作ろう!(購入編)
プチ宣伝
https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。初めに
こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。
使う技術・作るもの
noteみたいな記事を購入できるもの
rails
ruby
stripestripeってそもそもなに?
決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。
全体像
Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。アカウントのタイプ
stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。
Standard
開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。
Custom
開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。
準備編
ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。登録してAPIキーをもらう
新規登録
APIキーを取得する画面gemをインストール
gemfilegem "stripe" gem 'dotenv-rails'bundleを忘れずにー。
そしたら.envというファイルをアプリフォルダの直下に作りPUBLISHABLE_KEY="pk_test_xxx" SECRET_KEY="sk_test_xxx" CLIENT_ID="ca_xxx"と記述してください。
CLIENT_IDはこちらから取得config/initializers/stripe.rbRails.configuration.stripe = { publishable_key: ENV["PUBLISHABLE_KEY"], secret_key: ENV["SECRET_KEY"], } Stripe.api_key = Rails.configuration.stripe[:secret_key]アプリケーションを作っていく
最初から作るの手間なのでscaffoldにします。最初にも書きましたが今回はnoteのような記事を購入できるサービスを作ります。本当は画像アップロード機能とかもあるほうがいいですが今回はあくまでstripeが中心なのでアプリの機能は最小限にします。
rails g scaffold post title:string content:text price:integercontent...内容、商品
price...値段rails db:migrateこれでscaffoldができたと思います。
購入機能
購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。
routes.rbpost "posts/:id/charge", to: "charge#create", as: "charge"views
↓erbバージョン
show.html.erb<%= form_tag charge_path(@post) do %> <script src="https://checkout.stripe.com/checkout.js" class="stripe-button" data-key="#{ENV["PUBLISHABLE_KEY"]}" data-amount="<%= @post.price %>" data-currency="jpy" data-description="クレジット決済" data-name=<%= "#{@post.title}を購入" %> data-email=<%= "#{current_user.email}" %> data-label="購入する" data-image="https://stripe.com/img/documentation/checkout/marketplace.png" data-locale="auto" data-allow-remember-me="false"> </script> <% end %>↓slimバージョン
show.html.slim= form_tag charge_path(@post) do script.stripe-button data-amount="#{@post.price}\ " data-currency="jpy" data-description="クレジット決済\ " data-key="#{ENV["PUBLISHABLE_KEY"]}" data-locale="auto" data-name="#{@post.title}を購入\ " data-email="#{current_user.email}" data-label="購入する\ " data-allow-remember-me="false" src="https://checkout.stripe.com/checkout.js"viewsをかいたら
charges_controller.rb
というコントローラーを作ってください。charges_controller.rbclass ChargesController < ApplicationController def create @post = Post.find(params[:id]) customer = Stripe::Customer.create({ email: params[:stripeEmail], source: params[:stripeToken], }) charge = Stripe::Charge.create({ customer: customer.id, amount: @post.price, description: "商品ID:#{@post.id} 商品名:#{@post.title}", currency: "jpy", }) rescue Stripe::CardError => e flash[:error] = e.message redirect_to new_charge_path end end簡単に説明すると4行目でcustomer(顧客)を作っています。chargeは支払い情報をつくっています。
customerはそのまま
amountは商品の値段を設定
descriptionで商品の情報を設定(内容はなんでもいい)
currencyで扱う通貨を設定(USDやJPYなど)これで購入ができると思います。
テストするときのカード番号は
4242 4242 4242 4242
です。
cvcはなんでもいいです。
カードの期限は今後であればいつでもいいです。
ほかにもいくつかあります。
テストカード一覧終わりに
今回はここまでにします。
次回はdeviseを導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。
- 投稿日:2020-11-08T14:53:49+09:00
第1回 railsでショッピングサイトを作ろう!(購入編)
プチ宣伝
https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。初めに
こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。
使う技術・作るもの
noteみたいな記事を購入できるもの
rails
ruby
stripestripeってそもそもなに?
決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。
全体像
Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。アカウントのタイプ
stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。
Standard
開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。
Custom
開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。
準備編
ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。登録してAPIキーをもらう
新規登録
APIキーを取得する画面gemをインストール
gemfilegem "stripe" gem 'dotenv-rails'bundleを忘れずにー。
そしたら.envというファイルをアプリフォルダの直下に作りPUBLISHABLE_KEY="pk_test_xxx" SECRET_KEY="sk_test_xxx" CLIENT_ID="ca_xxx"と記述してください。
CLIENT_IDはこちらから取得config/initializers/stripe.rbRails.configuration.stripe = { publishable_key: ENV["PUBLISHABLE_KEY"], secret_key: ENV["SECRET_KEY"], } Stripe.api_key = Rails.configuration.stripe[:secret_key]アプリケーションを作っていく
最初から作るの手間なのでscaffoldにします。最初にも書きましたが今回はnoteのような記事を購入できるサービスを作ります。本当は画像アップロード機能とかもあるほうがいいですが今回はあくまでstripeが中心なのでアプリの機能は最小限にします。
rails g scaffold post title:string content:text price:integercontent...内容、商品
price...値段rails db:migrateこれでscaffoldができたと思います。
購入機能
購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。
routes.rbpost "posts/:id/charge", to: "charge#create", as: "charge"views
↓erbバージョン
show.html.erb<%= form_tag charge_path(@post) do %> <script src="https://checkout.stripe.com/checkout.js" class="stripe-button" data-key="#{ENV["PUBLISHABLE_KEY"]}" data-amount="<%= @post.price %>" data-currency="jpy" data-description="クレジット決済" data-name=<%= "#{@post.title}を購入" %> data-email=<%= "#{current_user.email}" %> data-label="購入する" data-image="https://stripe.com/img/documentation/checkout/marketplace.png" data-locale="auto" data-allow-remember-me="false"> </script> <% end %>↓slimバージョン
show.html.slim= form_tag charge_path(@post) do script.stripe-button data-amount="#{@post.price}\ " data-currency="jpy" data-description="クレジット決済\ " data-key="#{ENV["PUBLISHABLE_KEY"]}" data-locale="auto" data-name="#{@post.title}を購入\ " data-email="#{current_user.email}" data-label="購入する\ " data-allow-remember-me="false" src="https://checkout.stripe.com/checkout.js"viewsをかいたら
charges_controller.rb
というコントローラーを作ってください。charges_controller.rbclass ChargesController < ApplicationController def create @post = Post.find(params[:id]) customer = Stripe::Customer.create({ email: params[:stripeEmail], source: params[:stripeToken], }) charge = Stripe::Charge.create({ customer: customer.id, amount: @post.price, description: "商品ID:#{@post.id} 商品名:#{@post.title}", currency: "jpy", }) rescue Stripe::CardError => e flash[:error] = e.message redirect_to new_charge_path end end簡単に説明すると4行目でcustomer(顧客)を作っています。chargeは支払い情報をつくっています。
customerはそのまま
amountは商品の値段を設定
descriptionで商品の情報を設定(内容はなんでもいい)
currencyで扱う通貨を設定(USDやJPYなど)これで購入ができると思います。
テストするときのカード番号は
4242 4242 4242 4242
です。
cvcはなんでもいいです。
カードの期限は今後であればいつでもいいです。
ほかにもいくつかあります。
テストカード一覧終わりに
今回はここまでにします。
次回はdeviseを導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。
- 投稿日:2020-11-08T14:53:49+09:00
railsでショッピングサイトを作ろう!(購入編)
プチ宣伝
https://www.code-sell.net/
コードを販売できるサービスを作りました!いらないコードがある方はぜひ使ってく見てください。ちなみにこの記事で説明するstripeを使用しています。初めに
こんにちは!今回はrailsとstripeを使い簡単?にECサイトを作っていきます。少し前自分もrailsとstripeで コードを販売するサイトを作ったのですがstripeの情報が少なすぎて非常に苦労しました。正確には情報自体はたくさんあるのですが実践的な情報が少なくただただ公式ドキュメントのようにコードを並べちょこっと説明するみたいなものばっかりでした。今回は実際にサイトを作っていきます。
使う技術・作るもの
noteみたいな記事を購入できるもの
rails
ruby
stripestripeってそもそもなに?
決済システムです。payjpとくらべ手数料が安かったり送金機能があったりします。
全体像
Customer(顧客): 購入者でありお金を支払う方。上の図で緑。
Platform(プラットフォーム): 今から作るECサイト。サービスを提供するところ。
Connected accounts: プラットフォームを利用してサービスを提供し、入金を受ける方・販売者(子アカウントとも呼ばれます)。上の図のピンク。アカウントのタイプ
stripeにはStandardとCustomという子アカウント(販売者)のタイプがあります。特徴・登録方法が違うので目を通りておきましょう。
Standard
開発コスト(手間):簡単
ユーザー視点:微妙
何かあった時の責任:販売者(子アカウント)
おすすめ度:中これは実装するのが非常に簡単なタイプです。あとで書きますが登録フォームやシステムはほとんどstripe側がやってくれます。僕たちはその登録フォームのリンクを張り付けちょこっとコピペでコントローラーに書くだけです。なにかあった時(マイナス残高など)も僕たちではなく利用しているユーザー側の責任となります。ただこの方法だとユーザーにstripeを使っていることがしっかり伝わってしまいます。登録フォームは完全にstripeが作っているしStripe の管理画面(ダッシュボード)へ、販売者がアクセスできるようになります。デザインも変更できません。
Custom
開発コスト(手間):難
ユーザー視点:いい
何かあった時の責任:プラットフォーム(開発者)
おすすめ度:高これは実装するのが難しいタイプです。登録フォームもstripeに送信するシステムもダッシュボードも自分で作ります。何かあった時も自分の責任です。ただ登録から管理画面まですべて自分のサイトで完結します。デザインももちろん自由です。
準備編
ながなが説明してきましたがとりあえず細かいことは作って覚えましょう。
今回作るのは単発の購入のサイトです。定期支払などもできますがそれは別の記事でやっていたので...。登録してAPIキーをもらう
新規登録
APIキーを取得する画面gemをインストール
gemfilegem "stripe" gem 'dotenv-rails'bundleを忘れずにー。
そしたら.envというファイルをアプリフォルダの直下に作りPUBLISHABLE_KEY="pk_test_xxx" SECRET_KEY="sk_test_xxx" CLIENT_ID="ca_xxx"と記述してください。
CLIENT_IDはこちらから取得config/initializers/stripe.rbRails.configuration.stripe = { publishable_key: ENV["PUBLISHABLE_KEY"], secret_key: ENV["SECRET_KEY"], } Stripe.api_key = Rails.configuration.stripe[:secret_key]アプリケーションを作っていく
最初から作るの手間なのでscaffoldにします。最初にも書きましたが今回はnoteのような記事を購入できるサービスを作ります。本当は画像アップロード機能とかもあるほうがいいですが今回はあくまでstripeが中心なのでアプリの機能は最小限にします。
rails g scaffold post title:string content:text price:integercontent...内容、商品
price...値段rails db:migrateこれでscaffoldができたと思います。
購入機能
購入機能は意外に簡単です。とりあえず最初に作ってしまいましょう。
routes.rbpost "posts/:id/charge", to: "charge#create", as: "charge"views
↓erbバージョン
show.html.erb<%= form_tag charge_path(@post) do %> <script src="https://checkout.stripe.com/checkout.js" class="stripe-button" data-key="#{ENV["PUBLISHABLE_KEY"]}" data-amount="<%= @post.price %>" data-currency="jpy" data-description="クレジット決済" data-name=<%= "#{@post.title}を購入" %> data-email=<%= "#{current_user.email}" %> data-label="購入する" data-image="https://stripe.com/img/documentation/checkout/marketplace.png" data-locale="auto" data-allow-remember-me="false"> </script> <% end %>↓slimバージョン
show.html.slim= form_tag charge_path(@post) do script.stripe-button data-amount="#{@post.price}\ " data-currency="jpy" data-description="クレジット決済\ " data-key="#{ENV["PUBLISHABLE_KEY"]}" data-locale="auto" data-name="#{@post.title}を購入\ " data-email="#{current_user.email}" data-label="購入する\ " data-allow-remember-me="false" src="https://checkout.stripe.com/checkout.js"viewsをかいたら
charges_controller.rb
というコントローラーを作ってください。charges_controller.rbclass ChargesController < ApplicationController def create @post = Post.find(params[:id]) customer = Stripe::Customer.create({ email: params[:stripeEmail], source: params[:stripeToken], }) charge = Stripe::Charge.create({ customer: customer.id, amount: @post.price, description: "商品ID:#{@post.id} 商品名:#{@post.title}", currency: "jpy", }) rescue Stripe::CardError => e flash[:error] = e.message redirect_to new_charge_path end end簡単に説明すると4行目でcustomer(顧客)を作っています。chargeは支払い情報をつくっています。
customerはそのまま
amountは商品の値段を設定
descriptionで商品の情報を設定(内容はなんでもいい)
currencyで扱う通貨を設定(USDやJPYなど)これで購入ができると思います。
テストするときのカード番号は
4242 4242 4242 4242
です。
cvcはなんでもいいです。
カードの期限は今後であればいつでもいいです。
ほかにもいくつかあります。
テストカード一覧終わりに
今回はここまでにします。
次回はdeviseを導入してマイページを作りスタンダードアカウントやカスタムアカウントの作り方を説明していこうと思います。
大変なので結構先になるかもしれません。
- 投稿日:2020-11-08T14:14:54+09:00
Railsで RoutingError がFATALレベルでログに出てくることへ対処(したかった)
概要
RoutingError
がFATALレベルでログ出力される点が困っている。FATALは運用時に「即時対処が必要なもの」と決めていて夜間でもアラートが鳴るようにしているのだが、RoutingError
はクライアントが不正なリクエストをすれば簡単に起こせるので、そもそもログに出されなくていい。(404はwebサーバーのアクセスログでもわかる)何か設定を間違えているだけかと思っていたが、Railsガイド通りに新規作成したばかりのrailsアプリでも同じことが起きたため、railsが何をしているのかソースコードを色々と調べることになった。結局適切なオプションは見つけられず、railsの MiddlewareStack に手を加えるという形になってしまった。(この辺の仕組みに目を通せたのは収穫。)
環境: Rails 6.0.0 / Ruby 2.5.3 / Ubuntu 20.04
ログのサンプル
リクエストを受けた際のrailsのログを以下に示す。
ルーティングありI, [2020-11-07T23:01:42.988933 #12481] INFO -- : Started GET "/" for 127.0.0.1 at 2020-11-07 23:01:42 +0900 I, [2020-11-07T23:01:42.992722 #12481] INFO -- : Processing by MiniController#index as HTML I, [2020-11-07T23:01:43.002614 #12481] INFO -- : Rendering text template I, [2020-11-07T23:01:43.005314 #12481] INFO -- : Rendered text template (Duration: 0.0ms | Allocations: 4) I, [2020-11-07T23:01:43.005728 #12481] INFO -- : Completed 200 OK in 9ms (Views: 8.9ms | Allocations: 1395)ルーティングなしI, [2020-11-07T23:03:08.801059 #12481] INFO -- : Started GET "/illegal" for 127.0.0.1 at 2020-11-07 23:03:08 +0900 F, [2020-11-07T23:03:08.801822 #12481] FATAL -- : ActionController::RoutingError (No route matches [GET] "/illegal"): actionpack (6.0.0) lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call' actionpack (6.0.0) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call' (省略) /opt/rbenv/versions/2.5.3/lib/ruby/2.5.0/webrick/server.rb:307:in `block in start_thread'ちなみにrails5ではFATALログが4つに分割されていた(空行・例外・空行・バックトレース)。rails6ではこの通りひとまとめになったので、監視側でログを無視するのは楽になった。
ログが出るまでの仕組み
そもそも
RoutingError
はどう発生し、どこでログに記録しているのだろうと疑問に思った。ミドルウェアの入れ子
ログのスタックトレースを頼りにgemをgrepしていたところ、
Rails::Application::DefaultMiddlewareStack
の中で一連のアプリを登録していた。 ※railsの設定によって個数は変わるミドルウェア::ActionDispatch::HostAuthorization ::ActionDispatch::SSL ::Rack::Sendfile ::ActionDispatch::Static ::Rack::Cache ::Rack::Lock ::ActionDispatch::Executor ::Rack::Runtime ::Rack::MethodOverride ::ActionDispatch::RequestId ::ActionDispatch::RemoteIp ::Rails::Rack::Logger ::ActionDispatch::ShowExceptions ::ActionDispatch::DebugExceptions ::ActionDispatch::ActionableExceptions ::ActionDispatch::Reloader ::ActionDispatch::Callbacks ::ActionDispatch::Cookies config.session_store ::ActionDispatch::Flash ::ActionDispatch::ContentSecurityPolicy::Middleware ::Rack::Head ::Rack::ConditionalGet ::Rack::ETag ::Rack::TempfileReaper
- https://github.com/rails/rails/tree/v6.0.0/actionpack/lib/action_dispatch/middleware
- https://github.com/rack/rack/tree/master/lib/rack
これらのクラスには
#call
メソッドがあり、上側のクラスはひとつ下のクラスにリクエストを渡してレスポンスを受け取るという入れ子構造になっている。もちろんただ#call
を呼ぶだけでなく、前後にリクエスト・レスポンスを編集したり、例外の送出や捕捉なども必要に応じてしていて、それぞれが自分の役割を果たしている。※入れ子の順序は MiddlewareStack が管理するので、各クラスは自分が誰を呼び出すか知っている必要は無い。
DebugExceptions
肝心のこのアプリが何をしているのか、中身を読んだ。
ActionDispatch::DebugExceptions#calldef call(env) request = ActionDispatch::Request.new env _, headers, body = response = @app.call(env) if headers["X-Cascade"] == "pass" body.close if body.respond_to?(:close) raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end response rescue Exception => exception invoke_interceptors(request, exception) raise exception unless request.show_exceptions? render_exception(request, exception) end
- すぐ次のアプリにリクエストを渡してレスポンスを受け取る
- レスポンスヘッダーに
X-Cascade: pass
が設定されていたらRoutingError
を送出する- 処理中に例外が起きていれば捕捉し、必要に応じて処理をする
- interceptorが登録されていれば実行する(rails6で追加)
- 例外をログに記録する
- 例外の詳細についてのレスポンスを作成する ※development環境でよく出てくるやつ
- レスポンスを作成しないなら例外を再送出する(前段のアプリに任せる)
この「例外をログに記録する」処理を回避でき、他の処理にも悪影響が出ない条件分岐があればいい。(なお、ログ出力は
fatal
でハードコードされていて、レベルを下げることはできない)対処(の試行錯誤)
案1.
DebugExceptions
を外す「クラス名がdebugだし、無くても動くだろう」と思って試した。railsの設定時に、登録したミドルウェアを抜くことができる。
config/environments/<env>.rb などに追加config.middleware.delete ::ActionDispatch::DebugExceptionsするとFATALログは消えたものの、レスポンスボディが
Not Found
だけになってしまい、404ページが表示されなくなった。クライアント側$ curl -i http://localhost:3000/illegal HTTP/1.1 404 Not Found X-Cascade: pass Cache-Control: no-cache X-Request-Id: fefa1568-57dd-45be-bc31-f91f17a5c916 X-Runtime: 0.005418 Server: WEBrick/1.4.2 (Ruby/2.5.3/2018-10-18) Date: Sat, 07 Nov 2020 14:32:30 GMT Content-Length: 9 Connection: Keep-Alive Not Found
DebugExceptions
を外すということはRoutingError
が出なくなるということであり、前段のアプリ群が元と同じ例外処理をしなくなってしまう。名前に反してかなり重要なミドルウェア?というわけで却下。
案2.
show_exceptions
オプション「
DebugExceptions
の中でログ出力せずに例外を投げてくれればいいだろう」ということで、コード中のrequest.show_exceptions?
が偽になるようにする。これはrailsの設定項目に存在する。config/environments/<env>.rb などに追加config.action_dispatch.show_exceptions = falseやってみたら500エラーになってしまった。ログもrailsは問題ないが、webサーバー(webrick, pumaなど)のほうでエラーを吐き出している。
ログI, [2020-11-07T23:34:09.728873 #13864] INFO -- : Started GET "/illegal" for 127.0.0.1 at 2020-11-07 23:34:09 +0900 [2020-11-07 23:34:09] ERROR ActionController::RoutingError: No route matches [GET] "/illegal" /.../actionpack-6.0.0/lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call' /.../actionpack-6.0.0/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call' (省略) /opt/rbenv/versions/2.5.3/lib/ruby/2.5.0/webrick/server.rb:307:in `block in start_thread'というのも、これは名前通り前段の
ShowExceptions
を無効化するもので、/public/404.html
などエラーレスポンスが用意されなくなる。さらに、元々はここでRoutingError
を捕捉していたのに再送出してしまうようになり、前段のアプリ群の処理が変わってしまう。
ActionDispatch::ShowExceptions
rescues any exception returned by the application and renders nice exception pages if the request is local or ifconfig.consider_all_requests_local
is set totrue
. Ifconfig.action_dispatch.show_exceptions
is set tofalse
, exceptions will be raised regardless.https://guides.rubyonrails.org/configuring.html#configuring-middleware より引用、一部強調
というわけで却下。
案3.
DebugExceptions
にモンキーパッチ確実に成功する方法を試しておく。問題のFATALログの出力箇所はひとつのメソッドに纏められているので、それを何もしないように上書きしてしまえばいい。
config/environments/<env>.rb などに追加class ::ActionDispatch::DebugExceptions def log_error(*); end end成功例は省略する。問題としては、
RoutingError
以外の例外もログ出力されなくなってしまう- ライブラリのコード変更に弱い(しかも対象がprivateメソッド)
- モンキーパッチということで行儀が悪い
案4.
DebugExceptions
の代替品を自作案3よりは正攻法であり、柔軟性があり、その分だけ面倒でもある。
my_debug_exceptions.rb# RoutingError の送出部分だけをコピーした class MyDebugExceptions # 第1引数 app は必須、追加の引数は自由(今回は無視) def initialize(app, *) @app = app end # 引数は env def call(env) request = ActionDispatch::Request.new env _, headers, body = response = @app.call(env) if headers["X-Cascade"] == "pass" body.close if body.respond_to?(:close) raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end response rescue ActionController::RoutingError => routing_error # RoutingError 専用の処理、今回はひとまず無し raise routing_error rescue Exception => exception # RoutingError 以外の処理、今回はひとまず無し raise exception end end作成したアプリを元のものと差し替える。これはrailsの設定で可能。
config/environments/<env>.rb などに追加# app 以外の引数はここで指定できる config.middleware.swap ActionDispatch::DebugExceptions, MyDebugExceptions, "arg1", "arg2"結局…
「本当にこんな方法しか無いの?」という気持ちになった。リリースするものに組み込むのは躊躇する。一応もう少し調べ続けてみようと思う。
先に述べた通り、rails6では問題のFATALログがひとまとめになったので、railsには標準的な動作をさせておいて監視側で無視したほうがわかりやすい気もする。
付録
実験に使用したrailsアプリ
実験ではほとんどの設定を削ぎ落とすため、過去に作成した20行程度のものをベースにした。以下にコード全文を載せる。
クリックして展開
Gemfilesource "https://rubygems.org" gem "railties", "6.0.0"bin/railsAPP_PATH = File.expand_path('../config/application', __dir__) require 'rails/commands'config.rurequire_relative 'config/environment' run Rails.applicationconfig/environment.rbrequire_relative 'application' Rails.application.initialize!config/application.rbrequire 'action_controller/railtie' class MiniApp < Rails::Application config.logger = ::Logger.new(STDOUT) # ログをターミナル上で見れるように endconfig/routes.rbRails.application.routes.draw do root to: 'mini#index' endapp/controllers/mini_controller.rbclass MiniController < ActionController::Base def index render plain: "Hello, world!\n" end endエラーページの静的ファイル
public/404.html
はお好みで。(コードはここまで)
development環境だと設定が追加されることがあるので、production環境で実行する。
SECRET_KEY_BASE
を指定する必要があるが、適当でいい。実行方法$ bundle install --path vendor/bundle # 検索やコード改変しやすいように $ bundle exec rails routes # ルーティング確認 $ RAILS_ENV=production SECRET_KEY_BASE=_ bundle exec rails serverあとは
curl
やブラウザでhttp://localhost:3000/
にアクセスすれば試せる。
- 投稿日:2020-11-08T14:10:27+09:00
【Rails】DM(チャット)機能 + Ajax 実装!
まえがき
以前スクールの課題でDM機能を実装した際に、色々なサイトを参考にして作成したのですが、かなり複雑で無駄な記述が多くなってしまいました。無駄な部分を省きながらoutputしていきたいと思い投稿しました。おかしな点はご指摘お願いします、、!
実装環境
ruby '2.6.3'
gem 'rails', '~> 5.2.4', '>= 5.2.4.3'
gem 'jquery-rails'
gem 'devise'(DM機能実装過程でヘルパーメソッドのcurrent_userを使用しています)
gem 'bootstrap-sass', '~> 3.3.6'(無くても可)userモデルは事前に作成して、他ユーザーのshowページを閲覧できるようにしてある前提で進めていきます。
実装していく内容
usersのshowページにリンクを作成し、DMページ(chatsのshow)へ遷移して実際にトークが行えるように実装していきます。
model名やページ内に表示したい文字列等は適当に置き換えてください。実装後のイメージ
※スタイルはかなり適当なのでご自身で整えてください
実装していきましょう!!
①モデルとカラムの作成
rails g model room
rails g model user_room user_id:integer room_id:integer
rails g model chat user_id:integer room_id:integer message:string
rails db:migrate
②コントローラーの作成
rails g controller chats show
解説(クリックしてください)
showアクションも同時に作成してしまいます。③モデル同士のアソシエーション
user.rbhas_many :user_rooms has_many :chats has_many :rooms, through: :user_roomsroom.rbhas_many :chats has_many :user_roomsuser_room.rbbelongs_to :user belongs_to :roomchat.rbbelongs_to :user belongs_to :roomthroughについて上手く説明できる自信がないので下記のQiita記事を参考にしてみてください☟
【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】?④ルーティングの作成
routes.rbget 'chat/:id' => 'chats#show', as: 'chat' resources :chats, only: [:create]⑤users_controllerへ記述
users_controller.rbdef show @user = User.find(params[:id]) end #余計なコードは省いて記述しています。⑥usersのshowにリンク作成
users/show<% if current_user != @user %> <%= link_to 'チャットする', chat_path(@user.id) %> <% end %>
解説(クリックしてください)
current_userはDeviseのヘルパーメソッドです、ここではログインユーザーと@userが一致しない場合のみリンクを表示する記述しています。
pathの内容は一応rails routes
で確認してから記述してください。
現時点でリンクをクリックすると作成してあるshowページに遷移するはずです。⑦chatsコントローラーに記述
chats_controller.rbdef show @user = User.find(params[:id]) #ログインしているユーザーのidが入ったroom_idのみを配列で取得(該当するroom_idが複数でも全て取得) rooms = current_user.user_rooms.pluck(:room_id) #user_idが@user 且つ room_idが上で取得したrooms配列の中にある数値のもののみ取得(1個または0個のはずです) user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms) if user_rooms.nil? #上記で取得できなかった場合の処理 #新しいroomを作成して保存 @room = Room.new @room.save #@room.idと@user.idをUserRoomのカラムに配列で保存 UserRoom.create(user_id: @user.id, room_id: @room.id) #@room.idとcurrent_user.idをUserRoomのカラムに配列で保存 UserRoom.create(user_id: current_user.id, room_id: @room.id) else #取得している場合は、user_roomsに紐づいているroomテーブルのレコードを@roomに代入 @room = user_rooms.room end #if文の中で定義した@roomに紐づくchatsテーブルのレコードを代入 @chats = @room.chats #@room.idを代入したChat.newを用意しておく(message送信時のform用)←筆者の表現が合っているか分かりません、、 @chat = Chat.new(room_id: @room.id) end def create @chat = current_user.chats.new(chat_params) @chat.save end private def chat_params params.require(:chat).permit(:message, :room_id) end⑧Chatsのshowに記述
chats/show.html.erb<div class="container"> <div class="row"> <div class="col-xs-6"> <h2 id="room" data-room="<%= @room.id %>" data-user="<%= current_user.id %>"><%= @user.name %> さんとのチャット</h2> <table class="message table"> <thead> <tr> <th style="text-align: left; font-size: 20px;"><%= current_user.name %></th> <th style="text-align: right; font-size: 20px;"><%= @user.name %></th> </tr> </thead> <% @chats.each do |chat| %> <% if chat.user_id == current_user.id %> <tbody> <tr> <th> <p style="text-align: left;"><%= chat.message %></p> </th> <th></th> </tr> </tbody> <% else %> <tbody> <tr> <th></th> <th> <p style="text-align: right;"><%= chat.message %></p> </th> </tr> </tbody> <% end %> <% end %> </table> <%= form_with model: @chat do |f| %> <%= f.text_field :message %> <%= f.hidden_field :room_id %> <% end %> </div> </div> </div>※bootstrapで少しだけそれっぽい見た目にしているのでinstallしてない方は、必要のないdivタグやclass指定は省いて必要な部分のみの記述にしてください。
解説(クリックしてください)
form_withの記述でlocal: trueを記述していない点に注意してください。js形式のリクエストを送信する必要があるので何も記述しなくても問題ありません、<%= form_with model: @chat, remote: true do |f| %>と記述するのと同義になります。
destroyアクションなど追加して非同期にする際は、そちらにremote: trueと記述する必要があります。⑨jsファイル作成(あと少しです!)
app/views/chats直下ににcreate.js.erbを作成して、以下の内容を記述します。
chats/create.js.erb$('.message').append("<p style='text-align: left;'><%= @chat.message %></p>"); $('input[type=text]').val("")
解説(クリックしてください)
.meesageは指定してあるclass名です。
.append("<%= @chat.message %>
");で部分的な更新をしています。2行目の記述で更新時に入力フォームの中身を空にする処理の記述をしています。
・筆者は.appendについて上手く説明できないので以下の記事を参考にしてみてください☟
jQueryのappendメソッド以上で実装が出来たはずです。
おかしな記述や表現がある場合は指摘をおねがいします。
良ければこちらの記事も参考にしてみてください☟
Gemなし 複数検索機能の実装 に関する筆者の記事
- 投稿日:2020-11-08T14:05:27+09:00
【Rails(5.0)】 japanMapリンク使用時 js.erbファイル内のパラメータ書き方
初投稿になります。プログラミング始めて4ヶ月の初心者です。よろしくお願いします。
ポートフォリオ作成の際にjapanMapを使用して8地方ごとにイベントを検索できるようにしました。
その際、地方ごとのリンクに任意のパラメータを渡すのに少し苦労したので、自分で確認用として投稿しています。今回は application.jsファイルにjapanMapのプログラムを記述しました。
以下が自分の書いたコードになります。
application.js//検索ページ日本地図 $(function(){ //8地方でリンク作成 var areaLinks = { 1:"/user/index?sort=hokkaido", 2:"/user/index?sort=tohoku", 3:"/user/index?sort=kanto", 4:"/user/index?sort=chubu", 5:"/user/index?sort=kinki", 6:"/user/index?sort=chugoku_shikoku", 7:"/user/index?sort=kyusyu_okinawa", }; //8地方エリア指定 var areas = [ {code : 1, name: "北海道", color: "#ab86c4", hoverColor: "#dfcceb", prefectures: [1]}, {code : 2, name: "東北", color: "#6d93d1", hoverColor: "#91b0e3", prefectures: [2,3,4,5,6,7]}, {code : 3, name: "関東", color: "#f5a164", hoverColor: "#f5c09a", prefectures: [8,9,10,11,12,13,14]}, {code : 4, name: "中部", color: "#77e077", hoverColor: "#adedad", prefectures: [15,16,17,18,19,20,21,22,23]}, {code : 5, name: "近畿", color: "#ffe966", hoverColor: "#fff2a3", prefectures: [24,25,26,27,28,29,30]}, {code : 6, name: "中国・四国", color: "#e68ccc", hoverColor: "#f0b9e0", prefectures: [31,32,33,34,35,36,37,38,39]}, {code : 7, name: "九州・沖縄", color: "#de6474", hoverColor: "#f29da9", prefectures: [40,41,42,43,44,45,46,47]}, ]; //地図表示設定 $("#map-container").japanMap({ width: 600, areas : areas, selection : "area", borderLineWidth: 0.25, drawsBoxLine : false, movesIslands : true, showsAreaName : true, font : "MS Mincho", fontSize : 13, fontColor :"#777", fontShadowColor : "white", onSelect : function(data){ location.href = areaLinks[data.area.code]; }; });上記のこの部分がリンクになります。
var areaLinks = { 1:"/user/index?sort=hokkaido", 2:"/user/index?sort=tohoku", 3:"/user/index?sort=kanto", 4:"/user/index?sort=chubu", 5:"/user/index?sort=kinki", 6:"/user/index?sort=chugoku_shikoku", 7:"/user/index?sort=kyusyu_okinawa", };html.erbファイルでは下記のように記述するところを
○○.html.erb<%= link_to '◯◯', ○◯_path(:sort => 'hokkaido') %>js.erbファイルでのlink_to の書き方がわからなかった為、
○○.js.erb1:"/user/index?sort=hokkaido"のように記述しました。こうすることで任意のparams[:sort]のパラメータを渡すことができました。
[?sort=hokkaido] の部分がパラメーターになります。○○_controller.rbdef index if params[:sort] == 'hokkaido' @events = Event.where(prefecture_code: "北海道") @events = @events.page(params[:page]).per(6).order("id DESC")上記がコントローラーの一部になります。
[if params[:sort] == 'hokkaido']の記述でパラメーターを区別し表示するイベントを変更しています。初めての投稿で見にくいところ間違いなどあるかもしれませんが最後まで見ていただきありがとうございました。
- 投稿日:2020-11-08T13:34:35+09:00
Rails Tutorial 拡張機能のメッセージ機能を作ってみた(その1):モデルを作成
Rails Tutorialの第14章にある、拡張機能を作る件の続きです。
前回までで返信機能ができました。機能追加の2つ目、メッセージ機能を作ります。
機能の要件を調査
チュートリアルには
Twitterでは、ダイレクトメッセージを行える機能がサポートされています。この機能をサンプルアプリケーションに実装してみましょう
とあるので、Twitterの機能を確認します。
(ヒント: Messageモデルと、新規マイクロポストにマッチする正規表現が必要になるでしょう)。
MessageモデルとはRailsの機能なのか?ネットで調べることにします。
まずはTwitterの機能確認です。
DMを送信できる条件で、相手が自分をフォローしている必要があります。
この機能は後で作ることにします。既読を表示する機能もありますが、この機能は諦めます。
DMを受け取った際にプッシュ通知/SMS通知(ショートメール通知)/メール通知のいずれかが届くように設定できます。
この機能も諦めます。また、特定のアカウントからのDMをミュートすることで、通知を受け取らないようにすることも可能です。
この機能も諦めます。特定のアカウントからのDMを拒否する機能もあります。相手をブロックできます。この機能も諦めます。
受け取ったDMを削除することはできます。この機能は作ることにします。
送信者が送信後のDMを削除することはできません。DMとpostは違う画面が作られています。
文字数の最大数は違うのか、調べたところ2015年7月までは140文字それ以降は1万文字でした。
今回は140文字にします。送信先に複数を使えるか調べたところ、グループを作成して複数のアカウントと会話する機能がありました。2015年1月に出来たとのことなので、この機能は諦めます。
Twitterの機能が分かり、今回作る機能もイメージできました。
MessageモデルとはRailsの機能なのか調べましたが、それらしい記事は見つかりませんでした。チャット機能を自分で作る記事や、リアルタイムチャットを作る記事は見つかりましたが関係なさそうです。
機能のまとめ
1.メッセージを送ることができる。
2.送信先は複数ではなく1人だけ。
3.文字数は140文字まで。
4.送信できるか制限はせず、誰にでも送信できる。この制限機能は後で作ることとする。
5.既読の表示機能は作らない。
6.DMを受信時の通知機能は作らない。
7.受信したDMを削除することができる。
8.送信者は送信したDMを削除することができないモデルの設計
モデルの仕様を作ります。
tutorialのMicropostを作るところを読み直します。13章では最初に13.1でモデルを作っています。
同様にDMのモデルを作ることにします。
列名 属性 id integer content text sender_id integer receiver_id integer created_at datetime updated_at datetime 図 DMモデル
ここでreceiverもUserモデルと関係があります。この関係はfollowのmodelを作ったときと同じではと考え、tutorialを読み直します。
user削除時の仕様を検討
relationでは、userを削除するとrelationも削除されます。DMでは削除しない仕様に決めました。この点は違います。
relationでは、unfollowするとrelationも削除されます。DMではDMを受信者が削除しても、そのDMが消えるわけではなく、送信者の画面からは消えません。受信者の画面に表示されなくなるだけです。これはどういうことか考えてみます。DMの削除とは、なかったことにするのではない、送った事実は残るわけです。メールと同じで、受信者が受信済みのメールを削除したときに、送信者の送信済みのメールは削除されないです。Twitterで試してみましたが、確かでした。
メールのように、DMが入ったバケツを考えます。送信者と受信者が別々のバケツを持つモデルです。DMを送ると、送信者のバケツに1通、受信者のバケツに1通を入れます。バケツ2個に全く同じメッセージを入れるというのが重複感があると感じました。
なのでバケツは1個にして、受信者が削除したことが分かるように、削除フラグを追加することにします。
trueかfalseを入れる属性をネットで調べ、booleanがあることと、必ずデフォルト値を入れるべきと分かりました。理由はrubyではnilとfalseが同じ扱いになるためです。
https://qiita.com/jnchito/items/a342b64cd998e5c4ef3d変更後のモデルです。
列名 属性 id integer content text sender_id integer receiver_id integer deleted boolean created_at datetime updated_at datetime 図 DMモデル
DMモデルの開発
トピックブランチを作ります。
ubuntu:~/environment/sample_app (master) $ git checkout -b create-dmdmモデルを生成します。
ubuntu:~/environment/sample_app (create-dm) $ rails generate model dm content:text user:references create db/migrate/20201102003220_create_dms.rb create app/models/dm.rb create test/models/dm_test.rb create test/fixtures/dms.ymlrails generate mode dm content:text user:referencesマイグレーションを変更します。
インデックスは、senderを指定して時系列で取り出す場合と、receiverを指定して時系列で取り出す場合の2通りが考えられたので作りました。
deletedフラグのnullの扱いは先ほどのネットの記事を参考にしました。db/migrate/20201102003220_create_dms.rbclass CreateDms < ActiveRecord::Migration[5.1] def change create_table :dms do |t| t.text :content t.integer :sender_id t.integer :reciever_id t.boolean :deleted, default: false, null: false t.timestamps end add_index :dms, [:sender_id, :created_at] add_index :dms, [:receiver_id, :created_at] end endデータベースを更新します。
ubuntu:~/environment/sample_app (create-dm) $ rails db:migrateUserとDMの関連づけをします。
tutorialの14.1.2 「User/Relationshipの関連付け」を読みます。app/models/user.rbclass User < ApplicationRecord has_many :microposts, dependent: :destroy has_many :active_relationships,class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed has_many :followers, through: :passive_relationships, source: :follower has_many :sent_dms,class_name: "Dm", foreign_key: "sender_id" has_many :received_dms,class_name: "Dm", foreign_key: "receiver_id"app/models/dm.rbclass Dm < ApplicationRecord belongs_to :sender, class_name: "User" belongs_to :receiver, class_name: "User" end関連を整理するために、図を書きます。
id name 1 Michael 2 Archer userモデル
has_many
sender_id receiver_id content 1 2 ... 1 3 ... dmモデル
has_many
id name 2 Archer 3 ... userモデル
図 UserとDMの関連使えるようになるメソッド
使えるようになるメソッドは以下のとおりです。
メソッド 用途 user.sent_dms Userが送ったDMの集合を返す sent_dms.sender senderを返す sent_dms.receiver receiverを返す user.sent_dms.create(receiver_id: other_user.id) userと紐づけてDMを作 user.sent_dms.create!(receiver_id: other_user.id) userと紐づけてDMを作る(失敗時にエラーを出力) user.sent_dms.build(receiver_id: other_user.id) userと紐づた新しいDMオブジェクトを返す user.sent_dms.find_by(id:1) userと紐づいていて、idが1のDMを返す コンソールで試してみます。dm1を作ります。
>> user1 = User.first >> user2 = User.second >> dm1 = user1.sent_dms.create(receiver_id: user2.id, content: "hoge dm1")sender,receiverのUserオブジェクトが返されました。
>> dm1.sender => #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-10-26 01:37:04", updated_at: "2020-10-26 01:37:04", password_digest: "$2a$10$2TZtcwmSTCfl9Bigz2nYGO8U1YA8ksfNXUr2O/fSGOY...", remember_digest: nil, admin: true, activation_digest: "$2a$10$EaQUKa6hfGEHosjnICR4VuYMxfOxunTOsPGQYUimNLn...", activated: true, activated_at: "2020-10-26 01:37:03", reset_digest: nil, reset_sent_at: nil, unique_name: "Example"> >> dm1.receiver => #<User id: 2, name: "Van Zemlak", email: "example-1@railstutorial.org", created_at: "2020-10-26 01:37:04", updated_at: "2020-10-26 01:37:04", password_digest: "$2a$10$H22BJeNVA3hYdEw/a5RArekRy73q/0AtvidwRiVpoUK...", remember_digest: nil, admin: false, activation_digest: "$2a$10$xm7AJE4Q3fzq3gi5tmVnyeld8wahxMHN/dE2Sn2jSUW...", activated: true, activated_at: "2020-10-26 01:37:04", reset_digest: nil, reset_sent_at: nil, unique_name: "Craig1">userのDMのリストを検索、dmをidで検索します。
>> user1.sent_dms Dm Load (0.2ms) SELECT "dms".* FROM "dms" WHERE "dms"."sender_id" = ? LIMIT ? [["sender_id", 1], ["LIMIT", 11]] => #<ActiveRecord::Associations::CollectionProxy [#<Dm id: 2, content: "hogehoge", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-02 02:27:35", updated_at: "2020-11-02 02:27:35">, #<Dm id: 3, content: "hoge dm1", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-03 00:21:40", updated_at: "2020-11-03 00:21:40">]> >> user1.sent_dms.find_by(receiver_id: 2) Dm Load (0.4ms) SELECT "dms".* FROM "dms" WHERE "dms"."sender_id" = ? AND "dms"."receiver_id" = ? LIMIT ? [["sender_id", 1], ["receiver_id", 2], ["LIMIT", 1]] => #<Dm id: 2, content: "hogehoge", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-02 02:27:35", updated_at: "2020-11-02 02:27:35">自分がrecieverのDMのリストが必要です。followedが同じ構造なので、tutorialを読みます。メソッドはありそうなのでコンソールで試します。
>> user2.received_dms Dm Load (0.1ms) SELECT "dms".* FROM "dms" WHERE "dms"."receiver_id" = ? LIMIT ? [["receiver_id", 2], ["LIMIT", 11]] => #<ActiveRecord::Associations::CollectionProxy [#<Dm id: 2, content: "hogehoge", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-02 02:27:35", updated_at: "2020-11-02 02:27:35">, #<Dm id: 3, content: "hoge dm1", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-03 00:21:40", updated_at: "2020-11-03 00:21:40">]>モデルはできましたが、要件に必要なメソッドが揃ったのかがあいまいと感じました。
Twitterの画面をもう一度見直します。DMの画面は親子の構造になっており、親の画面は過去にやり取りしたことがあるユーザーのリストです。ユーザーを選ぶとDMのやり取りが一覧化された画面です。
必要なメソッドとして、過去にやり取りしたことがあるユーザーのリストを返すメソッドです。
ネットでfindを検索したところ、whereでOR条件が使えると分かりました。
https://qiita.com/nakayuu07/items/3d5e2f8784b6f18186f2
コンソールで試します。>> Dm.where(sender_id: 1).or(Dm.where(receiver_id: 1)) Dm Load (0.1ms) SELECT "dms".* FROM "dms" WHERE ("dms"."sender_id" = ? OR "dms"."receiver_id" = ?) LIMIT ? [["sender_id", 1], ["receiver_id", 1], ["LIMIT", 11]] => #<ActiveRecord::Relation [#<Dm id: 2, content: "hogehoge", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-02 02:27:35", updated_at: "2020-11-02 02:27:35">, #<Dm id: 3, content: "hoge dm1", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-03 00:21:40", updated_at: "2020-11-03 00:21:40">]> >> Dm.where(sender_id: 2).or(Dm.where(receiver_id: 2)) Dm Load (0.1ms) SELECT "dms".* FROM "dms" WHERE ("dms"."sender_id" = ? OR "dms"."receiver_id" = ?) LIMIT ? [["sender_id", 2], ["receiver_id", 2], ["LIMIT", 11]] => #<ActiveRecord::Relation [#<Dm id: 2, content: "hogehoge", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-02 02:27:35", updated_at: "2020-11-02 02:27:35">, #<Dm id: 3, content: "hoge dm1", sender_id: 1, receiver_id: 2, deleted: false, created_at: "2020-11-03 00:21:40", updated_at: "2020-11-03 00:21:40">]>この検索では親子の画面の機能まではできません。どの相手のDMも全て混ざっています。
ここで、仕様を深堀りし切れていないことが分かりました。
DMの相手ごとに画面を作る機能は、後で作れれば作ることとして進めることにします。モデルのテスト
モデルのテストを作ります。
DMのバリデーションをMicropostを参考に作ります。tutorialの13.1.2 「Micropostのバリデーション」を読みます。
fixtureのDMのファイルの中はサンプルなので削除します。test/models/dm_test.rbclass DmTest < ActiveSupport::TestCase def setup @sender = users(:michael) @receiver = users(:archer) @dm = Dm.new(content: "hogehoge1", sender_id: @sender.id, receiver_id: @receiver.id) end test "should be valid" do assert @dm.valid? end test "sender should be present" do @dm.sender_id = nil assert_not @dm.valid? end test "receiver should be present" do @dm.receiver_id = nil assert_not @dm.valid? end test "contentr should be present" do @dm.content = nil assert_not @dm.valid? end test "contentr should be at most 140 characters" do @dm.content = "a" * 141 assert_not @dm.valid? end endmicropostと同じ用にバリデーションを追加します。テストがGREENになりました。
app/models/dm.rbclass Dm < ApplicationRecord belongs_to :sender, class_name: "User" belongs_to :receiver, class_name: "User" validates :content, presence: true, length: { maximum: 140 } endDMを作るときのメソッドを慣習的に正しいやりかたに変更します。
test/models/dm_test.rbdef setup @dm = @sender.sent_dms.build(content: "hogehoge1", receiver_id: @receiver.id) endDMを新しい順に返すようにします。13.1.4 「マイクロポストを改良する」を読みます。
先にテストを書きます。test/models/dm_test.rbclass DmTest < ActiveSupport::TestCase ... test "order should be most recent first" do assert_equal sent_dms(:most_recent), Dm.first end endfixtureに親子関係のデータを作ります。
https://qiita.com/seimiyajun/items/ffefdfc74b9fce76a538
を参考にしました。test/fixtures/dms.ymlmorning: content: "Good morning!" sender: michael receiver: archer created_at: <%= 10.minutes.ago %>created_atの順に並ぶように設定します。テストがGREENになりました。
app/models/dm.rbclass Dm < ApplicationRecord belongs_to :sender, class_name: "User" belongs_to :receiver, class_name: "User" default_scope -> { order(created_at: :desc) } validates :content, presence: true, length: { maximum: 140 } end:destroyを追加するか考えます。
ユーザーが削除されたときには過去のDMは残る仕様に決めていました。
そのテストを追加します。リスト13.20を参考にします。test/models/user_test.rb GREENtest "associated dms should not be destroyed" do @user.save @user.sent_dms.create!(content: "Lorem ipsum", receiver_id: users(:archer).id) assert_no_difference 'Dm.count' do @user.destroy end end endここまででモデルはできました。
所要時間
10/31から11/6までの8.5時間です。
- 投稿日:2020-11-08T12:17:59+09:00
Rubyでの正規表現における行頭・行末の扱い
Railsでモデルのバリデーションを正規表現で、チェックしようとしたところエラーが起こったので備忘録としてメモしておきます。
環境
Ruby 2.5.1
Rails 5.2
エラーになった実装
user.rbclass User < ApplicationRecord validates :age, format: { with: /^[0-9]+$/, message: "は数値のみ入力可能です。"} endエラーの内容
The provided regular expression is using multiline anchors (^ or $), which may present a security risk. Did you mean to use \A and \z, or forgot to add the :multiline => true option?エラーの内容としては、
^
,$
はセキュリティのリスクがあるため、\A
,\z
を使えと言われているようです。
^
→A
$
→z
上記の方法ではない場合、:multiline => true
オプションをつけることで脆弱性を含むコードでも、あえてエラーを発生させないとい方法もあります。調べたところによると、Rails4以降ではセキュリティ対策として、正規表現が厳しくなったようです。
Rubyでは特定の頭と末尾を指定したマッチを行いたい場合は以下のように実装するのが良さそうです。
:multiline => true
オプションをつける方法もありますが、バリデーション処理の場合などは、特別な理由がない限りは、指定しないほうがいいのかなと感じました。user.rbclass User < ApplicationRecord validates :age, format: { with: /\A[0-9]+\z/, message: "は数値のみ入力可能です。"} end一応Rubyの公式リファレンスでも以下のように定義されています。
^ 行頭にマッチします。行頭とは、文字列の先頭もしくは改行の次を 意味します。
$ 行末にマッチします。 行末とは文字列の末尾もしくは改行の手前を意味します。
\A 文字列の先頭にマッチします。
\z 文字列の末尾にマッチします。参考文献
正規表現によるバリデーションでは ^ と $ ではなく \A と \z を使おう
Rails4では正規表現が厳しくなった。
Ruby 2.7.0 リファレンスマニュアル 正規表現
- 投稿日:2020-11-08T11:25:18+09:00
便利❣️Rails開発がスムーズになる機能!
現在私はdocker環境下でRuby on Railsで自社サービスの開発を行っており、
dockerを使用した方ならご理解頂けると思うのですが、、、
_人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人人_
> ?docker関連のコマンド反応がめっっっっっっっっっっっちゃ遅い!!!? <
 ̄^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
特に以下のケースを確認する時に時間がかかるとイライラします。。
コントローラに渡すパラメーターの中身を確認したい。
呼び出しているActive Recordの種類。
render元のファイル。
とまあいろいろあるのですが、とにかく以下から始まるコードはとにかく遅いです。
(この前migrationファイルを作成するのに約10分かかりました。。)$ docker-compose run ~
そんな私と同じような環境で開発している、Rails初心者の方へ朗報です!
Rails panell
というchromeの拡張機能はご存知でしょうか??
結論から言うとめちゃ便利です。
デバッグ関連でpry
とか使用している人もこれは入れて欲しい。
以下概要です!
Chrome store link
https://chrome.google.com/webstore/detail/railspanel/gjpfobpafnhjhbajcjgccbbdofdckggg
これを拡張機能に追加すると、検証ツールの一番右側に追加されて、以下のことを簡単に確認できます!
(上記画像参照。)
Breakdown: 処理時間の内訳(ActiveRecord, Rendering, Other)
Params: コントローラから参照できるparamsの内容
ActiveRecord: そのリクエストを処理する際に発行したSQLと処理時間
Rendering: ビューテンプレートごとの描画時間
(参考元:https://chopschips.net/blog/2015/03/06/rails-panel/)
なのでいちいちコンソールを立ち上げたり、
pry
で止めたりせずとも簡単に確認できちゃいます!
これでデバッグに係る時間を少しでも削減できますね♫
今回初投稿となり拙い文章で間違っている箇所もあるかと思いますが、
もしここまで読んでくれた方がいたらとても嬉しいです!
もっと便利な拡張機能あるよとかあれば、是非コメントで教えてください?♂️
以上初心者エンジニアでした!
- 投稿日:2020-11-08T11:08:11+09:00
【Rails/AWS】RDSのMySQLに繋がらないエラーの考えられる原因(database.yml)
はじめに
本記事は、RailsアプリをAWSにデプロイした際に発生する可能性のある
MySQLに繋がらないという事象に対する原因例を紹介します。
原因はかなり初歩的な原因でしたが、筆者はこのエラー原因が特定できずとても苦労したため、
今後同じエラーに遭遇した方の助けになれば幸いです。開発環境
- Ruby 2.5.1
- Rails 5.2.4.4
- AWS(EC2, RDS)
- MySQL(RDS) 5.6.48
前提条件
- RailsアプリをEC2のWebサーバー上にgitクローン済み。
- 基本的な設定は完了済みで、rake db:create RAILS_ENV=productionのコマンドを実行する手前の状態。
- RDSのDBインスタンスを作成済み。
- Railsのdatabase.ymlは下記の内容です。
database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: socket: /tmp/mysql.sock production: <<: *default database: データベース名 host: <%= ENV['DATABASE_HOST_PRODUCTION'] %> username: <%= ENV['DATABASE_USER_NAME_PRODUCTION'] %> password: <%= ENV['DATABASE_PASSWORD_PRODUCTION'] %>エラー内容
以下のコマンドだとRDSのMySQLに繋がったのですが・・・・
[ec2-user@ip-10-0-1-10 アプリのディレクトリ]$ mysql -h RDSのエンドポイント -u root -P 3306 -p以下のコマンドだと繋がらず、MySQLに繋がらないというエラーが出ました。
[ec2-user@ip-10-0-1-10 アプリのディレクトリ]$ rake db:create RAILS_ENV=production Can't connect to MySQL server on '10.0.1.10' (111) Couldn't create 'データベース名' database. Please check your configuration. rake aborted! Mysql2::Error::ConnectionError: Can't connect to MySQL server on '10.0.1.10' (111) Tasks: TOP => db:create (See full trace by running task with --trace)原因/解決策
当初はホスト名の設定ができていなかったので、エンドポイントを環境変数に入れて修正したのですが、
mysqlコマンドだと繋がるのにrakeコマンドだと繋がらないという謎の現象が発生しました。
いろいろ調べたところ、MySQLのパスワードにパスワードに"#"が入っていることが原因でした。
YAMLファイルはコメントアウトの記法が#ということで、パスワードに"#"が入っているとコメントされてしまいます。
パスワードを変更したら無事rakeコマンドが通りました。
みなさんお気を付けください。まとめ
MySQLのパスワードに"#'を含めるのは良くない。
- 投稿日:2020-11-08T00:42:06+09:00
【RSpec】Shoulda-Matchers で独自に設定したエラーメッセージをテストする方法
shoulda-matchersを使ってカスタムメッセージのバリデーションテストをするとき、書き方がわからなくて少し詰まったのでここに共有させて頂きます。
shoulda-matchersとは何ぞや?
shoulda-matchersというのは、通常のRSpecで書くと長くなるテストが簡潔にかけるようになるgemです。
例えば以下のようなバリデーションを持ったモデルがあったとします。
user.rbclass User < ApplicationRecord validates :nickname, presence: true, length: { maximum: 30 } endこのテストが以下のように一行でかけます。
spec/models/user_spec.rbit { is_expected.to validate_length_of(:nickname).is_at_most(30) }なかなかに便利ですね。
独自に設定したエラーメッセージをテストしたい場合
例えば以下のような、独自のエラーメッセージを持ったバリデーションを設定したとします。
user.rbvalidates :email, presence: { message: 'が入っていません' }バリデーションと同時に、独自のエラーメッセージが表示されているかということもチェックしたい場合
spec/models/user_spec.rbit { should validate_presence_of(:email). with_message('が入っていません') }このように書くことで独自のエラーメッセージとバリデーションを同時にチェックすることができます。
他にも使い方が色々あるので、気になる方はこちらを一読してみてください。
導入方法も書いてあります。
Shoulda-Matchers READ ME最後まで読んでいただきありがとうございます!
日々学んだことをアプトプットしてます!なにかご指摘などあればコメントいただけますと嬉しいです!
- 投稿日:2020-11-08T00:41:05+09:00
ruby小文字を大文字にする
Rubyで小文字を大文字にする
半角アルファベットの小文字で構成された長さ t の文字列 y が与えられます。
文字列 y を大文字に変換して出力して下さい。
入力される値
入力は以下のフォーマットで与えられます。
y
期待する出力
y を大文字に変換した文字列を出力してください。
入力例1
qiita
出力例1
QIITA
私の答え
y = gets puts y.upcase今回のポイント
1行目のgetsで文字列を取得しています
2行目で取得した文字列yをupcaseメソッドで出力しています
upcaseメソッドは小文字→大文字にするメソッドです。ちなみにその逆はdowncaseメソッドです。
y = "AAAAA" puts y.downcase出力結果▶︎ AAAAAとなります。
他にも小文字のみや大文字のみを逆にしてくれるswapcaseメソッドや先頭の大文字を小文字に変換してくれるcapitalizeメソッドがあります。
以上!
- 投稿日:2020-11-08T00:19:31+09:00
Rails Asset Pipeline 基礎のキ
Asset Pipelineとは
JavaScriptやCSSのアセットを最小化 (minify: スペースや改行を詰めるなど) または圧縮して連結するためのフレームワーク
参照: Railsガイド -AssetPipelineについて-
https://railsguides.jp/asset_pipeline.htmlこの記事は、AssetPipelineの概要を説明しておくことで、RailsガイドのAssetPipeline記事の理解度を深めるためのものです。
そもそもcoffeescript、sass、erbなどはプログラムを書く・読む人側にとって生のjs/cssよりも簡単に書けるので嬉しいが、実行するマシン側にとっては読み取りづらいもの。これらcoffeescript, sassなどは高級言語と呼ばれるが、これらを .js.coffee→.jsに、 .css.sass→.css に自動でコンパイルとminify(縮小)してくれるのが、Asset Pipelineの役目。
ただし、コンパイルは、開発(development)環境のみで行われる。開発環境では、debuggerなどでデバッグをする以上、出来る限り、開発中のjs/cssを保持していた方がプログラムを書く・読む人側にとっては助かるからである。
ここでのコンパイルの定義は、プリコンパイルを含まないものとします。後述。
では、本番環境では何が行われているか?
本番環境では1: プリコンパイルファイル群の存在
2: 1のファイル群を1枚のjsファイル、1枚のcssファイルに統合
3: 圧縮
4: ダイジェスト付与を行う。この最初に出てきたプリコンパイルファイルについてだが、そもそも本番環境でもコンパイルを行ってしまうと、アプリケーションの実行速度に大きく影響されてしまう。
そのため、予めコンパイルされたファイル群を持たせておく(プリコンパイルする)ことで本番環境での実行速度を保とう、ということだ。開発環境と、本番環境の流れを図で表すと、以下のようになる。左上から始まり、開発(development)環境は途中で出力されるが、本番環境は最後の右下に到達する。
上記の内容がRailsガイドの理解に深まることを願う。
https://railsguides.jp/asset_pipeline.html