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

Prefixについて!

①.結論! Prefixとは、ルーティングのURI Patternに名前をつけて変数化したものです! これにより、URI Patternの代わりにPrefixを用いてパスを表現できます! 簡単に言うと、遷移する指定先をパスで視覚化した事です! 細かく説明していきます! ②.解説! 例として、ターミナルでrails routesコマンドを実行して有効なルーティングを探すと、以下のような表示になります! Prefix Verb URI Pattern Controller#Action eats GET /eats(.:format) eats#index 他省略 一番左上の列を確認します! 一番上にはPrefixという文字があり、その配下にはPrefix名が出力されていることが確認できます! つまり、下記のようにURI Pattern(パス)が変数化したと言う事です! Prefix Verb URI Pattern -> eats GET /eats(.:format) このPrefix名に続けて_pathと記述を付け加えることによって、URI Patternのパスとして認識されます! 例えば、rootにアクセスしたいとき、'/'と表現出来ましたが、root_pathとも表記できます! 下記が例です! # Prefix未使用 <%= link_to "link_button", '/', class: "header__title--text" %> # Prefix使用 <%= link_to "link_button", root_path, class: "header__title--text" %> このような形で完成となります! ③.まとめ! 流れとしてはターミナルでrails routesをして、パスを確認して記述すると言う事です! パスの指定は必ず必要なので、このやり方は覚えておくべきですね! 何か説明で間違っていたらご指導お願い致します(_ _)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

既存アプリ(Rails)にdocker導入

はじめに 最近オリジナルアプリにDocker開発環境を導入したので、導入までに学習した所をこの記事にまとめたいと思います。 Dockerとは コンテナ技術を使ったアプリケーションの実行環境の構築や運用ができるオープンソースソフトウェアのこと。 コンテナ技術により、コンテナ上の環境は個別のサーバーのように扱うことができる。そのためホスト上とは別の環境を作ることができる。ホストOSとリソースを共有しているため、高速かつ軽量な仮想化を実現している。 Dockerの利点 インフラ環境をコードに落とし込み、そのコードをもとに開発環境を作れる。そのため、簡単に環境構築を行うことができる。さらにそのコードを共有すれば他の開発者と同じ環境を作れる。開発環境と同じ本番環境を作ることもできるので、開発環境と本番環境の違いによる不具合をなくすこともできる。 DockerではDockerfileと呼ばれるファイルに、インフラの構成情報を記述していく。 Dockerfileとは アプリの開発環境やインフラの構成情報をコードとして記述したもの。Dockerfileにかかれたコードをもとにイメージを作成し、コンテナを起動する。コンテナ内がDockerfileをもとに作られた環境になっている。このコンテナ内で開発を行っていく。 イメージがコンテナの設計図でコンテナがイメージからできた実体(インスタンス)のようなイメージ。 イメージはDocker Hub上でたくさん公開されている。 今回Dockerを開発環境に導入する理由 現在、オリジナルアプリケーションでGitHub Actionsを使いCI/CDを導入しています。 CI(継続的インテグレーション)は自動テストでコードの品質を担保してくれますが、開発環境とテスト環境が現状だと異なってしまっています。その部分を同じ環境にしてコードの信頼性を上げたかったので、Dockerを導入します。 Docker導入 実行環境 ・ macOS ・ Docker 20.10.7 ・ docker-compose 1.29.2 ①Dockerfile作成 Dockerfileはベースイメージからコマンドを重ねるように環境を構築していく。 Dockerfile # ベースイメージ FROM ruby:2.6.7 # Railsを使うために必要なコンポーネントを環境内にインストールする RUN apt-get update -qq \ && apt-get install -y nodejs yarn postgresql-client # myappディレクトリを作成し、移動する WORKDIR /myapp # gemfileとgemfile.lockをコピーして既存アプリと同じgemをインストールする COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install # 既存アプリのコードをコンテナ内のmyappディレクトリ以下にコピーする COPY . /myapp # docker run時にserver.pidファイルを削除する設定 COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] # コンテナ起動時に公開することを想定されているポートを記述、あったほうが親切 EXPOSE 3000 # docker run時にrailsサーバーを起動 CMD ["rails", "server", "-b", "0.0.0.0"] 以下がserver.pidを削除するシェルスクリプトです。 entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" ②docker-compose.ymlファイルを作成 DBはPostgreSQLを使用する。 Docker Containerは1コンテナでは1プロセスを動かす設計となっているため、DBはアプリケーションサーバとは別のコンテナとして作成し管理する必要がある。 そのため、複数のコンテナをまとめて管理できるdocker-compose.ymlファイルを作成していく。 docker-compose.yml # composeファイルのバージョン version: "3.9" # servicesの下に使うコンテナを記述 services: db: image: postgres # volumesにより(ホスト:コンテナ)のデータを同期する # コンテナを消してもホスト上にデータが同期されるからDBのデータは残る volumes: - ./tmp/db:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: password web: # カレントディレクトリにあるDockerfileをwebコンテナのイメージに使用 build: . # docker-compose コマンドで発動。server.pidファイル削除し、railsサーバー起動。 command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" # カレントディレクトリのファイルとコンテナ内のファイルを同期している # コードを変更すればコンテナ内のコードも変更される volumes: - .:/myapp # "ホストのポート番号:コンテナ内のポート番号" # コンテナの3000番ポートでrailsサーバが起動しているので、        # ローカルの3000番ポートと繋ぎローカルでも確認できるようにする。 ports: - "3000:3000" # コンテナの起動順序と依存関係を記述する。 # この場合、コンテナの起動順はdb→web、しかし完全にdbが起動してからwebを起動するのではない depends_on: - db ③Dockerfileイメージを作成 docker-compose.ymlファイルのbuild: .からカレントディレクトリのDockerfileを探しwebコンテナのもとになるイメージを作成する。 % docker-compose build ④database.ymlファイル変更 dbコンテナと通信できるようにdatabase.ymlを変更。 database.yml default: &default adapter: postgresql encoding: unicode # docker-compose.ymlでdepends_onにdbを記述しているのでhost:dbとすればdbコンテナに繋げられる host: db username: postgres password: password pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 development: <<: *default database: myapp_development test: <<: *default database: myapp_test ⑤docker-compose upでコンテナを起動 Dockerfileから作成したイメージとDocker Hubから取得してきたpostgresイメージをもとにコンテナを起動。 % docker-compose up この状態で、localhost:3000にアクセスするとdbがないエラーが出てくるためコンテナに入りDB作成。続けてテーブルも作成。 % docker-compose run web rails db:create % docker-compose run web rails db:migrate ここまででlocalhost:3000にアクセスしてアプリが正常に動いていれば導入完了 参考 既存のRailsアプリにDockerを導入する手順 既存のRails6アプリをDocker化しつつCircleCIでシステムスペックも実行できる環境を作る Quickstart: Compose and Rails
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

既存アプリ(Rails6)にDocker導入

