20210906のRailsに関する記事は23件です。

Rails6 画像のプレビュー機能 + バリデーション失敗時での画像表示

実装内容 carrierwaveを使用して画像アップロード機能を実装し、「よし!これで画像の投稿機能の完成だ!!」と思っていたのですが、何か物足りなさを感じて画像投稿機能に関する記事をいろいろと調べていた所、プレビュー機能というワクワク機能に出会ったので今回はその紹介 + バリデーション失敗時でも画像を表示する方法も紹介しております。 処理全体の流れ プレビュー機能の実装 バリデーション失敗時の画像表示方法 完成形はこちら プレビュー機能 バリデーション失敗時 環境 macOS Big Sur 11.2.3 ruby: 2.7.2 rails: 6.1.3 jQuery テンプレートエンジン: slim レイアウト: bootstrap4 前提 jquery,deviseの導入は省いております。 carrierwaveの導入と実装は省いております。 viewファイル ※Rail6ではform_forは非推奨ではありますが、上記に示した環境では問題なく動作しております。 app/views/devise/registrations/edit.html.slim = form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| = render 'layouts/error_messages', model: f.object ・ ・ ・ .form-group .d-block = f.label :profile_image - if f.object.profile_image? = image_tag f.object.profile_image.to_s, id: 'profile_img_prev', class: 'user-information__img' .form-group.mt-2 = f.check_box :remove_profile_image, class: 'mr-2' = f.label :remove_profile_image = f.hidden_field :profile_image_cache - else div = image_tag 'no_profile_img.png', id: 'profile_img_prev', class: 'user-information__img' = f.hidden_field :profile_image_cache br label.upload-btn = 'ファイルを選択' = f.file_field :profile_image, id: 'file_btn' jQueryの操作ではid属性を使ってセレクタの指定を行なっていきます。 今回は、画像を表示するimage_tagにid: 'profile_img_prev'を付与し、ファイル選択ボタンにid: 'file_btn'を付与しています。 プレビュー機能の実装 webAPIのFileReaderというものを扱っています。 こちらを使うことで画像を非同期で読み取ることができます。 app/javascript/packs/application.js require("shared/image_preview"); app/javascript/shared/image_preview.js $(function () { $('#file_btn').on('change', function (e) { var reader = new FileReader(); reader.onload = function (e) { $('#profile_img_prev').attr('src', e.target.result); } reader.readAsDataURL(e.target.files[0]); }); }); それでは順を追って説明いたします。 app/javascript/shared/image_preview.js $('#file_btn').on('change', function (e) { 上記コードは、id:file_btnを付与したファイル選択ボタンが押され、画像が選択されるとイベントが発生し、スコープ内の処理内容を実行してくれます。 (e)はイベントオブジェクトのことで、発生したイベントに関する様々な情報(データ)が入っており、今回の場合、#file_btnのデータが入っています。 app/javascript/shared/image_preview.js var reader = new FileReader(); // FileReaderオブジェクトの生成 reader.onload = function (e) { $('#profile_img_prev').attr('src', e.target.result); } 次に、処理内容について説明します。 上記では、FileReaderオブジェクトを生成し、この後に説明するreadAsDataURLで画像の読み込みが完了したらreader.onloadでイベントが発生し、スコープ内の$('#profile_img_prev').attr('src', e.target.result);が実行され、該当idを付与したimage_tagのsrc値に先ほど取得した画像データを反映させています。 app/javascript/shared/image_preview.js reader.readAsDataURL(e.target.files[0]); 最後に、先ほどの説明でも登場したreadAsDataURLで指定したFileオブジェクトを読み込んでいます。 また、ファイルを配列(複数)で受け取るようになっているため、最初のファイルのみ取得するようfiles[0]としています。 ここで読み込むが完了すると、先ほどのonloadメソッドが実行され、非同期で画像が表示されるようになります。 以上でプレビュー機能の完成です! バリデーションエラー時の画像の保持方法 ここでは、キャッシュ機能を使用してバリデーションの失敗時でもアップロードした画像ファイルのデータをもとに画像を表示する方法を説明しています。 app/views/devise/registrations/edit.html.slim - if f.object.profile_image? = image_tag f.object.profile_image.to_s, id: 'profile_img_prev', class: 'user-information__img' .form-group.mt-2 = f.check_box :remove_profile_image, class: 'mr-2' # 削除機能 = f.label :remove_profile_image # ラベル = f.hidden_field :profile_image_cache # キャッシュ機能 キャッシュ機能を使用するためには下記の2点を記述する必要があります! 「(カラム名)_cache」という名前のhidden_fieldの追加 ストロングパラメーターに「(カラム名)_cache」の記述 そうすることで、f.object.profile_image.to_sでアップロードした画像ファイルのデータを取得することができ、バリデーション失敗時でも画像を表示することができます。 最後に 今回プレビュー機能を実装してみての感想ですが、「javascript、、ムズい」です笑 でも記事にすることで再度実装内容を見直すことで多少は理解できたのかなと感じています! 今後も色んなことに挑戦しながら知識を付けていきたいと思います。 間違っている箇所や分かりづらい箇所が多々あるかと思います。 その際は、気軽にコメントいただけれると幸いです。 最後までご覧いただき、ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails tutorial6章進めた時の覚書

この章では何するの? 6章ではユーザー登録機能の中でも、一番重要なステップであるユーザー用のデータモデルの作成と、データを保存する手段の確保について学びます。 6~12章でRailsのログインと認証システムをひととおり開発します。 ユーザー登録で初めにやることは、それらの情報を保存するためのデータ構造を作成すること データベースとやりとりをするデフォルトのRailsライブラリはActive Recordと呼ばれる Railsにはマイグレーション(Migration)という機能がある。 データの定義をRubyで記述することができ、SQLのDDL(Data Definition Language)を新たに学ぶ必要がない。 このマイグレーションはDjangoと同じだよな?? データをデータベースに登録する作業という認識であっているかね。 name, email 属性をつけたUserモデルを作るコマンド $ rails generate model User name:string email:string String型のname属性、String型のemail属性を持つUserモデルを制作している コントローラ名には複数形を使い、モデル名には単数形を用いるという慣習を頭に入れる。 コントローラはUsers モデルはUser rails genarateコマンドの結果の一つとして、マイグレーションファイルが生成される マイグレーションは、データベースの構造をインクリメンタルに変更する手段を提供し、要求が変更された場合にデータモデルを適合させることができる マイグレーション自体は、データベースに与える変更を定義したchangeメソッドの集まり。 changeメソッドはcreate_tableというRailsのメソッドを呼び、ユーザーを保存するためのテーブルをデータベースに作成 t.timestampsは、特別なコマンドで、created_atとupdated_atという2つの「マジックカラム(Magic Columns)」を作成する。 マイグレーションの実行 $ rails db:migrate migrateコマンド叩くと、db/development.sqlite3ファイルが生成される これはSQLiteの実体 マイグレーションを戻すことを ロールバックと呼ぶ。Railsでは、下記叩く $ rails db:rollback コンソールをサンドボックで起動する。 そのセッションで行ったデータベースへの変更をコンソールの終了時全てロールバックしてくれる $ rails console --sandbox User.newを引数なしで呼んだ場合は、すべての属性がnilのオブジェクトを返す データベースにUserオブジェクトを保存するためには、userオブジェクトからsaveメソッドを呼び出す必要がある #ユーザーオブジェクトの生成(全てnilで設定される) >> User.new #ユーザーオブジェクトの値を設定 >> user = User.new(name: "Michael Hartl", email: "michael@example.com") #データベースにUserオブジェクトを保存 >> user.save #モデルの生成と保存を同時に行う >> User.create(name: "Gerge", email: "krooby@example.com") #user.createはユーザーオブジェクト自身を返す。変数に代入もできる >> foo = User.create(name: "Foo", email: "foo@example.com") ユーザーオブジェクトの検索 >> User.fidn(1) 特定の属性でユーザー検索 >> User.find_by(email: "michael@example.com") #データベースの最初のユーザーを返す >> User.first #データベースのすべてのUserオブジェクトを返す >> User.all ユーザーオブジェクトの更新 更新後、saveを実行する。 >> user.email = "changemail@example.com" >> user.save これは、何番目のユーザーのemailを変更しているんだ? >> user.email 更新方法もう一つ updateを使う。 >> user.update(name: "seno shu", email: "seno@example.com") 例えば下記を叩いたときに、idが幾つのユーザーとか指定していないが、更新されるのは最初のユーザーの情報という認識であっているのかな? >> user.email = "foo@bar.com" あーなるほど。更新する際に、 user.email をしてしたら、userに代入していたユーザーの情報が更新されるんだ。つまりそもそも下記の認識がそもそも間違えていて、 user に代入している値としっかり指定しているじゃないか。 idが幾つのユーザーとか指定していないが 特定の属性のみ更新したい場合は、 update_attributeを利用する。 >> user.update_attribute(:name, "Upload Name") => true >> user.name => "Upload Name" assertメソッド テスト用のメソッド。第一引数がtrueであることを期待する。 第二引数は期待が裏切られてたときに出力するメッセージ 例えばemailをテストする時に下記のように記述する もし、userがvalidじゃないときに、どのメールアドレスでテストが失敗したのかを特定できる。 user_test.rb assert @user.valid?, "#{valid_address.inspect} should be valid" セキュアなパスワードの追加 パスワードをハッシュ化する。 Rubyのデータ構造であるハッシュとは別の話で、今回の「ハッシュ化」は構造の事ではなく、ハッシュ関数を使って、入力されたデータを元に戻せない(不可逆な)データにする処理を指す。 セキュアなパスワードの実装は、モデルにhas_secure_passwordメソッドを利用することで殆ど可能。 セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。 2つのペアの仮想的な属性(passwordとpassword_confirmation)が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される19 。 authenticateメソッドが使えるようになる(引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド)。 has_secure_passwordメソッドを使うためにはモデル内にpassword_digest属性を持っている必要がある。 本番環境でUserモデルを使うためには、heroku runコマンドを使ってHeroku上でもマイグレーションを走らせる必要がある。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLチューニングについて知識まとめ

はじめに SQLをチューニングする業務を初めて任されたので、その際に学んだことを復習がてらメモ。初心者なので間違っているところなどありましたらご指摘いただけるととてもありがたいです! 前提 newRelic を使って種々のリソースの応答時間を監視している。そこでレスポンスに時間がかかっていたコントローラから、重いクエリを特定&改善する、というタスクだった。 原因となるSQLの特定 まずここが思っていたより手間取った。AWSのログと照らし合わせながら、コンソールでクエリを叩いて同じ出力になっている場所を確かめる、という方法で探した。 ActiveRecord のexplainを使いながら、ログを確認 terminal AreasArticle.where(parent_id: 401450).explain AreasArticle Load (164.4ms) SELECT `areas_articles`.* FROM `areas_articles` WHERE `areas_articles`.`parent_id` = 401450 => EXPLAIN for: SELECT `areas_articles`.* FROM `areas_articles` WHERE `areas_articles`.`parent_id` = 401450 +----+-------------+----------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+----------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ | 1 | SIMPLE | areas_articles | NULL | ALL | NULL | NULL | NULL | NULL | 388203 | 10.0 | Using where | +----+-------------+----------------+------------+------+---------------+------+---------+------+--------+----------+-------------+ 1 row in set (0.00 sec) こんな感じで出力される。 explain の読み方についてはこちらの記事が非常にわかりやすかったのでおすすめです 他に、rails のログも見ると良い。explain は複数テーブルの読み込みを一気に行ったとき(joinなど)に、読み込みにかかった時間を表示してくれなかった。(僕が気づいてないだけかも?) 根本的にSQLの知識が足りてなく、クエリそのものが何をしているかいまいち掴めてなかったのも特定しづらかった原因。 explain による分析 詳しいことは上記記事を見るのが一番わかりやすいが、要点だけまとめておく。 主に見る場所 select_type → type, table → key → extra みたいな順で見ていくのが良さそう。 select_type はサブクエリの有無などクエリのタイプに関するもの。まず初めにここを見る。SIMPLE以外になってる場合はなにかしら改善の余地がある可能性高い。。。? tabel を見ると、どのテーブルを読み込んだかがわかる。無駄に読み込んでいるテーブルや、joinの必要がないテーブルなどがないか確認する。 ただし、joinが無駄かどうかなどは周辺のコードを読まないとわからないのでなかなか難しい。クエリそのものを変えるのはそこそこのコスパがかかる。 次に type を見る。ここはデータをとってくる方式を記している。ALL、INDEX が入ってたら要注意。index を貼ることで改善できる可能性がある。 インデックスに関しては貼るだけで速度改善になりうるのでコスパはいい。ただし、無闇矢鱈とインデックスを貼ると、データをcreateするときに重くなる&オプティマイザが判断を誤って逆にクエリが遅くなる、といった弊害がありうるので、十分に吟味する、 一般に、中間テーブルなどよく検索に使われるものには貼っといて損がないことが多そう。 スロークエリの原因となるもの 種々の要因が絡まっていることもあり、特定が難しいが、調べた限りで出てきたものを簡単にまとめる 無駄なクエリの発行 これはシンプル。Rails のようにSQLをあまり意識しなくてよいフレームワークを使っている場合、無駄なクエリが生じている場合がある。今回生じていたものとして、includes の使用が挙げられる。 includes は、Railsがeager_loadとpre_load をよしなに振り分けてくれるメソッドだが、クエリによってはボトルネックになる。 以下記事がわかりやすかった。 他にも、必要のないjoinをしていないか?など観点は色々あるが、クエリそのものを書き換える変更は、当然ながら取得されるデータに違いがないかの確認が必要になるので、実装コストがかかる。一番に見直すのはincludes で良さそうか? インデックスが貼られていない そのまま。調べればたくさん出てくるので割愛。 オプティマイザの誤判断 発行するクエリを判断しているのはDBのオプティマイザで、これはDBに蓄えられた統計情報から最適なクエリを判断している。ゆえに、統計情報の更新タイミング付近などでは変なクエリが発行されてしまうことがあるらしい。 DBの設定にもよるが、軽く時間を置いてからもう一度クエリを発行すると症状がよくなっていることなどもある。 本番環境とテスト環境でのDBのデータ数の違い 上に少し被るが、オプティマイザの判断はDBに入っているデータの数にも左右される。従って、できる限り本番に近いDBの状態でクエリの分析を行うのが望ましい。テスト環境に本番と同じデータが入っているか?を気にしておく。入っていないならば本番環境のクエリで実行する必要がある。 おわりに また新しい知識が増えたら追記するかもしれません。とりあえずチューニングは奥が深いんだなぁとわかりました。まずSQLの勉強しよう...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-compose + rails + webpack-dev-serverで、webpack-containerが立ち上がらない

TL;DR Webpacker 5.x.を使う場合は、webpacker-dev-serverは3.x.以下にしなさい 経緯 これからは、Sprocketsではなく、webpackerが主流になるらしい。 ならば、早めに使えるようになっておこう。 参考「Railsガイド WebpackerがSprocketsと異なる理由」 https://railsguides.jp/webpacker.html#webpacker%E3%81%8Csprockets%E3%81%A8%E7%95%B0%E3%81%AA%E3%82%8B%E7%90%86%E7%94%B1 「Rails: webpack(er)に乗り換える25の理由(翻訳)」 https://techracho.bpsinc.jp/hachi8833/2020_04_10/90859 早速、railsのアプリにて、webpackを使いながら docker-composeを用いて、app用/db用/webpack-dev-server用の3コンテナを立ち上げる。 参考「dockerでwebpackerが遅い問題を改善する()」 https://qiita.com/MoguraStore/items/ca80c99c5542898ec3b5 docker-compose.yml version: '3' services: db: image: mysql:8.0 command: --default-authentication-plugin=mysql_native_password volumes: - mysql_data:/var/lib/mysql environment: MYSQL_ROOT_PASSWORD: password web: &app build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - ./src:/app environment: WEBPACKER_DEV_SERVER_HOST: webpacker ports: - "3000:3000" depends_on: - db webpacker: <<: *app depends_on: - web environment: - NODE_ENV=development - RAILS_ENV=development - WEBPACKER_DEV_SERVER_HOST=0.0.0.0 command: ./bin/webpack-dev-server ports: - "3035:3035" volumes: mysql_data: package.jsonはこんな感じ。 (初めて、webpacker導入を試みたので、いろいろ雑ですが…。) package.json {"name": "app", "private": true, "dependencies": { "@popperjs/core": "^2.10.1", "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "5.4.2", "bootstrap": "^5.1.0", "jquery": "^3.6.0", "popper.js": "^1.16.1", "turbolinks": "^5.2.0", "webpack": "^4.46.0", "webpack-cli": "^3.3.12" }, "version": "0.1.0", "devDependencies": { "webpack-dev-server": "^4.0.0" } } エラー発生…。 docker-compose upすると、こんなエラーが。 webpacker_1 | The command moved into a separate package: @webpack-cli/serve webpacker_1 | Would you like to install serve? (That will run yarn add -D @webpack-cli/serve) (yes/NO) じゃあ、@webpack-cli/serveとやらを追加すればいいのね、と。 yarn add -D @webpack-cli/serve package.json {"name": "app", "private": true, "dependencies": { "@popperjs/core": "^2.10.1", "@rails/actioncable": "^6.0.0", "@rails/activestorage": "^6.0.0", "@rails/ujs": "^6.0.0", "@rails/webpacker": "5.4.2", "bootstrap": "^5.1.0", "jquery": "^3.6.0", "popper.js": "^1.16.1", "turbolinks": "^5.2.0", "webpack": "^4.46.0", "webpack-cli": "^3.3.12" }, "version": "0.1.0", "devDependencies": { "@webpack-cli/serve": "^1.5.2",  #追加 "webpack-dev-server": "^4.0.0" } } docker-compose upすると、こんなエラーが。 webpacker_1 | /app/node_modules/webpack-cli/bin/utils/prompt-command.js:46 webpacker_1 | return func(...args); webpacker_1 | ^ webpacker_1 | webpacker_1 | TypeError: Class constructor ServeCommand cannot be invoked without 'new' webpacker_1 | at runWhenInstalled (/app/node_modules/webpack-cli/bin/utils/prompt-command.js:46:9) webpacker_1 | at promptForInstallation (/app/node_modules/webpack-cli/bin/utils/prompt-command.js:140:10) webpacker_1 | at /app/node_modules/webpack-cli/bin/cli.js:32:43 webpacker_1 | at Object.<anonymous> (/app/node_modules/webpack-cli/bin/cli.js:366:3) webpacker_1 | at Module._compile (internal/modules/cjs/loader.js:999:30) webpacker_1 | at Object.Module._extensions..js (internal/modules/cjs/loader.js:1027:10) webpacker_1 | at Module.load (internal/modules/cjs/loader.js:863:32) webpacker_1 | at Function.Module._load (internal/modules/cjs/loader.js:708:14) webpacker_1 | at Module.require (internal/modules/cjs/loader.js:887:19) webpacker_1 | at require (internal/modules/cjs/helpers.js:74:18) kyodokoza_webpacker_1 exited with code 1 docker-composeしても、app用/db用コンテナは立ち上がるけど、 webpack-dev-server用コンテナが立ち上がらない…。 ここから、いろいろ試しても全く解決せず、、、。 数時間無駄にしました、、、。 どうやって解決したか? railsの公式githubのissueにて以下のコメントを発見。 「webpack-dev-server is not compatible with Webpacker 5.x. You'll need Webpacker 6.0 for compatibility. Otherwise go back to webpacker-dev-server 3.x.」 (https://github.com/rails/rails/issues/43062) つまり、 Webpacker 5.x.を使う場合は、webpacker-dev-serverは3.x.以下にしなさい とのこと。。 また、同じissue内に「この構成だったらうまくいったよ!」とのコメントも。 "dependencies": { ... "@rails/webpacker": "5.4.2", "webpack": "^4.46.0", "webpack-cli": "^3.3.12" }, "devDependencies": { "webpack-dev-server": "3.11.2" } yarn add したりremoveしたりして、この構成にしたらうまくいきました。 あとがき 振り返ってみると、すごく灯台下暗し的な見落としでした…。 どなたかの参考になればうれしいです。 ご指摘の点などあれば、お気軽にコメントいただけますと幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] ネストについて

初めに 記事の対象者 ・routes.rbで詰まってしまうとき 環境 ・Macbook Air (Retina, 13-inch,2019) ・プロセッサ 1.6GHz デュアルコアIntel Core i5 ・メモリ 8GB 2133 Mhz LPDDR3 ・MacOS Big Sur バージョン 11.5.2 ネストってなんだ?(通販サイトで考えてみよう) 自分のイメージは ・resources君とresourcesさんが手を繋いでいると情報の共有ができるけどしていないとできない ・共有方法は。paramとして共有される ・繋ぐ方法は、do~endの間に他のresourcesを配置 ・手をつないでも、上下関係が生まれる。(入れ子構造) 通販サイトのDB(itemとpayment) ネストをしない状態(items=商品、payments=送り先、users=ユーザ) resources :items resources :payments, only:[:create] resources :users 上記のように設定すると、itemsとpaymentsは別々のものであり、結びついていない状態となる *これだと、どの商品をどこに送ればいいかわからない ネストしている状態(items=商品、payments=送り先、users=ユーザ) resources :items do resources :payments end resources : users 上記のように,do~endを使ってresources同士を紐付けさせた状態となる *どの商品をどこに送ればいいかわかるようになる。 items_controllerのアクション時にparams:idにpaymentのレコードが一緒についてくるようになる! 最後に ・paramsってなんだろう?についてのリンクはこちらに(製作中) ・ここまで書いて、もっとうまく説明する勉強しなきゃなってつくづく思う(汗) ・ここまで、読んでくれましてありがとうございます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】Uncaught TypeError: Cannot read property ‘プロパティ名’ of nullのエラーについて

すごく初歩的なエラーで1時間くらい時間を使ってしまったので 備忘録として残したいと思います。 前提 学習のためにメルカリのようなアプリを作成中です。 JavaScriptの非同期通信を用いて、販売価格を入力すれば 手数料と、入力した価格から手数料を差し引いた金額が自動で表示される機能を実装しようと下記のようにコードを記述しました。 application.js require("@rails/ujs").start() require("@rails/activestorage").start() require("channels") require("../item_price"); items/new.html.erb <%# 販売価格 %> <div class="sell-price"> <div class="weight-bold-text question-text"> <span>販売価格<br>(¥300〜9,999,999)</span> <a class="question" href="#">?</a> </div> <div> <div class="price-content"> <div class="price-text"> <span>価格</span> <span class="indispensable">必須</span> </div> <span class="sell-yen">¥</span> <%= f.text_field :price, class:"price-input", id:"item-price", placeholder:"例)300" %> </div> <div class="price-content"> <span>販売手数料 (10%)</span> <span> <span id='add-tax-price'></span>円 </span> </div> <div class="price-content"> <span>販売利益</span> <span> <span id='profit'></span>円 </div> </span> </div> </div> <%# /販売価格 %> layouts/application.html.erb <!DOCTYPE html> <html> <head> <title>Furima</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <script type="text/javascript" src="https://js.pay.jp/v1/"></script> <%= stylesheet_link_tag 'application', media: 'all'%> <%= javascript_pack_tag 'application' %> </head> <body> <%= yield %> </body> </html> item_price.js const priceInput = document.getElementById("item-price"); priceInput.addEventListener("input", () => { const inputValue = priceInput.value; const addTaxDom = document.getElementById('add-tax-price'); addTaxDom.innerHTML = Math.floor( parseInt(inputValue) * 0.1 ); const profitDom = document.getElementById('profit'); profitDom.innerHTML = Math.floor( parseInt(inputValue) - parseInt(addTaxDom.innerHTML) ); }) 本題 挙動を確認しようと、ブラウザの検証ツールのコンソールで見てみると Uncaught TypeError: Cannot read properties of null (reading 'addEventListener') at Object../app/javascript/item_price.js (item_price.js:3) at __webpack_require__ (bootstrap:19) at Object../app/javascript/packs/application.js (application.js:9) at __webpack_require__ (bootstrap:19) at bootstrap:83 at bootstrap:83 このようなエラーが出ていました。 最初"TypeError"と出ているのでスペルミスかな?と思いましたが、 そうではなく、エラー文しっかり調べてみると 「nullのプロパティからは値を引っ張ってこれないよ」と言われているようです。 ということはビューの記述がおかしいのかと思って調べていると、ある記事に似たような内容で悩んでいる人の記事に答えが載っていました。 HTMLは上から順に解釈されていく仕様になっており、 <head>に記述されている"JavaScriptを読み込む"と言う記述が <body>に記述されている内容にたどり着く前に読み込まれてしまうため、 JavaScriptの処理は<body>に入力してある値を取得する前に実行されてしまいます。 そのためnullとなり、値がないよ〜と言われてしまうわけです。 解決方法 ではどうすれば良いのか。 方法はいくつかあるようですが、今回は下記の記述をJSファイルに追記しました。 item_price.js window.addEventListener('load', function(){ const priceInput = document.getElementById("item-price"); priceInput.addEventListener("input", () => { const inputValue = priceInput.value; const addTaxDom = document.getElementById('add-tax-price'); addTaxDom.innerHTML = Math.floor( parseInt(inputValue) * 0.1 ); const profitDom = document.getElementById('profit'); profitDom.innerHTML = Math.floor( parseInt(inputValue) - parseInt(addTaxDom.innerHTML) ); }) }) window.addEventListener('load', function(){}) を記述することで、最初に情報を全て取得したから以降の処理を行うことができるので、 nullになることなく、意図した挙動を行うことができました。 しっかり勉強したつもりでしたが、基本的なことをすっかり忘れてしまっていたがために起きたエラーでした。 もう一度復習しなければ。 終わり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]resources、アクションについて

はじめに 記事の対象者 ・Railsのroutesで混乱しているヒト 環境 ・Macbook Air (Retina, 13-inch,2019) ・プロセッサ 1.6GHz デュアルコアIntel Core i5 ・メモリ 8GB 2133 Mhz LPDDR3 ・MacOS Big Sur バージョン 11.5.2 記事の目次 1)routesとは? 2)resourcesとは? 3)基本アクション 4)全アクション指定 routesとは? 自分なりにroutesについての説明は下記のリンクにあるよ↓ https://qiita.com/janani/items/1bb869260ce252f18a73 resourcesとは? 上記のリンク先のように説明すると。。。resources=記憶となる。 resources :コントローラ名 routes.rbに記入することによってプラウザから受け取ったアクション(地図)をroutes.rbさんがresources(記憶)にあるcontrollerさんへ案内してくれる。 基本アクションとは indexアクション ・基本的にアプリケーションのユーザが一番最初に見るページ ・他のページへ遷移するためにリンク先を集中して配置 ・通販サイトで例えると、ログインや新規登録、サイトについて、買い物リストなどいろいろなボタンがあるページ newアクション ・resources:controller名内に、ユーザ側からなにか新しいデータを追加させたいときに使う ・例えば、ユーザの識別のためのログイン/新規登録ページ createアクション ・newアクションでユーザが記入したデータを保存するために使う →例えば、新規登録をしたときのデータを保存してログイン時に再利用するとき *newとcreateは基本的にセット* showアクション ・保存されたデータを個別に詳細ページとして表示するときに使う →通販サイトで例えると、商品一覧ページにある商品の詳細ページ editアクション ・保存されたデータを編集するために使う →例えば、ログインするときにパスワードを忘れてしまい変えたいとき updateアクション ・editで編集したデータを保存するために使う →例えば、パスワード変更時のデータを保存してログイン時に再利用するとき *editとupdateは基本的にセット* destroyアクション ・データをまるごと消すときに使う →例えば、ログアウトをしたいときに使う アクションの指定方法 下記のようにresourcesの後ろに、使うアクション名をonly:[:]記入する。 resources :controller名, only: [:アクション名, :アクション名] *全アクションを使うなら下記のように設定すると上記の表の全アクション込みとなる* resources :controller名 *アクションは自分で作れる!* 例えば、トップページ→ ログイン/新規登録(new)→indexの順にページの遷移をしたい場合。 トップページのページへ遷移するためのアクション名をtopとする。 (routes.rb) resources :controller名, only: [:top, :index, :new] (controller.rb) def top end def index end def new end 最後に ここまで読んでくださいましてありがとうございます!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails turbolinksとbootstrapのドロップダウン

