- 投稿日:2020-03-30T23:08:41+09:00
Typeformのかっこいい入力フォームをWebhookを使って自作アプリに実装した話
概要
非エンジニアビジネス職(カスタマーサクセス職)の@yuta_maruyamaが、動的でイケてるかっこよい入力フォームを自力で作ろうと思ったけれど難易度が高すぎたので米国産SaaSのTypeformをWebhookで組み込んでフォームを実装した話です。
ちなみにカスタマーサクセスの話は一切なく、大好きなクラフトビールのテイスティングノートアプリを作ろうとしている中での話。環境
- Ruby 2.6.2
- Rails 6.0
- heroku
作ろうとしているもの
- ビールのテイスティングノートアプリ
- 飲んだビールに関する評価やコメントをnoteとして残す
- Rails練習用なので見てくれは勘弁
- 「ノートを残す煩わしさ」がないことをサービスのユニークな価値にしたかったのでそこにはこだわりたい
Typeformとは
- フォームやサーベイに特化した米国のサービス
- 無料で使える
- 有料版だとHidden Fields機能など色々使える
- 日本語対応も(一応)している
やったこと
Typeform側
フォームの作成
マグマパスタばりのすっ飛ばし方だが、まずはTypeformにアカウント作成し、フォーム作成をする。Googleフォームを作るのとほぼ変わらない。
Webhookの設定
WebhookなのでPOSTリクエストが前提となる。なので勝手にアクションを指定しなくとも勝手にPOSTになる。今回は
POST /notes
で設定。保存。
hidden_filedの設定(オプショナル)
Hidden Fieldsは有料オプションなので有料化するつもりなければ飛ばして良い。Hidden Fieldsを用いない場合フォーム内で必要な値を入力してもらうのが良さそう(ユーザーにとってはひと手間だが)
今回の場合は、どこのBrewery(醸造所)のなんていうBeerのnoteを書いているかの値を渡して、
- Typeformに遷移した際にどのビールに関してのnoteかをはっきり示す
- わざわざTypeform側でユーザーがなんのビールについてのnoteか入力させないようにするを実現したい。なので
beer_id
brewery_id
beer_name
brewery_name
のフィールドを用意しておくサービス側
Controller
notes#create
をいじる。データのもらい方
Typeformのフォームへの回答をイベントとし、JSON形式でwebhookは送られる。
要素としては大きく
- Event information
- definition
- answersだが階層がややこしい。コードはこんな感じ。
def create @note = Note.new() @note.beer_id = params["form_response"]["hidden"]["beer_id"] params["form_response"]["answers"].each do |answer| if answer["field"]["ref"] == "rating" @note.rating = answer["rating"] end if answer["field"]["ref"] == "sweetness" @note.sweetness = answer["number"] end if answer["field"]["ref"] == "bitterness" @note.bitterness = answer["number"] end if answer["field"]["ref"] == "sourness" @note.sourness = answer["number"] end if answer["field"]["ref"] == "aroma" @note.aroma = answer["number"] end if answer["field"]["ref"] == "comment" @note.comment = answer["text"] end if answer["field"]["ref"] == "serving_type" @note.serving_type = answer["choice"]["label"] end endView
beer#show
からTypeformにパラメータを渡しながら遷移する。こんな感じで書けばOK。<%= link_to("Add note", "https://beerbash.typeform.com/to/******?beer_id=#{@beer.id}&beer_name=#{@beer.name}&brewery_name=#{@brewery.name}") %>最後に
感謝
自分はカスタマーサクセス職としてSQLを業務中に書くことは多いが、Railsに関してはまさに勉強中といった感じ。
そんな中メンターとして色々教えてくれる勤め先のCTO @showwin には本当に感謝が尽きない。SaaSとの共存
showwin先生には
#write-code-or-die
というSlackチャンネルでご指導いただいている。そのチャンネルでこんなことを述べたが
非エンジニアの自分でも、外部サービスをきちんと使えれば学習時間を大幅に節約してそれなりのアプリケーションを作れる実感がわいた。ZapierなどのノンコーディングでSaaSを連携できるSaaSのためのSaaSもどんどん進化を遂げており、非エンジニアでもプロトタイプレベルならガンガン自分で作ってみて欲しい見た目
最後に、こんな感じの見た目になった。
Typeformに遷移。ちゃんとビール名とブルワリー名が出ている。
元々Javascriptで自分でどうにかしようとおもってたがこれは無理
そして、Yorroco Beerが好きな人はぜひ連絡ください!!
- 投稿日:2020-03-30T23:08:41+09:00
Typeformのかっこいい入力フォームを自作アプリに実装した話
概要
非エンジニアビジネス職(カスタマーサクセス職)の@yuta_maruyamaが、動的でイケてるかっこよい入力フォームを自力で作ろうと思ったけれど難易度が高すぎたので米国産SaaSのTypeformを組み込んでWebhookで連携しようとした話です。
ちなみにカスタマーサクセスの話は一切なく、大好きなクラフトビールのテイスティングノートアプリを作ろうとしている中での話。環境
- Ruby 2.6.2
- Rails 6.0
- heroku
作ろうとしているもの
- ビールのテイスティングノートアプリ
- 飲んだビールに関する評価やコメントをnoteとして残す
- Rails練習用なので見てくれは勘弁
- 「ノートを残す煩わしさ」がないことをサービスのユニークな価値にしたかったのでそこにはこだわりたい
Typeformとは
- フォームやサーベイに特化した米国のサービス
- 無料で使える
- 有料版だとHidden Fields機能など色々使える
- 日本語対応も(一応)している
やったこと
Typeform側
フォームの作成
マグマパスタばりのすっ飛ばし方だが、まずはTypeformにアカウント作成し、フォーム作成をする。Googleフォームを作るのとほぼ変わらない。
Webhookの設定
WebhookなのでPOSTリクエストが前提となる。なので勝手にアクションを指定しなくとも勝手にPOSTになる。今回は
POST /notes
で設定。保存。
hidden_filedの設定(オプショナル)
Hidden Fieldsは有料オプションなので有料化するつもりなければ飛ばして良い。Hidden Fieldsを用いない場合フォーム内で必要な値を入力してもらうのが良さそう(ユーザーにとってはひと手間だが)
今回の場合は、どこのBrewery(醸造所)のなんていうBeerのnoteを書いているかの値を渡して、
- Typeformに遷移した際にどのビールに関してのnoteかをはっきり示す
- わざわざTypeform側でユーザーがなんのビールについてのnoteか入力させないようにする
を実現したい。なので
beer_id
brewery_id
beer_name
brewery_name
のフィールドを用意しておくサービス側
Controller
notes#create
をいじる。データのもらい方
Typeformのフォームへの回答をイベントとし、JSON形式でwebhookは送られる。
要素としては大きく
- Event information
- definition
- answers
だが階層がややこしい。コードはこんな感じ。
def create @note = Note.new() @note.beer_id = params["form_response"]["hidden"]["beer_id"] params["form_response"]["answers"].each do |answer| if answer["field"]["ref"] == "rating" @note.rating = answer["rating"] end if answer["field"]["ref"] == "sweetness" @note.sweetness = answer["number"] end if answer["field"]["ref"] == "bitterness" @note.bitterness = answer["number"] end if answer["field"]["ref"] == "sourness" @note.sourness = answer["number"] end if answer["field"]["ref"] == "aroma" @note.aroma = answer["number"] end if answer["field"]["ref"] == "comment" @note.comment = answer["text"] end if answer["field"]["ref"] == "serving_type" @note.serving_type = answer["choice"]["label"] end endView
beer#show
からTypeformにパラメータを渡しながら遷移する。こんな感じで書けばOK。<%= link_to("Add note", "https://beerbash.typeform.com/to/******?beer_id=#{@beer.id}&beer_name=#{@beer.name}&brewery_name=#{@brewery.name}") %>最後に
感謝
自分はカスタマーサクセス職としてSQLを業務中に書くことは多いが、Railsに関してはまさに勉強中といった感じ。
そんな中メンターとして色々教えてくれる勤め先のCTO @showwin には本当に感謝が尽きない。SaaSとの共存
showwin先生には
#write-code-or-die
というSlackチャンネルでご指導いただいている。そのチャンネルでこんなことを述べたが
非エンジニアの自分でも、外部サービスをきちんと使えれば学習時間を大幅に節約してそれなりのアプリケーションを作れる実感がわいた。ZapierなどのノンコーディングでSaaSを連携できるSaaSのためのSaaSもどんどん進化を遂げており、非エンジニアでもプロトタイプレベルならガンガン自分で作ってみて欲しい見た目
最後に、こんな感じの見た目になった。
Typeformに遷移。ちゃんとビール名とブルワリー名が出ている。
元々Javascriptで自分でどうにかしようとおもってたがこれは無理
そして、Yorroco Beerが好きな人はぜひ連絡ください!!
- 投稿日:2020-03-30T20:23:35+09:00
renderの処理について
本記事を書いた経緯
私はエンジニアになるために、プログラミングスクールにて学習を行なっています。
そこで、renderの処理について学び、備忘録として残したい使い方がありましたので本記事を書くに至りました。renderの使い方
下記の[A]と[B]は書き方に違いがあれど、どちらも描画されるビューは全く同じです。
しかしながら、ある理由により、[A][B]いずれかの記述の方が好まれます。より良い記述は[A][B]はどちらでしょうか...?[A]の場合@users = User.all @users.each do |user| = render 'user', user: user[B]の場合@users = User.all = render 'user', users: @users良い記述は[B]となります。
[B]の方が好まれます。
理由は、[B]の方が処理が速いからです。[A]では、@usersにeachを使用して、user一人一人に対してrenderを実行しています。
この書き方だと、userが100人いると100回、50000人いると50000回、どの部分テンプレートを利用するのか探しにいく処理が実行されるため、ユーザーが多ければ多くなるほどパフォーマンスが低下していきます。一方、[B]では、renderする際の変数に、@usersを渡しています。
この書き方だと、どの部分テンプレートを利用するのか探しにいく処理は一度しか実行されないため、[A]より高速にビューを描画することができます。なお、render @users と省略して記述することも可能です。
- 投稿日:2020-03-30T19:40:02+09:00
『Q22 百マス計算で最小のマスをたどると?』もっとプログラマ脳を鍛える数学パズル
はじめに
アルゴリズムの勉強に、もっとプログラマ脳を鍛える数学パズル アルゴリズムが脳にしみ込む70問 を解いています。
経路問題がありましたので、Perl で実装してみました。苦労した所
最小値のマスを取り出すため、2次元配列 cost[r][c] の値でソートし 配列 que に格納された (r, c) 座標を返します。
ruby.rbque.sort_by!{|r, c| cost[r][c]} r, c = que.shiftjavascript.jsque.sort(function(a, b) { return cost[a[0]][a[1]] < cost[b[0]][b[1]]; } var r, c; [r, c] = que.shift();座標の受け渡しについて、後発の言語はスマートな感じがします。
特に、Ruby は、スッキリしていますね。perl.pl@que = sort { $cost[$$_[0]][$$_[1]] } @que; my $in = shift @que; my ($r, $c) = ($$in[0], $$in[1]);全ソース
q22_1.pluse v5.18; use warnings; my @col = (8, 6, 8, 9, 3, 4, 1, 7, 6, 1); my @row = (5, 1, 1, 9, 1, 6, 9, 0, 9, 6); my (@board, @cost); for my $r (0..@row-1) { for my $c (0..@col-1) { $board[$r][$c] = $row[$r] + $col[$c]; $cost[$r][$c] = 2000; } } $cost[0][0] = $board[0][0]; my @que =[0, 0]; while (@que) { # @que = sort { $cost[$$b[0]][$$b[1]] <=> $cost[$$a[0]][$$a[1]] } @que; # 降順用 @que = sort { $cost[$$_[0]][$$_[1]] } @que; my $in = shift @que; my ($r, $c) = ($$in[0], $$in[1]); if (0 <= $r - 1) { my ($x, $y) = ($r - 1, $c); if ($cost[$x][$y] > $cost[$r][$c] + $board[$x][$y]) { $cost[$x][$y] = $cost[$r][$c] + $board[$x][$y]; push @que, [$x, $y]; } } if (0 <= $c - 1) { my ($x, $y) = ($r, $c - 1); if ($cost[$x][$y] > $cost[$r][$c] + $board[$x][$y]) { $cost[$x][$y] = $cost[$r][$c] + $board[$x][$y]; push @que, [$x, $y]; } } if ($r + 1 < @row) { my ($x, $y) = ($r + 1, $c); if ($cost[$x][$y] > $cost[$r][$c] + $board[$x][$y]) { $cost[$x][$y] = $cost[$r][$c] + $board[$x][$y]; push @que, [$x, $y]; } } if ($c + 1 < @col) { my ($x, $y) = ($r, $c + 1); if ($cost[$x][$y] > $cost[$r][$c] + $board[$x][$y]) { $cost[$x][$y] = $cost[$r][$c] + $board[$x][$y]; push @que, [$x, $y]; } } } say $cost[@row - 1][@col - 1];優先度付きキュー
Java では、java.util.PriorityQueue があります。
優先度付きキュー
計算量 O(1) + O(logN)
(上記 sort は O(N) + O(N) )Ruby で実装されている方もいらっしゃいます。
競プロの為にRubyでヒープ(優先度付きキュー)作った(ABC128E)
Ruby で Priority Queue を実装してみたいまとめ
- ダイクストラ法について少しだけ知見を得た
参照したサイト
Perlのdeleteでひっかかった話
- 投稿日:2020-03-30T19:33:46+09:00
bundle installのエラー解決法(can't find gem bundler (>= 0.a) with executable bundler)
エラー内容と状況
エラー内容はタイトルの通り。bundle installしたとき、
can't find gem bundler (>= 0.a) with executable bundler (Gem::GemNotFoundException)
と怒られてしまった。
そもそもこの時はgithubのリモートからpullして、rails sをした時に、bundle install
してくださ〜いってエラーから始まっています。チーム開発でよくある事象かと思い、自分も何回か直面してその都度調べていたので、そろそろ自分でまとめようと思います!開発環境
- ruby 2.5.1p57
- Rails 5.2.4.1
bundlerとは
そもそもbundlerとは何だったのかを復習しましょう!
Railsアプリケーションにおいては非常に複雑なgemの管理をする必要があります。これらのgemを管理するのがbundler
というgemになります。
管理するものが無いのでbundle installできないってことだったのですね。ターミナルでbundlerをインストールするコマンドを入力しましょう。
gem install bundler解決するエラー内容の場合、上記コマンドで解決すると思います。(同様のエラーの記事を読むと)しかし今回は上手くbundlerをインストールできませんでした。
原因
今回の場合、
gemfile.lock
にbundlerのバージョンが記載されてえいるため、バージョンを指定しないとインストールができないことによるものでした。gemfile.lockBUNDLED WITH 2.0.2gem install bundler -v2.0.2上記を実装することでエラーを解決することができます。
- 投稿日:2020-03-30T18:44:31+09:00
DBから取得したデータの中身を取得する方法 Ruby on Rails
MySQLからデータを取得してきた
whereメソッドを使って、特定条件下で検索に該当するクエリを発行させると、条件に当てはまったレコード全てを引っ張ってくる。
log_in.rbyesterday_time = Time.now.yesterday log_in_collection = LogIn.all #LogInというモデルを作成してあり、そこからデータを全て引っこ抜いている log_in_list = log_in_collection.where("loged_in_at >= ?", yesterday_time)そうすると、log_in_listをpメソッドで出力すると、
#<ActiveRecord::Relation [#<LogIn id: 456, uid: "hogehoge", log_in_at: "2020-03-25 05:49:43", created_at: "2020-03-25 05:50:47", updated_at: "2020-03-25 05:50:47">]>こんな感じのデータが取れる。パッと見、ハッシュに見える物が取ってこれているのが分かる。今回必要なのはこのuidにおけるhogehogeだけを引っ張ってくることがゴールです。
以下の自分の考え方に間違いがあったら、指摘をお願いします。
最初に犯したミス
欲しいデータを手に入れたければ、ハッシュのキーになっているものでメソッドチェーンすればいいってことだったので、安直に前述したコードの変数であるlog_in_listを使って、
hogehoge.rbp log_in_list.uidと書いて、hogehogeが取れているかを確認してみました。すると、ログはおろか、localhostでこの処理部分を走らせてみると、画面に真っ赤な文字でundefined uidと表示されてしまう。何でやねん。
ハッシュみたいなものからオブジェクトを取り出すような書き方はできん
ハッシュみたいなやつと書いているのは、ActiveRecordから取ってきているデータのことをなんて呼んでいいかわからないからですが、こう考えてみると、log_in_listを最初にpした時、これはデータの型がハッシュみたいなやつであって、オブジェクトになっていない。これが本当にハッシュで、ハッシュの値を取り出すには、
example.rblog_in_list["uid"]とかにせねばならない。今回のデータはActiveRecordから引っ張ってきているものなので、これでのアクセスもできない・・・ハズ。
また、whereは条件にあった全てのレコードを引っ張ってくるので、条件にあった全てのuidを抜き取る作業を必要とする。それじゃどうするか・・・一旦、このハッシュっぽい奴のデータをオブジェクトにしてやればいい。
hogehoge.rblog_in_list.each do |data| log_in_users = data.uid p log_in_users endeachを使って、log_in_listの中身をdata変数へと入れ込むと、オブジェクト化してくれるので、オブジェクトへのアクセスする書き方が可能になる。これでhogehogeだけを引っ張ってこれる。
補足
後で知ったことですが、ハッシュ用のループ処理で使えるeachもある。この記事によると、やはりハッシュにはハッシュオブジェクトがちゃんと存在してそのオブジェクトへのアクセスの仕方も違うようだ・・・
考察
重要なのは2つあるかなと思いました。
書いているコードに内包されているデータの型を意識的に脳内で可視化すること。脳内で可視化って意味不明かもしれませんが、言わんとしていることは分かると思うんですよ。「今持ってきているデータは〇〇型で、まだ日オブジェクトだな」みたいな意識を持つイメージでしょうか。
また、それをオブジェクト化した時のアクセス方法を知っていること、もしくはそこから検索をかけて調べる足掛かりにする、ということでしょうか。
- 投稿日:2020-03-30T17:33:36+09:00
Ruby on Rails とかで docker 環境なのに #VSCode で ruby-rubocop 拡張を有効にするにはどうすれば良いのか? ( #Rails #Ruby )
- 投稿日:2020-03-30T17:21:50+09:00
vistaでkicad その2
概要
vistaでkicadやってみた。
sketchupで、dxf書いて、kicadで読み込んで見た。環境
windows vista 32bit
kicad 4.0.4
sketchup 8sketchupでbox書く。
skp_to_dxf.rbでdxfを出力。
0 SECTION 2 ENTITIES 0 3DFACE 8 GROUP0 10 2.54 20 2.54 30 0.0 11 0.0 21 0.0 31 0.0 12 0.0 22 2.54 32 0.0 13 0.0 23 2.54 33 0.0 70 1 0 3DFACE 8 GROUP0 10 0.0 20 0.0 30 0.0 11 2.54 21 2.54 31 0.0 12 2.54 22 0.0 32 0.0 13 2.54 23 0.0 33 0.0 70 1 0 3DFACE 8 GROUP0 10 2.54 20 0.0 30 2.54 11 0.0 21 2.54 31 2.54 12 0.0 22 0.0 32 2.54 13 0.0 23 0.0 33 2.54 70 1 0 3DFACE 8 GROUP0 10 0.0 20 2.54 30 2.54 11 2.54 21 0.0 31 2.54 12 2.54 22 2.54 32 2.54 13 2.54 23 2.54 33 2.54 70 1 0 3DFACE 8 GROUP0 10 2.54 20 0.0 30 2.54 11 0.0 21 0.0 31 0.0 12 2.54 22 0.0 32 0.0 13 2.54 23 0.0 33 0.0 70 1 0 3DFACE 8 GROUP0 10 0.0 20 0.0 30 0.0 11 2.54 21 0.0 31 2.54 12 0.0 22 0.0 32 2.54 13 0.0 23 0.0 33 2.54 70 1 0 3DFACE 8 GROUP0 10 0.0 20 2.54 30 2.54 11 0.0 21 0.0 31 0.0 12 0.0 22 0.0 32 2.54 13 0.0 23 0.0 33 2.54 70 1 0 3DFACE 8 GROUP0 10 0.0 20 0.0 30 0.0 11 0.0 21 2.54 31 2.54 12 0.0 22 2.54 32 0.0 13 0.0 23 2.54 33 0.0 70 1 0 3DFACE 8 GROUP0 10 0.0 20 2.54 30 2.54 11 2.54 21 2.54 31 0.0 12 0.0 22 2.54 32 0.0 13 0.0 23 2.54 33 0.0 70 1 0 3DFACE 8 GROUP0 10 2.54 20 2.54 30 0.0 11 0.0 21 2.54 31 2.54 12 2.54 22 2.54 32 2.54 13 2.54 23 2.54 33 2.54 70 1 0 3DFACE 8 GROUP0 10 2.54 20 2.54 30 0.0 11 2.54 21 0.0 31 2.54 12 2.54 22 0.0 32 0.0 13 2.54 23 0.0 33 0.0 70 1 0 3DFACE 8 GROUP0 10 2.54 20 0.0 30 2.54 11 2.54 21 2.54 31 0.0 12 2.54 22 2.54 32 2.54 13 2.54 23 2.54 33 2.54 70 1 0 ENDSEC 0 EOFkicadでdxfインポートする。
うまくいかん。
以上。
:
- 投稿日:2020-03-30T14:18:16+09:00
初めてのBitbucket
rails チュートリアルを進めるにあたって、bitbucketを一番初めに使用するときにわからないことがあって苦しんだためメモ
・cloud9(aws)環境で実施
・アカウント作成は省略公開鍵の作成
consolecat ~/.ssh/id_rsa.pub
上記のコマンド入力で公開鍵を所持しているか確認できます。所持していない場合には
No such file or directory
と表示されます。
鍵を所持していない場合はconsolecd ~/.ssh #sshフォルダに移動 ssh-keygen -t rsa -C saber@example.com # 自分のメールアドレス入力Entキー押すと色々と出てくるが特にいじる必要ないのでそのままEntキー何回か押して鍵を作成。鍵を作成した後に先ほどの
consolecat ~/.ssh/id_rsa.pub
を実行して鍵を表示させます。
公開鍵の追加
bitbucketにログイン後、
ユーザーアイコン
↓
Bitbucket setting
↓
セキュリティー
↓
SSH鍵
↓
鍵追加
↓
さきほど表示した鍵をコピペ
↓
作成
これで完了です。bitbucketへリポジトリ作成
+アイコンを押し、空のリポジトリを作成します。リポジトリ名はなんでも大丈夫せす(わかりやすくするなら自身のアプリケーション名が良いかと)。
bitbucketにリポジトリを作成するとバケツに何かを入れましょうというページに移動しgit remote add origin git@bitbucket.org:ユーザー名/先ほど作成したリポジトリ名.git
とgit push -u origin master
いうコマンドが表示されます。このコードそのまま入力してもいいのですが、まだ自身の環境にリポジトリを作成していない場合はconsolegit init (自身の環境の新しいリポジトリの初期化)ちなみにawsのcloud9の場合はgit導入済みのためこのコマンドが最初から使用できます。
consolegit add -A git commit -m "メッセージ"上記のコマンド実行し、自身のgitにリポジトリを作成したあとは先ほどの
git remote add origin git@bitbucket.org:ユーザー名/作成したリポジトリ名.git
とgit push -u origin master
いうコマンドを実行します。consolegit remote add origin git@bitbucket.org:ユーザー名/先ほど作成したリポジトリ名.git git push -u origin master
これでBitbucketに内容が反映されます。
あとがき
自分なりにわかりやすいよう書いてみましたがわかりにくかったらすみません。
参考になれば幸いです。
- 投稿日:2020-03-30T13:48:59+09:00
環境変数管理の便利ツール
環境変数管理
背景
秘密鍵を使うこともあり,
sample用の.env.sample
と開発用の.env
ファイルを分けている
.env.sample
をコミットに残すため、こちらを正として.env
を管理したいインストール方法
go get github.com/locona/envdef/cmd/envdefUsage
プロジェクトのルートにある.envファイルにアプリケーション構成を追加します。
S3_BUCKET=YOURS3BUCKET SECRET_KEY=YOURSECRETKEYGOESHEREプロジェクトのルートにある
.env.sample
ファイルS3_BUCKET=YOURS3BUCKET SECRET_KEY=YOURSECRETKEYGOESHERE REGION=REGION次に実行します
envdefその結果、
.env.new
ファイルが作成されましたREGION=REGION S3_BUCKET=YOURS3BUCKET SECRET_KEY=YOURSECRETKEYGOESHERE
- 投稿日:2020-03-30T13:21:39+09:00
active_interaction でアプリケーション固有のビジネスロジックを管理する
はじめに
active_interaction というビジネスロジックの秩序を提供してくれる gem があります。
これは Rails における Fat Model や Fat Controller にならない為の一つの解になり得ると注目しています。active_interaction をもっと上手く使いこなそうという考えから、使い方をまとめました。
README の部分的な翻訳 + 理解しやすいように加筆となっています。Active Interactionは、アプリケーション固有のビジネスロジックを管理します。
これはRubyのコマンドパターンを実装したものです。Active Interactionは、ビジネス・ロジックを配置する場所を提供します。
また、入力が期待通りのものであるかどうかを検証することで、より安全なコードを書くことができます。ActiveModel が名詞を扱う場合、Active Interaction は動詞を扱います。なぜ gem を使うの?
Rails: Service Objectはもっと使われてもいい(翻訳)
よくある Service Object は書き手によって書き方が異なるケースが多いです。
gem のレールに乗る事で、誰が書いても同じようなコードになる事を期待しています。Rails での使い方
インタラクションは
app/interaction
に入れることをお勧めします。また、モデルごとにグループ化するのも非常に便利です。
例えば Account モデルに関する処理はapp/interactions/accounts
配下で探すことができます。- app/ - controllers/ - accounts_controller.rb - interactions/ - accounts/ - create_account.rb - destroy_account.rb - find_account.rb - list_accounts.rb - update_account.rb - models/ - account.rb - views/ - account/ - edit.html.erb - index.html.erb - new.html.erb - show.html.erbこの構造を使うためには以下を
application.rb
に追加してください。# config/application.rb config.autoload_paths += Dir.glob("#{config.root}/app/interactions/*")Index
# GET /accounts def index @accounts = ListAccounts.run! endclass ListAccounts < ActiveInteraction::Base def execute Account.not_deleted.order(last_name: :asc, first_name: :asc) end end
ListAccounts
には何も入力を渡さないので、.run
の代わりに.run!
を使うのは理にかなっています。
これが失敗した場合、インタラクションの記述に失敗したことになります。Show
このアクションでは、正しいエラーを発生させるためのヘルパーメソッドを定義します。
.run!
を呼び出した時、エラーが発生するとActiveInteraction::InvalidInteractionError
が発生します。つまり、ActiveRecord::RecordNotFound
ではなくなるので、明示的にfail ActiveRecord::RecordNotFound
と書く必要があります。# GET /accounts/:id def show @account = find_account! end private def find_account! outcome = FindAccount.run(params) if outcome.valid? outcome.result else fail ActiveRecord::RecordNotFound, outcome.errors.full_messages.to_sentence end endこれはおそらく、あなたが慣れ親しんでいるものとは少し違っているように見えます。
Railsは一般的にbefore_action
で@account
を定義します。なぜこのようなインタラクションコードがすべて良いのでしょうか?
理由は2つあります。1つは、APIコントローラやResque
タスクなど、別の場所でFindAccount
インタラクションを再利用できるからです。2つ目は、アカウントの検索方法を変更したい場合は、1つの場所 (今回の場合はFindAccount
クラス) を変更するだけで済むということです。インタラクションの内部では、
#find_by_id
の代わりに#find
を使うことができます。そうすれば、コントローラの#find_account!
ヘルパーメソッドは不要になります。しかし、インタラクションからエラーを発生させないようにしなければなりません。そうすると、結果の妥当性だけでなく例外が発生することにも対処しなければいけないからです。class FindAccount < ActiveInteraction::Base integer :id def execute account = Account.not_deleted.find_by_id(id) if account account else errors.add(:id, 'does not exist') end end end実行中にエラーを追加しても全く問題ないことに注意してください。
すべてのエラーが型チェックやバリデーションに起因するものである必要はありません。New
New は、これまで見てきたものとは少し異なります。
.run
や.run!
を呼び出す代わりに、新しいインタラクションを初期化します。これはインタラクションがActiveModels
のように振る舞うためです。# GET /accounts/new def new @account = CreateAccount.new endインタラクションは
ActiveModels
のように動作するので、ActiveModel
のバリデーションを使用することができます。ここではfirst_name
とlast_name
が空白になっていないことを確認するためにバリデーションを使用します。class CreateAccount < ActiveInteraction::Base string :first_name, :last_name validates :first_name, :last_name, presence: true def to_model Account.new end def execute account = Account.new(inputs) unless account.save errors.merge!(account.errors) end account end endここではいくつかの高度な機能を使用しました。
to_model
メソッドは、ビューで使用する正しいフォームを決定するのに役立ちます。詳細はフォームのセクションをチェックしてください。execute
の中では、エラーをマージします。これはエラーをあるオブジェクトから別のオブジェクトに移動させるのに便利な方法です。詳しくはエラーのセクションを参照してください。Create
createアクションはnewアクションと多くの共通点があります。どちらも
CreateAccount
インタラクションを使用します。アカウントの作成に失敗した場合、このアクションは新しいアクションのレンダリングにフォールバックします。# POST /accounts def create outcome = CreateAccount.run(params.fetch(:account, {})) if outcome.valid? redirect_to(outcome.result) else @account = outcome render(:new) end end
.run
にハッシュを渡さなければならないことに注意してください。nil
を渡すとエラーになります。インタラクションを使用しているので、Strong Parameter は必要ありません。
インタラクションはフィルタで定義されていない入力を無視します。つまり、params.require
やparams.permit
は無くても大丈夫です。Destroy
destroy
アクションは、先ほど書いた#find_account!
ヘルパーメソッドを再利用します。# DELETE /accounts/:id def destroy DestroyAccount.run!(account: find_account!) redirect_to(accounts_url) endこの単純な例では、
destroy
インタラクションはあまり何もしていません。これをインタラクションに入れることで何かを得ることは明らかではありません。しかし、将来的にaccount.destroy
以上のことをする必要があるときには、DestrotAccount
クラスを変更するだけで済むようになるでしょう。Edit
destroy
アクションと同様に、編集は#find_account!
ヘルパーを使用します。
そして、フォームオブジェクトとして使用するための新しいインタラクションのインスタンスを作成します。# GET /accounts/:id/edit def edit account = find_account! @account = UpdateAccount.new( account: account, first_name: account.first_name, last_name: account.last_name) endアカウントを更新するインタラクションは、他のインタラクションよりも複雑です。更新にはアカウントが必要ですが、その他の入力は任意です。省略された場合、その属性は無視されます。属性が存在する場合は、それらを更新します。
ActiveInteractionは入力に対して述語メソッド(
#first_name?
のような)を生成します。これらのメソッドは入力がnil
の場合はfalse
を返し、そうでない場合はtrue
を返します。述語についての詳細は述語のセクションを確認してください。class UpdateAccount < ActiveInteraction::Base object :account string :first_name, :last_name, default: nil validates :first_name, presence: true, if: :first_name? validates :last_name, presence: true, if: :last_name? def execute account.first_name = first_name if first_name? account.last_name = last_name if last_name? unless account.save errors.merge!(account.errors) end account end endUpdate
もうコツがつかめたでしょう。アカウントを取得するために
#find_account!
を使い、UpdateAccount
のための入力を作成します。そしてインタラクションを実行し、更新されたアカウントにリダイレクトするか、編集ページに戻ります。# PUT /accounts/:id def update inputs = { account: find_account! }.reverse_merge(params[:account]) outcome = UpdateAccount.run(inputs) if outcome.valid? redirect_to(outcome.result) else @account = outcome render(:edit) end end突っ込んだ使い方
Callbacks
ActiveModel はコールバックを定義するための強力なフレームワークを提供しています。Active Interaction はそのフレームワークにフックしてインタラクションのライフサイクルの様々な部分にフックすることができます。
class Increment < ActiveInteraction::Base set_callback :type_check, :before, -> { puts 'before type check' } integer :x set_callback :validate, :after, -> { puts 'after validate' } validates :x, numericality: { greater_than_or_equal_to: 0 } set_callback :execute, :around, lambda { |_interaction, block| puts '>>>' block.call puts '<<<' } def execute puts 'executing' x + 1 end end Increment.run!(x: 1) # before type check # after validate # >>> # executing # <<< # => 2利用可能なコールバックは、順に
type_check
、validate
、execute
です。
これらのコールバックにはbefore
、after
、around
のいずれかを設定することができます。Composition
他のインタラクションの中から
#compose
を使ってインタラクションを実行することができます。
インタラクションが成功した場合は、結果を返します(.run
で呼び出した場合と同じように)。
何か問題が発生した場合、実行はすぐに停止し、エラーは呼び出し元に移動します。class Add < ActiveInteraction::Base integer :x, :y def execute x + y end end class AddThree < ActiveInteraction::Base integer :x def execute compose(Add, x: x, y: 3) end end AddThree.run!(x: 5) # => 8他のインタラクションからフィルタを導入するには、
.import_filters
を使用します。入力と組み合わせれば、他のインタラクションに委任するのも簡単です。class AddAndDouble < ActiveInteraction::Base import_filters Add def execute compose(Add, inputs) * 2 end endコンポジションされたインタラクションのエラーには、いくつかの厄介なケースがあることに注意してください。
それらについての詳細は、エラーのセクションを参照してください。トランザクションを貼りたい場合は?
元々はトランザクションをサポートしていたが削除したとのこと。
私たちが元々これらを追加したのは、インタラクションが複雑なビジネスロジックを管理するために使われると考えたからです。実際には、必ずしもそうとは限りません。 ActiveRecord トランザクションの薄いラッパーを提供するより、何も提供しない方が良いかもしれません。
# As it is currently: class AutomaticTransactionInteraction < ActiveInteraction::Base def execute # ... end end # As it might be: class ManualTransactionInteraction < ActiveInteraction::Base def execute ActiveRecord::Base.transaction do # ... end end endトランザクションを貼りたい場合は、後者の方で書けばよくない?という話。
具体例としては、以下のようになるだろうか。def execute ActiveRecord::Base.transaction do compose(A) compose(B) raise ActiveRecord::Rollback if invalid? end end外部リンク
- 投稿日:2020-03-30T12:23:15+09:00
Rails6 のちょい足しな新機能を試す 126(upsert_all bug fix編)
はじめに
Rails 6 に追加された新機能を試す第126段。 今回は、
upsert_all bug fix
編です。
Rails 6.0.0 では、 query のキャッシュが有効になっている状態で、upsert_all
を実行した後、キャッシュがクリアされないというバグがありました。Rails 6.0.1 以降では修正されています。
Ruby 2.6.5, Rails 6.0.2.2, Rails 6.0.1, Rails 6.0.0 で確認しました。
$ rails --version Rails 6.0.2.2今回は、 スクリプトを作成して確認していきます。
Rails プロジェクトを作る
Rails プロジェクトを新たに作成します。
$ rails new rails_sandbox $ cd rails_sandboxUser モデルを作成する
User モデルを作成します。
$ bin/rails g model User name
スクリプトを作成する
スクリプトを作成します。スクリプトは以下の処理を実行します。
- Andy という
name
を持つ User を1つ登録する。- キャッシュを有効にする。
- 登録したUserを
User.first
で検索して、name
を出力する。User.upsert_all
を実行して、 Andy から Bob にname
を更新する。User.first
を使ってname
を出力する。scripts/cache_clear.rbUser.delete_all ActiveRecord::Base.connection.enable_query_cache! User.create(name: 'Andy') user = User.first p user.name User.upsert_all( [ { id: user.id, name: 'Bob', created_at: user.created_at, updated_at: Time.now } ] ) user = User.first p user.nameRails 6.0.0 では
Rails 6.0.0 では、 キャッシュがクリアされないため、名前が変わりません。、
$ bin/rails -v Rails 6.0.0 $ bin/rails runner scripts/cache_clear.rb Running via Spring preloader in process 369 "Andy" "Andy"Rails 6.0.1 以降では
キャッシュがクリアされるため、名前が期待通りに変化します。
$ bin/rails -v Rails 6.0.1 $ bin/rails runner scripts/cache_clear.rb Running via Spring preloader in process 447 "Andy" "Bob"試したソース
https://github.com/suketa/rails_sandbox/tree/try126_clear_cache_in_upsert
参考情報
- 投稿日:2020-03-30T11:34:51+09:00
has_secure_passwordについて
はじめに
パスワードを実装してみたのでメモ
userモデルにパスワードを実装していきます1.has_secure_password
userモデルに
user.rbclass User < ApplicationRecord has_secure_password endと追加することで
(1).セキュアにハッシュ化したパスワードを、データベース内のpassword_digest
という属性に保存できるようになる。(条件あり)
(2).2つのペアの仮想的な属性 (password
とpassword_confirmation
) が使えるようになる。また、存在性と値が一致するかどうかのバリデーションも追加される。
(3).authenticate
メソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。
この3つの機能が使えるようになります。順番に解説していきます。(1)
ここでいうセキュアとは安全という意味です。設定したパスワードを暗号化(ハッシュ化)させて、
password_dijest
というカラムに保存できるようにします。すなわち、Userモデルにpassword_dijest
カラムを追加する必要があります(先ほどの条件とはこのこと)。consolerails generate migration add_password_digest_to_users password_digest:string rails db:migrateまた、パスワードを暗号化するためのgemとして、
bcrypt
(最新ので大丈夫です)というgemをgemfail
に追加してbundle install
する必要があります。(2)
password
とpassword_confirmation
のセットというのはパスワードとパスワード(確認用)ということです。ちなみに、has_secure_password
をUserモデルに追加したことで自動的にこの二つのバリデーションが追加されるようになっていますが、これは更新時には適用されないのでuser.rbvalidates :password, presence: trueと追加しておきましょう。パスワードの長さを制限したいときは
user.rbvalidates :password, presence: true, length: {minimum:5}とすれば5文字以上のパスワードに制限することができます。
(3)
authenticateメソッドではその説明の通り、引数の文字列がパスワードと一致しているか確認してくれます。
consoleUser.create(name: "saber",email: "saber@seihai.com",password: "shirou",password_confirmation: "shirou") user = User.find_by(name:"saber") user.authenticate("kotomine") => false user.authenticate("kiritugu") => false user.authenticate("shirou") => trueといった感じでそのユーザーのパスワードの正誤を確認してくれます。
autuenticate
では引数のパスワードをそのままデータベースの値と参照するわけではなく、一度ハッシュ化(暗号化)してからその値をデータベースにある値と参照してくれます。最後に
has_secure_password
を自分なりにかみくだいてみました。参考になれば幸いです。
- 投稿日:2020-03-30T11:08:56+09:00
dockerでカラムの変更ができないエラー
docker-composeで自作アプリのテーブルをいじっていたらこんなエラーに遭遇しました。
$ docker-compose run --rm app rails g migration rename_star_column_to_topics Starting sakelog_db_1 ... done Error response from daemon: OCI runtime create failed: container_linux.go:346: starting container process caused "exec: \"rails\": executable file not found in $PATH": unknown再起動してみたり、appをdbに書き換えてみたり、色々試しましたが解決できず。
でも解決法は意外と単純でした。
$ docker-compose run --rm app bundle exec rails g migration rename_star_column_to_topics Starting sakelog_db_1 ... done invoke active_record create db/migrate/20200330013557_rename_star_column_to_topics.rbrailsの前に bundle execを付け足したらよかったんですね。
今までよくわからずに放置して使ってたbundle exec。
今回のエラーを経て改めて検索し直したら、
bundle execをつけずに実行すると、グローバルにインストールされているgemが動くのに対し、
bundle execをつけて実行するとプロジェクト内にあるgemが動くんだそうで。なるほど、dockerのことはまだよくわかって無いですが、なんとな〜く理解できてきた気がするぞ。
- 投稿日:2020-03-30T10:07:57+09:00
shoulda-matchersが便利すぎる
はじめに
RSpecを書くときに、
shoulda-matchers
というGemを使うと非常に便利でした。shoulda-matchersとは
Shoulda Matchers provides RSpec- and Minitest-compatible one-liners to test common Rails functionality that, if written by hand, would be much longer, more complex, and error-prone.
Shoulda Matchers
は、手書きで書くと長くて、複雑で、エラーが起きやすいRailsのテストをワンライナーにします。(意訳)
ワンライナーって1行ってことでいいのでしょうか...使用方法
このような
userモデル
に対し、user.rbclass User < ApplicationRecord validates :name, presence: true has_many :topics end
shoulda-matchers
を使うとこのようなRSpecを書くことができます!user_spec.rbrequire 'rails_helper' RSpec.describe User, type: :model do it { is_expected.to validate_presence_of(:name) } it { is_expected.to have_many(:topics) } endバリデーションやアソシエーションのテストを一行で書けるのは便利すぎますね。
普通のRSpecの書き方で書いて、テストケースに抜けや漏れが出てしまうなんてことを防げます。上記の例では
presence
とhas_many
を取り上げましたが、他にも使えそうな書き方がたくさんあるので追記していきたいと思います。
- 投稿日:2020-03-30T10:02:27+09:00
USERにいろいろと機能をつけてく。〜Railsチュートリアル7章〜
いよいよ7章目に突入していきます。
大体10日前くらいからRailsチュートリアルはじめてそろそろ折り返し地点になるのかな。勉強時間はだいたいですけど60時間くらい。この調子でがんばりますよー!さて、今回はユーザーのページ作ったりプロフィール写真載せたりしてきます。
ユーザーを表示する
初めて動的なページを入れていきます。ビューの中にApplicationのデータベースから情報を取り出して各プロフィールの情報をカスタマイズしていきます。Applicationに動的なページを追加する準備としてデバック情報を追加します。
デバック情報ではコントローラはなにを使っているのか?アクションはなに使ってるのかといった動的ページを動かす際に必要な情報を確認することができます。デバック情報入れるには
<%= debug(params) if Rails.env.development? %>これを大元のApplication.htmlのフッターの下辺りに入れる。
Rails.env.developmentとは開発環境のこと。ここだけ表示される用にするよう指定している。railsにはテスト環境 (test)、開発環境 (development)、そして本番環境 (production) の3つの環境がデフォルトで装備されています。
rails consoleは開発環境 (development)です。Applicationを本番環境で実行する場合、本番のデータベースを利用できるよう実行する必要がある。
$ rails server --environment production そのため、rails db:migrateを本番環境で実行して本番データベースを作成します。 $ rails db:migrate RAILS_ENV=production console、server、migrateの3つのコマンドでは、デフォルト以外の環境を指定する方法がそれぞれ異なっているid=1のユーザーにアクセスするためのページのURIは/users/1となるが、現時点ではこのURLを使ってもエラーになります。
/users/1 のURLを有効にするために、routesファイルに以下を追加する。Resources:usersこれを入力すると、ユーザー情報を表示するURLを追加するだけでなく、ユーザーのURLを生成するための多数の名前付きルートと共に、RESTfulなUsersリソースで必要となるすべてのアクションが利用できるようになる。
| HTTPリクエスト | URL | アクション | 名前付きルート | 用途 | --------------------------------------------------- | GET | /users | index | users_path | すべてのユーザーを一覧するページ | | GET |/users/1| show | user_path(user) | 特定のユーザーを表示するページ | | GET |/users/new| new | new_user_path | ユーザーを新規作成するページ (ユーザー登録) | | POST | /users |create| users_path | ユーザーを作成するアクション | | GET | /users/1/edit|edit| edit_user_path(user) | id=1のユーザーを編集するページ | | PATCH | /users/1|update| user_path(user) | ユーザーを更新するアクション | | DELETE | /users/1|destroy| user_path(user) | ユーザーを削除するアクション |この時点でコントローラとroutesは指定できたものの、まだプロフィール画面(ビュー)ができていないためこれを作成していく。
まずapp/views/users/show.html.erbを手動で作成。
ビューへは<%= @user.name %>, <%= @user.email %>を入力。ユーザー名、Emailアドレスを表示する。
コントローラへはdef show @user = User.find(params[:id]) endを追加。ユーザーのid読み出しにはparamsを使う。Usersコントローラにリクエストが正常に送信されると、params[:id]の部分はユーザーidの1に置き換わります。(技術的な補足: params[:id]は文字列型の "1" ですが、findメソッドでは自動的に整数型に変換されます)。このIDを変数@userへ代入し、上のビューへ表示するようにする処理を作成した。
debuggerメソッドを使用する
byebug gemによるdebuggerメソッドを使用すればもっと直接的に確認ができる。
def show @user = User.find(params[:id]) debugger endメソッドを差し込んだらブラウザから /users/1 へアクセスしRailsサーバーを立ち上げてターミナルを確認する。dyebugプロンプトが表示される。
(byebug) @user.name "Example User" (byebug) @user.email "example@railstutorial.org" (byebug) params[:id] "1"このプロンプトではRailsコンソールのようにコマンドを呼び出すことができてApplicationのdebuggerが呼び出された瞬間の状態を確認することができる。
debuggerを終了させる
ControlーDを押すとプロンプトから抜け出せる。デバッグが終わったらshowアクション内のdebuggerの行を削除する。
ユーザー登録フォームを作成する
form_forを使用
ユーザー登録ページにはフォーム(入力する空欄のこと)が必要となる。
Railsではform_forヘルパーメソッドを使用する。
まずform_forの引数で必要となるUserオブジェクトを作成することになります。必要となる@user変数の定義はclass UsersController < ApplicationController def show @user = User.find(params[:id]) end def new @user = User.new end endと入力。
ビューは。。<% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= f.label :name %> <%= f.text_field :name %> <%= f.label :email %> <%= f.email_field :email %> <%= f.label :password %> <%= f.password_field :password %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>と入力する。 f.label :name のfはオブジェクト。Labelは属性の設定。テキストフィールドは記載情報のこと。
正しいフォーム
/usersへのPOSTリクエスト(フォームからの入力情報)はcreateアクションに送られます。ここで、createアクションでフォーム送信を受け取り、User.newを使って新しいユーザーオブジェクトを作成し、ユーザーを保存 (または保存に失敗) し、再度の送信用のユーザー登録ページを表示するという方法で機能を実装する。
コントローラにはこんな入力となる。
def create @user = User.new(params[:user]) # 実装は終わっていないことに注意! if @user.save # 保存の成功をここで扱う。 else render 'new' #失敗したらnewへ戻る。 end endこれは完璧な実装ではない。paramsハッシュ全体を初期化するという行為はセキュリティ上極めて危険だからです。
今のままだと、ユーザーが送信したデータをまるごとUser.newへ渡していることになります。(名前・アドレス・パスワード・再確認用パスワード)
これに追加で別の属性を一部に紛れ込ませて渡せてしまうという危険があります。(管理者権限等も渡せる危険があり非常に危険)Strong Parameters
Rails 4.0では、先述している危険を防止するためコントローラ層でStrong Parametersというテクニックを使うことが推奨されています。
private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end上記はparams[:user]の代わりとして使われる。これを追加することで許可された属性のみが含まれたparamsを使用できるように限定する。
このuser_paramsメソッドはUsersコントローラの内部でのみ実行され、Web経由で外部ユーザーにさらされる必要はない.
Rubyのprivateキーワードを使って外部から使えないようにしている。エラーメッセージ
ユーザー登録に失敗した場合の最後の手順として、問題が生じたためにユーザー登録が行われなかったということをユーザーにわかりやすく伝えるエラーメッセージを追加する。
保存に失敗すると、@userオブジェクトの関連付けされたエラーメッセージの一覧が生成される。これをビューへ表示したい。このメッセージをブラウザで表示するには、ユーザーのnewページでエラーメッセージのパーシャル (partial) を出力します。このとき、form-controlというCSSクラスも一緒に追加することで、Bootstrapがうまく取り扱ってくれるようになります。<% provide(:title, 'Sign up') %> <h1>Sign up</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= form_for(@user) do |f| %> <%= render 'shared/error_messages' %> <%= f.label :name %> <%= f.text_field :name, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation, "Confirmation" %> <%= f.password_field :password_confirmation, class: 'form-control' %> <%= f.submit "Create my account", class: "btn btn-primary" %> <% end %> </div> </div>'shared/error_messages'というパーシャルをrender (描画) している
複数のビューで使われるパーシャルは専用のディレクトリ「shared」によく置かれます
よって$ mkdir app/views/sharedにて新しくディレクトリを作成。ここにファイルも作成し、下記を入力。
<% if @user.errors.any? %> →#もし変数ユーザーにエラーが一つでもあったら <div id="error_explanation"> <div class="alert alert-danger"> The form contains <%= pluralize(@user.errors.count, "error") %>. </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>メソッドがcountとany?の2つ使用されている。countは上記の内容だとエラーの数を返してくれる。any?はempty?と逆の動作で要素が一つでもある場合はtrueを返す。
pluralizeは英語専用のテキストヘルパー単数形と複数形をcountの数に応じて変換し出力してくれる。失敗時のテスト
railsではフォーム用のテストを自動的に書くことができる。
$ rails generate integration_test users_signup invoke test_unit create test/integration/users_signup_test.rbここではユーザー登録ボタンを押したときに (ユーザー情報が無効であるために) ユーザーが作成されないことを確認します。
test/integration/users_signup_test.rb require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest test "invalid signup information" do get signup_path assert_no_difference 'User.count' do post users_path, params: { user: { name: "", email: "user@invalid", password: "foo", password_confirmation: "bar" } } end assert_template 'users/new' end endユーザー登録成功
ここまでではまだ完成ではなく、デフォルトのアクションに対応するビューを表示しようとするもcreateアクションに対応するビューのテンプレートがないためエラーが表示されてしまう。
createアクションに対応するテンプレートを作成することもできますが、Railsの一般的な慣習に倣って、ユーザー登録に成功した場合はページを描画せずに別のページにリダイレクト (Redirect) するよう設定する。
具体的には、新しく作成されたユーザーのプロフィールページにリダイレクトする。
class UsersController < ApplicationController . . . def create @user = User.new(user_params) if @user.save redirect_to @user →ユーザーURLへ飛ばすを意味する else render 'new' end end private def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end endさらにここに、登録完了後に表示されるページにメッセージを表示し (この場合は新規ユーザーへのウェルカムメッセージ)、2度目以降には表示しないようにするFrashという変数を使用する。
def create @user = User.new(user_params) if @user.save flash[:success] = "Welcome to the Sample App!" redirect_to @user else render 'new' end endこのとき、:successキーはシンボルでしたが、テンプレート内に反映させる際に埋め込みRubyが自動的に"success"という文字列に変換している点に注意してください。この性質を利用することで、キーの内容によって異なったCSSクラスを適用させることができ、メッセージの種類によってスタイルを動的に変更させることができる。
frash変数の内容をWebサイトのレイアウトに追加
<!DOCTYPE html> <html> . . . <body> <%= render 'layouts/header' %> <div class="container"> <% flash.each do |message_type, message| %> <div class="alert alert-<%= message_type %>"><%= message %></div> <% end %> <%= yield %> <%= render 'layouts/footer' %> <%= debug(params) if Rails.env.development? %> </div> . . . </body> </html>成功時のテスト
有効な送信に対するテストを書いていく。これによって、アプリケーションの振る舞いを検証し、もし今後バグが埋め込まれたらそれを検知できるようになります。今回の目的はデータベースの中身が正しいかどうか検証することです。すなわち、有効な情報を送信して、ユーザーが作成されたことを確認します。
require 'test_helper' class UsersSignupTest < ActionDispatch::IntegrationTest . . . test "valid signup information" do get signup_path assert_difference 'User.count', 1 do post users_path, params: { user: { name: "Example User", email: "user@example.com", password: "password", password_confirmation: "password" } } end follow_redirect! →POSTリクエストを送信した結果を見て、指定されたリダイレクト先に移動するメソッド assert_template 'users/show' end end一応7章目終了できました。やっていて思ったことは内容自体はprogateやってれば理解できる内容なのかなと思います。しかし、とにかくテストの書き方が難しい。これちゃんと自分で考えてできるようになるんですかね。
とりあえず今日はここまで。
- 投稿日:2020-03-30T01:25:17+09:00
【MySQL版】プログラミング初心者と行うrails環境構築講座【Win版】
プログラミング初心者向けのRuby on Rails環境構築をまとめた記事です。
なかなか一つにまとまっている記事が見つからず、時間がかかったので忘備録的な意味も込めてここに記したいと思います。動作環境
OS:Win10 (64bit)
Ruby2.4.9(x64)
Rails 5.0.7.2
db:MySQL以上が僕のRailsを動かすにあたる環境です。
まず最初にこれらのものを順にインストールしていきましょう。Rubyのインストール
インストーラのダウンロード
こちらのDEVKITの欄からRuby+Devkitをダウンロードしてください。
(64bitの方はx64、32bitの方はx86)
※バージョンはなんでも構いませんが、何もわからない!という方は推奨verの2.6.5-1をダウンロードするといいでしょう。インストール
インストーラ(~.exe)がダウンロードされたら、実行してインストールを行いましょう。
何もわからない場合はデフォルトのまま進めちゃってokです。
finishしたら、大きくRubyInstallerと書かれた画面に遷移しますが、指示通りに進めて大丈夫です。インストールできたか確認
ターミナルで下記のコマンドを実行して、無事インストールできたか確かめます。
$ ruby -v自分がインストールしたrubyのバージョンが表示されればokです。
バージョン確認できなかった場合
PATHが通ってない可能性があるので、Windowsの設定を開き、検索ボックスで環境変数を検索、環境変数の編集に飛んでいただき、上部のユーザーの環境変数からpathを選択して、編集。
インストールしたrubyフォルダのbinディレクトリを新規として追加しましょう。
※pathが存在しなかった場合、下部のシステム環境変数からpathを新規として追加してください。Railsのインストール
railsのインストールは簡単で下記コマンドを実行するだけです。
$ gem i rails #最新バージョンがインストールされる $ gem i -v (バージョン番号) rails上のコマンドだと最新のバージョンがインストールされてしまうので、それでは不都合が生じてしまう方は下のコマンドでバージョンを指定してください。
インストール確認
$ rails -vこれはお決まりです
MySQLのインストール
railsではsqlite3がデフォルトデータベースに設定されていますが、それだと僕は環境構築時に自力で解決できない不具合が発生したのでMySQLにしています。
また、現在自分自身データベースの知識が不足しているので、細かいことはよくわかりません。
ここには自分がうまくいった手順を載せています。インストーラのダウンロード
こちらから下のサイズが大きいものをダウンロードしてください。
oracleアカウントにログインさせようとしてきますが、今回は無視して下部のNo thanks~~をクリックしてダウンロードしましょう。インストール
インストーラ(~.msi)を実行しましょう。
これも基本的にデフォルトのまま適宜Executeボタンを押しつつ進めちゃってください。Accounts and Rolesの設定
Accounts and Rolesと書かれた画面が出てきたら上部のボックスに希望のパスワードを入力してください。
これがrootアカウントのパスとなるため、メモを控えといてください。次にMySQLアカウントを追加します。
Add Userボタンを押して希望のユーザー名、パスワードを入力(他はデフォルトのままで)し、ユーザーを追加します。
ここでのパスワードはrootアカウントのものと異なっていてもokです。この作業が終了したら、あとは前と同じようにデフォルトのまま進めていけばインストールが完了します。
インストール確認
$ mysql --versionバージョン確認できなかった場合
Rubyの場合と同じくPATHが通ってない可能性があるので、設定からPATHにインストールしたMYSQLフォルダのbinディレクトリを新規として追加しましょう。
Railsアプリの作成、設定
ここまででrailsアプリの作成に必要な最低限のソフトのインストールが終了しました。
ここからは環境に合わせつつ、Railsで作成するアプリの設定を行っていきます。Railsアプリの作成
下記コマンドを実行します。
$ cd (任意のディレクトリ名) #railsアプリを設置したいディレクトリに移動 $ rails new (任意のrailsアプリ名) --database=mysql #デフォルトのdbをmysqlに変更してアプリ作成これでrailsアプリが作成されます。
railsアプリデータベースの作成
database.ymlの編集
作成されたrailsアプリフォルダ内のconfig内に存在するdatabase.ymlファイルを編集します。
上のほうにあるデフォルトのusername
とpassword
だけ下記のように編集してください。default: &default adapter: mysql2 encoding: utf8 pool: 5 username: (MySQLインストール時に作成した名前) password: (MySQLインストール時に作成したパスワード) host: localhostデータベース作成
下記のコマンドをrailsアプリ上で実行し、データベースを作成してください。
$ rails db:create開発環境構築完了!!!
ひとまずRailsアプリを開発する環境はこれで整いました。
自分がこれまで学んできたことや、参考書、サイトなどを活かしてrailsアプリの開発を行いましょう。※herokuにデプロイして公開する記事も後日執筆予定です。
※何か間違っている点、疑問点等ございましたらコメントお願いします。
- 投稿日:2020-03-30T01:25:17+09:00
【MySQL版】プログラミング初心者と行うRuby on Rails環境構築講座【Win版】
プログラミング初心者向けのRuby on Rails環境構築をまとめた記事です。
なかなか一つにまとまっている記事が見つからず、時間がかかったので忘備録的な意味も込めてここに記したいと思います。注意
本記事では最低限の環境構築しか行わないため、手っ取り早くrailsアプリの開発が行いたい初心者さん向けの記事です。
様々な拡張機能や応用などには対応しきれていない部分もあるので、必要な方は個人で調べてください。
動作環境
OS:Win10 (64bit)
Ruby2.4.9(x64)
Rails 5.0.7.2
db:MySQL以上が僕のRailsを動かすにあたる環境です。
まず最初にこれらのものを順にインストールしていきましょう。※特にバージョンの指定などはありませんが、不安な場合は僕と同じバージョンをインストールしたほうが確実だと思います。
Rubyのインストール
インストーラのダウンロード
こちらのDEVKITの欄からRuby+Devkitをダウンロードしてください。
(64bitの方はx64、32bitの方はx86)
※バージョンはなんでも構いませんが、何もわからない!という方は推奨verの2.6.5-1をダウンロードするといいでしょう。インストール
インストーラ(~.exe)がダウンロードされたら、実行してインストールを行いましょう。
何もわからない場合はデフォルトのまま進めちゃってokです。
finishしたら、大きくRubyInstallerと書かれた画面に遷移しますが、指示通りに進めて大丈夫です。インストールできたか確認
ターミナルで下記のコマンドを実行して、無事インストールできたか確かめます。
$ ruby -v自分がインストールしたrubyのバージョンが表示されればokです。
バージョン確認できなかった場合
PATHが通ってない可能性があるので、Windowsの設定を開き、検索ボックスで環境変数を検索、環境変数の編集に飛んでいただき、上部のユーザーの環境変数からpathを選択して、編集。
インストールしたrubyフォルダのbinディレクトリを新規として追加しましょう。
※pathが存在しなかった場合、下部のシステム環境変数からpathを新規として追加してください。Railsのインストール
railsのインストールは簡単で下記コマンドを実行するだけです。
$ gem i rails #最新バージョンがインストールされる $ gem i -v (バージョン番号) rails上のコマンドだと最新のバージョンがインストールされてしまうので、それでは不都合が生じてしまう方は下のコマンドでバージョンを指定してください。
インストール確認
$ rails -vこれはお決まりです
MySQLのインストール
railsではsqlite3がデフォルトデータベースに設定されていますが、それだと僕は環境構築時に自力で解決できない原因不明の不具合が発生したのでMySQLにしています。
(友人も同じ現象が起こったため、この問題で悩んでる方、多いと思います)また、現在自分自身データベースの知識が不足しているので、細かいことはよくわかりません。
ここには自分がうまくいった手順を載せています。インストーラのダウンロード
こちらから下のサイズが大きいものをダウンロードしてください。
oracleアカウントにログインさせようとしてきますが、今回は無視して下部のNo thanks~~をクリックしてダウンロードしましょう。インストール
インストーラ(~.msi)を実行しましょう。
これも基本的にデフォルトのまま適宜Executeボタンを押しつつ進めちゃってください。Accounts and Rolesの設定
Accounts and Rolesと書かれた画面が出てきたら上部のボックスに希望のパスワードを入力してください。
これがrootアカウントのパスとなるため、メモを控えといてください。次にMySQLアカウントを追加します。
Add Userボタンを押して希望のユーザー名、パスワードを入力(他はデフォルトのままで)し、ユーザーを追加します。
ここでのパスワードはrootアカウントのものと異なっていてもokです。この作業が終了したら、あとは前と同じようにデフォルトのまま進めていけばインストールが完了します。
インストール確認
$ mysql --versionバージョン確認できなかった場合
Rubyの場合と同じくPATHが通ってない可能性があるので、設定からPATHにインストールしたMYSQLフォルダのbinディレクトリを新規として追加しましょう。
ターミナルがGit bashの場合
自分はgit bashをターミナルとして用いているのですが、MySQLコマンドを実行する場合は先頭に必ず
winpty
をつけないと実行できないようです。(原因はわからず、、、)Railsアプリの作成、設定
ここまででrailsアプリの作成に必要な最低限のソフトのインストールが終了しました。
ここからは環境に合わせつつ、Railsで作成するアプリの設定を行っていきます。Railsアプリの作成
下記コマンドを実行します。
$ cd (任意のディレクトリ名) #railsアプリを設置したいディレクトリに移動 $ rails new (任意のrailsアプリ名) --database=mysql #デフォルトのdbをmysqlに変更してアプリ作成これでrailsアプリが作成されます。
railsアプリデータベースの作成
database.ymlの編集
作成されたrailsアプリフォルダ内のconfig内に存在するdatabase.ymlファイルを編集します。
上のほうにあるデフォルトのusername
とpassword
だけ下記のように編集してください。default: &default adapter: mysql2 encoding: utf8 pool: 5 username: (MySQLインストール時に作成した名前) password: (MySQLインストール時に作成したパスワード) host: localhostデータベース作成
下記のコマンドをrailsアプリ上で実行し、データベースを作成してください。
$ rails db:create開発環境構築完了!!!
ひとまずRailsアプリを開発する環境はこれで整いました。
自分がこれまで学んできたことや、参考書、サイトなどを活かしてrailsアプリの開発を行いましょう。※herokuにデプロイして公開する記事も後日執筆予定です。
※何か間違っている点、疑問点等ございましたらコメントお願いします。