はじめに 最近オリジナルアプリにDocker開発環境を導入したので、導入までに学習した所をこの記事にまとめたいと思います。 Dockerとは コンテナ技術を使ったアプリケーションの実行環境の構築や運用ができるオープンソースソフトウェアのこと。 コンテナ技術により、コンテナ上の環境は個別のサーバーのように扱うことができる。そのためホスト上とは別の環境を作ることができる。ホストOSとリソースを共有しているため、高速かつ軽量な仮想化を実現している。 Dockerの利点 インフラ環境をコードに落とし込み、そのコードをもとに開発環境を作れる。そのため、簡単に環境構築を行うことができる。さらにそのコードを共有すれば他の開発者と同じ環境を作れる。開発環境と同じ本番環境を作ることもできるので、開発環境と本番環境の違いによる不具合をなくすこともできる。 DockerではDockerfileと呼ばれるファイルに、インフラの構成情報を記述していく。 Dockerfileとは アプリの開発環境やインフラの構成情報をコードとして記述したもの。Dockerfileにかかれたコードをもとにイメージを作成し、コンテナを起動する。コンテナ内がDockerfileをもとに作られた環境になっている。このコンテナ内で開発を行っていく。 イメージがコンテナの設計図でコンテナがイメージからできた実体(インスタンス)のようなイメージ。 イメージはDocker Hub上でたくさん公開されている。 今回Dockerを開発環境に導入する理由 現在、オリジナルアプリケーションでGitHub Actionsを使いCI/CDを導入しています。 CI(継続的インテグレーション)は自動テストでコードの品質を担保してくれますが、開発環境とテストを実行するステージング環境が現状だと異なってしまっています。その部分を同じ環境にしてコードの信頼性を上げたかったので、Dockerを導入します。 Docker導入 実行環境 ・ macOS ・ Docker 20.10.7 ・ docker-compose 1.29.2 ・ Ruby 2.6.7 ・ Rails 6.1.4 ①Dockerfile作成 Dockerfileはベースイメージからコマンドを重ねるように環境を構築していく。 Dockerfile # ベースイメージ FROM ruby:2.6.7 # Railsを使うために必要なコンポーネントを環境内にインストールする RUN apt-get update -qq \ && apt-get install -y nodejs yarn postgresql-client # myappディレクトリを作成し、移動する WORKDIR /myapp # gemfileとgemfile.lockをコピーして既存アプリと同じgemをインストールする COPY Gemfile /myapp/Gemfile COPY Gemfile.lock /myapp/Gemfile.lock RUN bundle install # 既存アプリのコードをコンテナ内のmyappディレクトリ以下にコピーする COPY . /myapp # docker run時にserver.pidファイルを削除する設定 COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["entrypoint.sh"] # コンテナ起動時に公開することを想定されているポートを記述、あったほうが親切 EXPOSE 3000 # docker run時にrailsサーバーを起動 CMD ["rails", "server", "-b", "0.0.0.0"] 以下がserver.pidを削除するシェルスクリプトです。 entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@" ②docker-compose.ymlファイルを作成 DBはPostgreSQLを使用する。 Docker Containerは1コンテナでは1プロセスを動かす設計となっているため、DBはアプリケーションサーバとは別のコンテナとして作成し管理する必要がある。 そのため、複数のコンテナをまとめて管理できるdocker-compose.ymlファイルを作成していく。 docker-compose.yml # composeファイルのバージョン version: "3.9" # servicesの下に使うコンテナを記述 services: db: image: postgres # volumesにより(ホスト:コンテナ)のデータを同期する # コンテナを消してもホスト上にデータが同期されるからDBのデータは残る volumes: - ./tmp/db:/var/lib/postgresql/data environment: POSTGRES_PASSWORD: password web: # カレントディレクトリにあるDockerfileをwebコンテナのイメージに使用 build: . # docker-compose コマンドで発動。server.pidファイル削除し、railsサーバー起動。 command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" # カレントディレクトリのファイルとコンテナ内のファイルを同期している # コードを変更すればコンテナ内のコードも変更される volumes: - .:/myapp # "ホストのポート番号:コンテナ内のポート番号" # コンテナの3000番ポートでrailsサーバが起動しているので、        # ローカルの3000番ポートと繋ぎローカルでも確認できるようにする。 ports: - "3000:3000" # コンテナの起動順序と依存関係を記述する。 # この場合、コンテナの起動順はdb→web、しかし完全にdbが起動してからwebを起動するのではない depends_on: - db ③Dockerfileイメージを作成 docker-compose.ymlファイルのbuild: .からカレントディレクトリのDockerfileを探しwebコンテナのもとになるイメージを作成する。 % docker-compose build ④database.ymlファイル変更 dbコンテナと通信できるようにdatabase.ymlを変更。 database.yml default: &default adapter: postgresql encoding: unicode # docker-compose.ymlでdepends_onにdbを記述しているのでhost:dbとすればdbコンテナに繋げられる host: db username: postgres password: password pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 development: <<: *default database: myapp_development test: <<: *default database: myapp_test ⑤docker-compose upでコンテナを起動 Dockerfileから作成したイメージとDocker Hubから取得してきたpostgresイメージをもとにコンテナを起動。 % docker-compose up この状態で、localhost:3000にアクセスするとdbがないエラーが出てくるためコンテナに入りDB作成。続けてテーブルも作成。 % docker-compose run web rails db:create % docker-compose run web rails db:migrate ここまででlocalhost:3000にアクセスしてアプリが正常に動いていれば導入完了 参考 既存のRailsアプリにDockerを導入する手順 既存のRails6アプリをDocker化しつつCircleCIでシステムスペックも実行できる環境を作る Quickstart: Compose and Rails
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

はじめてのRuby on Railsの環境構築 まとめ

1. はじめに 以前はDocker上で環境構築を行いましたが今回は前回できなかった自分のPCに環境構築できたのでメモ。 2. インストールする環境 PC: MacBookAir(Early 2015) OS: BigSur 11.1 CPU:1.6 GHz デュアルコアIntel Core i5 3. Homebrewをインストール Homebrewの公式サイトにコマンドが表示されていますが、今回はこちらのコマンドを入力します。 % /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" すると以下のような警告が出ますがダウンロードが進行します。 Warning: The Ruby Homebrew installer is now deprecated and has been rewritten in Bash. Please migrate to the following command: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ↑こちらが最新(2021/010/09時点)のコマンド Updated 1 tap (homebrew/core). ==> Installation successful! →Homebrewのインストールが完了 4. Rubyをインストール % brew install ruby Warning: ruby 3.0.2 is already installed and up-to-date. To reinstall 3.0.2, run: brew reinstall ruby 既にインストールされてますが再インストールします。 % brew reinstall ruby ==> Caveats By default, binaries installed by gem will be placed into: /usr/local/lib/ruby/gems/3.0.0/bin デフォルトと違う場所になっているという警告 ruby-build installs a non-Homebrew OpenSSL for each Ruby version installed and these are never upgraded. rubyがHomebrewを使用せずにインストールされているのでアップグレードがされないという警告 警告文 You may want to add this to your PATH. ruby is keg-only, which means it was not symlinked into /usr/local, because macOS already provides this software and installing another version in parallel can cause all kinds of trouble. If you need to have ruby first in your PATH, run: echo 'export PATH="/usr/local/opt/ruby/bin:$PATH"' >> ~/.zshrc For compilers to find ruby you may need to set: export LDFLAGS="-L/usr/local/opt/ruby/lib" export CPPFLAGS="-I/usr/local/opt/ruby/include" For pkg-config to find ruby you may need to set: export PKG_CONFIG_PATH="/usr/local/opt/ruby/lib/pkgconfig" ruby-build installs a non-Homebrew OpenSSL for each Ruby version installed and these are never upgraded. To link Rubies to Homebrew's OpenSSL 1.1 (which is upgraded) add the following to your ~/.zshrc: export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)" 【対処法】 .zshrcファイルを編集 rubyをインストールした際に出ていた警告文の中にあったexport〜で始まる文を追記する % echo 'export PATH="/usr/local/opt/ruby/bin:$PATH"' >> ~/.zshrc % export LDFLAGS="-L/usr/local/opt/ruby/lib" % export CPPFLAGS="-I/usr/local/opt/ruby/include" % export PKG_CONFIG_PATH="/usr/local/opt/ruby/lib/pkgconfig" % export RUBY_CONFIGURE_OPTS="--with-openssl-dir=$(brew --prefix openssl@1.1)" または直接書き込みしてもOK 完成図 →rubyのインストールが完了 5. Railsをインストール % sudo gem install rails Successfully installed rails-6.1.4.1 →Railsのインストールが完了 6. アプリを作成 作成したい場所に移動 % cd /Users/ユーザー名/アプリの保存したい場所 % rails new RailsApp(アプリ名) エラーが発生… rails aborted! LoadError: cannot load such file -- nokogiri/nokogiri /Library/Ruby/Gems/2.6.0/gems/nokogiri-1.12.5-arm64-darwin/lib/nokogiri/extension.rb:30:in rescue in [main] /Library/Ruby/Gems/2.6.0/gems/nokogiri-1.12.5-arm64-darwin/lib/nokogiri/extension.rb:4:in [main] /Library/Ruby/Gems/2.6.0/gems/nokogiri-1.12.5-arm64-darwin/lib/nokogiri.rb:11:in [main] 対処法 nokogiriファイルが読み込めてないようなので、不要なバージョンのnokogiriファイルを削除します。 作成したrailsアプリのフォルダを開き、Gemfile.Lockファイルを開く 私は念の為以前作成したDocker+Railsのフォルダを見比べながら編集 今回は下記2つを削除 nokogiri (1.12.5-arm64-darwin) nokogiri (~> 1.6) 2021年10月10日(追記) nokogiriファイル整理後にrails serverすると以下のエラーが出ます Webpacker configuration file not found Webpackerが無いのでダウンロードします。 rails webpacker:install 参考させていただいたサイト [Tips] Rails6起動時のError対応 もう一度トライ % rails server できました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 下書き機能の実装で使うenum,draft ,status,publishedについて

draft publishedとは draftは下書きのことです。 publishedは公開のことです。 statusは状態のことです。 下書きをしたり 、書いた記事を公開したりする時に使います。 statusは またenum(列挙型)と合わせて使われることがよくあります。 enumの資料です。 ⚫︎参考資料 https://qiita.com/shizuma/items/d133b18f8093df1e9b70 下書き記事と公開記事を作成 下書き記事と公開記事を作成を作成するには、modelのarticle.rbなどに enum,status,draft,publishedを使います。 これは記事の状態を表す記述です。 models/article.rb enum status { draft: 0, publishe: 1} 他にもこのような書き方があるみたいです。 models/article.rb enum status { draft: "draft", published: "published"} 削除も入れる時はdeletedも記述できるみたいです。 models/article.rb enum status { draft: 0, published: 1,deleted: 2} 配列で表すと下のようにできるみたいです。 models/article.rb enum state: [:draft, :published, :deleted] データベースに記事を保存しておくためのstatusカラム追加 $ rails g migration AddStatusToArticles status:integer ⚫︎補足 integerはstringでも使えるみたいです。 draft: "draft"のような書き方をした場合。 statusカラムの追加をするために、 *******_add_status_to_articles.rbに下のように追記します。 *******_add_status_to_articles.rb class AddStatusToArticles < ActiveRecord::Migration[6.0] def change add_column :articles, :status, :integer, default: 0, null: false end end 他にも add_column :articles, :status, :string, default: "draft" みたいなやり方もあるみたいです。 status:integerのintegerをstringにしたら上記みたいにもできるみたいです。 マイグレーション実行(データベース反映) $ rails db:migrate ⚫︎最後に この辺のやり方で下書き保存機能はできました。 開発している環境によって変わってくる場合があるので、 参考にしてみてください。 ⚫︎参考資料 https://pgmg-rails.com/blogs/21 https://www.autovice.jp/articles/124?tag_id=41 https://qiita.com/shimadama/items/798f3f02058b6f91a248 https://kimuraysp.hatenablog.com/entry/2017/01/07/102403
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSへのデプロイ後、ブラウザー上で「We're sorry, but something went wrong.」と表示された際の対処法