turbolinksとbootstrap バージョン Rails6.0.3 bootstrap4.5 今回発生した問題 サイト内にドロップダウンメニューをBootstrapで導入したが、ページ遷移直後にドロップダウンメニューを押しても機能せず、一度サイトを更新し直さないとドロップダウンが機能しないという点。 解決方法 application.js内にあったrequire("turbolinks").start()この記述を消す turbolinksによって少し挙動に影響があったみたいです。 ここを消すことによる弊害が今後発生するかもしれませんが、一旦今日のところは解決です。 ぜひ参考にしてみてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Mac】Docker+rails6+postgreSQLでの環境構築完全版

環境 MacOS Big Sur 11.5.2 ruby 3.0.2 rails 6.1.4 Docker 20.10.8 postgreSQL 13.4 ディレクトリ myapp 全て現時点(2021/09/06)での最新バージョンです。 1.用意するファイル ・Dockerfile ・Gemfile ・Gemfile.lock ・entrypoint.sh ・docker-compose.yml Dockerfile FROM ruby:3.0.2 # yarnパッケージ管理ツールをインストール RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN apt-get update -qq && apt-get install -y nodejs postgresql-client RUN mkdir /myapp WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"] ここでyarnをインストールしておくのがポイントです。しないと後でwebpackerがインストールできずエラーになります。 Gemfile source 'https://rubygems.org' gem 'rails', '~>6' Gemfile.lockは空のままで大丈夫です entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" docker-compose.yml version: '3' services: db: image: postgres volumes: - ./tmp/db:/var/lib/postgresql/data environment: - POSTGRES_PASSWORD=password web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp ports: - "3000:3000" depends_on: - db 2.実行 $ docker-compose run web rails new . --force --no-deps --database=postgresql ↓ $ docker-compose build ここでdatabase.ymlを編集します config/database.yml default: &default adapter: postgresql encoding: unicode host: db username: postgres password: password pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> development: <<: *default database: app_development test: <<: *default database: app_test production: <<: *default database: app_production username: app password: <%= ENV['APP_DATABASE_PASSWORD'] %> パスワードなど設定した後データベースと接続します。 $ docker-compose run web rake db:create ↓ $ docker-compose up 終わったらlocalhost:3000とネットで検索すればこの画面が出てきます。 そしたら環境構築成功です! おわりに 【Mac】Docker+rails6+MySQLでの環境構築完全版 MySQLバージョンも作ったのでぜひ参考にしてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォロー・フォロワー機能

