20220108のRailsに関する記事は15件です。

active_hashで保存した都道府県の検索機能について(ransack使用)

ransackを用いた検索機能実装 今回は、現在作成しているSNSアプリでransackを使用して、登録されているユーザーの一覧ページから登録地域で絞り込みをする機能を作成していました。 その中で、色々エラーで躓いたため学習用に投稿しておこうと思います。 gemのインストール gem 'ransack' コントローラーの変更 class UsersController < ApplicationController def index @users = User.paginate(page: params[:page]) @q = User.ransack(params[:q]) @users = @q.result(distinct: true) @user_prefecture_id = Prefecture.all end end viewの設定 <%= search_form_for @q do |f| %> <%= f.label :prefecture_id, "活動地域" %> <%= f.collection_select :prefecture_id_eq, @user_prefecture_id,:id,:name,include_blank: '地域絞り込み' %> <%= f.submit %> <% end %> フォームの記述 <%= f.collection_select :検索したいカラム名,実際に表示したい配列データ,:表示する際に参照するDBのカラム名: 実際に表示されるカラム名 %> フォームの記述は上記のようになっております。 ここで、どのテーブルにどのカラムを紐付けているかを把握していなければ、理解するのに時間がかかると思いす。(実際に自分がそうでした) 最後に 今回は簡単な検索機能を実施しましたが、DBに保存したデータがうまく取り出せずに、3日ほど詰まっていましたが、無事に解決できました。 初学者の方で私のようなエラーで悩む方の役に立てばと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails 6のアプリをgit clone後に遭遇したエラー

rails6 git clone後の手順 やりたいこと・背景 実装途中のrails6のアプリをgit cloneしたい 間違えて、ローカルのリポジトリを消してしまった際にそんなこと滅多にないgithubにpushしていればなんとかなる 昔作っていたrails6のアプリ実装を再開したい などなど ざっくり手順 1. Githubでgit cloneしたいリポジトリをコピー terminal. $ git clone クローンしたいリポジトリのurl 2. rm -rf Gemfile.lockでgemのバージョン依存関係をなくす git cloneしてきたrailsのアプリは、gemのバージョンの依存関係が固定されてしまっているため。 そのまま、bundle installするとエラーが出る。 cd アプリ名 で、railsアプリのディレクトリに移動した後 terminal. $ rm -rf Gemfile.lock 私は、bundlerでアプリごとに環境指定しているので、bundle install --path vendor/bundleとしていますが、bundle installでも問題ないです。 terminal. $ bundle install --path vendor/bundle terminal. $ rails db:migrate エラーです少数の方は出ます terminal. /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:145:in `block in ffi_lib': Could not open library '/Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/ext/libsass.bundle': dlopen(/Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/ext/libsass.bundle, 0x0005): tried: '/Users/○○/○○/○○/○○/○○/bundle/ruby/2.6.0/gems/sassc-2.4.0/ext/libsass.bundle' (no such file), '/usr/local/lib/libsass.bundle' (no such file), '/usr/lib/libsass.bundle' (no such file) (LoadError) from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:99:in `map' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:99:in `ffi_lib' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/lib/sassc/native.rb:13:in `rescue in <module:Native>' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/lib/sassc/native.rb:10:in `<module:Native>' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/lib/sassc/native.rb:6:in `<module:SassC>' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/sassc-2.4.0/lib/sassc/native.rb:5:in `<main>' from /Users/○○/○○/○○/○○/○○/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.9.3/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb:23:in `require' ... 省略 ... rails db:migrate後に上記のようなエラーが出た方は、以下のように対応してください。 Gemfile. gem "sassc", "< 2.2.0" 追記 優秀な先輩エンジニアが教えてくれていました Also fails for me on CentOS 7 + Ruby 2.5. However unlike what was reported by OP, Jekyll 4.0 doesn't seem to require sassc 2.2.0. Just adding gem "sassc", "< 2.2.0" to my Gemfile worked around the bug successfully. When installing the extension from 2.1.0, libsass.so is at ~/.gem/ruby/2.5.0/gems/sassc-2.1.0-x86_64-linux/lib/sassc/libsass.so. When building it from 2.2.0, it is at ~/.gem/ruby/2.5.0/gems/sassc-2.2.0/ext/libsass.so. 再度 terminal. $ bundle install エラー terminal. You have requested: sassc < 2.2.0 The bundle currently has sassc locked at 2.4.0. Try running `bundle update sassc` If you are updating multiple gems in your Gemfile at once, try passing them all to `bundle update` 素直にbundle update sassc terminal. $ bundle update sassc 以下のような出来たメッセージ出たらok terminal. ... Installing sassc 2.1.0 (was 2.4.0) with native extensions Using sassc-rails 2.1.2 Using sass-rails 6.0.0 Note: sassc version regressed from 2.4.0 to 2.1.0 Bundle updated! 3. rails db:migrate terminal. $ rails db:migrate 4. rails sで立ち上げよう terminal. $ rails s 5. Webpacker::Manifest::MissingEntryError in Homes#topエラー terminal. ActionView::Template::Error (Webpacker can't find application.js in /Users/yade/pg/dmm/review/meshiterro/public/packs/manifest.json. Possible causes: 1. You want to set webpacker.yml value of compile to true for your environment unless you are using the `webpack -w` or the webpack-dev-server. 2. webpack has not yet re-run to reflect updates. 3. You have misconfigured Webpacker's config/webpacker.yml file. 4. Your webpack configuration is not creating a manifest. Your manifest contains: { } ): 7: <%= csp_meta_tag %> 8: 9: <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 10: <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> 11: </head> 12: 13: <body> app/views/layouts/application.html.erb:10 これはrails6で必要なwebpackerなどがないので以下実行 詳しくは、先輩エンジニアの方を参考にしてください。 terminal. $ rails webpacker:install $ rails webpacker:compile terminal. $ rails s 終わりです。 最後まで読んでいただきありがとうございました。独学でやっているので、情報不足や分かりづらい点がございましたら、コメントしていただければ幸いです。 少しでもお役に立てたら、幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】ポートフォリオ制作「バイク専用SNS」KoKo-iiYo

【Rails】ポートフォリオ制作「バイク専用SNS」KoKo-iiYo はじめに DMM WEB CAMP通学中、3ヶ月目に制作したポートフォリオについて、サイト概要・実装機能などまとめます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sidekicq使って定期的なメール配信(rails/Docker/Heroku/circleCI)