はじめに AWS上にCapistranoを使用してRailsアプリをデプロイしましたが、ブラウザー上に「We're sorry, but something went wrong.」と表在されてしまうエラーに遭遇して、エラーの解決に苦戦しましたため、同様の悩みをお持ちの方のお役に立てばと思い、記載いたします。 開発環境 Mac Ruby 2.7.2 Rails 6.1.3.1 PostgreSQL 13.2 本番環境 puma nginx AWS(EC2、RDS、VPC、S3) 前提 RailsアプリをHerokuとS3を使用した環境で上手くデプロイできましたため、AWS(EC2、RDS、VPC)環境にRailsアプリをデプロイした際に発生したエラーとなります。 エラーの事象 ローカル環境のターミナルで「bundle exec cap production deploy」コマンドを実行し、表示されたIPアドレスをブラウザーに入力後、以下の画面が表示されました。本来であれば、アプリケーションのトップページが表示されるはずですが、エラー画面が表示されてしまいました。 ローカル環境のターミナルでデプロイエラーは出ていなかったため、サーバー環境のターミナルで「/var/www/アプリケーション名/shared/log/nginx.error.log」を確認したところ、以下のメッセージが表示されました。 /var/www/アプリケーション名/shared/log/nginx.error.log 2021/10/04 08:00:08 [crit] 25745#25745: *539 connect() to unix:/var/www/stop_sweets/shared/tmp/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream, client: xx.xx.xx.xxx, server: localhost, request: "GET /blog/wp-includes/wlwmanifest.xml HTTP/1.1", upstream: "http://unix:/var/www/stop_sweets/shared/tmp/sockets/puma.sock:/blog/wp-includes/wlwmanifest.xml", host: "xx.xx.xx.xxx" 2021/10/04 08:00:09 [crit] 25745#25745: *539 connect() to unix:/var/www/stop_sweets/shared/tmp/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream, client: xx.xx.xx.xxx, server: localhost, request: "GET /web/wp-includes/wlwmanifest.xml HTTP/1.1", upstream: "http://unix:/var/www/stop_sweets/shared/tmp/sockets/puma.sock:/web/wp-includes/wlwmanifest.xml", host: "xx.xx.xx.xxx" 「sockets/puma.sock failed (2: No such file or directory)」というメッセージでググってみたところ、pumaが起動した際にpuma.sockというファイルが作成されるとのことですので、pumaが上手く起動していないことによるエラーなのではと思い、pumaが上手く動かない原因を調べてみました。 以下のpuma起動用スクリプトからpumaを起動しているコマンドを確認しました。 /etc/systemd/system/puma_アプリ名_production.service [Unit] Description=Puma HTTP Server for stop_sweets (production) After=network.target [Service] Type=simple User=stop_sweets_user WorkingDirectory=/var/www/stop_sweets/current ExecStart=/home/stop_sweets_user/.rbenv/bin/rbenv exec bundle exec puma -C /var/www/stop_sweets/shared/puma.rb ExecReload=/bin/kill -TSTP $MAINPID StandardOutput=append:/var/www/stop_sweets/shared/log/puma_access.log StandardError=append:/var/www/stop_sweets/shared/log/puma_error.log Restart=always RestartSec=1 SyslogIdentifier=puma [Install] WantedBy=multi-user.target 上記のpuma起動用スクリプトから確認した起動コマンド「bundle exec puma -C /var/www/アプリ名/shared/puma.rb」をサーバー環境ターミナルのディレクトリ「/var/www/アプリ名/current」で実行したところ、以下のログが表示されました。 Puma starting in single mode... * Puma version: 5.2.2 (ruby 2.7.2-p137) ("Fettisdagsbulle") * Min threads: 0 * Max threads: 16 * Environment: production * PID: 13673 ! Unable to load application: ArgumentError: Missing required arguments: aws_access_key_id, aws_secret_access_key bundler: failed to load command: puma (/var/www/stop_sweets/shared/bundle/ruby/2.7.0/bin/puma) Traceback (most recent call last): 57: from /home/stop_sweets_user/.rbenv/versions/2.7.2/bin/bundle:23:in `<main>' 56: from /home/stop_sweets_user/.rbenv/versions/2.7.2/bin/bundle:23:in `load' エラーメッセージ「ArgumentError: Missing required arguments: aws_access_key_id, aws_secret_access_key」でググってみたところ、Railsアプリ内で画像投稿用のgem「CarrierWave」を使用して、投稿した画像をS3に保存するようにしておりましたが、S3を使用するためのアクセスキーとシークレットキーを設定していなかったことによるエラーであることが分かりました。そのため、railsアプリ内のコードを以下のように修正しました。 config/initializers/carrier_wave.rb if Rails.env.production? CarrierWave.configure do |config| config.fog_credentials = { provider: 'AWS', aws_access_key_id: Rails.application.credentials.dig(:aws, :access_key_id), aws_secret_access_key: Rails.application.credentials.dig(:aws, :secret_access_key), region: Rails.application.credentials.dig(:aws, :region) } config.fog_directory = Rails.application.credentials.dig(:aws, :bucket) end end config/credentials.yml.enc aws: access_key_id: 【IAMユーザーのアクセスキーID】 secret_access_key: 【IAMユーザーのシークレットアクセスキー】 region: ap-northeast-1 bucket: 【S3のバケット名】 上記の修正後、githubのmainブランチに反映し、ローカル環境のターミナルで「bundle exec cap production deploy」コマンドを実行致しました。ブラウザーからアプリへアクセスしたところ、無事にトップページが表示されるようになりました。 まとめ デプロイログやサーバログに真因に関するメッセージが出ていなかったため、 解決に手こずりましたが、エラーメッセージから地道に調査していくことが大切だと思いました。 nginxやpumaなどのサーバに関する知識も必要だと思いました。サーバに関する知識がないと原因がアプリ側なのか、インフラ側なのかの原因の切り分けが難しいと思いました。 参考資料 Capistrano自動デプロイ中の「ArgumentError: Missing required arguments: aws_access_key_id, aws_secret_access_key」エラー NginxとRails(Puma)をソケット通信で連携させる方法!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[rails6] 動的なカテゴリー機能の実装 後半(ancestry)

はじめに 前回、こちらからカテゴリー機能の実装をまとめてみた。 実装した機能は下記の通り。 ancestryによるデータの多階層化 非同期通信(Ajax)を用いた動的なセレクトボックスの実装 前回の機能だけではカテゴリーに所属するpostを絞り込み表示する機能がない。 ここでは各カテゴリーに所属する投稿(post)を表示する機能を実装する。 サンプルGIF カテゴリー検索機能の実装 ルーティング config/routes.rb resources :posts do collection do get "menu_search" end member do get "item_search" end end collection内にget "menu_search"を追加。 member内にget item_searchを追加。 パスは下記の通り。 ~/posts/menu_search ~/posts/:id/item_search menu_searchはカテゴリーのリストを表示するためのリクエスト。 item_searchはカテゴリーリストから選択したカテゴリーに紐づくpostを参照するリクエスト。 独自アクションの定義 app/controllers/posts_controller.rb def menu_search respond_to do |format| format.html format.json do if params[:parent_id] @childrens = Category.find(params[:parent_id].to_s).children elsif params[:children_id] @grandChilds = Category.find(params[:children_id].to_s).children elsif params[:gcchildren_id] @parents = Category.where(id: params[:gcchildren_id].to_s) end end end end private マウス操作によってカテゴリーリストを表示させるために必要なインスタンス変数を条件分岐する。 respond_toでリクエストされるフォーマットがhtmlとjsonの場合で処理を分けている。 format.jsonの処理が行われる場合、渡ってきた値によって格納された値の階層下をインスタンス変数に定義する。 params[:xxxxx_id].to_sはjsで記述したajaxで渡ってくる値を取得する。 childrenはancestryが提供するメソッド。子レコードを取得する。 ここのインスタンス変数はmenu_search.json.jbuilderに渡り、JSON形式へと変換される。 app/controllers/posts_controller.rb def item_search #クリックしたカテゴリーidに紐づくレコードを取得 @category = Category.find_by(id: params[:id]) #親カテゴリーが選択された時 if @category.ancestry.nil? #取得したレコードに紐づいた孫レコードidを取得 category = Category.find_by(id: params[:id]).indirect_ids if category.empty? @posts = Post.where(category_id: @category.id).order(created_at: :desc) else @posts = [] find_item(category) end #孫カテゴリーが選択された時 elsif @category.ancestry.include?("/") @posts = Post.where(category_id: params[:id]).order(created_at: :desc) #親でも孫でもない=子カテゴリーが選択された時 else #取得したレコードに紐づいた子レコードidを取得 category = Category.find_by(id: params[:id]).child_ids @posts = [] find_item(category) end end def find_item(category) category.each do |id| #子・孫レコードのidと一致するpostを取得 post_array = Post.where(category_id: id).order(created_at: :desc) next unless post_array.present? post_array.each do |post| #一致して取得したpostを1つずつ取り出して@postsに順次追加 @posts.push(post) if post.present? end end end private 条件に一致するカテゴリーのidに紐づいたpostの一覧を取得するアクション。 item_searchでは、親or孫orそれ以外(子)を分岐させてカテゴリーidに紐づくpostを取得し、それをインスタンス変数に定義。 find_item(category)では、item_searchに必要なカテゴリーidに紐づいた空ではないpostを表示させるアクションを定義。 present?は配列の中に無駄な空の配列を含めないために条件分岐している。 下記はancestry独自のメソッド。 indirect_idsは、孫レコード以下のIDを返すメソッド。 child_idsは、子レコードのIDを取得するメソッド。 jbuilderファイルの作成 touch app/views/posts/menu_search.json.jbuilder 空のファイルを作成。 中身を書いていく。 app/views/posts/menu_search.json.jbuilder json.array! @childrens do |child| json.id child.id json.name child.name end json.array! @grandChilds do |gc| json.id gc.id json.name gc.name json.root gc.root_id end json.array! @parents do |parent| json.parent parent.parent_id end 前回同様、JSON形式に変換するための記述を行う。 javascriptファイルの作成 touch app/javascript/packs/category.js 空のファイルを作成。 中身を書いていく。 app/javascript/packs/category.js $(document).on('turbolinks:load', function () { // 子カテゴリーを表示した時のhtml function childBuild(children) { let child_category = ` <li class="category_child"> <a href="/posts/${children.id}/item_search"><input class="child_btn" type="button" value="${children.name}" name= "${children.id}"> </a> </li> `; return child_category; } //孫カテゴリーを表示した時のhtml function gcBuild(children) { let gc_category = ` <li class="category_grandchild"> #posts/id/item_searchに <a href="/posts/${children.id}/item_search"><input class="gc_btn" type="button" value="${children.name}" name= "${children.id}"> </a> </li> `; return gc_category; } // マウスホバー時に親カテゴリーを表示 $('#categoBtn').hover( function (e) { e.preventDefault(); //イベントに対するデフォルトの動作をキャンセル e.stopPropagation(); //親要素に返るイベントの伝播をストップ timeOut = setTimeout(function () { $('#tree_menu').show(); //500ms指定のidを表示 $('.categoryTree').show();   //500ms指定のclassを表示 }, 500); }, function () { clearTimeout(timeOut); } ); // マウスホバー時に子カテゴリーを表示 $('.parent_btn').hover( function () { $('.parent_btn').css('color', ''); //cssの適用を変更 $('.parent_btn').css('background-color', ''); let categoryParent = $(this).attr('name'); //要素(parent_btn)に紐づく属性(name)の値を取得 timeParent = setTimeout(function () { $.ajax({ url: '/posts/menu_search', type: 'GET', data: { parent_id: categoryParent, }, dataType: 'json', }) .done(function (data) { $('.categoryTree-grandchild').hide();  //孫カテゴリーの表示を隠す $('.category_child').remove();    //上で記述した子のhtmlを削除 $('.category_grandchild').remove(); //上で記述した孫のhtmlを削除 $('.categoryTree-child').show();              //子カテゴリーを表示 data.forEach(function (child) { let child_html = childBuild(child); $('.categoryTree-child').append(child_html); // html要素(子)を動的に追加 }); $('#tree_menu').css('max-height', '490px'); }) .fail(function () { alert('カテゴリーを選択してください'); }); }, 400); }, function () { clearTimeout(timeParent); } ); // 孫カテゴリーを表示 $(document).on( { mouseenter: function () { //マウスが対象の要素の上に重なった時にイベント発火 $('.child_btn').css('color', ''); $('.child_btn').css('background-color', ''); let categoryChild = $(this).attr('name'); //要素(child_btn)に紐づく属性(name)の値を取得 timeChild = setTimeout(function () { $.ajax({ url: '/posts/menu_search', type: 'GET', data: { children_id: categoryChild, }, dataType: 'json', }) .done(function (gc_data) { $('.category_grandchild').remove(); //上記に記述した孫のhtmlを削除 $('.categoryTree-grandchild').show(); //孫カテゴリーを表示 gc_data.forEach(function (gc) { let gc_html = gcBuild(gc); $('.categoryTree-grandchild').append(gc_html); // html要素(孫)を動的に追加 let parcol = $('.categoryTree').find(`input[name="${gc.root}"]`); //classに含まれるinputに紐づいた孫のルートidを取得 $(parcol).css('color', 'white'); $(parcol).css('background-color', '#b1e9eb'); }); $('#tree_menu').css('max-height', '490px'); }) .fail(function () { alert('カテゴリーを選択してください'); }); }, 400); }, mouseleave: function () { //マウスが対象の要素の上から外れた時にイベント発火 clearTimeout(timeChild); }, }, '.child_btn' ); // 孫カテゴリーを選択時 $(document).on( { mouseenter: function () { //マウスが対象の要素の上に重なった時にイベント発火 let categoryGc = $(this).attr('name'); //要素(document)に紐づく属性(name)の値を取得 timeGc = setTimeout(function () { $.ajax({ url: '/posts/menu_search', type: 'GET', data: { gcchildren_id: categoryGc, }, dataType: 'json', }) .done(function (gc_result) { let childcol = $('.categoryTree-child').find(`input[name="${gc_result[0].parent}"]`); $(childcol).css('color', 'white'); $(childcol).css('background-color', '#b1e9eb'); $('#tree_menu').css('max-height', '490px'); }) .fail(function () { alert('カテゴリーを選択してください'); }); }, 400); }, mouseleave: function () { //マウスが対象の要素の上から外れた時にイベント発火 clearTimeout(timeGc); }, }, '.gc_btn' ); // カテゴリー一覧ページのボタン $('#all_btn').hover( function (e) { e.preventDefault(); //イベントに対するデフォルトの動作をキャンセル e.stopPropagation(); //親要素に返るイベントの伝播をストップ $('.categoryTree-grandchild').hide(); $('.categoryTree-child').hide(); $('.category_grandchild').remove(); $('.category_child').remove(); }, function () { // 何も記述しない } ); // カテゴリーを非表示 $(document).on( { mouseleave: function (e) { e.stopPropagation(); //親要素に返るイベントの伝播をストップ e.preventDefault(); //イベントに対するデフォルトの動作をキャンセル timeChosed = setTimeout(function () { $('.categoryTree-grandchild').hide(); $('.categoryTree-child').hide(); $('.categoryTree').hide(); $(this).hide(); $('.parent_btn').css('color', ''); $('.parent_btn').css('background-color', ''); $('.category_child').remove(); $('.category_grandchild').remove(); }, 800); }, mouseenter: function () { //マウスが対象の要素の上に重なった時の処理 clearTimeout(timeChosed); }, }, '#tree_menu' ); // カテゴリーボタンの処理 $(document).on( { mouseenter: function (e) { //マウスが対象の要素の上に重なった時の e.stopPropagation(); //親要素に返るイベントの伝播をストップ e.preventDefault(); //イベントに対するデフォルトの動作をキャンセル timeOpened = setTimeout(function () { $('#tree_menu').show(); $('.categoryTree').show(); }, 500); }, mouseleave: function (e) { //マウスが対象の要素の上から外れた時にイベント発火 e.stopPropagation(); e.preventDefault(); clearTimeout(timeOpened); $('.categoryTree-grandchild').hide(); $('.categoryTree-child').hide(); $('.categoryTree').hide(); $('#tree_menu').hide(); $('.category_child').remove(); $('.category_grandchild').remove(); }, }, '.header__headerInner__nav__listsLeft__item' ); }); リンクにマウスホバーした時の動作を定義。 javascriptの読み込みを有効化 require('./category'); 読み込みにcategory.jsファイルを指定して有効にする。 ビュー touch app/views/posts/_category.html.erb html.erbファイルをパーシャルで作成。 カテゴリー表示をするビューの部分を個別に持たせる。 app/views/posts/_category.thml.rb <ul class='header__headerInner__nav__listsLeft'> <li class='header__headerInner__nav__listsLeft__item'> <%= link_to posts_path, class: "category-button", id: 'categoBtn' do %> カテゴリーから探す <% end %> <div id='tree_menu'> <ul class='categoryTree'> <% @set_parents.each do |parent| %> <li class="category_parent"> <%= link_to item_search_post_path(parent) do %> <input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn"> <% end %> </li> <% end %> <li class="category_parent"> <%= link_to posts_path do %> <input type="button" value="全てのカテゴリー" id="all_btn"> <% end %> </li> </ul> <ul class='categoryTree-child'></ul> <ul class='categoryTree-grandchild'></ul> </div> </li> </ul> ここの記述から親カテゴリーを表示し、マウスホバーから動的に子・孫カテゴリーをjavascriptで表示させる。 link_to item_search_post_path(parent) doの記述によってidに基づくリンク先を生成。 inputタグによって、ボタン形式のカテゴリー名とidを格納したリンクが作られる。 touch app/views/posts/item_search.html.erb 先ほど作ったパーシャルを持たせるhtml.erbを作成。 ルーティング先であったitem_searchをビューで作り、ここにカテゴリーを選択した際の絞り込み結果が表示されるようにする。 app/views/posts/item_search.html.erb <%= render "category" %> <%= render @posts %> categoryは作ったパーシャルを読み込んでいる。 @postsはitem_searchアクションで取得したpost一覧を表示する。 あとは app/views/posts/index.html.erb <%= render "category" %> <%= render @posts %> ヘッダーに埋め込む方が現実的かとは思うが今回はindexにパーシャルを読み込ませる。 これで画面遷移した時に同じカテゴリー機能が表示されることになる。 ここの@postsはindexアクションにpost.allとした値を取得させる。 indexでは全てのpostをカテゴリーに関係なく一覧表示させる。 app/controllers/post_controller.rb def index @posts = Post.all end ここまでCSSを適用していないためサンプルの見た目にはなっていない。 最後にCSSを適用させて終わる。 CSS touch app/assets/stylesheets/category.scss 空のscssファイルを作成。 中身を書いていく。 app/assets/styleseets/category.scss #tree_menu { width: 200px; position: absolute; z-index: 10; display: flex; .categoryTree { display: none; height: 100%; border: 1px solid #34aeb3; background-color: #ffffff; border-radius: 4px; box-shadow: 4px 4px 4px #999; a { padding: 0; input[type='button'] { min-width: 200px; height: 35px; font-size: 14px; text-decoration: none; color: #3ccace; background: #ffffff; outline: none; transition: 0.4s; border: #ffffff; padding: 0 0.9em; cursor: pointer; } input[type='button']:hover { background: #62d4d7; color: white; } input[type='button']:active { -webkit-transform: translateY(2px); transform: translateY(2px); box-shadow: 0 0 1px rgba(0, 0, 0, 0.15); background-image: linear-gradient(#b1e9eb 0%, #30a1a4 100%); } } } .categoryTree-child { display: none; height: 100%; border: 1px solid #34aeb3; background-color: #ffffff; border-radius: 4px; box-shadow: 4px 4px 4px #999; .category_child { a { padding: 0; input[type='button'] { min-width: 230px; height: 35px; font-size: 14px; text-decoration: none; color: #3ccace; background: #ffffff; outline: none; transition: 0.4s; border: #ffffff; padding: 0 0.9em; cursor: pointer; } input[type='button']:hover { background: #62d4d7; color: white; } input[type='button']:active { -webkit-transform: translateY(2px); transform: translateY(2px); box-shadow: 0 0 1px rgba(0, 0, 0, 0.15); background-image: linear-gradient(#b1e9eb 0%, #30a1a4 100%); } } } } .categoryTree-grandchild { display: none; height: 100%; border: 1px solid #34aeb3; background-color: #ffffff; border-radius: 4px; box-shadow: 4px 4px 4px #999; a { padding: 0; input[type='button'] { min-width: 250px; height: 35px; font-size: 14px; text-decoration: none; color: #3ccace; background: #ffffff; outline: none; transition: 0.4s; border: #ffffff; padding: 0 0.9em; cursor: pointer; } input[type='button']:hover { border: 1px solid #62d4d7; background: #62d4d7; color: white; } input[type='button']:active { -webkit-transform: translateY(2px); transform: translateY(2px); box-shadow: 0 0 1px rgba(0, 0, 0, 0.15); background-image: linear-gradient(#b1e9eb 0%, #30a1a4 100%); } } } } 詳細ページに参照したカテゴリーを表示 app/contorollers/posts_controller.rb def show @post = Post.find(params[:id]) @category_id = @post.category_id @category_parent = Category.find(@category_id).parent.parent @category_child = Category.find(@category_id).parent @category_grandchild = Category.find(@category_id) end @postは特定のpostを参照。 @category_idはpostに紐づくカテゴリーを参照。 @category_parentは紐づいたカテゴリーの親の親を参照。 @category_childは紐づいたカテゴリーの親を参照。 @category_grandchildは紐づいたカテゴリーを参照。 app/views/posts/show.html.erb <%= "#{ @category_parent.name } > #{ @category_child.name } > #{ @category_grandchild.name }" %> 定義したインスタンス変数からカラムを指定して表示させる。 終わりに まるまる参考にさせてもらっている状態なので参考にした記事以上には、とても説明できないのが本音です。 参考にさせて頂いた記事を確認して頂くと間違いないかと思います。 重複する記述もあるのでもっとコンパクトにできそうに思えますが力不足でリファクタリングまではできませんでした。 また説明ができないのも自分の理解が追いついていない結果かと思いますので、いずれわかりやすく説明できるように努めたいと思います。 参考もと jQueryとancestryを使ったカテゴリー選択機能(Ajax) 【Rails】カテゴリ検索機能の実装で学んだこと
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[rails6] 動的なカテゴリー機能の実装 前半(ancestry)

