20210727のRailsに関する記事は16件です。

【Rails】Rspec 3.5からはrails-controller-testingは使わないほうが良いらしい

RSpec 3.5からはrequest specsを使う Rspec3.5(リリースされたのは2016年のかなり前ですが)からは「rails-controller-testing」ではなくて「request specs」を使うようにした方が良いらしいです。 For new Rails apps: we don't recommend adding the rails-controller-testing gem to your application. The official recommendation of the Rails team and the RSpec core team is to write request specs instead. Request specs allow you to focus on a single controller action, but unlike controller tests involve the router, the middleware stack, and both rack requests and responses. 引用:「RSpec 3.5 has been released!」 request specs 公式ドキュメントに記載されたコードを記載しておきます。 spec/requests/widget_management_spec.rb require "rails_helper" RSpec.describe "Widget management", :type => :request do it "creates a Widget and redirects to the Widget's page" do get "/widgets/new" expect(response).to render_template(:new) post "/widgets", :params => { :widget => {:name => "My Widget"} } expect(response).to redirect_to(assigns(:widget)) follow_redirect! expect(response).to render_template(:show) expect(response.body).to include("Widget was successfully created.") end it "does not render a different template" do get "/widgets/new" expect(response).to_not render_template(:show) end end spec/requests/に.specファイルを記載します。細かい使い方などはまた別途記事を更新します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2にRailsアプリをデプロイ⑤ ~本番環境の設定~

はじめに 前回の続きです。 EC2内でgemをインストールしたり、環境変数を設定したりしていきます。 Swapファイルでメモリの増強 現状で動かしているEC2インスタンスでは、無料クオリティなので、pcの能力が足りずにGemをインストールするときにメモリが足りずにエラーが発生する可能性があります。 そのためにSwapファイルというメモリの増強をする処理を行います。 まずはEC2にログインしている状態でホームディレクトリに移動します。 ターミナル # ホームディレクトリに移動 [ec2-user@ip-172-31-25-189 ~]$ cd 移動できたら下記のコマンドを実行します。 ターミナル # 処理に時間がかかる可能性があるコマンドです [ec2-user@ip-172-31-25-189 ~]$ sudo dd if=/dev/zero of=/swapfile1 bs=1M count=512 # しばらく待って、以下のように表示されれば成功です 512+0 レコード入力 512+0 レコード出力 536870912 バイト (537 MB) コピーされました、 7.35077 秒、 73.0 MB/秒 さらに続けて下記のコマンドを入力していきます。 ターミナル [ec2-user@ip-172-31-25-189 ~]$ sudo chmod 600 /swapfile1 ターミナル [ec2-user@ip-172-31-25-189 ~]$ sudo mkswap /swapfile1 # 以下のように表示されれば成功 スワップ空間バージョン1を設定します、サイズ = 524284 KiB ラベルはありません, UUID=74a961ba-7a33-4c18-b1cd-9779bcda8ab1 ターミナル [ec2-user@ip-172-31-25-189 ~]$ sudo swapon /swapfile1 ターミナル [ec2-user@ip-172-31-25-189 ~]$ sudo sh -c 'echo "/swapfile1 none swap sw 0 0" >> /etc/fstab' 必要なgemをインストール 下記のコマンドで必要なGemをインストールします。 ターミナル # クローンしたディレクトリに移動 [ec2-user@ip-172-31-23-189 www]$ cd /var/www/開発中のアプリケーション # rubyのバージョンを確認 [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ruby -v ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux] 続いて本番環境でbundlerをインストールします。 ローカル環境でのbundlerを確認して、本番環境でも同じバージョンを導入するようにします。 下記はローカル環境で実行して確認します。 ターミナル(ローカル) # 開発中のアプリケーションのディレクトリで実行 % bundler -v Bundler version 2.1.4 上記の例ではbundler 2.1.4がインストールしています。 同じバージョンを導入します。 EC2内で下記のコマンドを実行します。 ターミナル # 「2.1.4」の箇所は、ローカルで確認したbundlerのバージョンを導入します [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ gem install bundler -v 2.1.4 # 以下のコマンドは、処理に数分以上かかる場合があります [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ bundle install 環境変数を設定する Railsアプリを動作させるために、secret_key_baseというCookieの暗号化に用いられる文字列を用意します。この値は漏らしてはいけないので、環境変数に設定します。 まずは下記のコマンドでsecret_key_baseを作成します。 実行した後に表示される英数の羅列はメモアプリなどに控えておきます。 ターミナル [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rake secret 69619d9a75b78f2e1c87ec5e07541b42f23efeb6a54e97da3723de06fe74af29d5718adff77d2b04b2805d3a1e143fa61baacfbf4ca2c6fcc608cff8d5a28e8d 続いてEC2の中に環境変数を設定します。 下記のコマンドを実行します。 ターミナル [ec2-user@ip-172-31-23-189 ~]$ sudo vim /etc/environment 「~」がたくさん出てきたら「iキー」を押して入力可能にします。 そして、下記を入力します。値は指示通りの値を入力します。 PAYJPやBasic認証は必要があればここに環境変数として設定しておきます。 /etc/environment #前章で設定したデータベースのrootユーザーのパスワードを入力 DATABASE_PASSWORD='データベースのrootユーザーのパスワード' SECRET_KEY_BASE='さきほど作成したsecret_key_base' # 「AWSに画像をアップロードする」でダウンロードしたCSVファイルを参考に値を入力 AWS_ACCESS_KEY_ID='ここにCSVファイルのAccess key IDの値をコピー' AWS_SECRET_ACCESS_KEY='ここにCSVファイルのSecret access keyの値をコピー' # Basic認証で設定したユーザー名とパスワードを入力 BASIC_AUTH_USER='設定したユーザー名' BASIC_AUTH_PASSWORD='設定したパスワード' # PAY.JPで設定した公開鍵と秘密鍵を入力 PAYJP_SECRET_KEY='sk_test_*************' 入力が終わったら、「escキー」→「:wq」の順で実行し、保存します。 一度EC2からログアウトし環境変数を反映させましょう。 ターミナル [ec2-user@ip-172-31-23-189 ~]$ exit logout Connection to 52.xx.xx.xx closed. exitを実行すればログアウト完了です。 下記のコマンドで再度ログインします。 ターミナル $ ssh -i [ダウンロードした鍵の名前].pem ec2-user@[作成したEC2インスタンスと紐付けたElastic IP] それぞれの環境変数の確認は下記のコマンドでしっかり設定されているか確認できます。 ターミナル [ec2-user@ip-172-31-23-189 ~]$ env | grep SECRET_KEY_BASE SECRET_KEY_BASE='secret_key_base' [ec2-user@ip-172-31-23-189 ~]$ env | grep DATABASE_PASSWORD DATABASE_PASSWORD='データベースのrootユーザーのパスワード' [ec2-user@ip-172-31-23-189 ~]$ env | grep AWS_SECRET_ACCESS_KEY AWS_SECRET_ACCESS_KEY='Secret access key' [ec2-user@ip-172-31-23-189 ~]$ env | grep AWS_ACCESS_KEY_ID AWS_ACCESS_KEY_ID='Access key ID' [ec2-user@ip-172-31-23-189 ~]$ env | grep BASIC_AUTH_USER BASIC_AUTH_USER='設定したユーザー名' [ec2-user@ip-172-31-23-189 ~]$ env | grep BASIC_AUTH_PASSWORD BASIC_AUTH_PASSWORD='設定したパスワード' [ec2-user@ip-172-31-23-189 ~]$ env | grep PAYJP_SECRET_KEY PAYJP_SECRET_KEY='sk_test_*************' [ec2-user@ip-172-31-23-189 ~]$ env | grep PAYJP_PUBLIC_KEY PAYJP_PUBLIC_KEY='pk_test_*************' ポートの開放 ①で行ったのと同じようにポートの開放を行います。今回はRailsサーバを3000番ポートで起動するように解放します。 AWSのページからこのEC2インスタンスを選択しセキュリティのタブを開きます。 そして「インバウンドルールを編集」をクリックします。 ページが遷移したら「ルールを追加」をクリックし、設定します。 タイプ: 「カスタム TCP」 ポート範囲: 「3000」 カスタム: 「0.0.0.0/0」 というように設定します。↓の画像のように追加できればOKです。 できたらオレンジ色の「ルールを保存」をクリックしてポートの開放完了です。 本番環境でRailsを起動させる まず、アプリのdatabase.ymlファイルを編集します。 config/database.ymlファイルの「production:」の下を下記のように編集します。 config/database.yml production: <<: *default database:(※こちらは編集しないでください) username: root password: <%= ENV['DATABASE_PASSWORD'] %> socket: /var/lib/mysql/mysql.sock 編集できたらリモートリポジトリにコミット、プッシュしておきます。 下記のコマンドでEC2にも反映させます。 ターミナル [ec2-user@ip-172-31-23-189 <リポジトリ名>] git pull origin master 続いてEC2内にデータベースを作成します。 作成とマイグレートは下記のコマンドを実行します。 ターミナル [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rails db:create RAILS_ENV=production Created database '<データベース名>' [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rails db:migrate RAILS_ENV=production もし「Mysql2::Error: Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock'」というエラーが起こったら、mariadbが起動していない可能性があるので「sudo systemctl start mariadb」というコマンドをターミナルに打ち込みmysqlの起動を試してみます。 ここまで完了できたらRailsを起動させてみます。↓ ターミナル [ec2-user@ip-172-31-23-189 ~]$ cd /var/www/[リポジトリ] [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ bundle exec unicorn_rails -c config/unicorn.rb -E production -D アセットファイルをコンパイル アセットファイルは画像やCSS, JSなどを管理しているファイルで、それらを圧縮してデータを転送する処理(コンパイル)を行います。 これをしないとビューが崩れる原因になります。 下記のコマンドでアセットファイルをコンパイルします。 ターミナル [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ rails assets:precompile RAILS_ENV=production 本番環境でRailsを再起動 次はRailsを再起動します。 普段、ローカルでは、ターミナルで「Ctrl」 + 「c」でサーバを止めて、「rails s」を実行している作業を、本番環境ではどのように行うのか、ということです。 まずは、下記のコマンドで、Unicornのプロセスを確認。 ターミナル [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn すると下記のように表示されます。 ターミナル ec2-user 17877 0.4 18.1 588472 182840 ? Sl 01:55 0:02 unicorn_rails master -c config/unicorn.rb -E production -D ec2-user 17881 0.0 17.3 589088 175164 ? Sl 01:55 0:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D ec2-user 17911 0.0 0.2 110532 2180 pts/0 S+ 02:05 0:00 grep --color=auto unicorn 左から二番目の五桁の数字がプロセスのid(PID)です。 一番上の「unicorn_rails master」と書かれている行のPIDがUnicornのプロセス本体のidkです。 killコマンドを使って停止させます。↓ ターミナル [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ kill <確認したunicorn rails masterのプロセスid> もう一度プロセスを表示させ終了できているか確認します。 ターミナル [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ ps aux | grep unicorn ... ec2-user 17918 0.0 0.2 110532 2180 pts/0 S+ 02:05 0:00 grep --color=auto unicorn このように一つだけ残っていればOKです。 最後に「RAILS_SERVE_STATIC_FILES=1」コマンドを実行し、Railsがコンパイルされたアセットを見つけられるように指定します。 ターミナル [ec2-user@ip-172-31-23-189 <リポジトリ名>]$ RAILS_SERVE_STATIC_FILES=1 unicorn_rails -c config/unicorn.rb -E production -D 実行できたら http://<Elastic IP>:3000/ にアクセスして、サイトが正常に表示されているか確認します。 ここまでできたら後一息 次回、webサーバーの設定をして完了です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

