- 投稿日:2020-08-04T23:34:43+09:00
gem sitemap_generator で priority, lastmod, changefreq を記載しない方法
概要
sitemap_generator ではデフォルトでは priority, lastmod, changefreq にデフォルト値が記載されます。
これらの項目を出力したくないときもあるのでその方法を共有します。方法
priority, lastmod, changefreq に nil を渡すと出力されなくなります。
add '/home', priority: nil, lastmod: nil, changefreq: nil
- 投稿日:2020-08-04T23:06:27+09:00
[Ruby] 親モジュールを取得する
概要
以下のようなモジュールの階層があるときに、
Child
モジュールからParent
モジュールのVALUE
を参照したかった。module Parent VALUE = 'PARENT' module Child VALUE = 'CHILD' def self.parent_value # 'PARENT' がほしい end end endRails(activesupport) が使える環境なら簡単
流石に
Module#parent
的なメソッドがRubyで提供されてるだろうと思い調べて見たが、どうやらRuby(2.7現在)本体にはそのような機能はなく、Rails
の拡張(activesupport
)で提供されていることがわかった。そのため、Rails環境では以下のように単純に
parent
を参照することで対応可能module Parent VALUE = 'PARENT' module Child VALUE = 'CHILD' def self.parent_value self.parent::VALUE end end end[4] pry(main)> Parent::Child.parent_value => "PARENT"Rails以外でも使いたいので実装する
Railsでない(
activesupport
が使用できない) 状態の場合、以下のようにparent
メソッドがないよと怒られてしまう。irb(main):008:0> Parent::Child.parent_value Traceback (most recent call last): 5: from /Users/shingo.sasaki/.rbenv/versions/2.6.5/bin/irb:23:in `<main>' 4: from /Users/shingo.sasaki/.rbenv/versions/2.6.5/bin/irb:23:in `load' 3: from /Users/shingo.sasaki/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>' 2: from (irb):8 1: from /Users/shingo.sasaki/Docker/teachme/app/libraries/hoge.rb:8:in `parent_value' NoMethodError (undefined method `parent' for Parent::Child:Module)
activesupport
では以下のように実装されている。def parent parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object enddef parent_name if defined?(@parent_name) @parent_name else parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil @parent_name = parent_name unless frozen? parent_name end endと、コードを追っていくと以下のような手段を用いていることがわかる
Module#name
を使って、モジュール名を文字列で取得::
を元に、親モジュールまでのモジュール名を正規表現で抽出Object.const_get
を用いて文字列からモジュールを取得よって、
Module
クラス自体を以下のように拡張すれば近いことができる(シンプルさを重視して、実際のactivesupport
ほど手広くカバーしてません)class Module def parent parent_name = self.name =~ /::[^:]+\Z/ ? $`.freeze : nil parent_name ? Object.const_get(parent_name) : Object end end↑を読み込んだ状態ならこんな構造があっても
module Parent module Child module GrandChild end end end階層をたどることが出来る
[3] pry(main)> Parent::Child::GrandChild.parent => Parent::Child [4] pry(main)> Parent::Child::GrandChild.parent.parent => Parent [5] pry(main)> Parent::Child::GrandChild.parent.parent.parent => Object
- 投稿日:2020-08-04T17:31:39+09:00
Railsでランキング機能を実装する方法
以前、Qiitaのこの記事(Railsでお手軽ランキング機能)を参考にして、ランキング機能を実装したことがありました。
無事、実装できたのですが、以下の問題に直面しました。kaminariのページネーション機能と組み合わせると上手くいかない!
@posts = Post.find(Like.group(:post_id).order('count(post_id) desc').pluck(:post_id)).page(params[:page])kaminariのpageメソッドと組み合わせると上記のようになりますが、これだと以下のようなエラーが発生します。
そのためpageメソッドと組み合わせても、問題ないランキング機能をご紹介いたします。
前提
- 記事のテーブル名は posts
- いいねのテーブル名は likes
- すでにいいね機能を実装しており、postsとlikesのアソシエーションができていること
ランキング機能の実装
@posts = Post.joins(:likes).group(:post_id).order('count(post_id) desc')これでOKです!
一つずつ説明します。Post.joins(:likes) #postsテーブルとlikesテーブルを内部結合します group(:post_id) #post_idが同じものにグループを分けます order('count(post_id) desc') #それをpost_idの多い順番に並び替えるこれでランキング機能は完成です。
以下のように、コードの末尾にpageメソッドをつけてもエラーにならず、ページネーション機能が機能していると思います。@posts = Post.joins(:likes).group(:post_id).order('count(post_id) desc').page(params[:page])参考文献
- Railsでお手軽ランキング機能
https://qiita.com/mitsumitsu1128/items/18fa5e49a27e727f00b4
- 投稿日:2020-08-04T16:38:45+09:00
本番環境とcredentials.yml.enc
この記事で伝えたいこと
production環境の秘匿情報をcredentialsで扱う前にしっかり調べましょう。
ただのコピペダメ絶対。基礎知識 暗号化と復号について
〜開発環境〜
$ rails new
した時config/master.keyと共にcredentials.yml.encは作成されるようです。
そしてこのmaster.keyを使用して暗号化、復号します。
(master.keyは大切に保管しましょう)秘匿情報の編集には以下のコマンドを実行します。
$ rails credentials:edit #master.keyが存在しない時実行すると新たに作成する〜本番環境〜
暗号化と復号にはsecret_key_baseも必要になります。
ローカルで$ rails secret
コマンドを実行し作成します。
事前にローカルのmaster.keyをサーバーにも配置しておくこと。本番環境で気をつけること
credentials.yml.encの暗号化、復号にはmaster.keyを使用すると先ほど書きました。このmaster.key、デフォルトでgitignoreに登録されているためGitの管理対象外となっています。
ここからが大切です。
EC2でgitのリポジトリをクローンしてもこのmaster.keyは当然サーバー上にやってきません。
その事を忘れて本番環境の秘匿情報を追加しようと思い$ rails credentials:edit
コマンドを実行すると...サーバー上にはmaster.keyが無いので新たに生成されてしまいます。この時点ではローカルのmaster.keyとサーバーのmaster.keyが異なりcredentials.yml.encの復号ができなくなります。あら大変。
Couldn't decrypt config/credentials.yml.enc. Perhaps you passed the wrong key?こんなエラーや、
ActiveSupport::MessageEncryptor::InvalidMessageこんなエラーが発生します。
もう一度credentialsの復号がしたい
ローカルのmaster.keyをサーバーに置いてあげれば良いです。
master.keyを紛失した場合は、config/credentials.yml.encを削除してから以下のコマンドで
新たなものを生成してくれるようです。
ただしcredentialsの中身は全て吹き飛びますのでご注意を。$ sudo EDITOR=vim rails credentials:editRails6以降とcredentilas.yml.enc
6以降は環境ごとに秘匿情報を分けられるようになりました。(祝)
本番環境で情報を追加したい時以下コマンドを実行します。
環境に応じてenvironment以降を変化させます。$ rails credentials:edit --environment productionこのコマンドはconfig/credentials/production.yml.encと、config/credentials/production.keyを作成します。ファイル名とキーの名前にそれぞれ該当する環境が記載されます。
サーバーにはproduction.keyのみを上げれば良い。この場合でもmaster.keyやsecret_key_baseの扱いには注意。
参考
Rails 5.2 で ActiveSupport::MessageEncryptor::InvalidMessage
Rails5.2から追加された credentials.yml.enc のキホン
Rails5.2の新機能credentials等でパスワード等を管理する
【Ruby/Rails】デプロイ作業をCapistranoで自動化する
- 投稿日:2020-08-04T11:49:53+09:00
Flexboxで子要素の幅を指定する
プログラミングの勉強日記
2020年8月4日 Progate Lv.226
Flexboxでwidth
が効かなかったので原因を調べて解決した。(メモ)
flex-shrink
を0にすることでwidth
の指定をすることもできるみたいだが、今回はwidth
を使うのをやめてflex-basic
を使った。直面した問題
Flexboxで指定すると、画面幅によって子要素が伸びてしまい、綺麗に見れなくなってしまった。
下の画像のように子要素の幅を指定することで、画面幅によって子要素が伸びないようにしたい。
解決方法
flex-basis
を使う。子要素に対してwidth
と同じように%やpxで幅の値を指定することができる。初期値はauto
になっていて、auto
と指定した場合は子要素のコンテンツのサイズが適応される。html<div class="parent"> <div class="children"> <!--省略--> </div> </div>css.parent{ display:flex; } .children{ flex-basis: 500px; }
- 投稿日:2020-08-04T11:20:08+09:00
rails routes.rbのmemberとcollectionの違い
routes.rbのmemberとcollectionの違い
railsのroutes.rbでルーティングを設定する時にmemberとcollectionの違いに関して記載します。
menberの場合
routes.rbresources :buy_additional_actions, only: %i[] do member do get 'index' => 'buy_additional_actions#index'menberの場合は生成されたurlに:idが自動で追加されます。
buy_additional_action GET /buy_additional_actions/:id/index(.:format) buy_additional_actions#indexcollectionの場合
routes.rbresources :buy_additional_actions, only: %i[] do collection do get 'index' => 'buy_additional_actions#index'collectionの場合はurlには:idが付与されません。
buy_additional_actions GET /buy_additional_actions/index(.:format) buy_additional_actions#index
- 投稿日:2020-08-04T10:34:34+09:00
rails5中級チュートリアル中に発生したSassC::SyntaxError in Pages#index対処法
rails5中級チュートリアル中にエラーが発生 以下エラー画像
仮説と試したこと
エラー文の解説を翻訳してみると
「エラー:インポートするファイルが見つからないか、読み取れません:bootstrap-sprockets。
app / assets / stylesheets / application.scssの15:1行
@import "bootstrap-sprockets";」となる。 importの読み込み記述はされているが、sprocketsが存在しないことになっていると仮説。
エラー文で検索した際に出てくる多くのサイトではrails sをしていなかったのが原因と述べられているものが多いが、自分の場合rails sでの再起動を行うが効果なし。
参考サイト解決
application.scssをいじっていたら解決する事ができた
application.scss修正前@import "bootstrap-sprockets"; @import "bootstrap"; @import "protospace";三行目を削除
application.scss修正後@import "bootstrap-sprockets"; @import "bootstrap";どうやら解決するためにいくつかサイトを見ていた際、誤ってprotospaceをimport読み込みしていたのが原因だったらしい。
そのため解決法は rails s不足と誤ったimportを記述してしまっていた事であった。
- 投稿日:2020-08-04T09:53:16+09:00
ソースコードを読むとき
既存プロジェクトに入った際、各Classがどのようなメソッドをもっているかを確認する必要があると思います。
その際、自分がどのように確認を行っているかを以下で述べていこうと思います。
[環境]
Ruby
Rails
Solidus今回は、RailsのSolidusというGemを使って、説明させて頂ければと思います。
$rails c
でコンソールの中に入る。そこで、上記のように、任意の文字にオブジェクトを代入する。
(Spree::Taxonはオブジェクト)$a.methods
を実行。実行することで、そのオブジェクトが持っているmethodが表示される。$a.methods.grep /product/
とすると、productにまつわるmethodのみを取得できる。
上記のようにgrepを使う事で検索結果を絞って検索する事ができるので、結構使う機会も多いと思います。$a.method(:before_remove_for_products=).source_location
上記のように、method(メソッド名).source_locationとすることで、
どこのファイルにそのメソッドがあるかを探す事もできます。
- 投稿日:2020-08-04T08:33:46+09:00
既存のRailsアプリの開発環境にDockerを導入する手順【Rails, MySQL, Docker】
こんにちは.
今回は, 既存のRailsアプリの開発環境にDockerを導入する手順をまとめてみました.
まだまだ勉強不足ですので, 修正点・改善点等ございましたら, ご指摘いただけますと幸いです.事前準備
- Docker-for-macのインストール
- 既存のRailsアプリ(今回は以前作成した簡単な家計簿アプリを使用します.)
環境
Ruby:2.5.3
Rails:5.2.4.3
MySQL:5.6
Docker:19.03.8
docker-compose:1.25.4手順
1. ルートディレクトリにDockerfile,docker-compose.ymlを追加
既存のRailsアプリのルートディレクトリ直下に
Dockerfile
とdocker-compose.yml
を作成します.
以下, それぞれのファイルの中身です.Dockerfile
DockerfileFROM ruby:2.5.3 RUN apt-get update && apt-get install -y \ build-essential \ nodejs WORKDIR /kakeibo COPY Gemfile Gemfile.lock /kakeibo/ RUN bundle install
FROM ruby:2.5.3
の部分についてはアプリのRubyのバージョンに合わせる.RUN apt-get update && apt-get install -y ~
で必要なパッケージをインストールする.WORKDIR /kakeibo
でコンテナ内にフォルダを作成.COPY Gemfile Gemfile.lock /kakeibo/
でコンテナ内にGemfileとGemfile.lockをコピーした後,bundle install
を実行する.docker-compose.yml
docker-compose.ymlversion: '3' volumes: mysql-data: services: web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' ports: - '3000:3000' volumes: - '.:/kakeibo' tty: true stdin_open: true depends_on: - db links: - db db: image: mysql:5.6 volumes: - 'mysql-data:/var/lib/mysql' environment: - 'MYSQL_ROOT_PASSWORD=password'
Dockerfile
とdocker-compose.yml
の中身の詳しい説明はこちらの記事にわかりやすくまとめられていました.2. config/database.ymlを編集
config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: password host: db development: <<: *default database: kakeibo_development
config/database.yml
のpassword
とhost
をdocker-compose.ymlで設定した値に合わせます.3. コンテナ起動
terminal$ docker-compose build $ docker-compose up -d $ docker-compose exec web rails db:create $ docker-compose exec web rails db:migrateこれで http://localhost:3000 にアクセスすると無事にアプリが表示されるはずです.
参考
- 既存のrailsプロジェクトをDockerで開発する手順
- 既存のRailsアプリにDockerを導入する手順
- Dockerで既存アプリの開発環境を作成【Ruby2.6 + Rails5.2 + Mysql5.7】
- [Rails] DockerでRails + MySQLの開発環境をつくる手順
- Docker初心者がRails + PostgreSQL or MySQLで仮想環境構築した手順を丁寧にまとめる
- #Linux #Ubuntu #docker #Dockerfile のこれは何? apt-get install --no-install-recommends
- 『Dockerfile のベストプラクティス』
- 投稿日:2020-08-04T08:32:14+09:00
Railsアプリの開発環境をDockerで構築する手順【Rails, MySQL, Docker】
こんにちは.
今回は, Railsアプリの開発環境をDockerで構築する手順をまとめてみました.
まだまだ勉強不足ですので, 修正点・改善点等ございましたら, ご指摘いただけますと幸いです.事前準備
環境
Ruby: 2.5.8
Rails: 5.2.4.3
MySQL: 5.7.31
Docker: 19.03.8
Docker Compose: 1.25.4手順
1. プロジェクトのルートディレクトリを作成
terminal$ mkdir test-app初めに, プロジェクトのルートディレクトリを作成します.
2. ルートディレクトリ直下にファイルを追加
terminal$ cd test-app $ touch Dockerfile docker-compose.yml Gemfile Gemfile.lock作成したルートディレクトリの直下に
Dockerfile
,docker-compose.yml
,Gemfile
,Gemfile.lock
の4つのファイルを作成します.
それぞれのファイルの中身は以下のようになります. (Gemfile.lockは空のままにします.)DockerfileFROM ruby:2.5 RUN apt-get update && apt-get install -y \ build-essential \ nodejs WORKDIR /test-app COPY Gemfile Gemfile.lock /test-app/ RUN bundle installdocker-compose.ymlversion: '3' volumes: mysql-data: services: web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' ports: - '3000:3000' volumes: - '.:/test-app' tty: true stdin_open: true depends_on: - db links: - db db: image: mysql:5.7 volumes: - 'mysql-data:/var/lib/mysql' environment: - 'MYSQL_ROOT_PASSWORD=test-app'Gemfilesource 'https://rubygems.org' gem 'rails', '~>5.2'3. コンテナ内にRailsのセットアップを行う
terminal$ docker-compose run --rm web rails new . --force --database=mysql --skip-bundle --skip-testwebのコンテナ内で
rails new
を実行します.
今回はテストにRSpecを使用する予定でしたので,--skip-test
も追加しています.4. 作成されたconfig/database.ymlを編集
Railsのセットアップにより作成された
config/database.yml
を以下のように編集します.config/database.ymldefault: &default adapter: mysql2 encoding: utf8 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> username: root password: test-app #docker-compose.ymlのMYSQL_ROOT_PASSWORDの値を設定する host: db #docker-compose.ymlのservice名と合わせる development: <<: *default database: test-app_development5. コンテナの起動
terminal$ docker-compose up --build -d $ docker-compose run --rm web rails db:createこれで, http://localhost:3000 にアクセスすると, Railsのホーム画面が表示されるはずです.
参考
- 投稿日:2020-08-04T08:32:09+09:00
graphql-rubyを使って認可する方法
GraphQLを使っているときに様々な処理で認可させたい事があると思います。
- このQueryはログインユーザーのみ実行できるようにしたい
- このMutationは管理者のみ実行できるようにしたい
- このQueryは自分の所有しているデータのときだけ返却するようにしたい
- このFieldは自分の所有しているデータのときだけ返却するようにしたい
当初はgraphql-rubyの知識が乏しかったので取得や更新処理の中で認可する処理を呼び出していたのですが、graphql-rubyのドキュメントを改めて読み直したところ、認可のためのメソッド(authorized?)がある事がわかったので動作検証を兼ねて記事を書きました。
graphql-rubyについて
Ruby(Rails)でGraphQLを簡単に使えるようにしてくれるGemです。
https://github.com/rmosolgo/graphql-ruby細かいところは実際に試してみないとわからないことも多いですが、ドキュメントが充実していて素晴らしいです。
https://graphql-ruby.org/guidesこの記事を書いている時点では、
graphql: 1.11.1
を使っています。
まだガンガンバージョンアップしているGemなので、バージョンが違うと大幅に動作が変わっている可能性があるのでご注意ください。認可の実装例
最初に挙げた4つのパターンの実装例を説明します。
前提条件
認可に必要なログインユーザーの情報はcontextに格納していることとします。
認証についてはこの記事の本筋からの逸れるので説明は省略します。app/controllers/graphql_controller.rb# ログインユーザーの情報はcontext[:current_user]に格納 # 未ログインの場合はnil context = { current_user: current_user }このQueryはログインユーザーのみ実行できるようにしたい
ここでは『review_idを指定して該当するReviewTypeを返却するクエリー』を実装します。
認可を入れる前
認可を実装する前にReviewTypeを取得するクエリーを実装します。
app/graphql/types/query_type.rbmodule Types class QueryType < Types::BaseObject field :review, resolver: Resolvers::ReviewResolver end endapp/graphql/resolvers/review_resolver.rbmodule Resolvers class ReviewResolver < BaseResolver type Types::ReviewType, null: true argument :review_id, Int, required: true def resolve(review_id:) Review.find_by(id: review_id) end end endapp/graphql/types/review_type.rbmodule Types class ReviewType < BaseObject field :id, ID, null: false field :title, String, null: true field :body, String, null: true field :secret, String, null: true field :user, Types::UserType, null: false end endapp/graphql/types/user_type.rbmodule Types class UserType < BaseObject field :id, ID, null: false field :name, String, null: false field :email, String, null: false end end認可を実装
それでは先ほど実装した処理に『ログインユーザーのみ実行できる』という制約を追加します。
authorized?を使わない実装
以前の私はresolveメソッドでReviewを取得する前にログインチェックする実装を入れていました。
まずは様々なResolverから使えるようにBaseResolverにログインチェックメソッドを実装します。
context[:current_user]が入っていない場合はエラーを発生させます。
ちなみに、GraphQL::ExecutionError
を使うとraiseするだけでレスポンスをGraphQLのエラー形式に変換してくれます。app/graphql/resolvers/base_resolver.rbmodule Resolvers class BaseResolver < GraphQL::Schema::Resolver def login_required! # ログインしていなかったらraise raise GraphQL::ExecutionError, 'login required!!' unless context[:current_user] end end end次にBaseResolverのログインチェックを処理の最初に呼び出すようにします。
app/graphql/resolvers/review_resolver.rbdef resolve(review_id:) + # 処理の最初にログインチェックを行う + login_required! Review.find_by(id: review_id) end
GraphiQLで未ログインの状態で実行すると次のようになります。
この方法でもやりたいことは実現できているのですが、ログイン必須のResolverは処理の最初に必ず
login_required!
を書かなければいけません。
controllerのbefore_actionのように本処理が呼ばれる前に自動で認可してくれる方法はないのかをずっと探していました。authorized?を使う実装
graphql-rubyのガイドを改めて読んでいるとauthorized?というメソッドがあることに気づきました。
これを使うとresolveメソッドの前に認可を行い、実行可否を制御することができるようです。
下記はmutationに追加するガイドですが、Resolverにも同じように追加できます。
https://graphql-ruby.org/mutations/mutation_authorization.htmlログイン必須のResolverは汎用的に使えそうなので、ログイン必須のResolverが継承するlogin_required_resolverを作りました。
authorized?のパラメーター(args)にはresolveと同じパラメーターが格納されます。app/graphql/resolvers/login_required_resolver.rbmodule Resolvers class LoginRequiredResolver < BaseResolver def authorized?(args) context[:current_user].present? end end endreview_resolverはlogin_required_resolverを継承するように修正します。
他の実装は認可を追加する前と同じです。app/graphql/resolvers/review_resolver.rb- class ReviewResolver < BaseResolver + class ReviewResolver < LoginRequiredResolverGraphiQLで未ログインの状態で実行すると次のようになります。
authorized?の結果がfalseの場合はエラー情報はなく
data: null
だけ返却されるようになりました。
ガイドにも記載がある通り、authorized?がfalseの場合はdata: null
だけを返却するのがデフォルトの挙動のようです。
nullを返却するという仕様で問題なければこのままで良いですが、認可されない場合はエラー情報も返却するように変更してみます。エラー情報を追加する方法は簡単で、authorized?の中でGraphQL::ExecutionErrorをraiseすればできます。
ちなみに成功時はtrueを明示的に返却しないと成功と認識されないので注意が必要です。app/graphql/resolvers/login_required_resolver.rbmodule Resolvers class LoginRequiredResolver < BaseResolver def authorized?(args) # 認可できない場合はGraphQL::ExecutionErrorをraise raise GraphQL::ExecutionError, 'login required!!' unless context[:current_user] true end end endGraphiQLで未ログインの状態で実行すると次のようになります。
これでauthorized?を使った場合でもエラー情報を返却することができました。
authorized?を使った場合、resolveメソッドでは認可の処理を書く必要がなくなるのでシンプルに書くことができます。
(今回の例はかなりシンプルな実装なのでそこまで差はありませんが・・・)このMutationは管理者のみ実行できるようにしたい
ここでは『review_idを指定して該当するReviewのtitleとbodyを更新するMutation』を実装します。
認可を入れる前に
認可を実装する前にReviewを更新するMutationを実装します。
1つ前の例で使ったReviewTypeなどそのまま使うクラスは省略します。app/graphql/types/mutation_type.rbmodule Types class MutationType < Types::BaseObject field :update_review, mutation: Mutations::UpdateReview end endapp/graphql/mutations/update_review.rbmodule Mutations class UpdateReview < BaseMutation argument :review_id, Int, required: true argument :title, String, required: false argument :body, String, required: false type Types::ReviewType def resolve(review_id:, title: nil, body: nil) review = Review.find review_id review.title = title if title review.body = body if body review.save! review end end endGraphiQLで実行すると次のようになり、Reviewデータが更新されます。
認可を実装
Mutationでも先程の例と同様にauthorized?を使うことができます。
下記のガイトに記載されています。
https://graphql-ruby.org/mutations/mutation_authorization.html管理者しか利用できないMutationが継承する親クラスを作って継承するようにします。
app/graphql/mutations/base_admin_mutation.rbmodule Mutations class BaseAdminMutation < BaseMutation def authorized?(args) raise GraphQL::ExecutionError, 'login required!!' unless context[:current_user] raise GraphQL::ExecutionError, 'permission denied!!' unless context[:current_user].admin? super end end endapp/graphql/mutations/update_review.rb- class UpdateReview < BaseMutation + class UpdateReview < BaseAdminMutationMutationのauthorized?もfalseを返却するだけだとエラー情報は返却されず、dataがnullになり更新処理が実行されないようになります。
Resolverはそれでも良さそうですがMutationはエラー情報を返却しないとよくわからないと思うので、こちらもGraphQL::ExecutionErrorをraiseするように実装しました。
ちなみにガイドを読むと下記のように戻り値にerrorsを返却することでエラー情報を返す方法もあるようです。
試してみましたが下記の方法ではerrors配下のlocationsやpathは返却されませんでしたが、errorsのmessageは返却できました。
メッセージだけ返却できればよいのであればどちらの方法で実装しても良さそうです。def authorized?(employee:) if context[:current_user]&.admin? true else return false, { errors: ["permission denied!!"] } end endGraphiQLで管理者権限を持っていないユーザーが実行すると次のようになります。
もちろんエラーの場合は更新処理は実行されません。
このQueryは自分の所有しているデータのときだけ返却するようにしたい
ここでは最初に作った『review_idを指定して該当するReviewTypeを返却するクエリー』を基に改修します。
最初に作ったものはログイン状態のみ確認していましたが、今回はReviewが自分の所有物か?のチェックを追加します。ログインチェックと同じauthorized?に実装してみる
ログインチェックと同じauthorized?にチェックを追加できればよいのですが、今回のチェックはRevewを取得した後でないとチェックできません。
authorized?でもreview_idは引数で受け取るのでReviewを取得することもできるのですが、そうするとresolveの役割が曖昧になります。
実際に実装してみます。app/graphql/resolvers/login_required_resolver.rbdef authorized?(args) raise GraphQL::ExecutionError, 'login required!!' if context[:current_user].blank? + # この時点でreviewの取得が必要 + review = Review.find_by(id: args[:review_id]) + return false unless review + raise GraphQL::ExecutionError, 'permission denied!!' if context[:current_user].id != review.user_id true end
authorized?でReviewの取得が必要になります。
resolveメソッドでも取得するので、ここでも取得すると非効率な気がしますね。
では、resolve側にチェックを実装するとどうでしょうか?app/graphql/resolvers/review_resolver.rbdef resolve(review_id:) - Review.find_by(id: review_id) + review = Review.find_by(id: review_id) + raise GraphQL::ExecutionError, 'permission denied!!' if context[:current_user].id != review.user_id + review endこちらの方がauthorized?で実装するより効率は良さそうですが、authorized?にチェック処理を切り出すことでデータ取得処理のみ記載していたresolveにまたチェック処理が入ってしまいました。
当初はデータ取得後にしかチェックできないものがresolveでチェックするしかないと思っていたのですが、authorized?はReviewTypeにも定義できることを知ったのでReviewTypeに定義してみます。
ReviewTypeでチェックする
ReviewTypeでチェックするとはどういうことなのか?
実際に実装してみます。ReviewTypeは誰でも使えるようにしておきたいので、MyReviewTypeという自分しか閲覧できない制約をつけたReviewTypeを作ります。
app/graphql/types/my_review_type.rbmodule Types class MyReviewType < ReviewType def self.authorized?(object, context) raise GraphQL::ExecutionError, 'permission denied!!' if context[:current_user].id != object.user_id true end end endガイドにも記載されていますが、Typeで使うauthorized?はobjectとcontextを引数に受け取ります。
あと、クラスメソッドなので注意が必要です。
https://graphql-ruby.org/authorization/authorization.htmlあとはレスポンスのTypeをMyReviewTypeにするだけです。他の修正は不要です。
app/graphql/resolvers/review_resolver.rb- type Types::ReviewType, null: true + type Types::MyReviewType, null: trueGraphiQLで自分以外のReviewを指定すると次のようになります。
これでresolveメソッドには認可の処理を書く必要がなくなるのでシンプルに書くことができました。
また、レスポンスをMyReviewTypeにすることでスキーマ定義を読むだけで、このクエリーはMyReviewTypeを返却する=「自分しか閲覧できない」ということが明確になるので良いと思います。このFieldはログインユーザー自身のデータのときだけ返却する
1つ前の例ではMyReviewTypeを定義してレスポンス全体を自分のデータのときしか見れないようにしました。
しかし、全部ではなく特定のフィールドだけ見れないようにしたいこともあると思います。ReviewTypeを再掲します。
ここではsecretカラムは自分のデータしか見れないようにしたいと思います。app/graphql/types/review_type.rbmodule Types class ReviewType < BaseObject field :id, ID, null: false field :title, String, null: true field :body, String, null: true field :secret, String, null: true # <- これを自分の場合のみ見えるようにする field :user, Types::UserType, null: false end endガイドを読むとfieldにもauthorized?が実装できるようなのですが、1つのfieldだけをカスタマイズするのは難しそうなのでここではauthorized?を使わずに実装することにしました。
https://graphql-ruby.org/authorization/authorization.html
fieldのガイドはこちら
https://graphql-ruby.org/fields/introduction.html#field-parameter-default-values下記のようにfield名と同じメソッドを定義すると、そちらが呼び出されるようになります。
そのメソッド内に認可を実装しました。app/graphql/types/review_type.rbmodule Types class ReviewType < BaseObject field :id, ID, null: false field :title, String, null: true field :body, String, null: true field :secret, String, null: true field :user, Types::UserType, null: false # field名のメソッドを定義すると呼び出される def secret # ログインユーザーとレビューを書いたユーザーが違う場合、nilを返却 return if object.user_id != context[:current_user].id object.secret end end endGraphiQLで自分以外のReviewを指定すると次のようになります。
secretはnullが返却されています。
Resolverにこのチェックを実装するとReviewTypeを使うすべてのResolverがsecretの考慮をしなければいけなくなりますが、ReviewTypeに実装することで個別のResolverはsecretのアクセス制御を考える必要がなくなります。
最後に
graphql-rubyを使い始める前にもガイドは一通り目を通したつもりだったのですが、authorized?の存在は見落としていました・・・
authorized?以外にもまだまだ気づいていない便利な機能がありそうですね。
また、今はなかったとしてもがんがんバージョンアップされており、これからも新しい機能が追加される可能性も高いので、これからもgraphql-rubyの動向をチェックしていきたいと思います。
- 投稿日:2020-08-04T04:13:36+09:00
railsでのboorstrapの導入
自分用です!
bootstrapを導入していきます。
Gemfilegem 'bootstrap', '~> 4.4.1' gem 'font-awesome-sass', '~> 5.12.0' gem 'jquery-rails'Gemfileに記載をし、bundle installを実行。
ちなみに、公式のgemを検索するときは、rubygemsがおすすめ。
また gem 'font-awesome-rails'では、font-awesome5系に対応していないので gem 'font-awesome-sass'を使用する必要があります。
application.scss@import 'bootstrap'; @import 'font-awesome-sprockets'; @import 'font-awesome';を追加。
application.js//= require jquery3 //= require popper //= require bootstrap-sprocketsを追加してください。
//= require_tree . は一番下の行に記載していること
※ ここではrequireするJavaScriptファイルの順番に注意してください。
//= require jquery3よりも前に//= require_tree .を記載した場合、 //= require_tree .で読み込んだファイルでjqueryのコードを参照しているとjquery3が読み込まれる前に評価されるためメソッドが未定義となりエラーになるケースが後の課題で発生します。
- 投稿日:2020-08-04T04:02:06+09:00
scssの入れ子とパーシャル
自分用です!
sassのimport
railsでは、cssをsassで書いていきます。
・scssにすることで変数や親子関係の定義など、効率化の機能が使用でき、全体のコード量を削減できます。
・sass記法ではインデント制御のためデザイナーに引き渡しづらく、cssの記法から離れてしまうという面からscss記法を採用しています。application.scssとtop.scssがあるとして、top.scssをapplication.scssにインポートします。
application.scss@import 'top';このように、@importを使えば必要なcssファイルだけをインポートできます。
パーシャル
次に、sassと同じようにviewでも入れ子構造が使えます。
それがパーシャルです。application.html.erbにheaderとfooterのコードがたくさん入っていていると見えずらいですよね。
そこで、shared/_header.html.erbと、shared/_footer.html.erbを作成し、そこにヘッダーとフッターのコードをかき、application.htmlに入れ子する。
application.html.erb<body> <%= render 'shared/header' %> <%= yield %> <%= render 'shared/footer' %> </body>このようにすれば、見やすくなる。
- 投稿日:2020-08-04T03:38:14+09:00
rails gの際のファイル生成の設定
自分用です!
rails g コマンドを使用した際に、assets、helper、testファイル、ルーティングが生成されないように設定して行きます。
config/application.rbconfig.generators do |g| g.skip_routes true g.assets false g.helper false g.test_framework false endこれで、無駄なファイルが生成されない。
ちなみに、、、
環境設定をする際は、
config/initializers以下の各ファイル
特定のツールや機能に対する設定ファイルを、ファイル別に記述する。(assets.rb / aws.rbなど)config/environments以下
環境別に行いたい初期化作業を記述する
(production.rb / staging.rb など)config/applicaiton.rb
アプリケーション全体に関する設定を行う順序に寄らないものに関しては、基本的に初期化作業内で全て実行されますが、後々のメンテナンスのしやすさ等を考慮し、上記のようなディレクトリ構成でファイルを分けていくことが無難。
- 投稿日:2020-08-04T03:24:10+09:00
モデル、コントローラーの生成
- 投稿日:2020-08-04T00:09:39+09:00
[rails]部分テンプレートの作成方法
部分テンプレートとは
「部分テンプレート」は、複数のページで共通して利用できるViewファイルのことです。
複数のファイルで重複しているコードを一つのファイルにまとまることで、修正時に一箇所だけの修正で済んだり、コードの記述が少なくなり可読性が高くなります。手順はざっくりこんな感じです。
1.共通化できる部分を探し出す
2.部分テンプレートファイルを作成
3.部分テンプレートファイルに共通化部分を記述
4.部分テンプレートファイルを呼び出す1.共通化できる部分を探し出す
そのままですが、重複している箇所を探す作業です。
2.部分テンプレートファイルを作成
ファイル名の先頭にアンダースコア( _ )付きのerbファイルが、部分テンプレートファイルとして認識されます。今回は投稿機能を部分テンプレート化で見ていきます。
app/views/books/_newform.html.erb3.部分テンプレートファイルに共通化部分を記述
共通部分を切り出し、ファイルに貼り付けます。
基本的に部分テンプレートファイルではローカル変数(@がないやつ)を使います。
部分テンプレートファイル内でインスタンス変数(@がついてるやつ)を利用すると、controller側でインスタンス変数の名前や挙動を変更したとき、部分テンプレート側も変更しなければいけなくなるからです。app/views/books/_newform.html.erb<%= form_for(book) do |f| %> <div class="field row"> <%= f.label :title %><br> <%= f.text_field :title, class: "col-xs-3 book_title" %> </div> <p></p> <div class="field row"> <%= f.label :body %><br> <%= f.text_area :body, class: "col-xs-3 book_body" %> </div> <div class="actions row"> <%= f.submit class: "btn btn-primary col-xs-3" %> </div> <% end %> </div>4.部分テンプレートファイルを呼び出す
呼び出す時の書き方は以下の通りです。
なお、呼び出すときは部分テンプレートのアンダースコアは省略します。<%= render [部分テンプレートファイルの指定], [ローカル変数]:[渡す値] %>app/views/books/index.html.erb<%= render 'books/newform', book: @book %>