やりたいこと 自作の「家計簿アプリ」の中で、 定期的(今回は毎週月曜の午前9時)に「支出のまとめ」をメールで配信する。 ※完成イメージはgithubのrepositoryを参照してください。 環境 ruby 2.7.4 rails 6.1.4 アプリケーションは、Docker,circleCIを使って、Herokuにデプロイ済み。 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: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - ./src:/app - gem_data:/usr/local/bundle environment: WEBPACKER_DEV_SERVER_HOST: webpacker ports: - "3000:3000" depends_on: - db tty: true stdin_open: true Dockerfile FROM ruby:2.7.4 ENV RAILS_ENV=production RUN 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 -qq \ && apt-get install -y nodejs yarn build-essential default-mysql-client \ && apt-get install -y cron WORKDIR /app COPY ./src /app RUN bundle config --local set path 'vendor/bundle' \ && bundle install COPY start.sh /start.sh RUN chmod 744 /start.sh CMD [ "sh", "/start.sh" ] start.sh #! /bin/sh if [ "${RAILS_ENV}" = "production" ] then bundle exec rails assets:precompile fi bundle exec rails s -p ${PORT:-3000} -b 0.0.0.0 .circleci/config.yml version: 2.1 orbs: ruby: circleci/ruby@1.1.2 # config.ymlのruby向け記法を導入 node: circleci/node@2 heroku: circleci/heroku@1.2.3 jobs: build: docker: - image: circleci/ruby:2.7.4-node working_directory: ~/kyodokoza/src steps: - checkout: path: ~/kyodokoza - ruby/install-deps test: docker: - image: circleci/ruby:2.7.4-node - image: circleci/mysql:5.5 environment: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: app_test MYSQL_USER: root environment: BUNDLE_JOBS: "3" BUNDLE_RETRY: "3" BUNDLE_PATH: vendor/bundle APP_DATABASE_HOST: "127.0.0.1" RAILS_ENV: test working_directory: ~/kyodokoza/src steps: - checkout: path: ~/kyodokoza - ruby/install-deps - run: name: Database setup command: bundle exec rake db:create - run: name: yarn install command: yarn install - run: name: Database migrate command: bundle exec rake db:migrate RAILS_ENV=test - run: name: RSpec command: bundle exec rspec deploy: docker: - image: circleci/ruby:2.7.4 steps: - checkout - setup_remote_docker: version: 19.03.13 - heroku/install - run: name: heroku login command: heroku container:login - run: name: push docker image command: heroku container:push web -a $HEROKU_APP_NAME - run: name: release docker image command: heroku container:release web -a $HEROKU_APP_NAME - run: name: database setup command: heroku run bundle exec rake db:migrate RAILS_ENV=production -a $HEROKU_APP_NAME workflows: version: 2 build_test_deploy: jobs: - build - test: requires: - build - deploy: requires: - test filters: branches: only: main 実装開始の前に 今回は、「ActiveJob」の「キューイングライブラリ」として「sidekicq」を使用。 「ActiveJob」って何? Railsガイドから引用。 ActiveJobの目的は以下の通り。 Active Jobは、ジョブを宣言し、 それによってバックエンドでさまざまな方法によるキュー操作を実行するためのフレームワークです。 ジョブには、定期的なクリーンアップを始めとして、請求書発行やメール配信など、あらゆる処理がジョブになります。 これらのジョブをより細かな作業単位に分割して並列実行することもできます。 「バックグラウンドjob実行のための共通化インターフェース」ということのようです。 「sidekiq」とか「キューイングライブラリ」って何? Railsガイドから引用。 production環境でのジョブのキュー登録と実行では、キューイングのバックエンドを用意しておく必要があります。具体的には、Railsで使うべきサードパーティのキューイングライブラリを決める必要があります。 Rails自身が提供するのは、ジョブをメモリに保持するインプロセスのキューイングシステムだけです。 プロセスがクラッシュしたりコンピュータをリセットしたりすると、デフォルトの非同期バックエンドの振る舞いによって主要なジョブが失われてしまいます。アプリケーションが小規模な場合やミッションクリティカルでないジョブであればこれでも構いませんが、多くのproductionでは永続的なバックエンドを選ぶ必要があります。 そもそもActiveJobには、 定期実行や重たい処理等の「非同期な処理(=Jobの登録から実行まで時間的な隔たりがある処理)」を登録します。 その時間的隔たりの間に、プロセスクラッシュ等により、Jobが失われる可能性があります。 そうならないためにも、Jobを保管する場所を用意したい。 それがキューイングライブラリ。例えばsidekicq。 他には、ResqueやDelayedJobなどもあるらしいです。 sidekiqは公式githubのwikiが詳しいので、まずはざっと目を通すといいと思います。 https://github.com/mperham/sidekiq/wiki 実装(開発環境) 実装の流れ(開発環境) gemの導入(sidekiq, sidekiq-scheduler) docker-composeにredis用・sidekiq用のコンテナを導入 ActiveJob、sidekiqの設定 テスト用のジョブの設定、コンソール上で実行 sidekiq-schedulerの設定 テスト用のメール定期配信ジョブの設定、コンソール上で実行 sidekiqの導入~コンソール上で動作確認 gemの導入 gemdileに追加してbundle install gemfile gem 'sidekiq' gem 'sidekiq-scheduler' # 後々使うので、先に入れておく。 docker-composeにredis用・sidekiq用のコンテナを導入 Dockerも修正。redisサーバーをコンテナで立ち上げる。 Dokcer-compose.yml # 以下追加 redis: image: "redis:latest" ports: - "6379:6379" volumes: - "./data/redis:/data" sidekiq: build: . command: bundle exec sidekiq volumes: - ./src:/app - gem_data:/usr/local/bundle environment: REDIS_URL: redis://redis:6379 depends_on: - db - redis ActiveJob、sidekiqの設定 ActiveJobのキューインライブラリとしてsidekiqを設定 config/application.rb module App class Application < Rails::Application # 以下追加 config.active_job.queue_adapter = :sidekiq end end sidekiqのredisとの通信を設定。 docker-compose.ymlで環境変数を定義した、REDIS_URLを使用。 config/initializer/sidekiq.rb Sidekiq.configure_server do |config| config.redis = { url: ENV["REDIS_URL"] } end Sidekiq.configure_client do |config| config.redis = { url: ENV["REDIS_URL"] } end テスト用のジョブの設定、コンソール上で実行 docker-compose up でコンテナ立ち上げした後、 動作確認用にsamplejobクラスを定義して、コンソール上で実行してみる。 console docker-compose exec web bundle exec rails g job SampleJob app/jobs/sample_job.rb class SampleJob < ApplicationJob queue_as :default def perform(*args) # テスト用の処理を記述 puts "=====サンプルジョブです======" end end sidekiqのコンソールでSampleJobの実行 docker-compose exec sidekiq rails c irb [1] pry(main)> SampleJob.perform_later Enqueued SampleJob (Job ID: 541558a6-e161-4688-bdac-af87749c0ef6) to Sidekiq(default) => #<SampleJob:0x0000563727fb5fb8 @arguments=[], @exception_executions={}, @executions=0, @job_id="541558a6-e161-4688-bdac-af87749c0ef6", @priority=nil, @provider_job_id="8dc90dd89d58f0348c963338", @queue_name="default", @timezone="Asia/Tokyo"> sidekiqのlog 2022-01-03T00:56:14.908Z pid=1 tid=gup class=SampleJob jid=8f1e338731e0576cd6376cd4 INFO: start 2022-01-03T00:56:15.857Z pid=1 tid=gup class=SampleJob jid=8f1e338731e0576cd6376cd4 INFO: Performing SampleJob (Job ID: bfccddd8-2cc8-4cd9-9c78-f7a0b628edea) from Sidekiq(default) enqueued at 2022-01-03T00:56:14Z =====サンプルジョブです====== 2022-01-03T00:56:15.859Z pid=1 tid=gup class=SampleJob jid=8f1e338731e0576cd6376cd4 INFO: Performed SampleJob (Job ID: bfccddd8-2cc8-4cd9-9c78-f7a0b628edea) from Sidekiq(default) in 0.23ms 2022-01-03T00:56:15.862Z pid=1 tid=gup class=SampleJob jid=8f1e338731e0576cd6376cd4 elapsed=0.954 INFO: done コンソール上の動作確認ができました。 因みに、perform_laterの他に、以下のようなメソッドも使うことができます。 SampleJob.perform_async SampleJob.perform_in(5.minutes) SampleJob.perform_at(5.minutes.from_now) 参照: https://github.com/mperham/sidekiq/wiki/Getting-Started sidekiq-schedulerの導入~コンソール上で動作確認 単独でsidekiqのジョブ実行の確認ができました。 次に、定期実行のためにsidekiq-schedulerを導入、テストしていきます。 sidekiq-schedulerの設定 Sidekiq-schedulerのGemは前節で導入済みのはず。 sidekiq.ymlを追加して、cron likeなジョブスケジューリングを設定。 config/sidekiq.yml :schedule: sample_job: cron: '0 * * * * *' # Runs once per minute class: SampleJob うまくいけば、sidekiqのログにsample_jobの実行結果が表示される。 log sidekiq_1 | 2022-01-03T01:13:00.259Z pid=1 tid=gnh INFO: queueing SampleJob (SampleJob) sidekiq_1 | 2022-01-03T01:13:00.261Z pid=1 tid=gp5 class=SampleJob jid=b452e81949a58ad40be54307 INFO: start sidekiq_1 | 2022-01-03T01:13:00.375Z pid=1 tid=gnh INFO: Enqueued SampleJob (Job ID: 0df2f507-3e67-4fc9-a3ee-5e768d554877) to Sidekiq(default) sidekiq_1 | 2022-01-03T01:13:06.562Z pid=1 tid=gp5 class=SampleJob jid=b452e81949a58ad40be54307 INFO: Performing SampleJob (Job ID: 0df2f507-3e67-4fc9-a3ee-5e768d554877) from Sidekiq(default) enqueued at 2022-01-03T01:13:00Z sidekiq_1 | =====サンプルジョブです====== sidekiq_1 | 2022-01-03T01:13:17.173Z pid=1 tid=gp5 class=SampleJob jid=b452e81949a58ad40be54307 INFO: Performed SampleJob (Job ID: 0df2f507-3e67-4fc9-a3ee-5e768d554877) from Sidekiq(default) in 10595.9ms sidekiq_1 | 2022-01-03T01:13:17.181Z pid=1 tid=gp5 class=SampleJob jid=b452e81949a58ad40be54307 elapsed=16.92 INFO: done sidekiq_1 | 2022-01-03T01:14:00.220Z pid=1 tid=gnh INFO: queueing SampleJob (SampleJob) sidekiq_1 | 2022-01-03T01:14:00.222Z pid=1 tid=gwd class=SampleJob jid=fd5771e57a45b22f2d98da82 INFO: start sidekiq_1 | 2022-01-03T01:14:00.223Z pid=1 tid=gnh INFO: Enqueued SampleJob (Job ID: b712844c-1ba6-4774-b31d-53e857caaf95) to Sidekiq(default) sidekiq_1 | 2022-01-03T01:14:01.788Z pid=1 tid=gwd class=SampleJob jid=fd5771e57a45b22f2d98da82 INFO: Performing SampleJob (Job ID: b712844c-1ba6-4774-b31d-53e857caaf95) from Sidekiq(default) enqueued at 2022-01-03T01:14:00Z sidekiq_1 | =====サンプルジョブです====== sidekiq_1 | 2022-01-03T01:14:01.791Z pid=1 tid=gwd class=SampleJob jid=fd5771e57a45b22f2d98da82 INFO: Performed SampleJob (Job ID: b712844c-1ba6-4774-b31d-53e857caaf95) from Sidekiq(default) in 0.22ms sidekiq_1 | 2022-01-03T01:14:01.793Z pid=1 tid=gwd class=SampleJob jid=fd5771e57a45b22f2d98da82 elapsed=1.571 INFO: done 参照: https://kerubito.net/technology/3315 https://github.com/moove-it/sidekiq-scheduler テスト用のメール定期配信ジョブの設定、コンソール上で実行 まずは通常通り、メイラーのメソッド・ビューの記述。 app/mailers/user_mailer.rb class UserMailer < ApplicationMailer def weekly_notification user = User.first mail to: user, subject: "test mail" end end app/views/user_maier/weekly_notifications.html.erb <p>これはテスト用メールです<p> Jobの作成 console docker-compose run web bundle exec rails g job WeeklyMailJob app/jobs/weekly_mail_job.rb class WeeklyMailJob < ApplicationJob queue_as :default def perform(*args) UserMailer.weekly_notification.deliver_now puts "==== send WeeklyMail!! ====" end end docker-compose upで確認。 letter_opener_webのgemを使えばメール配信されていることを確認できると思います。今回は割愛。 実装(本番環境) ここからはredis,sidekiqをHerokuで使えるようにしていきます。 実装の流れ(本番環境) heroku redisの導入 circleci.yml, Dockerfile, docker-compose.ymlの記述 デプロイ、ログ・メール受信の確認 参考: Worker dyno、バックグラウンドジョブ、キューイング(Heroku公式) https://devcenter.heroku.com/ja/articles/background-jobs-queueing heroku redisの導入 heroku redisの導入 まずはheroku redisのadd-onを自分のアプリに導入。 コンソールからでも、ダッシュボードからでもOKです。 ダッシュボードからはこちらから↓。 https://elements.heroku.com/addons/heroku-redis ※デフォルトではredisのversionは6.2が設定される。 (https://devcenter.heroku.com/articles/heroku-redis#version-support-and-legacy-infrastructure) ※Heroku上でインスタンスを生成すると、REDIS_URL環境変数は自動で上書き設定される。 If you’ve manually created a REDIS_URL config var on your app, it is overwritten when you add your first heroku-redis add-on. 参照: https://devcenter.heroku.com/articles/heroku-redis#create-an-instance worker dynoを導入 RailsのWEBアプリをHeroku上で動かす場合、 自動でWEBプロセス(web dyno)を作り、その中で動作している (Herokuのdyno ≒ AWSのEC2インスタンス)。 Heoku上でsidekiqを動作させるには、web dynoと並列で、 worker dynoが必要です。 プロセスモデルについて詳細は以下を参照。 https://devcenter.heroku.com/ja/articles/process-model ここでは、webプロセス(Railsアプリケーションのフロントエンド)とworkerプロセス(sidekiq)を用意して、 1,webプロセスがクライアントからのリクエストを受信 2,webプロセスがタスクをジョブキュー(Redis)に追加 3,workerプロセスがキューに溜まったタスクを検知し、取り出して実行 という処理を実装します。 つまり構成のイメージを図にすると、以下の通りです。 ===== web dyno(rails app) ↓ redis ↑ worker dyno(sidekiq) ===== また、Dockerを使わずにデプロイする場合は プロジェクトのルートに以下を記載したProcfileを用意して Sidekiq用のworkerのプロセスタイプを定義し、herokuにpushすればOKです。 (参考:https://madogiwa0124.hatenablog.com/entry/2021/03/28/161255) 今回は、Dockerおよびherokuコンテナレジストリの仕組みを使ってデプロイするので、 Dockerfile,CircleCIのymlファイルを修正して、 web dyno, worker dynoを作ります。 circleci.yml, Dockerfile, docker-compose.ymlの記述 実装方針 デプロイの設定(circleci/config.ymlの記述)は以下の通り。 sidekiq導入前) rails app <= Dockerfile heroku container:push web app_name 導入後) rails app <= Dockerfile.web sidekiq <= Dockerfile.worker heroku container:push --recursive app_name 参照: https://devcenter.heroku.com/ja/articles/container-registry-and-runtime https://qiita.com/sho7650/items/9654377a8fc2d4db236d 実際に、各種設定ファイルを修正します。 Dockerfile.web ファイル名変更 Dockerfile => Dockerfile.web Dockerfile.worker Dockerfile.webと最後のCMD以外同じ内容です。 (本当はうまく共通化したいが、今のところ後回し。。) (うまいやり方あれば、教えていただけますと幸いです。。) Dockerfile.worker FROM ruby:2.7.4 ENV RAILS_ENV=production RUN 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 -qq \ && apt-get install -y nodejs yarn build-essential default-mysql-client WORKDIR /app COPY ./src /app RUN bundle config --local set path 'vendor/bundle' \ && bundle install COPY start_worker.sh /start_worker.sh RUN chmod 744 /start_worker.sh CMD [ "sh", "/start_worker.sh" ] こちらも追加。 start_worker.sh #! /bin/sh if [ "${RAILS_ENV}" = "production" ] then bundle exec rails assets:precompile fi bundle exec sidekiq docker-compose.yml 以下、変更点のみ記載。 dokcer-compose.yml # 変更前 web: &app build: .  (中略) sidekiq: build: context: . dockerfile: Dockerfile.worker command: bundle exec sidekiq # 変更後 web: &app build: context: . dockerfile: Dockerfile.web  (中略) sidekiq: build: context: . dockerfile: Dockerfile.worker .circleci/config.yml 以下、変更点のみ記載。 .circleci/config.yml # 変更前 deploy: steps: - run: name: push docker image command: heroku container:push web -a $HEROKU_APP_NAME - run: name: release docker image command: heroku container:release web -a $HEROKU_APP_NAME # 変更後 deploy: steps: - run: name: push docker image command: heroku container:push web worker --recursive -a $HEROKU_APP_NAME - run: name: release docker image command: heroku container:release web worker -a $HEROKU_APP_NAME 本番環境で毎分メールはさすがにしんどいので、 確認用として、「5分ごとにメール設定」に変更します。 sidekiq.yml :schedule: weekly_mail: cron: '0 */5 * * * *' class: WeeklyMailJob デプロイ、ログ・メール受信の確認 デプロイし、本番環境で立ち上げ確認。 コンソールで「worker dynoが起動していること」と「SampleJobが実行できること」を確認。 console # worker dynoが起動していることを確認 $ heroku ps -a kyodokoza === web (Free): sh /start.sh (1) web.1: up 2022/01/08 16:56:49 +0900 (~ 8m ago) === worker (Free): sh /start_worker.sh (1) worker.1: up 2022/01/08 16:54:42 +0900 (~ 10m ago) # SampleJobが実行できることを確認 $ heroku config -a app_name irb(main):001:0> SampleJob.perform_later herokuのlog 2022-01-08T07:33:14.282983+00:00 app[worker.1]: =====サンプルジョブです====== また、ログを見れば、 自動で5分ごとにweekly_mailのjobが実行されていることも確認できます。 herokuのlog 2022-01-08T07:35:01.151945+00:00 app[worker.1]: ==== send WeeklyMail!! ==== (中略) 2022-01-08T07:40:00.839061+00:00 app[worker.1]: ==== send WeeklyMail!! ==== (中略) 2022-01-08T07:45:00.570713+00:00 app[worker.1]: ==== send WeeklyMail!! ==== 問題なし! あとは自分の好みにスケジュールを修正しましょう。 今回は毎週月曜の朝9時に送信されるように設定します。 sidekiq.yml :schedule: weekly_mail: cron: '0 0 9 * * 1' # Runs at 9:00 every Monday class: WeeklyMailJob これでOK!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Lightsailのオブジェクトストレージバケットにユーザー画像を保存する