eachを使うと情報が全部表示される

はじめに コメント機能を実装しました。 しかし何故かDBの情報が全部表示されてしまいます。 すごい凡ミスだったのですが投稿します。 現在の記述 <%= form_with(model: [@review, @comment], local: true) do |f| %> <%= f.text_area :content %> <%= f.submit "コメントする", class: "btn btn-outline-info" %> <% end %> <%= @comments.each do |comment| %> #コメント一覧を表示 <%= comment.user.nickname %> #コメントしたユーザー名表示 <%= comment.content %> #コメント表示 <%= time_ago_in_words(comment.created_at) %>前にコメント  #コメントした時間表示 <% end %> コメントする前から最初から[]があったでおかしいとは思ってました。 結論 <%= @comments.each do |comment| %>  こいつに問題がありました。 これは表示されてはいけないので<% %>このように記述しなけらばなりません。 なので以下のように記述します。 <% @comments.each do |comment| %> 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails flashの使い方

flashメッセージとは 何らかのアクションがあった時に一時的に表示させるメッセージのことです。 ログインや投稿の際に使用します。 #コントローラー flash(:キー名) = 'メッセージ内容' #ビュー flash(:キー名) flashとflash.now flash=次のアクションまでデータを保持する。redirect_toと一緒に使う。 flash.now=次のアクションに移行した時点でデータが消える。renderと一緒に使う。 redirect_to=処理を強制的に指定の場所にとばす。 render=アクションの実行はせずに指定の場所を表示する。 #例 コントローラー def create @task = Task.new(task_params) if @task.save flash[:success] = '正常に送信されました' redirect_to @task else flash.now[:danger] = '送信されませんでした' render :new end end #例 ビュー <% flash.each do |message_type, message| %> <div><%= message %></div> <%= end %> #@task.save が成功すると、message_typeに[:success]、messageに'正常に送信されました'が代入される。 flashはハッシュなので、eachで取り出すと|kye, value|で取り出されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gem fog-awsを使用して画像アップロード機能を実装しようとしてハマった話