1. Relationshipモデルを作成 $ rails g model Relationship follower_id:integer followed_id:integer # follower_id:フォローするユーザーのid, followed_id:フォローされるユーザーのid $ rails db:migrate # マイグレーション実行 2. アソシエーションを記述 app/models/relationship.rb class Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" # class_name: "User"を定義することでUserテーブルのレコードを参照する end 補足説明:belongs_to :followerでfollowerテーブルのfollower_idを探しにいきます。 ただ、今回はfollowerテーブルは作成していないのと、 参照してもらいたいのはfollower_idに格納されている値と同じuser_idなので class_name: "User"でUserテーブルを参照するように定義します。 3. Userモデルにメソッドを記述 app/models/user.rb class User < ApplicationRecord def follow(user_id) relationships.create(followed_id: user_id)/1 end def unfollow(user_id) relationships.find_by(followed_id: user_id).destroy/2 end def following?(user) followings.include?(user)/3 end end 1.followed_idがuser_idであるRelationshipsモデルを生成して保存します。 2.followed_idがuser_idであるRelationshipsモデルを取得してActiveRecordを使って削除します。 3.userが、Followingsモデルの中に含まれているか判定します。 app/models/user.rb class User < ApplicationRecord has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :followers, through: :reverse_of_relationships, source: :follower # 被フォロー関係を通じて参照→followed_idをフォローしている人 has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy # 【class_name: "Relationship"】は省略可能 has_many :followings, through: :relationships, source: :followed # 与フォロー関係を通じて参照→follower_idをフォローしている人 end 4. Relationshipコントローラを作成・編集 まずはrelationshipsコントローラを作成します。 $ rails g controller relationships relationships_controller.rb class RelationshipsController < ApplicationController before_action :authenticate_user def create following = @current_user.follow(params[:user_id])/1 flash[:notice] = if following.save 'ユーザーをフォローしました' else 'ユーザーのフォローに失敗しました' end redirect_to("/users/#{params[:user_id]}") end def destroy following = @current_user.unfollow(params[:user_id])/2 flash[:notice] = if following.destroy 'ユーザーのフォローを解除しました' else 'ユーザーのフォロー解除に失敗しました' end redirect_to("/users/#{params[:user_id]}") end def followings user = User.find(params[:user_id])/3 @users = user.followings/4 end def followers user = User.find(params[:user_id])/3 @users = user.followers/4 end 1.relationshipsコントローラーのcreateアクションにおいては、先程userモデルで定義したfollowメソッドを使っています。 2.relationshipsコントローラーのdestroyアクションにおいて、先程userモデルで定義したunfollowメソッドを使っています。 3.user = User.find(params[:user_id])で取得したユーザーのidが、 フォローしている もしくは フォローされている ユーザーのid一覧を 取得することができます。 4.@users = user.followings、 @users = user.followersでは、 先程userモデルで定義したアソシエーションを利用します。 引用記事 【Ruby on Rails】フォロー・フォロワー機能の実装 Railsでフォロー機能を作る方法
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ruby on rails6にbootstrapを導入 