やりたいこと Railsでフランス語のWebアプリを個人開発してます。 developmentではS3で画像を保存していたのですが、本番デプロイするにあたり、Lightsailでアプリを運用することにしたのでLightsailのオブジェクトストレージを使うことにしました。 12ヶ月無料だって。やったー! 元々のS3での実装がどうなってたのか、記憶から消えていたのでこの辺を読んで復習するところから始まりました。 関連する該当箇所は以下な感じです。 Gemfile gem 'image_processing', '~> 1.2' user.rb has_one_attached :image users_controller.rb def users_params params.require(:user).permit(:account_name, :image) end config/storage.yml amazon: service: S3 access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> region: ap-northeast-1 bucket: MY_BUCKET_NAME どこかでS3クライアント作ったり、PutObjectとかしているのかと思ったらやってなくてgemがいい感じにしてくれているみたい...。今回大事なのはconfig/storage.ymlで、というかだけで、AdministratorAccessのあるIAMユーザーのアクセスキーを渡していました。 Lightsailのオブジェクトストレージも結局実態はS3ではあるのだろうけど、どうやってやるのだろう...。 実装! 結論 いっろいろ遠回りしたけど、結論はバケットのアクセスキーみたいです。 ホーム => ストレージ => 該当のバケット選択 => アクセス権限 => アクセスキー ここでアクセスキーを作成して、アクセスキーIDとシークレットアクセスキーを取得し、今まで環境変数なり、credentialsなりで、IAMのaccess_key_idとsecret_access_keyをセットしていたところを置き換えます。 これだけです。 一応ドキュメントはこれになるぽいです。操作は簡単なので、これ見なくてもできますが。 https://lightsail.aws.amazon.com/ls/docs/ja_jp/articles/amazon-lightsail-creating-bucket-access-keys 以上で終了ですが、参考までに以下に空振りしたものも書いておきます。 空振り1:アカウントのAPIアクセスキー 当初こうやって怒られていました。 Aws::S3::Errors::SignatureDoesNotMatch at / The request signature we calculated does not match the signature you provided. Check your key and signing method. そういえば、Lightsailをいじり始めた当初からLightsailと他のAWSサービスって画面が違うし、IAMとかどうなるんだろう?と疑問に思っていたのですよね。Lightsailコンソールをいじってみると。 右上にアカウントというメニューがある。 自分がAWSで作ったIAMユーザーになってる。(ここではadminという名前のIAMユーザーを使っている) お、APIアクセスキー。 このドキュメントで、ここに答えがあるかもと思ひつるを。 https://lightsail.aws.amazon.com/ls/docs/en_us/articles/lightsail-how-to-set-up-access-keys-to-use-sdk-api-cli Choose the name of the user for which you want to create an access key. The user you choose should have full access or specific access to Lightsail actions. IAMユーザーはフルアクセスかLightsailのactionsに対するアクセス権限を持ってないとダメと書いてありますが、このLightsailに使われているIAMユーザーはAdministratorAccessでProvides full access to AWS services and resources.だから既に"full access"ある...うーん。 試しに新規でIAMユーザーを作って、明確にLightsailを許可するポリシーをアタッチしてそのアクセスキーをセットしてみる。 (既存のポリシーにLightsailはないのでポリシーの作成ボタンから作る。) (作ったIAMのapi_access_key, secret_access_keyをcredentialsにセットする。) これをやってRailsサーバーを再起動して動作確認してみると、下記の通り違うエラーになりました。 Aws::S3::Errors::AccessDenied Access Denied 今度はバケットに拒否されている模様。 空振り2:バケットのアクセス権限 デフォルトのすべてのオブジェクトはプライベートです => すべてのオブジェクトはパブリックで読み取り専用ですと変更してみる。 ダメ。 ちなみに一番最初のうまくいった例では、ここをすべてのオブジェクトはプライベートですに戻してもきちんと動作しました。 最後に なんか的外れなこと書いてたら教えてください〜?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsのモデル同士の関連付けについて