起こった出来事 現在作成しているアプリで画像アップロード機能を実装しようと思い、fog-aws carrierwave を組み合わせてS3にアップロードしようと思ったところでかなりの時間を消費していましたが無事に解決できましたので、それまでの過程を備忘録として記入したいと思います。 そもそも何故ダメだったのか argumenterror ( is not a recognized provider):`というエラーが出ており、そもそもプロバイダー自体が読み込めていないと判断し carriewave.rbとimage-uploder.rbでaws設定関連の記述ミスや環境変数がちゃんと適用されてるか、バケット名が間違えてないかなど思い当たるところは片っぱしから調べましたが全部あっており、完全にわからない状態でした。 carriewave.rb require 'carrierwave/storage/abstract' require 'carrierwave/storage/file' require 'carrierwave/storage/fog' CarrierWave.configure do |config| case Rails.env when 'development', 'test' config.storage = :file config.cache_storage = :file else config.fog_credentials = { provider: 'AWS', aws_access_key_id: Rails.application.credentials.aws[:access_key_id], aws_secret_access_key: Rails.application.credentials.aws[:secret_access_key], region: 'ap-northeast-1', } config.fog_provider = 'fog/aws' config.storage :fog config.cache_storage = :fog config.fog_directory = 'バケット名' config.fog_public = false end end image-uploder.rb class ImageUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick if Rails.env.production? storage :fog else storage :file end def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end def default_url(*_args) '/images/' + [version_name, 'default.png'].compact.join('_') end version :thumb do process resize_to_fit: [128, 128] end version :medium do process resize_to_fit: [360, 360] end def extension_whitelist %w[jpg jpeg gif png] end end 解決策 しかし見落としがありました、私が参考にしたサイトには記述されていませんでしたが config.asset_host = "https://s3.ap-northeast-1.amazonaws.com/バケット名" この記述をcarriewave.rbに書いているサイトがあり、まさかと思い試しに追記してみたらこれが動くんですよね。 carriewave.rb require 'carrierwave/storage/abstract' require 'carrierwave/storage/file' require 'carrierwave/storage/fog' CarrierWave.configure do |config| case Rails.env when 'development', 'test' config.storage = :file config.cache_storage = :file else config.fog_credentials = { provider: 'AWS', aws_access_key_id: Rails.application.credentials.aws[:access_key_id], aws_secret_access_key: Rails.application.credentials.aws[:secret_access_key], region: 'ap-northeast-1', } config.fog_provider = 'fog/aws' config.storage :fog config.cache_storage = :fog:    config.asset_host = "https://s3.ap-northeast-1.amazonaws.com/バケット名" config.fog_directory = 'バケット名' config.fog_public = false end end 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Association namedエラーで彷徨った話

起こった出来事 現在作成中のアプリでマイプロフィールから今までにブックマークした投稿を一覧表示されるページを作成しrails sしてブラウザからのアクセスを試みたところ下禁輸のようなエラーが出てしばらく時間を使ってしまいました。 Association named 'game' was not found on Bookmark; perhaps you misspelled it? 対処方法 こちらはエラー内容の通りアソシエーションがで定義されておらず、gameとのアソシエーションをbookmarkモデルで見つけることができなかったという内容です。 なので該当のモデルを確認してみましたらところ、 bookmark.rb class Bookmark < ApplicationRecord class Bookmark < ApplicationRecord validates :user_id, presence: true validates :game_id, presence: true belongs_to :user belongs_to :game end end という風に何故かclassを二重にしてたのが原因でした。 このように直したら正常に動作しました。 bookmark.rb class Bookmark < ApplicationRecord validates :user_id, presence: true validates :game_id, presence: true belongs_to :user belongs_to :game end 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails c 動かない現象

ターミナルで Rails c と入力すると以下のようになりました。 入力できず、反応なし…。 ubuntu:~/environment/tasklist (main) $ rails c #これ以降空白 原因としてはプロジェクトを削除したり作成をしたりした際にコンソールを切り忘れたようです。 Rails cをするとspringも一緒に動くとのことだったので動いているspringを検索。 ubuntu:~/environment/tasklist (main) $ ps aux | grep spring ubuntu 19047 0.0 1.5 396400 15716 pts/1 Sl 04:50 0:00 spring server | tasklist | started 25 mins ago ubuntu 22759 0.0 0.1 14860 1068 pts/5 S+ 05:15 0:00 grep spring ubuntu:~/environment/tasklist (main) $ kill -9 19047 #不要なものを切りました 再度 Rails cを開きます。 ubuntu:~/environment/tasklist (main) $ rails c Running via Spring preloader in process 22936 Loading development environment (Rails 6.1.4) 3.0.0 :001 > 無事に開きました。 見えないところで作動していると見落としがちですね…。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】『エクササイズを楽しく継続したい』をテーマに、ポートフォリオを作成してみた

1.はじめに はじめまして。閲覧いただきありがとうございます。 今回作成したアプリについて、工夫・苦労したところ、各機能についてご紹介していければと思います。 2.アプリの特徴 『エクササイズ(運動)を楽しく継続したい』をテーマにしたSNS型記録アプリです。 基本的にはtwitterのような投稿、コメント、いいね、フォロー機能のあるSNSですが、その他に以下のような特徴のあるアプリです。 ユーザー別に、目標設定・編集できる ユーザー別に、好きなエクササイズを登録・編集・削除ができる エクササイズ合計時間によって、レベルがアップしていく レベルが上位3名になると、ランキングにユーザー名とレベルがのる 3.アプリ作成背景 ①課題 日頃の運動不足に加えて、最近はこんなご時世なので、気軽に外に出れないから、家で過ごす方が多くなったため、さらに運動不足を感じています。 運動には、心身ともに良い効果があると理解できていますが、モチベーションを維持できない原因を以下にまとめました。 ① 楽しくない ② すぐに効果がでないため 達成感がない 長期で取り組む必要がある ②目的 アプリを用いて、「楽しく運動を継続できる仕組み」づくりを行い、課題を解決する ③解決方法 「楽しく運動を継続できる仕組み」という方向性で、解決法を考えてみました。 ① 楽しくない 日々の頑張りを共有できる「投稿機能」 一緒に運動を頑張る仲間をつくれる「フォロー機能」 仲間と励まし合うことができる「いいね・コメント機能」 レベルがUPする楽しさ「レベル機能」 ② すぐに効果がでない 達成感を感じていただくため、目標を設定できる「目標設定機能」 長期でモチベーション維持できる「レベル・ランキング表示機能」 そして、私と同じ様に、「健康のために毎日運動をしたいけど、継続できない」悩みを抱えている方は、少なからずいるのではないかと考えました。 そのような考えから、『Tumieku』を開発しました。 (「ツミエク」と呼びます。”エクササイズを積み上げる”ので、コレにしました。) 4.使用技術 言語:Ruby (2.7.2) フレームワーク:Ruby on Rails (6.1.3.2) フロントエンド:HTML&CSS/Bootstrap/JavaScript/jQuey DB:PostgreSQL テスト:Rspec インフラ:Heroku(ステージング環境→本番環境)+AWS(S3) ソースコード管理:GitHub(Projectsのカンバン方式でタスクを管理) その他使用ツール&素材等 Visual Studio Code draw.io unDraw(イラスト素材) Color Hunt(カラーパレットツール) Material Design Icon(アイコン) ロゴメーカー / STORES(ロゴ) 5.ER設計 6.機能一覧 Tumiekuにつけたすべての機能は、以下の16機能です。 機能 Gem ① ログイン・ログアウト機能 devise ② アカウント登録、プロフィール編集機能 devise ③ ゲストログイン機能 ☓ ④ 目標登録・編集機能 ☓ ⑤ いいね機能(Ajax) ☓ ⑥ コメント機能 ☓ ⑦ 投稿機能(CRUD) ☓ ⑧ エクササイズ登録機能(CRUD) ☓ ⑨ フォロー機能 ☓ ⑩ ページネーション機能 kaminari ⑪ レベルアップ機能 ☓ ⑫ ランキング機能 ☓ ⑬ 画像アップロード機能 carrierwave ⑭ フラッシュメッセージ表示機能 ☓ ⑮ レスポンシブ対応 Bootstrap ⑯ Rspecテスト実装 rspec-rails / factory_bot_rails / faker 7.ポートフォリオのURL URL:https://tumieku-app.herokuapp.com ホーム画面・ヘッダーから、ゲストログインボタンで簡単にログインできます。(その他、アカウント登録・ログイン画面でもゲストログインが可能です。) 8.アプリの使用イメージ ①ホーム画面 ホーム画面に、アプリの特徴をまとめています。 ヘッダーのロゴ(Tumieku)を押すとホーム画面へリダイレクト。 トップへ戻るアイコンも設置しています。 ②マイページ(目標・レベル表示) アイコン(①)の編集から、目標とアカウントの編集ができます。 エクササイズの合計時間によって、レベルアップしていきます。 アカウント登録時に、プロフィール画像を設定していない場合は、デフォルト画像を表示しています。 自身の過去の投稿がみれます。 3件以上の投稿となると、ページネーションし見やすくしています。 ③エクササイズ登録画面 自身の取り組みたいエクササイズを登録できます。 登録したエクササイズは、一覧表示から編集・削除ができます。 編集・削除は、ログイン中のuser.idとexerciseのuseer.idが一致していないと、編集・削除のアイコンが表示されないようにしています。 ④投稿ページ(今日のTumieku) ヘッダーの「今日のTumieku」から、投稿画面へ移動します。 日付は、カレンダーをクリックで選択できます。 登録したエクササイズを選択し、実施時間と感想を入力できます。 ⑤投稿一覧とランキング機能 各ユーザーの投稿一覧画面になります。 レベルが上位3名になると、ランキングにユーザー名とレベルがのります。ランキングにのるのは、楽しいですよね。モチベーション上がります!同率順位の場合も表示されます。 ⑥投稿詳細、コメント一覧 投稿に対して、アイコン①又は②から、コメントを投稿できます。 ログイン中のuser.idとcommentのuseer.idが一致していないと、削除のアイコンが表示されないようにしています。 ⑦いいね機能(Ajax) 非同期で、いいね選択・解除ができます。 いいねの数もカウントされます。 ⑧レスポンシブ対応 スマートフォンユーザーへも対応しています。 デバイスによって、ハンバーガーメニューも実装しています。 9.工夫したこと ①チーム開発を意識 実務でのチーム開発を想定した開発を行いました。 ① Git, GitHubを用いたソース管理 ② Projects のカンバン方式でタスクを管理 ③ ブランチ運用は、Gitflowを採用し、プルリクベースで開発 ブランチ名 本番用 備考 master 本番用 本番リリース用のブランチ develop 開発用 機能実装用のブランチはここから切る feature 機能実装用 派生元は develop ブランチ release リリース用 develop ブランチから分岐、タグ付けを行う ②ゲストユーザーは、編集・削除できないように設定 下記のように設定し、誤って「更新」や「削除」をクリックしても、フラッシュメッセージが表示され、ホーム画面にリダイレクトされるようにしました。 routes.rb Rails.application.routes.draw do root 'homes#index' devise_for :user, controllers: { registrations: 'users/registrations' } (略) end app/controllers/users/registrations_controller.rb class Users::RegistrationsController < Devise::RegistrationsController before_action :ensure_normal_user, only: %i[update destroy] def ensure_normal_user if resource.email == "guest@example.com" redirect_to root_path, alert: "ゲストユーザーの更新・削除はできません。" end end end ③UI/UX 視覚で直感的にわかりやすいように、アイコンやロゴを積極的に使用し、シンプルにしました。 アプリのカラーは、TumiekuがSNS的な要素を持ち、「人との繋がり」「励まし合い」といった要素も含むため、メインカラ−のオレンジに設定。 統一感をだすため、下記の配色を参考にデザインしました。 10.苦労したところ フロントエンド UI/UXの調整(Bootstrap) レスポンシブ対応 基本BootstrapのGridsystemを利用しましたが、レスポンシブすると崩れたり、ホーム画面端に謎の余白ができてしまい、原因を探して改善するのに苦労しました。 バックエンド レベルアップ機能 ランキング機能 コメント削除機能(【Rails】コメント削除機能の実装(備忘録)) Rspecでのテスト全般(【RSpec】リクエストスペック実装(備忘録)) コメント削除機能とRspecでのテストで詰まったところは、アプトプットとしてQiita記事に詳細をまとめました。レベルアップ機能とランキング機能のロジックは、一番悩みました。 特に、ランキング機能での、ユーザーのレベルを取得して、同率順位も含めてViewで表示するのに苦労しました。もっと読みやすいコードに改善できればと考えています。 11.開発で意識したこと ①エラー解決に対して 実務に入ったときに先輩社員や上司の時間を無駄に奪うことにならないように まずは、下記の順番にそって自分の力で解決することを意識しました。 ① エラーメッセージやログをよく読む ② エラー文から、どの段階でエラーが出ているか考える ③ bindnig.pry でデバッグする ④ エラー内容を検索する(できるだけ一次情報から読み解くことを意識) ⑤ どうしても解決できなければメンターの方へ質問する 質問する場合は、下記の2つを実施。 質問する前に、コミュニティ内で同じような質問がないか検索し、似た内容は参考にする 質問して学んだことは、Qiita等へアウトプットし同じエラーが起こっても、今度は自分で解決できるようにする ②質問の仕方 Slackを使って、以下の構成で質問していました。 なるべく少ないやり取りで済み、相手の人の時間を無駄に奪わない内容にすることを意識しました。 ① 何をしたいのかを明確に記載する ② 試したこと(コード・参考記事も一緒に添付) ③ どこで詰まっているのか(エラーメッセージ) →メンターの方から意見を頂いたところで、わからない所は恥ずかしがらず即確認する。(わかったふりをして進めると、メンターの方の時間を奪ってしまうため。) 「質問しないこと=自走力」と勘違いしがちですが、実務において1人でずっと悩み続けることはかえって損失になってしまうと思うので、 上記の方法である程度(30~40分)自力で解決できなかった場合には、端的かつ過不足ない内容で質問することで、時間的な損失を最小限にすることを心がけていきたいと思います。 12.今後の課題 AWSへデプロイ デザイン面の改善 「いいね」通知機能 テストコードの充実 検索機能(キーワード検索) ログイン後のリダイレクト先変更 ログイン時、毎日1日1回ランダムに一言表示 パスワード忘れた場合のパスワード再発行機能 エクササイズ積み上げ記録(Chart.js使用して、グラフで表示) TwitterAPIの導入(アプリのつぶやき内容をTwitterへ投稿できる) まだ課題も多いですが、一つずつ改善してよりブラッシュアップしていきたく思います。 長い記事になってしまいましたが、ここまで読んでくださりありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Docker】Quickstart : Compose and Rails やってみた

概要 DockerでRails5に対応したRuby on Railsの環境構築を行いました。 このクィックスタートでは Docker Compose を使って、 簡単な Rails/PostgreSQL アプリを設定することができます。 学んだ一連の流れをまとめておきます。 (一番下にDockerのコマンド一覧も掲載しております!) 前提としてDockerとcomposeのインストールができている状態とします。 まだの人はこちらを参考にして下さい。 ・【図解】Dockerの全体像を理解する -前編- ・いまさらだけどDockerに入門したので分かりやすくまとめてみた ・【入門】はじめての Docker Desktop for Mac のインストールと CentOS の仮想環境構築のセットアップ 環境 ・MacOS Apple M1 バージョン11.4 ・Docker 20.10.7 ・Ruby 2.5.9 ・Rails 5.2.6 ー参考資料ー ・公式ドキュメントQuickstart: Compose and Rails ・クィックスタート: Compose と Rails ・DockerのQuickstart:Compose and Railsをやってみた 手順 インストールができている確認 docker % docker version Client: Cloud integration: 1.0.17 Version: 20.10.7 API version: 1.41 Go version: go1.16.4 Git commit: f0df350 Built: Wed Jun 2 11:56:23 2021 OS/Arch: darwin/arm64 Context: default Experimental: true Server: Docker Engine - Community Engine: Version: 20.10.7 API version: 1.41 (minimum version 1.12) Go version: go1.13.15 Git commit: b0f5bc3 Built: Wed Jun 2 11:55:36 2021 OS/Arch: linux/arm64 Experimental: false containerd: Version: 1.4.6 GitCommit: d71fcd7d8303cbf684402823e425e9dd2e99285d runc: Version: 1.0.0-rc95 GitCommit: b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7 docker-init: Version: 0.19.0 GitCommit: de40ad0 dockercompose % docker compose Docker Compose version v2.0.0-beta.6 Dockerを使ったRuby on Railsの環境構築 Dockerとcomposeのインストールが完了したのでDockerを用いたRuby on Railsの環境構築を行います。 DockerでRuby on Railsの環境を構築するプロジェクトのディレクトリを作成し移動します。 今回はmyappとします。 % cd myapp 4つのファイルを準備 ⑴ docker-compose.yml ⑵ Dockerfile ⑶ Gemfile ⑷ Gemfile.lock ⑴ docker-compose.yml docker-compose.ymlというファイル名をVScodeに作成します。 以下を記述↓↓ docker-compose.yml version: '3' services: db: image: postgres environment: - POSTGRES_PASSWORD=password web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/myapp ports: - "3000:3000" depends_on: - db ⑵ Dockerfile 次に、Dockerfileというファイル名をVScodeに作成します。 以下を記述↓↓ Dockerfile FROM ruby:2.5 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN mkdir /myapp WORKDIR /myapp ADD Gemfile /myapp/Gemfile ADD Gemfile.lock /myapp/Gemfile.lock RUN bundle install ADD . /myapp これでDockerを動かす記述が完了しました。 ⑶ Gemfile 次に、Gemfileというファイル名をVScodeに作成します。 以下を記述↓↓ Gemfile source 'https://rubygems.org' gem 'rails', '5.2.6' ⑷ Gemfile.lock 次に、Gemfile.lockというファイル名をVScodeに作成します。 この時、Gemfile.lockは空にしておきます。 Gemfile.lock # 記述なし これで4つのファイルの作成が完了しました。 上記4つのファイルを作ったうえで、コマンドをうっていきます。 % docker-compose build これでDockerfileのファイル内の記述を実行します。 次に以下のコマンドでrails new をします。 % docker-compose run web rails new . --force --database=postgresql これでダウンロードが完了したら、次にGemfileが更新されたので 再度以下のコマンドを実行します。 % docker-compose build Ruby on RailsのDB設定 config/database.yml を以下に書き換えます。 config/database.yml default: &default adapter: postgresql encoding: unicode host: db username: postgres password: password pool: 5 development: <<: *default database: myapp_development test: <<: *default database: myapp_test production: <<: *default database: myapp_production username: myapp password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %> DBを作成 次に書き換えたDBを作成します。 % docker-compose run web rails db:create コンテナを起動 最後にコンテナを起動させます。 % docker-compose up http://localhost:3000/ にアクセスします。 この画面が出てきたら成功です! これでDockerでRuby on Railsの環境構築が完了です! 簡単な Rails/PostgreSQL アプリを設定することができました! 以下はDokerの開発に必要なコマンド一覧を残しておきます。 Dockerで使用するコマンド一覧 コマンド 動作 補足 docker ps   稼働コンテナの一覧表示 docker ps -a すべてのコンテナを表示 docker run コンテナの生成/起動 docker stats コンテナの稼働確認 docker start コンテナの起動 docker stop コンテナの停止 docker restart コンテナの再起動 docker rm コンテナの削除 docker pause コンテナの中断/再開 docker version   Dockerのバージョン確認 docker info Dockerの実行環境確認 docker commit コンテナからイメージ作成 docker save イメージの保存 docker build -t DockerfileからDockerイメージの作成 docker ls   ファイル変更 docker images ファイル変更 docker ps   ファイル変更 docker ps   ファイル変更 docker ps   ファイル変更 docker ps   ファイル変更 以上となります。 Dockerコマンドはまだまだたくさんあります。 今後、Dockerの開発が進むにつれて、都度更新していきます。 学習頑張っていきましょう!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

備忘:MySQLでデータベースの中身を確認する方法

よく忘れて、探すので備忘 本当にデータが登録されているかとかで使う まずはターミナルからMySQLに接続する ルートで接続 mysql -u root 本当は危険 Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 276 Server version: 5.6.51 Homebrew 参考 https://www.dbonline.jp/mysql/connect/index3.html データベースを選択する use cooklog; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed 使うデータベースがわからない場合は databese.ymlで確認 development: adapter: mysql2 encoding: utf8 reconnect: false database: cooklog_development pool: 5 username: kiyomasa host: localhost test: adapter: mysql2 encoding: utf8 reconnect: false database: cooklog_test pool: 5 username: kiyomasa host: localhost 参考:https://www.dbonline.jp/mysql/database/index3.html テーブルを確認 mysql> SELECT * FROM favorites; +----+---------+-----------+----------------------------+----------------------------+ | id | user_id | recipe_id | created_at | updated_at | +----+---------+-----------+----------------------------+----------------------------+ | 1 | 1 | 23 | 2021-07-26 23:38:18.303379 | 2021-07-26 23:38:18.303379 | +----+---------+-----------+----------------------------+----------------------------+ 1 row in set (0.00 sec) mysql> SELECT * FROM users; +----+------------+-------------------+--------------------------------------------------------------+----------------------------+----------------------------+ | id | name | email | password_digest | created_at | updated_at | +----+------------+-------------------+--------------------------------------------------------------+----------------------------+----------------------------+ | 1 | sample1_18 | wanko1@prog-8.com | $2a$12$WbBJmW91Y4qDNBqy/h17A.XHpknRAD4NHtgXGO6YURP3U6IuKUMeS | 2021-05-09 04:45:52.315260 | 2021-07-25 04:43:25.422336 | | 3 | aaaaa | wanko5@prog-8.com | $2a$12$PBkaJE.sc5VI2Nl5UsBLle.rmUbmxkz0NDTSUXoaOcQ4MtKGc1ox. | 2021-06-21 22:02:01.573754 | 2021-06-21 22:02:01.573754 | | 4 | aaaaa | wanko6@prog-8.com | $2a$12$jKle2Pi5vIrNQ3tacVeo8ugTpy/IzR37Heyq3q76pvlmc3.joWl2G | 2021-06-23 22:18:20.534653 | 2021-06-23 22:18:20.534653 | +----+------------+-------------------+--------------------------------------------------------------+----------------------------+----------------------------+ 4 rows in set (0.01 sec) 参考:https://oreno-it3.info/archives/853
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【FC版】React + Rails API + axios + react-router-domでCRUDを実装する

こんにちは!スージーです。 以前書いたこちらの記事はClass Componentで書いた記事だったので、関数コンポーネント(Functional Component)で書き直してみました。関数コンポーネントで書くにあたり、hookを使って実装します やりたい事 CRUD(新規作成・一覧表示・詳細表示・更新・削除)を実装します やらない事 ログイン周りの実装 Rails側 アソシエーションを使ったモデルのリレーション モデルバリデーション CORSの説明 React側 クライアントバリデーション hooksの説明 css その他 コマンドの説明 SQLの説明 ログの説明 こんな書き方すればReact × Rails APIでCRUDが実装できるんだなーって感じで見ていただけると幸いです 開発環境 Ruby 2.7.1 Rails 6.0.4 MySQL node.js 14.8.0 React 17.0.2 参考 React:関数コンポーネントとクラスコンポーネントの違い React hooksを基礎から理解する (useEffect編) React hooksを基礎から理解する (useState編) まずAPI(Rails)側から実装開始 以前の記事とほぼ同じなので色々、割愛します mkdir react-form-sample && cd react-form-sample rails new backend -d mysql --api && cd backend gem 'rack cors'をインストールとモデル・コントローラを作成 gem 'rack-cors' bundle install rails g model post name:string neko_type:string rails db:create rails db:migrate rails g controller api/v1/posts routes.rb Rails.application.routes.draw do namespace :api do namespace :v1 do resources :posts end end end cors.rb Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins 'http://localhost:3001' resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head] end end Front(React)側は3001ポートで繋ぐのでoriginsは3001を許可します posts_controller.rb class Api::V1::PostsController < ApplicationController def index render json: Post.all end def show render json: Post.find(params[:id]) end def create post = Post.new(post_params) if post.save render json: post else render json: post.erros, status: 422 end end def update post = Post.find(params[:id]) if post.update(post_params) render json: post else render json: post.errors, status: 422 end end def destroy post = Post.find(params[:id]) post.destroy render json: post end private def post_params params.require(:post).permit(:name, :neko_type) end end seed.rb Post.create!(name: 'ニャア', neko_type: 'アメリカンショートヘア') Post.create!(name: 'まる', neko_type: 'スコッティシュフォールド') Post.create!(name: 'むぎ', neko_type: 'スコッティシュフォールド') curlコマンドで確かめる為にサンプルデータを作ります api動作確認 各エンドポイントは以下のようになっています rails routes api_v1_posts GET /api/v1/posts(.:format) api/v1/posts#index POST /api/v1/posts(.:format) api/v1/posts#create api_v1_post GET /api/v1/posts/:id(.:format) api/v1/posts#show PATCH /api/v1/posts/:id(.:format) api/v1/posts#update PUT /api/v1/posts/:id(.:format) api/v1/posts#update DELETE /api/v1/posts/:id(.:format) api/v1/posts#destroy rails db:seed rails s // もう一つタブを開く // index curl http://localhost:3000/api/v1/posts [{"id":1,"name":"ニャア","neko_type":"アメリカンショートヘア","created_at":"2021-07-24T15:01:05.371Z","updated_at":"2021-07-24T15:01:05.371Z"},{"id":2,"name":"まる","neko_type":"スコッティシュフォールド","created_at":"2021-07-24T15:01:05.376Z","updated_at":"2021-07-24T15:01:05.376Z"},{"id":3,"name":"むぎ","neko_type":"スコッティシュフォールド","created_at":"2021-07-24T15:01:05.381Z","updated_at":"2021-07-24T15:01:05.381Z"}] // show curl http://localhost:3000/api/v1/posts/1 {"id":1,"name":"ニャア","neko_type":"アメリカンショートヘア","created_at":"2021-07-24T15:01:05.371Z","updated_at":"2021-07-24T15:01:05.371Z"} // create curl -X POST http://localhost:3000/api/v1/posts -d "[name]=test&[neko_type]=test" {"id":4,"name":"test","neko_type":"test","created_at":"2021-07-24T15:36:01.040Z","updated_at":"2021-07-24T15:36:01.040Z"} // update curl -X PATCH http://localhost:3000/api/v1/posts/4 -d "[name]=update&[neko_type]=update" {"id":4,"name":"update","neko_type":"update","created_at":"2021-07-24T15:36:01.040Z","updated_at":"2021-07-24T15:36:31.170Z"} // delete curl -X DELETE http://localhost:3000/api/v1/posts/4 {"id":4,"name":"update","neko_type":"update","created_at":"2021-07-24T15:36:01.040Z","updated_at":"2021-07-24T15:36:31.170Z"} jsonが正常に返ってきました。アクションは正常に動いている事が確認できました。これでapi側は完了です 次にClient(React)側の実装開始 react-form-sample $ create-react-app frontend cd frontend npm install axios react-router-dom axios-case-converter 今回使うライブラリは以下の通りです axios HTTPクライアント用のライブラリ react-router-dom ルーティング設定用のライブラリ axios-case-converter axiosで送受信する値をスネークケース<=>キャメルケースに変換するライブラリ 使わないファイルを削除とjsファイルをjsxファイルに変換 rm src/App.css src/App.test.js src/logo.svg src/reportWebVitals.js src/setupTests.js mv src/App.js src/App.jsx mv src/index.js src/index.jsx index.jsxとApp.jsxを修正 // index.jsx import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; ReactDOM.render( <React.StrictMode> <App /> </React.StrictMode>, document.getElementById('root') ); // App.jsx import React from 'react'; const App = () => { return <h1>Hello World</h1>; }; export default App; npm startでlocalhost:3000にアクセスして「Hello World」が表示されればOK API Clientを作成 各コンポーネントでaxiosを使ってapiコールしても良いのですが、今回はclient.jsファイルとpost.jsファイルを作成してapiコール部分をまとめます。 mkdir -p src/lib/api touch src/lib/api/client.js touch src/lib/api/post.js // client.js import applyCaseMiddleware from 'axios-case-converter'; import axios from 'axios'; // ヘッダーに関してはケバブケースのままで良いので適用を無視するオプションを追加 const options = { ignoreHeaders: true, }; const client = applyCaseMiddleware( axios.create({ baseURL: 'http://localhost:3000/api/v1', }), options ); export default client; // post.js import client from './client'; // 一覧 export const getList = () => { return client.get('/posts'); }; // 詳細 export const getDetail = (id) => { return client.get(`/posts/${id}`); }; // 新規作成 export const createPost = (params) => { return client.post('/posts', params); }; // 更新 export const updatePost = (id, params) => { return client.patch(`/posts/${id}`, params); }; // 削除 export const deletePost = (id) => { return client.delete(`/posts/${id}`); }; 一覧画面を作成 /は一覧画面とします // App.jsx import React from 'react'; import { BrowserRouter as Router, Switch, Route, } from 'react-router-dom'; import List from './components/List'; const App = () => { return ( <Router> <Switch> <Route exact path='/' component={List} /> </Switch> </Router> ); }; export default App; // List.jsx import React from 'react'; const List = () => { return <h1>HOME</h1>; }; export default List; localhost:3000にアクセスするとHOMEとなっていればOK api側との疎通確認をする 先程作成したlib/api/post.jsが正常に動くか疎通確認をします // List.jsx import React, { useEffect, useState } from 'react'; import { getList } from '../lib/api/post'; const List = () => { const [dataList, setDataList] = useState({}); useEffect(() => { handleGetList(); }, []); const handleGetList = async () => { try { const res = await getList(); console.log(res.data); setDataList(res.data); } catch (e) { console.log(e); } }; return <h1>HOME</h1>; }; export default List; api側をrails s起動、client側をnpm startして立ち上げます backend $ rails s // 別タブで frontend $ npm start Would you like to run the app on another port instead? › (Y/n) Y // Y + enterで3001ポートでアクセスします console.logでapiコールで取得したデータがdevツールに表示されればOK apiから取得した一覧を表示する // List.jsx import React, { useEffect, useState } from 'react'; import { getList } from '../lib/api/post'; import { useHistory, Link } from 'react-router-dom'; const List = () => { const [dataList, setDataList] = useState([]); useEffect(() => { handleGetList(); }, []); const handleGetList = async () => { try { const res = await getList(); console.log(res.data); setDataList(res.data); } catch (e) { console.log(e); } }; return ( <> <h1>HOME</h1> <button>新規作成</button> <table> <thead> <tr> <th>名前</th> <th>猫種</th> <th colSpan='1'></th> <th colSpan='1'></th> <th colSpan='1'></th> </tr> </thead> {dataList.map((item, index) => ( <tbody key={index}> <tr> <td>{item.name}</td> <td>{item.nekoType}</td> <td> <Link to={`/edit/${item.id}`}>更新</Link> </td> <td> <Link to={`/post/${item.id}`}>詳細へ</Link> </td> <td> <button>削除</button> </td> </tr> </tbody> ))} </table> </> ); }; export default List; 更新ボタン・詳細リンク・削除ボタン・新規作成ボタンはこの後、実装します localhost:3001で見ると以下のようにデータが表示されていればOK 詳細画面を作成 次に詳細画面を実装していきます。 詳細画面は一覧画面から1レコードを取得 そのレコードのidをreact-router-domを使ってqueryパラメータから取得 取得したidをapiクライアントからサーバへリクエスト レスポンスを受け取る という流れを実装します まずDetailコンポーネントを作成します mkdir src/components/Detail.jsx 詳細画面のパスはlocalhost:3001/post/1とします // App.jsx import React from 'react'; import { BrowserRouter as Router, Switch, Route, } from 'react-router-dom'; import List from './components/List'; // 追加 import Detail from './components/Detail'; const App = () => { return ( <Router> <Switch> <Route exact path='/' component={List} /> <Route path='/post/:id' component={Detail} /> // 追加 </Switch> </Router> ); }; export default App; // Detail.jsx import React, { useEffect, useState } from 'react'; import { getDetail } from '../lib/api/post'; import { useHistory, useParams } from 'react-router-dom'; const Detail = (props) => { const [data, setData] = useState({}); // { id: "1" }を取得する const query = useParams(); // 戻るボタン用 const history = useHistory(); // 画面描画時にidがundefinedだとデータ取得できないので // 依存配列にidを入れて、idがundifined => 1と更新された時に     // useEffectの副作用を使い、処理をもう一度実行させる useEffect(() => { handleGetDetail(query); }, [query]); const handleGetDetail = async (query) => { try { const res = await getDetail(query.id); console.log(res.data); setData(res.data); } catch (e) { console.log(e); } }; return ( <> <h1>DETAIL</h1> <div>ID:{data.id}</div> <div>名前:{data.name}</div> <div>猫種:{data.nekoType}</div> <button onClick={() => history.push('/')}>戻る</button> </> ); }; export default Detail; localhost:3001/1で見ると以下のようにデータが表示されていればOK 削除機能を実装 一覧ページに配置した削除ボタンで掴んだレコードのデータを物理削除します 一覧画面から1レコードを取得 onClickイベントでidを取得 取得したidをapiクライアントに渡しdeleteリクエストをサーバに送る 削除したレコード以外を再描画する // List.jsx import React, { useEffect, useState } from 'react'; // deletePostを追加 import { getList, deletePost } from '../lib/api/post'; import { Link } from 'react-router-dom'; const List = () => { const [dataList, setDataList] = useState([]); useEffect(() => { handleGetList(); }, []); const handleGetList = async () => { try { const res = await getList(); console.log(res.data); setDataList(res.data); } catch (e) { console.log(e); } }; // 削除する関数を追加 const handleDelete = async (item) => { // 引数にitemを渡してitem.idで「1」など取得できればOK console.log('click', item.id) try { const res = await deletePost(item.id) console.log(res.data)             // データを再取得 handleGetList() } catch (e) { console.log(e) } } return ( <> <h1>HOME</h1> <button>新規作成</button> <table> <thead> <tr> <th>名前</th> <th>猫種</th> <th colSpan='1'></th> <th colSpan='1'></th> <th colSpan='1'></th> </tr> </thead> {dataList.map((item, index) => ( <tbody key={index}> <tr> <td>{item.name}</td> <td>{item.nekoType}</td> <td> <Link to={`/edit/${item.id}`}>更新</Link> </td> <td> <Link to={`/${item.id}`}>詳細へ</Link> </td> <td> {/* 追加 */} <button onClick={() => handleDelete(item)}>削除</button> </td> </tr> </tbody> ))} </table> </> ); }; export default List; localhost:3001で削除ボタンを押下後、レコードが1件削除できればOK railsのログを見るとdeleteメソッドでidを受け取り、deleteが実行されているのが分かります Started DELETE "/api/v1/posts/4" for ::1 at 2021-07-27 10:02:20 +0900 Processing by Api::V1::PostsController#destroy as HTML Parameters: {"id"=>"4"} Post Load (3.4ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 4 LIMIT 1 ↳ app/controllers/api/v1/posts_controller.rb:30:in `destroy' (23.6ms) BEGIN ↳ app/controllers/api/v1/posts_controller.rb:31:in `destroy' Post Destroy (0.4ms) DELETE FROM `posts` WHERE `posts`.`id` = 4 ↳ app/controllers/api/v1/posts_controller.rb:31:in `destroy' (2.1ms) COMMIT ↳ app/controllers/api/v1/posts_controller.rb:31:in `destroy' Completed 200 OK in 50ms (Views: 3.3ms | ActiveRecord: 29.4ms | Allocations: 2700) 新規作成画面を作成 次に新規作成を作成します。 touch src/components/New.jsx src/components/Form.jsx フォーム部分は編集画面でも使うのでForm.jsxを作成して、新規登録画面と共通化します テキストフィールドの入力された値をonChangeで検知 eventでname属性とvalue属性を取得 {name: hoge, nekoType: fuga}の連想配列の形でstateに保持 stateをapiクライアントに渡しpostリクエストをサーバに送る 新規作成画面のパスはlocalhost:3001/newとします ルータに追加 // App.jsx import React from 'react'; import { BrowserRouter as Router, Switch, Route, Redirect, } from 'react-router-dom'; import List from './components/List'; import Detail from './components/Detail'; // 追加 import New from './components/New'; const App = () => { return ( <Router> <Switch> <Route exact path='/' component={List} /> <Route path='/post/:id' component={Detail} /> <Route exact path='/new' component={New} /> // 追加 </Switch> </Router> ); }; export default App; 一覧ページの新規登録ボタンにイベント追加 // List.jsx import React, { useEffect, useState } from 'react'; import { getList, deletePost } from '../lib/api/post'; // useHistoryを追加 import { useHistory, Link } from 'react-router-dom'; const List = () => { // 省略 return ( <> <h1>HOME</h1> {/* 追加 */} <button onClick={() => history.push('/new')}>新規作成</button> <table> <thead> <tr> <th>名前</th> <th>猫種</th> <th colSpan='1'></th> <th colSpan='1'></th> <th colSpan='1'></th> </tr> </thead> {dataList.map((item, index) => ( <tbody key={index}> <tr> <td>{item.name}</td> <td>{item.nekoType}</td> <td> <Link to={`/edit/${item.id}`}>更新</Link> </td> <td> <Link to={`/post/${item.id}`}>詳細へ</Link> </td> <td> <button onClick={() => handleDelete(item)}>削除</button> </td> </tr> </tbody> ))} </table> </> ); }; export default List; 新規登録コンポーネント作成 // New.jsx import React, { useState } from 'react'; import FormBody from './Form'; import { createPost } from '../lib/api/post'; import { useHistory } from 'react-router-dom'; const New = () => { const [value, setValue] = useState({}) const history = useHistory(); const handleChange = (e) => { setValue({ ...value, [e.target.name]: e.target.value }) } const handleSubmit = async (e) => { e.preventDefault(); try { const res = await createPost(value) console.log(res) history.push('/') } catch (e) { console.log(e) } } return ( <> <h1>NEW</h1> <FormBody handleChange={handleChange} handleSubmit={handleSubmit} value={value} buttonType='登録' /> </> ) }; export default New; フォーム部分のコンポーネント作成 このコンポーネントをNew.jsxにimportします // Form.jsx import React from 'react'; const Form = (props) => { const { handleChange, handleSubmit, value, buttonType } = props return ( <> <form> <div> <label htmlFor="name">猫の名前:</label> <input type="text" name="name" id="name" onChange={(e) => handleChange(e)} value={value.name}/> </div> <div> <label htmlFor="nekoType">猫種</label> <input type="text" name="nekoType" id="nekoType" onChange={(e) => handleChange(e)} value={value.nekoType}/> </div> <input type="submit" value={buttonType} onClick={(e) => handleSubmit(e)}/> </form> </> ) }; export default Form; localhost:3001/newにアクセスして、nameとnekoTypeを入力 => 登録ボタン押下 => 一覧画面へリダイレクト、新規登録したデータが表示されればOK rails側のログを見るとフロントからparameterを受け取って、postテーブルにinsertされている事が確認できます Started POST "/api/v1/posts" for ::1 at 2021-07-26 23:40:50 +0900 Processing by Api::V1::PostsController#create as HTML Parameters: {"name"=>"バニやん", "neko_type"=>"エキゾチックショートヘア", "post"=>{"name"=>"バニやん", "neko_type"=>"エキゾチックショートヘア"}} (16.9ms) BEGIN ↳ app/controllers/api/v1/posts_controller.rb:13:in `create' Post Create (4.1ms) INSERT INTO `posts` (`name`, `neko_type`, `created_at`, `updated_at`) VALUES ('バニやん', 'エキゾチックショートヘア', '2021-07-26 14:40:50.273036', '2021-07-26 14:40:50.273036') ↳ app/controllers/api/v1/posts_controller.rb:13:in `create' (0.9ms) COMMIT ↳ app/controllers/api/v1/posts_controller.rb:13:in `create' Completed 200 OK in 48ms (Views: 1.8ms | ActiveRecord: 21.9ms | Allocations: 2432) 今回は特にバリデーションやエラーハンドリングしていないので素のHTMLで書いてますが、react-hook-formなどフォーム用の多機能なライブラリも色々あります 更新画面を作成 更新処理でCRUDの実装は終了です 一覧ページに配置した更新リンクでレコードのidを持って遷移させます 更新画面は一覧画面から1レコードを取得 そのレコードのidをreact-router-domを使ってqueryパラメータから取得 取得したidをapiクライアントからサーバへリクエスト レスポンスを受け取る 受け取ったレスポンスをテキストフィールドにセット 取得したidとparameterをapiクライアントに渡しpatchリクエストをサーバに送る touch src/components/Edit.jsx ルータに追加 編集画面のパスはlocalhost:3001/edit/1とします import React from 'react'; import { BrowserRouter as Router, Switch, Route, Redirect, } from 'react-router-dom'; import List from './components/List'; import New from './components/New'; import Detail from './components/Detail'; // 追加 import Edit from './components/Edit'; const App = () => { return ( <Router> <Switch> <Route exact path='/' component={List} /> <Route path='/post/:id' component={Detail} /> <Route exact path='/new' component={New} /> {/* 追加 */} <Route path='/edit/:id' component={Edit}/> </Switch> </Router> ); }; export default App; 更新リンクは一覧画面作成時に記述済です // List.jsx <Link to={`/edit/${item.id}`}>更新</Link> 更新画面の作成 // Edit.jsx import React, { useEffect, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; import { updatePost, getDetail } from '../lib/api/post'; import FormBody from './Form'; const Edit = () => { // apiで取得したデータを管理する為のstate const [value, setValue] = useState({ name: '', nekoType: '', }) // 一覧からreact-router-domを使ってidを取得 const query = useParams(); const history = useHistory(); // 画面が描画された時、queryが更新された時に関数を実行 useEffect(() => { handleGetData(query) },[query]) // idをapiクライアントに渡し、/api/v1/posts/:idのエンドポイントからデータ取得 const handleGetData = async (query) => { try { const res = await getDetail(query.id) console.log(res.data) // 使う値のみstateにセットする setValue({ name: res.data.name, nekoType: res.data.nekoType, }) } catch (e) { console.log(e) } } // テキストフィールドの変更を検知し値を書き換えstateで管理 const handleChange = (e) => { setValue({ ...value, [e.target.name]: e.target.value }) } // 更新ボタン押下後、idとparameterをapiクライアントに渡しリクエストを投げる const handleSubmit = async (e) => { e.preventDefault() try { const res = await updatePost(query.id, value) console.log(res) // リクエストが成功したら'/'にリダイレクトさせる history.push('/') } catch(e) { console.log(e) } } return( <> <h1>Edit</h1> <FormBody handleChange={handleChange} handleSubmit={handleSubmit} value={value} buttonType='更新' /> </> ) } export default Edit フォーム部分は新規作成画面でも使ったForm.jsxを使います 一覧画面で選択したレコードの更新リンクから/edit/1に遷移してnameとnekoTypeのデータがテキストフィールドにセットされていればOK 修正して更新ボタンを押下、一覧画面にリダイレクト、データが更新されていればOK railsのログを見るとpatchリクエストでパラメータを受け取りupdate処理が実行されていることが分かります Started PATCH "/api/v1/posts/1" for ::1 at 2021-07-27 09:57:49 +0900 Processing by Api::V1::PostsController#update as HTML Parameters: {"name"=>"ニャアupdate", "neko_type"=>"アメリカンショートヘア", "id"=>"1", "post"=>{"name"=>"ニャアupdate", "neko_type"=>"アメリカンショートヘア"}} Post Load (0.9ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`id` = 1 LIMIT 1 ↳ app/controllers/api/v1/posts_controller.rb:21:in `update' (0.3ms) BEGIN ↳ app/controllers/api/v1/posts_controller.rb:22:in `update' Post Update (29.4ms) UPDATE `posts` SET `posts`.`name` = 'ニャアupdate', `posts`.`updated_at` = '2021-07-27 00:57:49.761871' WHERE `posts`.`id` = 1 ↳ app/controllers/api/v1/posts_controller.rb:22:in `update' (3.6ms) COMMIT ↳ app/controllers/api/v1/posts_controller.rb:22:in `update' Completed 200 OK in 59ms (Views: 0.8ms | ActiveRecord: 34.1ms | Allocations: 3346) 以上でCRUDの実装が完了です!お疲れさまでした! 最後に Reactを業務で使い始めた時に、このCRUDに苦戦した事があったのと、リンクとか、画面間のidの受渡しとか、パラメータのapiクライアントへの渡し方など、自分で見返す為にもまとめてみました おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]seedファイル使い方