ruby on rails にbootstrapを使うときのコマンド rails等のバージョン ruby 2.7.4p191 (2021-07-07 revision a21a3b7d23) [x64-mingw32] Rails 6.1.4.1 yarn add jquery bootstrap popper.js だと最新になると動作しなかったので4.5.3に指定した。 これ yarn add jquery bootstrap@4.5.3 popper.js 完了 config/webpack/environment.jsにコード追加 importやrequireを使わずに$やBootstrapのJavaScriptを使えるようにする const webpack = require('webpack') environment.plugins.append( 'Provide', new webpack.ProvidePlugin({ $: 'jquery/src/jquery', jQuery: 'jquery/src/jquery', Popper: ['popper.js', 'default'] }) ) app/javascript直下にstylesheets/application.scssを作成 stylesheetsというフォルダを作り、application.scssというファイルを作成でOK あとはファイル内に @import "~bootstrap/scss/bootstrap"; と書き込んで完了 Bootstrapを使えるようにする 次にapp/javascript/packs/application.jsにコードを書き込む import 'bootstrap'; import '../stylesheets/application';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】GETとPOSTの違いを検証する

はじめに Railsの勉強中、GETは「情報を受け取る」、POSTは「情報を送る」という認識でコードを書いていました。 ですが実際は、GETを指定しても入力値を送信してDBを更新できますし、 POSTを指定してもredirectやrenderをせずとも対応したページの情報を受け取って表示しています。 同じくRailsを勉強している仲間も疑問に思っていたとのことで、今回GETとPOSTの違いについて検証しました。 同じテーマの記事をいくつか拝見しましたが、トークンについて触れている記事は少ないように見受けられましたので、ぜひ最後までご覧ください。 HTTPリクエストとHTTPレスポンスとは 前提として、HTTPリクエストについて再確認します。 HTTPリクエストとはWebブラウザ(PC上で見ている画面)が、Webサーバー(サイトページの情報が存在するコンピュータ)に対して送る要求(リクエスト)です。 この要求に対してWebサーバーが応答(レスポンス)することでPC上で見ている画面に望んでいたサイトページが表示されます。 HTTPメソッドとは HTTPリクエストの際にサーバーに何をしてほしいか指示するものです。 HTTPリクエストではWebサーバーに様々な情報を渡しており、そのなかにリクエストしたいメソッドやURLが含まれます。 これはブラウザの検証ツール(F12で開けます)の「Network」タブを見ることで確認することができます。 検証してみる まずは簡単に新規投稿画面を作成します。 現時点で使用するテーブルは空の状態としてあります。 Console pry(main)> Test.all.count (0.2ms) SELECT COUNT(*) FROM "tests" => 0 POSTメソッドの場合 まずは通常通りPOSTメソッドで実行します。 ⇒redirectやrenderは指定していませんが、問題なくcreateページが表示されています 検証ツールは下図のようになりました。 入力した内容は「FormData」に保存されて送信されているようです。 念のためデータ件数も確認します Console pry(main)> Test.all.count (0.6ms) SELECT COUNT(*) FROM "tests" => 1 GETメソッドの場合 次にform_withでメソッドをGETに指定して実行してみます。 ⇒POST同様、問題なくcreateページが表示されています。  しかしGETではURLにパラメーターがついています。 検証ツールを確認します。 RequestURLにパラメーターのようなものが含まれます。 また、先ほどは「FormData」であったところが「QuerySettingParameters」となっており、トークンも発行されていません。 念のためこちらもデータ件数を確認します。 Console pry(main)> Test.all.count (0.6ms) SELECT COUNT(*) FROM "tests" => 2 発行されたURLを再度送信してみる 先ほどのテストで発行されたURLをそれぞれ別のタブにコピー&ペーストします。 まずはPOSTメソッドで発行されたURL パラメーターが空じゃないかと怒られてしまいました。 本来はHTTPリクエストのFormDataにパラメータが入っているはずですが、ここではURLをコピー&ペーストしただけなので、createアクションのDB更新処理時に値がないよといわれたのです。 エラーが出たため、当然データ件数も変更はありませんでした。 Console pry(main)> Test.all.count (0.6ms) SELECT COUNT(*) FROM "tests" => 2 ※こちらのテストには補足があります。語弊が生まれないようご確認いただくことをお勧めします。1 次にGETメソッドで発行されたURL createのページが表示されました。 データ件数を確認すると、1件増えています。 createアクションがパラメーター値を受け取ってDB更新を行うことを考えれば納得の結果といえます。 Console pry(main)> Test.all.count (0.6ms) SELECT COUNT(*) FROM "tests" => 3 GETとPOSTの違いを考える この検証でわかったことは以下のようになりました。 GETメソッド URLにパラメーター値が含まれるため、再度そのURLにアクセスすると、実行時とまったく同じアクションを行うことができる POSTメソッド URLにパラメーター値は含まれないため、再度そのURLにアクセスしても同じアクションは行われない2 今回はcreateアクションだったためDB更新時にエラーが出ましたが、例えば検索機能で条件をパラメーターで渡す場合、GETであればURLをブックマークをしておけば次に開いたときにも指定した条件で閲覧することができるのに対して、POSTではパラメーターが存在しないため条件の指定がされていない状態になります。 また、最も重要な違いとして検証ツールの中身です。 POSTではauthenticity_tokenが発行されているのに対し、GETでは発行されていません。 ------authenticity_tokenとは------------------------------------------------------------------------------- RailsのCSRF対策の機能です。 CSRF対策とは、外部から悪意を持ったリクエストを送信できないようにするためのものです。 GET以外のメソッドはPOSTやPATCHなど更新系のメソッドであるため、authenticity_tokenを発行し、本人確認を行っています。 GETは本来データを受け取るだけのメソッドなので、必要ないということになります。 --------------------------------------------------------------------------------------------------------------- 加えてPOSTでは「FormData」であったところがGETでは「QuerySettingParameters」となっていることから、POSTは入力情報を1つの集まりとして送っているのに対し、GETではあくまでもクエリを実行するために必要な個々のパラメーターとして渡していると考えることができます。 createアクションでは両方ともパラメーターとして受け取っているためややこしいですが、全くの別物ということですね。 まとめ 上記のことから、GETとPOSTで行われている処理は全くの別物であることがわかりました。 「はじめに」でお話した疑問点については、 GETを指定しても入力値を送信してDBを更新できる ⇒クエリ用に渡したパラメータを使ってコントローラーに記述した更新処理をおこなっているだけ POSTを指定してもredirectやrenderをせずとも対応したページの情報を受け取って表示している ⇒POSTとGETの違いは入力値の受け渡し方法やトークンを発行するかどうかという点になるため、POSTの中でredirectやrenderを指定しなくてもURLに紐付いた画面は表示される GETでパラメーターを送信すれば、コントローラーで受け取ってデータの更新も可能ですが、CSRF対策の観点や、ブックマークをすると何度も同じ更新がされてしまうことから、望ましくないです。 また、場合によっては個人情報がパラメーターとして残ってしまったり、悪意を持ってパラメーターに渡す値を変更されてしまうなどの問題もありそうです。 POSTで表示をするのも条件検索を行うような場合では不便で、通常のページであってもHTTPリクエストがGETとPOSTを意味を持って区別していることを考えると好ましくないことがわかりました。 おわりに なんとなくリクエストの種類としてGETは受取りでPOSTは送信なんだなと使っていましたが、 内部を詳しく調べると使い分けるのにはきちんとした理由がありました。 検証は時間がかかりましたが、様々な発見があってとても為になる検証でした。 深く考えずにいましたが、理解できるとやっぱり面白いですね! ネット記事を調べるよりも実際の挙動からわかることをまとめた記事になりますので、至らない点がございましたらコメント欄にてご指摘いただけますと幸いです。 補足 このテストではあくまでも「POSTメソッドで発行されたURLをペーストしている」だけなのでリクエスト自体はGETメソッドがリクエストされています。今回はテストの都合上GETとPOSTの両方のルーティングを設定しているためこのような挙動になりますが、本来のサイトのようにPOSTメソッド用のルーティングしか設定していない場合にはルーティングエラーが表示されます ↩ link_toで直接渡す場合はPOSTでもパラメーターが含まれたURLが表示されます。(例えば、「<%= link_to "post",tests_path(pram1: "post"), method: :post %>」ならURLは「...cloud9.us-east-1.amazonaws.com/tests?pram1=post」となる)POSTはフォームの入力値をFormDataという箱に入れて送信しているため、フォーム入力値はURLに含まれませんが、フォーム入力値でないパラメーターについては箱に入れることができないので、GETと同じようにURLに表示されます。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rspec Userモデルの単体テストの手順