概要 Ruby on Railsにおけるモデル同士の関連付けについてアウトプットしたいと思います。 環境 ruby: '3.0.1' rails: '6.1.4' 前提 関連付けの説明にあたって下記モデルを前提とします。  モデル   User  id 1 name 'example_name' モデル Post id 1 content 'example_content' user_id 1 モデル Account id 1 account_number 1234 user_id 1 関連付けとは 関連付けとは異なるモデル同士をつなげてコードを簡素化するために行うものです。 関連付けを行っていない場合と行っている場合のコードの違いを見てみましょう。 userの投稿一覧を取得するというコードを比較します。 関連付けを行っていない場合 user = User.find(1) posts = Post.find_by(user_id: user.id) 関連付けを行っている場合 user = User.find(1) posts = user.posts 関連付けの目的 関連付けの目的は主に2点あります。 1. コードの簡素化 上記の例を見ると分かる通り投稿を作成する際にuser_idを気にしなくて良いので記載するコード量が少なくなりますし、 コードも直感的でわかりやすくなります。 2. コード記載ミスを減らすこと 関連付けを行っていない場合では、上記の例だと投稿を作成する際にuser_idを気にかけないといけなくなります。 つまり投稿を作成するときは必ずuser_idの記載が必要になるので、必然的にコード量が増え、記載ミス等が起きやすくなります。 関連付けの種類 関連付けの種類は3つあります。 1. belongs_to 使い方 models/post.rb class Post < ApplicationRecord belongs_to :user # 関連名は必ず単数形 end この関連付けはUserモデルと従属の関係を作ります。 つまり、投稿には必ずユーザーが必要になるということです。 ユーザーのいない投稿は作成できません。 よくhas_manyを定義したモデルの相方で定義されます。 また下記のように投稿したユーザーを取得できるようにもなります。 post = Post.find(1) post.user #=> 投稿したユーザー情報が返ってきます。 2. has_many 使い方 models/user.rb class User < ApplicationRecord has_many :posts # 関連名は必ず複数形 end この関連付けはPostモデルと1対多の関係を作ります。 つまり一人のユーザーに対して投稿が複数存在するということです。 よくbelongs_toを定義したモデルの相方で定義されます。 これによりユーザーの投稿一覧を取得できるようになります。 user = User.find(1) user.posts #=> ユーザーの投稿一覧が返ってくる またuser_idを気になくても下記のように投稿を作成することもできます。 user = User.find(1) post = user.posts.create(content: 'new_content') # 作成の際にuserから自動的にuser_idを取得してくれる 3. has_one 使い方 models/user.rb class User < ApplicationRecord has_one :account # 関連名は必ず単数形 end 関連する2つのモデルでそれぞれ定義する。 この関連付けはUserモデルとAccountモデルで1対1の関係を作ります。 よくbelongs_toを定義したモデルの相方で定義されます。 つまりユーザーは必ず1つのアカウントIDを持っているということになります。 下記のようにユーザーのアカウントを取得することもできるようになります。 user = User.find(1) user.account #=> userのアカウント情報を取得できる まとめ その他にも関連付けの際に使えるオプションもあるので別記事にて記載したので下記をご確認ください。  参考文献 Railsガイド 【Rails】 アソシエーションを図解形式で徹底的に理解しよう!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Carrierwaveで公開されているファイルURLからデータを保存する

やりたいこと 既に公開されている画像のURLからCarrierwaveを介してファイルを保存する。 ※ 試したのは画像だけですが他のファイルでもできそう 結論 # CarrierWave の Uploader class AvatarUploader < CarrierWave::Uploader::Base storage :file end # ActiveRecord class User < ApplicationRecord mount_uploader :avatar, AvatarUploader end User.create!(remote_avatar_url: "https://example.com/***.png") remote_**_url に値を指定することによって解決しました。 よくよくみるとgithubのreadmeにもformの例として remote_**_url に値をセットする方法が紹介されてました。 ちゃんとおってないですが多分ここら辺のActiveRecordへのパッチでこのメソッドが早されているのかなという感じでした。 ダメなパターン User.create!(avatar: URI.open("https://example.com/***.png")) 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails new 時に "ActiveSupport::EncryptedFile::InvalidKeyLengthError" エラーが出た時の対応