今回はRailsで開発を行っていく中で、便利な機能seedファイルの使い方について解説していきます。 きっと便利に開発を進められるので、参考にしてみてください。 seedファイルとは 開発段階のデータ作成方法として、実際にcreate機能を実装してからアプリを操作してデータを作る方法やrails cからデータを作成する方法などがありますが、どちらも大量のデータを作ろうとすると正直めんどくさいです。。 そこで使われるのがseedファイルです。 seedファイルのメリットとして、 開発段階で動作確認用に初期データを入れられる データベースをリセットしても、seedファイルに入っている初期データは再び使える 一度に大量のデータを作成できる というメリットがありますので、開発するときに便利なので、知っておいて損のない機能となってます。 初期データとして管理者のデータも入れられるので、管理者が必須なアプリの場合は、使う場合が多いです。 初期データ作成 では、実際に初期データを作っていきます。 seedファイルはデフォルトで搭載されていて、dbディレクトリ直下のseeds.rbにコードを記述していきます。 db/seeds.rb # Postモデルのデータを作る Post.create!( content: "てすとほげ", user_id: 1 ) これで、Postモデルのcontentカラムが「てすとほげ」、user_idが1のデータが作成できます。 記述できたらデータベースに反映させます。 $ rails db:seed コマンド実行後、rails cで確認してみましょう。 $ rails c 001 > Post.all : 作成したデータが確認できたら、反映できています。 複数のデータを作成 1つのデータを作成することができましたので、複数のデータを作りたいときはこの記述を何個も作っていったらいいのね、、、 db/seeds.rb # Postモデルのデータを作る Post.create!( content: "てすとほげ", user_id: 1 ) Post.create!( content: "てすとほげ2", user_id: 1 ) Post.create!( content: "てすとほげ3", user_id: 1 ) : とてもDRYなコードとは言えないですよね 複数のデータを作りたいときは、timesメソッドを使うと一括でデータが作成できます。 db/seeds.rb # 一括で10個のユーザーデータを作成 # 10回繰り返し処理を行う 10.times do |n| User.create!( # n + 1で数字が重複しないようにする email: "test#{n + 1}@test.com", name: "テストユーザー#{n + 1}", password: "testhoge" ) end これで、userが10個作成できます。 バリデーションでユニークを指定している場合はn + 1でデータが重複しないようにしておきます。 では、データベースを反映させて、rails cで確認してみてください。 $ rails db:seed $ rails c 001 > User.all : データベースのリセット方法 今あるデータを削除して初期データを入れたいなど、データベースをリセットしたい場合の処理をお伝えしておきます。 データベースをリセットする場合は、以下のコマンドを実行します。 $ rails db:migrate:reset これで、データベースがリセットされたので、seedファイルの初期データを投入していきます。 rails db:seed これで、記述していた初期データが反映されました。 まとめ seedファイルは、管理者や一括でデータを入れたい場合に非常に便利な機能です。 seedファイルをうまく使って、開発を進めていきましょう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_withについて学んだこと