テストコードの備忘録として記事を 書かせていただきます!! ①バリデーションの設定を行う ここではバリデーションに関するテストを実装致します。 ②Gemを追加 gemfileに gem 'rspec-rails', '~> 4.0.0'   を追加で記述 group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails', '~> 4.0.0' end group :development, :testというグループの中に記述。 そうすることで、Gemの動作に制限をもたせる。 ③bundle installを実行 ディレクトリの確認を忘れずに bundle install ④Rspecインストール rails g rspec:install ターミナル内で生成されるファイルがこれ create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb ⑤.rspecに設定を追加 この記述を行うとテストコードの結果をターミナル内でわかりやすく表示されるようになる。 --require spec_helper --format documentation ⑥テストコードを記述するファイルの準備 rails g rspec:model user 実行後、create spec/models/user_spec.rb がターミナル内で生成される。、 ⑦eampleを整理する テストコードを書くときは正常系と異常系で分ける。 例→どの時に新規登録ができて、どのようなときにできないのかを考える うまくいくときが正常系で うまくいかないときが異常系。 それを分けるためにはcontextで分ける! require 'rails_helper' describe User do before do @user = FactoryBot.build(:user) end describe 'ユーザー新規登録' do context '新規登録がうまくいくとき' do it "nicknameとemail、passwordとpassword_confirmationが存在すれば登録できる" do end it "nicknameが6文字以下で登録できる" do end it "passwordが6文字以上であれば登録できる" do end end context '新規登録がうまくいかないとき' do it "nicknameが空だと登録できない" do end it "nicknameが7文字以上であれば登録できない" do end it "emailが空では登録できない" do end it "重複したemailが存在する場合登録できない" do end it "passwordが空では登録できない" do end it "passwordが5文字以下であれば登録できない" do end it "passwordが存在してもpassword_confirmationが空では登録できない" do end end end end ↓FactoryBotがランダムでユーザーネームを選んでくれる記述 @user = FactoryBot.build(:user) describeとは、テストコードのグループ分けを行うメソッド 「どの機能に対してのテストを行うか」をdescribeでグループ分けし、その中に各テストコードを記述. describeにつづくdo~endの間に、さらにdescribeメソッドを記述することで、入れ子構造をとることもできる。 describe 'ユーザー新規登録' do itメソッドは、describeメソッド同様に、グループ分けを行うメソッド。 itの場合はより詳細に、「describeメソッドに記述した機能において、どのような状況のテストを行うか」を明記する。 it "nicknameが7文字以上であれば登録できない" do end 次回に続く
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【EC2】EC2にrailsのアプリをデプロイする方法 No.2(デプロイ)

はじめに それでは前回に引き続き、EC2を使ってrailsのアプリをデプロイしたいと思います。 前回はEC2の環境構築までを行いました。 これまでの工程について、まだ実装されていない方はこちらの記事をぜひご覧ください それでは早速デプロイしていきます! 手順3 gitと連携をします EC2インスタンスにアプリケーションのコードを送るためにはGithubを使います。 連携したGithubからEC2へクローンすることでデプロイをします。 そのためにGitHubとの接続設定をします。 ターミナルで下記のコマンドを実行します。 gitの連携 [ec2-user@ip-172-31-23-189 www]$ git clone リポジトリURL リポジトリURLはご自身のGitHubのページで確認することができます。 Githubのアカウント名とPWを入力し、 ダウロードが開始されます remote: Enumerating objects: 298, done. remote: Counting objects: 100% (298/298), done. remote: Compressing objects: 100% (190/190), done. remote: Total 298 (delta 109), reused 274 (delta 86), pack-reused 0 Receiving objects: 100% (298/298), 58.53 KiB | 365.00 KiB/s, done. Resolving deltas: 100% (109/109), done. 完了 これで、EC2にAppがクローンされています。 手順4 アセットファイルをコンパイルします まずはcssやJavaScript、画像ファイルのコンパイルを行います。 ローカル環境ではこの作業は不要でしたが、本番環境では処理速度の関係で自動的に行われないので随時行う必要があります。 下記のコマンドでアセットファイルをコンパイルします。 アセットファイルのコンパイル # クローンしたアプリのディレクトリに移動 [ec2-user@ip-172-31-47-9 ~]$ cd /var/www/レポジトリ名 # アセットファイルのコンパイルを実行 [ec2-user@ip-172-31-47-9 レポジトリ名]$ rails assets:precompile RAILS_ENV=production コンパイルが無事終了すればOKです。 手順5 database.ymlを編集しよう 本番環境でもデータベースを作成する必要があるので、ローカルのconfig/database.ymlの本番環境の定義部分を下記のように編集します。 databaseの値は編集しないでください。 本番環境の編集 production: <<: *default database: アプリ名_production username: root password: <%= ENV['DATABASE_PASSWORD'] %> socket: /var/lib/mysql/mysql.sock ローカルのファイルを編集したのでcommitし、pushします。 そしてEC2インスタンスでpullをしましょう。 手順6 データベースの作成 これでデータベースサーバーに接続することができるようになりました。 次にEC2インスタンス内に今回のアプリで使用するデータベースを作成します。 下記のコマンドを実行します。 データベースの作成 [ec2-user@ip-172-31-47-9 レポジトリ名]$ rails db:create RAILS_ENV=production # 以下のように表示されればOK Created database 'アプリ名_production' 次にデータベースにテーブルを作成します。 テーブルの作成 [ec2-user@ip-172-31-47-9 レポジトリ名]$ rails db:migrate RAILS_ENV=production エラーなくテーブルが作成されればOKです。 終わりに/完成形 以上でデプロイの準備が完成です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails リクエストフォーマットについて

はじめに 先日Rails インクリメンタルサーチという記事を投稿したのですが、作業中ターミナルを見ている中で、一つの勘違いに気づきましたので、記事にまとめてみようと思います。 これまで勘違いしていたのは、Railsには同期的なリクエスト(local:true, HTML)と非同期的なリクエスト(remote:true, JS)の2つのみ存在し、ターミナルのコントローラ#アクション名 as 〇〇 にて判断できるというものでした。 先日の記事にて、dataType:jsonでajax通信を行なった際に、コントローラには下記の出力がありました。 Started GET "/books?key_word=1" for ::1 at 2021-09-05 15:34:00 +0900 Processing by BooksController#index as JSON Parameters: {"key_word"=>"1"} Rendering books/index.json.jbuilder Book Load (0.2ms) SELECT "books".* FROM "books" WHERE (title LIKE('%1%') OR body LIKE('%1%')) ↳ app/views/books/index.json.jbuilder:1 Rendered books/index.json.jbuilder (1.4ms) Completed 200 OK in 13ms (Views: 6.3ms | ActiveRecord: 0.2ms) as JSON とは? となり調べてみようと思いました。 (今までは〇〇.jsファイルからajax通信を行なったときにはremote:trueと同じリクエストになる、つまり〇〇.js.erbと〇〇.json.jbuilderのどちらかから、何らかの規則でデータを渡すファイルが選ばれると思っていました...) リクエストフォーマットという概念 これまでリクエストフォーマットについてあまり意識せずに実装を行なってきましたが、あまり理解していない初心者でもなんとなく出来てしまうところがRailsの凄いところなのかなと改めて思いました。 例えばlink_toヘルパーを利用すると、デフォルトでHTML形式のリクエストが送られます。 また、form_withヘルパーを利用するとデフォルトでJS形式のリクエストが送られます。 これは「local:true」や「remote:true」のオプションを付加してあげることで制御できます。 Railsには「設定より規約」の原則がありますので、明示的に記述しなくても、 - HTML形式のリクエストの場合、〇〇.html.erb - JS形式のリクエストの場合、〇〇.js.erb のファイルを探して処理をしてくれます。 また、これまで2つしかないと思っていたリクエストフォーマットは他にも存在し、先日の記事にて利用したJSON形式やXML形式のリクエストが存在するということを知りました。 ここまできて追加の疑問が生まれました。 1. Railsはどのようにしてerbファイルを選択しているのか... 2. JSON形式でリクエストした後に呼び出されるファイルはなぜ〇〇.json.erbではないのか... 次の章にまとめていきたいと思います。 レンダリング Railsのレンダリングは、ActionView::TemplateHandlersのサブクラスで行われているそうです。 レイアウトとレンダリング Railsガイド せっかくなのでソースコードを読んで理解しようとしてみましたが、今の私の知識ではほとんど理解することはできませんでした。リンク先には4つのhandlerがありましたので、おそらくこの4つが同次元で扱われているものなのかなというレベルの理解です。 handlerの選択 Railsガイドによると、handlerの選択はテンプレートファイルの拡張子によって制御されており、.erbレイアウトが見つからない場合、.builderレイアウトを探索するようです。 .erbはERB(Embedded Ruby)テンプレートシステムを利用するファイルの拡張子で、Rubyスクリプトを埋め込むことができます。 .builderはBuilderテンプレートシステム(ERBの代わりに使用でき、よりプログラミング向きな記法)を利用するファイルです。XMLを生成する際に使われるようです。 jbuilderについて JSONを生成する際に使われます。JbuilderというRailsのGemfileにデフォルトで含まれているgemを導入することで、利用することができるようになります。 Jbuilder Railsガイド .jbuilderをどのように選択しているのかについて、記述を見つけることはできませんでしたが、おそらくgemを導入すると、.erbや.builderと同じように.jbuilderレイアウトを探索するようになるのだと思います。 小まとめ Railsでは「アクション名.リクエストフォーマット名.テンプレートシステム名」という命名をすれば自動的にテンプレートファイルを探してくれる リクエストフォーマット.erbファイルを探し、見つからない場合、リクエストフォーマット.builderファイルを探す 実験 前述した内容を少し試してみたいと思います。 - show.html.erbを作成し、正しく表示されることを確認した後、show.html.builderに変更 app/views/books/show.html.erb <p>app/views/books/show.html.erb</p> <p>HTML形式でリクエストを送っています。</p> ↓表示結果 show.html.erbを削除し、show.html.builderを作成します。 app/views/books/show.html.builder xml.p('app/views/books/show.html.builder') xml.p('HTML形式でリクエストを送っています。') ↓表示結果 ターミナルでも意図した通りに動いていることを確認できます。 Started GET "/books/1" for ::1 at 2021-09-06 01:28:33 +0900 Processing by BooksController#show as HTML Parameters: {"id"=>"1"} Rendering books/show.html.erb within layouts/application Rendered books/show.html.erb within layouts/application (0.3ms) Completed 200 OK in 17ms (Views: 16.2ms | ActiveRecord: 0.0ms) Started GET "/books/1" for ::1 at 2021-09-06 01:33:23 +0900 Processing by BooksController#show as HTML Parameters: {"id"=>"1"} Rendering books/show.html.builder within layouts/application Rendered books/show.html.builder within layouts/application (408.8ms) Completed 200 OK in 430ms (Views: 429.1ms | ActiveRecord: 0.0ms) respond_toメソッドについて 前述のようにRailsでは命名規則が正しければ、リクエストフォーマットに応じて自動的にテンプレートファイルを探してくれます。 さらに、respond_toというメソッドを使うことで、リクエストフォーマットに応じた制御が可能になるようです。 ActionController :: MimeResponds 現在作成しているPFのレベルではこの制御を必要とすることはありませんが、学習のために例を一つ挙げたいと思います。 app/controllers/books_controller.rb class BooksController < ApplicationController def show respond_to do |format| format.html format.js { redirect_to books_path} end end end この例では下記のような動きになります。 - HTMLリクエストの場合、デフォルトの動きでshow.html.erbをレンダリング - JSリクエストの場合、/booksにリダイレクト # HTMLリクエストの場合 Started GET "/books/2" for ::1 at 2021-09-06 10:54:31 +0900 Processing by BooksController#show as HTML Parameters: {"id"=>"2"} Rendering books/show.html.erb within layouts/application Rendered books/show.html.erb within layouts/application (0.3ms) Completed 200 OK in 22ms (Views: 20.8ms | ActiveRecord: 0.0ms) # JSリクエストの場合 Started GET "/books/2" for ::1 at 2021-09-06 10:55:21 +0900 Processing by BooksController#show as JS Parameters: {"id"=>"2"} Redirected to http://localhost:3000/books Completed 302 Found in 1ms (ActiveRecord: 0.0ms) Started GET "/books" for ::1 at 2021-09-06 10:55:21 +0900 Processing by BooksController#index as JS Rendering books/index.js.erb Book Load (0.4ms) SELECT "books".* FROM "books" WHERE (title LIKE('%%') OR body LIKE('%%')) ↳ app/views/books/_index.html.erb:1 Rendered books/_index.html.erb (4.9ms) Rendered books/index.js.erb (12.7ms) Completed 200 OK in 30ms (Views: 19.2ms | ActiveRecord: 0.4ms) どちらも意図した通りに動いていました。 もう一つの勘違い ここまで学習したことで、もう一つ大きな勘違いをしていたことに気づきました。 それは、〇〇.jsファイルにて $.ajax という処理をするには、.doneとjbuilderを使わなければならないと思っていたことです。 例えば先日投稿したインクリメンタルサーチに関する記事では下記の記述をしております。 app/assets/javascripts/search.js $.ajax({ type: 'GET', url: '/books', data: {key_word: key_word}, dataType: 'json' }) .done(function(data){ data.forEach(function(data){ const html = ` <tr> <td class='scroll__td'>${data.title}</td> <td class='scroll__td'>${data.body}</td> </tr> `; $('.scroll__tbody').prepend(html); }); }); ajaxというメソッドを使うからremote:trueと同じようなリクエストを送っていると勘違いしていましたが、実際にはdataType:'json'にてJSON形式のリクエストを送っています。 この記事では、先にindex.js.erbを作成し、その後index.json.jbuilderを作成するという流れで記述しています。もちろんそのままでも動くのですが、index.js.erbを作成済みであれば、下記のようにJSリクエストを送ってあげるだけで、index.js.erbがテンプレートフォーマットとして呼び出され、実装が終了していました。 app/assets/javascripts/search.js $.ajax({ type: 'GET', url: '/books', data: {key_word: key_word}, dataType: 'script' //'js'ではなく'script'とする必要がある }); // この場合.done以降は不要 おわりに 最近は新しい概念や機能の学習に目がいっていましたが、振り返ってみると基礎を全く理解できていなかったんだなと感じました。 また、これまでは〇〇.jsと〇〇.js.erbは全く別のものだと思っていましたが、2つを繋げて考えることができるようになった気がしています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsの仕組みについて