はじめに ancestryというgemを使用するにあたって、カテゴリー機能を実装するのにとても時間がかかってしまった。備忘録として残しつつ理解を深めていく。 長くなってしまったので前後半で分けた。今回の実装する部分は下記の通り。 前半の実装 初期データの投入(カテゴリーデータの作成) カテゴリーに紐づく動的なセレクトボックスの実装 SNS風の投稿アプリがある想定で下記の箇所を実装していく。 投稿の一覧(index) 投稿機能(new) ←今回の実装部分 投稿の詳細(show) 投稿の編集(edit) 完成図 環境 Ruby 2.7.4 Rails 6.1.4 jQuery 4.4.0 ancestry 4.1.0 テーブル - postsテーブル FK category_id - categoriesテーブル - id - name - ancestry 二つのテーブルは1(posts)対多(categories)の関係。 postは既存のテーブルを想定、後からcategoryを追加する形を取る。 実装にあたり余計なカラムは記述していない。 カテゴリー登録機能を実装 JQueryの導入 Rails6でのjQuery導入方法を参照。 ここでjQeryを扱う理由は以下の通り。 ブラウザの違いを意識せずに済む HTMLのDOM操作が簡単にできる Ajax処理が簡単に記述できる 参考もと jQueryとは?/筆者:寺谷文宏(てらたにふみひろ) gemの導入 gem 'ancestry' 上記をgemfileに記述。 ターミナルからbundle installを叩いてgemをインストール。 モデルの準備 rails g model category Categoryモデルを作成。 Postモデルも同様にここで作成してもいいがカテゴリー機能を後付けで実装する想定で進めるため、Postモデルは既にある状態とする。 マイグレーションファイルの編集 db/migrate/xxxxxxxxxxxxxx_create_categories.rb def change create_table :categories do |t| t.string :name, null: false t.string :ancestry t.timestamps end add_index :categories, :ancestry end categoriesテーブルにname,ancestryのカラムを記述。 rails g migration AddCategoryIdToPosts 既存のPostモデルに後付けでカラムを追加するため、新たにマイグレーションファイルを追加して対応。 マイグレーションファイルは一度マイグレーションで反映させてしまうと次回以降、参照対象から外れる。 そのため既存のxxxxxxxxxxx_create_teble.rbは書き換えても意味がない。 db/migrate/xxxxxxxxxxxxx_add_category_id_to_posts.rb class AddUserIdToPosts < ActiveRecord::Migration[5.2] def change add_reference :posts, :category, foreign_key: true end end 記述が終わったら忘れずrails db:migrateでマイグレーションする。 関連付け app/models/category.rb class Category < ApplicationRecord has_many :posts has_ancestry end categoryモデルに関連付けを行う。 has_manyで、Categoryは複数のpostを所属することを記述。 has_ancestryで、ancestryの階層化を有効にするよう記述。 app/models/posts.rb class Post < ApplicationRecord belongs_to :category end Postモデルに関連付けを行う。 belongs_toで、postがcategoryに従属している事を記述。 初期データの作成 googleスプレットシートを使用。 Aの列にid、Bの列にname、Cの列にancestryを配置する形で情報を記述。 親カテゴリーC列はnillとして扱うため記述をしなくてOK 子カテゴリーC列では、どの親カテゴリーに属しているかを親idで記述。 孫カテゴリーC列では、どの親と子に属しているかを親id/子idで記述。 初期データをCSVファイルに変換・保存 次にgoogleスプレッドシートの項目[ファイル]→[ダウンロード]→[カンマ区切りの値(.csv、現在のシート)]を選択。 シートに記述したデータをcsvファイルに変換してデスクトップに保存。 #CSVファイルを移動 mv ~/Downloads/category.csv ~/sample_app/db/category.csv 保存したCSVファイルをプロジェクトのdbに移す。 出力したCSVファイルはgoogleスプレッドシートのタイトル名+シート名でファイル名が決まるため必要に応じて変換すること。 初期データの投入 db/seeds.rb #CSVファイルの読み込みを有効化 require "csv" ##CSVファイルをforeachでインポート CSV.foreach('db/category.csv') do |row| Category.create(:id => row[0], :name => row[1], :ancestry => row[2]) end db/seed.rbに上記を記述。 CSV.foreach('csvファイルへのパス')でcsvファイルを指定して読み込む Category.create(:カラム => ブロック変数[n])でカンマ区切りの値をインデックス番号で振り分けながらカテゴリーを生成する。 リファレンスマニュアル rails db:seed ターミナルからrails db:seedを叩いて投入完了。 ルーティング config/routes.rb resources :posts do collection do get "get_category_children", defaults: { format: "json" } get "get_category_grandchildren", defaults: { format: "json" } end end resourcesでpostsに基づく7つの基本的なアクションをルーティング。 collectionで独自に定義するアクションをpostsの配下でルーティング。 生成されるパスはGET posts/get_category_childrenのような感じ。 defaults: { format: "json" }でJSON形式の応答を指定。 コントローラー app/controllers/posts_controller.rb before_action :set_parents private def set_parents @set_parents = Category.where(ancestry: nil) end def post_params params.require(:post).permit(:category_id) end set_parentsは親カテゴリーのみを抽出したインスタンス変数を定義。 params.require(:キーに紐づくモデル).permint(:取得するカラム)で意図しないカラムの更新を防ぐためにストロングパラメーターを設定。 privateは以下に定義したアクションを外部から呼び出されないように記述。 before_actionでアクションの実行前に特定のアクションが実行されるように記述 独自アクションを定義 app/controllers/posts_controller.rb def get_category_children @category_children = Category.find(params[:parent_id].to_s).children end def get_category_grandchildren @category_grandchildren = Category.find(params[:child_id].to_s).children end private ルーティングで設定したJSON用アクションを定義。 get_category_childrenは子カテゴリーのidをJSON経由で取得。 get_category_grandchildrenは孫カテゴリーidをJSON経由で取得。 params[:xxxx_id].to_sは後ほど記述するjavascriptファイル内のajaxから送られる値を記述。 childrenはancestryの提供するインスタンスメソッド。子のレコードを取得する。 jbuilderファイルを作成 touch app/views/posts/get_category_children.json.jbuilder touch app/views/posts/get_category_grandchildren.json.jbuilder 2つのjson.jbuilderを作成。 このファイルを介してアクションで定義した値がJSON形式へと変換される。 app/views/posts/get_category_children.json.jbuilder json.array! @category_children do |child| json.id child.id json.name child.name end app/views/posts/get_category_grandchildren.json.jbuilder json.array! @category_grandchildren do |grandchild| json.id grandchild.id json.name grandchild.name end 下記は変換されたJSONの返り値。 # [{"id": "idの値1", "name": "nameの値1"}, {"id": "idの値2", "name": "nameの値2"}] javascriptファイルを作成 touch app/javascript/packs/category_post.js 空のファイルを作って中身を書いていく。 ここの記述によって選択するセレクトボックスがカテゴリーの階層に合わせて動的に変化する。 app/javascript/packs/category_post.js $(document).on('turbolinks:load', function() { //①セレクトボックスに必要なoptionタグを生成 function appendOption(category){ var html = `<option value="${category.id}">${category.name}</option>`; return html; } //②子カテゴリー用のセレクトボックスを生成 function appendChildrenBox(insertHTML){ var childSelectHtml = ""; childSelectHtml = `<div class="category__child" id="children_wrapper"> <select id="child__category" name="post[category_id]" class="select_field"> <option value="">---</option> //optionタグを埋め込む ${insertHTML} </select> </div>`; //ブラウザに非同期で子セレクトボックスを表示される $('.append__category').append(childSelectHtml); } //③孫カテゴリー用のセレクトボックスを生成 function appendGrandchildrenBox(insertHTML){ var grandchildSelectHtml = ""; grandchildSelectHtml = `<div class="category__child" id="grandchildren_wrapper"> <select id="grandchild__category" name="post[category_id]" class="select_field"> <option value="">---</option> //optionタグを埋め込む ${insertHTML} </select> </div>`; //ブラウザに非同期で孫セレクトボックスを表示される $('.append__category').append(grandchildSelectHtml); } //親カテゴリー選択時に子カテゴリーのイベント発火 $('#post_category_id').on('change',function(){ //選択したカテゴリーのidを取得 var parentId = document.getElementById('post_category_id').value; if (parentId != ""){ //取得したidをコントローラへ渡し、子カテゴリーを取得 $.ajax({ url: '/posts/get_category_children/', type: 'GET', data: { parent_id: parentId }, dataType: 'json' }) //親が変更された時に子孫を削除 .done(function(children){ $('#children_wrapper').remove(); $('#grandchildren_wrapper').remove(); var insertHTML = ''; //forEachで子カテゴリーを展開 children.forEach(function(child){ //①と紐づいて取得した子のid,nameをoptionタグに埋め込む insertHTML += appendOption(child); }); appendChildrenBox(insertHTML); if (insertHTML == "") { $('#children_wrapper').remove(); } }) //ajax通信の失敗時にアラートを表示 .fail(function(){ alert('カテゴリー取得に失敗しました'); }) }else{ $('#children_wrapper').remove(); $('#grandchildren_wrapper').remove(); } }); //子カテゴリーの選択時に孫カテゴリーのイベント発火 $('.append__category').on('change','#child__category',function(){ var childId = document.getElementById('child__category').value; if(childId != ""){ $.ajax({ url: '/posts/get_category_grandchildren/', type: 'GET', data: { child_id: childId }, dataType: 'json' }) //子が変更された時に孫を削除 .done(function(grandchildren){ $('#grandchildren_wrapper').remove(); var insertHTML = ''; grandchildren.forEach(function(grandchild){ insertHTML += appendOption(grandchild); }); appendGrandchildrenBox(insertHTML); if (insertHTML == "") { $('#grandchildren_wrapper').remove(); } }) .fail(function(){ alert('カテゴリー取得に失敗しました'); }) }else{ $('#grandchildren_wrapper').remove(); } }) }); document.getElementById('post_category_id').value;では、セレクトボックスで選択されたidを取得。 $.ajax({~})の部分からJSON用のルートを通ってコントローラーへと取得したidが非同期で渡り、子・孫カテゴリーのレコードを取得。 removeでセレクトボックスが再選択された場合にルートの子・孫を削除。 forEachでajaxで取得した子・孫カテゴリーのレコードを展開。 appendOptionは取得した子・孫レコードからoptionタグにidとnameを埋め込む。 insertHTMLはappendxxxxxxBoxと紐づいて、appendOptionの内容を渡す。 alertはajax通信で失敗した場合にアラートを表示。 view app/views/posts/new.html.erb <%= form_with model: @post, local: true do |f| %> <div class="append__category"> <div class="category"> <div class="form__label"> <div class="weight-bold-text lavel__name "> カテゴリー </div> <div class="lavel__Required"> <%= f.collection_select :category_id, @set_parents, :id, :name,{ include_blank: "選択してください"},class:"select_field", id:"post_category_id" %> </div> </div> </div> </div> <%= f.submit class: "btn btn-primary btn-block" %> <% end %> collection_selectでモデルの情報をもとにセレクトボックスを作成。 @set_parentsで親カテゴリーを取得。 id:"post_category_idで選択されたカテゴリーidをjavascriptと共有。 javascriptの読み込みを有効化 app/views/layouts/application.html.erb <head> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </head> javascript_pack_tag 'application'がデフォルト記述されている事を確認。 ここのヘッダー部分からjavascriptの読み込みが行われている。 app/javascript/packs/application.js require('./category_post'); 新たに作成したcategory_post.jsは指定してあげないと読み込まれていないので、上記を記述する事で読み込むようにする。 ひとまず、ここまでがカテゴリーの登録機能の実装は完了。 次の記事でカテゴリーに所属するpostを検索・表示する機能を追加する。 参考もと ancestry/公式ドキュメント [Rails]カテゴリー機能 【Rails】 gem ancestry カテゴリー機能実装について ancestryによる多階層構造データを用いて、動的カテゴリーセレクトボックスを実現する~Ajax~
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Open Weather Mapで天気予報取得 web API