フォームを記述する際にhtmlとrailsでヘルパーメソッドを使って記述した場合の違いをまとめてみた。 <form aciton="/posts" method="post"> <input type="text" name="content"> <input type="submit" value="送信"> </form> action="/posts"localhost:3000/postsへ記載内容をリクエストする method="post" httpメソッドでpostを指定 type="text"1行のテキストボックスを作る name="content"contentという名前を指定している type="submit" フォームを実行するボタンを作る value="送信" 「送信」という初期値を入力する <%= form_with url:"/posts", method: :post, local: true do |form| %> <%= form.text_field :content %> <%= form.submit '送信' %> <% end %> method: :posthttpメソッドでpostを指定。初期値は:postなので、postメソッドを使う場合には省略可能 local: trueform_withはデフォルトでAjax(リモート送信)を行うことになっているので、それをキャンセルする。 form.text_field1行のテキストボックスを作る :content「content」というキーを設定する form.submitフォームを実行するボタンを作る 参考にしたサイト Qiita "Markdown記法一覧"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】複数画像投稿機能の導入

目的 Railsで作成したアプリに複数画像投稿機能を導入する。 開発環境 macOS: Big Sur Rubyバージョン: 2.6.5 Railsバージョン: 6.0.0 前提 アプリtest-appが作成されている。 【Rails】簡単な投稿アプリの作成 画像投稿機能が導入されている。 【Rails】画像アップロード機能の導入 手順 はじめに アソシエーションの設定 ストロングパラメーターの定義 画像投稿ページの編集 画像表示ページの編集 はじめに 今回は複数画像投稿機能を実装していきます! アソシエーションの設定 まずはアソシエーションの設定です。 現在、1対1で紐づいている関係を1対多に設定します。 app/models/post.rb class Post < ApplicationRecord has_many_attached :image end has_many_attachedメソッドとは、各レコードとファイルを1対多の関係で紐づけるメソッドです。 ストロングパラメーターの定義 次にストロングパラメーターの定義です。 送られてくるパラメーターは配列に格納されているため、ストロングパラメーターも配列形式にします! app/controllers/posts_controller.rb # 中略 private def test_params params.require(:post).permit(:text, image: []) end end 画像投稿ページの編集 複数枚画像投稿の場合は「multiple: true」を追記することによって、複数枚画像を選択することができます。 _form.html.erb <div class="field"> <%= form.label :image %> <%= form.file_field :images, multiple: true %> </div> 画像表示ページの編集 最後に保存した画像の表示をします。 @post.image.attached?で画像が存在するか確かめています。 app/views/posts/index.html.erb <% if @post.image.attached? %> <% @post.image.each do |image| %> <%= image_tag image.variant(resize: '500x500') %> <% end %> <% end %> これで、実装できました! 最後に 以上で画像投稿の複数化は完了です。 画像投稿機能が導入されていれば簡単に実装できるので、ぜひ試してみてください。 では。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rubyとrails のバージョン確認方法

