- 投稿日:2020-02-05T22:31:57+09:00
Rubyの基礎まとめ
基礎文法
繰り返し(while文)
while 条件 do 処理 if 中断条件 break end end各桁の和
a = 11 puts a.to_s.split('').map{|x| x.to_i}.inject(:+) #=> 2条件分岐(書き換えパターン)
pattern(1)
s = "hoge" if s==hoge puts "YES" else puts "NO" end #=>YESpattern(2)
s = "hoge" puts s == hoge ? "YES":"NO" #=>YESpattern(3)
s = "hoge" case s when "hoge" then puts "YES" when "no_hoge" then puts "NO" #=>YES.gsub(パターンマッチング)
string = "ruby ruby ruby" puts string.gsub(/ruby/, 'python') #=> python python python配列の操作
配列の割り算
a = [2, 4, 6] b = a.map(|x| x/2) puts b #=> [1, 2, 3]配列の合計
a = [1, 2, 3] puts a.inject(:+) #=> 6奇数or偶数番目要素の取得
ary = ('a'..'j').to_a #=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] # 偶数番目 ary.select.with_index { |_, i| i.even? } #=> ["a", "c", "e", "g", "i"] # 奇数番目 ary.select.with_index { |_, i| i.odd? } #=> ["b", "d", "f", "h", "j"]並べ替え
ary = [2, 4, 1, 5, 3] # 昇順 ary.sort #=> [1, 2, 3, 4, 5] # 降順 ary.sort.reverse #=> [5, 4, 3, 2, 1]重複要素を統一
ary = [1, 2, 2, 3, 3] ary.uniq #=> [1, 2, 3]配列→文字列
ary = [1, 2, 3, 4, 5] ary.join(' ') #=> '1 2 3 4 5'要素の追加
ary = ['a', 'b', 'c'] ary << 'd' #=> ['a', 'b', 'c', 'd']テクニック
メソッドを定義
def method(n) n += 1 end method(3) #=> 4
- 投稿日:2020-02-05T22:31:57+09:00
Rubyの基礎まとめ[メモ]
コーディングテスト用のメモ。
基礎文法
繰り返し
- for
for i in 1..3 do puts i end※配列でも可
for num in [1, 2, 3] do puts num end
- each
[1, 2, 3, 4].each do |num| p num end
- while
num = 0 #初期値 while num <= 12 do #条件 p num #処理 num += 3 #値の更新 end
- times
3.times do |num| p num end各桁の和
a = 11 puts a.to_s.split('').map{|x| x.to_i}.inject(:+) #=> 2a = 11 puts a.to_s.chars.map{|x| x.to_i}.sum ### 奇数or偶数番目要素の取得a = 11 puts a.digits.sum条件分岐(書き換えパターン)
- pattern(1)
s = "hoge" if s==hoge puts "YES" else puts "NO" end #=>YES
- pattern(2)
s = "hoge" puts s == hoge ? "YES":"NO" #=>YES
- pattern(3)
s = "hoge" case s when "hoge" then puts "YES" when "no_hoge" then puts "NO" #=>YES.gsub(パターンマッチング)
string = "ruby ruby ruby" puts string.gsub(/ruby/, 'python') #=> python python python数値の切り捨て
1.4.floor # 1 1.5.floor # 1 -1.4.floor # -2 -1.5.floor # -2数値の切り上げ
1.4.ceil # 2 1.5.ceil # 2 -1.4.ceil # -1 -1.5.ceil # -1配列の操作
配列の割り算
a = [2, 4, 6] b = a.map(|x| x/2) puts b #=> [1, 2, 3]配列の合計
a = [1, 2, 3] puts a.inject(:+) #=> 6ary = ('a'..'j').to_a #=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"] # 偶数番目 ary.select.with_index { |_, i| i.even? } #=> ["a", "c", "e", "g", "i"] # 奇数番目 ary.select.with_index { |_, i| i.odd? } #=> ["b", "d", "f", "h", "j"]並べ替え
ary = [2, 4, 1, 5, 3] # 昇順 ary.sort #=> [1, 2, 3, 4, 5] # 降順 ary.sort.reverse #=> [5, 4, 3, 2, 1]重複要素を統一
ary = [1, 2, 2, 3, 3] ary.uniq #=> [1, 2, 3]配列→文字列
ary = [1, 2, 3, 4, 5] ary.join(' ') #=> '1 2 3 4 5'要素の追加
ary = ['a', 'b', 'c'] ary << 'd' #=> ['a', 'b', 'c', 'd']最大値最小値
ary = [1, 25, 50, 75, 100] ary.minmax #=> [1, 100]
- 投稿日:2020-02-05T22:14:03+09:00
Leetcode: Serialize And Deserialize Binary Tree
require 'minitest/autorun' require 'pry' class SerializeAndDeserializeBinaryTree < Minitest::Test def test_run expected = [1, 2, 3, nil, nil, 4, 5] tree = TreeNode.new(1) tree.left = TreeNode.new(2) tree.right = TreeNode.new(3) tree.right.left = TreeNode.new(4) tree.right.right = TreeNode.new(5) serialized_tree = "12XX34XX5XX" assert_equal(serialized_tree, serialize(tree)) deserialize(serialized_tree) end def serialize(root) return "X" if root.nil? root.val left = serialize(root.left) right = serialize(root.right) root.val.to_s + left.to_s + right.to_s end def deserialize(serialized_tree) nodes_left = serialized_tree.split('') deserialize_helpder(nodes_left) end def deserialize_helpder(nodes_left) value_for_node = nodes_left.shift return nil if value_for_node == "X" new_node = TreeNode.new(value_for_node) new_node.left = deserialize_helpder(nodes_left) new_node.right = deserialize_helpder(nodes_left) new_node end end class TreeNode attr_accessor :val, :left, :right def initialize(val) @val = val @left, @right = nil, nil end end
- 投稿日:2020-02-05T22:06:25+09:00
【Rails】Docker環境下でbinding.pryを使えないとき確認すべきポイント4つ
はじめに
Docker環境下でこんなシーンに遭遇したことはありませんか?
「Railsでバグ発生!デバッグしなきゃ!よーし、
binding.pry
しよう!」↓
「うわ、コンソール出ない!!!これじゃ何も出来ない!!!」
こんなやるせない気持ちになった方のために、自分がこれまで詰まった箇所とその解決法を残しておこうと思います。
環境
OS: macOS Catalina 10.15.3 Ruby: 2.6.5 Rails: 6.0.2.1 Docker: 19.03.5 docker-compose: 1.24.11.
binding.pry
のコンソールが出ない
docker-compose up
などでrails serverを立ち上げていて、binding.pry
を入力した箇所で動作が止まっているのに、「コンソールが出ない!どうしよう!」
という状態を想定しています。
解決法
$docker container lsでRailsアプリのあるコンテナ名を確認します。
※ここではrails_app_web_1
とします。
↓$ docker attach rails_app_web_1
docker attach
で該当コンテナにattach
します。
これで、binding.pry
したときにコンソールが表示されます。※もし反応がなければ、
Enter
押下するとコンソールが出るかもです。2.せっかくコンソールが出たのに終了の仕方がわからない
多くの場合は
binding.pry
を一回だけして終了、ということはなく、続けて何度かデバッグ作業を行うかと思います。下手に
Ctrl + C
で終了してしまうと、コンテナが停止してしまうので、立ち上げ直しになってしまってかなり面倒です。解決法
pry
の画面から終了するならcontinue
と入力してrails serverを通常動作に戻し、docker attach
を維持するのが便利です。この状態であれば、次に
binding.pry
したときもスムーズにデバッグ作業が可能です。※デバッグが終了してコンテナから抜けたい場合、
Ctrl + P + Q
(Macの場合)でコンテナを停止せずに抜けられます。3.pryで日本語入力出来ない
少し外れますが、そもそも
rails console
でpry
を使っていて、日本語が入力できないパターンもあるかもしれません。解決法
DockerfileENV LANG C.UTF-8
Dockerfile
に上記のように追記すれば解決できます。これを忘れるとpryで日本語入力が効きません。
4.コンテナがすぐ落ちる、コンソールに文字が入力できない
2020/2/6追記
解決法
docker-compose.ymlweb: tty: true stdin_open: true上記が
docker-compose.yml
のRailsに関係する箇所に書かれているかどうか確認します。(今回はweb
としています。)
tty: true
ポート待受などをしていないコンテナを起動させ続けるオプションstdin_open: true
標準入力出来るようになるオプションおわりに
最後まで読んで頂きありがとうございました
どなたかの参考になれば幸いです
- 投稿日:2020-02-05T22:06:25+09:00
【Rails】Docker環境下でbinding.pryを使えないとき確認すべきポイント3つ
はじめに
Docker環境下でこんなシーンに遭遇したことはありませんか?
「Railsでバグ発生!デバッグしなきゃ!よーし、
binding.pry
しよう!」↓
「うわ、コンソール出ない!!!これじゃ何も出来ない!!!」
こんなやるせない気持ちになった方のために、自分がこれまで詰まった箇所とその解決法を残しておこうと思います。
環境
OS: macOS Catalina 10.15.3 Ruby: 2.6.5 Rails: 6.0.2.1 Docker: 19.03.5 docker-compose: 1.24.11.
binding.pry
のコンソールが出ない
docker-compose up
などでrails serverを立ち上げていて、binding.pry
を入力した箇所で動作が止まっているのに、「コンソールが出ない!どうしよう!」
という状態を想定しています。
解決法
$docker container lsでRailsアプリのあるコンテナ名を確認します。
※ここではrails_app_web_1
とします。
↓$ docker attach rails_app_web_1
docker attach
で該当コンテナにattach
します。
これで、binding.pry
したときにコンソールが表示されます。※もし反応がなければ、
Enter
押下するとコンソールが出るかもです。2.せっかくコンソールが出たのに終了の仕方がわからない
多くの場合は
binding.pry
を一回だけして終了、ということはなく、続けて何度かデバッグ作業を行うかと思います。下手に
Ctrl + C
で終了してしまうと、コンテナが停止してしまうので、立ち上げ直しになってしまってかなり面倒です。解決法
pry
の画面から終了するならcontinue
と入力してrails serverを通常動作に戻し、docker attach
を維持するのが便利です。この状態であれば、次に
binding.pry
したときもスムーズにデバッグ作業が可能です。※デバッグが終了してコンテナから抜けたい場合、
Ctrl + P + Q
(Macの場合)でコンテナを停止せずに抜けられます。3.pryで日本語入力出来ない
少し外れますが、そもそも
rails console
でpry
を使っていて、日本語が入力できないパターンもあるかもしれません。解決法
DockerfileENV LANG C.UTF-8
Dockerfile
に上記のように追記すれば解決できます。これを忘れるとpryで日本語入力が効きません。
おわりに
最後まで読んで頂きありがとうございました
どなたかの参考になれば幸いです
- 投稿日:2020-02-05T21:30:57+09:00
Rails、hamlでFontAwesomeを使う
今日、だいぶつまいたので、メモ程度に書いておきます。
①ハッシュロケットをシンボル型に直す。
ハッシュロケットは時代遅れになっているようで、 取り残されないようにシンボル型に書き換えます。
基本形
ハッシュロケット
:rocket => "rocket"シンボル型
hash: "hash"書き換えてみる
ハッシュロケット
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/シンボル型
%meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type"}/②link_toでFontAwesomeを表示
a(href)を
= link_to "#" doに変更= link_to new_group_path do
= fa_icon 'edit'
- 投稿日:2020-02-05T20:07:40+09:00
iOSエンジニアがSign in with Appleをサーバーサイドで実装したときに行き詰りを感じたところとか
Sign in with Apple 実装のデッドラインは4月 ですね☺️
Web/iOS/Androidなど、複数プラットフォームをサポートするためWebViewでログイン機能を実装しているサービスや、外部サービスAPI利用の際に独自のAPIを噛ませているなど、サーバーサイドでSign in with Appleに対応するケースはあるかと思います。iOSエンジニアであるわたしがサーバーサイドで実装した際に、わかりにくいな〜?と感じたポイントについて記載します。
自分のスキルセットとして、認証・認可、Webの知識はほぼ0でした?
実装したソースコードをの抜粋を載せていますが、言語はrubyです。やること
今回ご説明する Sign in with Apple の全体のざっくりフローは、
- Apple Developer サイトでの設定
- Apple ID サインイン画面表示(Authorizeエンドポイント)、認可コード受取り
- 認可コードを使い、AppleのユーザーIDを取得(Tokenエンドポイント)
です。
取得したいデータがemailとnameのみの場合 → ステップ2まで
ユーザーIDを取得したい場合 → ステップ3まで
実装する必要があります。参考までに前知識
Auth関連
まず最初に、OAuth周りの知識があると理解が早いと思います。
この方のqiitaが非常に参考になりました。(ありがとうございました。)Appleガイドライン関連
Apple Developer サイトでのもろもろ
ではさっそくApple Developerサイトを開きます。
Servise ID作成からドメイン認証
- 対象の App ID を作成し、Capabilities として Sign in with Apple を追加する
- Apple Developer の Certificates, Identifiers & Profiles から Service ID を追加する
- Primary App ID を 1 のAppに指定する
- Domainsに認可コードのリダイレクト先のドメインと Return URLs を指定する
- Domainの検証ファイル
apple-developer-domain-association.txt
をダウンロードする- Domainの検証ファイルをWebサイトの
https://.../.well-known/
ディレクトリに置くService ID と Return URLs はAuthorize、Tokenエンドポイントで利用します。
ドメイン検証に失敗したら
https://{yourdomain}/.well-known/apple-developer-domain-association.txt
にアクセスできるか- 検証ファイルは有効期限内か(7日間のみ有効)
- 検証ファイルのダウンロードは何回でも可能なので、常に最新のファイルを使う
- サーバーがTLSの条件を満たしているか
を確認する。
明文化されていなさそうですが、Appleさんからサポートを受けた開発者がフォーラムに書き込んでいます。
https://forums.developer.apple.com/thread/122124そのほか、ネットワーク構成などに問題がないか等、公式の Troubleshooting Domain Verification を読んでみるとよいです。
Key をダウンロード
Tokenエンドポイントのリクエストパラメータの JSON Web Token 作成に使います。
当然ですが、センシティブなファイルなので暗号化などよしなに行い、サーバーのどこか安全なところに置いておく。
今回はわたしは、.txt
に変換して使いました。公式ドキュメントは Create a Sign in with Apple private key
Apple IDサインインページ表示
Appleサインインボタン
公式ドキュメント Incorporating Sign in with Apple into Other Platforms の Add a Custom “Sign in with Apple” Button にダウンロードURLやサイズ、カラー指定のクエリパラメータの説明があります。
Human Interface Guidelines にしたがって正しくつかいましょう。Authorizeエンドポイント
Appleの認証ページURLのクエリパラメータ、レスポンスについては Incorporating Sign in with Apple into Other Platforms に記載があります。
大事なのは、
client_id = Service IDで指定した文字列 redirect_uri = Service IDに設定したリダイレクトURIです。
response_type
=id_token
にする場合、response_mode
はfragment
またはform_post
である必要がある- 氏名やメールアドレスを取得したい(
scope
を指定する)場合、response_mode
=form_post
にする必要があるなど、諸々の制約があります。
サインインページが表示されると、ユーザーによるApple IDの認証を受け付けます。
認証が完了すると、指定したリダイレクトURIにリダイレクトが行われます。
受け取れる値は認可コードと、scope
に指定がある場合初回1度のみユーザーID以外の個人情報(メールアドレス、氏名など)が受け取れます。サインインページがうまく表示されない
invalid_uri や invalid_client が表示されたら、焦らず
client_id
とredirect_uri
を今一度確認しましょう。
開発・本番など環境別でドメインを分けている場合、redirect_uri
が変わると思いますので要チェックです。
Apple Developer 上で作成した Service ID の Returns URLs に該当のリダイレクト先が登録されているか確認しましょう。アクセストークン・ユーザーID取得
Tokenエンドポイント
Appleのトークン発行エンドポイントにリクエストを行い、トークンの発行を行います。
パラメータ、レスポンスについては Generate and validate tokens に記載があります。
パラメータは Authorization のものを使います。
client_secret
の値のJSON Web Token(JWT)作成Apple Developers で発行したKeyを用いて、リクエストパラメータ
client_secret
に付与するJWTの作成を行います。
JWTとは、header
とpayload
から成るJSON構造をエンコードし電子署名をつけたものです。
デコード&エンコードしたJWTの検証は、 https://jwt.io/ がおすすめです。rubyでのJWTのエンコード&署名の例は以下です。
jwt/ruby-jwt というGemを利用しています。
kid
やiss
、sub
などは暗号化しておくと良いでしょう。require 'jwt' require 'openssl' # 署名用の鍵を取得 def pem file = "path/to/key.txt" # Apple Developerで発行したKeyファイル # key.txtを読み出し、暗号鍵を生成する OpenSSL::PKey::EC.new(File.read(file)) end # 署名付きIDトークンを取得 def id_token header = { 'kid' => 'Apple Developerで発行したKey ID' } claim = { 'iss' => 'Apple DeveloperでのTeam ID', 'iat' => Time.now.to_i, 'exp' => Time.now.to_i + 15777000, 'aud' => 'https://appleid.apple.com', 'sub' => 'Apple Developerで発行したService ID' } JWT.encode( claim, pem, 'ES256', header ) endレスポンスの検証
レスポンスに含まれるIDトークンの検証には、Appleの公開鍵取得が必要です。
エンドポイントについては Fetch Apple's public key for verifying token signature に記載があります。jwt/ruby-jwt を用いた検証の実装例です。
# IDトークンをデコードし署名を検証する # public_keysに渡すのは、https://appleid.apple.com/auth/keys の "keys"の値です。 def decode(id_token, public_keys) JWT.decode( id_token, nil, true, { algorithms: 'E256', jwks: { keys: public_keys } } ) endIDなどのユーザー情報
sub
の値がユーザーの一意の識別子(ユーザーID)です。
そのほかのレスポンスのIDトークンに含まれる情報については Retrieve the User’s Information from Apple ID Servers
に記載があります。
あとは取得したデータをもとに自サービスでのユーザー認証を完了させましょう。おしまい
書くと簡単ですが実際はたいへんだったのでiOS boys and girlsの皆様はできる限りネイティブでやりましょう
Thanks to
https://qiita.com/kiwi26/items/5b8cc53ed8d10a403f00
https://developer.okta.com/blog/2019/06/04/what-the-heck-is-sign-in-with-apple以上です???
ありがとうございました?
- 投稿日:2020-02-05T19:52:16+09:00
LINE の スマートスピーカー Clova Friends と Clova Desk の HTTP リクエストを調べる
概要
- LINE Clova から Custom Extension (スキル) 起動時に送信される HTTP リクエストを調べる
- Ruby + Heroku による調査用 Web サーバ (Extension サーバー) を立てて HTTP リクエストの内容を調べる
Clova とは
LINE が提供しているスマートスピーカー。
ClovaはLINEが開発したAIアシスタントです。
話しかけるだけでLINEの送受信や家電の操作など、あなたの様々なリクエストに応えます。Clova の HTTP リクエスト
Clova は CEK (Clova Extensions Kit) を介して HTTPS リクエストを Extension サーバーに送信している。
CEKの概要 - Clova Developer Center β
CEKは、Clova Extension(以下、Extension)を開発および配布する際に必要なツールとインターフェースを提供するプラットフォームです。ClovaプラットフォームとExtension間のデータの送受信をサポートします。Extensionは、音楽、ショッピングなどの外部のサービス(サードパーティサービス)、または家庭のIoTデバイスの制御など、Clovaの機能を拡張して、ユーザーに様々な体験を提供するWebアプリケーションです。
CEK APIのリファレンス - Clova Developer Center β
・HTTP/1.1バージョンでHTTP通信し、POSTメソッドを使用します。
・Hostとリクエストパスは、Extensionの開発者があらかじめ定義したURIに設定されます。
・リクエストボディのデータはJSON形式で、UTF-8エンコーディングを使用します。
・SignatureCEKフィールドとRSA公開鍵を使用して、Clovaから送信されたリクエストかどうかを検証することができます。Extension サーバーとは
Clova から送信される HTTPS リクエストを受信して応答を返す Web サーバ。
Custom Extensionを作成する - Clova Developer Center β
Clova Developer Centerに登録するExtensionサーバーです。このサーバーは、Clovaがユーザーの音声入力を解析した結果や、デフォルトで提供されるインテントを渡された際に、そのインテントを処理して適切な応答を返す必要があります。
今回の調査対象
- Clova Friends (画面なしモデル)
- Clova Desk (画面ありモデル)
- Clova Developer Center テストツール (Web ブラウザ上で使用できるテストツール)
Clova Friends とは
内蔵バッテリーを搭載している Clova。
画面は付いていない。Clova Friends | LINE Clova公式サイト
コンパクトなサイズで、見ためもPOPな、スマートスピーカーです。
バッテリーを内蔵しているので、音楽再生、LINE通話、占いなどの便利な機能を、お出かけ先などでご利用いただくことができます。Clova Desk とは
内蔵バッテリー、画面、赤外線送受信機を搭載している Clova。
天気やレシピ、歌詞も画面表示でもっと楽しめる
7インチの画面で、子供と一緒の画面を見ながら、お気に入りの曲を聴いたり、料理を楽しむことができます。IRと赤外線リモコンを搭載。リモコンを探すことなく、テレビやエアコンを操作することができます。
Clova Developer Center テストツールとは
実際に Custom Extension (スキル) を配布する前にテストすることができるツール。
Extensionをテストする - Clova Developer Center β
テスト画面では、次の2種類のテストを実行できます。
・対話モデルテストモード:任意のインテントのサンプル発話を入力して、インテントやスロットの解析結果やExtensionへのリクエストメッセージを確認できます。
・シナリオテストモード:LaunchRequestからSessionEndedRequestまでの一連のシナリオをテストできます。HTTP リクエストを受ける調査用 Web サーバ (Extension サーバー)
- HTTP リクエスト確認用 Ruby スクリプト を Heroku に設置する
- Clova 実機等で Custom Extension (スキル) を起動して、その際に送信される HTTP リクエストを Web サーバ (Extension サーバー) で受信してログに出力する
HTTP リクエスト確認用 Ruby スクリプト
require 'socket' # 標準出力を同期モードに設定 $stdout.sync = true # 接続を受け付けるポート番号を決定 # 環境変数 PORT が設定されているならそれを設定 port = 8000 port = ENV['PORT'].to_i if ENV['PORT'] # サーバー接続をオープン server = TCPServer.open(port) # HTTP リクエストを待ち続ける loop do begin # TCPSocket オブジェクトを取得 socket = server.accept # 受け付けた日時を出力 puts "[info]#{Time.new}" # HTTP リクエスト開始行を出力 if not req_start_line = socket.gets puts '[info]req_start_line is nil' next end puts "#{req_start_line}" # HTTP リクエストヘッダーを1行ずつ出力 while req_header = socket.gets.chomp puts "#{req_header}" break if req_header == '' # ヘッダー終了 # Content-Length ヘッダーがあれば値を変数にセット h = req_header.split(':') content_length = h[1].strip.to_i if h[0].strip.downcase == 'content-length' end # Content-Length がある場合はボディを出力 if content_length != nil puts socket.read(content_length) end # HTTP レスポンスを返す # 本文データ body = <<-'__EOS__' { "version": "1.0", "sessionAttributes": {}, "response": { "outputSpeech": { "type": "SimpleSpeech", "values": { "type": "PlainText", "lang": "ja", "value": "こんにちは、クローバ?" } }, "card": {}, "directives": [], "shouldEndSession": true } } __EOS__ # ステータス行 socket.write "HTTP/1.1 200 OK\r\n" # ヘッダー socket.write "Server: #{RUBY_DESCRIPTION}\r\n" socket.write "Content-Type: application/json\r\n" socket.write "Content-Length: #{body.bytesize}\r\n" socket.write "Connection: close\r\n" # 空行 socket.write "\r\n" # 本文 socket.write body rescue => e puts e.full_message ensure # HTTP 接続を閉じる puts '[info]close this socket' socket.close end end server.close調査結果
留意点
- ユーザー毎等で一意になるような値などは文字「X」による伏せ字に置き換えておく
- Heroku 経由なので一部の HTTP ヘッダ等が加工・追加されている可能性がある
- Signaturecek ヘッダ: Clova から送信されたかどうかを検証するためのヘッダ
- X-B3 ではじまるヘッダ: LINE 側でつけていると思われるヘッダ (分散トレーシングシステム Zipkin で使われるヘッダ)
- Heroku のリバースプロキシサーバ Vegur がつけていると思われるヘッダ: X-Request-Id, X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Port, Via, Connect-Time, X-Request-Start, Total-Route-Time
Clova Friends (ディスプレイ無しモデル) の HTTP リクエスト
HTTP リクエスト全体
POST /hello-clova/ HTTP/1.1 Host: example.herokuapp.com Connection: close User-Agent: Go-http-client/1.1 Content-Type: application/json; charset=utf-8 Signaturecek: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X-B3-Flags: 0 X-B3-Parentspanid: XXXXXXXXXXXXXXXX X-B3-Sampled: 1 X-B3-Spanid: XXXXXXXXXXXXXXXX X-B3-Traceid: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X-Clova-Experiments: 1=A&10=A&2=B&3=A&4=B&5=A&6=C&7=B&8=A&9=B X-Clova-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Accept-Encoding: gzip X-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX X-Forwarded-For: XXX.XXX.XXX.XXX X-Forwarded-Proto: https X-Forwarded-Port: 443 Via: 1.1 vegur Connect-Time: 0 X-Request-Start: 1580853072763 Total-Route-Time: 0 Content-Length: 744 {"version":"1.0","session":{"new":true,"sessionAttributes":{},"sessionId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","user":{"userId":"UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}},"context":{"System":{"application":{"applicationId":"info.maigo.lab.helloclova"},"device":{"deviceId":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","display":{"size":"none","contentLayer":{"width":0,"height":0}}},"user":{"userId":"UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}}},"request":{"type":"LaunchRequest","requestId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","timestamp":"2020-02-04T21:51:12Z","locale":"ja-JP","extensionId":"info.maigo.lab.helloclova","intent":{"intent":"","name":"","slots":{}},"event":{"namespace":"","name":"","payload":null}}}HTTP メッセージボディの JSON を整形したもの
{ "version": "1.0", "session": { "new": true, "sessionAttributes": {}, "sessionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "user": { "userId": "UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" } }, "context": { "System": { "application": { "applicationId": "info.maigo.lab.helloclova" }, "device": { "deviceId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "display": { "size": "none", "contentLayer": { "width": 0, "height": 0 } } }, "user": { "userId": "UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" } } }, "request": { "type": "LaunchRequest", "requestId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "timestamp": "2020-02-04T21:51:12Z", "locale": "ja-JP", "extensionId": "info.maigo.lab.helloclova", "intent": { "intent": "", "name": "", "slots": {} }, "event": { "namespace": "", "name": "", "payload": null } } }Clova Desk (ディスプレイ付きモデル) の HTTP リクエスト
HTTP リクエスト全体
POST /hello-clova/ HTTP/1.1 Host: example.herokuapp.com Connection: close User-Agent: Go-http-client/1.1 Content-Type: application/json; charset=utf-8 Signaturecek: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X-B3-Flags: 0 X-B3-Parentspanid: XXXXXXXXXXXXXXXX X-B3-Sampled: 1 X-B3-Spanid: XXXXXXXXXXXXXXXX X-B3-Traceid: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX X-Clova-Experiments: 1=A&10=A&2=B&3=A&4=B&5=A&6=C&7=B&8=A&9=B X-Clova-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX Accept-Encoding: gzip X-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX X-Forwarded-For: XXX.XXX.XXX.XXX X-Forwarded-Proto: https X-Forwarded-Port: 443 Via: 1.1 vegur Connect-Time: 0 X-Request-Start: 1580853042398 Total-Route-Time: 0 Content-Length: 787 {"version":"1.0","session":{"new":true,"sessionAttributes":{},"sessionId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","user":{"userId":"UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}},"context":{"System":{"application":{"applicationId":"info.maigo.lab.helloclova"},"device":{"deviceId":"XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","display":{"size":"custom","dpi":160,"orientation":"landscape","contentLayer":{"width":1024,"height":552}}},"user":{"userId":"UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"}}},"request":{"type":"LaunchRequest","requestId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","timestamp":"2020-02-04T21:50:41Z","locale":"ja-JP","extensionId":"info.maigo.lab.helloclova","intent":{"intent":"","name":"","slots":{}},"event":{"namespace":"","name":"","payload":null}}}HTTP メッセージボディの JSON を整形したもの
{ "version": "1.0", "session": { "new": true, "sessionAttributes": {}, "sessionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "user": { "userId": "UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" } }, "context": { "System": { "application": { "applicationId": "info.maigo.lab.helloclova" }, "device": { "deviceId": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "display": { "size": "custom", "dpi": 160, "orientation": "landscape", "contentLayer": { "width": 1024, "height": 552 } } }, "user": { "userId": "UXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" } } }, "request": { "type": "LaunchRequest", "requestId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "timestamp": "2020-02-04T21:50:41Z", "locale": "ja-JP", "extensionId": "info.maigo.lab.helloclova", "intent": { "intent": "", "name": "", "slots": {} }, "event": { "namespace": "", "name": "", "payload": null } } }Clova Developer Center テストツール の HTTP リクエスト
HTTP リクエスト全体
POST /hello-clova/ HTTP/1.1 Host: example.herokuapp.com Connection: close Signaturecek: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX Content-Type: application/json;charset=UTF-8 User-Agent: Apache-HttpClient/4.5.6 (Java/1.8.0_202) Accept-Encoding: gzip,deflate X-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX X-Forwarded-For: XXX.XXX.XXX.XXX X-Forwarded-Proto: https X-Forwarded-Port: 443 Via: 1.1 vegur Connect-Time: 0 X-Request-Start: 1580853979149 Total-Route-Time: 0 Content-Length: 594 {"version":"1.0","session":{"sessionId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","sessionAttributes":{},"user":{"userId":"XXXXXXXXXXXXXXXXXXXXXX","accessToken":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"},"new":true},"context":{"System":{"application":{"applicationId":"info.maigo.lab.helloclova"},"user":{"userId":"XXXXXXXXXXXXXXXXXXXXXX","accessToken":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"},"device":{"deviceId":"XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX","display":{"size":"l100","orientation":"landscape","dpi":96,"contentLayer":{"width":640,"height":360}}}}},"request":{"type":"LaunchRequest"}}HTTP メッセージボディの JSON を整形したもの
{ "version": "1.0", "session": { "sessionId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "sessionAttributes": {}, "user": { "userId": "XXXXXXXXXXXXXXXXXXXXXX", "accessToken": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" }, "new": true }, "context": { "System": { "application": { "applicationId": "info.maigo.lab.helloclova" }, "user": { "userId": "XXXXXXXXXXXXXXXXXXXXXX", "accessToken": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" }, "device": { "deviceId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "display": { "size": "l100", "orientation": "landscape", "dpi": 96, "contentLayer": { "width": 640, "height": 360 } } } } }, "request": { "type": "LaunchRequest" } }参考資料
- 投稿日:2020-02-05T19:45:23+09:00
Shift_jis(CP932) -> UTF-8へのcsvの変換機能
Shift_jis(CP932) -> UTF-8へのcsvの変換機能
(変換機能というか例外処理ですね。)
今回はrubyで開発をしたので、rubyの書き方で説明致します。
Shift_jisで書かれたcsvをUTF-8に変換する際、エラーが起きて変換出来ない件について。課題
エラー文
"\xFB\xFC" from Shift_JIS to UTF-8
コード
CSV.foreach({ファイル名}, encoding: "Shift_JIS:UTF-8", headers: true) do |row|Shift_JIS→UTF-8に変換してcsv出力したいのに、、エラーが。。
どう対処すれば、、。何が起きている??
このエラー文は、
変換出来ない文字が含まれているよー!だから変換出来ません!
と教えてくれています。変換出来ない文字とは、
Shift_JISの範囲外の文字(旧字体)
の事!
→Shift_JISで書かれたcsvに、Shift_JISが認識出来ない文字が含まれてる。Shift_JISが認識出来ない文字(Shift_JISにはない文字):
「髙(はしご高), ①, ㈱, 﨑, ✖︎」などの旧字体!csvにこの旧字体が含まれている事によって、エラーが起きてしまいます。
解決策
解決策は何通りかありますが、(1)一番確実に変換出来るものと、(2)簡単に変換出来るものをご紹介します!
(1) 強制変換
初めから、UTF-8としてcsvを読み込ませる!
CSV.parse(NKF::nkf('-w',File.read({ファイル名})), headers: true) do |row|
Shift_JIS → UTF-8 ( Shift_JISで書かれたcsv を UTF-8に変換する!)
のではなく、
Shift_JISで書かれたcsvを初めからUTF-8として変換する
初めからUTF-8として変換する事によって、文字化けも起こる事なく、エラーも回避する事が出来ます!(2) CP932を使う
単純です
CSV.foreach({ファイル名}, encoding: "CP932:UTF-8", headers: true) do |row|
Shift_JIS → UTF-8
CP932 → UTF-8
(ただ、Shift_JISをCP932に変えただけ)こちらでも、文字化け、エラーを防ぐ事が出来ます!
が、
これは、macでcsvファイルを作ったと想定した場合です。
Windowsでcsvファイルを作った場合を想定していません。。Windowsでcsvファイルを作った場合:
csvファイルの文字コードがWindows-31J
の場合があります。
その場合は、もちろん、CP932 → UTF-8 と変換しようとしたら、、Windows-31Jで書かれているファイルなのでCP932なんて知りませんよー。と怒られる可能性があります。
mac,Windowsどちらも対応したいのであれば、(1) 強制変換 がオススメ!!
で、確実!!ぜひ、お試しください^^
- 投稿日:2020-02-05T19:45:23+09:00
Shift_jis(CP932) -> UTF-8変換の例外処理
Shift_jis(CP932) -> UTF-8へのcsvの変換機能
今回はrubyで開発をしたので、rubyの書き方で説明致します。
Shift_jisで書かれたcsvをUTF-8に変換する機能を実装した際、エラーが起きてしまい変換出来ない件について。課題
エラー文
"\xFB\xFC" from Shift_JIS to UTF-8
コード
CSV.foreach({ファイル名}, encoding: "Shift_JIS:UTF-8", headers: true) do |row|Shift_JIS→UTF-8に変換してcsv出力したいのに、、エラーが。。
何を言っているんだ?
どーいうエラー?
どう対処すれば、、。何が起きている??
このエラー文は、
変換出来ない文字が含まれているよー!だから変換出来ません!
と教えてくれています。変換出来ない文字とは、
Shift_JISの範囲外の文字(旧字体)
の事!
→Shift_JISで書かれたcsvに、Shift_JISが認識出来ない文字が含まれてる。Shift_JISが認識出来ない文字(Shift_JISにはない文字):
「髙(はしご高), ①, ㈱, 﨑, ✖︎」などの旧字体!csvにこの旧字体が含まれている事によって、エラーが起きてしまいます。
例)
sample.csv :
山田花子,前田敦子,髙田啓介 ← 「髙」が紛れ込んでいる解決策
解決策は何通りかありますが、(1)一番確実に変換出来るものと、(2)簡単に変換出来るものをご紹介します!
(1) 強制変換
初めから、UTF-8としてcsvを読み込ませる!
CSV.parse(NKF::nkf('-w',File.read({ファイル名})), headers: true) do |row|
Shift_JIS → UTF-8 ( Shift_JISで書かれたcsv を UTF-8に変換する!)
のではなく、
Shift_JISで書かれたcsvを初めからUTF-8として変換する
初めからUTF-8として変換する事によって、文字化けも起こる事なく、エラーも回避する事が出来ます!(2) CP932を使う
単純です
CSV.foreach({ファイル名}, encoding: "CP932:UTF-8", headers: true) do |row|
Shift_JIS → UTF-8
CP932 → UTF-8
(ただ、Shift_JISをCP932に変えただけ)こちらでも、文字化け、エラーを防ぐ事が出来ます!
お試しください^^
- 投稿日:2020-02-05T19:28:49+09:00
RailsでHamlを書く
概要
初心者向けにhamlの基礎的な書き方とhamlにおけるrailsヘルパーメソッドのルールを書きました。
特にヘルパーメソッドの書き方は初心者がよくsyntaxエラーを起こしやすい箇所なので参考になればと思います。HTMLタグ
基礎
HTMLタグは基本的に「%」をつければOKです。
haml%div %a %input %header結果(HTML)<div></div> <a></a> <input> <header></header>クラス名、ID名
クラス名は「.(ドット)」、ID名は「#(シャープ)」をそれぞれHTMLタグの後ろにつけることで作成できます。
「.」や「#」を連続することで複数のクラス名やID名をつけることができます。また、HTMLタグ無しでクラス名やID名を記述するとdivタグになります。(divタグの省略記法)
haml%div.class-name %div#id-name %div.class1.class2.class3 .no-html-tag #no-html-tag結果(HTML)<div class="class-name"></div> <div id="id-name"></div> <div class="class1 class2 class3"></div> <div class="no-html-tag"></div> <div id="no-html-tag"></div>入れ子(ネスト)
ネストは半角2スペースで表現します。
(厳密に言うと2スペース以外もできますが、通例では2スペースです。)haml.parent .child結果(HTML)<div class="parent"> <div class="child"></div> </div>haml.parent .child 子供だよ結果(HTML)<div class="parent"> <div class="child">子供だよ</div> </div>ネストの深さでどのHTMLタグがどのHTMLタグの親かが決定します。
最初は見慣れないですが、慣れると可読性が高く、記述もHTMLより素早くできます。読むコツとしてはそのHTMLタグから視線をまっすぐ下に降ろすことです。
<下記hamlコードを例に>
「.parent」の子供はどこまでかを把握したい場合
「.parent」と同じネストの深さ(0 space)で書かれているのは「.parent-brother」なので、それまでの「.child」「.grand.child」「.brother」が子供です。「.child」の子供はどこまでかを把握したい場合
「.child」と同じネストの深さ(2 space)で書かれているのは「.brother」なので、それまでの「.grand.child」が子供です。「.brother」の子供はどこまでかを把握したい場合
次の行が自分より浅いネスト(0 space)の「.parent-brother」なので「.brother」は子供がありません。haml.parent .child .grandchild .brother .parent-brother .child結果(HTML)<div class="parent"> <div class="child"> <div class="grandchild"></div> </div> <div class="brother"></div> </div> <div class="parent-brother"> <div class="child"></div> </div>railsヘルパーメソッド
基礎
erbでは変数やヘルパーメソッドなどの表示したいものは「<%= 記述 %>」
ifやeachなどの表示したくない処理等は「<% 記述 %>」で記述していました。hamlでは以下になります。
- 表示したいものは「= 記述」
- 表示したくないものは「- 記述」
erb<%= link_to root_path %> <% hello = "こんにちわ" %> <%= hello %>haml= link_to root_path - hello = "こんにちわ" = hello結果(HTML)<a href="/">/</a> こんにちわ <!-- 「- hello = "こんにちわ"の部分は表示されない」 -->クラス名・ID名
ヘルパーメソッドにクラス名・ID名を付与する場合はHTMLタグとは違い、それぞれのヘルパーメソッドの構文に従います。
例えばLink_toは以下のような書き方が可能なのでそれに従って書きます。構文link_to(body, url = {}, html_options = {}) # url is a String; you can use URL helpers like # posts_path link_to(url, html_options = {}) do # name endhaml= link_to "リンクだよ",root_path,{class:"class-name",id:"id-name"} = link_to root_path,{class:"class-name",id:"id-name"} do リンクだよHTML(結果、どちらも同じ)<a class="class-name" id="id-name" href="/">リンクだよ</a>ネスト
ネストのルールはHTMLの項目で述べたものと同じく半角2スペースが通例です。
ここで意識すべきは記述の始まりは「=」や「-」であることです。
以下は「link_to」の始まりが揃っていますが、「=」の位置が違うのでネストの深さが違います。
初心者はよくここでエラーを起こしやすいです。haml%div = link_to root_path -#=>正 2space nest = link_to root_path -#=>誤 1space nest結果(HTML)<div> <a href="/">/</a> </div>また、逆にrailsヘルパーメソッドへ他の要素をネストさせる場合はヘルパーメソッドの構文に従います。
ここで意識すべきは「do end」で閉じる系のメソッドはendの閉じタグが不要になることです。
例えば「do end」で閉じて他の要素をネストさせることができるlink_toは以下のようになります。erb<%= link_to root_path do %> リンクだよ <% end %>haml= link_to root_path do リンクだよ結果(HTML)<a href="/">リンクだよ</a>おわりに
初心者に「公式ドキュメントを読め!」は流石に鬼畜な気もするので近日中に頻出ヘルパーメソッドについてはまとめようかと思います。
また、当記事を読んで疑問・修正等があればコメントいただけると助かります。
いいねをしていただけるとモチベーションになります!!
- 投稿日:2020-02-05T19:18:05+09:00
【heroku】ActiveRecord::IrreversibleMigration が出たときの対処法
nemlog検索サイト https://searchnemlog.herokuapp.com/
作成の時herokuデプロイで詰まってしまったので解決方法をメモしときます。ActiveRecord::IrreversibleMigration
おそらくスクレイピングを通してデータベースに保存していたので起きてしまったエラーと思われます。
以下からはこのエラーの解決手順を記したいと思います。
ロールバック
heroku run rake db:migrate:reset恐らくエラー文が表示されると思います。
そこに
DISABLE_DATABASE_ENVIRONMENT_CHECK=1と書かれていたらそれを利用してDB生成が可能になります。
ドロップ
heroku run RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:drop先ほどのエラー文を利用してドロップするとなんとかドロップしてくれます。
DB生成
あとはdb:createを実行。
heroku run rails db:createマイグレーション
最後はいつも通り heroku run をしましょう。
heroku run rake db:migrateこの手順でいけばなんとかアプリが立ち上がると思います。
まとめ
データベース系のエラーにハマるとデリケートなイメージなのでめちゃくちゃビビり倒してしまいます。
herokuエラーは前回大概ハマったと思っていましたがまだまだ見知らぬエラーがたくさんである意味奥が深いですね。
とりあえず誰かの参考になればと思います。
- 投稿日:2020-02-05T18:31:59+09:00
bin とは
binファイルとは?
binとはbinaryの略で、binファイルとは
テキスト形式ではなくバイナリ形式で書かれているデータを扱うファイル。バイナリデータとは、コンピュータが理解するためのプログラムが書かれたデータのことである。
- 投稿日:2020-02-05T15:16:52+09:00
rails_same_site_cookie gemで、RailsアプリにChrome 80向けのSameSite属性を指定する
はじめに
以下の記事にあるとおり、Chrome 80では2020年2月17日の週以降にデフォルトのSameSite属性が変更されます。
Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた - Qiita
この変更が入ると、次のように「決済や認証などで外部サービスを利用し、外部サイトからPOSTで戻ってくるサイト」でユーザーが識別できないエラーが発生します。
(画像の引用元:Chrome 80が密かに呼び寄せる地獄 ~ SameSite属性のデフォルト変更を調べてみた - Qiita)本記事ではRailsアプリケーションでこの問題に対応する方法を紹介します。
免責事項 (disclaimer)
この記事のとおりにあなたのRailsアプリケーションを変更して、何らかの不具合やセキュリティ上の問題が発生しても筆者は一切の責任を負いません。
技術的な背景と最新の技術情報を十分理解した上で、本記事の内容を適用してください。
(適用する必要がない場合は何もしないでください)対応方法
rackのバージョンを2.1.0以上に上げます。
(2.1.0以上でないと、SameSite=None
属性に対応していないため - 参考)(訂正:rails_same_site_cookieはrackの機能ではなく、独自にCookieを設定しているため、必ずしもrack 2.1.0以上に上げる必要はないようです)
rails_same_site_cookie gemをインストールします。
Gemfilegem 'rails_same_site_cookie'$ bundle install対応は以上です。
rails_same_site_cookie gemがやってくれること
rails_same_site_cookie
gemをインストールすると、自動的に全cookieにSameSite=None; Secure
属性が追加されます。ただし、iOS 12とmacOS 10.14のSafariなど、
SameSite=None; Secure
属性を付けると不具合が発生するブラウザ(参考)に対してはこの属性を付与しません。rails_same_site_cookie gemがやってくれないこと
rails_same_site_cookie gemはRails側のCookieを変更するだけで、JavaScript側で設定するCookieには何も変更を加えません。
もしJS内でCookieを設定しているコードがある場合は、何らかの対応が必要になります。(詳細未調査)参考1:動作確認の手順(例)
「決済で外部サービスを利用し、外部サイトからPOSTで戻ってくるサイト」を想定した場合の確認手順です。
いきなり本番環境で試すのではなく、テスト環境(ステージング環境)で試すようにしてください。
- こちらの手順に従って、Chromeの設定を変更する
- Railsアプリサイト上で商品を購入し、外部の決済サービスに遷移する
- 2分以上待つ(2分以上待たないとChromeがCookieを送信してしまうため)
- 決済を実行して、自サイトに戻る
- ログイン情報が維持されたまま、正常に決済が完了することを確認する(修正前は何らかの不具合が発生することも事前に確認しておく)
参考2:リクエストスペックでテストを書く(例)
SameSite=None
属性が適切に付与されているかどうかを確認するリクエストスペックの記述例です。
(非SSLで接続する場合、rails_same_site_cookie gemはSameSite=None
属性だけを付与し、Secure
属性は付与しません)ログイン処理はDeviseで実現されていることを前提とします。
テストコード内の_your_app_session
は、適宜ご自身のアプリケーション名に合わせて変更してください。spec/requests/cookies_spec.rbrequire 'rails_helper' RSpec.describe 'Cookies', type: :request do describe 'Cookie の SameSite 属性' do before do user = create :user login_as user end it 'User-Agent指定無しの場合 SameSite=None がつく' do get new_user_session_path expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/ end it 'SameSite=Lax がデフォルトになる Chrome 80 では SameSite=None がつく' do mac_chrome_80 = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.42 Safari/537.36 ' win_chrome_80 = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.16 Safari/537.36' get new_user_session_path, headers: { 'User-Agent' => mac_chrome_80 } expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/ get new_user_session_path, headers: { 'User-Agent' => win_chrome_80 } expect(response.headers['Set-Cookie']).to match /_your_app_session=.*SameSite=None/ end it 'SameSite=Noneの扱いにバグがある iOS12 Safari では SameSite=None がつかない' do iphone_ios12_user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 Mobile/15E148 Safari/604.1' get new_user_session_path, headers: { 'User-Agent' => iphone_ios12_user_agent } expect(response.headers['Set-Cookie']).to include '_your_app_session=' expect(response.headers['Set-Cookie']).not_to include 'SameSite' end end end参考文献
本記事を書くにあたって、下記記事を参考にさせてもらいました。
どうもありがとうございました。
- 投稿日:2020-02-05T15:07:52+09:00
【Rails】ActiveRecordの`find_by`で大文字と小文字を区別しないで取得する方法
はじめに
find_by
メソッドで値を取得する際にハマったので解決方法を探してみました。Rails : 6.0
Ruby: 2.7
SQLite3以上の環境しか確認していません。
検索対象と検索したい文字列を大文字に変換
保存されたタグを取得する際、大文字と小文字の違いで期待した値を取得することができない。
$ rails console # 大文字と小文字を混ぜた状態で保存します > Tag.create(tag_name: "RuBy") > # 小文字で先程作成したタグ名を検索してみます > Tag.find_by(tag_name: "ruby") #=> nilTags テーブルに保存された値は
"RuBy"
と保存しているためfind_by
メソッドで"ruby"
と検索しても取得することができません。ユーザーがタグを検索したい場合、
"ruby"
や"Ruby"
、"RUBY"
など様々な入力で検索する可能性があるため、大文字と小文字で区別されてしまうのは不便です。
大文字と小文字を区別せず
find_by
メソッドで取得するためには、SQL のupper()
関数を使いカラムに含まれる文字列を大文字に変換して、検索したい文字列も Ruby のupcase
メソッドで大文字に変換して取得します。$ rails console # 大文字と小文字を混ぜた状態で保存します > Tag.create(tag_name: "RuBy") > # 検索対象のカラムと検索したい文字列をそれぞれ大文字に変換 > Tag.find_by('UPPER(tag_name) = ?', "ruby".upcase) #=> tag_name: "RuBy"これで Tags テーブルの値を大文字と小文字を区別することなく取得することができました。
'UPPER()'
と大文字にしているのは SQL 的な記述のためなので、特に意味はありませんupper()
としても動作します。
- 投稿日:2020-02-05T14:20:53+09:00
Leetcode: Longest Common Ancestor Of Two Nodes
require 'minitest/autorun' require 'pry' class LongestCommonAncestorOfTwoNodes < Minitest::Test def test_run root = [3, 5, 1, 6, 2, 0, 8, nil, nil, 7, 4] p = 5 q = 1 tree = TreeNode.new(3) tree.left = TreeNode.new(5) tree.left.left = TreeNode.new(6) tree.left.right = TreeNode.new(2) tree.left.right.right = TreeNode.new(4) tree.left.right.left = TreeNode.new(7) tree.right = TreeNode.new(1) tree.right.left = TreeNode.new(0) tree.right.right = TreeNode.new(8) assert_equal(3, lowest_common_ancestor(tree, p, q)) end def lowest_common_ancestor(root, p, q) return nil if root.nil? if root.val == p || root.val == q return root end left = lowest_common_ancestor(root.left, p, q) right = lowest_common_ancestor(root.right, p, q) if left != nil && right != nil root else left ? left : right end end end class TreeNode attr_accessor :val, :left, :right def initialize(val) @val = val @left, @right = nil, nil end end
- 投稿日:2020-02-05T14:20:53+09:00
Leetcode: Lowest Common Ancestor Of Two Nodes
require 'minitest/autorun' require 'pry' class LowestCommonAncestorOfTwoNodes < Minitest::Test def test_run root = [3, 5, 1, 6, 2, 0, 8, nil, nil, 7, 4] p = 5 q = 1 tree = TreeNode.new(3) tree.left = TreeNode.new(5) tree.left.left = TreeNode.new(6) tree.left.right = TreeNode.new(2) tree.left.right.right = TreeNode.new(4) tree.left.right.left = TreeNode.new(7) tree.right = TreeNode.new(1) tree.right.left = TreeNode.new(0) tree.right.right = TreeNode.new(8) assert_equal(3, lowest_common_ancestor(tree, p, q)) end def lowest_common_ancestor(root, p, q) return nil if root.nil? if root.val == p || root.val == q return root end left = lowest_common_ancestor(root.left, p, q) right = lowest_common_ancestor(root.right, p, q) if left != nil && right != nil root else left ? left : right end end end class TreeNode attr_accessor :val, :left, :right def initialize(val) @val = val @left, @right = nil, nil end end
- 投稿日:2020-02-05T13:39:34+09:00
google api 住所から経度緯度取得してgoogle map に表示
やりたいこと
レストラン(写真。店名。店の説明。住所)の投稿の際に
住所入力したら、selfで経度緯度所得してgoogle map にピン立てる。環境
ruby 2.5.1
rails 5.2.3実装
DBの中身
postsテーブル
カラム名 型 内容 address string 住所 latitude float 住所緯度 longitude float 住所経度 title text 店の名前 description text 店の説明 image string レストランの写真 google API 所得
参考サイト
(https://nendeb.com/276)Maps JavaScript API
Geocoding API
この二つの有効化gem 導入
Gem.filegem "gmaps4rails" gem "geocoder"JS
ターミナル.rails g gmaps4rails:copy_jsapplication.html.haml%script(src="//maps.google.com/maps/api/js?v=3.23") %script(src="//cdn.rawgit.com/mahnunchik/markerclustererplus/master/dist/markerclusterer.min.js") %script(src="//cdn.rawgit.com/printercu/google-maps-utility-library-v3-read-only/master/infobox/src/infobox_packed.js" type="text/javascript") %script(src="/javascripts/gmaps_google.js")Model
post.rbgeocoded_by :address after_validation :geocode private def geocode uri = URI.escape("https://maps.googleapis.com/maps/api/geocode/json?address="+self.address.gsub(" ", "")+"&key=#{Rails.application.credentials.google_map_api}") res = HTTP.get(uri).to_s response = JSON.parse(res) self.latitude = response["results"][0]["geometry"]["location"]["lat"] self.longitude = response["results"][0]["geometry"]["location"]["lng"] endcontroller
posts_controller.rbdef show @post = Post.find(params[:id]) end private def post_params params.require(:post).permit(:image, :description, :text, :address, :latitude, :longitude) endveiw
show.html.haml#map :javascript function initMap() { var test = {lat: #{@post.latitude}, lng: #{@post.longitude}}; var map = new google.maps.Map(document.getElementById('map'), { zoom: 15, center: test }); var transitLayer = new google.maps.TransitLayer(); transitLayer.setMap(map); var contentString = '住所:#{@post.address}'; var infowindow = new google.maps.InfoWindow({ content: contentString }); var marker = new google.maps.Marker({ position:test, map: map, title: contentString }); marker.addListener('click', function() { infowindow.open(map, marker); }); } %script{:async => "", :defer => "defer", :src => "https://maps.googleapis.com/maps/api/js?v=3.exp&key=#{Rails.application.credentials.google_map_api}&callback=initMap"}show.scss#map { height: 400px; margin-left:auto; margin-right:auto; text-align:left; width: 80% }大切なこと
post.rbとshow.html.hamlのところに
下記の記載があると思います。#{Rails.application.credentials.google_map_api}この記載を消して自分で所得したAPIを打ち込めば動きますが
gitでプロジェクト管理してる時にgitにあげるとAPI_KEYが悪用される可能性があり、gitパトロールから注意が入ります。
なので環境変数を使います。環境変数の設定
rails のバージョンによって異なります。
今回はrails 5.2.3を使用しています。ターミナル.EDITOR="vi" bin/rails credentials:edit上記コマンドで環境変数の設定を行います。
aws:~~~~はデフォルトで書いてあると思います。
今回はgoogle_map_apiという変数に所得したAPI_KEYを代入している形です。
変数は自分の好きな名前で結構です。iで入力モードで編集追加できます。
:wqで保存、上書き保存できます。# aws: # access_key_id: 123 # secret_access_key: 345 google_map_api: 所得したAPI_KEY終わりに
私は今回このようなコードで住所から緯度経度所得してmapに表示させました。
gitにもあげているので詳細なコード確認したい方がおられましたら、キータの私のページにgitのリンクありますので見てください。
favorite_food_shareというプロジェクト名です。(キータ記事とコード少し違うところあります。)
- 投稿日:2020-02-05T13:18:21+09:00
factory_botでモデルのenum別のtraitを一発で書く小ネタ
今回はRailsでテストデータを作成する”factroy_bot”に関する小ネタです。
TL DR;
こんな感じでイケちゃう
User.account_type.values.each do |type| trait :"#{type}" do account_type { type } end end動作環境
- Rails: 5.2.3
- Ruby: 2.6.5
- factory_bot: 5.0.2
- factory_bot_rails: 5.0.1
※今回はRubyの記法に依る部分が大きいので、上記バージョンはあまり気にしなくても良いです
コード例
想定するモデル
この記事では以下のような
User
モデルを例として考えます。プロダクトコードによくある「複数のユーザー種類をenumのカラム(今回は例としてaccount_type
とする)で持ち判別する」モデルです。app/models/user.rbclass User < ApplicationRecord validates :name, presence: true # 中略 extend Enumerize enumerize :account_type, in: { normal: 0, admin: 1, client: 2, development: 3 }, scope: true endどうでもいい話ですが、色々な種類のユーザーが存在し、しかも同じユーザーが複数の種類を持つといった、ユーザー周りが複雑になるのはあるあるですが、本当にツラいですよね。AdminかEndUserかの2択ぐらいで収まれば、それぞれのユーザーから見える画面もきれいに分割できて丁度いいのですが...。
普通にfactoryを書く場合
さて、このようなenumカラムを持つUserモデルを素直にfacrotyで表現すると、このようになります。ああめんどくさい。モデルが複雑になれば、factoryが複雑になるのは当然のことですが...
spec/factories/user.rbFactoryBot.define do factory :user do name { 'test' } # 中略 trait :normal do account_type { normal } end trait :admin do account_type { admin } end trait :client do account_type { client } end trait :development do account_type { development } end end endやりたいこと
こんな意味のないコードをチマチマ全部書くのはやりたくない。いやでも書くしか無いし、一旦Pushするか...。いや、なんかそれらしいカッコいいやり方があるはずだ。多分。ほんの数行で全部のenumのtraitを生み出すやり方が。ついでに、カラムのenum定義が増えたときにも勝手に増えるようにしてほしい。忘れそうだし。
小技
そこで小技を使うと、こんな感じで書けます。「まさか動かないだろう」と思い、冗談で書いたら動きました。Rubyってすごい。
spec/factories/user.rbUser.account_type.values.each do |type| trait :"#{type}" do account_type { type } end end
User.account_type.values
と、enumのカラムから直接traitを作っているので、enumの定義が増えたときにテストデータを作り忘れることもありません。ユーザーの氏名やアドレスを表すカラムがあれば、そこに同様にtype
変数を突っ込めば、RSpecのテスタビリティも向上しそうです。ちなみに、
User.account_type.values
の部分はこんな感じで動作しています。ハッシュでenumを取り出して、そのvalueをvalues
メソッドで配列化し、その配列の値でtraitを定義しているわけです。もちろん必要であれば、keyとvalueの両方を取り出して使うこともできます。[1] pry(main)> User.account_type.values => ["normal", "admin", "client", "development"] [2] pry(main)> User.account_type => #<Enumerize::Attribute:0x0000558ff649f058 @i18n_scopes=["enumerize.user.account_type"], @klass=User (call 'User.connection' to establish a connection), @name=:account_type, @skip_validations_value=false, @value_hash= {"0"=>"normal", "1"=>"admin", "2"=>"client", "3"=>"development", "normal"=>"normal", "admin"=>"admin", "client"=>"client", "development"=>"development"}, @values=["normal", "admin", "client", "development"]>余談...
まあ、実際のプロダクトコードでは、各enumごとに紐付くアソシエーションも変更する必要があったりするので、なかなかこれ一つで完璧にtraitを表現することは難しいです。とはいえ、必要であれば切り出して書けばよいですし、アソシエーションが複雑になる前にスピーディーにテストデータを作りたい場合はぜひ。
- 投稿日:2020-02-05T13:13:35+09:00
Railsでbundle installができない
問題
bundle install
をするとInstalling mysql2 0.5.2 with native extensions
Gem::Ext::BuildError: ERROR: Failed to build gem native extension.
(略)
An error occurred while installing mysql2 (0.5.2), and Bundler cannot continue.
Make sure thatgem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'
succeeds before bundling.と出る
指示通り
gem install mysql2 -v '0.5.2' --source 'https://rubygems.org/'
を走らせても解決しない解決策
bundle config
を実行するとbuild.mysql2
Set for your local app (/Users/ユーザー名/アプリケーション名/.bundle/config): "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
と出るそこで
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
を実行した
bundle config
で確認すると
build.mysql2
Set for your local app (/Users/GO/source_code/clubru/.bundle/config): "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
となっていた再度
bundle install
を実行すると無事mysqlをインストールできた調べて出てきた結果と違ったこと
自分の場合は
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib"
で解決したが、調べて出てきた解決策は
bundle config --local build.mysql2 "--with-ldflags=-L/usr/local/opt/openssl@1.1/lib --with-cppflags=-I/usr/local/opt/openssl@1.1/include"
や
bundle config --local build.mysql2 "--with-cppflags=-I/usr/local/opt/openssl@1.1/include"
datta
- 投稿日:2020-02-05T12:47:38+09:00
【Ruby】gsub()で複数の文字を変換する方法
- 投稿日:2020-02-05T12:26:11+09:00
「rbenv: rails: command not found」の対処法
発生原因
これは主にRuby(rbenv)のversionを新しくした場合に発生するそうです。
私の場合は2.5.1から2.6.4にして$ rails new
した際に発生しました。~/MyApp ❯ rails _5.1.6_ new portfolio rbenv: rails: command not found The `rails' command exists in these Ruby versions: 2.5.1状況確認
まずrbenv(2.6.4)の中身を確認してみます。
~/.rbenv/versions/2.6.4/bin ❯ ls bundle bundler erb gem irb rake rdoc ri ruby注目すべきはbundleとrailsの有無みたいです。
私の場合はbundleは有ってrailsが無いですね。
ちなみに今まで使用していた(2.5.1)の中身を見てみると~/.rbenv/versions/2.5.1/bin ❯ ls annotate gem nokogiri rspec spring aws.rb gem2gv pry rubocop sprockets bundle git2gv puma ruby thor bundler htmldiff pumactl ruby-parse tilt byebug i18n-tasks rackup ruby-rewrite update_rubygems coderay irb rails ruby2gv xml2gv dot2ruby launchy rake sass yard erb ldiff rdoc sass-convert yardoc erd listen ri scss yriこんな感じでした。当然ですがbundleもrailsも有りますね。
対処する
私の場合はrailsが無かったのでインストールします。
まずRailsチュートリアルに沿って.gemrc
に対して次のコマンドを設定した後に実行しました。~ ❯ printf "install: --no-document \nupdate: --no-document\n" >> ~/.gemrc ~ ❯ gem install rails -v 5.1.6 Fetching concurrent-ruby-1.1.5.gem Fetching thread_safe-0.3.6.gem Fetching tzinfo-1.2.6.gem Fetching i18n-1.8.2.gem Fetching activesupport-5.1.6.gem Fetching nokogiri-1.10.7.gem Fetching mini_portile2-2.4.0.gem ... # 省略bundleが無かった方は
$ bundle install
で解決するそうです。
両方無い方は$ bundle install
した後に$ gem install rails
を行ってください。動作確認
実行できるか確認してみます。
~/MyApp ❯ rails _5.1.6_ new portfolio create create README.md create Rakefile create config.ru create .gitignore create Gemfile ... # 省略無事うまくいきました!
- 投稿日:2020-02-05T12:18:56+09:00
生に近い HTTP リクエスト情報を調査するために Ruby で Web サーバを作って Heroku にデプロイ
概要
- できるだけ生に近い HTTP リクエスト情報を調査するために Web サーバを作る
- Ruby の標準ライブラリのみを使用する
- Web サーバを macOS Catalina で動作させて curl からの HTTP リクエストを確認する
- Web サーバを Heroku で動作させて curl からの HTTP リクエストを確認する
Web サーバのソースコード
Ruby 標準ライブラリのみを使用する。
マルチスレッドには非対応。myserver.rbrequire 'socket' # 標準出力を同期モードに設定 $stdout.sync = true # 接続を受け付けるポート番号を決定 # 環境変数 PORT が設定されているならそれを設定 port = 8000 port = ENV['PORT'].to_i if ENV['PORT'] # サーバー接続をオープン server = TCPServer.open(port) # HTTP リクエストを待ち続ける loop do begin # TCPSocket オブジェクトを取得 socket = server.accept # 受け付けた日時を出力 puts "[info]#{Time.new}" # HTTP リクエスト開始行を出力 if not req_start_line = socket.gets puts '[info]req_start_line is nil' next end puts "#{req_start_line}" # HTTP リクエストヘッダーを1行ずつ出力 while req_header = socket.gets.chomp puts "#{req_header}" break if req_header == '' # ヘッダー終了 # Content-Length ヘッダーがあれば値を変数にセット h = req_header.split(':') content_length = h[1].strip.to_i if h[0].strip.downcase == 'content-length' end # Content-Length がある場合はボディを出力 if content_length != nil puts socket.read(content_length) end # HTTP レスポンスを返す # 本文データ body = "<html><body>Hello, world</body></html>\r\n" # ステータス行 socket.write "HTTP/1.1 200 OK\r\n" # ヘッダー socket.write "Server: #{RUBY_DESCRIPTION}\r\n" socket.write "Content-Type: text/html; charset=utf-8\r\n" socket.write "Content-Length: #{body.bytesize}\r\n" socket.write "Connection: close\r\n" # 空行 socket.write "\r\n" # 本文 socket.write body rescue => e puts e.full_message ensure # HTTP 接続を閉じる puts '[info]close this socket' socket.close end end server.closemacOS Catalina で動かす
macOS Catalina + Ruby 2.7.0 の環境で Web サーバプログラム myserver.rb を起動する。
$ ruby myserver.rbcurl から HTTP GET リクエストする例
クライアント側のコマンドと出力結果。
$ curl -i -H 'small: sss' -H 'LARGE: LLL' http://localhost:8000/foo/?aaa=xxx HTTP/1.1 200 OK Server: ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19] Content-Type: text/html; charset=utf-8 Content-Length: 40 Connection: close <html><body>Hello, world</body></html>サーバ側の出力結果。
[info]2020-02-04 08:03:08 +0900 GET /foo/?aaa=xxx HTTP/1.1 Host: localhost:8000 User-Agent: curl/7.68.0 Accept: */* small: sss LARGE: LLL [info]close this socketcurl から HTTP POST リクエストする例
クライアント側のコマンドと出力結果。
$ curl -i -H 'Content-Type: application/json' http://localhost:8000/bar/ -d '{"foo": {"bar": ["あいうえお"]}}' HTTP/1.1 200 OK Server: ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19] Content-Type: text/html; charset=utf-8 Content-Length: 40 Connection: close <html><body>Hello, world</body></html>サーバ側の出力結果。
[info]2020-02-04 08:03:14 +0900 POST /bar/ HTTP/1.1 Host: localhost:8000 User-Agent: curl/7.68.0 Accept: */* Content-Type: application/json Content-Length: 37 {"foo": {"bar": ["あいうえお"]}} [info]close this socketHeroku で動かす
Heroku + Ruby 2.7.0 の環境で Web サーバを起動する。
できるだけ生に近い HTTP リクエストを調査したいが、Heroku のリバースプロキシが間に入るためリクエストヘッダがいくつか増えてしまう問題がある(許容するしかない)。
Heroku にデプロイするために必要なファイル
サーバプログラム myserver.rb 以外に Gemfile, Gemfile.lock, Procfile が必要。
Gemfile
ruby '2.7.0'Gemfile.lock
Gemfile.lockGEM specs: PLATFORMS ruby DEPENDENCIES RUBY VERSION ruby 2.7.0p0 BUNDLED WITH 2.1.2Procfile
web: ruby myserver.rbcurl から HTTP GET リクエストする例
クライアント側のコマンドと出力結果。
$ curl -i -H 'small: sss' -H 'LARGE: LLL' https://example.herokuapp.com/foo/?aaa=xxx HTTP/1.1 200 OK Date: Mon, 03 Feb 2020 23:03:28 GMT Connection: keep-alive Server: ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux] Content-Type: text/html; charset=utf-8 Content-Length: 40 Via: 1.1 vegur <html><body>Hello, world</body></html>サーバ側の出力結果。
[info]2020-02-03 23:03:28 +0000 GET /foo/?aaa=xxx HTTP/1.1 Host: example.herokuapp.com Connection: close User-Agent: curl/7.68.0 Accept: */* Small: sss Large: LLL X-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX X-Forwarded-For: XXX.XXX.XXX.XXX X-Forwarded-Proto: https X-Forwarded-Port: 443 Via: 1.1 vegur Connect-Time: 1 X-Request-Start: 1580771008907 Total-Route-Time: 0 [info]close this socketcurl から HTTP POST リクエストする例
クライアント側のコマンドと出力結果。
$ curl -i -H 'Content-Type: application/json' https://example.herokuapp.com/bar/ -d '{"foo": {"bar": ["あいうえお"]}}' HTTP/1.1 200 OK Date: Mon, 03 Feb 2020 23:03:36 GMT Connection: keep-alive Server: ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-linux] Content-Type: text/html; charset=utf-8 Content-Length: 40 Via: 1.1 vegur <html><body>Hello, world</body></html>サーバ側の出力結果。
[info]2020-02-03 23:03:37 +0000 POST /bar/ HTTP/1.1 Host: example.herokuapp.com Connection: close User-Agent: curl/7.68.0 Accept: */* Content-Type: application/json X-Request-Id: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX X-Forwarded-For: XXX.XXX.XXX.XXX X-Forwarded-Proto: https X-Forwarded-Port: 443 Via: 1.1 vegur Connect-Time: 1 X-Request-Start: 1580771017243 Total-Route-Time: 0 Content-Length: 37 {"foo": {"bar": ["あいうえお"]}} [info]close this socket参考資料
- 投稿日:2020-02-05T11:50:02+09:00
【RSpec】重複のバリデーションエラーを回避するFactoryBotの書き方
ユーザー登録でemailを重複させないようにバリデーションをかけましが、テストのときにでFactoryBotで複数ユーザを予め生成する必要があったので方法を調べました。シーケンスを使ってユニークなデータを生成するやり方です。備忘録として残します。
問題点
ファクトリーで複数のユーザをセットアップする際に、emailをバリデーションでユニークなデータとして設定している場合、下記のようにするとテストコードが走る前に例外が発生します。
spec/factories/users.rbFactoryBot.define do factory :user do user_name { "tester" } email { "tester@example.com" } password { "password" } end endspec/models/user_spec.rbit "複数のユーザー登録" do user1 = FactoryBot.create(:user) user2 = FactoryBot.create(:user) endこれでテストを実行すると、当然ですが次のようなバリデーションエラーが発生します。
ターミナルFailures: 1) 複数のユーザー登録 Failure/Error: user2 = FactoryBot.create(:user) ActiveRecord::RecordInvalid: バリデーションに失敗しました: メールアドレスはすでに存在します解決策
シーケンスを使います。シーケンスはファクトリから新しいオブジェクトを作成するたびにカウンタの値を1づつ増やしながら値を設定します。
spec/factories/users.rbFactoryBot.define do factory :user do user_name { "tester" } sequence(:email) { |n| "tester#{n}@example.com" } password { "password" } end end
email { "tester@example.com" }
をsequence(:email) { |n| "tester#{n}@example.com" }
と書き換えることで、userが生成されるたびにn
が1づつ増えていき、tester1@example.com
tester2@example.com
のようにユニークで連続したemailが設定されます。ターミナル#見やすいように加工してあります user1 <User id: 13, user_name: "tester", email: "tester1@example.com", password: "password", created_at: "2020-02-05 01:56:36", updated_at: "2020-02-05 01:56:36">ターミナル#見やすいように加工してあります user2 <User id: 14, user_name: "tester", email: "tester2@example.com", password: "password", created_at: "2020-02-05 01:57:21", updated_at: "2020-02-05 01:57:21">
- 投稿日:2020-02-05T11:45:26+09:00
Ruby コーティング規約について 構文編 3
はじめに
レイアウト編はこちらをクリック願います。
構文編 2 はこちらをクリック願います。
Rubyの基礎を学習中の方に向けて記載致します。
私自身これからチーム開発を行う上で大事にしたい。知っておきたいことをOutputします。構文について
① 否定形のときはifよりunlessを優先的に使う。(もしくは||構文を使う。)
qiita.rb# 悪い例 do_something if !some_condition # 悪い例 do_something if not some_condition # 良い例 do_something unless some_condition # 良い例(||を使う場合) some_condition || do_something② unlessをelse付きで使ってはいけない。 肯定条件を先にしてから書き換える。
qiita.rb# 悪い例 unless success? puts 'fail' else puts 'success' end # 良い例 if success? puts 'success' else puts 'fail' endさいごに
毎日更新します。
皆様の復習等にご活用頂けますと幸いです。
- 投稿日:2020-02-05T08:55:23+09:00
UUIDをHashidsに変換して戻してみた。
目的
DBでUUIDで生成したキーをもとにテーブル分割すると「table_dd6ee086-936c-4dae-bbd7-f94e4203d868」みたいに長くなるのが嫌でなんとか短くしたいです。
base64とかやってみたけど「+,/,=」みたいな記号のハンドリングがめんどくさかったです。
そこでhashidsという数値とアルファベットだけで変換してくれるライブラリがあったのでこれを使いました。
RustとRubyとPostgreSQLで使いたかったので、それぞれ試してみました。コード
Rust
Cargo.toml[package] name = "test" version = "0.1.0" edition = "2018" [dependencies] uuid = { version = "^0.8", features = ["v4"] } harsh = "^0.1"main.rsfn main() { let converter = harsh::HarshBuilder::new().salt("salt goes here!").init().unwrap(); let my_uuid = uuid::Uuid::new_v4(); println!("{}", my_uuid); let target = converter.encode_hex(&my_uuid.to_simple().to_string()).unwrap(); println!("{}", target); let my_uuid = uuid::Uuid::parse_str(&converter.decode_hex(&target).unwrap()).unwrap(); println!("{}", my_uuid); }Ruby
Gemfile# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "hashids"main.rbrequire 'securerandom' require 'hashids' uuid = SecureRandom.uuid puts uuid hashids = Hashids.new "this is my salt" id = hashids.encode_hex(uuid.gsub(/-/,"")) puts id decoded = hashids.decode_hex(id).downcase uuid = "#{decoded[0..7]}-#{decoded[8..11]}-#{decoded[12..15]}-#{decoded[16..19]}-#{decoded[20..35]}" puts uuidPostgreSQL
select t3.src ,t3.simple ,t3.id ,hashids.decode_hex(id := t3.id, salt := 'salt')::uuid as dst from ( select t2.src ,t2.simple ,hashids.encode_hex(hex := t2.simple, salt := 'salt') as id from ( select t1.src ,regexp_replace(t1.src::text, '-', '', 'g') as simple from ( select gen_random_uuid() as src ) as t1 ) as t2 ) as t3結果
3f44b2e6-d6c4-4d2e-9956-bb56bfd58032 gyBm1Kw7M9u8Ral478g6ixxRXZrB 3f44b2e6-d6c4-4d2e-9956-bb56bfd58032ハイフンを抜くと4文字しか得しませんね?
参考
- 投稿日:2020-02-05T08:09:47+09:00
手に馴染む道具(プログラミング言語)
はじめに
職人は、自分の道具を使えば使うほど手に馴染んで使いやすくなっていく。プログラミング環境でもエディタなんかはそう。そしてプログラミング言語もそう。
プログラミング言語の老舗といえばCであり、多くの職業プログラマはCを通ってきているはず。
そうでもないか。
いや、ある程度のスキルを持っている人はCに精通しているし、アセンブラも分かるだろう。
その前提で、やはり言語仕様(文法)はCライクなものが手に馴染みやすいと思うんだ。そういった点で、JavaScriptは手に馴染みやすい。JavaScript系は割りと好きな部類だ。
本題
しかし、世の人気を二分するメジャー・スクリプト言語RubyとPython、どちらもCライクではない。なぜだ。慣れてしまえばそれまでなのだが。
動的型付け言語は散々な言われようだが、スキルさえ問題なければアドホックでスピーディーな開発では超絶な生産性を達成できると思うんだ。
JavaScriptでnode.jsでも良いのだか、あれはもう全くLightweightではないよね。挙動も独特だし。
なぜ動的型付け言語でCライクな汎用かつlightweightなスクリプト言語がない、もしくは人気が無いのか。
という気持ちだけ表明してみた。
本日は以上。