- 投稿日:2021-08-29T21:18:16+09:00
【Rails】エラーメッセージ表示
目標 エラーメッセージを表示させる ユーザーの登録や投稿がなんで失敗したか知らせてくれるメッセージ 実装 エラーメッセージのファイル作成 app/views/layouts/_error_messeages.html.erb <% if model.errors.any? %> <div id="error_explanation" class="alert alert-warning"> <ul class="mb-0"> <% model.errors.full_messages.each do |message| %> <li><%= message %></li> <% end %> </ul> </div> <% end %> バリデーション 新規登録の際の制限を記述。 ここに記述した制限に引っかかった場合にエラーメッセージで表示される。 app/models/user.rb class User < ApplicationRecord before_save { email.downcase! } validates :name, presence: true, length: { maximum: 50 } validates :email, presence: true, length: { maximum: 255 }, format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i }, uniqueness: { case_sensitive: false } has_secure_password 新規登録フォームにパーシャルrenderでエラーメッセージファイルを記述する。 <%= render 'layouts/error_messages', model: f.object %> app/views/users/new.html.erb <h1>Sign up</h1> <%= form_with(model: @user) do |f| %> <%= render 'layouts/error_messages', model: f.object %> <div> <%= f.label :name, 'Name' %> <%= f.text_field :name, class: 'form-control' %> </div> <div> <%= f.label :email, 'Email' %> <%= f.email_field :email, class: 'form-control' %> </div> <div> <%= f.label :password, 'Password' %> <%= f.password_field :password, class: 'form-control' %> </div> <div> <%= f.label :password_confirmation, 'Confirmation' %> <%= f.password_field :password_confirmation, class: 'form-control' %> </div> <%= f.submit 'Sign up', class: 'btn btn-primary' %> <% end %> これでエラーメッセージが表示されます、他のフォームも同様の方法で大丈夫です。
- 投稿日:2021-08-29T18:57:56+09:00
rails g devise User を実行の際のエラーの話
背景 rails g devise User を実行の際、以下のエラーが発生 rails aborted! StandardError: An error has occurred, this and all later migrations canceled: PG::DuplicateColumn: ERROR: column "email" of relation "users" already exists エラー文を見ると、どうやらusersテーブルが重複しているようだ。 試した事 rails db:migrate:reset しかし、 rails aborted! ActiveRecord::NoEnvironmentInSchemaError: Environment data not found in the schema. To resolve this issue, run: bin/rails db:environment:set RAILS_ENV=development 以上のようなエラーが発生。 binは一切弄っていないので他に原因がありそう。 解決方法 対象モデルを削除する rails destroy devise user 次にテーブルを削除するために、削除用のmigrationファイルを作成する rails g migration drop_table_users(←適当なファイル名で可) 作成したmigrationファイルにテーブル削除を記述する class DropTableUsers < ActiveRecord::Migration def change drop_table :users end end マイグレーション実行 rails db:migrate
- 投稿日:2021-08-29T18:44:13+09:00
LINE Messaging APIを使ってみる
概要 LINEのMessaging APIを使ってみる。 Railsでサーバー立ち上げて、実際にWebhookでどんな値が連携されるのか見ていきます。 (公式にあるのかもだけど見当たらなかったので) ただ、Messaging APIの動きを見たいだけなので環境とかは適当に作ります。 誰かの役に立てば嬉しいです。 RailsのDockerイメージを作る 以下をまるまる真似してDockerでRailsが動くところまでやった。 - DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】 と、言いたいところだったけど、postgressのイメージバージョンの影響で動かないところがあった。 以下の対応でとりあえず回避した。 - PostgresのDockerImageに変更があって起動ができなくなった話 GrapeでAPIを作る Grapeで最低限リクエスト受け取って動く形にする。 Grapeの導入はREADME見てもらえればわかるかと思います。 実装は以下のようにした。 app/api/line/line.rb module Line class API < Grape::API version 'v1', using: :header, vendor: 'line' format :json prefix :api resource :messaging do desc "user test" post do User.all end end end end Postman使ってローカルのAPI叩けるか確認した。 herokuにデプロイする Messaging APIのリクエストを受け取るためにはデプロイしないといけないので、herokuでデプロイする。 herokuのGetting Startedを参考にすればデプロイまでは簡単にできる。 welcomeページいらないけど、必要みたいなのでとりあえず追加。 Messaging APIのWebhoom URLを設定する herokuでデプロイしたサーバーのAPIのURLを LINE Developersで設定する。 herokuのURLを入れるとverifyが正常に動く。 Messaging APIのWebhookで渡ってくるパラメータ 今回の目的は、Messaging APIのWebhookがどういうパラメータを連携するか知りたいので、grape_loggingを入れてパラメータを確認した。 以下、友だち追加、メッセージ送信、ブロックのときのパラメータ。 Messaging APIのQRを自分のLINEで読み込んでそれぞれの動作を確認。 follow params => { "destination" => "", "events" => [{ "type" => "message", "message" => { "type" => "text", "id" => "", "text" => "てすと" }, "timestamp" =>, "source" => { "type" => "user", "userId" => "" }, "replyToken" => "[FILTERED]", "mode" => "active" }] } text params => { "destination" => "", "events" => [{ "type" => "message", "message" => { "type" => "text", "id" => "", "text" => "てすと" }, "timestamp" =>, "source" => { "type" => "user", "userId" => "" }, "replyToken" => "[FILTERED]", "mode" => "active" }] } unfollow params => { "destination" => "", "events" => [{ "type" => "unfollow", "timestamp" =>, "source" => { "type" => "user", "userId" => "" }, "mode" => "active" }] } さいごに パラメータの構造がイメージできなかったけど、これでテスト書いて実装できそう。 公式とかでわかりやすい記述があったら教えていただけるとたいへん喜びます。
- 投稿日:2021-08-29T17:10:26+09:00
flashについて
なぜ書くのか? flashで表示すべきか?htmlで表示するべきか?を判断するときに、それぞれの技術の性質や役割を理解していないために、どちらを使えば良いのかを判断ができなかった。またそれらの技術の特徴を理解する以前に、基本的なこと(ブラウザのリクエスト、レスポンス、セッション(Cookie))についての理解が曖昧なために、どれだけ説明をよんでもちゃんと理解できなかった。そのため、本記事では研修で学んだ基礎的なことの復習をし、flashの技術の性質を理解するためにかく。 基本的なことのおさらい HTTPとは HTTPとはWebサーバとWebブラウザの間で情報のやり取りをするための通信規則です。(プロトコル) 例えばgoogleでサイトを閲覧するときも、このプロトコルに則ってブラウザがサーバーにリクエストし、その結果サイトを閲覧することができている。 HTTPの特徴として、常にブラウザ側からサーバーをよびだし、1つの要求(リクエスト)には1つの応答(レスポンス)を返すこと。そして、以前に何を要求したかで応答が変わることなく、同じ条件ならば、ある要求に対する応答は常に同じものになることです。(ステートレス) 似た名前でHTTPSがあります。HTTPとHTTPSの違いは通信が暗号化されていないか暗号化されているかの違いで、HTTPSが暗号化されています。( 僕の肌感ですが、だいたいのWebサイトがhttpsになっている気がします。 HTTPリクエスト HTTPメソッドを使ってサーバーに対して、必要なレスポンスの要求をすることです。HTTPリクエストには「HTTPリクエスト行」「HTTPヘッダー」「データ本体」の3つのパートがあります。 HTTPリクエスト行 HTTPリクエストの1行目が、「メソッド」「URL」「HTTPのバージョン」の3つの情報が含まれています。 HTTPヘッダー 主に5つが含まれている ユーザーエージェント名(User-Agent) ブラウザの種類やOSの情報 2.リファラ(Referer) リクエストの発生元のページ名 クッキー(Cookie) Cookieとは、ブラウザに対し特定の情報を保持させておく仕組みです。例えば、ログインしたことあるサイトであればIDとパスワードの情報を打たなくても、入力フォームに情報が入っているなど。 更新されていたら(If-Modified-Since)/同じでなければ(If-None-Match) ブラウザに保存されているローカルキャッシュが変更されているかどうかを問い合わせています 受け取り希望情報(Accept、Accept-Language、Accept-Encoding、Accept-Charset) どのようなデータを受け取りたいのか、言語、文字コード、画像の種類などの情報 データ本体 メソッドに「POST」が指定されている場合、送るデータが記載される 「GET」の場合は空になる HTTPレスポンス HTTPリクエストに従って、必要なレスポンスを返すこと。HTTPレスポンスには「レスポンス状態行」「HTTPヘッダー」「データ本体」の3つのパートがあります。 レスポンス状態行 HTTPステータスコードが含まれている HTTPヘッダー データ本体の各種の状態を示す情報が入れられている部分です。主に6つが含まれています。 コンテンツタイプ(Content-Type) どのようなデータを受け取ったのか?HTMLなのか画像、文字コードなどの情報。 再利用期限(Expires) 取得したデータを再度サーバーに問い合わせなくてもブラウザが再利用していい期限(キャッシュの制御に使われる) データの最終更新日時(Last-Modified)やエンティティ情報(ETag) HTMLや画像などがいつ更新されたものかの情報や、サーバー上のファイルの場所ID、ファイルのサイズ、更新日時などから算出した、更新チェック情報。次回同じデータをリクエストする際に、これらの情報を使って更新されているかどうかを確認します。 キャッシュ制御(Cache-ControlやPragma) ブラウザや通信を橋渡しするプロキシが、データのキャッシュをどう扱うかの情報。 接続状況(Connection) 接続を持続するのか(keep-alive)、毎回接続を切断するのか(close)。ブラウザもサーバーもHTTPバージョン1.1の持続接続(keep-alive)を使える場合、通信のやりとりが効率良くなります。 移動先(Location) リクエストと違う場所からデータを取得するように示す指示。新しい場所のURLが含まれます。いわゆるリダイレクト先を示す情報です。 本題(flashの技術の性質を理解していく) flashって? flashの性質を理解しようと、いくつかのサイトを参考にまとめた結果、下記のように動いていることが分かった。 リダイレクト前にセッションに値を保存して、リダイレクト後のページでセッションから値を取り出して、次のリクエストでセッションに保存した値を削除する ここから2つの疑問がうまれる sessionにいつ保存したん? flash[:key]= "hoge" としか書いてないのに、なぜcookieにflashの値が保存されているの? ブラウザでcookieの中を探しても、hogeらしき文言が見つからない! sessionから、値を取得するための処理をコントローラ内に書かないと!? cookieに保存された値を取得するためのメソッドはどこに書かれているのか? 外部情報の取得 == GETリクエスト (間違った解釈) のはずだから、検証ツールのNetworkを確認すれば、GETのレスポンスの中にhogeがいるのではないか! 疑問(勘違い)を抱いた背景 1の原因 railsチュートリアルでは、ログイン済みユーザーを返すためにに下記のコードを書いていた。 なので、sessionと連携するために、session[:flash] = flashみたいなものを書くべき!と考えた。 # sessionに保存 def log_in(user) session[:user_id] = user.id end flashの中の実装がどうなっているかを知らなかった cookieに保存するときに、値が暗号化されることを忘れてしまっていた 2の原因 外部情報の取得 == GETリクエスト (間違った解釈) HTTPリクエストをちゃんと理解ができていなかった railsとsessionとの関係が理解できていない 下記では、flashの確認の前に、sessionの実体を探っていく Sessionの仕組みについて知る そもそもsession[:key] = "hoge"も、どうやって実装しているんだっけ?を紐解いていく ApplicationControllerから、順に継承元を見ていくと ActionController::Metalというクラスに、初めてsessioの文字を発見(githubリンク) module ActionController ... class Metal < AbstractController::Base ... delegate :session, to: "@_request" ... end ... end ここから、ActionController::Metalがsessionのメソッドを持っていることがわかるが、中身が@_requestで何を示していいるのかがわからない。。 inspectメソッドでクラス名を調べる アクションの中に、raise session.inspectを挿入にしてオブジェクトを調べてみる def show raise session.inspect @task = Task.find(params[:id]) end 結果、sessionがActionDispatch::Request::Sessionであるとことが判明! どのクラスかがわかったので、おってみる(gituhbリンク) module ActionDispatch class Request ... class Session ... def [](key) @delegate[key] end ... def []=(k, v) @delegate[k] = v end ... end end end これでようやくアプリケーションの controllerで、sesion[:hoge]= "hogehoeg"のメソッドが使える理由が判明! (継承クラスを追ってみて思った。結構複雑。日々何気なく、使ってるメソッドも先人の努力の賜物であり、先人に感謝しないとなと思った。) セッションの管理 Railsでは、セッションの管理にCookieStoreがデフォルトで用意されている cookieの他、Redis(インメモリ方式)などがある Session情報を全てsecret_key_baseで暗号化し、クライアントのCookieに保存する リクエストの際に、cookieに保存した暗号かされた情報から、アプリケーション内でsecret_key_baseで復号し、Session情報を取得する flashの実装の中身を見ていく sessionの仕組みがある程度わかったので、flashの実体を追っていく def show raise flash.inspect @task = Task.find(params[:id]) end flashは、ActionDispatch::Flash::FlashHashであることがわかった。この中身を追っていく(github) module ActionDispatch class Flash ... class FlashHash ... def [](key) @delegate[key] end ... def []=(k, v) @delegate[k] = v end ... end end end なるほど!これで、アプリケーションの controller、flash[:hoge]= "hogehoeg"のメソッドが使える理由がわかりました。 ここにflash.nowの実装も書かれていました。 flashをsessionに保存 or 削除 def commit_flash # :nodoc: session = self.session || {} flash_hash = self.flash_hash # flash_hashをsessionに保存 if flash_hash && (flash_hash.present? || session.key?("flash")) session["flash"] = flash_hash.to_session_value self.flash = flash_hash.dup end # sessionに保存していたflashをkeyとするhashを削除 if session.loaded? && session.key?("flash") && session["flash"].nil? session.delete("flash") end end このcommit_flashはActionDispatch::Routing::Redirectionの中で、読み込まれており、リダイレクトのたびに実行される。 4つのポイントに分けて確認する 1. flashメッセージをセッションに登録 コントローラ内のupdateアクションで、flashを作成 ここでは、sessionには保存されていない def update ... flash[:success] = "タスク「#{@task.name}」を更新しました" binding.pry ... end pry(#<TasksController>)> flash => #<ActionDispatch::Flash::FlashHash:0x00007f980022c028 @discard=#<Set: {}>, @flashes={"success"=>"タスク「変更」を更新しました"}, @now=nil> pry(#<TasksController>)> session[:flash] => nil 2. ルーティング後のコントローラ リダイレクトのタイミングで、commit_flashメソッドが実行され、sessionにflashを保存 def index binding.pry @tasks = Task.all ... end pry(#<#<Class:0x00007f95019d9228>>)> session[:flash] => {"discard"=>[], "flashes"=>{"success"=>"タスク「変更」を更新しました"}} 3. Viewでflashを表示 sessionに保存していたflash_hashの情報を取得し、画面に表示 <% binding.pry %> <% flash.each do |key, value| %> <%= content_tag(:p, value, class: "mb-4 alert alert-#{key}") %> <% end %> pry(#<#<Class:0x00007fcb0feecd20>>)> flash => #<ActionDispatch::Flash::FlashHash:0x00007fcb1232e3f0 @discard=#<Set: {"success"}>, @flashes={"success"=>"タスク「変更」を更新しました"}, @now=nil> 4. 2回目のルーティング後のコントローラ リダイレクトのタイミングで、commit_flashが実行され、sessionのflashが削除 flashの中身もすべて削除されている def show binding.pry @task = Task.find(params[:id]) end pry(#<TasksController>)> session[:flash] => nil pry(#<TasksController>)> flash => #<ActionDispatch::Flash::FlashHash:0x00007fcb194be380 @discard=#<Set: {}>, @flashes={}, @now=nil> この結果から、もちろんだが次のviewではflashメッセージは表示されていません。 flashとhtmlの使い分け flash アクションの結果をリダイレクト先で一時的に表示するときに使う html アクションの結果を現在のページで表示したい時(永続的)に使う 結果 研修でなんとなくで読んでいた箇所を今回改めて読むと以前よりも、理解できる箇所やイメージがつく箇所が増えた。正直なんとなくの理解でも、コードは動くことは動く。ただ、なんとなくの理解では、模倣でしかコードを書くことができず、応用がきかなくなりすぐに頭打ちがきそう。数年後の自分のためにも、納得して進めていきたいと思う。
- 投稿日:2021-08-29T16:23:39+09:00
Ruby メソッドについて 引数や戻り値など
メソッドとは メソッドとは何の処理をしているかをわかりやすくまとめた、かたまりのことです。 ⚫︎例えば def sports puts "野球" puts "サッカー" puts "バスケ" end sports みたいにdefとendで囲みます。defの後にsportsと書いてあるので sportsのことが書いてあるのだなとわかりますよね。これでsportsメソッドとなります。 その後def endの間に書くだけだと何も出力されないのでsportsとdefとendの間以外に 書くとsportsの中が出力されます。spotrsを10回書くとspotrsの中が10回出力されます。 変数、戻り値、返り値、引数を使うとどうなるか。 例えば平均点を求めるとします。 def average(a+b+c) #変数 (a+b+c)/3 #戻り値 #返り値 end average(4,5,3) #引数 average(6,5,7) これを出力すると 3 6 と出力されます。 これは引数のaverage()の中のものをdef averageの後に(a+b+c)に値が返ってきます。 値が返ってくる(引っ張ってくる)から引数と思ってください。 そして引数で引っ張ってきたらdefとendの中の(a+b+c)/3が処理をされて出力されます。 *3と出力されるのは(4+5+3)÷3で3です。 *6と出力されるのは(6+5+7)÷3で6です。
- 投稿日:2021-08-29T16:06:04+09:00
Active Jobについて知る
なぜ書くのか? ユーザー登録時にslackに通知する機能を実装した際に、他の実装を真似てApplicationJobを継承した。実装自体はできたが、Active Jobの役割は非同期処理をするくらいの認識で、その仕組みについてしらないことだらけ。なので、Railsガイドに参考に分からない単語を調べたり、実際に実装してみて確かめながら理解を深めていくことにする。 Active Jobの目的 Active Jobの主要な目的は、あらゆるRailsアプリケーションにジョブ管理インフラを配置することです。これにより、Delayed JobとResqueなどのように、さまざまなジョブ実行機能のAPIの違いを気にせずにジョブフレームワーク機能やその他のgemを搭載することができるようになります。バックエンドでのキューイング作業では、操作方法以外のことを気にせずに済みます。さらに、ジョブ管理フレームワークを切り替える際にジョブを書き直さずに済みます。 railsガイドより引用 色々難しいことを書いているが、ここでは一旦Active Jobは非同期処理を実装するための機能であり、複数のgemと連携して使うこともできると理解しておく。 まずは言葉の理解から Active jobの理解の前に頻繁にでてくる単語、Job(ジョブ)とqueue(キュー)について理解する。技術ブログ ActiveJobとはを参考 jobとは ジョブは現実世界で例えるなら、買い物にいく、料理をする、片付けをするといったような一つ一つのタスクのようなもの。 queueとは キューはタスクを登録するための入れ物のようなイメージ このようにタスクを順番にqueueに登録していき、jobを順番に実行していく。このフローのことをジョブキューと呼ばれ、先に登録されたものから先に実行される。(先入れ先出し) 実装例 例えば、ユーザー登録時にメール送信とslackに通知する場合 キューを利用することで、メール送信とslack通知を待たずに、次の処理に入ることできる。またキューで登録された処理が仮に失敗しても、次の処理を行うことも可能にする。 jobとqueueについて理解できたので、下記では実際にジョブを作成しキューに登録、実行までの流れを確認していく。 簡単なJobを作成して実行して見る User modelを作成し、UserJobを作成 shell $ rails new active_job_sample $ cd active_job_sample $ bin/rails db:create $ bin/rails g model user name $ bin/rails db:migrate #自動で下記のクラスを作成する $ bin/rails generate job user class UserJob < ApplicationJob queue_as :default def perform(name="hoge") # 下記は追記した User.create!(title: name) end end performメソッドの種類について確認 上記のperformは2種類の呼び出し方がある メソッド名 概要 perform_now 同期処理: キューに入ることなく即座に実行される perform_later 非同期処理: ジョブをキューに入れ、キューが空き次第ジョブを実行する rails cでjobの動作確認したいときなどに、perform_nowを使うとよい! Active Jobはキューイングのバックエンドが必要 ? Rails自身が提供するのは、ジョブをメモリに保持するインプロセスのキューイングシステムだけ。 プロセスがクラッシュしたりコンピュータをリセットしたりすると、デフォルトの非同期バックエンドの振る舞いによって主要なジョブが失われてしまう。なので、開発環境や小規模アプリケーションの場合には、ActiveJobのみでも問題なさそうだが、本番環境でアプリケーションを運用していく場合は、キューイングのバックエンドが必要になる。 キューイングライブラリ キューイングライブラリとして有名なのは、Delayed JobやSidekiq、Resqueなど。これらは、Active Jobが導入される以前から、非同期処理を行うgemとして使われていたらしい。 Active Jobはどのgemに対しても、キューイングバックエンドに接続できるアダプタがビルトインで用意されている(優秀)。また、Active Jobを使用せずに上記のgemのみを使用することも可能である。今回は上記のgemの中でも最もdefaultなSidekiqを使用してみる。 Sidekiqって? sidekiqの特長↓ 項目 DB Redis スレッド マルチスレッド リトライ処理 あり Redisとは Redisは、ネットワーク接続された永続化可能なインメモリデータベース。ジョブの情報を保存している。 スレッドとはプログラムの一連の処理のまとまりのこと。マルチスレッドのプログラムは、複数の処理を並行して実行させることが可能。 Job(Worker)の中でraiseをキャッチして、自動でリトライする。 デフォルトの設定では21日間に25回のリトライを行う。リトライは、m2秒後、4秒後、8秒後・・・というように指数関数的に間隔をあけ、行われる。 sidekiqを利用してみる Gemfile gem 'sidekiq' gem 'sinatra', require: false # ダッシュボードを利用するため bundle install application.rb module ActiveJobSample class Application < Rails::Application ... config.active_job.queue_adapter = :sidekiq ... end end config/initializers/sidekiq.rb Sidekiq.configure_server do |config| config.Redis = { url: 'Redis://localhost:6379'} end Sidekiq.configure_client do |config| config.Redis = { url: 'Redis://localhost:6379'} end sidekiq.yml :verbose: false # 対話モード :concurrency: 10 # worker process 数 :queues: # 処理するキュー名 - default 大きなプロジェクトではqueueを複数に分けて、queueごとにsidekiqのプロセス(インスタンス)を分けるが、今回は動作確認程度なのでdefaultにする。 $ bundle exec sidekiq -C config/sidekiq.yml http://localhost:3000/sidekiqにアクセスしてダッシュボードを確認 これでようやく、sidekiqのキューにジョブを登録する準備が整った コンソールからジョブを実行してみる $ rails c irb(main):001:0> UserJob.perform_later Enqueued UserJob (Job ID: 02002483-9077-4832-963b-1058ef6534fe) to Sidekiq(default) => #<UserJob:0x00007ffa08f36f38 @arguments=[], @job_id="02002483-9077-4832-963b-1058ef6534fe", @queue_name="default", @priority=nil, @executions=0, @exception_executions={}, @timezone="UTC", @provider_job_id="26bcc5664da36768f743b851"> 完了が1になった!いい感じにsidekiqが機能し、非同期で実装できていそう! この他にも、setメソッドを使いジョブの実行をスケジュールすることも可能 内部実装を見ていく Activejobかキューイングライブラリsidekiqと連携するまで module ActiveJob # :nodoc: ... class Base include Enqueuing ... end ... end ActiveJob::BaseはActiveJob::Enqueuingをincludeしており、ActiveJob::Enqueuingにクラスメソッドとしてperform_laterメソッドが定義されている module ActiveJob module Enqueuing extend ActiveSupport::Concern ... module ClassMethods def perform_later(...) job = job_or_instantiate(...) enqueue_result = job.enqueue yield job if block_given? enqueue_result end private def job_or_instantiate(*args) # :doc: args.first.is_a?(self) ? args.first : new(*args) end ... end ... def enqueue(options = {}) self.scheduled_at = options[:wait].seconds.from_now.to_f if options[:wait] self.scheduled_at = options[:wait_until].to_f if options[:wait_until] self.queue_name = self.class.queue_name_from_part(options[:queue]) if options[:queue] self.priority = options[:priority].to_i if options[:priority] self.successfully_enqueued = false run_callbacks :enqueue do if scheduled_at queue_adapter.enqueue_at self, scheduled_at else queue_adapter.enqueue self end self.successfully_enqueued = true rescue EnqueueError => e self.enqueue_error = e end ... end end perform_laterの引数に何も指定しなければ定義したActiveJobがインスタンス化され、enqueueメソッドが呼び出される。enqueueメソッドで、ジョブがscheduledされていないか?やどのキューに登録するかを確認する。queue_adapterはconfig.active_job.queue_adapterで指定した名前から生成されるアダプタクラスになる。つまり、今回では:sidekiq を指定しているので、下記のqueue_adapterメソッドを呼びActiveJob::QueueAdapters::SidekiqAdapterが生成されることになる。 def queue_adapter=(name_or_adapter) case name_or_adapter when Symbol, String queue_adapter = ActiveJob::QueueAdapters.lookup(name_or_adapter).new assign_adapter(name_or_adapter.to_s, queue_adapter) else if queue_adapter?(name_or_adapter) adapter_name = "#{name_or_adapter.class.name.demodulize.remove('Adapter').underscore}" assign_adapter(adapter_name, name_or_adapter) else raise ArgumentError end end end その後、ActiveJob::QueueAdapters::SidekiqAdapter内で、ジョブのクラス名や引数を Sidekiq::Clientに投げている。 def enqueue(job) # :nodoc: # Sidekiq::Client does not support symbols as keys job.provider_job_id = Sidekiq::Client.push \ "class" => JobWrapper, "wrapped" => job.class, "queue" => job.queue_name, "args" => [ job.serialize ] end Sidekiqによるエンキューとデキュー コードが多くなりすぎるので、テキストのみ掲載 Redisのキューにジョブの情報を登録 Sidekiq.client.push処理でsidekiq内の実装で、受け取ったジョブ情報をJsonに直し、ジョブIDの情報を追加して、 Redisのqueues:#{queue_name}キーに追加 Redisのキューから登録したジョブを実行する bundle exec sidekiq を実行し、Sidekiq::Managerがworker作成。その後、workerはそれぞれのスレッドを起動 Sidekiq::Processorがメインの処理で、queueから取り出したjobを実行する process_oneメソッド内で、Redisからjobの情報をdequeueする processメソッドで、取得したjobから各種情報を引き出しロードし、dispatchメソッド内でjobのインスタンスを作成して、jobの処理を実行している。 感想 社内のソースコードを真似すればいい感じに実装されるといったことが多々あり、あたかも自分が簡単に複雑な機能を実装したと勘違いしてしまいやすい。今回のslack通知を実装した際も、なんか非同期になってる、なんかRedisにジョブの情報を保存してるといった曖昧な理解しかできなかった。なんとなくでその場を乗り切ったとしても、自分の実力は上がらないし知識も増えない。だから、今後も日々の業務で理解しきれなかったことを今回のようにミニマムで実際に動かしてみたり、コードリーディングを通して仕組みを理解するようにする。 参考記事
- 投稿日:2021-08-29T13:39:56+09:00
[Ruby] sortメソッド
はじめに 今回はsortメソッドという配列の順番を並び替えられるメソッドを紹介します。 sortメソッド 配列に対して使います。 配列に対してsortメソッドを使うと中身を昇順に並び替えてくれます。 array = [1, 5, 3, 2, 4] array.sort => [1, 2, 3, 4, 5] またreverseメソッドを組み合わせれば降順にできます。 array.sort.reverse => [5, 4, 3, 2, 1] 文字列にも使えます。 array = ["b", "a", "d", "f", "e", "c"] array.sort => ["a", "b", "c", "d", "e", "f"] sort! とすることで破壊的に使用することも可能です。
- 投稿日:2021-08-29T09:11:06+09:00
制作状況
model/order.rb class Order < ApplicationRecord : has_many :ordered_items has_many :items, through: :ordered_items enum status: {"入金待ち":0,"入金確認":1, "製作中":2, "発送準備中":3, "発送済み":4} : end model/ordered_item.rb class OrderedItem < ApplicationRecord belongs_to :order belongs_to :item enum making_status: {"製作不可":0,"製作待ち":1,"製作中":2,"製作終了":3} end orders.controller.rb class Admin::OrdersController < ApplicationController def show @order = Order.find(params[:id]) @ordered_items = @order.ordered_items end # 注文ステータスの更新 def update @order = Order.find(params[:id]) # ステータス更新ボタンが押された注文データ特定 @ordered_items = @order.ordered_items # 注文商品から注文商品テーブルの呼び出し @order.update(order_params)# 注文ステータスの更新、更新するカラムはstatus if @order.status == "入金確認" # 紐づく注文ステータスが「入金確認」に更新されていたら @ordered_items.update_all(making_status: "製作待ち") # 制作ステータスを全商品「製作待ち」に更新 end redirect_to admin_order_path(@order) end private def order_params params.require(:order).permit(:status) end end orders/show.html.erb <tr> <th>注文ステータス</th> <td> <%= form_with model: @order, url: admin_order_path,local:true, method: :patch do |f| %> <%= f.select :status, Order.statuses.keys %> #status: {"入金待ち":0,"入金確認":1, "製作中":2, "発送準備中":3, "発送済み":4} <%= f.submit '更新' ,class: "btn btn-success btn-sm" %> <% end %> </td> </tr> : <table> <tr> <th>商品名</th> <th>単価(税込)</th> <th>個数</th> <th>小計</th> <th>制作ステータス</th> <th></th> </tr> <% total=0 %> <% @ordered_items.each do |ordered_item|%> #注文商品一覧が出る <tr> <td><%= ordered_item.item.name %></td> #商品名 <td><%= ordered_item.tax_included_price %></td> #税込価格 <td><%= ordered_item.quantity %></td> #数量 <td><%= total += ordered_item.tax_included_price*ordered_item.quantity %></td> #小計 <td> <%= form_with model: ordered_item, url: admin_ordered_item_path(ordered_item), local:true,method: :patch do |f| %> <%= f.select :making_status, OrderedItem.making_statuses.keys %> #{"製作不可":0,"製作待ち":1,"製作中":2,"製作終了":3} <%= f.submit "更新", class: "btn btn-success btn-sm" %> <% end %> </td> </tr> <%end%> </table> OrderedItemsController class Admin::OrderedItemsController < ApplicationController before_action :authenticate_admin! # making_status => 制作ステータス(OrderedItemsモデルのカラム) # status => 注文ステータス(Orderモデルのカラム) def update @ordered_item = OrderedItem.find(params[:id]) # making_statusボタンどれ押されたか特定 @order = @ordered_item.order # 注文商品から注文テーブルの呼び出し(何度も呼び出すのは処理が増える為) @ordered_item.update(ordered_item_params) # 製作ステータスの更新にはmaking_statusのデータ取得を必要と定義 if @ordered_item.making_status == "製作中" # 紐づく商品のmaking_statusがひとつでも「製作中」になったら @order.update(status: "製作中") # 注文ステータスを「製作中」に"自動更新" # 「注文商品の個数」、「making_statusが「製作終了」の商品数」カウント # 数が一致すれば、全商品のmaking_statusが「製作終了」となる # 前提の補足: パティシエは商品が制作終了するたびにその商品のmaking_statusを3に"手動更新"していく elsif @order.ordered_items.count == @order.ordered_items.where(making_status: "製作終了").count @order.update(status: "発送準備中") #注文ステータスを「発送準備中」に"自動更新" end redirect_to admin_order_path(@ordered_item.order) #注文詳細に遷移 end private def ordered_item_params params.require(:ordered_item).permit(:making_status) end end
- 投稿日:2021-08-29T08:03:41+09:00
【Ruby on Rails】Rate.jsを使ったときに星が増えるバグの解消法(jQuery)
対象者 レビュー評価を実装している方 星が増える不具合を実装している方 目的 星が増える不具合を解消して、星を5個で収める 実際の手順と実例 1.結論(解決策) scriptタグに$('').empty();を追加すればOK! 下記例です div.rateの部分は人によって違うと思います。 <script> $(document).on("turbolinks:load", function() { $('div.rate').empty(); #これを追加しました $('div.rate').raty({ size: 14, starOff: '<%= asset_path('star-off.png') %>', starOn : '<%= asset_path('star-on.png') %>', starHalf: '<%= asset_path('star-half.png') %>', half: true, readOnly: true, }); }); </script> 2.原因 星の表示されてるページから 他のページへ遷移 その後、再度星が表示されているページへ この間に星が削除されていない(保存されたまま??)のが原因みたいです。 3..emptyメソッドについて 下記記事によると jQueryオブジェクトで指定した要素の内容(子孫要素やテキストなど)を削除します。jQueryオブジェクトで指定した要素は削除しません。 とありました。 つまり、 ページ再読み込み時に まず子要素である"div.rate"を.emptyで削除 その後、.rateで星を表示 となって星5個の表示が保たれています。 参照 emptyメソッド - 初心者向けのjQuery入門講座 投稿者コメント 今回は「どのような流れでそうなったか」という点に重きを置いて記事を書いてみました。次回以降の投稿でもどのような手順でそうなったかを曖昧にさせないように理解しながら書いていきたいと思います。 星が増えるとなんか笑っちゃいますよね。笑 My Profile プログラミング学習歴3ヶ月目のアカウントです! プログラミングスクールで学んだ内容や自分が躓いた箇所等のアウトプットの為に発信しています。 また、プログラミング初学者の方にわかりやすく、簡潔にまとめて情報共有できればと考えています。 もし、投稿した記事の中に誤り等ございましたら、コメント欄でご教授いただけると幸いです。
- 投稿日:2021-08-29T00:35:01+09:00
例外処理について基本
概要 例外処理についての基本的なことについてまとめです 実務において例外処理行わないことはないので、基礎的な部分を記載しておきます。 基礎構文 begin # エラーが起こりうる処理 rescue # エラーが起きた時にどのような挙動をしてほしいか記述 end エラーを補足したい場合は上記のように begin ~ rescue の間に処理を挟む。 エラーが起きると rescue ~ end の間に処理が移る。 ちなみにメソッド単位なら begin は省略可能 rescue は複数書ける begin # エラーが起こりうる処理 rescue NomethodError puts 'NomethodError' rescue RuntimeError puts 'RuntimeError' rescue StandardError puts 'StandardError' end 特定のエラー起こしたいぜ! つ raise begin raise RuntimeError rescue NomethodError puts 'NomethodError' rescue RuntimeError puts 'RuntimeError' # => ここに処理が飛ぶ rescue StandardError puts 'StandardError' end raise で特定のエラーを引き起こすことができる ここでは raise RuntimeError とすることで rescue RuntimeError に処理が飛ぶ 特定のエラーを作りたいぜ! つ エラーを継承 class FugaError < StandardError; end begin raise FugaError rescue FugaError puts "FugaError" end # 出力 # FugaError StandardError を継承して特定のエラーを作り出すことができる rescue の処理の順番に注意すること class FugaError < StandardError; end class HogeError < FugaError; end begin raise HogeError rescue FugaError puts "FugaError" rescue HogeError puts "HogeError" end # 出力 # FugaError エラーが参照されるのは親から。 StandardError > FugaError > HogeError の順で継承している。 FugaError が HogeError より上に記載されており、かつ HogeError は FugaError の子クラスなので先に参照されてしまう。 HogeError 出したければ逆にすること class FugaError < StandardError; end class HogeError < FugaError; end begin raise HogeError rescue HogeError puts "HogeError" rescue FugaError puts "FugaError" end # 出力 # HogeError ちなみに raise のみ記載しておくと StandardError として補足される begin raise rescue => e p e.class #=> StandardClass end 番外編:例外処理の定義の仕方色々 #パターン1 通常 # begin # raise StopIteration # rescue KeyError => e # p 'きーえらー' # rescue StopIteration => e # p 'すとっぷいてれーしょん' # end # パターン2 横につなげる # begin # raise NoMethodError # rescue KeyError, StopIteration, NoMethodError # p 'きーえらーとストップイテレーションとのーめそっどえらー' # rescue StopIteration => e # p 'すとっぷいてれーしょん' # end # パターン3 スプラッシュ演算子 begin raise NoMethodError rescue *[KeyError, StopIteration, NoMethodError] p 'きーえらーとストップイテレーションとのーめそっどえらー' rescue StopIteration => e p 'すとっぷいてれーしょん' end
- 投稿日:2021-08-29T00:09:31+09:00
[Rails]Mysql2::Error:Unknown column(フォロー機能)
はじめに 本記事では、フォロー機能導入中に起きたエラーの 私が行った対処法を記述します。 昨日もフォロー機能を実装しており、ようやく終わりといった感じです。 前回の記事になります。 エラー内容 ActiveRecord::StatementInvalid in ~ Mysql2::Error:Unknown column 'relationships.user_id' in 'where clause' というエラーになりました。 clause=句 という意味のようです。(Google先生) 内容としては、'relationships.user_id'というカラムは知らんぜ。 と言っています。 ?? コード routes.rb Rails.application.routes.draw do devise_for :users root to: 'foods#index' resources :foods do collection do get :search end resource :likes, only: [:create, :destroy] end resources :users do resources :relationships, only: [:create, :destroy] get :followings, on: :member get :followers, on: :member end end フォロー機能なので、Userモデル user.rb class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :foods, dependent: :destroy has_many :likes, dependent: :destroy has_many :liked_foods, through: :likes, source: :food has_many :relationships has_many :followings, through: :relationships, source: :follower has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: :follower_id has_many :followers, through: :reverse_of_relationships, source: :following has_one_attached :image def already_liked?(food) self.likes.exists?(food_id: food) end def is_followed_by?(user) reverse_of_relationships.find_by(following_id: user.id).present? end end 該当箇所のみ <div class="mypage-follow-info"> <h2 class="mypage-follow">フォロー</h2> <%= link_to @user.followings.count, followings_user_path(@user) %> <h2 class="mypage-follow">フォロワー</h2> <%= link_to @user.followers.count, followers_user_path(@user) %> </div> 原因究明 よくわからないので、とりあえず、怒られているところを丸々消してみました。 コメントアウトしてみた <div class="mypage-follow-info"> <h2 class="mypage-follow">フォロー</h2> <%#= link_to @user.followings.count, followings_user_path(@user) %> <h2 class="mypage-follow">フォロワー</h2> <%= link_to @user.followers.count, followers_user_path(@user) %> </div> すると、ブラウザがコメントアウトしたところ抜いて無事に表示されました。 <%= link_to @user.followers.count, followers_user_path(@user) %> ここは表示されたので、 原因は <%= link_to @user.followings.count, followings_user_path(@user) %> であることが判明。 形は、followersでは問題なかったので、 モデルかコントローラーではないかと探しました。 結果 モデルでした。 user.rb class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :foods, dependent: :destroy has_many :likes, dependent: :destroy has_many :liked_foods, through: :likes, source: :food has_many :relationships ←ここ!! has_many :followings, through: :relationships, source: :follower has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: :follower_id has_many :followers, through: :reverse_of_relationships, source: :following has_one_attached :image def already_liked?(food) self.likes.exists?(food_id: food) end def is_followed_by?(user) reverse_of_relationships.find_by(following_id: user.id).present? end end user.rb class User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable has_many :foods, dependent: :destroy has_many :likes, dependent: :destroy has_many :liked_foods, through: :likes, source: :food has_many :relationships, foreign_key: :following_id has_many :followings, through: :relationships, source: :follower has_many :reverse_of_relationships, class_name: 'Relationship', foreign_key: :follower_id has_many :followers, through: :reverse_of_relationships, source: :following has_one_attached :image def already_liked?(food) self.likes.exists?(food_id: food) end def is_followed_by?(user) reverse_of_relationships.find_by(following_id: user.id).present? end end has_many :relationships foreign_key: :following_idが記述できていませんでした。 外部キー (foreign key) 【SQL入門】外部キーとは?主キーとの関係や作成方法について解説 外部キー(FOREIGN KEY)とは、 関連したテーブル間を結ぶために設定する列のことで、データの整合性をデータベースに保証させるために利用します。 設定し忘れていたので、followingが宙に浮いていた感じですね。 終わりに オリジナルアプリにDockerを取り扱いたいと思ったため、 Dockerの学習を始めました。 ちょっと難しいく頭が混乱しますが、 仮想という言葉が好きらしく、興味は沸いてます。 YouTubeでこの方と一緒に同じことをしていました。 よければ見てみてください。 Docker超入門講座 合併版 | ゼロから実践する4時間のフルコース 今回の参考記事です。 Mysql2::Error: Unknown column 'favorites.true' in 'where clause'の対処について 明日は日曜日ですが、 引き続きDockerの学習も頑張ります! どこかのタイミングでDockerの記事も書きたい!