ターミナル上でrubyとrailsのバージョンを確認する方法をメモしておく。 ruby ruby -v rails rails -v (例) username@computernames-MacBook-Pro currentdirectory % rails -v Rails 6.0.4 username@computernames-MacBook-Pro currentdirectory % ruby -v ruby 2.6.5
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカルにおけるrubyとrailsの環境構築

実際の開発業務に参加した際に、ローカルでrubyとrailsの開発環境をセットアップして、苦労したのでその軌跡をまとめておく。 なお今回のインストールするバージョンは以下の通りである。 ruby 2.7.1 rails 6.0.3 mysql 5.7 参考記事 Homebrewのインストール - MacにHomebrewをinstallする $ /bin/bash -c "$(curl -fsSL <https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh>)" - Homebrewのupdateの準備 $ git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core fetch --unshallow $ git -C /usr/local/Homebrew/Library/Taps/homebrew/homebrew-cask fetch --unshallow - Homebrewのupdate $ brew update Rubyのインストール(ここでは2.7.1 - ruby rbenv ruby-buildのインストール $ brew install ruby rbenv ruby-build - rubyのバージョンを2.7.1にする $ rbenv install 2.7.1 $ rbenv global 2.7.1 - .zshrcの修正 $ vim ~/.zshrc # 以下を追記 eval "$(rbenv init -)" $ source ~/.zshrc - rubyのバージョン確認 $ ruby -v # => ruby 2.7.1 $ which ruby # => /Users/******/.rbenv/shims/ruby MySQL 5.7のインストール(クローン先のDB設定に準拠する - Homebrewでmysql 5.7 をインストールする $ brew install mysql@5.7 - MySQL5.7にパスを通す $ echo 'export PATH="/usr/local/opt/mysql@5.7/bin:$PATH"' >> ~/.zshrc $ source ~/.zshrc - MySQLの起動 $ brew services start mysql@5.7 - MySQLに接続確認 $ mysql -uroot プロジェクトをgit clone 割愛 環境変数の設定 割愛 bundle installに向けた事前対策 ここはクローン先のリポジトリによって大きく変化すると思います。 Railsの依存ライブラリmimemagicのライセンス問題を解消する $ brew install shared-mime-info ローカルにrails 6.0.3をinstallする $ gem install rails -v 6.0.3 起動 $ bundle install $ rails db:create $ rails db:migrate $ rails db:seed $ rails s localhostへアクセス 以上です!最後までお読み頂き、ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む