1.はじめに プログラミングスクールでRuby on Railsの勉強を始めるにあたって、 Railsの仕組みを調査する機会があったのでまとめました。 2.Railsとは Rubyプログラミング言語で書かれたフレームワーク。 Railsの2つの基本理念 同じことを繰り返すな(Don't Repeat Yourself: DRY) → コードの繰り返しを避ける 保守性と拡張性を確保し、バグが減少する。 設定より規約(Convention over Configuration: CoC) → 各種設定に対してデフォルト値を予め設定 設定ファイルの設定作業を行う負担が減少。 3.MVCとは RailsはMVCと呼ばれる3機能からなるソフトウェアアーキテクチャに基づいて構成されている。 Model(モデル)  データの処理を行う機能。 データベースの入出力や、計算を行う。 View(ビュー) ブラウザへの表示に関する機能。 HTMLによるインターフェースの表示を行う。 (erbはHTML内にRubyスクリプトを埋め込むためのライブラリ。) Controller(コントローラ) ユーザからの入力をModelやViewに伝える機能。 Viewによって表示されたUIからの入力に応じて、対応するメソッドを呼ぶ。 ルーティング リクエストURLと処理を結びつける仕組み。 MVCの流れ 1.ユーザがWebページに入力を行う。 2.Controllerがユーザのアクションに応じてModelのメソッドを呼び、データの処理が行われる。 3.データがModelからControllerを経由してViewに伝達される。 4.ViewはHTMLを生成し、Controllerを経由してWebページに表示する。 (出典:MVCの仕組みについて - https://qiita.com/wacker8818/items/7c66fb9db7a2b110cb6c) 5.参考 Railsドキュメント - https://railsdoc.com/ Railsガイド - https://railsguides.jp/getting_started.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Mac】Docker+rails6+MySQLで環境構築中にaddress already in useと出たら?

環境 ・macOS Big Sur バーション11.5.2 ・シェル zsh ・Ruby 3.0.2 ・rails 6.1.4 ・My SQL 8.0.23 ・Docker 20.10.8 Docker file Dockerfile FROM ruby:3.0.2 RUN apt-get update -qq && apt-get install -y nodejs # yarnパッケージ管理ツールをインストール # https://classic.yarnpkg.com/en/docs/install/#debian-stable RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update && apt-get install yarn WORKDIR /myapp COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install COPY . /myapp # Add a script to be executed every time the container starts COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 # Start the main process. CMD ["rails", "server", "-b", "0.0.0.0"] docker-compose.yml docker-compose.yml version: "3" services: db: image: mysql:8.0.26 command: mysqld --default-authentication-plugin=mysql_native_password environment: MYSQL_USER: user MYSQL_PASSWORD: password MYSQL_ROOT_PASSWORD: password ports: - 3306:3306 volumes: - ./tmp/db:/var/lib/mysql web: build: . command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" environment: MYSQL_HOST: db volumes: - .:/myapp ports: - "3000:3000" depends_on: - db エラー内容 $ docker compose up ↓ Starting mysql_db_1 ... error ERROR: for mysql_db_1 Cannot start service db: Ports are not available: listen tcp 0.0.0.0:3306: bind: address already in use ERROR: for db Cannot start service db: Ports are not available: listen tcp 0.0.0.0:3306: bind: address already in use ERROR: Encountered errors while bringing up the project. 【Docker】Rails+MySQLの環境をDockerで構築中「ポートの衝突」に遭遇してから解決するまで こちらの記事を参考に2つの解決策を試してみるも解決できず、、、 エラーにはaddress already in useとあるので他にaddressが使われていないか調べていきます。 $ lsof -i:3306 ↓ COMMAND PID USER ... mysqld 117 SAKAE ... 出てきました!これを消せばいいわけです! 消すコマンドはこちら $ kill 117 それでもうまくいかない場合 探すとき $ sudo lsof -i:3306 or $ sudo lsof -i -P | grep "LISTEN" 消したい時 $ sudo kill <killしたいportのPID> それでもダメなら docker-compose.yaml ports: - "3306:3306" ↓ ports: - "3307:3306" このようにポート番号を変えることで解決できる場合もあります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ratyを使ったレビュー機能 バリデーションエラーの表示方法

実装内容 Bootstrap4では、サーバーサイドでチェックした入力エラーのメッセージを表示するための「invalid-feedback」クラスが用意されており、項目ごとに分かりやすく表示されるようになっています。 しかし、入力欄に<input>タグを使用していない場合、このエラーメッセージは表示されません、、(自分の場合、Ratyを使ったレビュー機能で起こりました、、) この問題に遭遇し参考記事を探してみたのですが、該当する記事を見つけることができなかったこともあり、備忘録も兼ねて紹介しています。 処理全体の流れ 三項演算子を使用して適切なクラスの付与 styleの修正 エラーメッセージ表示用のdivタグ作成 完成形はこちら Before After 入力後 環境 macOS Big Sur 11.2.3 ruby: 2.7.2 rails: 6.1.3 jQuery テンプレートエンジン: slim レイアウト: bootstrap4 前提 gem bootstrap4 と Ratyの導入は省いております 修正前のviewファイル まず、修正前のコードを御覧ください。 app/views/travel_records/new.html.slim = simple_form_for @travel_record do |f| = render 'layouts/error_messages', model: f.object ・ ・ ・   #star   = f.label :'オススメ度【必須】', required: false   .d-flex   = f.hidden_field :review, id: :review_star   = render 'shared/star' レビュー機能のため、 カラム名をreviewとしています。 Ratyの動作に必要なjsコードはパーシャル化し、= render 'shared/star'で呼び出しています。 また、バリデーションはvalidates :review, presence: trueとし、入力がない場合にエラーを表示させるようにしています。 修正後のviewファイル こちらが完成形のコードになります。 ※スタイル適用時にレビュー星を枠で囲って表示するため、コードの記述順を変更しています。 app/views/travel_records/new.html.slim = simple_form_for @travel_record do |f| = render 'layouts/error_messages', model: f.object ・ ・ ・ = f.label :'オススメ度【必須】', required: false - is_error = f.object.errors[:review].present? #star class=(is_error ? 'form-control is-invalid' : 'form-control is-valid' if @travel_record.review? || is_error) .d-flex = f.hidden_field :review, id: :review_star = render 'shared/star', show_raty: 'new' div class=("invalid-feedback #{f.object.errors[:review].present? ? 'd-block' : 'd-none'}") | オススメ度を入力してください エラーメッセージのファイル app/views/layouts/_error_messages.html.slim - if model.errors.any? - model.errors.full_messages.each do | error_message | .alert.alert-danger.alert-dismissible.mb-2 role="alert" button type="button" class="close" data-dismiss="alert" span aria-hidden="true" &times; span.sr-only close = error_message それでは順を追って説明いたします。 赤枠の表示 まず初めに、エラー時に表示される赤枠の実装から行なっていきます。 app/views/travel_records/new.html.slim - is_error = f.object.errors[:review].present? # コードが重複しているため、変数に格納 #star class=(is_error ? 'form-control is-invalid' : 'form-control is-valid' if @travel_record.review? || is_error) 赤枠を表示させるにはbootstrapクラスのform-controlとis-invalidが必要になり、入力があった場合、緑枠を表示させるため、form-controlとis-validが必要になります。 また、入力の有無で表示を切り替えるため、三項演算子を使用して条件によって適切なクラスを付与するようにしています。 まず、三項演算子でクラスを付与する前にif @travel_record.review? || is_errorで条件分岐しています。 ※ この記述をしないと、未入力の場合でも緑枠が表示されてしまいます。 上記コードでは、f.object.errors[:review].present?でエラーの有無をチェックしています。エラーの場合はform-controlとis-invalidを付与して赤枠を表示させ、入力があった場合はform-controlとis-validを付与して緑枠を表示させています。 styleの修正 レビュー星を枠で囲って表示するため、styleの修正をしています。 app/assets/stylesheets/aplication.scss // エラーメッセージの設定 .form-control { height: auto; } form-controlのheightが悪さをしており、値をautoに変更した所、うまく表示することができました。 エラーメッセージの表示 次に、エラーメッセージ表示の実装を行なっていきます。 div class=("invalid-feedback #{is_error ? 'd-block' : 'd-none'}") | オススメ度を入力してください 赤文字のエラー文を表示するにはinvalid-feedbackクラスをもつ<div>タグが必要になります。 こちらも三項演算子を使用し、エラーの場合は、d-blockクラスを付与してエラーメッセージを表示させ、そうでない場合は、d-noneを付与して非表示にしています。 2つのクラスはどちらもbootstrapが用意しているクラスです。 d-block・・・display: block; d-none・・・display: none; 最後に 以上がBootstrap4 バリデーションエラーの表示方法でした! 思った以上に時間が掛かってしまったのですが、正常に動作した時の喜びは、その疲れを吹き飛ばしますね笑 これからも色んなことに挑戦し、学んだことを投稿していきたいと思います!! 間違っている箇所や分かりづらい箇所が多々あるかと思います。 その際は、気軽にコメントいただけれると幸いです。 最後までご覧いただき、ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フォーム入力欄に最初から数字が入ってしまう現象をvalue: "" で対処した。

なぜかフォーム入力欄に最初から数字の 0 が入ってしまう現象が発生してしまった。 <div class="weight-bold-text"> 商品名 <span class="indispensable">必須</span> </div> <%= f.text_area :item_name, class:"items-text", id:"item-name", placeholder:"商品名(必須 40文字まで)", maxlength:"40" %> 原因はわかりませんが、ページを開くと入力欄に数字の0が既に入ってしまい対処に困りました。 <div class="weight-bold-text"> 商品名 <span class="indispensable">必須</span> </div> <%= f.text_area :item_name, class:"items-text", id:"item-name", placeholder:"商品名(必須 40文字まで)", maxlength:"40" , value: "" %> とりあえず value: "" を記述して入力欄を空にできましたが、何故このような現象が起きたのかは、未だに分かっていません…
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RailsマンにとってPandasのDataFrame型がモヤモヤする理由

PandasのDataFrame型はモヤモヤする 当方、元々Rails歴3年で、最近Kaggleを始めた超初心者なのですが、Railsの経験が活かされる場面もあれば、逆にRailsの経験がバイアスになり混乱する場面もあります。入門してまず躓いたのは、PandasのDataFrame型です。特にdf['name']やdf.iloc[0]とやらで混乱してしまいました。 RailsとPandas テーブルデータの操作の違い 例えば、次のようなテーブルデータからオブジェクトを作成し、1行目・1列目を取り出す場合のコードの違いを比較します。 id name 1 田中太郎 2 佐藤花子 3 山田一郎 PandasのDataFrame型 data = { 'id': [1, 2, 3], 'name': ['田中太郎', '佐藤花子', '山田一郎'] } users = pd.DataFrame(data) # 1行目を取り出したいとき users.iloc[0] # 1列目を取り出したいとき users['id'] RailsのActive Record data = [ {id: 1, name: '田中太郎'}, {id: 2: name: '佐藤花子'}, {id: 3: name: '山田一郎'}, ] users = User.new(data) # 1行目を取り出したいとき users[0] # 1列目を取り出したいとき users.select('id') 何が違うのか? PandasもRailsも、テーブルのデータを操作するという点で共通ですが、決定的に違う点があります。それはズバリ、 Pandasは列でデータを区切るが、 Railsは行でデータを区切る という違いになります。 それぞれのdataの型を見比べればさらに明らかになります。 Pandasでは辞書型になっていますが、Railsでは配列型になっています。 実はPandasがusers['id']と書くのは、Pandasだから(あるいはPythonだから)ではなく、辞書型だから当然のことなのです。 なぜ違うのか?(私見) どちらもテーブルのデータを操作するという点で共通ですが、なぜPandasでは列でデータを区切り、Railsは行でデータを区切るのでしょうか? これは公式の見解ではなく、私見になります。 それは単純に、PandasとRailsで用途が違うから だと思います。 Pandasの用途はデータ分析なので、行よりも列を柔軟に扱いたい →データ分析では、列ごとに何かしらの処理をしたいことが多いです。例えばある列の平均値を算出したり、ある列の欠損値を埋めたりなど。逆に分析対象のデータの行数が増減したり、行ごとに抽出したりするシーンは比較的少ないのだと思います。なので列ごとにデータを区切ったほうが都合が良いです。 Railsの用途はWeb開発なので、列よりも行を柔軟に扱いたい →一方、Web開発では行ごとに何かしらの処理をしたいことが多いです。where文である条件に合致する行だけを抽出したり、行の新規追加、削除、更新など。逆にテーブルの列が増減したりは比較的少ないと思います。なので行ごとにデータを区切ったほうが都合が良いです。 PandasのDataFrameはスバラシイ 以上のように理解してから、PandasのDataFrame型と仲良くなれた気がします。公式の見解ではありませんが、少しでもPandas習得の助けになれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】redcarpetを使ったMarkdown記法の拡張機能のまとめ

対象者 マークダウンを実装予定の方 gem redcarpetを使う予定の方 目的 redcarpetの拡張機能を一つひとつ理解しながら使用する 実際の手順と実例 1.gemの導入 Gemfile : gem redcarpet : bundle installを実行 2.拡張機能一覧 機能 説明 1 :no_intra_emphasis 単語の強調。**や--で囲う 2 :fenced_code_blocks フェンスで囲まれたコードブロックを解析します。3つ以上の~バックティックで区切られたブロックは、インデントする必要なしにコードと見なされます。オプションの言語名は、コードブロックの開始フェンスの最後に追加できます。 3 :autolink リンクを解析します。http、https、およびftpプロトコルの自動リンクは自動的に検出されます 4 :disable_indented_code_blocks 各行の前に4つのスペースがあるテキストをコードブロックに変換します。 5 :strikethrough 打ち消し線を使います。〜文字〜と入力すると打ち消しされます 6 :space_after_headers 見出しを作ります。#の数でh1〜h6まで分かれます。#の後に半角スペースが必要 ※随時更新 参照 red carpet 公式Github 投稿者コメント PFにマークダウンを実装したので、拡張機能をまとめました。関連記事として、マークダウン自体の実装方法も記事にする予定です。 My Profile プログラミング学習歴4ヶ月目のアカウントです! プログラミングスクールで学んだ内容や 自分が躓いた箇所等のアウトプットの為に発信しています。 もし、投稿した記事の中に誤り等ございましたら、 コメント欄でご教授いただけると幸いです。 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails+MySQLで参照整合性の崩壊を引き起こすデータ操作を検証してみた

モチベーション 「スッキリわかるSQL入門」という書籍を進めていて、参照整合性の崩壊を引き起こすデータ操作が4パターンあるとのことを知り、外部キー周りの理解がなんとなくだったので実際に手を動かして検証してみようと思いました。 目的 この記事では、参照整合性の崩壊を引き起こすデータ操作を行った際に実際にどのような挙動になり、どのようにすれば防げるのかをRails+MySQLで検証します。 参照整合性とは 外部キーが指し示す先にきちんと行が存在してリレーションシップが成立していること。 参照整合性の崩壊を引き起こすデータ操作 「スッキリわかるSQL入門」という書籍で参照整合性の崩壊を引き起こすデータ操作として次の4つのパターンがあるとのことでした。 この4つのパターンを実際に試していこうと思います。 「ほかの行から参照されている」行を削除してしまう 「ほかの行から参照されている」行の主キーを変更してしまう 「存在しない行を参照する」行を追加してしまう 「存在しない行を参照する」行に更新してしまう 今回登場するテーブル 書籍を参考に「家計簿テーブル」と「費目テーブル」が登場します。 ER図 参照整合性が崩壊している例 例えば、以下の家計簿テーブルのid=2のレコードは、費目テーブルのid=2を参照しますが費目テーブルにはid=2のレコードが存在していないため参照整合性が取れていない状態になっています。 さっそく、検証してみる 何も意識せずにテーブルを作成する 今回は参照整合性の検証のため、インデックスやNOT NULL制約など何も考えずにテーブルを作成していきます。 # 家計簿テーブル作成 rails g model household_account_book date:date expense_item_id:integer memo:string income:integer expenditure:integer # 費目テーブル作成 rails g model expense_item name:string # マイグレーション実行 rails db:migrate 作成されたスキーマ(DB情報が反映されたもの) # 家計簿テーブル create_table "expense_items", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false end # 費目テーブル create_table "household_account_books", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| t.date "date" t.integer "expense_item_id" t.string "memo" t.integer "income" t.integer "expenditure" t.datetime "created_at", null: false t.datetime "updated_at", null: false end データを用意する 費目テーブルのデータを作成します。 ExpenseItem.create!(name: "食費") ExpenseItem.create!(name: "外食費") ExpenseItem.create!(name: "交際費") ExpenseItem.create!(name: "書籍代") ExpenseItem.create!(name: "水道代") ExpenseItem.create!(name: "電気代") ExpenseItem.create!(name: "ガス代") ExpenseItem.create!(name: "携帯代") ExpenseItem.create!(name: "インターネット料金") ExpenseItem.create!(name: "定期代") ExpenseItem.create!(name: "病院代") ExpenseItem.create!(name: "被服費") ExpenseItem.create!(name: "日用品費") ExpenseItem.create!(name: "住宅費") ExpenseItem.create!(name: "給与") 家計簿テーブルのデータを作成します。 内容は適当に入力していきます。 HouseholdAccountBook.create!(date: Date.today, expense_item_id: 1, memo: "食費", income: nil, expenditure: 1000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 2, memo: "外食費", income: nil, expenditure: 2000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 3, memo: "交際費", income: nil, expenditure: 3000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 4, memo: "書籍代", income: nil, expenditure: 4000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 5, memo: "水道代", income: nil, expenditure: 5000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 6, memo: "電気代", income: nil, expenditure: 6000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 7, memo: "ガス代", income: nil, expenditure: 7000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 8, memo: "携帯代", income: nil, expenditure: 8000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 9, memo: "インターネット料金", income: nil, expenditure: 9000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 10, memo: "定期代", income: nil, expenditure: 10000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 11, memo: "病院代", income: nil, expenditure: 11000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 12, memo: "被服費", income: nil, expenditure: 12000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 13, memo: "日用品費", income: nil, expenditure: 13000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 14, memo: "住宅費", income: nil, expenditure: 14000) HouseholdAccountBook.create!(date: Date.today, expense_item_id: 15, memo: "給与", income: 1000000, expenditure: nil) 家計簿テーブルの状態。 費目テーブルの状態。 検証 1. 「ほかの行から参照されている」行を削除する 家計簿テーブルから参照されている費目テーブルの行を削除してみます。 → 削除できました irb(main):039:0> ExpenseItem.find_by(name: "食費").destroy ExpenseItem Load (0.4ms) SELECT `expense_items`.* FROM `expense_items` WHERE `expense_items`.`name` = '食費' LIMIT 1 (0.2ms) BEGIN ExpenseItem Destroy (0.3ms) DELETE FROM `expense_items` WHERE `expense_items`.`id` = 1 (2.6ms) COMMIT => #<ExpenseItem id: 1, name: "食費", created_at: "2021-09-05 13:30:52", updated_at: "2021-09-05 13:30:52"> 2. 「ほかの行から参照されている」行の主キーを変更する 家計簿テーブルから参照されている費目テーブルの行の主キーを変更してみます。 → 変更できました。 irb(main):040:0> expense_item = ExpenseItem.find_by(name: "外食費") ExpenseItem Load (0.4ms) SELECT `expense_items`.* FROM `expense_items` WHERE `expense_items`.`name` = '外食費' LIMIT 1 => #<ExpenseItem id: 2, name: "外食費", created_at: "2021-09-05 13:30:52", updated_at: "2021-09-05 13:30:52"> irb(main):041:0> expense_item.id = 99 => 99 irb(main):042:0> expense_item.save! (0.3ms) BEGIN ExpenseItem Update (0.3ms) UPDATE `expense_items` SET `id` = 99, `updated_at` = '2021-09-05 13:55:48' WHERE `expense_items`.`id` = 2 (4.6ms) COMMIT => true 3. 「存在しない行を参照する」行を追加する 家計簿テーブルに費目テーブルに存在しない行を参照する行を追加してみます。 → 追加できました。 irb(main):043:0> HouseholdAccountBook.create!(date: Date.today, expense_item_id: 999, memo: "存在しない費目", income: nil, expenditure: 999) (0.2ms) BEGIN HouseholdAccountBook Create (0.3ms) INSERT INTO `household_account_books` (`date`, `expense_item_id`, `memo`, `expenditure`, `created_at`, `updated_at`) VALUES ('2021-09-05', 999, '存在しない費目', 999, '2021-09-05 14:07:16', '2021-09-05 14:07:16') (2.9ms) COMMIT => #<HouseholdAccountBook id: 16, date: "2021-09-05", expense_item_id: 999, memo: "存在しない費目", income: nil, expenditure: 999, created_at: "2021-09-05 14:07:16", updated_at: "2021-09-05 14:07:16"> 4. 「存在しない行を参照する」行に更新する 家計簿テーブルで費目テーブルに存在する行を参照している行を存在しない行を参照するように更新してみます。 → 更新できました。 irb(main):045:0> household_account_book = HouseholdAccountBook.find_by(memo: "外食費") HouseholdAccountBook Load (0.3ms) SELECT `household_account_books`.* FROM `household_account_books` WHERE `household_account_books`.`memo` = '外食費' LIMIT 1 => #<HouseholdAccountBook id: 2, date: "2021-09-05", expense_item_id: 2, memo: "外食費", income: nil, expenditure: 2000, created_at: "2021-09-05 13:40:41", updated_at: "2021-09-05 13:40:41"> irb(main):046:0> household_account_book.expense_item_id = 998 => 998 irb(main):047:0> household_account_book.save! (0.3ms) BEGIN HouseholdAccountBook Update (0.3ms) UPDATE `household_account_books` SET `expense_item_id` = 998, `updated_at` = '2021-09-05 14:10:33' WHERE `household_account_books`.`id` = 2 (2.6ms) COMMIT => true 検証結果 当たり前ですが、何も制約等を設定していないため、はこのように参照整合性の崩壊を引き起こすことができる状態になってしまっていることがわかりました。 外部キー制約を指定を行ってテーブルを作成する 参照整合性の崩壊は、絶対に避けなければなりません。 そういったミスを防ぐために、外部キー制約(FOREIGN KEY制約)があります。 外部キー制約を指定することで、先ほど検証した参照整合性の崩壊を引き起こすデータ操作を行った際に強制的に処理を中断するようになります。 実際に検証してみます。 外部キー制約を指定する # 家計簿テーブルの費目idカラムに外部キー制約を指定 rails g migration AddExpenseItemIdToHouseholdAccountBook expense_item:references # 生成されたマイグレーションファイル class AddExpenseItemIdToHouseholdAccountBook < ActiveRecord::Migration[5.2] def change add_reference :household_account_books, :expense_item, foreign_key: true end end # データをリセットして、マイグレーションをやり直す rails db:migrate:reset これだとエラーになったため、最終的に下記のようにマイグレーションファイルを変更して再実行するとうまくいきました。 (rails db:rollbackもうまくいきました。) class AddExpenseItemIdToHouseholdAccountBook < ActiveRecord::Migration[5.2] def up change_column :household_account_books, :expense_item_id, :bigint add_foreign_key :household_account_books, :expense_items, column: :expense_item_id end def down remove_foreign_key :household_account_books, column: :expense_item_id change_column :household_account_books, :expense_item_id, :integer end end スキーマファイルでも外部キー制約が指定されています。 ActiveRecord::Schema.define(version: 2021_09_05_143824) do create_table "expense_items", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| t.string "name" t.datetime "created_at", null: false t.datetime "updated_at", null: false end create_table "household_account_books", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t| t.date "date" t.bigint "expense_item_id" t.string "memo" t.integer "income" t.integer "expenditure" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["expense_item_id"], name: "fk_rails_619f5a0007" end add_foreign_key "household_account_books", "expense_items" end データを用意する 「何も意識せずにテーブルを作成する」と同じようにデータを用意。 検証 1. 「ほかの行から参照されている」行を削除する 家計簿テーブルから参照されている費目テーブルの行を削除してみます。 → 削除できなくなりました! irb(main):031:0> ExpenseItem.find_by(name: "食費").destroy ExpenseItem Load (0.3ms) SELECT `expense_items`.* FROM `expense_items` WHERE `expense_items`.`name` = '食費' LIMIT 1 (0.2ms) BEGIN ExpenseItem Destroy (0.5ms) DELETE FROM `expense_items` WHERE `expense_items`.`id` = 1 (2.4ms) ROLLBACK Traceback (most recent call last): 1: from (irb):31 ActiveRecord::StatementInvalid (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`app_development`.`household_account_books`, CONSTRAINT `fk_rails_619f5a0007` FOREIGN KEY (`expense_item_id`) REFERENCES `expense_items` (`id`)): DELETE FROM `expense_items` WHERE `expense_items`.`id` = 1) 2. 「ほかの行から参照されている」行の主キーを変更する 家計簿テーブルから参照されている費目テーブルの行の主キーを変更してみます。 → 変更できなくなりました! irb(main):032:0> expense_item = ExpenseItem.find_by(name: "外食費") ExpenseItem Load (0.3ms) SELECT `expense_items`.* FROM `expense_items` WHERE `expense_items`.`name` = '外食費' LIMIT 1 => #<ExpenseItem id: 2, name: "外食費", created_at: "2021-09-05 15:16:46", updated_at: "2021-09-05 15:16:46"> irb(main):033:0> expense_item.id = 99 => 99 irb(main):034:0> expense_item.save! (0.3ms) BEGIN ExpenseItem Update (0.7ms) UPDATE `expense_items` SET `id` = 99, `updated_at` = '2021-09-05 15:20:28' WHERE `expense_items`.`id` = 2 (3.4ms) ROLLBACK Traceback (most recent call last): 1: from (irb):34 ActiveRecord::StatementInvalid (Mysql2::Error: Cannot delete or update a parent row: a foreign key constraint fails (`app_development`.`household_account_books`, CONSTRAINT `fk_rails_619f5a0007` FOREIGN KEY (`expense_item_id`) REFERENCES `expense_items` (`id`)): UPDATE `expense_items` SET `id` = 99, `updated_at` = '2021-09-05 15:20:28' WHERE `expense_items`.`id` = 2) 3. 「存在しない行を参照する」行を追加する 家計簿テーブルに費目テーブルに存在しない行を参照する行を追加してみます。 → 追加できなくなりました! irb(main):035:0> HouseholdAccountBook.create!(date: Date.today, expense_item_id: 999, memo: "存在しない費目", income: nil, expenditure: 999) (0.2ms) BEGIN HouseholdAccountBook Create (0.6ms) INSERT INTO `household_account_books` (`date`, `expense_item_id`, `memo`, `expenditure`, `created_at`, `updated_at`) VALUES ('2021-09-05', 999, '存在しない費目', 999, '2021-09-05 15:21:26', '2021-09-05 15:21:26') (3.1ms) ROLLBACK Traceback (most recent call last): 2: from (irb):35 1: from (irb):35:in `rescue in irb_binding' ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot add or update a child row: a foreign key constraint fails (`app_development`.`household_account_books`, CONSTRAINT `fk_rails_619f5a0007` FOREIGN KEY (`expense_item_id`) REFERENCES `expense_items` (`id`)): INSERT INTO `household_account_books` (`date`, `expense_item_id`, `memo`, `expenditure`, `created_at`, `updated_at`) VALUES ('2021-09-05', 999, '存在しない費目', 999, '2021-09-05 15:21:26', '2021-09-05 15:21:26')) 4. 「存在しない行を参照する」行に更新する 家計簿テーブルで費目テーブルに存在する行を参照している行を存在しない行を参照するように更新してみます。 → 更新できなくなりました! irb(main):036:0> household_account_book = HouseholdAccountBook.find_by(memo: "外食費") HouseholdAccountBook Load (0.3ms) SELECT `household_account_books`.* FROM `household_account_books` WHERE `household_account_books`.`memo` = '外食費' LIMIT 1 => #<HouseholdAccountBook id: 2, date: "2021-09-05", expense_item_id: 2, memo: "外食費", income: nil, expenditure: 2000, created_at: "2021-09-05 15:16:54", updated_at: "2021-09-05 15:16:54"> irb(main):037:0> household_account_book.expense_item_id = 998 => 998 irb(main):038:0> household_account_book.save! (0.3ms) BEGIN HouseholdAccountBook Update (0.6ms) UPDATE `household_account_books` SET `expense_item_id` = 998, `updated_at` = '2021-09-05 15:22:19' WHERE `household_account_books`.`id` = 2 (3.4ms) ROLLBACK Traceback (most recent call last): 1: from (irb):38 ActiveRecord::InvalidForeignKey (Mysql2::Error: Cannot add or update a child row: a foreign key constraint fails (`app_development`.`household_account_books`, CONSTRAINT `fk_rails_619f5a0007` FOREIGN KEY (`expense_item_id`) REFERENCES `expense_items` (`id`)): UPDATE `household_account_books` SET `expense_item_id` = 998, `updated_at` = '2021-09-05 15:22:19' WHERE `household_account_books`.`id` = 2) 検証結果 外部キー制約を追加したことにより、ROLLBACKを行い例外が発生するようになりました。 まとめ 外部キーを持つテーブルのカラムには、外部キー制約を指定して参照整合性の崩壊を引き起こすデータ操作ができないようにしよう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】コントローラーからjavascriptへパラメータを渡す

はじめに ・本記事ではコントローラからjavascriptにパラメータを渡す方法を説明します。 controller→view→javascriptとviewを介してパラメーターを渡すことができます。 まず、コントローラからビューへは、インスタンス変数を使いパラメータを渡します。 /app/controller/books_controller.rb def index #パラメータを、インスタンス変数でビューに渡す @book = 'コントローラからjavascriptへパラメータを渡す方法' end view(ビュー)ではinputタグでtypeをhidden属性にし、valueとして受け取ります。 /app/views/books/index.html.erb <h1>indexページ</h1> <input type="hidden" id="book" value="<%= @book %>"> javascriptで、htmlの要素をdocument.getElementByIdで取得します。 /app/assets/javascripts/application.js window.onload = function () { const book = document.getElementById('book').value alert(book); } ブラウザで確認すると、コントローラから渡した@bookのパラメータが、javascriptのalertログで出力されていることが分かります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む