- 投稿日:2021-01-05T23:40:34+09:00
簡単カレンダーを少し装飾してみた
- 投稿日:2021-01-05T22:25:32+09:00
simple_calendarを導入したRails6.0でのDB環境PostgreSQLの設定
学習メモ用です。
解決したい問題
simple_calendarを導入したRails6.0で
開発環境DBがSQLite3、本番環境DBがPostgreSQL本番環境において下記エラーが発生し、posts/index viewが表示されない事態に。
simple_calendarを使っているから?
ActionView::Template::Error (PG::UndefinedColumn: ERROR: column posts.user_id does not exist
なぜか、本番環境ではpostsテーブルにuser_id columnだけが存在しないからうまくいかない。
と、いうことで開発環境にもPostgreSQLを適用してみる!
開発環境
Ruby 2.7.1
Rails 6.0.3
OS macOS Catalinapggemのインストール
rails6.0では
% rails db:system:change --to=postgesql
このコマンドで簡単に移行できるよう。
以下のようにsqlite3だったところがpgに置き換わる(Gemfileも同様に置き換わる)
config/database.yml<!-- some long code --> default: &default - adapter: sqlite3 + adapter: postgresql <!-- some long code -->bundle installを実行すると
[!] There was an error parsing `Gemfile`: You cannot specify the same gem twice with different version requirements. You specified: pg (~> 1.4) and pg (>= 0). Bundler cannot continue.pggemのバージョン違いが同時にあるためbundleinstallできないとのこと
pg (~> 1.4)を残し、gem pgは削除して再度Fetching gem metadata from https://rubygems.org/. Could not find gem 'pg (~> 1.4)' in any of the gem sources listed in your Gemfile.'pg (~> 1.4)'が見つからないとのことで、gem istall pgを実行すると
Building native extensions. This could take a while... ERROR: Error installing pg: ERROR: Failed to build gem native extension. <!-- some long code --> checking for pg_config... no No pg_config... trying anyway. If building fails, please try again with --with-pg-config=/path/to/pg_config checking for libpq-fe.h... no Can't find the 'libpq-fe.h header *** extconf.rb failed *** Could not create Makefile due to some reason, probably lack of necessary <!-- some long code -->No pg_configとCan't find the 'libpq-fe.h header が問題っぽい。
まずPostgreSQLインストールをできていないのでは?
% brew install postgresql↑これだと最新安定版をインストール可
インストール成功!% postgres --version postgres (PostgreSQL) 13.1gem install pgを実行
Building native extensions. This could take a while... Successfully installed pg-1.2.3 Parsing documentation for pg-1.2.3 Installing ri documentation for pg-1.2.3 Done installing documentation for pg after 1 seconds 1 gem installed gem 'pg (~> 1.4)' in any of the gem sources listed in your Gemfilegem 'pg (~> 1.4)'はインストールできなかったので、指定やめた。
再度bundle install実行、無事成功!
rails sを起動してうまくいってるか確かめようとしたところ、
いつも通りyarn installをしろとのことで、% yarn install --check-files yarn install v1.22.10 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... warning " > webpack-dev-server@3.11.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0". warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0". [4/4] ? Building fresh packages... [1/3] ⠈ fsevents [-/3] ⠈ waiting... error /Users/shigeyuki_yunoki/ucms_app/node_modules/node-sass: Command failed. Exit code: 1 <!-- some long code --> No receipt for 'com.apple.pkg.CLTools_Executables' found at '/'. No receipt for 'com.apple.pkg.DeveloperToolsCLILeo' found at '/'. No receipt for 'com.apple.pkg.DeveloperToolsCLI' found at '/'. gyp: No Xcode or CLT version detected! gyp ERR! configure error gyp ERR! stack Error: `gyp` failed with exit code: 1 gyp ERR! stack at ChildProcess.onCpExit (/Users/shigeyuki_yunoki/ucms_app/node_modules/node-gyp/lib/configure.js:345:16) gyp ERR! stack at ChildProcess.emit (node:events:376:20) gyp ERR! stack at Process.ChildProcess._handle.onexit (node:internal/child_process:284:12) gyp ERR! System Darwin 19.6.0 gyp ERR! command "/usr/local/Cellar/node/15.5.0/bin/node" "/Users/shigeyuki_yunoki/ucms_app/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library=" gyp ERR! cwd /Users/shigeyuki_yunoki/ucms_app/node_modules/node-sass gyp ERR! node -v v15.5.0 gyp ERR! node-gyp -v v3.8.0 gyp ERR! not ok Build failed with error code: 1 <!-- some long code -->yarnにおけるXcodeとCLTをインストール
gyp: No Xcode or CLT version detected!これが問題らしい
% pkgutil --packages | grep CLCLToolsを確認するが何も出力されず、
Xcodeのバージョンの問題だと思われるため、再インストール
% sudo rm -rf $(xcode-select -print-path) Password:macのパスワードを入力したものの反応なし
% sudo rm -rf /Library/Developer/CommandLineToolsXcode Command Line Toolsのあるディレクトリを削除することでアンインストールする。
こちらは実行していないが、最終的にうまくいったので、Xcodeはインストールしていなかったものと思われ。% xcode-select --installコマンド上でXcode Command Line Toolsをインストール。
xcode-select: error: command line tools are already installed, use "Software Update" to install updatesインストール済みだと上記になる
Xcodeをインストールしてから実行すると以下が出力された。
% pkgutil --packages | grep CL com.apple.pkg.CLTools_Executables com.apple.pkg.CLTools_SDK_macOS110 com.apple.pkg.CLTools_SDK_macOS1015 com.apple.pkg.CLTools_macOS_SDK % pkgutil --pkg-info com.apple.pkg.CLTools_Executables package-id: com.apple.pkg.CLTools_Executables version: 12.3.0.0.1.1607026830 volume: / location: / install-time: 1609036372 groups: com.apple.FindSystemFiles.pkg-groupNo Xcode or CLT versionは解決した!
% yarn install --check-files yarn install v1.22.10 [1/4] ? Resolving packages... [2/4] ? Fetching packages... [3/4] ? Linking dependencies... warning " > webpack-dev-server@3.11.0" has unmet peer dependency "webpack@^4.0.0 || ^5.0.0". warning "webpack-dev-server > webpack-dev-middleware@3.7.2" has unmet peer dependency "webpack@^4.0.0". [4/4] ? Building fresh packages... ✨ Done in 184.82s.yarn install成功!
PGSQLサーバーの起動
rails sを起動してみる
PG::ConnectionBad (could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/tmp/.s.PGSQL.5432"? ):PGSQLサーバーが動いていないということらしい
% brew service start postgresql Error: Unknown command: service% brew tap homebrew/servicesこのコマンドでservicesのような外部コマンドを取り込めるとのこと
% brew services start postgresql ==> Successfully started `postgresql`PGSQLサーバーが動いた!
PGSQLでのDBの作成と権限付与
rails s起動すると
ActiveRecord::NoDatabaseError FATAL: database "ucms_app_development" does not existDBが存在していない
このページで最初にあげた
config/database.yml
の詳しい中身はこちら。config/database.ymldefault: &default - adapter: sqlite3 + adapter: postgresql username: ucms_app 追記 password: 追記 host: localhost 追記 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> timeout: 5000 development: <<: *default - database: db/development.sqlite3 + database: ucms_app_development test: <<: *default - database: db/test.sqlite3 + database: ucms_app_test production: <<: *default - database: db/production.sqlite3 # database: ucms_app_production Herokuへのデプロイを考慮して削除 # username: ucms_app default内に追記したので削除 password: <%= ENV['UCMS_APP_DATABASE_PASSWORD'] %>下記コマンドにより、PGSQLのDB設定にpgが導入され、sqliteが削除された。
更に設定を追記したのが上記。% rails db:system:change --to=postgesqlこれでDBが作られたわけではない。
% psql -lPGSQLのDB表示する
List of databases
Name Owner Encoding Collate Ctype Access privileges postgres (macユーザー) UTF8 C C template0 (macユーザー) UTF8 C C =c/(macユーザー) template1 (macユーザー) UTF8 C C =c/(macユーザー) (3 rows)
"(macユーザー)"はユーザー毎に異なる。
DBを作成する
% createdb ucms_app_development % createdb ucms_app_test% createdb ucms_app_productionはherokuで本番環境を扱うため不要
% psql -lList of databases
Name Owner Encoding Collate Ctype Access privileges postgres (macユーザー) UTF8 C C template0 (macユーザー) UTF8 C C =c/(macユーザー) template1 (macユーザー) UTF8 C C =c/(macユーザー) ucms_app_development (macユーザー) UTF8 C C ucms_app_test (macユーザー) UTF8 C C (5 rows)
% psql -c 'select * from pg_user' postgres
usename usesysid usecreatedb usesuper userepl usebypassrls passwd valuntil useconfig (macユーザー) 10 t t t t ******** (1 row)
ユーザーを作成する
% createuser ucms_app'(macユーザー)'でpsqlを操作する
% psql -U (macユーザー) ucms_app_development ucms_app_development=# \du #ユーザー一覧を表示List of roles
Role name Attributes Member of (macユーザー) Superuser, Create role, Create DB, Replication, Bypass RLS {} ucms_app {} ucms_app_development=# ALTER ROLE "ucms_app" WITH Superuser; ALTER ROLE #⬆️(macユーザー)がucms_appに権限を付与。 ucms_app_development=# \duList of roles
Role name Attributes Member of (macユーザー) Superuser, Create role, Create DB, Replication, Bypass RLS {} ucms_app Superuser {}
config/database.yml
のusername:
と一致させる必要がある。% psql -c 'select * from pg_user' postgres
usename usesysid usecreatedb usesuper userepl usebypassrls passwd valuntil useconfig (macユーザー) 10 t t t t ******** ucms_app 16384 f t f f ******** (2 rows)
% psql -lList of databases
Name Owner Encoding Collate Ctype Access privileges postgres (macユーザー) UTF8 C C template0 (macユーザー) UTF8 C C =c/(macユーザー) + (macユーザー)=CTc/(macユーザー) template1 (macユーザー) UTF8 C C =c/(macユーザー) + (macユーザー)=CTc/(macユーザー) ucms_app_development ucms_app UTF8 C C ucms_app_test ucms_app UTF8 C C ucms_app_test-0 ucms_app UTF8 C C ucms_app_test-1 ucms_app UTF8 C C (7 rows)
% createdb ucms_app_development % createdb ucms_app_test本当は上記のコマンドは
% createdb ucms_app_development -0 ucms_app % createdb ucms_app_test -0 ucms_appとして、オーナーを
username:
と同様のucms_app
でDB作成すれば良かったが、
どこかの段階でオーナーが切り替えられたのでよしとする。ucms_app_development=# ALTER ROLE "ucms_app" WITH Superuser; ALTER ROLE多分ここ。
rails sすると下記になるのでmigrateを実行する
ActiveRecord::PendingMigrationError ( Migrations are pending. To resolve this issue, run: rails db:migrate RAILS_ENV=development ):これでPostgreSQLのDB環境設定は完了!
gem 'yaml_db'でsqlite3のデータをpgに移行
gem 'yaml_db'bundle exec rails db:data:dump下記ファイルが作られる。
db/data.ymlーーー ar_internal_metadata: columns: - key - value - created_at - updated_at records: - - environment - development - '2020-11-13 12:22:09.119176' - '2020-11-13 12:22:09.119176' --- posts: columns: - id - title - content - start_time - user_id - created_at - updated_at records: <!-- some long code --> - - 75 - ddd - ddd - '2020-12-18 00:00:00' - 1 - '2020-12-18 12:29:39.573445' - '2020-12-18 12:29:39.573445' <!-- some long code --> --- users: columns: - id - name - email - seriousness - created_at - updated_at - password_digest records: - - 1 - sssss - s@gmail.com - 中等症 - '2020-11-13 12:23:49.247847' - '2020-11-29 01:57:03.602324' - "$2a$12$/r0ip..." <!-- some long code -->% bundle exec rails db:data:loadPostgreSQLにこれで移行できる。
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "user_id" of relation "posts" does not exist LINE 1: ...INTO "posts" ("id","title","content","start_time","user_id",... ^ bin/rails:4:in `<main>' Caused by: PG::UndefinedColumn: ERROR: column "user_id" of relation "posts" does not exist LINE 1: ...INTO "posts" ("id","title","content","start_time","user_id",...なぜか、postsのuser_idがなくなっている。。。
schema.rbが書き変わってる。。。なぜだ。。。PostgreSQL導入前
ActiveRecord::Schema.define(version: 2020_11_15_015429) do create_table "posts", force: :cascade do |t| t.string "title" t.text "content" t.datetime "start_time" t.integer "user_id" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false t.index ["user_id", "created_at"], name: "index_posts_on_user_id_and_created_at" t.index ["user_id"], name: "index_posts_on_user_id" endPostgreSQL導入後
ActiveRecord::Schema.define(version: 2020_11_15_015429) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" create_table "posts", force: :cascade do |t| t.string "title" t.text "content" t.datetime "start_time" t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false end直接、PostgreSQLに:user_id columnを作る!
ucms_app_development=# ALTER TABLE posts ADD COLUMN user_id integer; ALTER TABLE ucms_app_development=# \d posts Table "public.posts" Column | Type | Collation | Nullable | Default ------------+--------------------------------+-----------+----------+----------------------------------- id | bigint | | not null | nextval('posts_id_seq'::regclass) title | character varying | | | content | text | | | start_time | timestamp without time zone | | | created_at | timestamp(6) without time zone | | not null | updated_at | timestamp(6) without time zone | | not null | user_id | integer | | | Indexes: "posts_pkey" PRIMARY KEY, btree (id) (END)これでpostsテーブルにuser_id追加完了!
% rails db:migrate == 20201115015429 AddUserIdToPosts: migrating ================================= == 20201115015429 AddUserIdToPosts: migrated (0.0000s) ========================再度、% bundle exec rails db:data:loadでdb/data.ymlファイルをpgのDBに!
本番環境herokuにDB適応させる!
% heroku pg:psql --> Connecting to postgresql... psql (13.1, server 12.5 (Ubuntu 12.5-1.pgdg16.04+1)) SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off) Type "help" for help. ucmsapp...::DATABASE=> \d posts Table "public.posts" Column | Type | Collation | Nullable | Default ------------+--------------------------------+-----------+----------+----------------------------------- id | bigint | | not null | nextval('posts_id_seq'::regclass) title | character varying | | | content | text | | | start_time | timestamp without time zone | | | created_at | timestamp(6) without time zone | | not null | updated_at | timestamp(6) without time zone | | not null | Indexes: "posts_pkey" PRIMARY KEY, btree (id) ucmsapp...::DATABASE=> ALTER TABLE posts ADD COLUMN user_id integer; ALTER TABLE開発環境と同様にuser_idを作成
% heroku run bundle exec rails db:data:loadもしかすると
% heroku run rails db:migrateすれば良かっただけかも。
herokuに開発環境のDBをコピーするには?
heroku pg:push ucms_app_development ucmsapp... ▸ Unknown database: ucmsapp... Valid options are: ▸ DATABASE_URLconfig/database.yml#You can use this database configuration with: # # production: # url: <%= ENV['DATABASE_URL'] %>productionのurlは'DATABASE_URL'とのこと。
heroku pg:push ucms_app_development DATABASE_URL heroku-cli: Pushing ucms_app_development ---> postgresql... ▸ Remote database is not empty. Please create a new database or use ▸ heroku pg:reset heroku pg:reset ▸ WARNING: Destructive action ▸ postgresql... will lose all of its data ▸ ▸ To proceed, type ucmsapp... or re-run ▸ this command with --confirm ucmsapp... > ucmsapp... Resetting postgresql... doneheroku pg:push ucms_app_development DATABASE_URL heroku-cli: Pushing ucms_app_development ---> postgresql... pg_dump: last built-in OID is 16383 pg_dump: reading extensions pg_dump: identifying extension members <!-- some long code --> pg_restore: creating CONSTRAINT "public.posts posts_pkey" pg_restore: creating CONSTRAINT "public.schema_migrations schema_migrations_pkey" pg_restore: creating CONSTRAINT "public.users users_pkey" heroku-cli: Pushing complete.まとめ
本番環境DBがPostgreSQLで、DBを移行することができた!
何故、postsテーブルにuser_id columnだけが存在しなくなるのかはわからなかったので、
その疑問もいつか解決できれば!
- 投稿日:2021-01-05T21:27:05+09:00
Rails 初心者から抜け出すべく意識すること (※随時追加予定)
コードが動けばいいやの時期はそろそろ終わりたい
と思い、調べてメモするまた業務でおこなっていて指摘されることも書きます
Ruby編
メソッド
1つのメソッドに複数のロジックを書かずに別のメソッドに抽象化させる
ロジックや操作ごとにメソッドを作って、それを呼び出す形にする例) if文で条件により操作を分岐させているときに、
2つ以上の操作をそのメソッド内に直接書くのではなく、
操作ごとにメソッドを作って、それを呼び出す形にする定数
ロジックで用いる具体的な数字は定数に格納する
ロジックを作るときに、生の数字を書いてしまうと初見の人には伝わらない
名前をつけた定数に格納することで、その数字が何を意味するのか分かる例) 税 TAX = 10
基本ストレージ DEFAULT_STORAGE = 5 などRails
Routing
ルーティングの書く順番だけではなく**ネストできるところはする::
独自URLを設定しているルーティングは
resourcesにネストして書いた方が分かりやすくなるconfig/routes.rbRails.application.routes.draw do resources :posts do resource :likes end endController
ルーティングのresourcesにonlyやexpectオプションを付けることで、
どのリソースを使っているかを可視化するクラスを継承してsuperメソッドを使う
publicメソッドはアクションのみに
処理の移動先は基本Model
Modelへの移動とリファクタリングを同時にするのが厳しそうな場合は、
アクションの中身を一旦全てModelに映してから
必要に応じてControllerに戻す、という手順を取るのも良いModel
ビジネスロジックはmodelに書く
concerns/に置くのはあくまで複数のmodel間で共通する処理、
クラスをまたぐ処理はconcernに同ファイル内でmodule化する
複数のクラスで使いたい処理のまとまりはconcern化して、取り込むクラスにmix-inする複数のmodelにまたがる処理はservice層に書く
modelからデータを取り出す方法としてscopeを使う
その場合、where句を多用して条件を毎回書かなくても、scopeに定義すればメソッドのように使えるView
共通パーツ化(部分テンプレート、partial)するのと、
viewにロジックを書かない(ヘルパー、helper)Helperメソッドに定義する
viewにはなるべくロジックを書かない、見た目に関するメソッドはヘルパーに記載するページごとのjsファイルに分離する
erbファイルにスクリプトタグを仕込みかかないviewに直接styleを記述しない
js専用のclassはjs-プレフィックスをつけて区別する
その他
繰り返し使う文字列をlocaleに定義する
時刻のフォーマット等もlocaleを使い、strftimeは基本使わない
金額や距離、日付等も指定できるなるべく短い書き方、リファクタ
→後置if、三項演算子、ぼっち演算子、nilガード、%記法など参考記事
https://qiita.com/TOSHIMITSU_MIYACHI/items/fec069bcdf23b6be7623
https://qiita.com/d0ne1s/items/fabbc0df7c273cf04fc3
https://qiita.com/shunsuke227ono/items/60de21690238aa25e9d4
- 投稿日:2021-01-05T21:22:25+09:00
gem bulletを入れてみた(N+1問題に自分で気づけない人のために)
何をしたか
Railsアプリを作っています。
N+1問題
について前々から関心はあったのですが、SQLについて学習が不足していることもあり、自分では気づけないことも多いので、N+1問題が発生している箇所があったら教えてくれるgem bullet
を入れてみることにしました。▶︎公式のドキュメントはこちら:bullet
導入方法
以下のコードをGemfileに記載します。
Gemfilegroup :development do gem 'bullet' end
bundle install
後、以下のコマンドを実行します。bundle exec rails g bullet:installこの時、テスト環境にも
bullet
を入れるかどうか聞かれると思いますが、公式のinstall
のところにはdevelop
環境へのインストールだけが記されていたのと、公式のテスト環境へのインストールの記述を見てみても、なんだか色々大変そうだな(今回はそこまで時間をかける必要はない)だったので、今回は見送りました。
デフォルトで設定される内容
↑上記のコマンドで、
config/environments/development.rb
にデフォルトで以下の様なコードが追加されました。config/environments/development.rbRails.application.configure do # ここから config.after_initialize do Bullet.enable = true Bullet.alert = true Bullet.bullet_logger = true Bullet.console = true # Bullet.growl = true デフォルトでコメントアウトされてる Bullet.rails_logger = true Bullet.add_footer = true end # ここまでが追加される。以下、同ファイルにもとから入っていた内容は省略 endbulletの設定可能な項目一覧は←こちらにありましたが、デフォルトで入っている項目について確認すると、
Bullet.enable
...Bulletのgemを利用可能にします。でも、何もしません。Bullet.alert
...ブラウザにJSのアラートを出します。Bullet.bullet_logger
...bulletのログファイルを出します(場所:Rails.root/log/bullet.log)Bullet.console
...console.logに警告を出します。Bullet.growl
...Growl
がインストールされているときに、ポップアップの警告を出します。Bullet.rails_logger
...railsのログに警告を出します。Bullet.add_footer
...画面左下にメッセージを出します。...なるほどです。
実際の使用画面
直し方まで教えてくれていて便利ですね^^
未解決の問題
users
とimages
の両方のテーブルに対して、N+1
問題が発生していたので、この様に治したのですが...。@posts = Post.includes(:user, :images).order(created_at: 'DESC')実際に本当にこれでクエリが最適化しているのかは(クエリ数は減っている)、SQLをきちんと勉強しないとわからないなと思ったのでした...。
とりあえず、SQLが未熟なまま開発を進めていた身としては、ひとつ助けになりそうです
- 投稿日:2021-01-05T19:45:03+09:00
Herokuにデプロイしたのにうまく反映できない時
- 投稿日:2021-01-05T17:29:56+09:00
has_one association factory_bot accepts_nested_attributes_for request_spec [自分用]
class Project < ApplicationRecord has_one :location, class: 'Project::Location' accepts_nested_attributes_for :location, allow_destroy: true endclass Project::Location < ApplicationRecord belongs_to project endfactory
FactoryBot.define do factory :project_location, class: 'Project::Location' do association :prefecture association :project address { '港区芝公園4丁目2−8' } station { 'JR浜松町駅' } end endrequest spec
let!(:prefecture) { create(:prefecture) } let!(:category) { create(:project_category) } let(:project_params) do attributes_for(:project, location_attributes: attributes_for(:project_location, prefecture_id: prefecture.id) end post projects_path, params: { project: project_params }prefecture_idを指定してあげる
- 投稿日:2021-01-05T16:16:25+09:00
Rails6でMaterialize.cssのDatepickerを使いたい!
初めまして、ゆん♂です!
不動産業界に激震を走らせるためのアプリを作ってます!どうせ作るなら最新の環境で作りたい!ということで
Rails6系
でアプリ制作してます。が!
Rails5系
とRails6系
では大きく仕様が異なっており、表題のような簡単なことでも超絶つまづいてしまいました。
解決してみたら何のことはなかったんですが、僕はこれだけで丸1日溶けました…。
なので同じ轍を踏まないよう、僕の試行錯誤の軌跡をここに残しておきます。長ったらしい話はいいからさっさと結果だけよこせや、って方はこちらへどうぞ!
開発環境
言語:
Ruby2.6.6
フレームワーク:Rails6.0.3.4
データベース:MySQL(ローカル)
IDE:VScode
Rails6ってなんかカッコイイ!!
これは
Rails6
を採択した理由です。マジです。
Rails6
って何か響きだけでカッコ良くないですか!?
しかも5よりも6のほうが新しいし、古いバージョンで作って早々バージョンアップデートとか必要になったら二度手間!
(↑これは多分正しい、、、はず)
という安直な理由でRails6
を採択しました。このあと地獄を見るとも知らずに、、、
Materialize.cssのDatepickerオシャレ!!
ちょっと見てくださいコレ。
めちゃくちゃオシャレじゃないですか??
CSS
全く書かずにこんなUIが実現できるなんて便利な世の中ですよねほんと☺️で、このドキュメントには
「カレンダー表示させたいinputタグ
にdatepicker
というクラス名を付けてな」
「あとはJavaScript
かjQuery
でちょちょいと書いたら終わりやで」
と書いてあります。オージーザス、神よありがとう?gem じゃなくて webpacker!!
Datepicker
を使うにはjQuery
を入れておいた方が良さそうです。
Rails5系
だとgem
でインストールしてCSS
やJS
で読み込ませてねー的な記事がたくさん出てくるのですが、Rails6系
では JavaScript系のパッケージ機能をgem
ではなくwebpacker
で管理するようになったそうです!Webpackerとは、 Webpackを使用してRuby on Rails上でJavaScript開発をするために必要な一連のまとまりを、標準で実装することができるgemパッケージ です。 Rails6.0より、webpackerが標準実装になりました。
なんてこったパンナコッタ!!
オーマイゴッドファーザー降臨!よいしょ!
こりゃ便利!!、、、、、なのでしょうか???
この辺り実はメリデメがよく分かっていません。
んー、あいまいみーまいん?まぁ正確な理解は置いておいて、
Rails6 jQuery
で調べると必ずと言っていいほどwebpacker
で管理しましょうねーという記事が出てきます。
というかそんな記事しかありません!
(↑それ自体は多分正しい)
併せてjquery-ui-dist
なるものもインストールする必要があるようです。以下、参考にした記事
Rails6でjQueryの導入方法
【Rails6】jQueryがなかなか使い始められない人へ
【Rails6】Webpackerを用いてjQueryをインストールする手順を簡単にまとめてみた
Rails 6:webpackerを介してjquery-uiを追加する方法は?実はこの時すでに術中にハマってました。
js系のパッ毛0時は必ずwebpacker
で管理しなければならないと思い込んでいたのです。
そのため、rails6 webpacker datepicker
とかwebpacker jquery-ui-dist
などと調べていたため、余計にドツボにハマる結果となりました。
パッ毛0時
て何やねん、パッケージ
やろがい。丸1日かけてやったこと(間違い)
リアルに丸1日ハマり倒した内容を簡潔に書いていきます。
間違ってもこの手順を踏まないでください。
あくまでも同じ過ちを犯さないで欲しいがためのメモです。
これは誤った手順です!① Yarnで
jQuery
とjquery-ui-dist
をインストール$ yarn add jquery $ yarn add jquery-ui-dist↑ターミナルでこのコマンドを実行しました。
②
jQuery
とjquery-ui-dist
を有効化config/webpack/environment.jsconst { environment } = require('@rails/webpacker') // 追加ここから ////////////////////////////////////////// const webpack = require('webpack') environment.plugins.prepend('Provide', new webpack.ProvidePlugin({ $: 'jquery', jQuery: 'jquery' }) ); const aliasConfig = { 'jquery': 'jquery-ui-dist/external/jquery/jquery.js', 'jquery-ui': 'jquery-ui-dist/jquery-ui.js' }; environment.config.set('resolve.alias', aliasConfig); // 追加ここまで ////////////////////////////////////////// module.exports = environmentapp/javascript/packs/application.jsrequire("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") // 追加ここから ////////////////////////////////////////// global.$ = require("jquery") require("jquery") require("jquery-ui") // 追加ここまで //////////////////////////////////////////app/views/layouts/application.html.erb<!DOCTYPE html> <html> <head> <title>ReTECH</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <!-- Compiled and minified CSS --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> <!-- Compiled and minified JavaScript --> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> <!-- Let browser know website is optimized for mobile --> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <!-- Make FontAwesome available --> <script src="https://kit.fontawesome.com/xxxxxxxxxxx.js" crossorigin="anonymous"></script> <%= favicon_link_tag 'favicon.ico' %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= stylesheet_link_tag '//ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/themes/le-frog/jquery-ui.min.css' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <%= yield %> </body> </html>↑ 各ファイルにこのような記述を追加しました。
※app/views/layouts/application.html.erb
に関してはMaterialize.css
をCDN読み込みしてたりするので丸々掲載しました。FontAwesome
はアカウントごとにリンクが異なるので自分でアカウント発行してください。参照: Rails 6: How to add jquery-ui through webpacker?
参照: FontAwesome6 無料登録③
datepicker
をフォームに適用してみるapp/javascript/modules/datepicker.js$(document).ready(function(){ $('.datepicker').datepicker(); });↑datepicker用jsファイルを定義
app/javascript/packs/application.jsrequire("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") global.$ = require("jquery") require("jquery") require("jquery-ui") // 追加ここから ////////////////////////////////////////// require("../modules/datepicker") // 追加ここまで //////////////////////////////////////////↑作ったdatepicker定義ファイルの読み込み
app/views/devise/registrations/new.html.erb<div class="input-field"> <%= f.label :birthday %><br /> <%= f.text_field :birthday, required: true, class: 'datepicker' %> </div>↑誕生日入力フォームに
datepicker
というクラス名を付与出来上がったものがこちらです☺️
おい!
昔のiPhoneかよ!
一瞬電卓に見えたわ!
マテリアルの「マ」の字もないな!
作りたいのはこっち!!
どれどれ、、、
電卓かて!!
たぶん原因はこんな感じ
恐らく原因は CDNで
Materialize.css
を先に呼び出してしまい、そのあとwebpacker
の設定が読み込まれるため デフォルトのdatepicker
のUIで上書きされてしまっているのかなと推測しました。ダメ元で
Materialize.css
のCDN呼び出しの記述をファイル後尾に移動したり、CDNではなくCSSファイル
とJSファイル
をダウンロードしてきて配置したりしました。が、やはりダメ、、、
今までやったことは一応
Github
でプルリク作って、もう一度やり直すことを決めました。。。そして色んな試行錯誤を重ねた結果、こんな境地にたどり着いたのです。
jQuery
もCDNで呼び出せちゃったりしたりするかもなんじゃない?そうです、何も
webpacker
で管理せず、Materialize.css
と同じようにCDNで呼び出しちゃえばいいのでは?
そうすれば先にjQuery
呼び出して、後からMaterialize.css
を呼び出せる、、、UIを上書きされることもないはず!!ということでやってみた(これが正解)
app/views/layouts/application.html.erb<!DOCTYPE html> <html> <head> <title>ReTECH</title> <%= csrf_meta_tags %> <%= csp_meta_tag %> <!-- Expressly read CDN to make jQuery available before make Materialize-UI available --> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <!-- Compiled and minified CSS --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> <!-- Compiled and minified JavaScript --> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> <!-- Let browser know website is optimized for mobile --> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <!-- Make FontAwesome available --> <script src="https://kit.fontawesome.com/xxxxxxxxxxxx.js" crossorigin="anonymous"></script> <%= favicon_link_tag 'favicon.ico' %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %> </head> <body> <%= yield %> </body> </html>↑
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
という記述でjQuery
を先に読み込み、そのあとにMaterialize.css
のCDNを読み込んでいる。app/javascript/modules/datepicker.js$('document').ready(function() { $('.datepicker').datepicker(); })↑datepicker定義
app/javascript/packs/application.jsrequire("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") // 追加ここから ////////////////////////////////////////// require("../modules/datepicker") // 追加ここまで //////////////////////////////////////////↑datepicker読み込み
app/views/devise/registrations/new.html.erb<div class="input-field"> <%= f.label :birthday %><br /> <%= f.text_field :birthday, required: true, class: 'datepicker' %> </div>↑誕生日入力フォームに
datepicker
というクラス名を付与出来上がったものがこちらです?
完璧!!!
以上、長文にお付き合いいただきありがとうございました?♂️
- 投稿日:2021-01-05T14:43:43+09:00
【Ruby on Rails】resourcesメソッドを使って、ルーティングを自動で作成する。
はじめに
resourcesメソッドについて、備忘録として残しておきます。
resourcesメソッド
・
resources
メソッドはroutes.rbファイルの中に書き込むメソッドです。
・Railsの基本となる7つのアクションのルーティングをまとめて追加することができます。Railsの基本となる7つのアクション
基本となる7つのアクションを以下に記載します。
アクション HTTP 役割 URL index get リソースの一覧を表示する。 /users show get リソースの詳細を表示する。 /users/:id new get リソースを新規作成する。 /users/new create post リソースを新規作成して、保存する。 /users edit get リソースを編集する。 /users/:id/edit update put/patch リソースを更新させる。 /users/:id destroy delete リソースを削除する。 /users/:id 定義の仕方
まずは、resourcesメソッドを定義していない状態でルーティングを確認し、何も定義されていないことを確認します。
続いて、以下のように
resources
メソッドを定義していきます。
※「users」の部分には作成したコントローラ名が入ります。今回はusers_controller.rbを例に行います。routes.rbRails.application.routes.draw do #resourcesメソッド定義する resources :users end定義した後にルーティングを確認すると・・・
基本的な7つのアクションのルーティングが追加されていることがわかります。Prefix Verb URI Pattern Controller#Action users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroyresourcesメソッドのオプション
ここからは、
resources
メソッドのオプションをめもしておきます。only
7つのアクションのうち、特定のアクションのみを指定したい時に使用できます。
routes.rbRails.application.routes.draw do #createアクションとnewアクションのみ resources :users, only:[:create, :new] endルーティングを見てみると、こんな感じになっています。
POST /users(.:format) users#create new_user GET /users/new(.:format) users#newmemeber
member
メソッドは、7つのアクション以外のアクションを追加することができます。
まずは、定義方法について、以下記載します。routes.rbRails.application.routes.draw do #memberメソッドの定義方法 resources :users do member do get :following, :followers end endこのように定義することで、7つのアクション以外に以下のようなルーティングが得られます。
member
メソッドでは、idで指定した個々のリソースに対するアクションを定義できます。Prefix Verb URI Pattern Controller#Action following_user GET /users/:id/following(.:format) users#following followers_user GET /users/:id/followers(.:format) users#followers users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroycollection
collection
メソッドもmember
メソッドと同様に7つのアクション以外のアクションを追加することができます。
member
メソッドと違う点は、collection
メソッドはリソース全体に対するアクションを定義するという点です。定義方法も
member
メソッドと同様です。routes.rbRails.application.routes.draw do #collectionメソッドの定義方法 resources :users do collection do get :following, :followers end endルーティングを見てみると、
member
メソッド同様7つのアクション以外に追加されていることがわかります。
が、URI Pattern
の箇所が異なります。
collection
メソッドの方は、全てのリソースに対してアクションを定義しているので:/idの部分が省略されています。Prefix Verb URI Pattern Controller#Action following_user GET /users/following(.:format) users#following followers_user GET /users/followers(.:format) users#followers users GET /users(.:format) users#index POST /users(.:format) users#create new_user GET /users/new(.:format) users#new edit_user GET /users/:id/edit(.:format) users#edit user GET /users/:id(.:format) users#show PATCH /users/:id(.:format) users#update PUT /users/:id(.:format) users#update DELETE /users/:id(.:format) users#destroy【2つのメソッドの比較】
Prefix Verb URI Pattern Controller#Action ##memberメソッド following_user GET /users/:id/following(.:format) users#following followers_user GET /users/:id/followers(.:format) users#followers ##collectionメソッド following_user GET /users/following(.:format) users#following followers_user GET /users/followers(.:format) users#followers参考文献
Rails tutorial 第7章 ユーザー登録
https://railstutorial.jp/chapters/sign_up?version=4.2Rails tutorial 第14章 ユーザーをフォローする
https://railstutorial.jp/chapters/following_users?version=6.0#cha-following_users【Rails】resourcesメソッドを使ってルーティングを定義しよう!
https://pikawaka.com/rails/resources
- 投稿日:2021-01-05T14:04:49+09:00
デプロイ前にファイルの容量を確認
- 投稿日:2021-01-05T12:54:57+09:00
RSpecテストでActionCableをテストしたい
前提
Rspecのセットアップなどは説明しません。
この記事などを参考にしてセットアップしてください。
https://qiita.com/tatsurou313/items/c923338d2e3c07dfd9ee1. Capybaraのサーバーをpumaに設定する。
調べてみたところCapybaraのデフォルトのサーバはRailsアプリを別スレッドで動かしているだけのようで、ActionCableを利用するにはpumaを使う必要があります。
そのため、rails_heplerなどにpumaを使用するように記載する必要がありました。rails_htpler.rbCapybara.server = :pumaこれでOKと思いましたが、なぜかまだテストがエラーになります。
expect { fill_in "message_content", with: "宜しくお願いします。" click_button "送信" }.to change(Message, :count).by(1) expected `Message.count` to have changed by 1, but was changed by 02. sleepで待ち時間を設定する。
少し考えれば分かる簡単なことですがおそらく、非同期通信をしているのですぐにはMessageテーブルのカウントが増えませんでした。
messages_spec.rbexpect { fill_in "message_content", with: "宜しくお願いします。" click_button "送信" sleep 5 }.to change(Message, :count).by(1)そこでこのようにsleepを入れてあげれば、無事テストはパスするようになりました。
Capybaraをpumaに変更するという記事はいくつかありましたがsleepも設定するという記事は見当たらなかったので投稿しました。
参考記事
https://qiita.com/tatsurou313/items/c923338d2e3c07dfd9ee
https://blog.willnet.in/entry/2017/06/05/092956
- 投稿日:2021-01-05T08:39:44+09:00
railsでレコード登録前に確認画面を表示する
はじめに
Ruby on Rails5 速習実践ガイドのアウトプットで投稿しています。
今回は、レコードの登録前に「こちらの内容で登録します」などの確認画面を表示する機能について投稿します!例として、タスク管理アプリケーションを作成しています。目次
アクションを作成する
まずは、確認画面に遷移するアクションをcontrollerに作成します。
app/controller/tasks_controller.rbdef confirm_new @task = current_user.tasks.new(task_params) render :new unless @task.valid? endルーティングの追加
今回はタスクにネストさせるため以下のように設定します。
config/routes.rbresources :tasks do post :confirm, action: :confirm_new, on: :new endこの記述によって、
/tasks/new/confirm
というURLが生成されます。ビューの追加と編集
追加
まず、確認画面のビューを作成します。
app/views/tasks/confirm_new.html.slimh1 登録内容の確認 = form_with model: @task, local: true do |f| table.table.table-hover tbody tr th= Task.human_attribute_name(:name) td= @task.name = f.hidden_field :name tr th= Task.human_attribute_name(:description) td= simple_format(@task.description) = f.hidden_field :description = f.submit '戻る', name: 'back', class: 'btn btn-secondary mr-3' = f.submit '登録', class: 'btn btn-primary'この画面を表示するタイミングでは、データは保存されていません。このあとにcreateメソッドを実行するときにデータが必要なので、hidden_fieldで前の画面のデータをユーザーからは見えないように保持して渡すようにしています。
編集
次に、新規追加画面の遷移先を確認画面に設定しておきます。
app/views/tasks/new.html.slim= form_with model: @task, local: true, url: confirm_new_task_path do |f| .form-group = f.label :name = f.text_field :name, class: 'form-control', id: 'task-name' .form-group = f.label :description = f.text_area :description, rows: 5, class: 'form-control', id: 'task_description' = f.submit "確認", class: 'btn btn-primary'戻るボタンの実装
確認画面の戻るボタンが押されたときの処理を実装していきます。先ほど作成した確認画面の戻るボタンには、name属性に「back」をつけています。このボタンを押したときにparams[:back]というパラメータが送られるので、このparamsがあれば、newメソッドにレンダーするという流れで実装します。
app/controllers/tasks_controller.rbdef create @task = current_user.tasks.new(task_params) if params[:back].present? render :new return end if @task.save redirect_to tasks_url else render :new end end以上で、確認画面の実装は終了です!
参考文献
- 投稿日:2021-01-05T07:36:32+09:00
SystemSpec導入と書き方
はじめに
これまでに引き続き、現場で使える Ruby on Rails 5速習実践ガイドのアウトプットを投稿します!
今回はrspecについてです!
タスク管理アプリケーションを例に進めていきます。目次
Rspecの準備
・gemをインストール
Gemfileの
group :development, :test do
のブロックに以下を追記します。gem 'rspec-rails', '~> 3.7'記述後
bundle install
でgemをインストールします。完了したら、以下のコマンドを実行し、RSpecに必要なディレクトリや設定ファイルを作成します。rails g spec:install・testディレクトリ削除
rails new
でアプリケーションを立ち上げた時に自動で作成されるtestディレクトリを削除します。なぜなら、Rspecのファイルは、specディレクトリの中に作成するからです。rm -r ./test・Capybaraを使うためにspec_helper.rbを編集
Capybaraはrails new
をした際にインストールされているため、機能の読み込み
と実行するドライバの設定
を記述します。spec/spec_helper.rbrequire 'capybara/spec' config.before(:each, type: :system) do driven_by :selenium_chrome_headless end・FactoryBotのインストール
Gemfileの
group :development, :test do
のブロックに以下を追記します。gem 'factory_bot_rails', '~> 4.11'Specの書き方
ここでは、タスク管理アプリケーションの一覧表示に関するテストを例にします。
tasks_spec.rbdescribe '一覧表示機能' do context 'Aさんがログインしているとき' do before do # テスト条件を満たすよう処理を記述する end it 'Aさんの投稿だけが表示される' do # 期待する動作を記述する end end end上記の例では条件が一つですが、複数ある場合はcontextをネストすることもできます。
FactoryBotでテストデータを作成
spec/factories/users.rb
を作成し、Userモデルのデータを記述します。spec/factories/users.rbFactoryBot.define do factory :user do name { 'テストユーザー' } email { 'test@example.com' } password { 'password' } end end
factory :user
の記述で、railsがUserモデルのテストデータだなと、解釈してくれます。もし、違う名前をつけたいときは、以下のようにclassを明記します。factory :test_user, class: User do次にいま作成したユーザーに紐づく投稿データを作成します。先程と同様に、
spec/factories/tasks.rb
を作成します。spec/factories/tasks.rbFactoryBot.define do factory :task do name { 'テストを作成する' } description { '必要なものをインストールし、作成する。' } user end end上記の
user
は、先ほど作成した:user
のデータに紐づくものと定義しています。こちらも、モデル名と違うテストデータを紐付けるときはuser
の箇所を以下のように書きます。association :user, factory: :admin_userテストを書く
まずは、日本語で枠組みを作成していきます。
spec/system/tasks_spec.rbrequire 'rails_helper' describe '一覧表示機能' do before do # ユーザーAを作成する # ユーザーAのタスクを作成する end context 'ユーザーAがログインしているとき' do before do # ユーザーAでログイン # ログイン画面に遷移 # メールアドレスを入力 # パスワードを入力 # ログインボタンを押す end it 'ユーザーAが作成したタスクが表示される' # 作成されたタスクが表示されている end end endテストの枠組みができたら、実際にテストコードを書いていきます!
spec/system/tasks_spec.rbrequire 'rails_helper' describe '一覧表示機能' do before do # ユーザーAを作成する user_a = FactoryBot.create(:user) # ユーザーAのタスクを作成する FactoryBot.create(:task, name: "最初のタスク", user: user_a) end context 'ユーザーAがログインしているとき' do before do # ユーザーAでログイン # ログイン画面に遷移(ログイン画面のpathにvisit) visit login_path # メールアドレスを入力(labelの名称を指定します) fill_in 'メールアドレス', with: 'a@example.com' # パスワードを入力(labelの名称を指定します) fill_in 'パスワード', with: 'password' # ログインボタンを押す click_botton 'ログインボタン' end it 'ユーザーAが作成したタスクが表示される' # 作成されたタスクが表示されている expect(page).to have_content '最初のタスク' end end end参考文献
- 投稿日:2021-01-05T05:31:08+09:00
Rails + GraphQLでAPI作成
各バージョン
ruby: 2.7.1 rails: 6.0.3.4 graphql-ruby: 1.11.6GraphQL Ruby
RailsでGraphQLを扱う場合↑のgemを使ってAPIを実装していきます。
graphiql-rails
合わせて graphiql-rails gemを入れておくとブラウザ上で実装したGraphQLの
確認ができるIDEが使えるようになります
※graphql-ruby
のinstall時にgraphiql-rails
のgemをGemfileに追加してくれます環境構築
Gemfilegem 'graphql' gem 'graphiql-rails' # 今回は先に入れましたgemがインストールされたら
rails generate graphql:install
コマンドを実行し各ファイルを生成します。
生成されたファイルは以下の通り↓$ rails generate graphql:install create app/graphql/types create app/graphql/types/.keep create app/graphql/app_schema.rb create app/graphql/types/base_object.rb create app/graphql/types/base_argument.rb create app/graphql/types/base_field.rb create app/graphql/types/base_enum.rb create app/graphql/types/base_input_object.rb create app/graphql/types/base_interface.rb create app/graphql/types/base_scalar.rb create app/graphql/types/base_union.rb create app/graphql/types/query_type.rb add_root_type query create app/graphql/mutations create app/graphql/mutations/.keep create app/graphql/mutations/base_mutation.rb create app/graphql/types/mutation_type.rb add_root_type mutation create app/controllers/graphql_controller.rb route post "/graphql", to: "graphql#execute" gemfile graphiql-rails route graphiql-railsこの時点での
routes.rb
は以下のようになっています。Rails.application.routes.draw do # GraphQL if Rails.env.development? mount GraphiQL::Rails::Engine, at: '/graphiql', graphql_path: '/graphql' end post '/graphql', to: 'graphql#execute' end実装
Query 作成
まずは各テーブルに対応するTypeを定義しないといけないので、
例として以下のusers
テーブルに対応するuser_type
を作成してみたいと思います。create_table :users do |t| t.string :name, null: false t.string :email t.timestamps end以下コマンドを実行すると
user_type
が作成されます。
(指定する型はID
がGraphQLで定義されているid用の型です(実態はString)
また語尾に!
が付いているものはnullを許容しない型となり、!
が付いてないものはnull許容になります。)$ bundle exec rails g graphql:object User id:ID! name:String! email:String【補足】既にDBにテーブルが存在している場合はよろしくやってくれるっぽいので
$ bundle exec rails g graphql:object User↑これでも大丈夫でした
生成されたファイル
graphql/type/user_type.rb
は以下のようになっていました。module Types class UserType < Types::BaseObject field :id, ID, null: false field :name, String, null: false field :email, String, null: true field :created_at, GraphQL::Types::ISO8601DateTime, null: false field :updated_at, GraphQL::Types::ISO8601DateTime, null: false end end既に生成されている
graphql/type/query_type.rb
に以下を追加します。field :users, [Types::UserType], null: false def users User.all end
http://localhost:3000/graphiql
上で以下クエリを投げるとレスポンスが返ってくるかと思います。{ users { id name email } }Mutationsの作成
次にユーザーを作成するMutations
CreateUser
を作成してみたいと思います。$ bundle exec rails g graphql:mutation CreateUser
graphql/mutations/create_user.rb
が作成されるので、以下の様に修正します。module Mutations class CreateUser < BaseMutation field :user, Types::UserType, null: true argument :name, String, required: true argument :email, String, required: false def resolve(**args) user = User.create!(args) { user: user } end end end既に生成されている
graphql/types/mutation_type.rb
に以下を追記します。module Types class MutationType < Types::BaseObject field :createUser, mutation: Mutations::CreateUser # 追記 end end
http://localhost:3000/graphiql
上で以下を実行するとUserが作成されます。mutation { createUser( input:{ name: "user" email: "user@email.com" } ){ user { id name email } } }Association
- 1:1の関連テーブルの場合
例として
Post
がLabel
と1:1で関連付されている場合label_type.rbmodule Types class LabelType < Types::BaseObject field :id, ID, null: false field :name, String, null: false ... end endmodule Types class PostType < Types::BaseObject field :label, LabelType, null: true end end↑の様に
label
をLabelType
として定義できます。
この場合の Query のイメージとしては{ posts { id label { id name } } }上記の様に
label
をLabelType
として必要な値をQueryできます。
- 1:Nの関連テーブルの場合
例として
User
がPost
と1:Nの場合module Types class PostType < Types::BaseObject field :id, ID, null: false field :label, LabelType, null: true end endmodule Types class UserType < Types::BaseObject field :posts, [PostType], null: false end end上記の様に
posts
を[PostType]
として定義でき、Queryとしては{ user(id: 1234) { id posts { id label { id name } } } }↑の様に呼び出す事ができます。
graphql-batch
↑の説明の様に 1:1や1:Nの関連テーブルのデータも取ってくる事ができますが
今のままだとDBへの問い合わせが大量に発生してしまう場合があります。
User
がPost
と1:Nの場合の例でPost
が100件ある場合、それぞれ100回問い合わせが発生してしまいます。そこで解決方法の一つである複数問い合わせをまとめやってくれる graphql-batch を導入してみます。
gem 'graphql-batch'Gemをインストールしたら、
loader
を作成していきます。
loader
は「複数問い合わせをまとめる」部分の実装になります。graphql/loaders/record_loader.rbmodule Loaders class RecordLoader < GraphQL::Batch::Loader def initialize(model) @model = model end def perform(ids) @model.where(id: ids).each { |record| fulfill(record.id, record) } ids.each { |id| fulfill(id, nil) unless fulfilled?(id) } end end endこれを先程の
Post
がLabel
と1:1で関連付されている場合に適用するとmodule Types class PostType < Types::BaseObject field :label, LabelType, null: true def label Loaders::RecordLoader.for(Label).load(object.label_id) end end endこんな感じで書けます。
User
がPost
と1:Nの場合には別途loaderを作成します。graphql/loaders/association_loader.rbmodule Loaders class AssociationLoader < GraphQL::Batch::Loader def self.validate(model, association_name) new(model, association_name) nil end def initialize(model, association_name) @model = model @association_name = association_name validate end def load(record) raise TypeError, "#{@model} loader can't load association for #{record.class}" unless record.is_a?(@model) return Promise.resolve(read_association(record)) if association_loaded?(record) super end # We want to load the associations on all records, even if they have the same id def cache_key(record) record.object_id end def perform(records) preload_association(records) records.each { |record| fulfill(record, read_association(record)) } end private def validate unless @model.reflect_on_association(@association_name) raise ArgumentError, "No association #{@association_name} on #{@model}" end end def preload_association(records) ::ActiveRecord::Associations::Preloader.new.preload(records, @association_name) end def read_association(record) record.public_send(@association_name) end def association_loaded?(record) record.association(@association_name).loaded? end end end※ loaderはgraphql-batchのリポジトリにサンプルがあるので、そちらを参考にして実装すると良さそうです
以下の様に書くと、まとめて問い合わせしてくれるようになります。
module Types class UserType < Types::BaseObject field :posts, [PostType], null: false def posts Loaders::AssociationLoader.for(User, :posts).load(object) end end endスキーマファイルからドキュメント生成
最後に定義したスキーマファイルから良い感じのドキュメントを自動で生成するようにしてみたいと思います。
routes.rb
にマウントできてデプロイ毎に自動でgraphdocが更新される
便利なgemを探していたらgraphdoc-rubyというgemがあったので試してみます。
Gemfile
に以下を追加gem 'graphdoc-ruby'また、npmパッケージの@2fd/graphdocも必要なので
予めDockerイメージ内でインストールしておきます。(Docker使用してない場合はローカル環境にインストールすれば良いかと思います)例)
RUN set -ex \ && wget -qO- https://deb.nodesource.com/setup_10.x | bash - \ && apt-get update \ && apt-get install -y \ ... --no-install-recommends \ && rm -rf /var/lib/apt/lists/* \ && npm install -g yarn \ && npm install -g @2fd/graphdoc # インストールしとく
config/routes.rb
に以下を追記config/routes.rbRails.application.routes.draw do mount GraphdocRuby::Application, at: 'graphdoc' end※ エンドポイントを変更している場合、
config/initializers/graphdoc.rb
を修正する例)
GraphdocRuby.configure do |config| config.endpoint = 'http://0.0.0.0:3000/api/v1/graphql' endRailsを再起動して、http://localhost:3000/graphdoc でドキュメントが生成されればOKです
バッドノウハウ
http://localhost:3000/graphiql
アクセス時に以下エラーが発生する場合Sprockets::Rails::Helper::AssetNotPrecompiled in GraphiQL::Rails::Editors#show
解決方法1
app/assets/config/manifest.js
に以下を追加する//= link graphiql/rails/application.css //= link graphiql/rails/application.jsAssetNotPrecompiled error with Sprockets 4.0 · Issue #75 · rmosolgo/graphiql-rails
-> ただこれだとProduction時にSprockets::FileNotFound: couldn't find file 'graphiql/rails/application.css'
エラーが出て使えない...解決方法2 (うまくいった方法)
gem 'sprocket'のバージョン3.7.2に下げる
gem 'sprockets', '~> 3.7.2' [#1098: slowdev/knowledge/ios/FirebaseをCarthageで追加する](/posts/1098)↑を追加し、
bundle update
Rails6のAPIモードでGraphQLを使う方法(エラー対策も含む) - Qiitagraphiqlの画面に
TypeError: Cannot read property 'types' of undefined
が表示される
-> 手元の環境だとRails再起動で治りましたgraphiqlの画面に
SyntaxError: Unexpected token < in JSON at position 0
が表示される
-> エラーが発生してる可能性がるのでログを見て修正する参考になったURL
- 【Rails】graphql-rubyでAPIを作成 - Qiita
- REST APIが主流のプロジェクトの中でGraphQLを導入してみた話(サーバーサイド編) - Sansan Builders Blog
- 「GraphQL」徹底入門 ─ RESTとの比較、API・フロント双方の実装から学ぶ - エンジニアHub|若手Webエンジニアのキャリアを考える!
- GraphQLを使ったAPI仕様中心開発の導入とその効果の紹介 - Kaizen Platform 開発者ブログ
- 雑に始める GraphQL Ruby【class-based API】 - Qiita
- hawksnowlog: Ruby (Sinatra) で GraphQL 入門
- 既存のRailsプロジェクトにGraphQL APIを追加してみた - Qiita
- Ruby on Rails で sprockets から Webpacker へ移行し、移行できないものは共存させる方法 - Qiita
- Reading: 初めてGraphQL - 型の基礎|tkhm|note
- https://github.com/loopstudio/rails-graphql-api-boilerplate
- https://github.com/rmosolgo/graphql-ruby-demo
- 投稿日:2021-01-05T02:43:44+09:00
姓と名を別々に入力させて、保存する前に結合する
忘備録です。
Deviseでの新規登録
名前のフォームを姓と名に分けて入力させ、保存する前に結合させるregistrations/new.html.erb<%= f.label :firstName, "姓" %> <%= f.text_field :firstName, autofocus: true, required: true, class: 'form-control' %> <%= f.label :lastName, "名" %> <%= f.text_field :lastName, autofocus: true, required: true, class: 'form-control' %>controllers/application_controller.rbclass ApplicationController < ActionController::Base before_action :configure_permitted_parameters, if: :devise_controller? protected # strong parameterで姓と名の属性(firstNameとlastName)をpermitする def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:firstName, :lastName]) end endmodels/user.rb# 姓と名をDBに保存する前に結合 before_create :create_name def create_name self.name = "#{firstName} #{lastName}" end