- 投稿日:2020-11-23T23:47:09+09:00
[初心者向け]怪奇!Herokuで画像投稿する際の謎のエラー
- 投稿日:2020-11-23T23:23:44+09:00
初期データをseeds.rbに記述して、ゲストログイン機能のエラーを解決してみた
はじめに
ゲストログイン機能を実装したが、ユーザー登録以前に、2つの情報を登録し、ユーザーと紐づけていたので、それを解決するために、試行錯誤した結果、初期データの作成にたどり着いた。
seeds.rbとは
Rails6.0では、(もう少し前からだとは思うが、)デフォルトで入っているファイル。dbディレクトリの配下にある。最初から、コメントアウトで色々記述されているが、説明なので、消してしまって構わない。
記述方法
seeds.rb
には、初期データとして作成しておきたい(テスト用などの目的)データを直接作る。seeds.rbUser.create!(name: 'ゲスト', email: 'gest@sample.com')
モデル名.create!(カラム名: 値)
が基本形。
create!
の部分は他にも、いくつか使えるメソッドがある。(次回、紹介予定)
上記のように、書けば、いくつでも初期データを作成できる。モデル名の部分を他に変えれば、別のテーブルにも作成可能。カラム名にidを用いることもできるので、いつもは自動で振り当てられるidについても、任意で作成可能。
ちなみに、eachメソッドやtimesメソッドを使って、繰り返し処理によって、大量の初期データを作成することも可能。
初期データ生成方法
ターミナルで、
rails db:seedを実行。特にエラーが無ければ、特に反応なく、次の行にいき、待機状態となる。(success!みたいに、表示してくれれば安心なのに…)。テーブルで実際に保存されているか、確認するとよい。
最後に
これで本番環境でも、生成のコマンドさえ実行すれば、無事に、ゲストログインもできるはず!
- 投稿日:2020-11-23T23:14:18+09:00
「N+1問題」とは??
1. 「N+1問題」とは??
アソシエーションを利用した際に、データベースへのアクセス回数が多くなってしまう問題を「N+1問題」といいます
例えば1つのツイート投稿(tweet)は1人のユーザー(user)とアソシエーションで結びついているとします。そして、データベースから全てのツイート投稿を取得するとアソシエーションによりユーザー情報も取得しようとします。
この時、次のコードだとターミナルではこのようなログを示します。class TweetsController < ApplicationController def index @tweets = Tweet.all end end
水色の箇所がTweetsテーブルとUsersテーブルにアクセスしているログです。
Tweetsテーブルに対しては1回で全ての情報を取得していますが、Usersテーブルには6回アクセスしています。
(このときTweetsテーブルに存在するTweetのレコードは6つです。)
取得したTweet1つ1つに対して、アソシエーションを使ってユーザーのレコードを取得する処理を繰り返していることを示しています。つまり100個のツイート投稿を取得すると、100回Usersテーブルにアクセスすることになり、データ取得に時間がかかります。その結果、アプリケーションのパフォーマンスが著しく下がることになります。これが「N+1問題」です。これを解決するためには、includesメソッドを利用します。2. includesメソッド
モデル名.includes(:アソシエーションで紐付くモデル名)includesメソッドは、引数に指定された関連モデルを1度のアクセスでまとめて取得するメソッドです。このメソッドを用いて以下のようにコードを書き換えます。
class TweetsController < ApplicationController def index @tweets = Tweet.includes(:user) end end
結果、水色の箇所が2つに変わりました。Tweetsテーブルに対して1回、Usersテーブルに対しても1回のアクセスで全ての情報を取得できました。3. さいごに
私自身、今まで勉強する中で膨大なデータの使用を想定した開発をしていませんでした。そのため「N+1問題」を勉強して、アプリケーションのパフォーマンスを意識した開発する重要性を感じました。この記事を読んで、内容が良かったらLGTMお願いします!また、ご意見があれば是非コメントもお願いします。
- 投稿日:2020-11-23T22:44:40+09:00
[Rails]collection_selectについて勉強してみた![初心者]
はじめに
現在作成しているアプリの中で、
collection_select
を使用する場面があり、備忘録のためにアウトプットします。
正直、このメソッドにたどり着くまでに、メチャクチャ時間がかかりました笑
collection_select
とは、モデルの情報を元に、セレクトボックスを生成できるメソッドです。
具体的に見ていきましょう!!やりたかったこと
セレクトボックス内に、ユーザーの登録済住所を選択肢として用意したかったのですが、どのような記載方法が適切なのか、全然分からなくて、けっこう長い時間グーグル検索して辿り着いたのが、
collection_select
です。
このセレクトボックスをクリックすると、
というように、登録している住所一覧が出るようにしたかったです!!使い方
collection_select(オブジェクト名, メソッド名, 要素の配列, value属性の項目, テキストの項目 [, オプション or HTML属性 or イベント属性])f.collection_select(メソッド名, オブジェクトの配列, value属性の項目, テキストの項目 [, オプション or HTML属性 or イベント属性])使用例
<%= f.collection_select(:address_id, @addresses, :id, :order_address, prompt: "選択してください") %>上の
collection_select
がHTMLでどうなっているのか確認すると、こうなっていました。<select name="order[address_id]" id="order_address_id"><option value="">選択してください</option> <option value="1">1111111京都市田中</option> <option value="2">2222222アメリカ佐藤</option> <option value="3">3333333インド安井</option> </select>それぞれの引数について確認しましょう!!
第一引数 → :address_id
第一引数(プロパティ名)は、selectタグのid属性とname属性に関係しています。
今回で言うと、
name="order[〜〜〜]" id="order_〜〜〜"
という風に反映されています。第二引数 → :@addresses
第二引数(オブジェクトの配列)は、コントローラで
@addresses = Address.where(customer_id: current_customer.id)
と定義しています。
つまり、現在ログインしているユーザー(cuurent_customer)の登録済住所を全て取得しているということです。第三引数 → :id
第三引数(value属性の項目)は、
<option value="〜〜〜">
において。設定したい値のカラム名が入ります。
今回はidカラムの値を設定するので、上記記述となります。第四引数 → :order_address
第四引数(テキストの項目)は、optionタグ内のテキスト
<option>〜〜〜</option>
に設定したい値のカラム名が入ります。第五引数 → prompt: "選択してください")
最後はオプションですが、これを付け足すことにより、
選択してください
の文言が一番上に表示されるようになります。
- 投稿日:2020-11-23T22:40:16+09:00
�【Rails】重複した要素を取り除くメソッド!
現在作っているウェブアプリで、記事を投稿したユーザーの画像と名前をサイドバーに一覧表示したい!と思って使ったメソッドの話です。
まずやってみたのは、controllerで投稿ユーザーの情報をインスタンス変数 @post_usersに代入。
def post_users @post_users = Post.select(:user_id) endその後、viewのファイルで@post_usersから要素を取り出して表示。
しかしこのやり方だと(当然ですが)ブログの投稿の数だけユーザーの写真と名前が表示されてしまいます(写真ではuserの象が2つ出てきている)。
そこで使ったのがdistinctメソッドです。Railsガイドにも記述がありました。
https://railsguides.jp/active_record_querying.html
(下記Railsガイドより)特定のフィールドについて、重複のない一意の値を1レコードだけ取り出したい場合、distinctを使用できます。
Client.select(:name).distinct
上のコードを実行すると、以下のようなSQLが生成されます。SELECT DISTINCT name FROM clients
「重複のない一意の値を1レコードだけ取り出したい場合」というのがいかにもRailsガイドっぽい言い回しの気がしますが、、、要するに
モデル.select(:取得する列).distinct"
の形にすれば、重複した要素をはじいてくれるようです。早速controllerにdistinctを追加。
def post_users @post_users = Post.select(:user_id).distinct end
- 投稿日:2020-11-23T22:19:17+09:00
Railsアプリケーション全体のジェネレーターの設定変更方法
Railsアプリケーション全体のジェネレータの設定変更方法について、調べながら学習したのでここにアウトプットさせていただきます。
- 開発環境
- Ruby 2.6.4
- Rails 5.2.3
設定方法
Railsアプリ全体のジェネレーターの設定を変更するには、
config/application.rb
ファイルに設定コードを追記してあげると変更する事ができます。
application.rb
ファイルのclass Application < Rails::Application
の記述下に、config.generators do |g| end以上のコードを追記し、
config.generators do |g| 〜 end
の間に設定コードを追記します。config.generators do |g| #ここに設定コードを追記する end今回は
Rails generate controller
でファイルを自動生成する際に、asset, helper, testファイル, ルーティング
が生成されない様に制限を設けたかったので、以下の4行のコードを追記しました。config.generators do |g| g.assets false #assetsを生成しない g.helper false #helperを生成しない g.test_framework false #testファイルを生成しない g.skip_routes true #ルーティングを生成しない endこれでRailsアプリケーション全体のジェネレーターの設定を変更する設定ができました。
あとはこの状態で
rails g controller user show
の様なジェネレーターコマンドを入力すると、上記の様に必要なディレクトリやファイルだけを生成する事ができます。
終わり
以上でアウトプットを終わります。
最後まで読んでいただきありがとうございました。
少しでもお役に立てれる内容であれば幸いです。
- 投稿日:2020-11-23T22:15:29+09:00
Ruby ② 配列・繰り返し処理(each文)・ハッシュ・nil(何もない)
配列
配列とは、複数の値をまとめて管理するために用いる
["John", "Mike", "Emily"] [10, 20, 30]値の間にはコンマで区切る
文字列には "~" をつける配列を変数に代入
names = ["John", "Mike", "Emily"] puts names # John # Mike # Emilyインデックスで管理
names = ["John", "Mike", "Emily"] puts names[1] # Mikeインデックスの番号は0から始まるため
0 → John
1 → Mike
2 → Emily
となる配列と変数展開
names = ["John", "Mike", "Emily"] puts "私の名前は #{names[1]} です" # 私の名前は Mike です繰り返し処理(each文)
基礎文法
配列.each do |変数名| #実行したい処理 endnames = ["John", "Mike", "Emily"] names each.do|name| puts name end # John # Mike # Emily配列内の値が一つずつ変数に入る
変数が使用できる範囲はendまでの間ハッシュ
ハッシュとは変数と同様に複数の値をまとめて管理できる
配列と違う点は
①キーを用いること
②[ ]ではなく{ }を使う基礎文法
{キー1 => 値1 , キー2 => 値2}{"name" => "John", "age" => 20}ハッシュを変数に代入
user = {"name" => "John", "age" => 20} puts user # {"name" => "John", "age" => 20}キーで管理
user = {"name" => "John", "age" => 20} puts user[name] # Johnハッシュの要素を更新
user = {"name" => "John", "age" => 20} puts user[age] user[age] = 21 puts user[age] # 20 # 21ハッシュの要素を追加
user = {"name" => "John", "age" => 20} puts user user["Birthplace"] = "NewYork" puts user # {"name" => "John", "age" => 20} # {"name" => "John", "age" => 20, "Birthplace" => "NewYork"}ハッシュの別の書き方
{"name" => "John", "age" => 20} ↓ {:name => "John", :age => 20}文字列ではなく先頭にコロンを付けた書き方もできる
この書き方を シンボル というハッシュのキーにシンボルを用いる
user = {"name" => "John", "age" => 20} puts user["name"] user = {:name => "John", :age => 20} puts user[:name] # John # John出力結果は同じになる
ハッシュのキーにシンボルを用いた時の省略方法
user = {:name => "John", :age => 20} ↓ user = {name : "John", age : 20}:name => を name: に省略可能
user = {name : "John", age : 20} puts user[:name]省略しても同様にシンボルで出力できる
nil (何もない)
nilとは、ハッシュから存在しないキーの値を取り出した時の「何もない」という値
user = {name : "John", age : 20} puts user[:Birthplace] # 表示なし (nil)nilの回避方法
nilの回避方法としてif文を使用する
true → false と nil以外
false → false と niluser=[name:"John",age:20] if puts user[:age] # ageの値があるため true となる puts "#{user[:name]}さんは#{user[:age]}歳です" else puts "#{user[:name]}さんの年齢は秘密です" end # Johnさんは20歳ですuser=[name:"John"] if puts user[:age] # ageの値がないため false となる puts "#{user[:name]}さんは#{user[:age]}歳です" else puts "#{user[:name]}さんの年齢は秘密です" end # Johnさんの年齢は秘密です配列の要素にハッシュを入れる
基礎文法
[ {キー1 : 値1} , {キー2 : 値2}][ {name : "John",age : 20} , {name : "Mike",age : 21}] [ {name : "John",age : 20} , {name : "Mike",age : 21} ][ ]で囲み、ハッシュをコロンで区切る
要素ごとに縦に書くこともできるeach文を使う
users=[ {name : "John",age : 20} , {name : "Mike",age : 21} ] users.each do|user| puts user[:name] end # John # Mike配列の各要素が順に変数(user)に入る
- 投稿日:2020-11-23T22:15:29+09:00
Ruby ② 配列・繰り返し処理(each文)・ハッシュ・nil(何もない)
配列
配列とは、複数の値をまとめて管理するために用いる
["John", "Mike", "Emily"] [10, 20, 30]値の間にはコンマで区切る
文字列には "~" をつける配列を変数に代入
names = ["John", "Mike", "Emily"] puts names # John # Mike # Emilyインデックスで管理
names = ["John", "Mike", "Emily"] puts names[1] # Mikeインデックスの番号は0から始まるため
0 → John
1 → Mike
2 → Emily
となる配列と変数展開
names = ["John", "Mike", "Emily"] puts "私の名前は #{names[1]} です" # 私の名前は Mike です繰り返し処理(each文)
基礎文法
配列.each do |変数名| #実行したい処理 endnames = ["John", "Mike", "Emily"] names each.do|name| puts name end # John # Mike # Emily配列内の値が一つずつ変数に入る
変数が使用できる範囲はendまでの間ハッシュ
ハッシュとは変数と同様に複数の値をまとめて管理できる
配列と違う点は
①キーを用いること
②[ ]ではなく{ }を使う基礎文法
{キー1 => 値1 , キー2 => 値2}{"name" => "John", "age" => 20}ハッシュを変数に代入
user = {"name" => "John", "age" => 20} puts user # {"name" => "John", "age" => 20}キーで管理
user = {"name" => "John", "age" => 20} puts user[name] # Johnハッシュの要素を更新
user = {"name" => "John", "age" => 20} puts user[age] user[age] = 21 puts user[age] # 20 # 21ハッシュの要素を追加
user = {"name" => "John", "age" => 20} puts user user["Birthplace"] = "NewYork" puts user # {"name" => "John", "age" => 20} # {"name" => "John", "age" => 20, "Birthplace" => "NewYork"}ハッシュの別の書き方
{"name" => "John", "age" => 20} ↓ {:name => "John", :age => 20}文字列ではなく先頭にコロンを付けた書き方もできる
この書き方を シンボル というハッシュのキーにシンボルを用いる
user = {"name" => "John", "age" => 20} puts user["name"] user = {:name => "John", :age => 20} puts user[:name] # John # John出力結果は同じになる
ハッシュのキーにシンボルを用いた時の省略方法
user = {:name => "John", :age => 20} ↓ user = {name : "John", age : 20}:name => を name: に省略可能
user = {name : "John", age : 20} puts user[:name]省略しても同様にシンボルで出力できる
nil (何もない)
nilとは、ハッシュから存在しないキーの値を取り出した時の「何もない」という値
user = {name : "John", age : 20} puts user[:Birthplace] # 表示なし (nil)nilの回避方法
nilの回避方法としてif文を使用する
true → false と nil以外
false → false と niluser=[name:"John",age:20] if puts user[:age] # ageの値があるため true となる puts "#{user[:name]}さんは#{user[:age]}歳です" else puts "#{user[:name]}さんの年齢は秘密です" end # Johnさんは20歳ですuser=[name:"John"] if puts user[:age] # ageの値がないため false となる puts "#{user[:name]}さんは#{user[:age]}歳です" else puts "#{user[:name]}さんの年齢は秘密です" end # Johnさんの年齢は秘密です配列の要素にハッシュを入れる
基礎文法
[ {キー1 : 値1} , {キー2 : 値2}][ {name : "John",age : 20} , {name : "Mike",age : 21}] [ {name : "John",age : 20} , {name : "Mike",age : 21} ][ ]で囲み、ハッシュをコロンで区切る
要素ごとに縦に書くこともできるeach文を使う
users=[ {name : "John",age : 20} , {name : "Mike",age : 21} ] users.each do|user| puts user[:name] end # John # Mike配列の各要素が順に変数(user)に入る
- 投稿日:2020-11-23T21:50:16+09:00
README に記述するアプリ開発の背景
はじめに
個人開発したオリジナルアプリのREADMEを記述しています。このアプリを開発した背景や問題、課題を記載しました。問題と背景の切り分け、考え方の本質を書き記します。
目次
1.問題と課題の違い
2.オリジナルアプリの開発背景から設定した問題と課題1.問題と課題の違い
辞書などにおける意味は下記の通り。言葉そのもの意味に差異はない。
問題:批判・論争・研究などの対象となる事柄。解決すべき事柄。課題。
課題:解決しなければならない問題。果たすべき仕事。しかし、ビジネスにおいては明確な違いがある。主に下記のような意味で使われる。
問題:好ましくない状態
課題:問題を解決すべく行うこと問題と課題の因果関係は上位に問題、下位に課題となる。基本的には1つの問題に対し、複数の課題がぶら下がる。逆算的に見れば複数ある下位の課題を全て解決すると、上位の問題も解決されることになる。
オリジナルアプリの開発背景から設定した問題と課題
背景
コロナ渦をきっかけとしてリモートワークが増えており、それに伴い自宅にいる時間も増えてる。普段の職場における労働と比べ、具体的には下記2点の傾向が増加していると考えた。・一人で部屋にこもりがち。
・仕事とプライベートの切り替えが曖昧。これまで仕事をしない時間帯や場所で仕事をする。問題
下記2点の理由から「メンタルヘルスが悪化する可能性がある」を問題として設定した(状態)。・人との接点が減り孤独を感じやすくなる
・デスク周りの汚れや整理整頓に対し、他者から指摘されない。部屋が汚く無意識にストレスを感じる。課題
メンタルヘルスが悪化するという問題に対し、下記2点を課題とした(やること)。
・対面に変わる、孤独を感じない仕組みの確立
・清潔が保たれている空間の確保
そして各課題をさらに細かく分類し、具体的な機能を7つ抽出した。逆算的にみれば抽出した7つの機能を満足することにより2つの課題は解決され、最終的には問題も解決できる。
以上
- 投稿日:2020-11-23T21:37:20+09:00
個人アプリ開発日記 #4
では前回書いた様々なメソッドが機能してくれるのか簡単なビューを使って確かめてみましょう、、!
まずは新規登録から
このような簡単なビューで新規登録
users/new.html
<% if current_user %> <h1>welcome to my app</h1> <% else %> <%= link_to "ログイン", login_path%> <% end %> <h2><%= link_to "ユーザー一覧",users_path %></h2> <h2><%= link_to "投稿一覧", drinks_path %></h2> <% unless logged_in? %> <div class="col-md-6 col-md-offset-3"> <%= render 'shared/form' %> </div> <% end %> <% if current_user %> <p>ログインしてます</p> <%= link_to @current_user.nickname, user_path(@current_user) %> <%= link_to "ログアウト",logout_path,method: :delete %> <%= link_to "投稿する", new_drink_path %> <%= link_to "退会する", user_path(current_user),method: :delete %> <% else %> <strong>ログインしてません</strong> <% end %>前回定義したcurrent_userメソッドや、logged_in?メソッドを使ってみました
これが今回のルート画面です
ログインしてみます
しっかり前回書いたコードが機能してるみたいですね、、、!!
users/show.html.erb
<h1><%= current_user.nickname%></h1> <%= link_to "toppage", root_path %> <%= link_to "setting", edit_user_path(current_user)%> <p><%= current_user.nickname %>さんの投稿一覧</p> <% @drinks.each do |drink| %> <%= drink.price%> <span class="name"><%= drink.name %></span> <p><%= drink.explain %></p> <% end %>drinkリソースの実装は次回で書きます
前回書いたusers#showあたりがしっかり機能していて、sessions_helperのcurrent_userも使えます
便利メソッドはビューでも呼び出せるので、ビューでも呼び出したいメソッドはhelperメソッドに定義した方がいいのかもしれません次はuserの更新を見てみましょう
先ほどのユーザー詳細ページにsettingというところをクリックすると
という画面に遷移します
コードはこんな感じ
users/edit.html.erb
<h1>Update <%= @user.nickname %>'s profile</h1> <div class="row"> <div class="col-md-6 col-md-offset-3"> <%= render 'shared/form' %> </div> </div><%= render 'shared/form' %>部分テンプレートを使用してるのでそちらもみてみましょう
shared/_form.html.erb
<%= form_with(model: @user, local: true) do |f| %> <%= render 'shared/error_messages', object: f.object %> <%= f.label :nickname %> <%= f.text_field :nickname, class: 'form-control' %> <%= f.label :email %> <%= f.email_field :email, class: 'form-control' %> <%unless @current_user%> <%= f.label :password %> <%= f.password_field :password, class: 'form-control' %> <%= f.label :password_confirmation %> <%= f.password_field :password_confirmation, class: 'form-control' %> <% end %> <%= f.submit "submit"%> <% end %>ここは新規登録ページのユーザーに関する入力フォームと一緒です
ですが
<%unless @current_user%>
<%end%>
とパスワードに関するフォームを囲むことにより、現在ログインしてる本人ならパスワードを改めて入力せずに
プロフィールの更新ができます。パスワードを入力せずに値を更新するためには
user.rb
validates :password, presence: true, length: { minimum: 6 },allow_nil: trueallow_nil: true にしなければなりません
パスワードがなくても新規登録できちゃうじゃん!
と思うかもしれませんが、has_secure_passwordをuser.rbに書いてるので、
新規登録の時はhas_secure_passwordくんがパスワードあるかどうか確かめてくれるらしいです、
賢いですね。とりあえず「そうくん」に名前を変更しときましょう
ログインしたことによるトップページの変化
では早速ログアウトしてみましょう
ログインしてないバージョンのtoppageに戻ります
そしてもう一回ログイン
ユーザーの詳細ページにリダイレクトされました
ユーザーが投稿したコーヒーの感想がチラッと見えてますが、これは次回投稿しますちなみにremember me on this computerにチェックしたので、ブラウザを閉じたりしてもログイン情報は保持される仕組みです
どのような仕組みかは前回解説しております
何はともあれ前回書いたコードがしっかり機能することが確かめられました、、、!
本当はrails consoleとかでもうちょい上手く確かめられると思うのですが、、、、、次回はコーヒーの感想を投稿したり、削除したり、一覧表示したり、誰の投稿かを定義したり、drinkリソースに関することをやっていきたいと思います
- 投稿日:2020-11-23T20:46:03+09:00
【RSpec】Ruby on Rails チュートリアル「第3章」のテストを RSpec で書いてみた
はじめに
Ruby on Rails チュートリアルでは「minitest」を使用してテストが実施されていますが、実際の現場では主に「RSpec」を使用してテストを実施するとのことなので、チュートリアルのテストを実際の現場に近づけるために「RSpec」で実施するようにしました。まず手初めに「第3章」のテストを RSpec で書き換えてみたので、同じようにチュートリアルのテストを RSpec で実施してみたい人の参考になれば幸いです。
対象者
- Ruby on Rails チュートリアルのテストを Rspec で実施予定、または実施してみたい人
- Ruby on Rails チュートリアル「第3章」のテストを Rspec で書きたいけど、書き方が分からない人
テストコード
実際にテストコードを書き換えた結果が下記になります。
Minitest
static_pages_controller_test.rbrequire 'test_helper' class StaticPagesControllerTest < ActionDispatch::IntegrationTest def setup @base_title = "Ruby on Rails Tutorial Sample App" end test "should get home" do get static_pages_home_url assert_response :success assert_select "title", "Home | #{@base_title}" end test "should get help" do get static_pages_help_url assert_response :success assert_select "title", "Help | #{@base_title}" end test "should get about" do get static_pages_about_url assert_response :success assert_select "title", "About | #{@base_title}" end endRSpec
static_pages_spec.rbrequire "rails_helper" RSpec.describe "StaticPages", type: :request do before do @base_title = "Ruby on Rails Tutorial Sample App" end describe "Home page" do it "should get home" do visit static_pages_home_url # /static_pages/home/ へアクセス expect(page.status_code).to eq(200) # HTTP ステータスコードが "200" か判定 expect(page).to have_title "Home | #{@base_title}" # タイトルに 「Home | Ruby on Rails Tutorial Sample App」 が含まれているか判定 end end describe "Help page" do it "should get help" do visit static_pages_help_url expect(page.status_code).to eq(200) expect(page).to have_title "Help | #{@base_title}" end end describe "About page" do it "should get about" do visit static_pages_about_url expect(page.status_code).to eq(200) expect(page).to have_title "About | #{@base_title}" end end end次回
「第4章」のテストコードを RSpec に書き換える予定です。
- 投稿日:2020-11-23T18:53:38+09:00
Rails 幹事向けアプリの作り方 複数レコードの同時登録編
最初に
この記事は、幹事向けのアプリを作成方法を記載します。
どんな構造を取るかアプリと言うと、2段階の複数登録を行うものです。①複数のテーブルと複数レコードを同時登録(イベントと複数の日付の登録)
②①で登録した複数レコード(日付など)の中間テーブルレコードの一括登録(出欠状態など)
開発時に、②複数のレコードを登録する方法について
参考となる情報がなく苦労しました。
そこで、作り方を公開して、同じ悩みを持った方の
助けに慣れればと思い記載致します。※UI部分については今回紹介していません。Gitにて確認お願いします。
目次
- 概要
- テーブルデータについて
- イベント(親)と複数の日程(子)、お店(子)の登録機能
- 参加者(親)と参加状況(子)の登録機能
- 参加者の参加状況の編集機能
- 参加状況の削除機能
1.アプリの概要
イベントを開催して、参加者の状況を管理。最終的にはイベントの日時・場所を決定するアプリです
ユーザーは2種類あり、「イベントの主催者」と「参加者」を想定しています。
Git:https://github.com/tsuyatsuya-april/ikang
HP:http://kyomo-ikang.com/events/1機能
主催者
・イベントの概要・候補日・候補店を登録・編集・削除する機能
2.テーブルデータについて
Userテーブル...主催者の名前・email・passwordを登録
Eventテーブル...イベントの名称と概要を登録
Scheduleテーブル...イベントの候補日を登録
Shopテーブル...イベントの開催場所候補を登録
Joinテーブル...参加者の名前を登録
DateAnswerテーブル...JoinとScheduleを親として、参加日程の状況を登録
ShopAnswerテーブル...JoinとShopを親として、開催場所の評価を登録作成するアプリでは、主に2つのフォームで下記の関係での登録を行う
Event(親)、Schedule(子)、Shop(子)のイベント登録フォーム
Join(親)、DateAnswer(子)、ShopAnswer(子)の参加者登録フォーム3.イベント(親)と複数の日程(子)、お店(子)の登録機能
イベントの登録機能を下記の小項目に沿って説明を行う。
尚、ユーザーの登録は、メジャーなので割愛させていただきます。
また、コードの一部抜粋した形で説明させていただきます。小項目
- 注目コード記載(Model,Controller,HTML,JS)
- fields_forメソッド
- name属性の修正
1.注目コード記載
model/event.rbclass Event < ApplicationRecord belongs_to :user has_many :shops, inverse_of: :event, dependent: :destroy accepts_nested_attributes_for :shops, allow_destroy: true endmodel/shop.rbclass Shop < ApplicationRecord belongs_to :event, inverse_of: :shops validates_presence_of :event endevents_controller.rbdef new @event = Event.new 1.times { @event.shops.build } end def create @event = Event.new(event_params) @event.user_id = current_user.id if @event.save redirect_to event_path(@event.id) else render "new" end end private def event_params params.require(:event).permit(:name, :description, schedules_attributes: [:savedate, :savetime], shops_attributes: [:shop_name, :shop_url, :map_url, :comment]) endevents/new.html<%= form_for @event,id:"event-new-form", local: true do |f| %> <%= render "share/error_messages", model: f.object %> <% 中略%> <div id="shop-add-btn"> <%= link_to "お店追加", "#", class:"btn-flat-border" %> </div> </div> <%= f.fields_for :shops do |shop_fields| %> <div id="new-shop-top"> <div id="new-shop"> <div id="shop-name-box"> <div id="shop-name"><p>店名(必須)</p></div> <%= shop_fields.text_field :shop_name, class:"shop-name-input"%> </div> <% 中略 %> <div id="shop-delete-box"> <%= link_to "お店削除", "#", class:"btn-flat-border-red", id:"shop-delete" %> </div> </div> </div> <% end %> </div> <div class="submit-box"> <%= f.submit "イベントの登録",class:"btn-flat-border-submit", id:"new-submit" %> </div> <% end %>main.js//お店の追加 function newShopAdd(){ nameNumberShopAdjust(); const shopParent = document.getElementById("new-shop-top"); const addShopBtn = document.getElementById("shop-add-btn"); let currentShopLength = document.querySelectorAll("#new-shop").length; let nextNum = currentShopLength; let shopHtml = ` <div id="new-shop"> <div id="shop-name-box"> <div id="shop-name"><p>店名(必須)</p></div> <input class="shop-name-input" type="text" name="event[shops_attributes][${nextNum}][shop_name]" id="event_shops_attributes_${nextNum}_shop_name"> </div> ~<中略>~`; addShopBtn.onclick = function(){ shopParent.insertAdjacentHTML("beforeend", shopHtml); shopDelete(); newShopAdd(); }; } //お店の削除 function shopDelete(){ let shopParent = document.querySelectorAll("#new-shop"); let shopDeleteBtn = document.querySelectorAll("#shop-delete"); let shopParentLength = shopParent.length; for (let i=0; i < shopParentLength; i++){ shopDeleteBtn[i].onclick = function(){ let conformShopLength = document.querySelectorAll("#new-shop").length; if(conformShopLength != 1){ return shopParent[i].remove(); } }; }; } //お店のname属性の値に入る数値の調整 function nameNumberShopAdjust(){ let saveShop = document.querySelectorAll(".shop-name-input"); let saveShopLength = saveShop.length; for(let j=0; j<saveShopLength; j++){ saveShop[j].removeAttribute("name"); saveShop[j].setAttribute("name",`event[shops_attributes][${j}][shop_name]`); } } }2.fields_forメソッド
このメソッドは、公式ドキュメントにて下記のような説明をしている。
モデルを固定してフォームを生成
form_for内で異なるモデルを編集できるようになる。つまり、今回でいうとEvent(親)とは別のShop(子)モデルの編集ができるようになるというものである。
ただし、fields_forを使う為にはいくつか準備が必要である。アソシエーションの設定
model/parent.rb親と子のモデルの双方向の関連付けができるようにする has_many :子モデル(複数形), invers_of: :(親モデル), dependent: :destroy 子モデルが同時に登録できるようにする accepts_nested_attributes_for :子モデル(複数形), allow_destroy: true(空白のフォームがあれば登録できないようにする)model/child.rb親と双方向の関連付けしてバリデーション設定などができるようにする。(例 shops.eventなど) belongs_to :親モデル, inverse_of: :子モデルコントローラの設定
controller/test.rbdef new @event = Event.new 関連した子の要素を生成する時はbuild(newのエイリアス)を使用する 1times { @event.shops.build } ちなみにtimesの数値の分だけ子の要素の登録フォームができる end 通常に保存する場合と同じ def create @event = 親モデル.new(event_params) if @event.save redirect_to 指定のパス else render アクション名 end end private def event_params params.require(:親モデル).permit(:親カラム1, :親カラム2, 子モデル名(複数系)_attributes: [:子カラム1]) .merge(user.id: :current_user.id) endビューの設定
views/test.html<%= form_for @event,id:url, local: true do |f| %> <%= f.text_field :親カラム名 %> ここで子モデルの登録ができるように設定を行う <%= f.fields_for :子モデル do |sf| %> <%= sf.text_field :子モデルカラム名 %> <% end %> <%= f.submit "イベントの登録" %> <% end %>上記のビュー・モデル・コントローラーの設定を行えば、
フォームの送信ボタンを押した後にデータが下記のようなパラメータで送られます。paramater.rb通常の場合 親モデル名 = { :first => 1, :second => 2} fields_forを使用した場合 親モデル名 = {:first => 1, :second => 2, 子モデル名 => {:first => 1, :second => 2 }}親モデルの中に子モデルがネストされてデータが受け渡され保存されるということになります。
これで複数モデルの複数レコードの保存の下地が整いました。3. name属性の修正
2では子モデルの複数のレコードが保存できる設定の説明を行ってきた。
ただし、javascriptを用いてお店と日程フォームの増減が行えるように設定をしています。
この時に保存が上手く行かなくなることがあったので、その原因と解消方法について記載します。問題
javascriptでフォームの追加を実行した後に全てのフォームが保存されない時があった。前提条件
fields_forを使った時のフォームのname属性がhtml上で下記のように変換されて表示されるtest.htmlfields_for内のinputタグの中にあるname属性の名前 event[schedules_attributes][0][savedate] event[schedules_attributes][1][savedate] event[schedules_attributes][3][savedate] 公式化 親モデル名[子モデル名(複数系)_attributes][要素番号][子モデルの対象カラム名]解消方法
javascriptを使ってデータの送信前にname属性の要素番号が被らないように調整する。main.js//(送信ボタンを押した時に要素番号を調整するメソッドを実行) function nameNumberShopAdjust(){ //お店のフォームのセレクタを取得 let saveShop = document.querySelectorAll(".shop-name-input"); //要素の数がいくらあるかを取得 let saveShopLength = saveShop.length; //要素数分だけループを実行 for(let j=0; j<saveShopLength; j++){ //フォームの既存で設定されたname属性を削除する saveShop[j].removeAttribute("name"); //フォームのname属性について、現在のループ回数を要素番号として付け直す。 saveShop[j].setAttribute("name",`event[shops_attributes][${j}][shop_name]`); } } }ここの部分に関しては、ドキュメントに記載されておらず挙動を読んで
仮説検証をたてたものですが上記方法で解決致しました。4.参加者(親)と参加状況(子)の登録機能
大項目3で指定した日程とお店について、参加状況を登録する方法について記載する。
join(親)、shop(親)、shop_answer(子)の中間テーブル、
join(親)、schedule(親)、date_answer(子)の中間テーブル
上記2つの登録を主に行う。
尚、解説はお店の方のみさせていただく小項目
- 注目コード記載(Model,Controller,HTM, routes)
- viewのname属性の調整
- 中間テーブルの親_idの格納
- event/showページで子のjoinコントローラにパラメータを渡す方法
- joinコントローラーのupdateアクション
1. 注目コード記載(Model,Controller,HTM, routes)
model/join.rbhas_many :shop_answers, dependent: :destroy accepts_nested_attributes_for :shop_answers, allow_destroy: truemodel/shop.rbhas_many :shop_answesr, dependent: :destroymodel/shop_answer.rbbelongs_to :join validates_presence_of :join belongs_to :shopevents_controller.rbdef show if params[:join_id] set_join else @join = Join.new 1.times { @join.shop_answers.build } end endjoins_controller.rbdef create @join = Join.new(join_params) if @join.save redirect_to event_path(params[:event_id]) else render "events/show" end end private def join_params params.require(:join).permit(:nickname, :email, shop_answers_attributes: [:shop_id, :status, :vote]) .merge(event_id: params[:event_id]) endevents/show.html<%= form_with model: @join, url:event_joins_path(@event.id), id:"join-form", local: true do |f| %> <div id="join-box"> <div id="join-name-label"> <label>ユーザー名</label> </div> <div class="join-users"> <%= f.text_field :nickname, id:"join-user",placeholder:"ニックネームを入力ください"%> </div> <div id="shop-answer"> <h1 id="shop-answer-title">店舗選択</h1> <div class="circle-text">説明: お店を◯×△で評価,一番良いと思う店に一番ボタンで投票下さい</div> <table id="shop-answer-table"> <tbody> <% @event.shops.each do |es| %> <%= f.fields_for :shop_answers do |shop_fields| %> <tr id="join-shops" class="bottom-line"> <th class="shop-label"> <div> <%= link_to es.shop_name, es.shop_url, target: :_blank %> </div> </th> <td class="shop-vote-balance"> <%= shop_fields.hidden_field :shop_id,class:"shop-id", value: es.id %> <%= shop_fields.hidden_field :status, id:"shops-status" %> <%= shop_fields.hidden_field :vote, id:"shops-vote" %> <div class="change-status"> <h1 class="btn btn--orange btn--circle btn--circle-a btn--shadow " id="shop-yes">◯</h1> <h1 class="btn btn--orange btn--circle btn--circle-a btn--shadow choice" id="shop-delta">△</h1> <h1 class="btn btn--orange btn--circle btn--circle-a btn--shadow cross-vote" id="shop-no">×</h1> </div> <div id="shop-change-vote"> <h1 class="btn btn--orange btn--circle btn--circle-a btn--shadow" id="shop-vote">一番</h1> </div> </td> </tr> <% end %> <% end %> </tbody> </table> </div> <div id="join-submit-box"> <%= f.submit "回答",class:"btn-flat-border", id:"join-submit-inputbox", :onclick => "return check_name()" %> </div> <% end %>routes.rbRails.application.routes.draw do root to: "events#index" resources :events do resources :schedules resources :shops resources :joins resources :comments resources :date_decisions resources :shop_decisions end end2. viewのname属性の調整
大項目3で登録した複数データに対して、複数の中間テーブルの保存を行うには下記のコードの構造をとる。
events/show.html<%= form_with model: @join, url:event_joins_path(@event.id), id:"join-form", local: true do |f| %> 親モデルのnicknameカラムの保存フォーム <%= f.text_field :nickname, id:"join-user",placeholder:"ニックネームを入力ください"%> イベントページに紐づいているお店のレコードを全て出力する。 <% @event.shops.each do |es| %> fields_forで複数レコード登録できるようにする。 <%= f.fields_for :shop_answers do |shop_fields| %> 中間テーブルの親IDを登録するフォーム。事前にid番号をvalueに格納する <%= shop_fields.hidden_field :shop_id, value: es.id %> javascriptを使って、数字1(=◯),2(=△),3(=×)が格納されるようにしている <%= shop_fields.hidden_field :status, id:"shops-status" %> javascriptを使って投票数0,1を格納する。 <%= shop_fields.hidden_field :vote, id:"shops-vote" %> onclickでユーザーのニックネームが格納されていない場合にフォームを送信できないようにする <%= f.submit "回答",:onclick => "return check_name()" %> <% end %> <% end %> <% end %>
解決方法
大項目3と同様な形でjavascriptを用いて、
出力されたお店の分、name属性の要素番号を付け直す
これで出力されたお店のレコード分だけ、中間テーブルのレコードが保存できるようになった。3. 中間テーブルの親_idの格納
中間テーブルであるshop_answerテーブルに親IDを紐付ける方法を記載する。
・1点目のjoin_idはfields_forメソッドを使用しているので親子関係となり自動的にIDが割り振られる。
・2点目のshop_idは下記の通りeachメソッドを用いて、各ID番号をshop_idのフォームに格納している。events/show.html<% @event.shops.each do |es| %> <%= f.fields_for :shop_answers do |shop_fields| %> 先ほども記述したがes.idでshop(親)のID番号をshop_answer_idに渡している。 <%= shop_fields.hidden_field :shop_id, value: es.id %> <% end %> <% end %>以上で中間テーブルの保存ができる状態になった
4. events/showページで子のjoinコントローラにパラメータを渡す方法
初学者にとってevents/showページで登録を行う場合、
events_controllerにパラメータをpostするという印象が強い。
しかし今回はjoins_controllerにパラメータをpostして登録を行うので
その場合のurl指定方法を記載する。まず下記コマンドを実行しURLの確認を行う
test.rbrails routes次に確認したURLの内joinの登録であるURLをform_withメソッドの中に適応させる
events/show.html<%= form_with model: @join, url:event_joins_path(@event.id), id:"join-form", local: true do |f| %>以上を行うことで、events/showページからjoins_controllerにデータを受け渡すことができるようになる。
5.参加者の参加状況の編集機能
events/show.htmlにて参加者毎の編集フォームを表示する方法について記載を行う。
複数の情報を出力する上で小項目
- 同一ページ内で参加者IDの情報をコントローラに受け渡す方法
- fields_forを用いた場合の編集フォームのviewを表示について。
- objectメソッド、親データの店名やURLを引き出す方法
1. 同一ページ内で参加者IDの情報をコントローラに受け渡す方法
まず、events/show.html上に参加者の編集フォームを出力するには、
コントローラに各参加者のjoin_idの数値を送り、データを抽出する必要がある。その為に、link_toメソッドを用いて、join_idをパラメータとしてコントローラに引き渡す手法を用いた。
events/show.html<% if @event.joins %> 登録されている参加者全てを出力する <% @event.joins.each do |event_join| %> ループ中の参加者のID、join.idをjoin_idというキーに格納してコントローラに渡している <%= link_to event_join.nickname, event_path(@event.id,join_id: event_join.id) %> <% end %> <% end %>コントローラではjoin_idというパラメータの有無によって
編集フォームの出力か登録ページの出力かの分岐を行っている。events_controller.rbdef show @joins = Join.all if params[:join_id] set_join else @join = Join.new 1.times { @join.shop_answers.build } end end private def set_join @join = Join.find(params[:join_id]) end編集フォームの場合は、決まった値をフォームに格納した状態で表示される。
登録フォームの場合は、新規にデータを作成できる状態で表示される
これでビューに表示できる条件が整った。2. fields_forを用いた場合の編集フォームのviewを表示について
登録時には、shop.each doとfields_forを用いたが編集時には、fields_forのみを用いれば、選択したJoinレコードに紐づくshop_answerレコードが全て出力される。
events/show.html<% unless params[:join_id] %> 新規登録の時の処理 <% else %> 編集時の処理 <%= form_with model: @join, url:event_join_path(@event.id, @join.id), id:"join-edit-form" , local: true do |f| %> ユーザー名の編集 <%= f.text_field :nickname,id:"join-edit-user", placeholder:"ニックネームを入力ください"%> fields_forを用いて、選択したJoinレコードに紐づく全てのshop_answerレコードを出力 <%= f.fields_for :shop_answers, @join.shop_answers do |shop_edit_fields| %> 編集ページでは新たに、joinのidカラムを格納するフォームを用意する。それ以外は登録と同じ。 <%= shop_edit_fields.hidden_field :id,class:"shop-answers-id" %> <%= shop_edit_fields.hidden_field :shop_id,class:"shop-edit-id" %> <%= shop_edit_fields.hidden_field :status, id:"shops-edit-status" %> <%= shop_edit_fields.hidden_field :vote, id:"shops-edit-vote" %> <% end % > <%= f.submit "更新",class:"btn-flat-border", id:"join-edit-submit-inputbox", :onclick => "return check_name()" %> <% end %> <% end %>同じ躓きをする方もいると思うので私が失敗した時の場合も下記の画像にて記載します。
3. objectメソッド、親テーブルの店名やURLを引き出す方法
小項目2では、フォームが正しく表示されたが実は1つ問題がある。
それは、fields_forでは、
「中間テーブルの子モデルに対し、親モデルのデータが引き出せない」ことである。
私のアプリでは店名やURLが表示されず、どのデータに紐づいているか判断出来なくなる。これに対して、shops.each do を用いて、店名を引き出せば良いと考えたこともあった。
しかし、結果は小項目2の最後の画像の通り、フォームが正しく表記されない。そこで、objectメソッドを用いることにした。
このメソッドを変数の後につけることで、変数に格納しているデータを取得できる。test.HTMLshop_answer = { id => 1, join_id => 1, shop_id => 1, status => 1} shop = { id => 1, shop_name => "吉野家", shop_url => "yoshinoya" } <%= f.fields_for :shop_answers, @join.shop_answers do |shop_edit_fields| %> snum = shop_id = 1 <% snum = shop_edit_fields.object.shop_id%> sn = Shop.find(1) <% sn = Shop.find(snum) %> これでfields_for内でshop_answerの親モデルのお店情報が出力できるようになった sn.shop_name = 吉野家 sn.shop_url = yoshinoya5. joinコントローラーのupdateアクション
fields_forを用いた時は、paramsの表記方法が異なる。
登録時には子モデルのID番号が必要なかったが
更新時には小モデルのID番号が必要となるまた、:_destroyをつけることで親モデルに紐づく子モデルのデータを削除することができる。
例えば、編集時に子モデルのフォームを空白に変更して更新するとデータが削除されるなど。joins_controller.rbdef update if @join.update(join_update_params) redirect_to event_path(params[:event_id]) else render "events/show" end end private 通常時 def join_params params.require(:join).permit(:nickname, :email, date_answers_attributes: [:schedule_id, :status], shop_answers_attributes: [:shop_id, :status, :vote]) .merge(event_id: params[:event_id]) end 更新時 def join_update_params params.require(:join).permit(:nickname, :email, date_answers_attributes: [:id,:schedule_id, :status, :_destroy], shop_answers_attributes: [:id,:shop_id, :status, :vote, :_destroy]) end6. 参加状況の削除機能
アソシエーションの設定で親レコードの削除に伴い紐づく子のレコードが削除されるようにする必要がある。
allow_destroy: trueは
親要素が削除された時、関連付けている情報もまとめて削除するためのオプションです。model/join.rbhas_many :date_answers, dependent: :destroy accepts_nested_attributes_for :date_answers, allow_destroy: truemodel/shop.rbhas_many :shop_answer, dependent: :destroymodel/shop_answer.rbbelongs_to :join validates_presence_of :join belongs_to :shop以上のアソシエーションをつけることで通常時と変わりなく、削除を実行することができる。
今回はビューとコントローラーの記述を割愛させていただきます。最後に
ここまで読んで下さってありがとうございます。
はじめて個人で作成したアプリなのでアラが目立つかとは存じますが
ご容赦下さいますようお願いします。また、何かご質問があればコメントお願いします。
お答えできる限りはお答え致します。参考
- 投稿日:2020-11-23T18:46:08+09:00
【ruby on rails】「devise」gemを利用したウィザード方式のユーザー登録
Toshiです!!
今日はウィザード方式でのユーザー情報の登録についてまとめます。この記事の対象の方
・Ruby on railsを学んでいる方
・deviseのgemの使い方がわかる方ただ、条件関係なくその他の方も全然Welcomeです
「ウィザード形成」とは
ウィザード形式とは、対話するように順番に操作が進んでいく方式のことです。例として、ユーザー登録の際に、最初のページでユーザー情報(名前やメールアドレスなど)の登録を行い、次のページで住所などの情報を登録するように、ページを切り替えて登録を行うことが挙げられます。
ユーザーのメリットとして1ページで縦長に全ての情報を登録するよりも、どの情報を、どのページで登録しているのか分かるので、ユーザーからすると見やすく使いやすいという特徴があります。
STEP0 事前に準備できていること
・deviseのgemを、ruby on railsにインストールしていること
(すでにできているアプリを前提に説明をしていきます)
・ビューが完成し、ユーザー登録はできている状態。STEP1 ウィザード形式のイメージを掴む
画像は完全拝借物ですが、上記のようなイメージです。
STEP2 「ウィザード形式で次の画面で登録するための情報用のモデルを作成する」
今回はユーザー情報に加えて、本人の住所情報を登録させます。
今回は「Addressモデル」という名前で、ユーザー情報登録後に登録する住所情報のモデルを作成しています。
⭐️重要ポイント①
最初のユーザー情報登録画面画面で、「user_id」が外部キーの設定によって作られる形ですが、addressesテーブルには、住所情報入力時点では登録されない!そのため、下記のように住所の登録時にuser_id(Addressesテーブルの外部キー)が、nil(空欄)でも登録を許可するという設定を、モデルに追加します。
app/models/address.rbclass Address < ApplicationRecord belongs_to :user, optional: true #⭐️ここがポイント!! validates :postal_code, :address ,presence: true end
次にユーザー情報と住所情報を1:1の関係なのでアソシエーションを設定します。app/models/user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable validates :name, :age ,presence: true has_one :address #追加 end
⭐️重要ポイント②
ルーティングにて、Userモデルのコントローラーとの紐付けの設定を追加する
まず、deviseのコントローラーを作成します。作成後、「rails routes」を実行すると以下のようになります。**ターミナル% rails g devise:controllers users
devise管理下にusersコントローラーを作成されていることがわかります。
しかし、deviseはgemをインストールしてしまえば、ビューファイルやモデルを作ったりなどでユーザー情報管理ができてしまう反面、gemの中でほぼ完結してしまい、ウィザード形式のように追加実装がやりにくいデメリットもあります。
そこで、作成したコントローラーに紐付けの設定を行い、そこでcreateやnewといった7つのアクションを記載し実施できるようにします。ルーティングを以下のように設定します。config/routes.rbRails.application.routes.draw do #以下を変更 devise_for :users, controllers: { registrations: 'users/registrations' } #ここまで root to: "home#index" #すでに作成しているコントローラーに関するルーティングです。 end
rails routesを再度実行すると紐づくコントローラーが変更されています。
これで、users配下のコントローラーに7つのアクションを記載し処理を実装することができます。STEP3 「users配下のコントローラーに、ユーザー情報登録のアクションを記載する」
ユーザー新規登録ページへ遷移するnewアクションを設定します。
app/controllers/users/registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController # before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] def new @user = User.new end # 中略 endUserモデルの新規インスタンスを生成しました。new.html.erbを下記のように編集します。(ビューファイルはデフォルトから変えていません)
app/views/devise/registrations/new.html.erb<h2>ユーザー情報登録</h2> <%= form_for(@user, url: user_registration_path) do |f| %> #変更 <%= render "devise/shared/error_messages", resource: @user %> #変更 <div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :age %><br /> <%= f.text_field :age %> </div> <div class="field"> <%= f.label :email %><br /> <%= f.email_field :email, autofocus: true, autocomplete: "email" %> </div> <div class="field"> <%= f.label :password %> <% if @minimum_password_length %> <em>(<%= @minimum_password_length %> characters minimum)</em> <% end %><br /> <%= f.password_field :password, autocomplete: "new-password" %> </div> <div class="field"> <%= f.label :password_confirmation %><br /> <%= f.password_field :password_confirmation, autocomplete: "new-password" %> </div> <div class="actions"> <%= f.submit "Next" %> #変更 </div> <% end %> <%= render "devise/shared/links" %>※実際作る人はここで画面の確認を。
次に保存のアクションを記載していきます。app/controllers/users/registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController # before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] def new @user = User.new end def create #追加 @user = User.new(sign_up_params) unless @user.valid? render :new and return end session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] @address = @user.build_address render :new_address end #ここまで # 省略 end⭐️重要ポイント③
画面遷移をしても情報を保持するために「session」を使う最後のページまで遷移した後に保存させる為に、sessionを用います。
1ページ目で入力した情報のバリデーションチェックが完了したらsession["devise.regist_data"]に値を代入します。この時、sessionにハッシュオブジェクトの形で情報を保持させるために、attributesメソッドを用いてデータを整形しています。
なお、[devise.regist_data]は変数ではありますが、deviseが落ちてしまった時に自動で保持する値を削除できるといった副次的なメリットもあります。
また、paramsの中にはパスワードの情報は含まれていますが、attributesメソッドでデータ整形をした際にはパスワードの情報は含まれません。そこで、パスワードを再度sessionに代入する必要があります。これをsession["devise.regist_data"][:user]["password"]が担い、sessionに追加しています。
最後の「render :new_address」で次の画面の遷移をできるようにしています。new_addressはまだ未作成のためここで保存をするとエラーになります。
STEP4 「住所情報を登録する、ウィザード形式の完成」
住所情報の登録のために先ほど、new_addressのビューに飛ぶ設定(renderメソッド)を追加しました。その画面で登録ページを表示させる「new_address」アクションと、登録を行う「create_address」アクションをルーティングに設定していきます。
config/routes.rbRails.application.routes.draw do devise_for :users, controllers: { registrations: 'users/registrations' } devise_scope :user do #追加 get 'addresses', to: 'users/registrations#new_address' post 'addresses', to: 'users/registrations#create_address' end #ここまで root to: "home#index" end
次に Viewを作成していきます。今回は、借り物のViewファイルを使います。app/views/devise/registrations/new_address.html.erb<h2>住所情報登録</h2> <%= form_for @address do |f| %> <%= render "devise/shared/error_messages", resource: @address %> <div class="field"> <%= f.label :postal_code %><br /> <%= f.text_field :postal_code %> </div> <div class="field"> <%= f.label :address %><br /> <%= f.text_field :address %> </div> <div class="actions"> <%= f.submit "Sign up" %> </div> <% end %> <%= render "devise/shared/links" %>ここまでで住所登録までの画面遷移はできますが、まだ登録はできません。
住所情報を保存するために、コントローラーの設定を追加します。⭐️重要ポイント④
ウィザード形式で遷移後の画面であっても、「registrations_controller.rb」(作成したコントローラーファイル)にアクションを記載していく。
create_addressアクション内でやることは、以下4点です。・2ページ目で入力した住所情報のバリデーションチェック
・バリデーションチェックが完了した情報とsessionで保持していた情報を合わせ、ユーザー情報として保存
・sessionを削除する
・ログインをするnew_addressはビューファイルを用意し、特に情報を遷移時に表示させる必要はないのでコントローラーに記載は入りません。「registrations_controller.rb」に、「create_address」を以下のように追記していきます。
app/controllers/users/registrations_controller.rbclass Users::RegistrationsController < Devise::RegistrationsController # before_action :configure_sign_up_params, only: [:create] # before_action :configure_account_update_params, only: [:update] def new @user = User.new end def create @user = User.new(sign_up_params) unless @user.valid? render :new and return end session["devise.regist_data"] = {user: @user.attributes} session["devise.regist_data"][:user]["password"] = params[:user][:password] @address = @user.build_address render :new_address end def create_address #追加 @user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(address_params) unless @address.valid? render :new_address and return end @user.build_address(@address.attributes) @user.save session["devise.regist_data"]["user"].clear sign_in(:user, @user) end private def address_params params.require(:address).permit(:postal_code, :address) end #ここまで # 省略 end上記の4つがどのように設定されているのか。
・2ページ目で入力した住所情報のバリデーションチェック
@user = User.new(session["devise.regist_data"]["user"]) @address = Address.new(address_params) unless @address.valid? render :new_address and return endvalid?メソッドを用いて、address.rbファイルに記述したバリデーションを実行します。この際、アプリケーションが設定したバリデーションの条件を満たしている場合はtrueを返します。その逆に、バリデーションの条件を満たしていない場合はfalseを返します。今回はunlessを用いているため、falseの場合にunless内に記述した処理が実行されます。
render :new_addressと記述しているため、設定したバリデーションの条件を満たさない場合は、renderメソッドによってnew_addressアクションに画面遷移します。
このとき、and returnと記述することで処理を中断させています。もしこのand returnがない場合はunless以降の記述も実行され、ユーザーの情報は保存されてしまいます。これを防ぐためにand returnを記述して処理を中断しています。
・バリデーションチェックが完了した情報とsessionで保持していた情報を合わせ、ユーザー情報として保存
@user.build_address(@address.attributes) @user.save同コントローラーにある「create」アクションにて、「@address」の変数を@user.build_addressと、@user変数のbuild_address要素としてあとで格納できるようにしています。
現在セッションの情報が格納されている@userに対して、「build_address」の要素を使い代入する形で住所情報を格納しています。saveメソッドを用いてテーブルに保存します。・sessionを削除する
session["devise.regist_data"]["user"].clear「.clear」メソッドを用いて、sessionの情報を削除します。
・ログインをする
sign_in(:user, @user)情報が保存できてもログインできているわけではないので、sign_inメソッドでログインをします。
create_address.html.erbのビューファイルを作成し完成です
app/views/devise/registrations/create_address.html.erb<h2>登録が完了しました</h2> <%= link_to "トップへ戻る", root_path%>最後に
最初はどうしても拝借気味にはなりますが、自分なりの理解している内容などを盛り込むのも大事ですね。引き続きよろしくお願いします!!
- 投稿日:2020-11-23T18:20:43+09:00
【超絶初心者向け】プログラミングをかじってみる
0. はじめに
この記事はプログラミング初心者が挫折せずに学習を始められるよう、なるべく分かりやすい構成にしました。(したつもりです。)
色んなプログラミングスクールが乱立する昨今、どこに通えばいいか分からず、通ったら通ったで思った以上に難易度が高く、覚えること多過ぎて無事死亡…なんてことが多発しているようです。知らんけど。
高いお金払ってスクールに通わなくても、今の時代は無料(もしくは少額)で多くのことを学ぶことが出来ます。
無料で独学するもよし、まとまったお金を払って集中的に学ぶもよし。どちらにしてもスタートで挫折する人がとても多いようです。
なので、まずはこの記事でプログラミングの触りについて学んでみて、自分に適性があるかどうか確認してみてはどうでしょうか。
適性がないと思ったらスッパリと諦め別の道を探しましょう。
プログラミング以上に面白くて稼げてやり甲斐があって世の中の役に立つ仕事なんてこの世にゴマンとあるので。知らんけど。まずはこの記事の目次を確認して、やるべきことの概要をざっくり把握してください。
1. PCを準備する
当たり前ですがPCがないとプログラミングは出来ません。
今どき売られているPCであればどれでも構いません。
個人的にはMacBookをオススメしてますが、Windowsでももちろん大丈夫です。
むしろつよつよエンジニア(スキルの高いエンジニア)はWindowsを好んで使っているような気もします。2. Visual Studio Code(略してVScode)をダウンロードする
Visual Studio Code
はPCでプログラミングをするために必要なツール(道具)だと思ってください。3. Visual Studio Code の説明
ダウンロードが出来たら
Visual Studio Code
を開いてください。
多少の違いはあれど、同じような見た目になってるかと思います。上記画面を確認出来たら、下の画面のように
ターミナル > 新しいターミナル
を選択してください。すると画面の下半分に新たな枠が表示されました。
画面の上半分のエリアをテキストエディタ
、下半分のエリアをターミナル
と呼びます。ひとまずこの状態で次の手順に進みましょう。
4. Rubyをインストールする
世界には色んな言語がありますよね?
日本語、英語、中国語、スペイン語など。
それと同様にプログラミング言語にもたくさんの種類があります。
今回は初心者でも学習しやすいRuby
を使用します。
Ruby
を使うには下準備が必要です。
MacにはRubyが標準でインストールされているのでこの手順を飛ばしてください。
Windowsには こちらの記事 を参考にインストールしてください。5. プログラムを書く
Visual Studio Code
のテキストエディタに下記コードをコピペしてください。puts "Hello World!"
コピペ出来たらファイルを保存しましょう。
ファイル名はsample.rb
、保存場所はデスクトップ
にしてから保存します。無事保存出来たらターミナルで下記のコマンドをコピペしてEnterを押してください。
※ Windowsユーザーの方は別のコマンドになると思います。。。cd Desktop
6. プログラムを実行する
ではターミナルで下記コマンドをコピペしてEnterを押してください。
ruby sample.rb
するとターミナルに
Hello World!
と表示されたら成功です!
あなたはプログラミングの道に一歩踏み出したのです。これが一体なんの役に立つのか?
それはこれから分かります。
- 投稿日:2020-11-23T17:32:00+09:00
GitHub Actions を使った Rails アプリの自動デプロイを10分で理解する
こんにちは、駆け出しエンジニアのよしこ @k2_yoshikoukiです
CI/CDってカッコいいですよね。なんかこう、カッコいいんですよね(語彙力)
しかし「どうやって実装したらいいのかワケワカメ\(^o^)/」というエンジニアの方は多いと思います。でも実際はとても簡単なんだよということを知ってほしかったので、10分で読める記事で CI/CD のうちのCD(自動デプロイ)を実装していきたいと思います。
実作業時間は詰まらなかったら30分かからないくらいです。
ゴール
CI/CD をやってみたいがやったことがないし何から手を付けたら良いか分からないエンジニア向けに、Rails アプリ (6系) で自動デプロイを最速で実装する。
結果、GitHub Actions を使ったCI/CDの大枠を把握できて自力で実装できるようになる
実装フローを見ていく
- Rails アプリを作る
- Heroku に手動デプロイして動作確認
- GitHub Actions の初期設定
./.github/workflows/{好きな名前}.yml
の作成- 必要な鍵などの取得・GitHubへ設定
すでにアプリケーションがある方は 3 からです。たった2ステップで自動デプロイが実装できます。早速やっていきましょう
手順
Railsアプリの準備
rails new sample-rails-with-gha
~/tmp/sample-rails-with-gha ❯ rails -v Rails 6.0.3.4 ~/tmp/sample-rails-with-gha ❯ ruby -v ruby 2.7.1p83 (2020-03-31 revision a0c7c23c9c) [x86_64-darwin19] # 後から2.7.2にアップデートします(デバックのため)bundle install rails server
リポジトリの準備
- GitHub上にリポジトリを作る
https://github.com/newローカルにリポジトリを登録
git remote add origin https://github.com/yoshikouki/sample-rails-with-gha.git
アップロード
git add -A git commit -m "rails new" git push -u origin masterデプロイの初期設定と初回デプロイ
今回はデプロイ先に Heroku を使う(簡単なので)
~/tmp/sample-rails-with-gha master ❯ heroku --version zsh: command not found: herokuHeroku 入ってなかった・・・
brew tap heroku/brew && brew install heroku # https://devcenter.heroku.com/articles/heroku-cliHeroku アカウントを作っておく
https://signup.heroku.com/Heroku にログイン(ターミナル上)
heroku login --interactive
Heroku 用のアプリケーションを作る(アップロードする場所を作る)
heroku create # URL を控えておく # https://sleepy-beyond-32826.herokuapp.com/ # `$ heroku open` でも開けるHeroku は sqlite3 に対応していないため、
./Gemfile
を操作するHeroku へデプロイする
git push heroku masterあらら
- エラーログ
$ heroku logs 2020-11-22T14:39:05.610528+00:00 app[web.1]: I, [2020-11-22T14:39:05.610419 #4] INFO -- : [a6b71e5b-f13a-4224-a857-9b791da3ecc4] Started GET "/" for 133.32.232.41 at 2020-11-22 14:39:05 +0000 2020-11-22T14:39:05.611222+00:00 app[web.1]: F, [2020-11-22T14:39:05.611152 #4] FATAL -- : [a6b71e5b-f13a-4224-a857-9b791da3ecc4] 2020-11-22T14:39:05.611223+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] ActionController::RoutingError (No route matches [GET] "/"): 2020-11-22T14:39:05.611224+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] 2020-11-22T14:39:05.611225+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/debug_exceptions.rb:36:in `call' 2020-11-22T14:39:05.611225+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/show_exceptions.rb:33:in `call' 2020-11-22T14:39:05.611226+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] railties (6.0.3.4) lib/rails/rack/logger.rb:37:in `call_app' 2020-11-22T14:39:05.611226+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] railties (6.0.3.4) lib/rails/rack/logger.rb:26:in `block in call' 2020-11-22T14:39:05.611226+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] activesupport (6.0.3.4) lib/active_support/tagged_logging.rb:80:in `block in tagged' 2020-11-22T14:39:05.611227+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] activesupport (6.0.3.4) lib/active_support/tagged_logging.rb:28:in `tagged' 2020-11-22T14:39:05.611227+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] activesupport (6.0.3.4) lib/active_support/tagged_logging.rb:80:in `tagged' 2020-11-22T14:39:05.611228+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] railties (6.0.3.4) lib/rails/rack/logger.rb:26:in `call' 2020-11-22T14:39:05.611228+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/remote_ip.rb:81:in `call' 2020-11-22T14:39:05.611228+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/request_id.rb:27:in `call' 2020-11-22T14:39:05.611229+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] rack (2.2.3) lib/rack/method_override.rb:24:in `call' 2020-11-22T14:39:05.611229+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] rack (2.2.3) lib/rack/runtime.rb:22:in `call' 2020-11-22T14:39:05.611230+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] activesupport (6.0.3.4) lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call' 2020-11-22T14:39:05.611230+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/executor.rb:14:in `call' 2020-11-22T14:39:05.611231+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/static.rb:126:in `call' 2020-11-22T14:39:05.611231+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] rack (2.2.3) lib/rack/sendfile.rb:110:in `call' 2020-11-22T14:39:05.611232+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] actionpack (6.0.3.4) lib/action_dispatch/middleware/host_authorization.rb:76:in `call' 2020-11-22T14:39:05.611232+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] railties (6.0.3.4) lib/rails/engine.rb:527:in `call' 2020-11-22T14:39:05.611232+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/configuration.rb:228:in `call' 2020-11-22T14:39:05.611233+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/server.rb:713:in `handle_request' 2020-11-22T14:39:05.611233+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/server.rb:472:in `process_client' 2020-11-22T14:39:05.611233+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/server.rb:328:in `block in run' 2020-11-22T14:39:05.611234+00:00 app[web.1]: [a6b71e5b-f13a-4224-a857-9b791da3ecc4] puma (4.3.6) lib/puma/thread_pool.rb:134:in `block in spawn_thread' 2020-11-22T14:39:05.612663+00:00 heroku[router]: at=info method=GET path="/" host=sleepy-beyond-32826.herokuapp.com request_id=a6b71e5b-f13a-4224-a857-9b791da3ecc4 fwd="133.32.232.41" dyno=web.1 connect=1ms service=4ms status=404 bytes=1902 protocol=https 2020-11-22T14:39:05.858618+00:00 heroku[router]: at=info method=GET path="/favicon.ico" host=sleepy-beyond-32826.herokuapp.com request_id=831fe7f9-bc01-496d-a9ac-380c9c89dd3c fwd="133.32.232.41" dyno=web.1 connect=1ms service=2ms status=200 bytes=143 protocol=https
- なるほどねえ(分かっていない)
ActionController::RoutingError (No route matches [GET] "/"):
# ./config/routes Rails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html endGitHub Actions を設定していく
空の設定ファイルを作る
mkdir -p ./.github/workflows tocuh -p ./.github/workflows/sample_cd.yml設定していく
設定ファイル
name: Deploy to sample-rails-with-gha on: # master ブランチにプッシュされた場合に動作する push: branches: - master jobs: deploy: runs-on: ubuntu-latest steps: # GitHub リポジトリからコードを引っ張ってくる - name: Checkout uses: actions/checkout@v2 # 前回のコンテナキャッシュがあれば使用する。なければキャッシュを作る - name: Cache multiple paths uses: actions/cache@v2 with: path: vendor/bundle key: ${{ runner.os }}-gems-${{ hashFiles('**/Gemfile.lock') }} restore-keys: | ${{ runner.os }}-gems- # Ruby をインストールする - name: Set up Ruby 2.7 uses: actions/setup-ruby@v1 with: ruby-version: 2.7 # バンドラーをインストールし、初期化する - name: Bundle install run: | gem install bundler bundle config path vendor/bundle bundle install --jobs 4 --retry 3 # ヘロクにデプロイする - uses: akhileshns/heroku-deploy@v3.6.8 # This is the action with: heroku_api_key: ${{secrets.HEROKU_API_KEY}} heroku_app_name: "sleepy-beyond-32826" heroku_email: "yoshikouki@gmail.com"簡単な設定ファイルなので説明はファイル内のコメントを参考にしてください。
- アクションという Ruby でいう Gem のような機能を使用しています。
ドキュメント
secrets.HEROKU_API_KEY
が必要なので Heroku で取得する
- https://dashboard.heroku.com/account もしくは
自分のアイコン
>Account settings
- 下部
API Key
ここのKeyをコピーする取得した Key をGitHub で使用できるように設定する
sample-rails-with-gha
のリポジトリ >Settings
>Secrets
- GitHub Actions で使用する非公開環境変数の設定画面になる >
New repository secret
- Name に
HEROKU_API_KEY
Value に先程Herokuで取得したキーをセット >Add secret
準備は整ったので、master/main ブランチにプッシュしてみる
- そして順調にエラーになった
- エラーになった画面
https://github.com/yoshikouki/sample-rails-with-gha/runs/1438564960?check_suite_focus=trueデバッグ
- actions/cache のドキュメントは bundle がインストールしてある前提だったので
gem install bundler
を追加したGemfile に記述されている ruby-version と GitHub Actions のコンテナ内のRubyを一致させること
uses: actions/setup-ruby@v1 with: ruby-version: 2.7
- GitHub Actions setup-ruby ではマイナーバージョンまでしか指定できないみたいなので、手元Rubyのメンテナンス (ビルド) バージョンをあげることになるかもしれない
- マイナーバージョンが上がるわけではないので、素直に上げておけばいいと思う
そして...
まとめ
以上で Rails アプリ (6系) で自動デプロイまでを最速で実装しました。
以後は、master (main) ブランチへ push するだけで heroku へ自動デプロイされます。これで2〜3コマンド分の実行の手間が省けたことになりますし、あなたはもう完全に CI/CD を習得したエンジニアです。
(実際のところ、今回はCI部分を何も扱っていませんし、デプロイに関してもデータベースを触っていないので中途半端な状態ではあるのですが、最低限は実装できたので良しとしましょう。してください。)
参考URL
- 投稿日:2020-11-23T17:18:42+09:00
【Rails】formの検索ボタンを虫眼鏡のアイコンにする
Ruby on RailsでFont-Awesomeを初めて使ったの投稿しました。
Font Awesome HP
https://fontawesome.com/icons?d=galleryポイント:使いたいアイコンのUnicodeの前に \u をつける
1 . CDNでfont-awesomeを読み込む
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">2 . submitの記述の後を下記に変更。
<%= f.submit "\uF002" %>3 . cssで下記を追加
font-family: FontAwesome;
- 投稿日:2020-11-23T17:18:42+09:00
【Rails / FontAwesome】formの検索ボタンを虫眼鏡のアイコンにする
Ruby on RailsでFont-Awesomeを初めて使ったの投稿しました。
Font Awesome HP
https://fontawesome.com/icons?d=galleryポイント:使いたいアイコンのUnicodeの前に \u をつける
1 . CDNでfont-awesomeを読み込む
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">2 . submitの記述の後を下記に変更。
<%= f.submit "\uF002" %>3 . cssで下記を追加
font-family: FontAwesome;
- 投稿日:2020-11-23T16:25:17+09:00
【Rails】form_withの使い方について解説【初心者向け】
はじめに
Rails開発をする中で、form_withを使う機会は非常に多いですが、
- 結局form_withとは何なのか?
- form_withにはどんな役割があるのか?
- form_withはどのように用いたらいいのか?
といった悩みを抱えている方は多いと思います。
本記事はそのような方達に向けて、書いた記事となります。
ぜひ最後までご覧下さい。
使用技術
Ruby on Rails 5.1〜
Ruby 2.6〜筆者の自己紹介
- テックキャンプ86期生(2020/9/21~)
- 執筆時点で学習開始から約9週間経過
- 約6週間で最終課題(メルカリクローンアプリの作成)を終了
本記事の内容
1.form_withとはそもそも何者なのか?
2.form_withはどのような役割を果たしているのか?
3.form_withの具体的な記述法
4.まとめ
5.最後に
6.参照サイト1.form_withとはそもそも何者なのか?
form_withは①ヘルパーメソッドの一種で、ビューファイル上で②フォーム入力欄を作成できるメソッドのことを言います。
①ヘルパーメソッド
ヘルパーメソッドとはRails内部で定義されているメソッドのことで、ビューファイルにおける記述をシンプルにするためのものです。
ビューファイルではERBタグ(本記事ではビューファイルにテンプレートエンジンとしてerbを使用)を使えば、直接Rubyの記述を書くことはできますが、DRYの視点から見た場合そのようなコードは綺麗なコードとは言えません。
そういったことを防ぐためにヘルパーメソッドは使用されます。
ヘルパーメソッドを使用しない場合<%= form action="/posts" method="post" %> <%= input type="text" name="content" %> <%= input type="submit" value="投稿する" %> <%= /form %>ヘルパーメソッドを使用した場合<%= form_with url: "/posts", method: :post, local: true do |form| %> <%= form.text_field :content %> <%= form.submit '投稿する' %> <% end %>
またヘルパーメソッドはform_withの他にも様々なものが存在していて、有名なものでいえばlink_toメソッドが存在します。※他のヘルパーメソッドが気になる方は下記で詳細を確認してみてください
ActionView::Helpers②フォーム入力欄を作成できる
またヘルパーメソッドはビューファイル上で用いられ、HTMLの代わりのような役割を果たします。
具体的にはHTMLタグを出現させたり、テキストの加工を行うことが可能です。
form_withの場合は、HTMLにおけるinputタグにあたるものを生成できます。
つまりフォーム入力欄が作成できることになります。
HTMLのフォーム記述<form action="/posts" method="post"> <input type="text" name="content"> <input type="submit" value="投稿する"> </form>ヘルパーメソッドを用いたフォーム記述<%= form_with url: "/posts", method: :post, local: true do |form| %> <%= form.text_field :content %> <%= form.submit '投稿する' %> <% end %>③まとめ
ヘルパーメソッドとはRails内部で定義されているメソッドのことで、ビューファイルでの記述をシンプルにするためのもの
form_withはヘルパーメソッドの一種である
form_withはHTMLでinputタグにあたるものを作成し、フォーム入力欄を作成することができる
2. form_withはどのような役割を果たしているのか?
次にform_withメソッドは実際どのような役割を果たしているのか?について考えていきます。
結論からいうと、form_withメソッドは
ユーザーからサーバー側に情報を送信する
という役割を果たしています。
これだけでは少しわかりづらいので、実際のアプリケーションであるTwitterを例に考えてみましょう
Twitterでform_withがどういう役割を果たしているのか?考えてみる
Twitterには様々な機能がありますが、その中でも今回は投稿機能に着目して考えてみます
Twitterの投稿機能では
①ユーザーが投稿フォームに値を入力し、ボタンをクリックする
②その投稿がサーバー側に送られる
③サーバーに送られた投稿内容が投稿一覧表示画面に反映されるという流れで処理が行われています。
この際にform_withがどのような動きをしているのか?を考えてみましょう。
①ユーザーが投稿フォームに値を入力し、ボタンをクリックする
まず最初に、form_withで作成された投稿フォームにユーザーが値を入力し、送信(投稿)ボタンをクリックします。
作成した投稿フォーム<%= form_with url: "/posts", method: :post, local: true do |form| %> <%= form.text_field :content %> <%= form.submit '投稿する' %> <% end %>②その投稿がサーバー側に送られる
次に、送信(投稿)ボタンを押したため、先ほど作成したツイートの内容が送信されます。
ここで大半の人が疑問に思っていることが
「ツイートが送信されるのは感覚的にはわかるが、どこへ送信されているのか?」
「ツイートはどのようにして送信されているのか?」だと思います。
なので、この送信される過程についてもう少し詳しく見ていきましょう。
この過程は大きく分けると、次の3つに分かれます。
- リクエストが送信される
- ルーティングによって、「どのコントローラーのどのアクションを行うのか?」が決められる
- 2で指定されたコントローラーへツイートの内容が渡される
1. リクエストが送信される
まず送信ボタンをクリックすると、使用しているブラウザからサーバー側にリクエストが送られます。
リクエストに関してわからない人がいれば、「HTTPリクエスト」と検索してみてください。
簡単にいうと、ユーザーからサーバーへの「〜して欲しい!」という要望みたいなものです。そしてこの時リクエストには パラメーターと呼ばれるものが含まれます
※ パラメーター
パラメーターとはリクエストに含まれてサーバーの外部から渡されるデータのことを指します。
わかりやすく言い換えると、外から入ってくる値のことです。
少し身近な例に例えて考えてみましょう。
例えば、自動販売機を想像してみてください。
自販機は100円を入れたら、「100」と料金表示の欄に表示されます。また1000円札を入れれば、「1000」と表示されます。
この時、自販機の料金表示機能をプログラムと見立てた場合に、
自販機に入金したお金というのは自販機側からは外から入ってくるものであるので、パラメータになります。
これがパラメータのイメージです。
そしてパラメータにはいくつか種類があります。
例えば、URLに含まれるパラメータはURLパラメータと呼ばれたりします。これは http://tweets.jp/tweets/1 の
/1
ように、URLの末尾に文字列が含まれているものを指します。2. ルーティングによって、「どのコントローラーのどのアクションを行うか」が決定される
次に送信されたリクエストは、サーバー側のルーターに遷移します。
このルーターでは受け取ったリクエストを見て、「どのコントローラのなんの処理を行うか」を決定します。
このように、
「このリクエストに対してはこのコントローラーのこの処理を行う」
みたいなルールのことをルーティング
と言います。ここでは詳細の説明は省きます。
また「コントローラーやアクション」などの詳しい説明に関しても、今回は省かせていただきます。
RailsにおけるMVCの理解は最低限できているものとして本記事は進ませていただきますので、ご注意ください。3. 2で指定されたコントローラーへツイート内容(パラメーター)が渡される
最後に、2で指定されたコントローラーへ
リクエストに含まれるパラメーター
が渡されますただこの時、パラメーターには様々な情報があるため、そのまま渡されると非常にわかりづらくなってしまいます。
そこで、パラメータはparamsと呼ばれるものに一度格納されてからコントローラーへ渡されます。
※ params
paramsとはハッシュのような構造を取るパラメーターを格納するためのものです。
実際には以下のような形をとっています。
しかし、これだと少しわかりづらい方もいらっしゃると思うので、
「paramsはパラメータを格納するための箱のようなもの」
と考えると、イメージしやすいかもしれません。以下のようなイメージです。
フォームに入力された値は、リクエストの中に含まれるパラメータとしてリクエストと共にサーバー側のルーターに送信され、そこでparamsに格納されてサーバー側のコントローラーへ送信される
ということになります。
③サーバーに送られた投稿内容が投稿一覧表示画面に反映される
次にサーバーに送られた投稿内容は、コントローラーによって処理が行われます。
今回だと、投稿一覧画面に投稿内容を反映させるために、送られてきた投稿内容を保存するという処理が行われます。
具体的には
tweets_controller.rbdef create Tweet.create(tweet_params) endのような処理が行われ、投稿一覧画面に反映されます。
Twitterの投稿機能の場合は、このように処理が行われます。
ではこの時、
form_withはどのような役割を果たしていたのか?考えてみましょう。
form_withは、上の一連の処理の中では①で登場しました。
つまり、サーバーに投稿内容を送信するという役割を果たしていることがここでわかるかと思います。
3. form_withの具体的な記述法
form_withの表記の仕方は大きく2つに分かれます。
①基本形
基本形<%= form_with url: "パス" do |form| %> フォーム内容 <% end %>urlにはリクエスト先のパス(URLと考えてもらって大丈夫です)が入ります。
そして、この基本形の場合はオプションをつけることが可能です。
※オプションについて
オプションとは
メソッドに補足的に付けられるもので、メソッドの情報を補足する働きがあります。
基本形の場合は以下のようなオプションをつけることが可能です。
methodオプション
form_withで送信されるHTTPリクエストのデフォルトはPOSTであり、methodオプションはそのHTTPリクエストを変更したい場合に用います
localオプション
form_withから送られたリクエストはデフォルトではHTTP通信が行われていないため、それをHTTP通信にする場合localオプションを用います
オプションを付けた場合<%= form_with url: "/posts", method: :post, local: true do |form| %> フォーム内容 <% end %>②応用形
①のようにurlにリクエスト先を指定したり、methodオプションでHTTPメソッドを指定したりするのが面倒な場合はそれらを省略して、以下のように記述することができます
応用形<%= form_with model: モデルクラスのインスタンス do |form| %> フォーム内容 <% end %>そして、この応用形には他にも様々なメリットがあるためform_withを用いる場合は
こちらがよく使われる傾向にあります。
4. まとめ
- form_withはヘルパーメソッドの一種で、ビューファイル上でフォーム入力欄を作成できるメソッドのことを言います。
- form_withメソッドはユーザーからサーバー側に情報を送信するという役割を果たしている
- form_withの表記は大きく分けると、urlオプションとmomdelオプションを使う場合の2種類が存在する
5. 最後に
本記事の内容がみなさんの参考になれば嬉しいです。
最後までご覧いただきありがとうございました。
6. 参照サイト
https://qiita.com/snskOgata/items/44d32a06045e6a52d11c
https://qiita.com/tomoharutt/items/46e6358acc8b45cd7db1
https://qiita.com/hayulu/items/5bf26656d7433d406ede
- 投稿日:2020-11-23T16:20:25+09:00
JavaScript基礎まとめ
JavaScriptの基礎を自分の備忘録&アウトプットも兼ねて記述。
そもそも、、JavaScriptはWEBページ内でページ遷移や更新をせずに画面を表示できる。
つまり、読み込む時間がないのでユーザーはストレスなく使えるということ。検証ツール(ディベロッパーツール)で確認ができる(Rubyでいうirb的なやつ)
右クリック+検証、または⌘ + option + Cで出せる1.変数定義
var(ほとんど使わないらしいから省略)
const
let の3種類・const 再代入、再定義NG
const sample = "Hello" sample = "Hello" 再代入NG const sample ="こんにちは" 再定義もNG・let
const sample = "Hello" sample = "Hello" 再代入OK const sample ="こんにちは" 再定義はNG2.テンプレートリテラル
文字の中に変数を入れるrubyだと
name = "yamada" puts #{name}さんだったのが
const name = "yamada" console.log(`${name}さん`)この``で囲うのがテンプレートリテラル
3.条件分岐
if (条件式1) { // 条件式1がtrueのときの処理 } else if (条件式2) { // 条件式1がfalseで条件式2がtrueのときの処理 } else { // 条件式1も条件式2もfalseのときの処理 }rubyとの違いは
条件式を()で囲ったり、条件式の後の処理は{}で囲うこと、
elsif ではなく else ifぐらい、、?4.繰り返し(for文)
for ([①初期化式]; [②条件式]; [③加算式]) { // 繰り返す処理の内容 }①初期化式
変数の定義をする
②条件式
処理内容を何回繰り返すか
③加算式
①で定義をした変数の増減を書くlet name = ['A','B','C','D','E'] for (let i = 0; i <= name.length; i ++) { console.log(name[i]) }①②③で考えると
①変数 i = 0
②配列の中から i 番目を取り出す(現時点ではi=0としている)
このi番目の長さがnameの配列の個数以上になるまで繰り返す
③繰り返す度にiに1ずつ加えていく、、、もし間違えている点があればコメントいただきたいです!よろしくお願いいたします。
- 投稿日:2020-11-23T16:18:59+09:00
【Rails6】Active Job + Sidekiqを動かしてみた
Railsの非同期処理をActive Job + Sidekiqで実装したのでメモを残します。
※RailsアプリケーションはDocker環境で構築済みの前提です。環境構築はこちら。
※Active Jobとバックエンドの比較はこちら。環境
- Ruby 2.7.2
- Rails 6.0.3.4
- MySQL 8.0.20
- Redis 6.0.9
- Sidekiq 6.1.2
- Docker version 19.03.13
1. Redisの導入
まずredisコンテナを用意します。
ポート番号はdocker-compose.override.yml
で指定していますが、下記で設定して問題ないと思います。docker-compose.ymlversion: '3.7' services: db: image: mysql:8.0.20 volumes: - mysql:/var/lib/mysql:delegated command: --default-authentication-plugin=mysql_native_password env_file: .env web: build: context: . dockerfile: Dockerfile command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" tty: true stdin_open: true env_file: .env depends_on: - db - chrome - redis volumes: - .:/app:cached - bundle:/usr/local/bundle:delegated - node_modules:/app/node_modules chrome: image: selenium/standalone-chrome:3.141.59 volumes: - /dev/shm:/dev/shm redis: image: redis:6.0.9 env_file: .env command: redis-server --appendonly yes volumes: - redis:/data volumes: mysql: bundle: node_modules: redis:docker-compose.override.ymlversion: '3.7' services: db: ports: - 3306:3306 web: ports: - 3000:3000 chrome: ports: - 4444:4444 redis: ports: - 6379:6379Redisの設定ファイルを追加します。
host名に注意してください。config/redis.ymldefault: &default db: sidekiq: 0 # cache: 1 # session: 2 development: <<: *default host: redis test: <<: *default host: redis2. Sidekiqの設定
まずはGemを追加します。
ruby:Gemfile
gem 'sidekiq'
bundle install
を実行し、設定ファイルを追加します。
詳しくは、こちらを参照してください。config/sidekiq.yml:verbose: false :max_retries: 1 :concurrency: 10 :pidfile: ./tmp/pids/sidekiq.pid :logfile: ./log/sidekiq.log :queues: - development_default続いてSidekiqとRedisの接続情報も追加します。
config/initializers/sidekiq.rbredis_config = YAML.load_file('config/redis.yml')[Rails.env] redis_config['db'] = redis_config['db']['sidekiq'] Sidekiq.configure_server do |config| config.redis = { url: "redis://#{redis_config['host']}/#{redis_config['db']}" } end Sidekiq.configure_client do |config| config.redis = { url: "redis://#{redis_config['host']}/#{redis_config['db']}" } endダッシュボードのルーティングも設定します。
config/routes.rbrequire 'sidekiq/web' Rails.application.routes.draw do mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end3. Active Jobの設定
Active Jobで非同期処理を実装していきます。
config/application.rbrequire "active_job/railtie" module App class Application < Rails::Application config.active_job.queue_adapter = :sidekiq config.active_job.queue_name_prefix = Rails.env # これは任意 end end4. ジョブの作成と動作確認
ようやくジョブを作成します。
rails g job sampleapp/jobs/sample_job.rbclass SampleJob < ApplicationJob queue_as :default def perform puts '--------------------------------' puts '------------ Test ------------' puts '--------------------------------' end end続いて動作確認を行います。
dockerコンテナを起動し、RailsとRedisが動いていることを確認します。
コンテナ内でsidekiqを起動します。$ bundle exec sidekiq -C config/sidekiq.yml 2020-11-23T07:06:57.513Z pid=74 tid=9om INFO: Booting Sidekiq 6.1.2 with redis options {:url=>"redis://redis/0"} m, `$b .ss, $$: .,d$ `$$P,d$P' .,md$P"' ,$$$$$b/md$$$P^' .d$$$$$$/$$$P' $$^' `"/$$$' ____ _ _ _ _ $: ,$$: / ___|(_) __| | ___| | _(_) __ _ `b :$$ \___ \| |/ _` |/ _ \ |/ / |/ _` | $$: ___) | | (_| | __/ <| | (_| | $$ |____/|_|\__,_|\___|_|\_\_|\__, | .d$$ |_| 2020-11-23T07:06:57.938Z pid=74 tid=9om INFO: Booted Rails 6.0.3.4 application in development environment 2020-11-23T07:06:57.939Z pid=74 tid=9om INFO: Running in ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux] 2020-11-23T07:06:57.939Z pid=74 tid=9om INFO: See LICENSE and the LGPL-3.0 for licensing details. 2020-11-23T07:06:57.939Z pid=74 tid=9om INFO: Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org 2020-11-23T07:06:57.946Z pid=74 tid=9om INFO: Starting processing, hit Ctrl-C to stop別タブのターミナルでRailsコンソールから
SampleJob
をキューイングしてみます。$ rails c Loading development environment (Rails 6.0.3.4) > SampleJob.set(wait: 5.second).perform_later Enqueued SampleJob (Job ID: fdcf5c60-3542-4b26-bfd2-9662ffafada9) to Sidekiq(development_default) at 2020-11-23 07:08:49 UTC => #<SampleJob:0x00005567e535e4c0 @arguments=[], @exception_executions={}, @executions=0, @job_id="fdcf5c60-3542-4b26-bfd2-9662ffafada9", @priority=nil, @provider_job_id="7c42f602bd499e75efecad26", @queue_name="development_default", @scheduled_at=1606115329.3860393> >キューイングすると先ほど起動したSidekiq側でジョブが実行されたことが確認できました。
2020-11-23T07:08:52.800Z pid=74 tid=b6y class=SampleJob jid=7c42f602bd499e75efecad26 INFO: start -------------------------------- ------------ Test ------------ -------------------------------- 2020-11-23T07:08:53.067Z pid=74 tid=b6y class=SampleJob jid=7c42f602bd499e75efecad26 elapsed=0.267 INFO: done
- 投稿日:2020-11-23T16:08:09+09:00
【自分メモ】モデル/コントローラー/ビューの役割
モデル…コントローラーで定義したインスタンス変数にデータを入れるため、コントローラーからモデルへデータを取ってくるよう指示が出る(モデル名.all)→指示を受けたモデルはデータベースからデータを取得後、コントローラーのインスタンス変数へ入れる。
またコントローラーから指示を受けて新しいオブジェクトを作成する(モデル名.new)。コントローラー…インスタンス変数に入れるデータをモデルに持ってくるよう指示を出したり、そのインスタンス変数をビューに渡したりする。
ビュー…コントローラーから受け取ったインスタンス変数を元にhtmlファイルを作成する。
※自分の考えをメモとして残していますので、間違っていたり修正した方が良い点がございましたら、ドシドシつっこみいただけると嬉しいです!
- 投稿日:2020-11-23T16:04:46+09:00
[Ruby]ハマりを最小限に抑えるseleniumの導入方法
はじめに
seleniumは、Webアプリケーションのテストを自動化するためのツールであり、プログラムからWebブラウザの自動操作を行うことが出来ます。
自分が導入した際につまづいた点を振り返りながら、初心者なりに考え、仮にエラーが出たとしても調査しやすいような手順にまとめましたので、これから導入される方のお役に立てればと思います。(フォルダ構成が変わるだけで、WinもMacも作業は同じです)selenium導入手順 目次
0_webスクレイピングで気をつけること
1_確認作業用フォルダの作成
2_chromedriverのダウンロード
3_seleniumのインストール
4_中間動作確認
5_chromedriverの移動
6_最終動作確認0_webスクレイピングで気をつけること
seleniumを使ってWebアプリケーションの自動テストの他に、webスクレイピングも出来ます。スクレイピングの際、やって良いこと/悪いことがありますので、これから始めるという方は下記の記事を読まれることをおすすめします。
- Webスクレイピングの注意事項一覧
- WEBスクレイピングの技術と懸念事項1_確認作業用フォルダの作成
確認作業のためにデスクトップにフォルダを作り、中にhtmlファイルとRubyファイルを作成します。
コンソールで下記を実行
mkdir /Users/ユーザーネーム/Desktop/sele_work
cd /Users/ユーザーネーム/Desktop/sele_work
touch sample.html
touch selenium_test.rb
ファイルを編集
sample.html<!DOCTYPE html> <html> <head> <title>テスト用</title> </head> <body> <h1>ハンバーガーショップ</h1> <div> メニュー: <select name="menu"> <option value="">メニューを選択してください</option> <option value="normal">ハンバーガー</option> <option value="cheese">チーズバーガー</option> <option value="double">ダブルバーガー</option> </select> <br> サブメニュー: <input type=checkbox name="submenu" value="drink">飲み物 <input type=checkbox name="submenu" value="potato">ポテト <input type=checkbox name="submenu" value="nugget">ナゲット <br> お持ち帰り: <input type=radio name="takeout" value="yes">yes <input type=radio name="takeout" value="no">no <br> メモ: <input type=text name="memo"> </div> <input type=submit value="OK"> <input type=reset value="キャンセル"> </body> </html>selenium_test.rbrequire "selenium-webdriver" url = "file:///Users/ユーザーネーム/Desktop/sele_work/sample.html" # chromeドライバは作業フォルダに仮置中なので作業フォルダを指定する chromedriver_path = "/Users/ユーザーネーム/Desktop/sele_work/chromedriver" Selenium::WebDriver::Chrome::Service.driver_path = chromedriver_path driver = Selenium::WebDriver.for :chrome # urlへアクセス driver.get(url) p driver.page_source driver.quit2_chromedriverのダウンロード
Google Chromeのバージョンを確認してから、自分のchromeのバージョンに合ったドライバをこちらからダウンロードし、解凍した
chromedriver
を/Users/ユーザーネーム/Desktop/sele_work
に置きます。3_seleniumのインストール
コンソールで下記を実行
gem install selenium-webdriver
4_中間動作確認
ここで一旦、
ruby selenium_test.rb
を実行し、sample.html
のsourceが取得できていることを確認します。セキュリティーのエラーが出たら、左上のりんごマーク > システム環境設定 > セキュリティーとプライバシーに進み許可を選択します。セキュリティーブロックを解除してもエラーが発生する場合は、以下が考えられます。
- Google Chromeとchromedriverのバージョンが合っていない
- 誤字やファイル指定のミスが有る5_chromedriverの移動
chromedriver
の動作確認が出来たので、本来置くべき場所の/usr/local/bin
へ移動します。
コンソールで下記を実行し、ちゃんと移動できたかも確認。
sudo mv chromedriver /usr/local/bin/
which chromedriver
PATHが通ってるか確認
echo $PATH
もし通っていないならPATHを追加
export PATH=“/usr/local/bin:$PATH”
6_最終動作確認
最後に、
selenium_test.rb
を修正し、再度ruby selenium_test.rb
を実行する。
中間動作確認と同じ結果が得られれば任務完了。
ここでエラーが出る場合は、PATHが通っていないことが考えられます。selenium_test.rbrequire "selenium-webdriver" url = "file:///Users/ユーザーネーム/Desktop/sele_work/sample.html" # chromeドライバは作業フォルダに仮置中なので作業フォルダを指定する # コメントアウト ここから # chromedriver_path = "/Users/ユーザーネーム/Desktop/sele_work/chromedriver" # Selenium::WebDriver::Chrome::Service.driver_path = chromedriver_path # ここまで コメントアウト driver = Selenium::WebDriver.for :chrome # urlへアクセス driver.get(url) p driver.page_source driver.quit
- 投稿日:2020-11-23T15:52:10+09:00
Enumerator::Lazy でエラトステネスの篩
Enumerator::Lazy は無限数列を扱うことができるが、それを使って「エラトステネスの篩」をちょっとおもしろく実装できることに気づいた。何はともあれコードである。
lazy_prime.rbprime_seq = Enumerator.new do |y| sieve = 2.step.lazy loop do a = sieve.first y << a sieve = sieve.reject {|x| (x % a).zero?} end end prime_seq.take(10) #=>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Enumerator::Lazy はsieve = 2.step.lazy
のところで生成している。これの先頭a
は必ず素数なので、素数列として Enumerator で取り出す(y << a
)。そして、sieve
からa
の倍数をreject
ですべて篩い落とし(Lazy だから可能なのである)、そうされたものを新しいsieve
とする。以下、繰り返し、というわけである。これは子供の頃に紙と鉛筆でやってみた「エラトステネスの篩」のやり方そのものである。小さい方から素数に丸でも打っておいて、その倍数を斜線で消していく…。実装としては、素直といえば素直だ。
しかし、これ、めちゃめちゃに遅いのはすぐわかる。素数ひとつ得るたびに、Enumerator::Lazy オブジェクトを作り直しているのだから、どれほど遅いかというと、例えば最初の 500個の素数を得るのに、この方法だとわたしの環境でなんと 2.8秒ほどもかかる。標準添付ライブラリの prime を使うと 0.0002秒ほどだから、比較にもならない。
でもまあ、ちょっとおもしろかったので書いてみた。
なお、単なる Enumerator ももちろん無限数列を扱うことができるが、
map
やselect
、それにここで使ったreject
などの結果が有限数列(Array)になってしまう。なので、Enumerator::Lazy を使ったのである。為念。以上はブログ記事の転載です。
- 投稿日:2020-11-23T14:59:19+09:00
自分のメモrubyhttp/httpsリクエスト
いつもhttpリクエストでどういう実装するのか悩むのでコピペですぐ使えるようにする。
faradayを経験してみる/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/net/protocol.rb:44:in `connect_nonblock': SSL_connect returned=1 errno=0 state=SSLv3 read server hello A: sslv3 alert handshake failure (OpenSSL::SSL::SSLError)
- https://jsonplaceholder.typicode.com/
- https://jsonplaceholder.typicode.com/todos/1
- https://h5y1m141.github.io/step-up-javascript/doc/use_jsonplaceholder_with_chrome.html
- https://qiita.com/takano-h/items/dd10818eb7e09161bc29 これを経験してみる
ruby version
$ ruby -v ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]rubyのマニュアルはどこにあるの?
ruby マニュアルで検索
https://docs.ruby-lang.org/ja/
https://docs.ruby-lang.org/ja/2.6.0/doc/index.htmlhttpはネットワークのところにあった。
https://docs.ruby-lang.org/ja/2.6.0/library/index.html
https://docs.ruby-lang.org/ja/2.6.0/library/net=2fhttp.htmlマニュアルに沿って試してみる
例1: GET して 表示するだけ。httpでアクセスしていると思う
$ cat test.rb require 'net/http' Net::HTTP.get_print 'jsonplaceholder.typicode.com', '/todos/1' $ ruby test.rb { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false
Net::HTTP.get_printの機能を調べるにはどうするか?
- https://docs.ruby-lang.org/ja/latest/class/Net=3a=3aHTTP.html
- 非常にわかりづらい,ページした方の方に各メソッドのリンクがある
URI.parseのマニュアルは?
例2: URI を使う
$ cat test.rb require 'net/http' require 'uri' Net::HTTP.get_print URI.parse('http://jsonplaceholder.typicode.com/todos/1') # httpsにすると動かなかった $ ruby test.rb { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false例3: より汎用的な例
require 'net/http' require 'uri' url = URI.parse('http://jsonplaceholder.typicode.com') res = Net::HTTP.start(url.host, url.port) {|http| http.get('/todos/1') } puts res.body $ ruby test.rb { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }
- http.start https://docs.ruby-lang.org/ja/latest/method/Net=3a=3aHTTP/s/start.html
- http.get https://docs.ruby-lang.org/ja/latest/method/Net=3a=3aHTTP/s/get.html
例4: 上の例よりさらに汎用的な例
require 'net/http' url = URI.parse('http://www.example.com/index.html') req = Net::HTTP::Get.new(url.path) res = Net::HTTP.start(url.host, url.port) {|http| http.request(req) } puts res.body 結果は省略、動いた
- Net::HTTP::Get.new https://docs.ruby-lang.org/ja/latest/class/Net=3a=3aHTTP=3a=3aGet.html
リクエストヘッダを指定する方法、portも変える
https://qiita.com/takano-h/items/dd10818eb7e09161bc29
https://qiita.com/mogulla3/items/a4bff2e569dfa7da1896require "net/http" uri = URI.parse("http://jsonplaceholder.typicode.com/todos/1") http = Net::HTTP.new(uri.host, uri.port) # port指定したければuri.portをport番号に置き換える # puts uri.port # httpなら80、httpsなら443がデフォルト値になる http.use_ssl = uri.scheme === "https" # uri.schemeがhttp or httpsだと思われ。一致したらtrueをhttp.use_sslを代入する。一致しなければfalseを代入。trueだとsslを使用するという設定になる headers = { "Content-Type" => "application/json" } # ヘッダーの設定 req = Net::HTTP::Get.new(uri.path) req.initialize_http_header(headers) response = http.request(req) # リクエストを投げる puts response.code # status code puts response.body # response body最終形、リクエストを投げて、JSONオブジェクトをHashへパースする
https://qiita.com/mogulla3/items/a4bff2e569dfa7da1896
require 'net/http' require 'uri' require 'json' require 'logger' # [ロガー] # カレントディレクトリのwebapi.logというファイルに出力 # (DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN) logger = Logger.new('./webapi.log') logger.info("start") # [クエリパラメータ] # URI.encode_www_formを使って「application/x-www-form-urlencoded」形式の文字列に変換 # 文字列はURLエンコードされた形式に変換(半角スペースの"+"への変換等) # # (変換例) # 'bar baz' => 'bar+baz' # 'あ' => '%E3%81%82' # params = URI.encode_www_form({ param1: 'foo', param2: 'bar baz' , param3: 'あ' }) # [URI] # URI.parseは与えられたURIからURI::Genericのサブクラスのインスタンスを返す # -> 今回はHTTPプロトコルなのでURI::HTTPクラスのインスタンスが返される # # オブジェクトからは以下のようにして構成要素を取得できる # uri.scheme => 'http' # uri.host => 'mogulla3.com' # uri.port => 4567 # uri.path => '' # uri.query => 'param1=foo¶m2=bar+baz¶m3=%E3%81%82' # uri = URI.parse("http://mogulla3.com:4567?#{params}") uri = URI.parse("http://jsonplaceholder.typicode.com/todos/1") begin # [GETリクエスト] # Net::HTTP.startでHTTPセッションを開始する # 既にセッションが開始している場合はIOErrorが発生 response = Net::HTTP.start(uri.host, uri.port) do |http| # Net::HTTP.open_timeout=で接続時に待つ最大秒数の設定をする # タイムアウト時はTimeoutError例外が発生 http.open_timeout = 5 # Net::HTTP.read_timeout=で読み込み1回でブロックして良い最大秒数の設定をする # デフォルトは60秒 # タイムアウト時はTimeoutError例外が発生 http.read_timeout = 10 # Net::HTTP#getでレスポンスの取得 # 返り値はNet::HTTPResponseのインスタンス http.get(uri.request_uri) end # [レスポンス処理] # 2xx系以外は失敗として終了することにする # ※ リダイレクト対応できると良いな.. # # ステータスコードに応じてレスポンスのクラスが異なる # 1xx系 => Net::HTTPInformation # 2xx系 => Net::HTTPSuccess # 3xx系 => Net::HTTPRedirection # 4xx系 => Net::HTTPClientError # 5xx系 => Net::HTTPServerError case response # 2xx系 when Net::HTTPSuccess # [JSONパース処理] # JSONオブジェクトをHashへパースする # JSON::ParserErrorが発生する可能性がある # {"userId"=>1, "id"=>1, "title"=>"delectus aut autem", "completed"=>false} # p JSON.parse(response.body) result = JSON.parse(response.body) p result p result["userId"] # 3xx系 when Net::HTTPRedirection # リダイレクト先のレスポンスを取得する際は # response['Location']でリダイレクト先のURLを取得してリトライする必要がある logger.warn("Redirection: code=#{response.code} message=#{response.message}") else logger.error("HTTP ERROR: code=#{response.code} message=#{response.message}") end # [エラーハンドリング] # 各種処理で発生しうるエラーのハンドリング処理 # 各エラーごとにハンドリング処理が書けるようにrescue節は小さい単位で書く # (ここでは全て同じ処理しか書いていない) rescue IOError => e logger.error(e.message) rescue TimeoutError => e logger.error(e.message) rescue JSON::ParserError => e logger.error(e.message) rescue => e logger.error(e.message) ensure logger.info("end") end ---------------- $ ruby test.rb {"userId"=>1, "id"=>1, "title"=>"delectus aut autem", "completed"=>false} 1
- 返り値はNet::HTTPResponseのインスタンス https://docs.ruby-lang.org/ja/latest/class/Net=3a=3aHTTPResponse.html
- logger https://docs.ruby-lang.org/ja/latest/class/Logger.html
- https://jsonplaceholder.typicode.com/todos/1 応答がないことがよくある
httpsでGETする。postが必要になったらまた調査しよう
https://qiita.com/katu_/items/a116c261c8616fd01fca
require 'net/https' require 'uri' require 'json' require 'logger' # [ロガー] # カレントディレクトリのwebapi.logというファイルに出力 # (DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN) logger = Logger.new('./webapi.log') logger.info("start") # [クエリパラメータ] # URI.encode_www_formを使って「application/x-www-form-urlencoded」形式の文字列に変換 # 文字列はURLエンコードされた形式に変換(半角スペースの"+"への変換等) # # (変換例) # 'bar baz' => 'bar+baz' # 'あ' => '%E3%81%82' # params = URI.encode_www_form({ param1: 'foo', param2: 'bar baz' , param3: 'あ' }) # [URI] # URI.parseは与えられたURIからURI::Genericのサブクラスのインスタンスを返す # -> 今回はHTTPプロトコルなのでURI::HTTPクラスのインスタンスが返される # # オブジェクトからは以下のようにして構成要素を取得できる # uri.scheme => 'http' # uri.host => 'mogulla3.com' # uri.port => 4567 # uri.path => ''AA # uri.query => 'param1=foo¶m2=bar+baz¶m3=%E3%81%82' uri = URI.parse("https://jsonplaceholder.typicode.com/todos/1") begin # [GETリクエスト] # Net::HTTP.startでHTTPセッションを開始する # 既にセッションが開始している場合はIOErrorが発生 # httpのようにブロックを使ったら動かなかった # HTTP.startは使えない。startを使うと、すぐにsessionを貼るので、https.use_ssl=falseができない https = Net::HTTP.new(uri.host, uri.port) https.use_ssl = uri.scheme === "https" https.verify_mode = OpenSSL::SSL::VERIFY_NONE # テストのときはこれでよい。社内テストでエラーのときは対応が必要。ssl証明書を取得してあげる? # Net::HTTP.open_timeout=で接続時に待つ最大秒数の設定をする # タイムアウト時はTimeoutError例外が発生 https.open_timeout = 5 # Net::HTTP.read_timeout=で読み込み1回でブロックして良い最大秒数の設定をする # デフォルトは60秒 # タイムアウト時はTimeoutError例外が発生 https.read_timeout = 10 req = Net::HTTP::Get.new(uri.request_uri) response = https.request(req) # [レスポンス処理] # 2xx系以外は失敗として終了することにする # ※ リダイレクト対応できると良いな.. # # ステータスコードに応じてレスポンスのクラスが異なる # 1xx系 => Net::HTTPInformation # 2xx系 => Net::HTTPSuccess # 3xx系 => Net::HTTPRedirection # 4xx系 => Net::HTTPClientError # 5xx系 => Net::HTTPServerError case response # 2xx系 when Net::HTTPSuccess # [JSONパース処理] # JSONオブジェクトをHashへパースする # JSON::ParserErrorが発生する可能性がある # {"userId"=>1, "id"=>1, "title"=>"delectus aut autem", "completed"=>false} # p JSON.parse(response.body) result = JSON.parse(response.body) p result p resul["userId"] # 3xx系 when Net::HTTPRedirection puts "baaa" # リダイレクト先のレスポンスを取得する際は # response['Location']でリダイレクト先のURLを取得してリトライする必要がある logger.warn("Redirection: code=#{response.code} message=#{response.message}") else logger.error("HTTP ERROR: code=#{response.code} message=#{response.message}") end # [エラーハンドリング] # 各種処理で発生しうるエラーのハンドリング処理 # 各エラーごとにハンドリング処理が書けるようにrescue節は小さい単位で書く # (ここでは全て同じ処理しか書いていない) rescue IOError => e logger.error(e.class) logger.error(e.message) logger.error(e.backtrace) rescue TimeoutError => e logger.error(e.class) logger.error(e.message) logger.error(e.backtrace) rescue JSON::ParserError => e logger.error(e.class) logger.error(e.message) logger.error(e.backtrace) rescue => e logger.error(e.class) logger.error(e.message) logger.error(e.backtrace) ensure logger.info("end") end今度これをやってみよう
https://qiita.com/toshihirock/items/19fc868d3c4c52411aa9
postがわかる気がするfaraday
https://qiita.com/YumaInaura/items/fb18e3d56dced19cbd28
http://nekorails.hatenablog.com/entry/2018/09/28/152745
使いこなせなかったrequire 'faraday' require 'json' require 'logger' # gem install faraday が必要 # シンプル1 # p Faraday.get("https://jsonplaceholder.typicode.com/todos/1", ssl: { verify: false }) # シンプル2 # connection = Faraday.new("https://jsonplaceholder.typicode.com/todos/1", ssl: { verify: false }) # p connection.get # シンプル3 # connection = Faraday.new(url: "https://jsonplaceholder.typicode.com/todos/1", ssl: { verify: false }) # p connection.get # シンプル4 connection = Faraday.new(url: "https://jsonplaceholder.typicode.com",ssl: { verify: false }) response = connection.get "/todos/1" do |request| request.headers["Content-Type"] = "application/json" # ssl を設定しようとしたがエラーになる end #p response p response.body # レスポンスbody result = JSON.parse(response.body) p result p result["userId"] #p response.headers # レスポンスheader #p response.status # ステータスコード #p response.success? # リクエストは成功か?(ステータスコードが200番台か?)エラーハンドリング
- 投稿日:2020-11-23T14:59:19+09:00
自分のメモrubyhttpリクエスト
いつもhttpリクエストでどういう実装するのか悩むのでコピペですぐ使えるようにする。
httpsエラーになったの諦めた。ssl証明書のせいかも。理解できなかった/System/Library/Frameworks/Ruby.framework/Versions/2.6/usr/lib/ruby/2.6.0/net/protocol.rb:44:in `connect_nonblock': SSL_connect returned=1 errno=0 state=SSLv3 read server hello A: sslv3 alert handshake failure (OpenSSL::SSL::SSLError)
- https://jsonplaceholder.typicode.com/
- https://jsonplaceholder.typicode.com/todos/1
- https://h5y1m141.github.io/step-up-javascript/doc/use_jsonplaceholder_with_chrome.html
- https://qiita.com/takano-h/items/dd10818eb7e09161bc29 これを経験してみる
ruby version
$ ruby -v ruby 2.6.3p62 (2019-04-16 revision 67580) [universal.x86_64-darwin19]rubyのマニュアルはどこにあるの?
ruby マニュアルで検索
https://docs.ruby-lang.org/ja/
https://docs.ruby-lang.org/ja/2.6.0/doc/index.htmlhttpはネットワークのところにあった。
https://docs.ruby-lang.org/ja/2.6.0/library/index.html
https://docs.ruby-lang.org/ja/2.6.0/library/net=2fhttp.htmlマニュアルに沿って試してみる
例1: GET して 表示するだけ。httpでアクセスしていると思う
$ cat test.rb require 'net/http' Net::HTTP.get_print 'jsonplaceholder.typicode.com', '/todos/1' $ ruby test.rb { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false
Net::HTTP.get_printの機能を調べるにはどうするか?
- https://docs.ruby-lang.org/ja/latest/class/Net=3a=3aHTTP.html
- 非常にわかりづらい,ページした方の方に各メソッドのリンクがある
URI.parseのマニュアルは?
例2: URI を使う
$ cat test.rb require 'net/http' require 'uri' Net::HTTP.get_print URI.parse('http://jsonplaceholder.typicode.com/todos/1') # httpsにすると動かなかった $ ruby test.rb { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false例3: より汎用的な例
require 'net/http' require 'uri' url = URI.parse('http://jsonplaceholder.typicode.com') res = Net::HTTP.start(url.host, url.port) {|http| http.get('/todos/1') } puts res.body $ ruby test.rb { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }
- http.start https://docs.ruby-lang.org/ja/latest/method/Net=3a=3aHTTP/s/start.html
- http.get https://docs.ruby-lang.org/ja/latest/method/Net=3a=3aHTTP/s/get.html
例4: 上の例よりさらに汎用的な例
require 'net/http' url = URI.parse('http://www.example.com/index.html') req = Net::HTTP::Get.new(url.path) res = Net::HTTP.start(url.host, url.port) {|http| http.request(req) } puts res.body 結果は省略、動いた
- Net::HTTP::Get.new https://docs.ruby-lang.org/ja/latest/class/Net=3a=3aHTTP=3a=3aGet.html
リクエストヘッダを指定する方法、portも変える
https://qiita.com/takano-h/items/dd10818eb7e09161bc29
https://qiita.com/mogulla3/items/a4bff2e569dfa7da1896require "net/http" uri = URI.parse("http://jsonplaceholder.typicode.com/todos/1") http = Net::HTTP.new(uri.host, uri.port) # port指定したければuri.portをport番号に置き換える # puts uri.port # httpなら80、httpsなら443がデフォルト値になる http.use_ssl = uri.scheme === "https" # uri.schemeがhttp or httpsだと思われ。一致したらtrueをhttp.use_sslを代入する。一致しなければfalseを代入。trueだとsslを使用するという設定になる headers = { "Content-Type" => "application/json" } # ヘッダーの設定 req = Net::HTTP::Get.new(uri.path) req.initialize_http_header(headers) response = http.request(req) # リクエストを投げる puts response.code # status code puts response.body # response body最終形、リクエストを投げて、JSONオブジェクトをHashへパースする
いつかhttpsに対応させる
https://qiita.com/mogulla3/items/a4bff2e569dfa7da1896
require 'net/http' require 'uri' require 'json' require 'logger' # [ロガー] # カレントディレクトリのwebapi.logというファイルに出力 # (DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN) logger = Logger.new('./webapi.log') logger.info("start") # [クエリパラメータ] # URI.encode_www_formを使って「application/x-www-form-urlencoded」形式の文字列に変換 # 文字列はURLエンコードされた形式に変換(半角スペースの"+"への変換等) # # (変換例) # 'bar baz' => 'bar+baz' # 'あ' => '%E3%81%82' # params = URI.encode_www_form({ param1: 'foo', param2: 'bar baz' , param3: 'あ' }) # [URI] # URI.parseは与えられたURIからURI::Genericのサブクラスのインスタンスを返す # -> 今回はHTTPプロトコルなのでURI::HTTPクラスのインスタンスが返される # # オブジェクトからは以下のようにして構成要素を取得できる # uri.scheme => 'http' # uri.host => 'mogulla3.com' # uri.port => 4567 # uri.path => '' # uri.query => 'param1=foo¶m2=bar+baz¶m3=%E3%81%82' # uri = URI.parse("http://mogulla3.com:4567?#{params}") uri = URI.parse("http://jsonplaceholder.typicode.com/todos/1") begin # [GETリクエスト] # Net::HTTP.startでHTTPセッションを開始する # 既にセッションが開始している場合はIOErrorが発生 response = Net::HTTP.start(uri.host, uri.port) do |http| # Net::HTTP.open_timeout=で接続時に待つ最大秒数の設定をする # タイムアウト時はTimeoutError例外が発生 http.open_timeout = 5 # Net::HTTP.read_timeout=で読み込み1回でブロックして良い最大秒数の設定をする # デフォルトは60秒 # タイムアウト時はTimeoutError例外が発生 http.read_timeout = 10 # Net::HTTP#getでレスポンスの取得 # 返り値はNet::HTTPResponseのインスタンス http.get(uri.request_uri) end # [レスポンス処理] # 2xx系以外は失敗として終了することにする # ※ リダイレクト対応できると良いな.. # # ステータスコードに応じてレスポンスのクラスが異なる # 1xx系 => Net::HTTPInformation # 2xx系 => Net::HTTPSuccess # 3xx系 => Net::HTTPRedirection # 4xx系 => Net::HTTPClientError # 5xx系 => Net::HTTPServerError case response # 2xx系 when Net::HTTPSuccess # [JSONパース処理] # JSONオブジェクトをHashへパースする # JSON::ParserErrorが発生する可能性がある # {"userId"=>1, "id"=>1, "title"=>"delectus aut autem", "completed"=>false} # p JSON.parse(response.body) result = JSON.parse(response.body) p result p result["userId"] # 3xx系 when Net::HTTPRedirection # リダイレクト先のレスポンスを取得する際は # response['Location']でリダイレクト先のURLを取得してリトライする必要がある logger.warn("Redirection: code=#{response.code} message=#{response.message}") else logger.error("HTTP ERROR: code=#{response.code} message=#{response.message}") end # [エラーハンドリング] # 各種処理で発生しうるエラーのハンドリング処理 # 各エラーごとにハンドリング処理が書けるようにrescue節は小さい単位で書く # (ここでは全て同じ処理しか書いていない) rescue IOError => e logger.error(e.message) rescue TimeoutError => e logger.error(e.message) rescue JSON::ParserError => e logger.error(e.message) rescue => e logger.error(e.message) ensure logger.info("end") end ---------------- $ ruby test.rb {"userId"=>1, "id"=>1, "title"=>"delectus aut autem", "completed"=>false} 1
- 返り値はNet::HTTPResponseのインスタンス https://docs.ruby-lang.org/ja/latest/class/Net=3a=3aHTTPResponse.html
- logger https://docs.ruby-lang.org/ja/latest/class/Logger.html
- https://jsonplaceholder.typicode.com/todos/1 応答がないことがよくある
今度これをやってみよう
https://qiita.com/toshihirock/items/19fc868d3c4c52411aa9
postがわかる気がする
- 投稿日:2020-11-23T14:43:33+09:00
Rails + Docker でhello worldを表示するまでを簡単に
アプリ作成時に毎回調べている気がするので、備忘録的にまとめてみました。
下記のapp名
の箇所には適宜アプリ名を入力して下さい。※間違いがありましたら変更しますのでコメント頂けると嬉しいです^^
新規ディレクトリ作成 〜 hello world!!まで
まず、アプリの土台となるディレクトリを作ります。
さらに、touchコマンドで、2つの空ファイルを作成します。$ mkdir app名 && cd app名 $ touch Gemfile Gemfile.lock私は、VScodeを使用して開発しているので
code コマンドを使用して起動しています。
ちなみに、code コマンドは起動と作成をしてくれます。Gemfileを編集します。
$ code GemfileGemfilesource 'https://rubygems.org' gem 'rails', '~>5.2'Dockerfileを作成して編集します。
$ code DockerfileDockerfileFROM ruby:2.5 RUN apt-get update RUN apt-get install -y \ build-essential \ libpq-dev \ nodejs \ postgresql-client \ yarn \ vim WORKDIR /app名 COPY Gemfile Gemfile.lock /app名/ RUN bundle installdocker-compose.ymlファイルを作成して編集します。
$ code docker-compose.ymldocker-compose.ymlversion: "3" volumes: db-data: services: web: build: . ports: - "3000:3000" volumes: - ".:/app名" environment: - "DATABASE_PASSWORD=postgres" tty: true stdin_open: true depends_on: - db links: - db db: image: postgres volumes: - "db-data:/var/lib/postgresql/data" environment: - "POSTGRES_HOST_AUTH_METHOD=trust" - "POSTGRES_USER=postgres" - "POSTGRES_PASSWORD=postgres"コンテナの起動を行い、webコンテナに入って、rails new します。
$ docker-compose up --build -d $ docker-compose exec web bash $ rails new . --force --database=postgresqlrails new で作成された database.yml ファイルに追記します。
database.ymldefault: &default adapter: postgresql encoding: unicode host: db #追記 user: postgres #追記 port: 5432 #追記 password: <%= ENV.fetch("DATABASE_PASSWORD") %> #追記 pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>$ rails db:migrate $ rails s -b 0.0.0.0Chromeの検索バーに、localhost:3000 と入力してアクセスするとhello worldが表示されているかと思います!
- 投稿日:2020-11-23T14:34:22+09:00
【自分用】マイグレーション関連【ロールバック、カラムの追加、型の変更】
完全に自分用の書き殴りです。
誤り等ございましたら、コメント下さい!ロールバック
rails db:migrate:down VERSION=20190611235049
VERSION
で指定した箇所まで戻れるrails db:rollback STEP=3
- 現在の migration ファイルから遡って、
STEP
で指定した回数戻れるカラムの追加
rails g migration Addカラム名Toテーブル名 @@@:integer @@@:string
rails db:migrate
- Add 以下はキャメルケースで記載
- カラム名のところは何を書いてもいいが、追加したいカラム名の複数形に統一しておく
- テーブル名にはカラムを追加するテーブル名の複数形を書く
@@@
はカラム名を単数形で記入以下は user テーブルにふりがなを追加したときの例。
class AddKanaToUser < ActiveRecord::Migration[5.2] def change add_column :users, :kana, :string end endカラム型の変更
rails g migration change_data_<カラム名>_to_<テーブル名の複数形>
class ChangeColumnToUsers < ActiveRecord::Migration[5.2] def change end end上記のように空の migration ファイルが作成されるので、下記のように編集
class ChangeColumnToUser < ActiveRecord::Migration[5.2] def up change_column :users, :detail, :text end def down change_column :users, :detail, :string end end注意点
カラムの追加とカラム型の変更では、方法がやや異なっていて、migration ファイルを触る時は、追加(up)するだけでなく、削除(down)するときのことも考えなければならない。
つまり、カラムの追加では、change で書いても rollback 時など down する際にもよしなにしてくれるが、カラム型の変更で change を使ってしまうと、up 時は良くても、down 時に rollback できないというようなことが起こってしまう。ということ。
簡単なことだが、ここを理解したときに、migration ファイルに対する怖さが一気に消えた。おまけ
これからは上記の様に書くとして、これまでに書いてしまった箇所を rollback するさいはどうすればいいのか...
これは簡単で、今ある migration ファイルの change と記載している箇所を、up に上書き変更して、その下に down 時の動作を追記すればよい。down 時の動作は変更前のカラム型のこと。
なんとなく、migration ファイルを上書きするのは怖かったが、これで問題なく動作した。
- 投稿日:2020-11-23T13:59:25+09:00
Docker×Rails6(メモ)
事前準備
環境構築
Docker 関連ファイルを用意
FROM ruby:2.6.3-alpine ENV LANG=ja_JP.UTF-8 ENV TZ=Asia/Tokyo ENV ROOT=/myapp \ GEM_HOME=/bundle \ BUNDLE_PATH=$GEM_HOME ENV BUNDLE_BIN=$BUNDLE_PATH/bin ENV PATH /app/bin:$BUNDLE_BIN:$PATH WORKDIR $ROOT RUN apk update && \ apk upgrade && \ apk add --no-cache \ gcc \ g++ \ libc-dev \ libxml2-dev \ linux-headers \ make \ nodejs \ postgresql \ postgresql-dev \ tzdata \ imagemagick \ yarn && \ apk add --virtual build-packs --no-cache \ build-base \ curl-dev COPY Gemfile $ROOT COPY Gemfile.lock $ROOT RUN bundle install -j4 # 不要ファイル削除 RUN rm -rf /usr/local/bundle/cache/* /usr/local/share/.cache/* /var/cache/* /tmp/* && \ apk del build-packs COPY . $ROOT # Add a script to be executed every time the container starts. COPY entrypoint.sh /usr/bin/ RUN chmod +x /usr/bin/entrypoint.sh ENTRYPOINT ["sh", "/usr/bin/entrypoint.sh"] EXPOSE 3000docker-compose.ymlversion: "3.8" services: db: image: postgres:11.0-alpine volumes: - postgres:/var/lib/postgresql/data:cached ports: - "5432:5432" environment: PGDATA: /var/lib/postgresql/data/pgdata POSTGRES_USER: postgres POSTGRES_PASSWORD: password POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=ja_JP.UTF-8" TZ: Asia/Tokyo app: build: . command: ash -c "rm -f tmp/pids/server.pid && ./bin/rails s -p 3000 -b '0.0.0.0'" volumes: - .:/myapp:cached - rails_cache:/myapp/tmp/cache - node_modules:/myapp/node_modules:cached - bundle:/bundle:cached tmpfs: - /tmp tty: true stdin_open: true ports: - "3000:3000" environment: RAILS_ENV: development NODE_ENV: development DATABASE_HOST: db DATABASE_PORT: 5432 DATABASE_USER: postgres DATABASE_PASSWORD: password WEBPACKER_DEV_SERVER_HOST: webpacker depends_on: - db - webpacker webpacker: build: . command: ./bin/webpack-dev-server volumes: - .:/myapp:cached - node_modules:/myapp/node_modules:cached environment: RAILS_ENV: development NODE_ENV: development WEBPACKER_DEV_SERVER_HOST: 0.0.0.0 tty: false stdin_open: false ports: - "3035:3035" volumes: rails_cache: node_modules: postgres: bundle:source 'https://rubygems.org' gem 'rails', '6.0.3' gem 'devise' # to upload images gem 'carrierwave', '~> 2.0' gem "mini_magick"Gemfile.lock(empry)```entrypoint.sh #!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /myapp/tmp/pids/server.pid # Then exec the container's main process (what's set as CMD in the Dockerfile). exec "$@"cmd
docker-compose run app rails new . --force --no-deps --database=postgresql --skip-bundle
Gemfile を編集する
source 'https://rubygems.org' git_source(:github) { |repo| "https://github.com/#{repo}.git" } ruby '2.6.3' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.3', '>= 6.0.3.4' # Use postgresql as the database for Active Record gem 'pg', '>= 0.18', '< 2.0' # Use Puma as the app server gem 'puma', '~> 4.1' # Use SCSS for stylesheets gem 'sass-rails', '>= 6' # Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker gem 'webpacker', '~> 4.0' # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks gem 'turbolinks', '~> 5' # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder gem 'jbuilder', '~> 2.7' gem 'devise' # Use Redis adapter to run Action Cable in production # gem 'redis', '~> 4.0' # Use Active Model has_secure_password # gem 'bcrypt', '~> 3.1.7' # Use Active Storage variant # gem 'image_processing', '~> 1.2' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.4.2', require: false group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] end group :development do # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3.3.0' gem 'listen', '~> 3.2' # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' end group :test do # Adds support for Capybara system testing and selenium driver gem 'capybara', '>= 2.15' gem 'selenium-webdriver' # Easy installation and use of web drivers to run system tests with browsers gem 'webdrivers' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] # to upload images gem 'carrierwave', '~> 2.0' gem "mini_magick"その後のコマンド
docker-compose run app bundle update docker-compose run app rails webpacker:installdatabase.yml を修正
config/database.ymldefault: &default adapter: postgresql encoding: unicode host: db username: postgres password: password pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> ...Troubleshooting
up 時に check_yarn_integrity関連 というエラーが出たら webpacker.yml を修正
config/webpacker.yml... development: <<: *default compile: true # Verifies that correct packages and versions are installed by inspecting package.json, yarn.lock, and node_modules check_yarn_integrity: false # true -> falseに変更 ...docker-compose build docker-compose up -d docker-compose run app rake db:createまた up 時に "webpack-dev-server" not found が出たら
docker-compose run app yarn add webpack-dev-server
- 投稿日:2020-11-23T13:57:58+09:00
Rubyで文字列を大文字(upcase)にする、又は小文字にする(downcase)方法
Ruby 2.7.0の記事です
- upcase 全ての小文字を対応する大文字に置き換えた文字列を返す
公式ドキュメントStringクラスupcase- downcase 全ての大文字を対応する小文字に置き換えた文字列を返す
公式ドキュメントStringクラスdowncaseputs "STring".upcase # => "STRING" puts "STring".downcase # => "string"
- 投稿日:2020-11-23T12:43:47+09:00
Vue(Nuxt)+Rails APIで、ネストした配列で画像とJSONのパラメーターを送る方法
なにこれ
Vue(Nuxt)で、子テーブルへ画像データとJSONを配列にしてパラメーターで送りたい!って時にかなりつまづいたので、備忘録として書き残しておきます。
この記事で得られること
1.Vue(Nuxt)で画像+JSONの形式でPOSTする方法
2.ネストさせた配列で、画像とJSON形式のパラメーターを送る方法大事なポイント
1.FormData型の変数を宣言して、その変数にappendしていく。
test.jsconst req = new FormData() req.append(`name`, this.product.name)2.ネストした配列で送りたい時はこうする。配列を明示してその中にオブジェクトを生成&appendする。
test.jsreq.append(`product_sub_attributes[][image]`, subs[i].image)3.axiosでリクエストを投げる時、
'Content-Type': 'multipart/form-data'
にすること。test.jsconst response = await axios.post('/products', req, { headers: { 'Content-Type': 'multipart/form-data' } })備考
- バックエンドはRails APIモードを仕様しています
- 画像アップ方法は、フロントはvue-cropeer、バックエンドはcarrierwaveを使用していますが、まだ理解しきれてない箇所があるのでそちらも後日記事にします。 なので、vue-cropperとcarrierwaveの説明は割愛します。今はJSON配列で送れるんだなーと知っていただければ幸いです。
- バックエンドはみんな大好き
gem 'active_model_serializers’
を使用しています。- 一部vuetify、bootstrap、axiosを使っています。
注意事項
今回はタイトルの方法を紹介するのがメインになるので、かなり端折っています。
全部解説すると膨大な量になってしまうので。
雰囲気を掴んでいただければ、と思っているのでご了承ください。
既存のコードをQiita用に抽出して載せているので、一部間違いがあるかも知れません。前提条件
テーブル構造はこんな感じです。
productsテーブル
とproducts_subs
テーブルがあって、1:Nの関係です。
両方とも画像投稿用のimageカラムを持っています。
登場する全てのカラムはstring型です。product.rbclass Product < ApplicationRecord has_many :products_subs accepts_nested_attributes_for :products_subs endproduct_sub.rbclass ProductSub < ApplicationRecord mount_uploader :image, ProductSubUploader belongs_to :product validates :name, presence: true validates :image, presence: true endやりたいこと
productsテーブルへcreateしたい時に、子テーブルのproducts_subsへ同時にデータを保存させたい。
そのためにはネストして送る必要がある。早速解説します。
画像アップのおおまかな流れ
プレビュー表示用のimgタグと画像アップ用のinputタグを用意して、
imgタグをクリックすると$refsでinputを参照してクリックしたことにします。配列なので$refs.subImageに[i]をつけて、番号を参照しています。
inputタグはクリックされるとsetSubImageメソッドを呼び出します。setSubImageで画像を読み込んだ後にvue-cropperに投げて、
らcropSubImageメソッドでsubImageCropperを参照してプレビューを表示する変数に突っ込んだり、画像格納用のオブジェクトに代入したりしてます。
余談だけど、このvue-cropperさんの動きが理解できてなくて、たまに$refsで参照できないバグが生まれます。笑test.vue<template v-for="(sub, i) in product.productsSubAttributes"> <div> <v-textarea v-model="sub.name" > </v-textarea> <img :src="sub.imageSrc ? sub.ImageSrc : '' " @click.prevent="$refs.subImage[i].click()" > <input ref="subImage" class="d-none" type="file" name="image" accept="image/*" @change="setSubImage($event)" /> <v-card> <vue-cropper ref=“subImageCropper" :src="imgSrc" /> <button @click="cropSubImage(sub), i)"> 保存 <button> </v-card> </div> </template> <script> export default { methods: { setSubImage(e) { const reader = new FileReader() reader.onload = (e) => { this.imgSrc = e.target.result } reader.readAsDataURL(e.target.files[0]) }, async cropSubImage(sub, i) { // imageSrcがプレビュー表示用のプロパティです。 sub.imageSrc = this.$refs.subImageCropper[0].getCroppedCanvas().toDataURL() this.$refs.subImageCropper[0].getCroppedCanvas().toBlob((blob) => { sub.image = blob }) } } } } </script>本題の画像とJSONオブジェクトをパラメーターで送る方法
今回はaxiosを使います。
その際に、configを設定してmultipart/form-data
という形式に変換します。
【axios】HTTPリクエストメソッド別の引数一覧表(エイリアスを使用した場合)
この記事がよくまとまってたのでリンク貼っておきます。multipart/form-dataとは、画像ファイル(Blob型)を送信できるようにするHTTPリクエストメソッドです。
application/jsonだと画像データが送れないです。パラメーターで送るための方法
細かく解説するためにコメント式にしました。
test.jsasync onClickCreate() { // FormData型の変数reqを定義して、そちらにappendしていきます。 const req = new FormData() // 今回は使用しませんが、こんな感じでバックエンドのカラム名に合わせて // オブジェクトを生成してappendすることで、パラメーターを送れます。 req.append('name', this.product.name) // 定数subにアップした画像や文字列のデータを代入して、for文で回します。 // for文を使っている理由は複数ありますが、今回は省略。状況によってはforEachでも代用可能です。 const subs = this.product.productSubAttributes for(let i = 0; i < subs.length; i++){ // 送りたいパラメーターに[]をつけることで、0から順番に配列で送ることが出来ます。 // ただ、配列にインデックスを指定する方法が分からないです。 req.append(`product_sub_attributes[][name]`, subs[i].name) // subs[i].imageがBlob型である=先程vue-cropeerで整形したデータなので、直接カラムに代入します。 if (subs[i].image instanceof Blob) { req.append(`product_sub_attributes[][image]`, subs[i].image) // 画像がない場合、番号を指定できないので、空文字を代入しないと順番が狂います。 // どういうことかと言うと、配列内のオブジェクトが2個以上あった場合、0番目から探して空いているカラムに代入されます。 // ちなみにここでかなりハマりました。 } else { req.append(`product_sub_attributes[][image]`, '') } } try { // 先程代入していった変数reqをパラメーターで送ります。 // 余談ですが、Railsの場合全てキー名をスネークケースにする必要があります。 const response = await axios.post('/products', req, { headers: { 'Content-Type': 'multipart/form-data' } }) } catch (error) { console.error(error.response) } }ちなみにバックエンドはこんな感じ
products_controller.rbdef product_params params.permit( :id, :name, { product_sub_attributes: [ :id, :name, :image, ] } ) endおわり
こんな感じでVue(Nuxt)からパラメーターで画像を配列+JSONで送ることが出来ます。
自分が実装しようとした時にこの方法にたどり着くまで苦労したので、
個人的な感想
やり方(How)を覚えるのは良いことなんですけど、
裏側のなぜこの方法なのか(How)の方を理解していきたいです。