- 投稿日:2020-05-20T21:59:13+09:00
【Rails】ActionCableを使う際のnginxの設定でハマった
はじめに
Ruby on Railsで作ったアプリにリアルタイムチャットの機能を付けたいと思って、ActionCableを使ったときの話です。
ローカル環境ではうまく動いたのですが、本番環境ではリアルタイムチャットの部分が動きませんでした。
原因を調べたところ、ActionCableで使うwebsocketという通信をするために、それに対応するようなnginxの設定が必要なようでした。
(為参考)元々のnginxの設定
元々のnginxの設定です。
修正前と修正後の差分がわかりやすいようにこちらも載せます。
ActionCableを実装する前はこれで問題なく動いていました。nginx.conf# https://github.com/puma/puma/blob/master/docs/nginx.md upstream app { server unix:///app/tmp/sockets/puma.sock; } server { listen 80; server_name ***.***.***.***; # アプリのIPアドレス keepalive_timeout 5; # static files root /app/public; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; # static files if (-f $request_filename) { break; } if (-f $request_filename.html) { rewrite (.*) $1/index.html break; } if (-f $request_filename.html) { rewrite (.*) $1.html break; } if (!-f $request_filename) { proxy_pass http://app; break; } } location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ { expires max; break; } }ActionCableに対応したnginxの設定
websocketの通信を扱えるようにするために
location /cable以下を追加しました。nginx.conf# https://github.com/puma/puma/blob/master/docs/nginx.md upstream app { server unix:///app/tmp/sockets/puma.sock; } server { listen 80; server_name ***.***.***.***; # アプリのIPアドレス keepalive_timeout 5; # static files root /app/public; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; # static files if (-f $request_filename) { break; } if (-f $request_filename.html) { rewrite (.*) $1/index.html break; } if (-f $request_filename.html) { rewrite (.*) $1.html break; } if (!-f $request_filename) { proxy_pass http://app; break; } } #以下を追加 location /cable { proxy_http_version 1.1; proxy_set_header Upgrade websocket; proxy_set_header Connection Upgrade; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_pass http://app/cable; } #追加部分ここまで location ~* \.(ico|css|gif|jpe?g|png|js)(\?[0-9]+)?$ { expires max; break; } }
- 投稿日:2020-05-20T21:16:31+09:00
paizaランクCの達成方法(timesメソッド)
はじめに
paizaランクCを達成するにはどうすれば良いか?
目安を解説しました。前提
筆者がRubyを主に学習しているので、Rubyで解説しています。
実行
Cランクを目指すには、繰り返し条件を使いこなす必要があります。
私が良く使うのはtimesメソッド
です。t = gets.to_i t.times do puts "hello!" end以上のプログラムを実行すれば、入力した回数だけ
hello!
と出力されます。
応用すれば、次のような事も出来ます。t = gets.to_i i = 1 t.times do puts i i += 1 end以上プログラムを実行すれば、1から入力され数までの整数が順番に出力されます。
配列も使用できます。t = gets.to_i num = [] i = 1 t.times do num << i i += 1 endこのプログラムを実行すれば、
[1, 2, 3, …,i]
という配列ができます。paizaランクCを目指すには、まずはこのtimesメソッドを使いこなすことが必要です。
- 投稿日:2020-05-20T20:42:59+09:00
Rails6について
前提
Rails6について学んだことを書いていきます。
macOS
Docker・Docker Composeインストール済み本題
仮想環境を使う理由
Railsアプリケーションの開発をしようとするときに問題がある。
それは開発用マシン(作業マシン)と実運用を行うマシン(プロダクションマシン)の環境が異なること。
ここで言う環境とは、OSの種類やバージョン、あるいはOS上で動くソフトウェアやライブラリの種類やバージョンをさす。
Ruby on RailsはWindowsでもMacOSでもLinuxでも動作するように設計されている。
しかし、Railsを拡張するために導入するGemパッケージは必ずしもそうではない。
仮に動いたとしても、特定の環境では振る舞いが微妙に異なったり、不具合が出たりすることがある。この問題の解決方法として、作業マシンとプロダクションマシンの環境を一致させること。
そこで現れるのが仮想環境と言うオプション。
Windows、MacOS、ubuntuなどのデスクトップOSの上に仮想環境を構築し、そこにLinuxベースのServerOSをインストールする。開発中のRailsアプリケーションは仮想環境で動作させる。
デスクトップOS上のWebブラウザからRailsアプリケーションにアクセスして動作を確認。
他方、Railsアプリケーションのソースコードは共有フォルダの機能によって、デスクトップOS上で開いて編集する。
こうすれば、使い慣れたテキストエディタやIDEを使い続けながら、プロダクションマシンと同等の環境下でRailsアプリケーションの開発が行える。Dockerとは
仮想環境を提供するオープンソース・ソフトウェア。
設定の容易さや起動の早さが人気。
個々の仮想環境をコンテナと呼ぶ。
コンテナの内容はDockerfileと呼ばれるテキストファイルで記述される。
このファイルがあれば様々なOS上でコンテナを復元できる。Docker-composeとは
Dockerを用いて、Railsアプリケーションを開発したり、プロダクション環境で動かすとき、Railsアプリケーションとデータベースサーバーを別々のコンテナとして構築するのが一般的。
Docker-composeはこれらの複数のコンテナをまとめて起動・停止するためのツール。
docker-compose up
というコマンドを実行するだけで、Webアプリケーションを構成する全てのコンテナ群が動き出す。Rails開発環境の構築
DockerとDocker Composeを用いてRails開発用コンテナ群を構築。
terminal.$ git clone https://github.com/oiax/rails6-compose.git $ cd rails6-compose $ ./setup.shコンテナ群の起動と停止
コンテナ群を全て起動するにはターミナルで下記のコマンドを実行。
オプション-dはコンテナをデーモン(バックグラウンドプロセス)として動かすためのもの。terminal.$ docker-compose up -dコンテナ群を停止するにはターミナルで下記のコマンドを実行。
terminal.$ docker-compose stopwebコンテナにログイン
terminal.$ docker-compose exec web bashRailsのバージョン確認
bash-4.4$ rails --versionRails6.0.0と結果出力されればOK!
rails newコマンドで新規Railsアプリケーションの作成。
bash-4.4$ rails new アプリ名 -d postgresql --skip-test-unit
--skip-test-unit
はTest::Unit関連のコードの生成を省略するためのオプション。中身の確認
bash-4.4$ ls -a アプリ名/Gemfileの編集
gem "bcrypt" #パスワードの暗号化 gem "rails-i18n" #railsが出力するエラーメッセージ、日付、時刻、通貨単位などの翻訳ファイルを集めたもの gem "kaminari" #ページネーション機能 gem "date_validator" #日付のバリデーションを行う gem "valid_email2" #メールアドレスへのバリデーションを行う gem "nokogiri" #XML/HTMLの解析・生成のため※email_validatorはバージョンアップでバリデーションの基準が緩くなり、実用性が乏しくなった。
例えば、@を2個含むようなメールアドレスを有効と判定してしまう。
- 投稿日:2020-05-20T20:13:07+09:00
【Ruby on Rails】undefined method `id' for nil:NilClassのエラー解消方法
"undefined method `id' for nil:NilClass"のエラー
もし打ち間違えなどがないのにこのようなNoMethodErrorのundefined method `id' for nil:NilClassのエラーというエラーがでた場合どんなことが原因として考えられるか。メソッドが定義されていないというエラーがでました...どうしてきちんと定義したのにこのようなエラーがでたのだろう...
この実装はログインしたユーザーには自分の情報が見れても、他のユーザーには見せたくない場合にこのような実装を用います。
<% if @user.id == current_user.id %>
実装したコードの記述に間違いがないのにどうしてこうなったんだろうと考えてあることに気がつきます!
ログインをしていませんでした!
ログインをしていないと当然成立しない処理なので当然ですよね^_^;
ログインしてから再度確認をしたら無事にエラーが解消されました!
- 投稿日:2020-05-20T19:19:34+09:00
100日後に1人前になる新人エンジニア(0日目)
100日後に1人前になる新人エンジニア(自己紹介)
営業社員から転職をしてエンジニアになりました。
Javaでポートフォリオを作り、今後はRailsで開発を行っていく予定です。筋トレが好きです。
社会人の2年目です。
餃子も好きです。目的
- 技術的な蓄積
- アドバイスをいただく
- たくさんLGTMが欲しい
以上が主な目的です。
特に3つ目は私のエンジニアとしてのモチベーションに関わります笑100日で1人前
なれるかはわからないです。
ただ期限を持ってやることが大切だと思うので
ワニさんを見習って100日にしました。ちなみに今日はまだ0日目1日1日を大切にして進んでいきたいと思っています!
本格的な更新は明日からです。本日はまだ導入です。
お願いします
多くの人に見てもらうこと、いろんな意見をいただくことが大切だと感じています。
肯定的な意見でも、否定的な意見でもすべて僕の財産になっていくと思うので、
何か思ったことがありましたらコメントをいただけると幸いです。それではまた明日
1人前のエンジニアになるまであと100日
- 投稿日:2020-05-20T19:03:14+09:00
AtCoderでRuby学習8【第一回アルゴリズム実技検定 2倍チェック 】正規表現
はじめに
Ruby学習の一環として「アルゴリズム実技検定」に挑戦します。
そのための学習の中で学んだことをアウトプットしていきます。
今回は「第一回アルゴリズム実技検定」の一問目(2倍チェック)より。
第一回アルゴリズム実技検定 過去問自分の解答と、解答する中で学んだ表現・メソッドなどを紹介していきます。
問題
3桁の整数を受け取り、それを2倍にして出力するプログラムの作成をします。
しかし、入力される文字列 S には英小文字が紛れ込むことがあります。その場合はエラーを出力します。
S が3桁の整数である場合('0'で始まる場合も含む)はその2倍の整数を出力し、そうでない場合は'error'と出力すること。制約
・S は長さ3の文字列である
・S の各文字は、数字もしくは英小文字である入力は以下の形で与えられる。
S 入力例① 678 入力例② 4g4出力例 # 入力例①678 => 678 # 入力例②4g4 => error解答
まずは僕が提出した解答から。
s = gets.chomp print /^[0-9]+$/ === s ? s.to_i * 2 : "error"受け取った文字列を、正規表現を用いて各桁の文字列が0から9であるかを判定し、
真ならInteger型に変換した後に2倍した数字を、偽ならerrorと出力するようにしました。正規表現
ここで、正規表現とは何か、を改めて確認しておきたいと思います。
初めは、「正規表現」という言葉自体わかりづらかったのですが、
こちらの記事を読んでなんとなく腑に落ちました。
正規表現とは?正規表現とは「様々な文字列をひとつの文字列で表現する表現法」
そして「文字列の検索や置換に便利」。他にもあるかもしれませんが、"他"は、その"他"に出会った時に覚えることにします。
整数を表現した正規表現で判定する
正規表現とは何かをひとまず定義したところで、今回用いた正規表現を見ていきます。
/^[0-9]+$/ === sここで使用している正規表現に関係するものは以下の通り
===
左辺の正規表現の文字列パターンが、右辺の文字列に含まれているかを判定します。
左辺と右辺は入れ替えて記述してはいけません。/ /
//で囲むことで正規表現オブジェクトを生成することが出来ます。
つまり、「//で囲まれたものが正規表現だよ」という宣言になります。[ ]
角括弧[ ]で挟まれた部分を文字クラスと言います。
1個以上の文字を列挙したもので、いずれかの1文字にマッチします(上記では0から9のうち1文字)。-(ハイフン)
文字の範囲を示します。
[A-C]ならA,B,Cのいずれか、[0-9]なら0から9のうちいずれか、となります。^(/^xxx/)
行頭。文字列の先頭や改行文字の直後の位置にマッチします。+
1回以上の繰り返しを表します。$(/xxx$/)
行末。文字列の末尾や改行文字の直前の位置にマッチします。よって、下記の記述の意味は、
「sは、全ての文字が0から9の数字から成る文字列か」を判定していることになります。/^[0-9]+$/ === s最後に
以上、「第一回アルゴリズム実技検定」の一問目(2倍チェック)を解く中で学んだ正規表現についてまとめました。
正規表現は、とにかく問題をこなして慣れていくのが大事かなと思います。
使えそうな場面では、積極的に調べながら使っていきたいです。もし間違いなどございましたら、ご指摘いただけると嬉しいです。
- 投稿日:2020-05-20T17:21:48+09:00
はじめてのWSLとrubyをインストールする
WSL (Windows Subsystem for Linux)
Windows上でLinuxを動かすことができる(詳細は他記事に譲る)
- Linuxカーネルが動作するLinux環境
- Ubuntuの種類が3種類ほどあるので、今回はUbuntuを使うDockerやVirtualBoxなどは多少触ったことありますが、勉強がてら使ってみます。
Ruby install
まずはUpdate。最初に実行しましょう!
$ sudo apt-get updateGet rbenv
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenvSet Path
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc $ echo 'eval "$(rbenv init -)"' >> ~/.bashrc $ source ~/.bashrcGet install Command
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build $ sudo apt install build-essentialInstall Ruby
$ rbenv install 2.7.1 Downloading ruby-2.7.1.tar.bz2... -> https://cache.ruby-lang.org/pub/ruby/2.7/ruby-2.7.1.tar.bz2 Installing ruby-2.7.1... BUILD FAILED (Ubuntu 20.04 using ruby-build 20200519) Inspect or clean up the working tree at /tmp/ruby-build.20200520163201.5902.R4jqnb Results logged to /tmp/ruby-build.20200520163201.5902.log Last 10 log lines: from ./tool/rbinstall.rb:846:in `block (2 levels) in install_default_gem' from ./tool/rbinstall.rb:279:in `open_for_install' from ./tool/rbinstall.rb:845:in `block in install_default_gem' from ./tool/rbinstall.rb:835:in `each' from ./tool/rbinstall.rb:835:in `install_default_gem' from ./tool/rbinstall.rb:799:in `block in <main>' from ./tool/rbinstall.rb:950:in `block in <main>' from ./tool/rbinstall.rb:947:in `each' from ./tool/rbinstall.rb:947:in `<main>' make: *** [uncommon.mk:373: do-install-all] Error 1ログファイルを見ると、opensslがないためインストールする
$ sudo apt-get install openssl引き続き検証しながら、完結したら続き更新します
- 投稿日:2020-05-20T16:17:13+09:00
rails の link_to
railsのhelper(link_to・form_withなど)でget以外のメソッドが使えなくなるとき。
それは、jsの設定がうまくいってないことが多々あります。
- 投稿日:2020-05-20T16:13:12+09:00
Gem::ConflictErrorについて(activesupport)
今回はminitestを実行しようとした際に急にエラーが出始めましたのでその経緯と解決策を記載します。
環境は下記でした。
ruby 2.6.3 Rails 5.1.7 minitest (5.14.1, 5.14.0, 5.11.3, 5.10.3, 5.10.1)この記事は以下のテキストを進めていた際に発生したエラーについての執筆です。
※書籍内容に誤り等があるわけではなく、他の作業と並行して進めていた際に意図せずgemを更新してしまったことによるエラーとその解決法についての執筆です
minitestのエラーに関しては著者様のほうでも各種の解決策を投稿していただいておりましたが、どれにも当てはまらない状況でした。
書籍進行中に急にテストでエラーが出るようになったので、おそらく、合間に行った別の作業が要因かと思いましたのでそのような経験がある方のみ、参考にしてください。以下、エラー内容です。
ec2-user:~/environment/ruby-book $ ruby lib/sample_test.rb Traceback (most recent call last): 9: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:68:in `block in autorun' 8: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:126:in `run' 7: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:97:in `load_plugins' 6: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:97:in `each' 5: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:103:in `block in load_plugins' 4: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in ` require' 3: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require' 2: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/railties-5.1.7/lib/minitest/rails_plugin.rb:2:in `<top (required)>' 1: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:117:in `require' /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:117:in `require': cannot load such file -- rails/test_unit/reporter (LoadError) 12: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:68:in `block in autorun' 11: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:126:in `run' 10: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:97:in `load_plugins' 9: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:97:in `each' 8: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:103:in `block in load_plugins' 7: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require' 6: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require' 5: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/railties-5.1.7/lib/minitest/rails_plugin.rb:2:in `<top (required)>' 4: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:34:in `require' 3: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:123:in `rescue in require' 2: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems.rb:218:in `try_activate' 1: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/specification.rb:1415:in `activate' /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/specification.rb:2298:in `raise_if_conflicts': Unable to activate railties-5.1.7, because activesupport-6.0.3.1 conflicts with activesupport (= 5.1.7) (Gem::ConflictError) 13: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:68:in `block in autorun' 12: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:126:in `run' 11: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:97:in `load_plugins' 10: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:97:in `each' 9: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/minitest-5.14.1/lib/minitest.rb:103:in `block in load_plugins' 8: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require' 7: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require' 6: from /home/ec2-user/.rvm/gems/ruby-2.6.3/gems/railties-5.1.7/lib/minitest/rails_plugin.rb:2:in `<top (required)>' 5: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:34:in `require' 4: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/core_ext/kernel_require.rb:123:in `rescue in require' 3: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems.rb:217:in `try_activate' 2: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems.rb:224:in `rescue in try_activate' 1: from /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/specification.rb:1415:in `activate' /home/ec2-user/.rvm/rubies/ruby-2.6.3/lib/ruby/site_ruby/2.6.0/rubygems/specification.rb:2298:in `raise_if_conflicts': Unable to activate railties-5.1.7, because activesupport-6.0.3.1 conflicts with activesupport (= 5.1.7) (Gem::ConflictError)activesupportの6.0.3.1と5.1.7がコンフリクトしているよというもの。
思い当たる要因としては、前日にsinatraでのアプリ制作時にいろいろと作業したことかと。
結果的にはgemをアンインストールすることで解決しました。
ec2-user:~/environment/ruby-book $ gem uninstall activesupport -v 6.0.3.1 You have requested to uninstall the gem: activesupport-6.0.3.1 activemodel-6.0.3.1 depends on activesupport (= 6.0.3.1) activerecord-6.0.3.1 depends on activesupport (= 6.0.3.1) If you remove this gem, these dependencies will not be met. Continue with Uninstall? [yN] y Successfully uninstalled activesupport-6.0.3.1上記で6.0.3.1のほうをアンインストールしました。
ec2-user:~/environment/ruby-book $ ruby lib/sample_test.rb Run options: --seed 27045 # Running: E Finished in 0.002129s, 469.6838 runs/s, 0.0000 assertions/s. 1) Error: SampleTest#test_sample: NoMethodError: undefined method `upcase' for nil:NilClass Did you mean? case lib/sample_test.rb:5:in `test_sample' 1 runs, 0 assertions, 0 failures, 1 errors, 0 skips ec2-user:~/environment/ruby-book $ ruby test/deep_freezable_test.rb Run options: --seed 56480 # Running: . Finished in 0.002055s, 486.6611 runs/s, 486.6611 assertions/s. 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips結果的に無事、テスト検証できました。
参考に、アンインストール後のバージョンを再度掲載します。
ec2-user:~/environment/ruby-book $ gem list | grep activesupport activesupport (5.1.7, 5.1.6, 5.0.0)同じように詰まった方の参考になれば。
- 投稿日:2020-05-20T14:18:32+09:00
新しくなったStripeのCheckoutスニペットジェネレータがでない時
Ruby on Rails などではレガシー版のCheckoutの実装しかない。2019年にリニューアルされたCheckoutはクライアントサイドのみの実装であれば導入がJavaScriptのコードをどこかのHTMLに貼り付けるだけで超簡単なのだが、そのCheckout機能を有効化しないとコードスニペットが出てこないという罠にハマった。
公式リファレンス
https://stripe.com/docs/payments/checkout/client
英語だけどこれを見つけられれば早い。ググってもレガシー版のリファレンスばかりがでてきた。新しいChecoutのやり方Qiita
https://qiita.com/kskinaba/items/2ba42eda07227a9b96d5
基本はkん
この中段のCheckoutの設定
から
クライアント専用組み込みを有効にする。
すると左側に商品メニューがでて、商品を作成すると(本番とテストで商品は別扱いとなることに注意)
Checkoutで使用
というボタンが出る。これを押して出てくるJavaScriptコードを任意のWebページに貼り付けてやれば、あとはよしなにやってくれる決済ボタンが表示される。ちなみに商品とSKU(購入情報)を混同しないように注意。補足
レガシー版のCheckoutのドキュメントではRails向けに書かれていたのに、新しい版のサーバーサイドも開発するドキュメントではRubyのコードとしてしか書かれていいないので、初心者からすると「ProgateでRailsはやったけど純粋なRubyのコードだけ見せられてもわけわからん…」となって結構ハードルが高い印象。
その他の参考記事
Railsでのやり方
https://qiita.com/roadfox303/items/b93e440c10af82bda7bd
レガシー版のRailsでのやり方
https://qiita.com/disk131/items/8381f35be9c70bd7b9e2
ただしURLの:id
の取得をうまくしてくれずルーティングがうまくできませんみたいなエラーが出た。
- 投稿日:2020-05-20T14:13:23+09:00
Railsで多階層カテゴリー(ancestry)にカウンター(counter_culture)を実装する
肉 > 鶏肉 > むね肉
のような多階層カテゴリーがあった時、
レシピにむね肉カテゴリーを登録したら、親カテゴリーである肉 > 鶏肉
もカウントする機能を実装してみた。ancestryを用いた多階層カテゴリーにカウンターをつけている記事が見つからず、自分で調べてみて実装したので解説してみる。
同じような人がいたらぜひ参考にしてください。
はじめに
環境
Ruby: 2.6.6 Rails: 6.0.2.2前提
レシピを掲載するサイトを作成している。
レシピDBとカテゴリーDBがある。
レシピには複数のカテゴリーがついている。(中間テーブル)
カテゴリーは多階層カテゴリになっている。
例: 肉 > 鶏肉 > むね肉
※ ancestryで実装(経路列挙モデル)1カテゴリーに何個レシピがあるかカウンターを実装している
使用しているgem
ancestry: 3.0.7 counter_culture: 2.5.1counter_cultureの導入はこちらを参照。
関連レコード数の集計(カウンターキャッシュ) - Qiita現状
Recipes Table
Table name: recipes id :bigint not null, primary key title :string not null description :string not nullRecipeとCategoryの中間テーブル
1つのレシピに対して、複数のカテゴリーが登録される関係になっている。Table name: recipe_categories id :bigint not null, primary key recipe_id :bigint not null category_id :bigint not nullCategories Table
Table name: categories id :bigint not null, primary key name :string not null ancestry :string recipes_count :integer default(0), not nullModel
class Recipe < ApplicationRecord has_many :recipe_categories, dependent: :destroy endclass Category < ApplicationRecord has_ancestry has_many :recipe_categories, dependent: :destroy endclass RecipeCategory < ApplicationRecord belongs_to :recipe belongs_to :category counter_culture :category, column_name: :recipes_count end現状の問題点
1:1の関係だと下記を中間テーブルに入れるだけでカウントされる。
counter_culture :category, column_name: :recipes_count今回は親カテゴリーも同時にカウントされる機能をつけたい。
例えば
照り焼きチキンのレシピに、むね肉のカテゴリーがついている。Recipe.find(1) => #<Recipe id: 1, title: "照り焼きチキン" > RecipeCategory.find(1) => #<RecipeCategory id: 1, recipe_id: 1, category_id: 20 > Category.find(20) => #<Category id: 20, name: "むね肉", ancestry: "1/10", recipes_count: 1 >むね肉カテゴリーは以下の関係になっている。
id:1 > id:10 > id:20
肉 > 鶏肉 > むね肉1:1のカウンターだと関連付けした「むね肉」はカウントされるが、「肉」と「鶏肉」にカウントされない。
親カテゴリーである「肉」と「鶏肉」もカウントされるように実装をしてみた。親子カテゴリのカウンター実装方法
結論からいうと、こう実装した。
class RecipeCategory < ApplicationRecord belongs_to :recipe belongs_to :category counter_culture :category, column_name: :recipes_count, foreign_key_values: proc { |category_id| Category.find(category_id).path_ids } end
foreign_key_values
は外部キーを上書きするオプション。通常だと関連づいている
category_id: 20
が外部キーとなり、Categoryのid:20のカウントが増減する。
foreign_key_values
に配列形式で数値を渡すと、渡した値を外部キーとして対象すべてのカウントを増減する。親子カテゴリーのすべてをカウントしたいため、例だと
[1, 10, 20]
の値を渡す形で実装する。実装の解説
公式のリファレンスと翻訳解説されている記事を参考にした。
GitHub - magnusvk/counter_culture: Turbo-charged counter caches for your Rails app.
Rails向け高機能カウンタキャッシュ gem 'counter_culture' README(翻訳)|TechRachoforeign_key_values: proc { |category_id| Category.find(category_id).path_ids }
proc { |category_id| }
まずprocで引数を渡す、この時に渡されるのは通常の時の外部キー(今回だと20
)
Category.find(category_id)
対象のレコードオブジェクトを取得する。
.path_ids
ancestryの機能でpath_ids
を行うとオブジェクトの親子関係のIDをリストで取得することができる。参照: 【翻訳】Gem Ancestry公式ドキュメント - Qiita
実行してみると
=> [1, 10, 20]
が返ってくることがわかる。Category.find(20).path_ids Category Load (1.0ms) SELECT "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT $2 [["id", 20], ["LIMIT", 1]] => [1, 10, 20]
[1, 10, 20]
をforeign_key_values:
にわたすことで親子カテゴリーすべてをカウントできるようになった。カウント再計算の機能実装
counter_culture
にはcounter_culture_fix_counts
というカウントを再計算する機能が実装されている。
すでにあるデータをカウントしたり、カウントのズレを修正するために使うのだが、foreign_key_values
オプションを使うとこの機能が使えない。RecipeCategory.counter_culture_fix_counts => Fixing counter caches is not supported when using :foreign_key_values; you may skip this relation with :skip_unsupported => true調べたところ、
foreign_key_values
を使った場合、自分で再計算機能を実装するしかないようなので実装してみた。結論から、こう実装した。
すべて計算し直しているため、件数が多いと時間がかかるかもしれない。
良い実装方法があったら教えてください。class RecipeCategory < ApplicationRecord #... # Categoryのrecipes_countをすべて再計算する def self.fix_counts Category.update_all(recipes_count: 0) target_categories = pluck(:category_id) # categoriesの個数を計算する => { 1: 10, 10: 3, 20: 1 } count_categories = target_categories.group_by(&:itself).transform_values(&:size) count_categories.each do |category_id, count| count_up_categories = Category.find(category_id).path_ids Category.update_counters(count_up_categories, recipes_count: count) end end end実行している内容は以下のとおり。
- カウンターをすべて0にする
- 中間テーブルにあるcategory_idを配列で取得
- 個数を計算する => { 1: 10, 10: 3, 20: 1 }
- それぞれのカテゴリーの親子カテゴリーIDの配列を取得
- 親子カテゴリーすべてのカウントを個数分増やす
3の個数を計算する方法は、以下の記事を参考にした。
配列に同じ要素が何個あるかを数える - patorashのブログ5のカウントを個数分増やす方法は、以下の記事を参考にした。
[Rails+MySQL] カウンターの実装 【なければ新規作成したいし、あれば適切にインクリメントしたい】 - Qiita
ActiveRecord::CounterCache::ClassMethodsこれにて、
RecipeCategory.fix_counts
を実行するとカウントの再計算を行うようになった。結論
RecipeCategoryを以下にすることで親子カテゴリーのカウンター機能を実装できた。
class RecipeCategory < ApplicationRecord belongs_to :recipe belongs_to :category counter_culture :category, column_name: :recipes_count, foreign_key_values: proc { |category_id| Category.find(category_id).path_ids } # Categoryのrecipes_countをすべて再計算する def self.fix_counts Category.update_all(recipes_count: 0) target_categories = pluck(:category_id) # categoriesの個数を計算する => { 100: 3, 102: 2 } count_categories = target_categories.group_by(&:itself).transform_values(&:size) count_categories.each do |category_id, count| count_up_categories = Category.find(category_id).path_ids Category.update_counters(count_up_categories, recipes_count: count) end end endぜひ、参考にしてみてください。
FBや改善点がありましたら、コメントで教えていただけると助かります。
- 投稿日:2020-05-20T12:33:56+09:00
devise ページネーション など 基礎
忘れがちな余談
コントローラーのアクション内に定義されたインスタンス変数はそのアクションのビューで使用することができる。
書くカラムに保存されている値のことをプロパティ値と言います。
ヘルパーメソッド
Railsでは予めviewでHTMLタグを出現させたり、テキストを加工するためのメソッドが用意されています。
これらをヘルパーメソッドと言います。
simple_format 改行で<br>
を付与、文字列を<p>
で囲ってくれるメソッド
form_tag フォームを出現させるメソッド
link_to aタグを出現させるメソッド
などがあります。CSSファイルの読み込みはHTMLのheadタグ内の
stylesheet_link_tag
から読み込まれます。require_treeは、引数として与えられたディレクトリ以下のCSSファイルをアルファベット順に全て読み込むという意味がある。
.
は引数であり、カレントディレクトリを表します。application.cssrequire_tree .MVC
ルーティング→コントローラー→モデル→ビュー の順に処理が行われるのがRailsです。
モデル・ビュー・コントローラーを使用して処理を行うシステムをそれぞれの頭文字をとってMVCと言います。フォーム
フォームとはユーザーが情報を入力し、その情報をサーバーに送信するためのもの。
HTMLのコード内にform要素を作成し、その中にinput要素やtextarea要素を入れることで作成できます。
formタグでもフォームは作成できるが、セキュリティの観念上、Railsではヘルパーメソッドである、form_tag, form_for, form_withを使うのが推奨されている。ユーザーがフォームに入力した値は、コントローラー内では
params
という変数に入っています。
params
はハッシュオブジェクトだと考えましょう。
ビューでフォームに入力された情報は、コントローラーにキーと一緒にパラメーターとして送られます。
このパラメーターはparams
というメソッドを使うことで取得することができます。
使用方法はparams[:キー名]form内のinputやtextareaにあるname属性の値がキー名に当たります。
ストロングパラメーター
ストロングパラメーターとは、指定したキーを持つパラメーターのみを受け取るようにするもの。
不正な情報などを受け取らないようにするのでセキュリティを強化できます。
任意のストロングパラメーター名_params
というメソッドを作成することで定義ができます。xxxxx.rbprivate def sample_params ###任意のストロングパラメーター名_params params.permit(:キー名, :キー名) endメソッドの中には
params.permit
と記述して受け取ることを許可するパラメーターのキー名を続けます。
permitメソッドは、ビューから送られてきた情報のハッシュであるparams
から後に続けたキーの名前がバリューと共に新たなハッシュとして生成します。privateメソッド
class内でprivateと記述するとそれ以降に定義したメソッドはclassの外部から呼び出せなくなります。
外部から呼ばれたら困るメソッドを守れたり、private以下は読まなくて良くなるのでコードの可読性が上がります。image_tag
Railsではロゴやバナーなど固定して表示したい画像はapp/assets/images以下に置くことが普通です。
sample.html.erb<%= image_tag 'sample.png' %>pry-rails
pry-railsとは、Gemの一つであり、Rails向けのデバッグツールです。
これを使うことによりバグの有無を確認したり処理を途中で止めてソースコードが正しいか確認できます。
特に使う機能がbinding.pry
です。
これを使うことによって処理を途中で止めて、かつrailsコンソールと同じことができます。止めたいソースコードのところでbinding.pry
を記述しましょう。変数の値を取得したり、binding.pry
を複数個設置することによって処理が正しく行われているか確認することができます。
処理を再開させたい場合はターミナルでexit
を入力します。sample_controller.def create Sample.create(sample_params) binding.pry end private def sample_params params.permit(:name, :image, :text) end投稿画面で投稿する。
(new.html.erb)するとターミナルでコンソールが立ち上がる
ターミナル.[1] pry(#<SamplesController>)> params => <ActionController::Parameters {"authenticity_token"=>"DnHBKql5oQq9lARgEnK8gQiFAhnTvIVa1XH8d13u7tuVjDUGWPM1eEjVbF44iFms57+VE7+vT3SYUePDy3lwWQ==", "name"=>"aa", "image"=>"https://kumamoto.photo/archives/_data/i/upload/2019/03/13/20190313172208-2113bf9d-la.jpg", "text"=>"dddddddd", "controller"=>"samples", "action"=>"create"} permitted: false> [2] pry(#<SamplesController>)> params[:text] => "dddddddd"コンソールで
params
と入力すると投稿画面で入力した値がキー名と共にパラメーターとして出力される。
exit
と入力すると処理が再開する。ルートパス
URLにパスを付けない、ホスト名だけのURLのことをルートパスと言います。
routes.rbでの書き方は、routes.rbroot 'コントローラー名#アクション名'orderメソッド
インスタンスを並び替えるメソッドです。
全てのレコードを取得してきた場合レコードを並び替えられます。.order("カラム名 順序の指定”)ASCは昇順 日付で言うと古い方から
DESCは降順 日付で言うと新しい方からページネーション
ページを分割させてくれるもの。1ページ当たりの表示件数を決められます。
gem kaminariを利用することで実装できます。
page
メソッド
このメソッドでページ数を指定できます。全体のページ数でなく、今何ページにいるかと言うことです。
per
メソッド
1ページあたりの表示件数を決められます。sample_controller.@samples = Sample.order("id ASC").page(params[:page]).per(10)pageメソッドの引数は、kaminari を導入した際に、モデルクラスにpageというキーが追加されておりそれを記述しています。そのキーの値はビューで指定したページの番号となります。
devise-ログイン機能-
ログイン機能を簡単に実装できるgemです。
Gemfileにdeviseを記述して
bundle install後
アプリのディレクトリで
rails g devise:install
と叩くと
設定ファイルが作成されます。
config/initialize/devise.rb
とconfig/locales/devise.en.yml
です。アカウント作成のためのモデルを作らなければいけないので
rails g devise モデルクラス名
でモデルなどの関連ファイルを作成します。
モデルファイルや、マイグレーションファイルが作成されます。
それと同時に、routes.rb
に
devise_for :モデル名の複数形
が追記されます。
devise_forはログイン周りに必要なルーティングを一気に生成してくれるdeviseのヘルパーメソッドです。current_user, user_signed_in?などのヘルパーメソッドも使えるようになっています。
rails g devise:views
でデバイスのビューファイルを作成できます。Prefix
ルーティングのパスが入った変数のことです。
unless文
if文の逆で、条件式が偽の時にする処理を記述します。
unless 条件式 偽の時に実行する処理 end 条件分岐が1行の場合は、1行で記述できる。 puts "ログインしてください" unless user_signed_in? ###ログインしてなければ(つまり偽)"ログインしてください"が表示されるredirect_toメソッド
Railsでは基本的にアクションの処理が終わるとアクションと同名のビューに遷移しますが、
redirect_toメソッド
を使えば別のアクションを実行したり、ビューに遷移させたりできます。redirect_to { action: :index }キーに
action:
をとり、値にアクションの名前のシンボル型をとります。
例えば、:index
などです。{}は省略できます。before_actionメソッド
コントローラー内のアクションが実行する前に実行したいメソッドを指定できます。シンボル型のメソッド名で指定できます。
オプションでexcept
やonly
があり、アクションごとに制限をかけられます。テーブルにカラムを追加
rails g migration Addカラム名Toテーブル名 カラム名:型configure_permitted_parametersメソッド
deviseでログイン機能を実装した際、パラメーターの受け取り方が普通とは違います。ストロングパラメーターは初期状態では、メールアドレスとパスワードのみ受け取る設定がされています。追加するにはconfigure_permitted_parametersメソッドを使います。
application_controller.rbbefore_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters devise_parameter_sanitizer.permit(:追加したいアクション名, keys: [:追加するキー]) endapplication_controller.rbclass ApplicationController < ActionController::Base # protect_from_forgery with: :exception before_action :configure_permitted_parameters, if: :devise_controller? def configure_permitted_parameters devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname]) end end
- 投稿日:2020-05-20T12:02:11+09:00
FontAwesomeのiconをulやliに使う方法
はじめに
某プログラミングスクールにてフリマアプリを作成する際にFontAwesomeのiconが□に文字化けして悩んだ事を記事にします!!
完成図
以下の様にしたい!
しかし色々調べてみると
FontAwesomeのiconをulやolのlist-styleに使いたくなることってありますよね?
残念ながら、list-styleに直接アイコンを使うことは出来ないのです・・・・え!?
しかし似た様な事はできる様なので早速実践!!
show.html.haml.main__showMain__contentRight__topContent__itemBox__optionalArea %ul %li.optionalBtn.likeBtn#likeBtn %i.fas.fa-star お気に入り 0 %ul.optional %li.optionalBtn =link_to '' do %i.fa-flag.likeIcon 不適切な商品の通報show.scss&__optionalArea { display: flex; justify-content: space-between; ul { margin: 10px 0 0; display: flex; list-style: none; .likeBtn { margin-right: auto; padding: 6px 10px; border-radius: 40px; color: #3CCACE; border: 1px solid #ffb340; i { display: inline-block; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; } i:before { content:'\f005'; font-weight: 400; font-family: "Font Awesome 5 Free"; } } } .optional { margin: 10px 0 0; display: flex; .optionalBtn { font-size: 14px; a { padding: 6px 10px; display: inline-block; border-radius: 4px; color: #333; border: 1px solid #333; text-decoration: none; i { display: inline-block; font: normal normal normal 14px/1 FontAwesome; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; } i:before { content:'\f024'; font-weight: 400; font-family: "Font Awesome 5 Free"; } } } } }ポイントのなる所
show.scssul { list-style: none; ←これ } i:before { content:'\f005'; ←これ font-weight: 400; ←これ font-family: "Font Awesome 5 Free"; ←これ }これで□に文字化けせずに表示できる様になりました!!
最後まで見て頂きありがとうございます!!
この記事が少しでも参考になれば嬉しいです
参考記事
- 投稿日:2020-05-20T11:28:29+09:00
ancestoryを使ってカテゴリー機能を実装
はじめに
某プログラミングスクールに通いフリマアプリをチームで作成した際にカテゴリーの多階層構造の実装をしたので紹介します!!
完成図
それでは紹介したいと思います。
1.モデルの作成
まず商品とカテゴリーを紐付けさせたいので『category』と『item』のモデルを作成します。
$ rails g model category $ rails g model item2.ancestoryの導入
Gemfileにancestoryを導入します。
gem 'ancestry'ターミナルで下記コマンドを実行
$ bundle install $ rails g migration add_ancestry_to_category ancestry:string:index $ rails db:migrate3.アソシエーション
ancestoryを使うことで多対多の関係が1対多の関係になりDB設計がシンプルになります。
category.rbにhas_ancestoryを記述
category.rbclass Category < ApplicationRecord has_many :items has_ancestry enditem.rbclass Item < ApplicationRecord belongs_to :category end3.データを導入
seeds.rbにカテゴリーのレコードを導入していきます。
seeds.rblady = Category.create(name: "レディース") lady_1 = lady.children.create(name: "トップス") lady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])ancestryを入れると.childrenと記述することで直前の変数の子要素として扱うことができます。
以下の記述で親カテゴリーであるレディースが登録されます。
seeds.rblady = Category.create(name: "レディース")次に、以下の記述で子カテゴリーであるトップスが登録されます。
seeds.rblady_1 = lady.children.create(name: "トップス")そして、以下の記述で孫カテゴリー群が登録されます。
seeds.rblady_1.children.create([{name: "Tシャツ/カットソー(半袖/袖なし)"},{name: "Tシャツ/カットソー(七分/長袖)"},{name: "シャツ/ブラウス(半袖/袖なし)"},{name: "シャツ/ブラウス(七分/長袖)"},{name: "ポロシャツ"},{name: "キャミソール"},{name: "タンクトップ"},{name: "ホルターネック"},{name: "ニット/セーター"},{name: "チュニック"},{name: "カーディガン/ボレロ"},{name: "アンサンブル"},{name: "ベスト/ジレ"},{name: "パーカー"},{name: "トレーナー/スウェット"},{name: "ベアトップ/チューブトップ"},{name: "ジャージ"},{name: "その他"}])seeds.rb内にレコードを記述したら、ターミナルで以下のコマンドを実行しDBに反映させます。
$ rails db:seedDBではこのように登録されます。
最後まで見て頂きありがとうございます!!
この記事が少しでも参考になれば嬉しいです
- 投稿日:2020-05-20T10:56:33+09:00
マイグレーションファイルのrollback方法
はじめに
某プログラミングスクールにてフリマアプリを作成する際にカラム名を変更したい時にマイグレーションファイルのrollbackが必要だった為その時に調べた事を書きます。
1.マイグレーションファイルを1つずつ差し戻す方法
まずターミナルで以下を実行
$ rails db:rollback $ rails db:migrate:status ↓ターミナルにこの様に表示されると思います Status Migration ID Migration Name -------------------------------------------------- up 20200510074001 Create spending categories up 20200510074005 Create income categories up 20200510074056 Create incomes down 20200510074140 Create spendings2.複数のファイルを差し戻す
下記のように末尾にSTEP=で数を指定してあげると複数のファイルを差し戻すことが可能です。
今回は4つまとめて差し戻します。$ rails db:rollback STEP=4 $ rails db:migrate:status ↓ターミナルにこの様に表示されると思います Status Migration ID Migration Name -------------------------------------------------- down 20200510074001 Create spending categories down 20200510074005 Create income categories down 20200510074056 Create incomes down 20200510074140 Create spendings3.任意のマイグレーションファイルのみを差し戻す
下記のようにVERSIONで "Migration ID" を指定してあげると選択のマイグレーションファイルのみを差し戻すことが可能です。
$ rails db:migrate:down VERSION=20200510074001 $ rails db:migrate:status ↓ターミナルにこの様に表示されると思います Status Migration ID Migration Name -------------------------------------------------- down 20200510074001 Create spending categories up 20200510074005 Create income categories up 20200510074056 Create incomes up 20200510074140 Create spendings最後まで見て頂きありがとうございます!!
この記事が少しでも参考になれば嬉しいです
- 投稿日:2020-05-20T09:42:45+09:00
Railsで画像を使用する
- 投稿日:2020-05-20T08:34:59+09:00
RSpecによるTDDでRailsAPIを実装してみた。part1
はじめに
この記事は自分のための記憶を整理したりまとめたいという理由で書く記事です。もちろん読まれても恥ずかしく無いように書くますが、あくまでも自分で整理できればそれでOKな記事でありますので、説明不足ということがあるかと思いますが、あしからず...。
記事の内容
- この記事はTDDの形で開発をすめていきます。テストフレームワークはRSpecを使っています。
- 簡単なArticleとUserのモデルを使った記事管理システムを作っていきます。
- 認証はgithubアカウントを使ったOAuthを使います。
- databaseはapiに集中するためにデフォルトのsqlite3を使います
- part1はindexアクションの実装までを目標に進めていきます
# versions ruby 2.5.1 Rails 6.0.3.1 RSpec 3.9 - rspec-core 3.9.2 - rspec-expectations 3.9.2 - rspec-mocks 3.9.1 - rspec-rails 4.0.1 - rspec-support 3.9.3開発の準備
terminal$ rails new api_name -T --api-T はminitestに関わるファイルを生成しない
--api はこのプロジェクトはapiのためだけに使いますという宣言を前もってすることで、apiのためのファイル構成で始めることができる(ビューファイルが生成されないなど)gemfilegem 'rspec-rails' # rspecのgem gem 'factry_bot_rails' # テスト用のデータを入れるgem gem 'active_model_serializers' # serializeするためのgem gem 'kaminari' # pagenateするためのgem gem 'octokit', "~> 4.0" # githubユーザーを扱うためのgem以上のgemをdevelopmentの中に挿入
bundle install
これでいったん開発の準備は終わる
では開発していく
まず全体的な流れをなぞっておくと、
indexアクションの実装
↓
認証関係の実装(userモデルの実装)
↓
createアクションの実装
↓
updateアクションの実装という流れで進めていく
articleモデルを作成
$ rails g model title:string content:text slug:stringこんな構成のモデルを作成する
そして先ほど作成された
models/article_spec.rb
にテストコードを記述していくmodels/article_spec.rbrequire 'rails_helper' RSpec.describe Article, type: :model do describe '#validations' do it 'should validate the presence of the title' do article = build :article, title: '' expect(article).not_to be_valid expect(article.errors.messages[:title]).to include("can't be blank") end end endこのテストの内容はもしもタイトルが空だった時にエラーメッセージを出す、というものと、もう一つはvalidationに引っかかるというテスト
$ rspec spec/models/article_spec.rb
を実行し、テストを動かす
その後エラーが出ることを確認する
今回の場合は
undefined method build
というようなエラーが出る、これはfactory_botが動いていない証拠なので、以下を追加
rails_helper.rbconfig.include FactoryBot::Syntax::Methodsその後再びrspecを実行
$ rspec spec/models/article_spec.rb
failureexpected #<Article id: nil, title: "", content: "MyText", slug: "MyString", created_at: nil, updated_at: nil> not to be validvalidationに引っかからずにtitleがnilで保存されてしまっていることがわかる
なので、実際にmodelにvalidationを記述していくapp/models/article.rbclass Article < ApplicationRecord validates :title, presence: true end再度テストを実行して通る
そしてこれをcontentとslugも同じように記述していく
models/article_spec.rbit 'should validate the presence of the content' do article = build :article, content: '' expect(article).not_to be_valid expect(article.errors.messages[:content]).to include("can't be blank") end it 'should validate the presence of the slug' do article = build :article, slug: '' expect(article).not_to be_valid expect(article.errors.messages[:slug]).to include("can't be blank") endapp/models/article.rbvalidates :content, presence: true validates :slug, presence: trueテストを実行し全て通る
3 examples, 0 failuresそしてもう一つvalidationをかけておきたいのが、slugが一意かどうか
なので、uniqueに関するテストも追加で記述
models/article_spec.rbit 'should validate uniqueness of slug' do article = create :article invalid_article = build :article, slug: article.slug expect(invalid_article).not_to be_valid end一度articleをcreateで作り再びbuildでarticleを作る。そして二つ目のarticleには一つ目のarticleのslugを指定しているので、二つのarticleのslugは同じものになる。これでテストをする。
ちなみにcreateとbuildの違いはデータベースに保存するかどうかで、全てcreateで完結させることもできる。しかし、createを全てに使ってしまうと、重くなってしまうので、データベースに保存する必要のない(その後直接expectで判定される)ものなどはbuildを使いメモリの消費をできるだけ減らす。テストを実行しエラーが出るのを確認
failureexpected #<Article id: nil, title: "MyString", content: "MyText", slug: "MyString", created_at: nil, updated_at: nil> not to be validそのままuniqueではないのに保存されてしまっているのがわかる
なので、modelを記述していくapp/models/article.rbvalidates :slug, uniqueness: trueその後テストは全てクリアする
これでarticleモデルのテストは終わりarticles#index
次はコントローラーを実装していく、まずはルーティングからテストしていく
articles#index routing
spec/routing/
を作成
その後spec/routing/articles_spec.rb
を作成中身を記述していく
spec/routing/articles_spec.rbrequire 'rails_helper' describe 'article routes' do it 'should route articles index' do expect(get '/articles').to route_to('articles#index') end endこれはrouteが正しく動いているかを確認するテスト
実行するとエラーが出る
No route matches "/articles"
/articles
のルーティングは存在していないというエラーが出る
なのでルーティングを追加するroutes.rbresources :articles, only: [:index]テストを実行するがエラーが出る
failureA route matches "/articles", but references missing controller: ArticlesController
これは/articleというルートは存在しているが、articlescontrollerに当てはまるものが存在していないということなので、実際にコントローラーに記述していく
$ rails g controller articlescontrollerを作成
中身を記述していくapp/controllers/articles_controller.rbdef index; endテストがとおる
ついでにshowアクションも実装していく
spec/routing/articles_spec.rbit 'should route articles show' do expect(get '/articles/1').to route_to('articles#show', id: '1') endroutes.rbresources :articles, only: [:index, :show]articles_controller.rbdef show endそしてテストを実行し、通ることを確認する。
articles#index 実装
次に実際にcontrollerの中身を実装していく
まずはテストから書いていく
spec/controllersというファイルを作りその中に
articles_controller_spec.rb
というファイルを作っていくspec/controllers/articles_controller_spec.rbrequire 'rails_helper' describe ArticlesController do describe '#index' do it 'should return success response' do get :index expect(response).to have_http_status(:ok) end end endこのテストは単純に、get :indexのリクエストを送り、200番が返って来ることを期待するというテスト。:okは200と同じ意味。
そのままテストを実行する。
expected the response to have status code :ok (200) but it was :no_content (204)
というメッセージが出て、テストが失敗する。
このメッセージは200を期待していたが204が返ってきたことを意味する。
204は何も返ってこなかった、ということなので、大体はdeleteやupdateなどのレスポンスで利用される。しかし今回は200が返ってきて欲しいので、controllerを編集していく。app/controllers/articles_controller.rbdef index articles = Article.all render json: articles end内容はシンプルで、データベースから全てのarticleを取り出し、それをrenderで返すという処理をしている。
json形式で返すためにjson:
という書き方をしている
ちなみに、render articles
とそのまま返しても200が返るのでこのテストは成功する。json形式ではないresponseは好ましいとは言えない。後にserializerを使ってresponseを解析していくが、その時にはやはりjsonに変換しておく必要があるので json:はつけ忘れないようにする。それではjson形式を確認するtestを書いていく
spec/controllers/articles_controller_spec.rbit 'should return proper json' do create_list :article, 2 get :index json = JSON.parse(response.body) json_data = json['data'] expect(json_data.length).to eq(2) expect(json_data[0]['attributes']).to eq({ "title" => "My article 1", "content" => "The content of article 1", "slug" => "article-1", }) endいったんこのテストを実行する。説明は必要な時に書いていく。
Validation failed: Slug has already been taken
まずこのエラーが出る。slugはunique出ないとvalidationにかかってしまう。なのでfactory botを編集して一つひとつのarticleをuniqueにする。
spec/factories/articles.rbFactoryBot.define do factory :article do sequence(:title) { |n| "My article #{n}"} sequence(:content) { |n| "The content of article #{n}"} sequence(:slug) { |n| "article-#{n}"} end endこのようにsequenceを使えば、一つ一つのarticleの名前をuniqueにすることができる。
これで実行してみる。
Failure/Error: json_data = json[:data] no implicit conversion of Symbol into Integerこのようなメッセージが出て失敗する。これは[:data]が存在しないことによるエラーで、この[:data]はserializerを使って形式を変換したときのものなので、serializerを使って、jsonの形式を変換していく。
最初にあらかじめ
gem 'active_model_serializers'
という記述をしてgemを入れているので、それを使って、記述していく。まずはserializer用のファイルを作っていく。
$ rails g serializer article title content slugこれにより、
app/serializers/article_serializer.rb
が作成される。そしてその新しく導入したactive_model_serializerを適応させるために新しく記述する。
config/initializers/active_model_serializers.rbActiveModelSerializers.config.adapter = :json_apiこのファイルを作り、記述を追加することで新しく導入したserializerを適応させる。
これにより、defaultで使われていたものを変更することができ、[:data]などで取り出すことができる形式に変換することができる。
それぞれのレスポンスの違いを見ていく。
ActiveModel::Serializer導入前
JSON.parse(response.body) => [{"id"=>1, "title"=>"My article 1", "content"=>"The content of article 1", "slug"=>"article-1", "created_at"=>"2020-05-19T06:22:49.045Z", "updated_at"=>"2020-05-19T06:22:49.045Z"}, {"id"=>2, "title"=>"My article 2", "content"=>"The content of article 2", "slug"=>"article-2", "created_at"=>"2020-05-19T06:22:49.049Z", "updated_at"=>"2020-05-19T06:22:49.049Z"}]ActiveModel::Serializer導入後
JSON.parse(response.body) => {"data"=> [{"id"=>"1", "type"=>"articles", "attributes"=> {"title"=>"My article 1", "content"=>"The content of article 1", "slug"=>"article-1"}}, {"id"=>"2", "type"=>"articles", "attributes"=> {"title"=>"My article 2", "content"=>"The content of article 2", "slug"=>"article-2"}}]}中の構造が変わっていることがわかる。また、serializerで指定していない、created_atやupdated_atの属性は切り捨てられている。
これで再びテストを実行すると成功する。
成功したものの重複した表現が多いので、少しリファクタリングをする。
get :index
だが、これは複数回送ることが多いので、まとめて一回で定義する。describe '#index' do subject { get :index }このように記述することで、まとめて記述できる。
その定義を使うためには定義した場所から下でsubject
と打つだけで使うことができる。
仮に、その下の階層でもう一度定義された場合は、そのあとから定義した方が使われる。それを使い二箇所にsubjectと置き換えることができる。
it 'should return proper json' do articles = create_list :article, 2 subject json = JSON.parse(response.body) json_data = json['data'] articles.each_with_index do |article, index| expect(json_data[index]['attributes']).to eq({ "title" => article.title, "content" => article.content, "slug" => article.slug, }) endそして、この部分はeach_with_indexを使うことで、重複した表現をまとめることができる。
そしてさらに
json = JSON.parse(response.body) json_data = json['data']この二つの記述は繰り返し使うことが多いので、helperメソッドに定義しておく。
spec/support/
を作成し、その下にjson_api_helpers.rb
を作成する。spec/support/json_api_helpers.rbmodule JsonApiHelper def json JSON.parse(response.body) end def json_data json["data"] end endこれを全てのファイルで扱えるようにするために、spec_helper.rbでincludeさせておく。
spec/rails_helper.rbconfig.include JsonApiHelpersそしてsupportを読み込ませるようにしたの記述のコメントアウトを外しておく。
spec/rails_helper.rbDir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }これにより、以前の記述を省略して書ける。
spec/controllers/articles_controller_spec.rbit 'should return proper json' do articles = create_list :article, 2 subject articles.each_with_index do |article, index| expect(json_data[index]['attributes']).to eq({ "title" => article.title, "content" => article.content, "slug" => article.slug, }) endこれで、indexはほぼ完成した。しかし、記事の順番が最新が最後になってしまう。最新は最初に来るように、sortを設定する。
まずは期待するテストから書く。
spec/controllers/articles_controller_spec.rbit 'should return articles in the proper order' do old_article = create :article newer_article = create :article subject expect(json_data.first['id']).to eq(newer_article.id.to_s) expect(json_data.last['id']).to eq(old_article.id.to_s) end以上の記述を追記する。
2度articleをcreateし、古い方と新しい方を作る。
そして、それらがjson_dataの何番目かで、最新が、先に来ているかどうかを判定する。
to_sメソッドを使っているのはserializerを使うとvalueの全てが文字列に変換されるので、to_sメソッドを使って、factorybotで生成したデータは文字列にしなければいけない。idも文字列に変換されているので注意が必要。これでテストを実行する。
rspec spec/controllers/articles_controller_spec.rb
failureexpected: "2" got: "1"
このようなメッセージが出力される。
まだ、sortを何も触っていないので、当たり前だが、最後のarticleは最初に来ている。sortを実装したいのだが、メソッドとしてmodelに記述したいのでmodelのテストをまず先に書く。
メソッド名は.recentとする。spec/models/article_spec.rbdescribe '.recent' do it 'should list recent article first' do old_article = create :article newer_article = create :article expect(described_class.recent).to eq( [ newer_article, old_article ] ) end endこれはcontrollerのテストと似たようなことをしているが、違うのはcontrollerとは分離していて、メソッドを呼び出す処理のみにフォーカスしていること。要するにこのテストのみで完結する。
described_class.method_name
これにより、クラスメソッドを呼び出すことができる。
この場合のdescribed_classはArticleとなるが記述するファイルにより、そこに何が入るかは変わって来る。あと、updateした時に古い記事が最近に来るかどうかもテストしたい。
なので以下を追記する。spec/models/article_spec.rbit 'should list recent article first' do old_article = create :article newer_article = create :article expect(described_class.recent).to eq( [ newer_article, old_article ] ) old_article.update_column :created_at, Time.now expect(described_class.recent).to eq( [ old_article, newer_article ] ) endテストを実行すると
undefined method recent
と出るので、recentを定義していく。これまでrecentをメソッドとして定義するというふうに書いていたが、scopeの方が用途に合っているのでscopeで実装する。
scopeはほとんどの場合、classメソッドと同じように扱われる。長々とテストを書いたが、実装はすぐ終わる。
app/models/article.rbscope :recent, -> { order(created_at: :desc) }一行を追加してscopeを定義する。
そして、テストを実行するmodelのテストは全てがグリーンになる。
rspec spec/models/article_spec.rb
しかし、controllerの方はいくつか失敗する。
rspec spec/controllers/articles_controller_spec.rb
それはrecentを定義しただけで、controllerdで使ってないからなので、実際にcontrollerに記述していく。app/controllers/articles_controller.rbarticles = Article.recentArticle.allをArticle.recentに変更する。
これでもう一度テストを実行する。
rspec spec/controllers/articles_controller_spec.rb
すると、test側でsortできていなくて失敗するので、test側もsortする。
spec/controllers/articles_controller_spec.rbArticle.recent.each_with_index do |article, index| # 14行目このようにarticles.recentではなく、Article.recentと記述したのは、articlesはfactory_botの生成したものなので、実際のArticleのインスタンスではないので、.recentが使えないため。
直接factorybotで作成したものを利用する必要がなくなったので、
articles = create_list :article, 2
この記述は
create_list :article, 2
このようにcreateするだけにしておく。これでテストを実行すれば成功する。
paginationを次は実装する。
paginationとは一ページあたり幾つの記事というふうに指定できるようにするもの。まずはいつものようにテストから書いていく。
spec/controllers/articles_controller_spec.rbit 'should paginate results' do create_list :article, 3 get :index, params: { page: 2, per_page: 1 } expect(json_data.length).to eq 1 expected_article = Article.recent.second.id.to_s expect(json_data.first['id']).to eq(expected_article) endこの行を追加。paramsに新しいparameterを指定している。
pageは何ページ目かを指定していて、per_pageは一ページあたり、幾つのarticleかを指定している。今回の場合は、2ページ目が欲しくて、そのページに記載されているのは1つのarticleということになる。テストを実行する。
failureexpected: 1 got: 3
一つしか返ってこないことを期待したが、全てのarticleが返ってきてしまった。なので、ここからpaginationを実装していく。
この記事の一番初めにkaminariというgemは既に追加している。kaminariはpaginationを簡単に実現することができるgem。
なのでそのgemを使って実装していく。
app/controllers/articles_controller.rbarticles = Article.recent. page(params[:page]). per(params[:per_page])recentに続けて、追記する。このように、orマッパーのように、.pageや.perを使うことができる。そしてその引数にparamsで送られてきた値を挿入すれば良い。
これで、responseを絞り込むことができ、一度のresponseによって返すデータ量を指定することができる。
これでもう一度テストを実行すれば成功する。
いったんここで予定していたpart1は終わり。indexの実装は完了。
pert2は随時投稿予定。お疲れ様でした。
- 投稿日:2020-05-20T00:50:08+09:00
ターミナルとは?-絶対パス&相対パス-
ターミナルとは
ターミナルとは、コマンドラインの一つで、PCに命令を与えるためのツールです。記述したコードのファイルを実行するのに必要となります。
コマンドラインとは、コマンドラインインターフェース(CLI)の略で、PC対してキーボードからコマンドという文字を打ち込んで操作を行う方法です。
これとは対照に、グラフィックを用いてPCの操作を行う方法をグラフィカルユーザーインターフェース(GUI)と言います。
コマンド
ターミナルでよく使うコマンドを紹介します。
cd
コマンド
デイレクトりを移動するコマンドです。
ls
コマンド
現在いるディレクトリの直下にあるファイルやフォルダを表示します。
ディレクトリとは
- ディレクトリとはファイルの入れ物のようなものでフォルダを指します。 ディレクトリのは階層構造になっており、 その一番上の階層のディレクトリのことをルートディレクトリと言います。 自分が現在いるディレクトリのことをカレントディレクトリと言います。 また、ターミナルを開いた時に表示されるディレクトリのことをホームディレクトリと言います。Macでのデフォルトのホームディレクトリは/Users/ユーザー名です。
話戻ります。
cd
コマンドを使ってディレクトリを行き来できるわけですが、cd
コマンドに続けてパスを入力することによって目的のディレクトリに辿り着くことができます。そのパスに記述の仕方には2通りあります。
・絶対パス
・相対パスです絶対パス
- 絶対パスとはルートディレクトリから記述するパスのことです。
/
から記述するのが決まりです。ターミナル.$ cd / ###これでルートディレクトリに移動できますMacのデフォルトのルートディレクトリはMacintosh HDだと思いますので
cd /
でMacintosh HDに移動します。
ls
コマンドで直下のディレクトリを確認すればパスがわからなくても確認して移動できます。相対パス
相対パスは先頭に
/
をつけません。
現在いるディレクトリの直下にあるディレクトリから記述することによって移動することができます。この記事を読んでいただきありがとうございました。