初めに 今回ポートフォリオに「世界のお天気」ページを追加しました。 以下のような感じです。 APIキーの取得 以下のページからAPIキーを取得します。 私の場合は既に取得済みなので
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails rakeタスクをherokuのschedulerで定期実行してみた! 【2分30秒に1回】

rakeタスクをherokuで定期実行させたい ポートフォリオでwebアプリを作成中にwhenever gemを使用して定期実行をしていたが、herokuではサポートされていないのでアドオンのHeroku Schedulerを使用してみる。 前提 herokuにデプロイ済み rakeタスクを作成する lib/tasks/〇〇.rakeに作成する make_aaa_article.rake namespace :make_aaa_article do desc '「aaa」のツイートが私物に関するものかを判別し私物に関するものであれば記事を作成する' task account_aaa: :environment do article = Article.new article.aaa_article end end 問題点 ・2分30秒に1回実行させたいのに、Herokuのschedulerは一番短いスパンでも10分に1回しか実行されない timesメソッドsleepメソッドを使用する 目的は、rakeタスクの中で複数回実行させ(timesメソッド)、実行させる間隔も指定する(sleepメソッド) make_aaa_article.rake namespace :make_aaa_article do desc '「aaa」のツイートが私物に関するものかを判別し私物に関するものであれば記事を作成する' task account_aaa: :environment do 3.times{ #この行 article = Article.new article.make_aaa_article sleep(150) #この行 } end end 3.times→ 3回実行する sleep(150)→ 150秒=2分30秒停止させる 流れとしては、 article = Article.new article.make_aaa_article を実行したら、150秒=2分30秒プログラムを停止させ、次の処理を実行する。 これを3回実行させている。 timesメソッド self 回だけ繰り返します。 self が正の整数でない場合は何もしません。 またブロックパラメータには 0 から self - 1 までの数値が渡されます。 ターミナル irb(main):012:1>def aa irb(main):013:1> 5.times{ irb(main):014:2* puts "太郎" irb(main):015:2> } irb(main):016:1> end => :aa irb(main):017:0> article = Article.new irb(main):018:0> article.aa 太郎 太郎 太郎 太郎 太郎 => 5 sleepメソッド sec 秒だけプログラムの実行を停止します。 ターミナル irb(main):001:0> def a irb(main):002:1> puts "1" irb(main):003:1> sleep(10) irb(main):004:1> puts "10秒経ちました" irb(main):005:1> end => :a irb(main):006:0> article = Article.new irb(main):007:0> article.a 1 # 10秒経ってから↓が出力される 10秒経ちました これでOK rakeタスクをテストする ローカルでテストしてみる 正常に実行できたらデプロイし、Heroku schedulerの設定を行う $ heroku run rake make_aaa_article:account_aaa スケジューリングする $ heroku addons:open scheduler 間隔とタスク名を設定する 今回は ・10分毎に実行させる ・コマンドは $ bundle exec rake make_aaa_article:account_aaa Save Jobを押して完了 参考にさせていただいたサイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.rbenv/shims/docker-sync: line 21: /usr/local/Cellar/rbenv/1.1.2/libexec/rbenv: No such file or directory

はじめに brew upgradeしたらdocker-sync だけ見つからなくなりました。 gemで入れたもの全部動かなくなった(全部は確認してないけど) 探しても対処法がぱっと見つからなかった(ちゃんと探してないだけ)のでメモがてら書きます。 結論(解決した方法) 以下を実行で解決した。 rbenv rehash rbenv rehash コマンドを実行すると、大まかには ~/.rbenv/versions/*/bin/ 以下のファイルを ~/.rbenv/shims/ 以下にコピーする。 なぜコピーする必要があるかというと、通常PATHが通っているのは ~/.rbenv/shims 以下となっているからで、ここにコピーしないと rspec とか rubocop といったGemが提供するコマンドを実行できないからである。 cf. https://mogulla3.tech/articles/2020-12-29-01 ということみたいです。 やったこと brewのupdate, upgradeがうまくいってない? brew update && brew upgrade 全て最新になってるっぽい。 brewで問題が起きている? brew doctor dockerのlinkができてないというのが出たので、link brew link docker でも、直らない。他に問題はなさそう。 rbenvの問題? brew reinstall rbenv まだ問題変わらず、、 ?? ----- ここから下を確認して実行すればOK ----- /usr/local/Cellar/rbenv/1.1.2/libexec/rbenv: No such file or director rbenv/1.1.2? rbenvのバージョン見てみる rbenv -v => rbenv 1.2.0 バージョンが違う、、 ll /usr/local/Cellar/rbenv total 0 drwxr-xr-x 10 kazmaw admin 320B 10 9 10:07 1.2.0 rbenv/1.1.2なんてないじゃないか。 brew ls --formula -lt | grep rbenv drwxr-xr-x 3 kazmaw admin 96 Oct 9 10:07 rbenv rbenvのバージョンが上がってしまったからか、、 gemも動かない事に気づいた gem uninstall docker-sync /(略)/.rbenv/shims/gem: line 21: /usr/local/Cellar/rbenv/1.1.2/libexec/rbenv: No such file or directory あー、rbenvのパス通すやつなんかあったな、、 rbenv rehash ❯ docker-sync -h [10:32:08] Commands: docker-sync --version, -v # Prints out the version of docker-sync and exits docker-sync clean # Stop and clean up all sync endpoints docker-sync help [COMMAND] # Describe available commands or one specific command docker-sync list # List all sync-points of the project configuration path docker-sync logs # Prints last 100 lines of daemon log. Only for use with docker-sync started in background. docker-sync restart # Restart docker-sync daemon docker-sync start # Start all sync configurations in this project docker-sync stop # Stop docker-sync daemon docker-sync sync # just sync - do not start a watcher though Options: -c, [--config=CONFIG] # Path of the docker_sync config -n, [--sync-name=SYNC_NAME] # If given, only this sync configuration will be references/started/synced -v, [--version], [--no-version] # prints out the version of docker-sync and exits いけたーー!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

railsのconstraintsを使って、ルーティングを制限する

おはようございます! 朝から警察の方に職務質問を受けてしまった、プレイライフの熊崎です。 そんな怪しさ全開の雰囲気が出ていた僕ですが、引き続きアウトプットを行なっていきたいと思います。 constraintsとは? :constraintsオプションを使って、動的セグメントのURLフォーマットを特定の形式に制限できます。 ref: https://railsguides.jp/routing.html#セグメントを制限する → ルーティングに制限を設定するオプション。constraintsの和訳が制約なので、そのまんま。 制限の仕方 routes.rb get '/articles/:id' , to: 'articles#show', constraints: { id: /[A-Z]\d{5}/ } # match => /articles/A11111, not_match => /articles/111 また、リクエスト内容に応じて制限を加えることができる。 ref: https://railsguides.jp/routing.html#リクエスト内容に応じて制限を加える routes.rb get '/articles/:id' , to: 'articles#show', constraints: { port: 3000 } # ポート番号が3000以外の場合、マッチしない。 また、以下のようにmatchesメソッドを使用して制限を加えることができる。 routes.rb get '/articles/:id' , to: 'articles#show', constraints: ArticleIdRestrict.new class ArticleIdRestrict def matches?(request) # URLの後ろについているidが、記事テーブルのidの中に含まれているか? Article.pluck(:id).include?(request[:id]) end end どんな時に使用するのか? → ルーティングを制限したいとき。 例: 記事を、ジャンルで絞り込みを行う際。 genresテーブル id name 1 SQL 2 Rails routes.rb get '/articles/:genre_name', to: 'articles#index', constraints: ArticleGenreRestrict.new class ArticleGenreRestrict def matches?(request) # ジャンル名がSQLかRailsの場合のみマッチする。 Genre.pluck(:name).include?(request[:genre_name]) end end メリット コントローラー側で、クエリパラメータが正しいかどうかを判断する必要がなくなる。 注意点 以下のように書くと、指定したルーティングが無効になる。 routes.rb resources :articles, only: %i[index] get '/articles/:genre_name', to: 'articles#index', constraints: ArticleGenreRestrict.new class ArticleGenreRestrict def matches?(request) # ジャンル名がSQLかRailsの場合のみマッチする。 Genre.pluck(:name).include?(request[:genre_name]) end end 理由 Railsのルーティングは、ルーティングファイルの「上からの記載順に」マッチします。このため、たとえばresources :photosというルーティングがget 'photos/poll'よりも前の行にあれば、resources行のshowアクションがget行の記述よりも優先されますので、get行のルーティングは有効になりません。これを修正するには、get行をresources行 よりも上 の行に移動してください。これにより、get行がマッチするようになります。 ref: https://railsguides.jp/routing.html#crud、動詞、アクション 参考記事 https://railsguides.jp/routing.html#セグメントを制限する https://railsguides.jp/routing.html#リクエスト内容に応じて制限を加える https://railsguides.jp/routing.html#crud、動詞、アクション 最後に ルーティング周りでこんな機能があるとは思わなかった。 まだまだ知らない機能がいっぱいあるので、1つ1つ覚えていく必要があると感じた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

人それぞれMVC

こんにちは! 今日はMVCのおさらいについて他の方の記事を参考にして、自分の頭に落とし込んだ知識を自分の言葉でアウトプットしていきたいと思います。 Ruby on Railsでオリジナルアプリケーションを作成しましたが、MVCって何??って聞かれて言語化できないような気がしたので記事にしてみました。 フレームワークとは プログラミング手法の1種であるMVCは、多くのフレームワークで採用されております。 そもそもフレームワークって何ですかって聞かれると言語化できないので、そこからアウトプットします。 フレームワークはWebアプリケーションの作成を容易にしてくれる仕組みです。 Webアプリケーションにおいて、必要となる作業やリソースがすでに標準装備 されてあるものと考えればいいでしょう。 RubyだとRuby on Rails、PHPはLaravelといったところでしょうか。 MVCそれぞれ フレームワークにはwebアプリケーション作成を容易にしてくれるMVCという機能が標準装備されております。MVCのおかげでWebアプリケーション作成の際に必要な機能の処理の一部を分類し分けることで、保守性や可読性の高いコードを維持することができます。 MVCはそれぞれ 「M」はModel、 「V」はView、 「C」はController の略となります。 それぞれ見ていきましょう! Model Webアプリケーションにおけるビジネスロジックを定義します。 いきなりビジネスロジックとかわかりません。 簡単に言うと「データベースとやりとり行う」部分です。 ECサイトにおける購入リクエストに対して、商品が購入可能かどうかをチェックするというデータの整合性の確認や、日時の取得の際にユーザーが見やすいようデータを加工(日時変換)したりします。 これらをModelに定義しておくことでデータベースにあるテーブルを フレームワークで管理できるようになります。 View Webアプリケーションにおける実際にクライアント側に表示される見た目 の部分です。 Controller クライアント側からリクエストに対する処理をまとめて用意し、 クライアントにレスポンスを返します。 また、レスポンスに必要なデータがあればModelを介して データベースからデータを持ってきます。 あくまでデータの用意がControllerの仕事なので、 データの加工などビジネスロジックはControllerには 記述しません。 MVCは考え方 Ruby on Railsを用いたオリジナルアプリケーションを作成した際に、文字数制限をModelに記述しました。文字数制限はviewに記述したよって方もおられました。不正なデータをviewで査定して、以降の処理を行わせないためらしい。これは僕の予想ですが、データベースへのアクセスが減るから、負荷もかからないとかそんな理由もあるのかな? MVCは個人、チーム、作成したいWebアプリケーションによって考え方が異なってくるのだなあと思い、やっぱりプログラミングは面白いですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progate Ruby on Rails5 I〜III 個人的ざっくりまとめ

Railsの立ち上げ ターミナル $ rails new アプリケーション名 例) rails new tweet_app $ rails server 上記入力後、ブラウザのurlに「localhost:3000」を入力し、「Yay!You're on Rails!」と書いてあるページが表示されていればOK ページ作成 ○ページ作成に必要なもの ・ ビュー ・ コントローラ ・ ルーティング ビュー ビューとは、ページの見た目を作るHTMLファイルのこと。 ターミナル $ rails generate controller コントローラ名 アクション名 例)rails generate controller home top または、 rails g controller home top でもOK 「rails g controller home top」でviewsフォルダの中にhomeフォルダとtop.html.erbファイルが作成される。 app/views/home/top.html.erb <h1>Home#top</h1> <p>Find me in app/views/home/top.html.erb</p> コントローラ ページを表示するとき、railsではコントローラを経由して、ブラウザに返している。 先ほど、「rails g controller home top」を実行したとき、同時にコントローラも作成されており、コントローラファイルの場所は、「app/controllers/home_controller.rb」である。 また、rails g controllerでtopアクションも指定しているため、home_controller.rbの中身は以下のようになっている。 app/controllers/home_controller.rb class HomeController < ApplicationController def top #アクション end end コントローラ内のアクションは、コントローラと同じ名前のビューフォルダから、アクションと同じ名前のHTMLファイルを探して、ブラウザに返す役割を担っている ルーティング ページを表示するときコントローラを経由していたが、ブラウザとコントローラを結ぶ役割がルーティングである。 ルーティングは対応表にして考えるとわかりやすい URL コントローラ アクション home/top home top ルーティングが定義されている場所は、「config/routes.rb」で、中身は以下のようになっている config/routes.rb Rails.application.routes.draw do #get "URL" => "コントローラ名#アクション名" get "home/top" => "home#top" #localhost:3000/home/top #URLの部分を「top」に変えると、localhost:3000/topでアクセスできるようになる get "top" => "home#top" #localhost:3000/top #トップページの場合は、URLの部分を"/"にする get "/" => "home#top" end CSSファイルや画像を保存するファイルの場所 ○CSSファイル 「app/assets/stylesheets/コントローラ名.scss」 ○画像を保存するファイル 「public/画像名」 <% %>と<%= %> <% %>は中身が表示されないので、変数の定義などに使われる。 <%= %>は中身が表示される。 <% language = "Ruby" %> <%= language %>を学ぼう Rubyを学ぼう <% language %>を学ぼう を学ぼう @変数 通常、アクションで定義した変数をビューでそのまま使用することはできない。 そこで、「@変数」とすることで、ビューでも使えるようにする。 posts_controller.rb class PostsController < ApplicationController def index @post1 = "hogehoge" post2 = "fugafuga" end end posts/index.html.erb <%= @post1 %> hogehoge <%= post2 %> ※エラー発生 テーブル作成(モデルとマイグレーション) ターミナル $ rails g model モデル名 カラム名:データ型 例) rails g model Post content:text 上記をターミナルに入力し実行すると、「モデルが定義されたファイル」と「マイグレーションファイル」の2つファイルが生成される。 モデルが定義されたファイルの場所は、「app/models/ファイル名.rb」(ファイル名は単数形) models/post.rb class Post < ApplicationRecord end マイグレーションファイルの場所は、「db/migrate/マイグレーション名.rb」 db/migrate/20170228021417_create_posts.rb class CreatePosts < ActiveRecord::Migration[5.0] def change create_table :posts do |t| t.text :content t.timestamps end end end マイグレーションファイルを使って、データベースに変更を反映するにはターミナルに以下を実行する ターミナル $ rails db:migrate rails console rails consoleはRubyのコードを手軽に実行できる。「quit」でやめることができる。 ターミナル $ rails console Loading Development environment (Rails 5.0.1) [1]pry(main) > 1 + 1 => 2 [2]pry(main) > text = "Hello" => "Hello" [3]pry(main) > text + "World" => "HelloWorld" [3]pry(main) > quit saveメソッド saveメソッドはデータベースに保存するメソッド。 例えば、作成したPostインスタンスをpostsテーブルに保存するとき。(このとき、saveメソッドが使えるのは、PostモデルがApplicationRecordを継承しているから) ターミナル $ rails console > post = Post.new(content: "Hello World") > post.save firstメソッド firstメソッドはテーブルの最初のデータを取得できる。 postsテーブル id content 1 hogehoge 2 fugafuga ターミナル > post = Post.first > post.content => "hogehoge" allメソッド allメソッドはテーブルの全てのデータを取得できる。 index番号を指定することで特定の箇所のデータを取得することもできる。 postsテーブル id content 1 hogehoge 2 fugafuga ターミナル > post = Post.all[1].content => "fugafuga" link_toメソッド link_toメソッドは「第一引数に表示する文字」、「第二引数にURL」 application.html.erb <%= link_to("News", "/news") %> ↓ 以下のように変換される application.html <a href="/news">News</a> find_byメソッド find_byメソッドは、特定のidの投稿を取得できる。(allメソッドのindex番号指定でも同じことができる) postsテーブル id content 1 hogehoge 2 fugafuga ターミナル > post = Post.find_by(id: 1) > post.content => "hogehoge" 変数Params 変数Paramsは、ルーティングで設定したURLの「:id」の値を取得できる。 コントローラのアクション内で使う。 例)ルーティングを「localhost:3000/posts/1」に設定した場合 get "post/:id" => "post#show" posts_controller.rb def show @id = params[:id] end posts/show.html.erb <%= @id %> 結果は「1」と出る ルーティングでデータベースに変更を加えるアクションの場合は「get」ではなく「post」 routes.rb post "posts/create" => "posts#create" form_tagメソッド form_tagメソッドは、フォームに入力されたデータを送信することができる。 「form_tag(送信先のURL) do」のように送信先のURLを指定し、そこにデータを送信する。 posts/new.html.erb <%= form_tag("/posts/create") do %> <textarea name="content"></textarea> <input type="submit" value="投稿"> <% end %> name属性を指定したフォームに入力されたデータは、コントローラのアクション内で受け取ることができる。 フォームのデータは変数paramsで受け取る。 paramsはname属性に設定した文字列をキーとしたハッシュになっている。 posts_controller.rb def create params[:content] end #textareaでhogehogeと打って投稿ボタンを押すと、params[:content]にはhogehogeというデータが入ってくる redirect_toメソッド redirect_toメソッドは、他のURLに転送することができる。 「redirect_to("URL")」とすることで、そのページに転送することができる。 posts_controller.rb def create redirect_to("/posts/index") end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】【JS】GoogleMapで固定した場所にピンを刺したい!【Geocoder】

はじめに この記事では, RailsアプリでGoogle Mapを利用する際に、固定した場所を表示する方法を書き記しています。 特徴は以下です。 Google MapのAPIを利用 指定した緯度軽度の場所にピンを刺す ピンの情報ウィンドウをクリックすると指定したページに遷移する 遷移したページの指定した場所に移動できる(sectionタグを用いる) ページ遷移をなくすこともできます!! この記事をもとに、GoogleMapの表示ができていることが前提とします。 1. Viewを編集する posts/index.html.erb <div id='map'></div> <style> #map { height: 600px; width: 100%; } </style> <script> function initMap() { //初期表示位置:東京駅 let latlng = new google.maps.LatLng(35.68114292160832, 139.76699220422807); //デザイン let styles = [ { stylers: [ { "saturation": 0 }, { "lightness": 0 } ] } ]; let map = new google.maps.Map(document.getElementById('map'), { zoom: 5.3,  //倍率を決める styles: styles, center: latlng }); let transitLayer = new google.maps.TransitLayer(); transitLayer.setMap(map); //複数マーカー ここから ( function() { let markerLatLng = new google.maps.LatLng({lat: 35.170662, lng: 136.923430}); // 名古屋の位置情報を表示 let marker = new google.maps.Marker({ position: markerLatLng, map: map });    //マーカーをクリックしたとき、詳細情報を表示 let infowindow = new google.maps.InfoWindow({ position: markerLatLng, content: "<nav><a href='<%= tweets_url %>#first-block' target='_blank'>名古屋</a></nav>" }); //飛びたいページのURL、タグを入力 marker.addListener('click', function() { infowindow.open(map, marker); }); })(); ( function() { let markerLatLng = new google.maps.LatLng({lat: 34.397521, lng: 132.459266}); // 広島の位置情報を表示 let marker = new google.maps.Marker({ position: markerLatLng, map: map });    //マーカーをクリックしたとき、詳細情報を表示 let infowindow = new google.maps.InfoWindow({ position: markerLatLng, content: "<nav><a href='<%= tweets_url %>#second-block' target='_blank'>広島</a></nav>" }); //飛びたいページのURL、タグを入力 marker.addListener('click', function() { infowindow.open(map, marker); }); })(); ( function() { let markerLatLng = new google.maps.LatLng({lat: 38.260021, lng: 140.882344}); // 仙台の位置情報を表示 let marker = new google.maps.Marker({ position: markerLatLng, map: map });    //マーカーをクリックしたとき、詳細情報を表示 let infowindow = new google.maps.InfoWindow({ position: markerLatLng, content: "<nav><a href='<%= tweets_url %>#third-block' target='_blank'>仙台</a></nav>" }); //飛びたいページのURL、タグを入力 marker.addListener('click', function() { infowindow.open(map, marker); }); })(); //複数マーカー ここまで } </script> <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBbOOQW7X6GRBDQbvKGXhHsw3R8j2Fe-XA&callback=initMap" async defer></script>  指定する地図のピンの位置について //複数マーカー ここから ( function() { let markerLatLng = new google.maps.LatLng({lat: 35.17101, lng: 136.88149}); // 名古屋の位置情報を表示 let marker = new google.maps.Marker({ position: markerLatLng, map: map }); {lat: 35.170662, lng: 136.923430}に、緯度軽度を入力します。 Google Mapで入れたい場所を検索し、ピンの位置を右クリックすると緯度と軽度が表示されます。 飛ばしたい遷移先のURLについて content: "<nav><a href='<%= tweets_url %>#third-block' target='_blank'>   名古屋</a></nav>"}); //飛びたいページのURL、タグを入力 続いて、遷移先のページ指定についてです。 Rubyを使っている場合は、 $ rails routes をするとpathが表示されると思いますが、pathのかわりにurlといれます。 また、そのページの遷移場所を指定するときは、 タグを用いて、遷移先のページを書き、その後 <%= tweets_url %> の後ろに #third-block のように入力します。 sectionタグの使い方に関してはこのような記事を参考にしてください。 リファレンス idについて ページ内遷移について 2-5. 自己紹介サイトを作ろう! 終わりに これでGoogle Mapを利用する際に、固定した場所を表示することができました! Railsで書いているけどMapに関してはJSを用いなければならず、よくわからない!という人をよく見受けます。 確かによくわからない部分もありますが、どこがどんな風に対応しているかを考えれば糸口は見つかりますのでご自身の必要に応じて変更してみてください!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む