タイミングは rails new に限りませんが、rails でコマンドを叩いた時に以下のようにActiveSupport::EncryptedFile::InvalidKeyLengthError が出ることがあったので対処方法を書きます。 $ rails new xxxxx /Users/yyyyyy/.rbenv/versions/3.0.3/lib/ruby/gems/3.0.0/gems/activesupport-7.0.1/lib/active_support/encrypted_file.rb:114:in `check_key_length': Encryption key must be exactly 32 characters. (ActiveSupport::EncryptedFile::InvalidKeyLengthError) # 環境情報 $ ruby -v ruby 3.0.3p157 (2021-11-24 revision 3fb7d2cadc) [arm64-darwin21] $ rails -v Rails 7.0.1 原因 環境変数に設定していた RAILS_MASTER_KEY が適切な文字数 (=32) ではなかったことが原因でした。 $ export -p | grep RAILS_MASTER_KEY declare -x RAILS_MASTER_KEY="hogehoge" ? ここ 背景 検証中、ローカル端末の ~/.bash_profile 内で設定している環境変数のうち RAILS_MASTER_KEY に一時的に適当な値を設定していました。 (Rails 5.2 以上で実装された credentials.yml と master.key の仕組みで使う環境変数) しかし RAILS_MASTER_KEY は 32 文字であるという制限事項が設けられているため、適当な値をセットしていると弾かれる仕組みのようです。 対策 検証で一時的に適当な値をセットする際は、RAILS_MASTER_KEY には 32 文字の文字列をセットしましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[記事編集中]Look Back - 振り返る サブスク管理 -

〜作成中〜 作成したオリジナルアプリについて紹介します。 READMEはこちら READMEに記載がないもの 下記からジャンプできます。 2.制作背景  きっかけ  解決策  どうしてその仕組にしたのか 3.アプリテーマ 5.工夫したこと →gif画像付きで解説をしています 6.Q&A  簡単な振り返りに意味があるのか?  カリキュラム外で取り入れたものは何か 全体の目次 1.アプリ概要 2.制作背景  きっかけ  解決策  どうしてその仕組にしたのか 3.アプリテーマ 4.機能一覧 5.工夫したこと 6.Q&A  簡単な振り返りに意味があるのか?  カリキュラム外で取り入れたものは何か アプリ概要 小さな行動につなげるための、サブスク管理アプリです。 このアプリでできることは、以下の通りです。 サブスク情報の一括管理 累計金額・契約期間等、サブスク情報の閲覧 活用度の振り返り・アクションプラン設定 制作背景 きっかけ 自身のサブスク活用に課題を感じていたことがきっかけです。 課題に感じていたことは、以下の2つです。 サブスクには金額に見合った価値があるが、自己投資としては損益が出ている。 一括10万円の買い物の前は悩むのに、毎月1000円のサブスクは気軽に支払っている。 解決策 アクションプランの設定をする 自己投資の利益である「自己成長・現状改善という結果」を出すために、 サブスクから得た情報を実際に活用する必要がある。 そのために、行動につながる「アクションプラン」の設定を義務化する。 振り返りを習慣化する コンテンツの良し悪しに加え、「どれくらい活用できたか」という活用度を意識することで、 自己投資金額を釣り合いが取れているか判断する様になる。 そのために、活用度の振り返りを習慣化する。 どうしてその仕組にしたのか プログラミング学習を通して、実践なくして習得・成長はないと再認識したから。 毎日の学習終了後、振り返り記入している際に、ハッと気づきを得る経験を何度もした為。 アプリテーマ このアプリが目指すのは、「小さな行動につなげる」ことです。 具体的には、情報を閲覧するといった受け身な活用法から、 得た情報で行動するという実践的な活用法につなげます。 機能一覧 現在実装済の機能は、下記の通りです。 各機能の紹介 サブスク評価機能 →Jamp:簡単な振り返りで意味があるのか 工夫したこと 行動につながるアプリ設計をした サブスク管理機能に振り返り機能を加え、サブスクの活用を保証する設計にした 振り返り項目でアクションプランを必須にし、行動を促す仕様にした ☆評価で手軽な評価を実現し、振り返りを継続するハードルを下げる仕様にした 実際のサブスクに対応できるように、契約プランを充実させた 年、月、日のタイプを選択可能 年、月の場合は、「1日はじまりか否か」も選択可能 サブスク登録時、契約開始日が過去の場合にも対応した 昔から登録しているサブスクも、過去期間を含めた累計金額・契約期間を計上する 利用しやすいUIを心がけて設計した レスポンシブ対応にした 削除前に確認アラートを表示し、誤操作によるデータ消失を防止した。 更新待ちのサブスクには通知アイコンを付け、目立たせた 感覚的な操作ができるように、アイコン、hoverイベントを使用 アイコン イベント サブスク登録時、自動で当日の日付が入る(カレンダー選択不要) ハンバーガーメニューから、ワンクリックで編集・削除・解約操作ができる 後で振り返る機能を利用し、内容の一時保存ができる 複数のサブスクで「後で振り返るレビュー」が存在しても、未評価一覧から処理できる 気づきを得るきっかけを得られるように、様々な角度の情報を表示した サブスク契約金額だけでなく、累計金額・期間を表示 解約理由・コメントを残すことで、過去の判断・価値観を見返す事が可能 平均評価も表示することで、ロングスパン視点も持つことができる Q&A 簡単な振り返りに意味があるのか? 2つの理由から、「意味がある」と考えています。 気づきにつながる 作業としては☆を選択するだけでも、 どのくらいアクションしたか?を☆数で評価するには、 「何か行動しただろうか」「いつ」「どのくらい」など、自然と振り返る思考が生まれます。 振り返ることで、「意外と行動はできなかったな...」など、気付きにつながるからです。 行動につながる アクションプランの設定は必須にしているため、 次回更新日までに「何をするか」を決めることになります。 行動を誘発することで、成長につながると考えています。 カリキュラム外で取り入れたものは何か tailwindcss フロント実装スピードを上げるため sweetalert2 アラートデザインを変更するため rary ☆評価を行うため Active Support コア拡張機能 beginning_of_monthメソッド等を利用するため トランザクション処理 サブスク登録時に、契約更新情報も同時登録するため サブスク更新時に、振り返りを行うため canva トップページに表示するロゴを作成するため 学んだこと・感想 技術編 問題を解くことと、問題を解決することの違い 仕様通りのアプリを実装するのは、問題を「解く」ことであり、 オリジナルアプリを要件定義から行うことは、問題を「解決する」ことだと感じました。 課題通りのアプリの作成中に、 エラー解消に取り組んだり、実装方法に悩むことも勿論ございました。 しかし、 どの方法を使うのが最適か? この方法で問題が解決出来るか? バリデーションに漏れはないか?と、 方法から検証することは、プログラミング知識だけでは解決できないと実感しました。 アイディアを文字に落とし込む言語化能力や 設計を組み立てる論理的思考力など、 不足している能力を実感できる経験となりました。 また、足りないが故に、失敗・修正を繰り返しましたが、 工夫する余地の大きさは「やりがい」に、 難しさは「できたときに充実感」に変換されました。 エンジニアの仕事の本質は、 相手の問題を解決したり、ニーズを満たすことだと思いますので、 プログラミング知識をしっかりつけながら、 ヒューマンスキルも磨いていきたいと思います。 デザインの持つ力 同じ情報が表示されていても、ザインによって、情報の理解しやすさが変わることを体感し、 デザインのもつ力・奥深さを学びました。 Qiitaの記事やノンデザイナーズ・デザインブックの挿絵を見て、 デザインの四原則を少し意識するだけで、 ガラッと印象が変わることを知り、衝撃を受けました。 UI
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Look Back - 振り返る サブスク管理 -

作成したオリジナルアプリについて紹介します。 READMEはこちら READMEに記載がない項目一覧 下記からジャンプできます。 2.制作背景  きっかけ  解決策  どうしてその仕組にしたのか 3.アプリテーマ 7.工夫したこと  (GIF画像を追加し、解説をしています) 8.学んだこと  技術編  マインド編 9.Q&A  簡単な振り返りに意味があるのか?  カリキュラム外で取り入れたものは何か 全体の目次 1.アプリ概要 2.制作背景  きっかけ  解決策  どうしてその仕組にしたのか 3.アプリテーマ 4.設計  画面遷移図  DB設計 5.機能一覧 6.各機能の紹介  ユーザー登録機能  ログイン機能  ログアウト機能  サブスク登録機能  サブスク編集機能  サブスク削除機能  サブスク一覧表示機能  サブスク詳細表示機能  サブスク更新機能  サブスク評価機能  サブスク評価一覧表示機能  サブスク評価詳細表示機能  サブスク評価編集機能  サブスク未評価一覧表示機能  サブスク解約機能  解約サブスク一覧表示機能  解約サブスク詳細表示機能 7.工夫したこと 8.学んだこと  技術編  マインド編 9.Q&A  簡単な振り返りに意味があるのか?  カリキュラム外で取り入れたものは何か アプリ概要 小さな行動につなげるための、サブスク管理アプリです。 このアプリでできることは、以下の通りです。 サブスク情報の一括管理 累計金額・契約期間等、サブスク情報の閲覧 活用度の振り返り・アクションプラン設定 制作背景 きっかけ 自身のサブスク活用に課題を感じていたことがきっかけです。 課題に感じていたことは、以下の2つです。 サブスクには金額に見合った価値があるが、自己投資としては損益が出ている。 一括10万円の買い物の前は悩むのに、毎月1000円のサブスクは気軽に支払っている。 解決策 アクションプランの設定をする 自己投資の利益である「自己成長・現状改善という結果」を出すために、 サブスクから得た情報を実際に活用する必要がある。 そのために、行動につながる「アクションプラン」の設定を義務化する。 振り返りを習慣化する コンテンツの良し悪しに加え、「どれくらい活用できたか」という活用度を意識することで、 自己投資金額を釣り合いが取れているか判断する様になる。 そのために、活用度の振り返りを習慣化する。 どうしてその仕組にしたのか プログラミング学習を通して、実践なくして習得・成長はないと再認識したから。 毎日の学習終了後、振り返り記入している際に、ハッと気づきを得る経験を何度もした為。 アプリテーマ このアプリが目指すのは、「小さな行動につなげる」ことです。 具体的には、情報を閲覧するといった受け身な活用法から、 得た情報で行動するという実践的な活用法につなげます。 設計 画面遷移図 DB設計 users Column Type Options email string null: false, unique: true encrypted_password string null: false nickname string null: false Association has_many :subscriptions has_many :reviews subscriptions Column Type Options name string null: false price integer null: false contract_date date null: false update_type_id integer null: false update_cycle integer null: false update_day_type_id integer user references null: false, foreign_key: true Association belongs_to :user has_many :reviews has_one :contract_renewal has_one :contract_cancel contract_renewals Column Type Options renewal_count integer null: false total_price integer null: false total_period integer null: false update_date date null: false next_update_date date null: false subscription references null: false, foreign_key: true Association belongs_to :subscription contract_cancels Column Type Options cancel_date date null: false reason_id integer null: false cancel_comment text subscription references null: false, foreign_key: true Association belongs_to :subscription reviews Column Type Options review_rate integer review_comment text start_date date null: false end_date date null: false later_check_id integer null: false user references null: false, foreign_key: true subscription references null: false, foreign_key: true Association belongs_to :user belongs_to :subscription has_one :action_plan action_plans Column Type Options action_rate integer action_review_comment text action_plan text review references null: false, foreign_key: true Association belongs_to :review 機能一覧 現在実装済の機能は、下記の通りです。 各機能の紹介 ユーザー登録機能 + トップページで、sign upをクリック + 必要事項を入力し、Createをクリック ログイン機能 ヘッダーのLoginをクリック 必要事項を入力し、Loginをクリック ログアウト機能 ヘッダーのLogoutをクリック サブスク登録機能 + マイページで、Addをクリック + 必要事項を入力・選択し、登録するをクリック サブスク編集機能 + マイページで、サブスクのハンバーガーメニューをクリック + メニューから、編集をクリック + 必要事項を入力・選択し、更新するをクリック + 確認ホップアップが表示されるので、OKをクリック + 更新完了のホップアップが表示されるので、OKをクリック サブスク削除機能 + マイページで、サブスクのハンバーガーメニューをクリック + メニューから、削除をクリック + 確認ホップアップが表示されるので、OKをクリック + 削除完了のホップアップが表示されるので、OKをクリック サブスク一覧表示機能 + マイページトップ(Listsタブ)で、契約中のサブスク一覧が確認できる サブスク詳細表示機能 + マイページで、サブスクのカードをクリック + サブスク情報を確認できる サブスク更新機能 + サブスク更新日になると、通知アイコンが表示される + 次回更新日がClickで更新に変化する + Clickで更新をクリック + 更新完了後、振り返りを促すホップアップが表示されるので、Goをクリック + 更新完了後のホップアップで、後で振り返るをクリックし、後で振り返ることも可能 サブスク評価機能 + サブスク更新後に表示される画面に、必要事項を入力する + レビューをクリック + 後で振り返るをONにすると、現在の入力を一時保存できる サブスク評価一覧表示機能 + マイページで、サブスクのカードをクリック + Reviewのもっと見る→をクリック + 過去のレビュー 一覧が確認できる サブスク評価詳細表示機能 + マイページで、サブスクのカードをクリック + Reviewのもっと見る→をクリック + 閲覧したいレビューのカードのLook More→をクリック + 入力したレビューの詳細を確認できる サブスク評価編集機能 + マイページで、サブスクのカードをクリック + Reviewのもっと見る→をクリック + 閲覧したいレビューのカードのLook More→をクリック + Editをクリック + 必要事項を入力して、レビューをクリック サブスク未評価一覧表示機能 + マイページで、Reviewsタブをクリック + 後で振り返るをONにしているレビュー 一覧を確認できる サブスク解約機能 + マイページで、サブスクのハンバーガーメニューをクリック + メニューから、解約をクリック + 必要事項を入力・選択し、解約するをクリック 解約サブスク一覧表示機能 + マイページで、Cancelsタブをクリック + 解約済のサブスク一覧を確認できる 解約サブスク詳細表示機能 + マイページで、Cancelsタブをクリック + 閲覧したいサブスクカードをクリック + サブスク詳細ページに解約理由が表示される 工夫したこと 行動につながるアプリ設計をした サブスク管理機能に振り返り機能を加え、サブスクの活用を保証する設計にした 振り返り項目でアクションプランを必須にし、行動を促す仕様にした ☆評価で手軽な評価を実現し、振り返りを継続するハードルを下げる仕様にした 実際のサブスクに対応できるように、契約プランを充実させた 年、月、日のタイプを選択可能 年、月の場合は、「1日はじまりか否か」も選択可能 サブスク登録時、契約開始日が過去の場合にも対応した 昔から登録しているサブスクも、過去期間を含めた累計金額・契約期間を計上する 利用しやすいUIを心がけて設計した レスポンシブ対応にした 削除前に確認アラートを表示し、誤操作によるデータ消失を防止した。 更新待ちのサブスクには通知アイコンを付け、目立たせた 感覚的な操作ができるように、アイコン、hoverイベントを使用      サブスク登録時、自動で当日の日付が入る(カレンダー選択不要) ハンバーガーメニューから、ワンクリックで編集・削除・解約操作ができる 後で振り返る機能を利用し、内容の一時保存ができる 複数のサブスクで「後で振り返るレビュー」が存在しても、未評価一覧から処理できる 気づきを得るきっかけを得られるように、様々な角度の情報を表示した サブスク契約金額だけでなく、累計金額・期間を表示 解約理由・コメントを残すことで、過去の判断・価値観を見返す事が可能 平均評価も表示することで、ロングスパン視点も持つことができる 実際の開発を意識したアプリケーション作成を心がけた プルリクエストでWhat, Whyを記述 プルリクエスト前に、単体テスト・RuboCopの実行 要件定義、画面遷移図を作成後実装開始 学んだこと・感想 技術編 問題を解くことと、問題を解決することの違い 仕様通りのアプリを実装するのは、問題を「解く」ことであり、 オリジナルアプリを要件定義から行うことは、問題を「解決する」ことだと感じました。 課題通りのアプリの作成中に、 エラー解消に取り組んだり、実装方法に悩むことも勿論ございました。 しかし、 どの方法を使うのが最適か? この方法で問題が解決出来るか? バリデーションに漏れはないか?と、 「どうやって」といった方法から検証することは、 プログラミング知識プラス、 言語化する能力や、論理的思考力などが必要だと実感しました。 また、足りないが故に、失敗・修正を繰り返しましたが、 工夫する余地の大きさは「やりがい」に、 難しさは「できたときに充実感」に変換されました。 エンジニアの仕事の本質は、 相手の問題を解決したり、ニーズを満たすことだと思います。 そのため、プログラミング知識をしっかりつけながら、 問題解決ができるエンジニアを目指していきたいと思いました。 デザインの持つ力 ユーザーが利用しやすいアプリケーションを実現するには、 デザインが必須だと学びました。 具体的には、Qiitaの記事「なぜエンジニアが作る画面はダサいのか…?「理由」と「対策」を徹底解説【エンジニア向け画面デザイン講座】」や、 ノンデザイナーズ・デザインブックの挿絵を見て、 デザインの四原則を適応することで、印象がガラッと変わる視覚的体験をし、 デザインのもつ力・奥深さに衝撃を受けました。 同じ情報が表示されていても、デザインによって、 情報の理解しやすさが変わることを体感し、デザインの重要さを学びました。 また、上記を参考にマイページのビューを作り変えた一例が下記です。 マインド編 絶望とうまく付き合う方法 オリジナルアプリ実装に当たり、何度も実力不足を感じるタイミングがございました。 その際、建設的に考えるために次のように捉えるようにしました。 ・実力不足だと感じるのは、今の自分にとって難しいと感じることに「挑戦中」である。 ・レベルが低いと感じるなら、それだけ「伸びしろ」があり、想像もできない様な成長が可能である。 知識・実力不足を受け入れながら、 前向きな捉え方もすることで、 継続的な学習モチベーションの維持になったと考えています。 本心の確認 自身のキャリアと現状、なぜプログラミングなのか、と 転職を考える前に自問し、自分なりに答えを出した上で、学習を開始しました。 しかし、それが口だけでないかどうか、 プログラミングを都合の良い魔法の道具や逃げ場にしていないかは、 「行動」で確かめるのが良いと考えていました。 実際アプリ実装中に、何度も絶望を感じながらも、 「作り上げたい、作ってみたい」「できる事が増えて楽しい」と感じていました。 プロトタイプを完成まで、簡単に諦めない・逃げなかったことから、 「エンジニア転職をする」と決めたのは意志であり、本心であったと感じました。 学習をすることと、仕事にすることは、勿論異なりますが、 半年間仕事と両立しながら学習を進めたこと、 オリジナルアプリを制作したこと、 アウトプットとしてブログを投稿したこと、 メンター質問を200問以上利用したことなど、 行動ができた=中途半端な気持ちではないと、自身では思っております。 Q&A 簡単な振り返りに意味があるのか? 2つの理由から、「意味がある」と考えています。 気づきにつながる 作業としては☆を選択するだけでも、 どのくらいアクションしたか?を☆数で評価するには、 「何か行動しただろうか」「いつ」「どのくらい」など、自然と振り返る思考が生まれます。 振り返ることで、「意外と行動はできなかったな...」など、気付きにつながるからです。 行動につながる アクションプランの設定は必須にしているため、 次回更新日までに「何をするか」を決めることになります。 行動を誘発することで、成長につながると考えています。 カリキュラム外で取り入れたものは何か tailwindcss フロント実装スピードを上げるため sweetalert2 アラートデザインを変更するため rary ☆評価を行うため canva トップページに表示するロゴを作成するため
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby 2.5.0コンテナでRails6の開発環境を作るときのトラブルシューティング

こちらのハンズオンをやっていたところ、いくつかの問題に遭遇したので備忘録。 Rails アプリケーションをコンテナで開発しよう ! 第 1 回 - まずは Rails アプリケーション作りから Rails7のインストールに失敗する 記事では Ruby2.5 & Rails6 を使用しているが、現時点(2022/1)での最新版は 7.0.1。 エラーメッセージにもあるように7.0.1 ではRuby2.7以上が必要であるため gem install railsに失敗する。 root@810c33297af8:/work# gem install rails Fetching: concurrent-ruby-1.1.9.gem (100%) Successfully installed concurrent-ruby-1.1.9 Fetching: i18n-1.8.11.gem (100%) Successfully installed i18n-1.8.11 Fetching: tzinfo-2.0.4.gem (100%) Successfully installed tzinfo-2.0.4 Fetching: activesupport-7.0.1.gem (100%) ERROR: Error installing rails: There are no versions of activesupport (= 7.0.1) compatible with your Ruby & RubyGems. Maybe try installing an older version of the gem you're looking for? activesupport requires Ruby version >= 2.7.0. The current ruby version is 2.5.0. gem install のオプションでRailsのバージョンを指定してインストールすることとした。 root@810c33297af8:/work# gem install -v 6.0.3.2 rails Nokogiriのインストールに失敗する さて、Railのバージョン指定をして改めてgem installを実施したところ、 root@810c33297af8:/work# gem install -v 6.0.3.2 rails ... Fetching: activesupport-6.0.3.2.gem (100%) Successfully installed activesupport-6.0.3.2 ERROR: While executing gem ... (Gem::RemoteFetcher::FetchError) bad response Forbidden 403 (https://api.rubygems.org/quick/Marshal.4.8/nokogiri-1.13.0-x64-unknown.gemspec.rz) Nokogiri 1.13.0のインストールに失敗している。 403なのでライブラリが不足している的なやつではないみたい。 rubygems.org でNokogiriの過去バージョンを調べて 1.12.5 を予めインストールすることで解決。 root@810c33297af8:/work# gem install nokogiri -v 1.12.5 Fetching: nokogiri-1.12.5-x86_64-linux.gem (100%) Successfully installed nokogiri-1.12.5-x86_64-linux 1 gem installed root@810c33297af8:/work# gem install -v 6.0.3.2 rails Fetching: loofah-2.13.0.gem (100%) Successfully installed loofah-2.13.0 Fetching: rails-html-sanitizer-1.4.2.gem (100%) Successfully installed rails-html-sanitizer-1.4.2 Fetching: rails-dom-testing-2.0.3.gem (100%) Successfully installed rails-dom-testing-2.0.3 Fetching: builder-3.2.4.gem (100%) Successfully installed builder-3.2.4 Fetching: erubi-1.10.0.gem (100%) Successfully installed erubi-1.10.0 Fetching: actionview-6.0.3.2.gem (100%) Successfully installed actionview-6.0.3.2 Fetching: actionpack-6.0.3.2.gem (100%) Successfully installed actionpack-6.0.3.2 Fetching: activemodel-6.0.3.2.gem (100%) Successfully installed activemodel-6.0.3.2 Fetching: activerecord-6.0.3.2.gem (100%) Successfully installed activerecord-6.0.3.2 Fetching: globalid-1.0.0.gem (100%) Successfully installed globalid-1.0.0 Fetching: activejob-6.0.3.2.gem (100%) Successfully installed activejob-6.0.3.2 Fetching: mini_mime-1.1.2.gem (100%) Successfully installed mini_mime-1.1.2 Fetching: mail-2.7.1.gem (100%) Successfully installed mail-2.7.1 Fetching: actionmailer-6.0.3.2.gem (100%) Successfully installed actionmailer-6.0.3.2 Fetching: nio4r-2.5.8.gem (100%) Building native extensions. This could take a while... Successfully installed nio4r-2.5.8 Fetching: websocket-extensions-0.1.5.gem (100%) Successfully installed websocket-extensions-0.1.5 Fetching: websocket-driver-0.7.5.gem (100%) Building native extensions. This could take a while... Successfully installed websocket-driver-0.7.5 Fetching: actioncable-6.0.3.2.gem (100%) Successfully installed actioncable-6.0.3.2 Fetching: mimemagic-0.3.10.gem (100%) Building native extensions. This could take a while... Successfully installed mimemagic-0.3.10 Fetching: marcel-0.3.3.gem (100%) Successfully installed marcel-0.3.3 Fetching: activestorage-6.0.3.2.gem (100%) Successfully installed activestorage-6.0.3.2 Fetching: actionmailbox-6.0.3.2.gem (100%) Successfully installed actionmailbox-6.0.3.2 Fetching: actiontext-6.0.3.2.gem (100%) Successfully installed actiontext-6.0.3.2 Fetching: thor-1.2.1.gem (100%) Successfully installed thor-1.2.1 Fetching: method_source-1.0.0.gem (100%) Successfully installed method_source-1.0.0 Fetching: railties-6.0.3.2.gem (100%) Successfully installed railties-6.0.3.2 Fetching: sprockets-4.0.2.gem (100%) Successfully installed sprockets-4.0.2 Fetching: sprockets-rails-3.4.2.gem (100%) Successfully installed sprockets-rails-3.4.2 Fetching: rails-6.0.3.2.gem (100%) Successfully installed rails-6.0.3.2 29 gems installed
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails 6】Formオブジェクトでシンプルな検索機能を作ってみる

概要 記事タイトル通り、Form オブジェクトを使用したシンプルな検索機能を Rails6 で作ってみます。 Form オブジェクトの概念については、以下の記事の表現が非常にわかりやすかったので参考にされてください。 Formオブジェクトはフォームの責務をカプセル化し、コントローラやビューを疎結合に保つために必要なデザインパターンです。 ユーザの入力の整形や永続化をコントローラだけで行うと、コントローラが肥大化してしまいます。 この原因はコントローラがモデル層の知識をもちすぎるためにあります。 このときビューもフォームを表示するための知識をもつことになるため、コントローラと同じような問題が起こってしまいます。 このことは単一責任の原則に反し、モデル層の変更がコントローラやビューに影響を及ぼすことになります。 逆にActiveRecordモデルにこういった責務をもたせると、今度はActiveRecordモデルがフォームの知識を持ちすぎてしまいます。 フォームという独立した責務があるのであれば、これをひとつのクラスにカプセル化する、というのがFormオブジェクトの役割です。 参照: Railsのデザインパターン: Formオブジェクト 完成イメージ 名前、性別、年齢、都道府県といった複数の条件でユーザーを絞り込んでいく事が可能です。 仕様 Ruby 3 Rails 6 MySQL 5.7 Bootstrap 5 Docker ※ Bootstrap はバージョン5から JQuery が不要になり導入方法が少し変わったのでご注意ください。 下準備 まず最初に下準備から始めていきます。 各種ディレクトリ & ファイルを作成 $ mkdir rails-search-form && cd rails-search-form $ touch Dockerfile docker-compose.yml entrypoint.sh Gemfile Gemfile.lock ./Dockerfile FROM ruby:3.0 RUN curl https://deb.nodesource.com/setup_14.x | bash RUN 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 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs yarn ENV APP_PATH /myapp RUN mkdir $APP_PATH WORKDIR $APP_PATH COPY Gemfile $APP_PATH/Gemfile COPY Gemfile.lock $APP_PATH/Gemfile.lock RUN bundle install COPY . $APP_PATH COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] ./docker-compose.yml version: "3" services: db: image: mysql:5.7 environment: MYSQL_ROOT_PASSWORD: password volumes: - mysql-data:/var/lib/mysql - /tmp/dockerdir:/etc/mysql/conf.d/ ports: - 4306:3306 web: build: context: . dockerfile: Dockerfile command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp - ./vendor/bundle:/myapp/vendor/bundle environment: TZ: Asia/Tokyo RAILS_ENV: development ports: - "3000:3000" depends_on: - db volumes: mysql-data: ./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 "$@" ./Gemfile # frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rails", "~> 6" ./Gemfile.lock # 空欄でOK rails new おなじみのコマンドでアプリの雛型を作成。 $ docker-compose run web rails new . --force --no-deps -d mysql --skip-test database.ymlを編集 デフォルトの状態だとデータベースとの接続ができないので「database.yml」の一部を書き換えます。 ./config/database.yml default: &default adapter: mysql2 encoding: utf8mb4 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: db development: <<: *default database: myapp_development test: <<: *default database: myapp_test production: <<: *default database: <%= ENV["DATABASE_NAME"] %> username: <%= ENV["DATABASE_USERNAME"] %> password: <%= ENV["DATABASE_PASSWORD"] %> host: <%= ENV["DATABASE_HOST"] %> コンテナを起動 & データベースを作成 $ docker-compose build $ docker-compose up -d $ docker-compose run web bundle exec rails db:create 動作確認(localhost:3000 にアクセス) localhost:3000 にアクセスしてウェルカムページが表示されればOKです。 slim を導入 個人的にビューのテンプレートエンジンは erb よりも slim の方が好みなので変更します。 ./Gemfile gem "slim-rails" gem "html2slim" $ docker-compose build 既存のビューファイルを slim に書き換え。 $ docker-compose run web bundle exec erb2slim app/views app/views $ docker-compose run web bundle exec erb2slim app/views app/views -d Bootstrap を導入 見た目を整えるために Bootstrap を使用します。 ※ 下記の手順はバージョン5を想定したものになるので、別のバージョンを使いたい場合は他の記事を参考に導入してください。 yarn install 必要なライブラリをインストール。 $ docker-compose run web yarn add bootstrap @popperjs/core app/views/layouts/application.html ./app/views/layouts/application.html - = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' + = stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' 「app/views/layouts/application.html」内の9行目あたりに記述されている「stylesheet_link_tag」を「stylesheet_pack_tag」に書き換えます。 app/javascript/stylesheets/application.scss $ mkdir app/javascript/stylesheets $ touch app/javascript/stylesheets/application.scss 「app/javascript/stylesheets/」以下に「application.scss」を作成し、次の1行を追記してください。 ./app/javascript/stylesheets/application.scss @import "~bootstrap/scss/bootstrap"; app/javascript/packs/application.js 「app/javascript/packs/application.js」内に次の2行を追記。 ./app/javascript/packs/application.js import "bootstrap"; import "../stylesheets/application"; これで Bootstrap の設定は完了です。 本実装 準備ができたので、本格的な実装に入りましょう。 User モデルを作成 今回は簡単な User モデルを例に検索機能を実装していきたいと思います。 名前で検索(姓・名・フルネーム) 性別で検索 年齢で検索 都道府県で検索 $ docker-compose run web rails g model User first_name:string last_name:string gender:integer birthdate:date prefecture_id:integer $ docker-compose run web rails db:migrate ./app/models/user.rb class User < ApplicationRecord extend ActiveHash::Associations::ActiveRecordExtensions belongs_to_active_hash :prefecture validates :first_name, presence: true, length: { maximum: 10 } validates :last_name, presence: true, length: { maximum: 10 } validates :gender, presence: true validates :birthdate, presence: true validates :prefecture_id, presence: true enum gender: { male: 0, female: 1 } # 新しい順 scope :order_by_latest, -> { order(id: :desc) } # 古い順 scope :order_by_oldest, -> { order(id: :asc) } # フルネーム(姓 + 名) def full_name "#{last_name} #{first_name}" end # 年齢(生年月日から計算) def age return if birthdate.blank? date_format = "%Y%m%d" (Date.today.strftime(date_format).to_i - birthdate.strftime(date_format).to_i) / 10_000 end end サンプルデータを作成 ./Gemfile gem "active_hash" gem "faker" $ docker-compose build active_hash ハッシュ形式のデータをActiveRecordと同じ感覚で使用できるようになるgem。基本的に変更される事の無い「都道府県」のような情報を取り扱う時などに便利。 faker ランダムな値のダミーデータを作成する際に定番のgem。一部で日本語対応も。 $ docker-compose run web rails g model Prefecture --skip-migration ./app/models/prefecture.rb class Prefecture < ActiveHash::Base self.data = [ {id: 1, name: "北海道"}, {id: 2, name: "青森県"}, {id: 3, name: "岩手県"}, {id: 4, name: "宮城県"}, {id: 5, name: "秋田県"}, {id: 6, name: "山形県"}, {id: 7, name: "福島県"}, {id: 8, name: "茨城県"}, {id: 9, name: "栃木県"}, {id: 10, name: "群馬県"}, {id: 11, name: "埼玉県"}, {id: 12, name: "千葉県"}, {id: 13, name: "東京都"}, {id: 14, name: "神奈川県"}, {id: 15, name: "新潟県"}, {id: 16, name: "富山県"}, {id: 17, name: "石川県"}, {id: 18, name: "福井県"}, {id: 19, name: "山梨県"}, {id: 20, name: "長野県"}, {id: 21, name: "岐阜県"}, {id: 22, name: "静岡県"}, {id: 23, name: "愛知県"}, {id: 24, name: "三重県"}, {id: 25, name: "滋賀県"}, {id: 26, name: "京都府"}, {id: 27, name: "大阪府"}, {id: 28, name: "兵庫県"}, {id: 29, name: "奈良県"}, {id: 30, name: "和歌山県"}, {id: 31, name: "鳥取県"}, {id: 32, name: "島根県"}, {id: 33, name: "岡山県"}, {id: 34, name: "広島県"}, {id: 35, name: "山口県"}, {id: 36, name: "徳島県"}, {id: 37, name: "香川県"}, {id: 38, name: "愛媛県"}, {id: 39, name: "高知県"}, {id: 40, name: "福岡県"}, {id: 41, name: "佐賀県"}, {id: 42, name: "長崎県"}, {id: 43, name: "熊本県"}, {id: 44, name: "大分県"}, {id: 45, name: "宮崎県"}, {id: 46, name: "鹿児島県"}, {id: 47, name: "沖縄県"} ] end 「db/seeds.rb」内を以下のように編集し、サンプルデータを挿入してください。 ./db/seeds.rb puts "Creating users..." Faker::Config.locale = "ja" 500.times do gender = User.genders.keys.sample User.create!( first_name: gender == "male" ? Faker::Name.male_first_name : Faker::Name.female_first_name, last_name: Faker::Name.last_name, gender: gender, birthdate: Faker::Date.birthday(min_age: 18, max_age: 65), prefecture_id: Prefecture.all.sample.id ) end puts "Finished" $ docker-compose run web rails db:seed 良い感じに入ってますね。 日本語化対応 「config/application.rb」内に以下を内容を記述し、デフォルトのタイムゾーンや言語を日本に変更してください。 ./config/application.rb module Myapp class Application < Rails::Application # ... # 追記 config.time_zone = "Tokyo" config.active_record.default_timezone = :local config.i18n.default_locale = :ja config.i18n.load_path += Dir[Rails.root.join("config", "locales", "**", "*.{rb,yml}").to_s] # ... end end また、「config/locals/」以下にja.ymlを作成します。 $ touch config/locales/ja.yml ./config/locales/ja.yml ja: activerecord: models: user: ユーザー attributes: id: ID created_at: 作成日時 updated_at: 更新日時 user: first_name: 名 last_name: 姓 full_name: 名前 gender: 性別 birthdate: 生年月日 age: 年齢 prefecture_id: 都道府県 enums: user: gender: male: 男性 female: 女性 Form オブジェクト(UserSerachForm) を作成 ここからが本記事の目玉。 検索を行うためのロジックを Form オブジェクトで実装していきます。 $ mkdir app/forms $ touch app/forms/user_search_form.rb ./app/forms/user_search_form.rb class UserSearchForm include ActiveModel::Model attr_accessor :first_name, :last_name, :gender, :min_age, :max_age, :prefecture_id def initialize(params = {}) @first_name = params[:first_name] @last_name = params[:last_name] @gender = params[:gender] @min_age = params[:min_age] @max_age = params[:max_age] @prefecture_id = params[:prefecture_id] end # クエリを実行 def query base_relation .then(&method(:search_by_name)) .then(&method(:search_by_gender)) .then(&method(:search_by_ages)) .then(&method(:search_by_prefecture_id)) end private # 基本条件 def base_relation User.all end # 名前で検索(必ずしも姓・名の両方が入力されるとは限らないため、入力された値によって検索条件を変える) def search_by_name(relation) if first_name.present? && last_name.present? # 姓・名の両方が入力された場合 search_by_full_name(relation) elsif first_name.present? && last_name.blank? # 名のみ入力された場合 search_by_first_name(relation) elsif first_name.blank? && last_name.present? # 姓のみ入力された場合 search_by_last_name(relation) else relation end end # フルネーム(姓 + 名)で検索 def search_by_full_name(relation) relation .where("last_name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(last_name)}%") .where("first_name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(first_name)}%") end # 名で検索 def search_by_first_name(relation) relation.where("first_name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(first_name)}%") end # 姓で検索 def search_by_last_name(relation) relation.where("last_name LIKE ?", "%#{ActiveRecord::Base.sanitize_sql_like(last_name)}%") end # 性別で検索 def search_by_gender(relation) return relation if gender.blank? relation.where(gender: gender) end # 年齢で検索(Userモデルに年齢の値そのものが保持されているわけではないので誕生日から計算) def search_by_ages(relation) return relation if min_age.blank? || max_age.blank? start_date = Date.today - max_age.to_i.years end_date = Date.today - min_age.to_i.years relation.where(birthdate: start_date..end_date) end # 都道府県で検索 def search_by_prefecture_id(relation) return relation if prefecture_id.blank? relation.where(prefecture_id: prefecture_id) end end 基本的にはコードを読んでいただければ何をしているのかわかると思いますが、あまり見慣れない部分としては def query base_relation .then(&method(:search_by_name)) .then(&method(:search_by_gender)) .then(&method(:search_by_ages)) .then(&method(:search_by_prefecture_id)) end この辺かなと思います。 この then は「レシーバを引数としてブロックを呼び出し、そこで評価された結果を返す」というメソッドで、 tap メソッドと似ている事からしばしば比較されていたりします。 参照: Ruby: Object#tap、Object#then を使ってみよう その性質からメソッドチェーンにおいて便利と言われており、今回もそんな感じの用途で使用しました。 「一つ前の処理で返ってきた結果に対し再度処理を施してその結果を返す...」 というプロセスを繰り返し、各条件に合わせた絞り込みを行なっていくイメージですね。 今回は &記法にしているので若干わかりにくいかもしれませんが、もう少し素直に書くなら def query base_relation .then { |relation| search_by_name(relation)} .then { |relation| search_by_gender(relation)} .then { |relation| search_by_ages(relation)} .then { |relation| search_by_prefecture_id(relation)} end こんな感じになると思います。どちらでも正常に動くのでお好きな方を採用してください。 ちなみに、Rubyには yiled_self という同じ挙動を持つ別のメソッドがあったりしますが、 「then」 はそのエイリアスになります。(「yiled_self」という名前がやや不評で後ほど追加されたとの事。) コントローラーを作成 コントローラーを作成し、先ほど定義した Form オブジェクトを呼び出します。 $ docker-compose run web rails g controller users ./app/controllers/users_controller.rb class UsersController < ApplicationController def index @users = User.order_by_latest end def search @users = UserSearchForm.new(user_search_params).query.order_by_latest render :index end private # 検索用のストロングパラメータ def user_search_params params.fetch(:q, {}).permit( :first_name, :last_name, :gender, :min_age, :max_age, :prefecture_id ) end end ビューを作成 見た目の部分を作り込んでいきます。 $ touch app/views/users/index.html.slim app/views/users/_search_form.html.slim ./app/views/users/index.html.slim .container.p-3 .row.mb-4 .col-12 .d-flex.align-items-center.justify-content-center = render "search_form" .row .col-12 .mt-1.mb-2 = "#{@users.count} 件" .card.card-outline .card-body table.table thead tr th scope="col" = t("activerecord.attributes.id") th scope="col" = t("activerecord.attributes.user.full_name") th scope="col" = t("activerecord.attributes.user.gender") th scope="col" = t("activerecord.attributes.user.age") th scope="col" = t("activerecord.attributes.user.prefecture_id") - if @users.present? - @users.each do |user| tr td = user.id td = user.full_name td = user.gender_i18n td = user.age td = user.prefecture.name - else tr td 該当するデータがありません td td td td ./app/views/users/_search_form.html.slim = form_with url: search_users_path, scope: :q, method: :get, local: true do |f| .row .col-4 .form-group = f.label t("activerecord.attributes.user.last_name"), class: "control-label" = f.text_field :last_name, value: params.dig(:q, :last_name), class: "form-control" .col-4 .form-group = f.label t("activerecord.attributes.user.first_name"), class: "control-label" = f.text_field :first_name, value: params.dig(:q, :first_name), class: "form-control" .col-4 .form-group = f.label t("activerecord.attributes.user.gender"), class: "control-label" = f.select :gender, User.genders_i18n.invert, { include_blank: true, value: nil, selected: params.dig(:q, :gender) }, class: "form-select" .col-4.mt-2 .form-group = f.label "#{t('activerecord.attributes.user.age')}(from)", class: "control-label" = f.select :min_age, [*(18..65)], { include_blank: true, value: nil, selected: params.dig(:q, :min_age) }, class: "form-select" .col-4.mt-2 .form-group = f.label "#{t('activerecord.attributes.user.age')}(to)", class: "control-label" = f.select :max_age, [*(18..65)].reverse, { include_blank: true, value: nil, selected: params.dig(:q, :max_age) }, class: "form-select" .col-4.mt-2 .form-group = f.label t("activerecord.attributes.user.prefecture_id") = f.collection_select :prefecture_id, Prefecture.all, :id, :name, { include_blank: true, value: nil, selected: params.dig(:q, :prefecture_id) }, class: "form-select" .d-flex.align-items-center.justify-content-center.mt-4 = f.submit "検索", class: "btn btn-primary me-1" = link_to "リセット", users_path, class: "btn btn-secondary ms-1" scope: :q という部分で検索用のパラメータにまとめている部分だけご注意ください。 参照: form_withの:scopeオプション ルーティングを設定 最後にルーティングを設定しましょう。 ./config/routes.rb Rails.application.routes.draw do resources :users, only: :index do collection do get :search end end end 動作確認 localhost:3000/users にアクセスしてこんな感じになっていれば完成です。 試しに色々な条件で検索してみてください。 あとがき 以上、Form オブジェクトを使ってシンプルな検索機能を作ってみました。 ロジック部分の大半を Form オブジェクトに切り出した事でモデル・コントローラー・ビューがすっきり保てているのが大きなメリットですね。 少しでも参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Model-View=contoroller(MVC)メモ

メソッド名とアプリケーションネームは同じ名前にする していないとプレビューで The action 'goodbye' could not be found for ApplicationController と表示される。 application_controller.rb class ApplicationController < ActionController::Base def hello #ここ render html: "hello, world!" end end routes.rb Rails.application.routes.draw do # For details on the DSL available within this file, # see https://guides.rubyonrails.org/routing.html root 'application#hello' #ここ end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Model-View=Contoroller(MVC)メモ

メソッド名とアプリケーションネームは同じ名前にする していないとプレビューで The action 'goodbye' could not be found for ApplicationController と表示される。 application_controller.rb class ApplicationController < ActionController::Base def hello #ここ render html: "hello, world!" end end routes.rb Rails.application.routes.draw do # For details on the DSL available within this file, # see https://guides.rubyonrails.org/routing.html root 'application#hello' #ここ end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Model-View-Contoroller(MVC)メモ

メソッド名とアプリケーションネームは同じ名前にする していないとプレビューで The action 'goodbye' could not be found for ApplicationController と表示される。 application_controller.rb class ApplicationController < ActionController::Base def hello #ここ render html: "hello, world!" end end routes.rb Rails.application.routes.draw do # For details on the DSL available within this file, # see https://guides.rubyonrails.org/routing.html root 'application#hello' #ここ end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む