20200919のRailsに関する記事は19件です。

フォームオブジェクトを使ったdate型カラムの保存方法

フォームオブジェクトを使って、date型のカラムを保存しようとしたところ、
フォームオブジェクトのクラスでインスタンスを生成してしまうとdate型のカラムが認識されないので、

unknown attribute 'expire_date(1i)' for ItemsTag.

のようにエラーが出てしまう。。

なので、コントローラーのcreateメソッドのなかで、インスタンス生成した際に、date型にあらかじめ変形させておいた値をmergeさせてフォームオブジェクトに渡してあげる、ということをしていきます。

[やること]
songsテーブルに追加した、date型 「 t.date :sales_date, null: false」
を保存させたい。

[方法]

[フォームのビュー]

new.html.erb
   <div class="weight-bold-text"> 
        リリース日
       <span class="indispensable">(必須)</span>
      </div>
    <div class='input-sales-wrap'>
      <%= raw sprintf(
                  f.date_select(
                    :sales_date,
                    class:'select-birth',
                    use_month_numbers: true,
                    prompt:'--',
                    start_year: (Time.now.year),
                    end_year: 1930,
                    date_separator: '%s'),
                  "<p> 年 </p>", "<p> 月 </p>") + "<p> 日 </p>" %>
    </div>

[コントローラー]

songs_controller.rb
 def create
    @song = SongsArtist.new(song_params)
    date = params.require(:songs_artist).permit(:sales_date)
    sales_date = Date.parse( date["sales_date(1i)"] + "-" + date["sales_date(2i)"] + "-" + date["sales_date(3i)"] )
    @song = SongsArtist.new(song_params.merge(sales_date: sales_date))
end

 def song_params
    params.require(:songs_artist).permit(:name, :text, :image, :translate, :youtube_url, :genre_id, :art_name,:featuring_name,:producer_name).merge(user_id: current_user.id)
  end

sales_date以外は、song_paramsメソッド内でpermitのなかに入れてフォームオブジェクトに渡すが、
sales_dateは、
date = params.require(:songs_artist).permit(:sales_date)
sales_date = Date.parse( date["sales_date(1i)"] + "-" + date["sales_date(2i)"] + "-" + date["sales_date(3i)"] )
この2行の記述でdate型に変更し、
@song = SongsArtist.new(song_params.merge(sales_date: sales_date))
フォームオブジェクトに渡す値であるsong_paramsに、date型に変更したsales_dateをmergeさせて
一緒に渡す。

song_artist.rb
class SongsArtist
  include ActiveModel::Model
  attr_accessor :name, :text, :image, :genre_id, :translate, :youtube_url, :sales_date,:art_name,:producer_name,:featuring_name, :user_id,:song_id
  validates :name, :text,:image,:genre_id, presence: true
  validates  :genre_id, numericality: { other_than: 1 }
  def save
    artist = Artist.where(art_name: art_name).first_or_initialize
    artist.save
    featuring = Featuring.where(featuring_name: featuring_name).first_or_initialize
    featuring.save
    producer = Producer.where(producer_name: producer_name).first_or_initialize
    producer.save
    song = Song.create(name: name, text: text, image: image, genre_id: genre_id, translate: translate, youtube_url: youtube_url, sales_date: sales_date,user_id: user_id,producer_id: producer.id,featuring_id: featuring.id)
    SongArtistRelation.create(song_id: song.id, artist_id: artist.id)
  end
end

フォームオブジェクトの記述は、普段と変わらず、 attr_accessorでコントローラーから渡ってきたカラムを許可し、createアクションの引数に保存するカラムを記述する。

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ruby】例外処理で、施されたら施し返す

例えばあるデータをパースして、特定のデータのみデータベースに保存したいとする。
その中で、パースできない拡張子があればエラーが出て、処理が中断してしまいバグを引き起こしてしまう。

そこで、便利なのが例外処理というもので、処理を中断させず例外事案の場合の処理を施すことができる。

まさに、施すこされたら施し返す、恩返しですを実現できるのだ。

今回は単純な計算を例に、例外処理を見ていこう。

エラーが発生した場合の処理 begin / rescue

Ruby内で、10を0で割ろうとするとエラーが出てしまう。

puts 10 / 0
puts "こんにちわ"
divided by 0 (ZeroDivisionError)

処理が途中で、中断されてしまう。

そこで、
- エラーの対象になりそうな箇所を、beginで囲う。
- エラーが発生した時の処理を、rescue内に記述する。

begin
  10 / 0
rescue
  p "0で割れません"
end

puts "こんにちわ"
"0で割れません"
"こんにちわ"

処理が中断されず、エラー時の処理と正常処理どちらも実行された。

省略法

beginなしで、rescueさせる

puts 10 / 0 rescue 0
puts 10 / nil rescue 0 
0
0

エラー内容を、変数に格納 rescue =>

begin
  10 / 0
rescue => e
  puts e
end

puts "こんにちわ"
divided by 0
こんにちわ

エラーオブジェクトが、変数eに格納されて出力できる。

エラーごとの処理を変える rescue "エラーメッセージオブジェクト"

begin
  10 / 0
rescue NoMethodError
  puts "そのようなメソッドはない"
rescue ZeroDivisionError
  puts "0で割れません"
end
0で割れません

2番目のエラーメッセージに該当するので、そのrescueに反応する。

注意点

対象の例外クラスの親が、先に記述されいた場合はそちらが先に処理される

begin
  10 / 0
rescue StandardError
  puts "基本的なエラー"
rescue ZeroDivisionError
  puts "0で割れません"
end
基本的なエラー

明示的なエラーを発生させて、処理を中断させる raise

使用用途は、
1. パラメータが想定されたものでないとき
2. 不正なアクセスがきた時

begin
  raise NoMethodError
rescue => e
  p e
end
NoMethodError

独自のエラーを発生させる

例外クラス(StandardErrorクラス)を継承させてあげる。

class Hoge < StandardError
end

begin
  raise Hoge
rescue => e
  p e
end
#<Hoge: Hoge>

エラー時でも、再度初めから実行させる retry

num = 0

begin
  puts 10 / num
rescue ZeroDivisionError => e
  puts e
  num = 1
  retry
end

puts "終了しました"
divided by 0
10
終了しました

ループ1回目では、エラーが発生していて、
ループ2回目では、正常に処理がされている。

エラーが発生しても、しなくても行う処理 ensure

begin
  puts "例外なし"
rescue => e
  puts e
ensure
  puts "Hello"
end
例外なし
Hello

ensureは、いかなる時でも必ず実行される。

番外編 エラーオブジェクトとは

begin
  10 / 0
rescue => e
  puts e.class
  puts e.class.superclass
  puts e.class.superclass.superclass
  puts e.class.superclass.superclass.superclass
end
ZeroDivisionError
StandardError
Exception
Object

Exceptionが元となるクラスで、Objectがその親

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第11章】

前提

・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者

基本方針

・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。

 
 認証システム開発・第6段回目、第11章に入ります。セキュリティ強化の観点から、アカウント有効化のステップを入れていきます。よくある、登録後にメールが送られてきて、そのリンクを踏むと本登録になるやつですね。
 
本日のBGMはこちら。
Tatuki Seksu "Hanazawa EP"
いろいろあやしいのをシューゲサウンドでパッケージングしましたってかんじが好き。

 

【11.1.1 AccountActivationsコントローラ 演習】

1. 現時点でテストスイートを実行すると greenになることを確認してみましょう。
→ 現時点ではテスト書いてないのでGREENです。

 
2. 表 11.2の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: 私達はこれからメールで名前付きルートを使います。
→ pathは相対パス(/とか省略した形)、urlは絶対パス(https:~~とか完全な形)。メールというrails外の処理にはurlの完全形である絶対パスが必要と考えます。

 

【11.1.2 AccountActivationのデータモデル メモと演習】

コールバックではbefore_〇〇の、〇〇の動作の直前に特定のメソッドを実行することができる。メソッド参照という。ブロックを渡すよりこちらが推奨。
(おさらい)privateより下にメソッドを定義することで、外部に非公開にできる。

1. 本項での変更を加えた後、テストスイートが green のままになっていることを確認してみましょう。
→ GREEN

 
2. コンソールからUserクラスのインスタンスを生成し、そのオブジェクトからcreate_activation_digestメソッドを呼び出そうとすると (Privateメソッドなので) NoMethodErrorが発生することを確認してみましょう。また、そのUserオブジェクトからダイジェストの値も確認してみましょう。
→ こんなかんじ

>> user = User.third
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? OFFSET ?  [["LIMIT", 1], ["OFFSET", 2]]
=> #<User id: 3, name: "Mr. Sage Hartmann", email: "example-2@railstutorial.org", created_at: "2020-09-17 08:34:09", updated_at: "2020-09-17 08:34:09", password_digest: "$2a$10$.HyqPb.DwmFICve62DsYte1alLAVihIdeS2F8Rjndry...", remember_digest: nil, admin: false, activation_digest: "$2a$10$9VKv/p9kYrz84SdMs/7s/uzEV3mqzGMmTubIq7.Vz4b...", activated: true, activated_at: "2020-09-17 08:34:09">
>> user.create_activation_digest
Traceback (most recent call last):
        1: from (irb):2
NoMethodError (private method `create_activation_digest' called for #<User:0x000000000426b7a0>)
Did you mean?  restore_activation_digest!
>> user.activation_digest
=> "$2a$10$9VKv/p9kYrz84SdMs/7s/uzEV3mqzGMmTubIq7.Vz4bbIb.ZeLDRy"

 
3. リスト 6.34で、メールアドレスの小文字化にはemail.downcase!という (代入せずに済む) メソッドがあることを知りました。このメソッドを使って、リスト 11.3のdowncase_emailメソッドを改良してみてください。また、うまく変更できれば、テストスイートは成功したままになっていることも確認してみてください。
→ email.downcase!に変えるだけやね。

user.rb
    def downcase_email
      email.downcase!
    end

 

【11.2.1 アカウント有効化のメール送信 演習】

1. コンソールを開き、CGIモジュールのescapeメソッド (リスト 11.15) でメールアドレスの文字列をエスケープできることを確認してみましょう。このメソッドで"Don't panic!"をエスケープすると、どんな結果になりますか?
→ 下記。(クエリパラメータとCGIは用語集に入れてます)

>> CGI.escape('foo@example.com')
=> "foo%40example.com"
>> CGI.escape("Don't panic!")            
=> "Don%27t+panic%21"

 

【11.2.2 送信メールのプレビュー メモと演習】

 AWScloud9を使用している場合、チュートリアルでは旧cloud9使用しているので'example.com'に入力する内容の見た目がかなり違うので戸惑いますが、内容は一緒です。Railsサーバーを立ち上げて、別タブで表示した画面のURLのhttps://以下をすべてコピペすればOKです。

1. Railsのプレビュー機能を使って、ブラウザから先ほどのメールを表示してみてください。「Date」の欄にはどんな内容が表示されているでしょうか?
→ 今日の日付とUTC(協定世界時)が表示される。(協定世界時って言葉カッコよくないですか?中二心をくすぐられる)

 

【11.2.3 送信メールのテスト メモと演習】

 ここのassert_matchがいまひとつしっくり来ないですが、「メールの中身に対し、名前と有効化トークンとエスケープしたメルアドが含まれているかテストしている」ぐらいに認識しておきます。

1. この時点で、テストスイートが greenになっていることを確認してみましょう。
→ Yes, GREEN !

 
2. リスト 11.20で使ったCGI.escapeの部分を削除すると、テストが redに変わることを確認してみましょう。
→ REDになるのは、メールアドレスにメタ文字が含まれていて、エスケープしないと正規表現ではないからですかね。こちらの記事参照。

 

【11.2.4 ユーザーのcreateアクションを更新 メモと演習】

deliver_now:名前のとおり、その瞬間にメール送信処理を実行。

1. 新しいユーザーを登録したとき、リダイレクト先が適切なURLに変わったことを確認してみましょう。その後、Railsサーバーのログから送信メールの内容を確認してみてください。有効化トークンの値はどうなっていますか?
→ homeに戻りました。Welcome to the Sample App! Click on the link below to activate your account: 以下のURLに含まれているランダムな文字列が有効化トークンです。

 
2. コンソールを開き、データベース上にユーザーが作成されたことを確認してみましょう。また、このユーザーはデータベース上にはいますが、有効化のステータスがfalseのままになっていることを確認してください。
→ activated: false になっていました。

 

【11.3.1 authenticated?メソッドの抽象化 メモと演習】

 もう何のダイジェストの話してんのか混乱してきましたね。ってなったら11章の始めにあった表11.1を見返しましょう。何の話をしているのか、しっかり把握しながら進めないと。
 そんで、この節のタイトルは抽象化より一般化ってかんじがする。authenticated?メソッドを表11.1のどのパターンでも使用できるようにするわけやし。

1. コンソール内で新しいユーザーを作成してみてください。新しいユーザーの記憶トークンと有効化トークンはどのような値になっているでしょうか? また、各トークンに対応するダイジェストの値はどうなっているでしょうか?
→ こんな感じっすね。ユーザー作っただけだから記憶系はnilのまま…

>> user = User.create(name: "muteki", email: "muteki@man.com", password: "mutekimuteki", password_confirmation: "mutekimuteki")
   (0.1ms)  SAVEPOINT active_record_1
  User Exists (0.2ms)  SELECT  1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER(?) LIMIT ?  [["email", "muteki@man.com"], ["LIMIT", 1]]
  SQL (2.5ms)  INSERT INTO "users" ("name", "email", "created_at", "updated_at", "password_digest", "activation_digest") VALUES (?, ?, ?, ?, ?, ?)  [["name", "muteki"], ["email", "muteki@man.com"], ["created_at", "2020-09-17 12:57:50.404544"], ["updated_at", "2020-09-17 12:57:50.404544"], ["password_digest", "$2a$10$eDPAP444JbjJDGucKnoFE.MWFBTAR8dxQ.wXPJfzql9E0TPRVDQfq"], ["activation_digest", "$2a$10$zql927sHRszT.bjitRxBn.slJil.Zvc74AJkztqBZzt7kUiSqBgx."]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User id: 102, name: "muteki", email: "muteki@man.com", created_at: "2020-09-17 12:57:50", updated_at: "2020-09-17 12:57:50", password_digest: "$2a$10$eDPAP444JbjJDGucKnoFE.MWFBTAR8dxQ.wXPJfzql9...", remember_digest: nil, admin: false, activation_digest: "$2a$10$zql927sHRszT.bjitRxBn.slJil.Zvc74AJkztqBZzt...", activated: false, activated_at: nil>

なので、記憶系をわざわざ作ります。

>> remember_token = User.new_token
=> "5dyf7BoW9H3H9SYH6VPRYg"
>> remember_digest = User.digest(remember_token)
=> "$2a$10$1CymqXEPzP.b05TblQ3Zye/ukhNblEpGlDxI4kT2VoiLUJK1EHVy2"
>> user.update_attribute(:remember_token, remember_token)
   (0.1ms)  SAVEPOINT active_record_1
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
>> user.update_attribute(:remember_digest, remember_digest)
   (0.1ms)  SAVEPOINT active_record_1
  SQL (0.2ms)  UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ?  [["updated_at", "2020-09-17 13:45:51.944577"], ["remember_digest", "$2a$10$1CymqXEPzP.b05TblQ3Zye/ukhNblEpGlDxI4kT2VoiLUJK1EHVy2"], ["id", 102]]
   (0.0ms)  RELEASE SAVEPOINT active_record_1
=> true

ということで、各トークンはこんな感じ。ダイジェストは上で出てます。

>> user.remember_token
=> "5dyf7BoW9H3H9SYH6VPRYg"
>> user.activation_token
=> "p5rorOE7trfF4L-YJynnxg"

 
2. リスト 11.26で抽象化したauthenticated?メソッドを使って、先ほどの各トークン/ダイジェストの組み合わせで認証が成功することを確認してみましょう。
→ 確認するだけ…ってあれー??activation_tokenでNameError??
って調べたら解決。あくまでユーザーに紐づいてるものだからuser.activation_tokenか。

>> user.authenticated?(:remember, remember_token)
=> true
>> user.authenticated?(:activation, activation_token)
Traceback (most recent call last):
        1: from (irb):11
NameError (undefined local variable or method `activation_token' for main:Object)

よって以下でtrue。remember_token作るときに、新しく定義するのではなく、user.remember_tokenで紐付けしたほうがよかったな。

>> user.authenticated?(:activation, user.activation_token)
=> true

 

【11.3.2 editアクションで有効化 演習】

1. コンソールから、11.2.4で生成したメールに含まれているURLを調べてみてください。URL内のどこに有効化トークンが含まれているでしょうか?
→ 11.2.4の演習1と同じです。

 
2. 先ほど見つけたURLをブラウザに貼り付けて、そのユーザーの認証に成功し、有効化できることを確認してみましょう。また、有効化ステータスがtrueになっていることをコンソールから確認してみてください。
→ 認証成功、有効化できていました。

 

【11.3.3 有効化のテストとリファクタリング メモと演習】

・テストに出てくる配列deliveriesは変数。なので、他のテストに支障が出ないよう、setupでclearしている。
・sizeメソッド:lengthメソッドと同じ。ここではメールの数(=1)を確認している。
・assignsメソッド:対応するアクション内(ここではsignupをテストしているのでcreateアクション)のインスタンス変数(@user)にアクセスできるようになる。
・whereメソッド:与えられた条件にマッチするレコードをすべて返す。こちらにfind,find_byとの比較がありました。

1. リスト 11.35にあるactivateメソッドはupdate_attributeを2回呼び出していますが、これは各行で1回ずつデータベースへ問い合わせしていることになります。リスト 11.39に記したテンプレートを使って、update_attributeの呼び出しを1回のupdate_columns呼び出しにまとめてみましょう (これでデータベースへの問い合わせが1回で済むようになります)。また、変更後にテストを実行し、 greenになることも確認してください。
→ 下記

user.rb
  def activate
    update_columns(activated: true, activated_at: Time.zone.now)
  end

 
2. 現在は、/usersのユーザーindexページを開くとすべてのユーザーが表示され、/users/:idのようにIDを指定すると個別のユーザーを表示できます。しかし考えてみれば、有効でないユーザーは表示する意味がありません。そこで、リスト 11.40のテンプレートを使って、この動作を変更してみましょう9 。なお、ここで使っているActive Recordのwhereメソッドについては、13.3.3でもう少し詳しく説明します。
→ 下記

users_controller.rb
  def index
    @users = User.where(activated: true).paginate(page: params[:page])
  end

  def show
    @user = User.find(params[:id])
    redirect_to root_url and return unless @user.activated?
  end

 
3. ここまでの演習課題で変更したコードをテストするために、/users と /users/:id の両方に対する統合テストを作成してみましょう。
訳注: updateメソッドは、コールバックとバリデーションを実行せずにスキップしますので、コールバックやバリデーションをかける必要がある場合は注意が必要です。
→ ここ難しいですね。発想としては、「有効化してないユーザー(@non_activated)がindexに表示されていない」のと、有効化してないユーザーのページ(user_path(@non_activated))にアクセスしようとすると、homeに飛ばされる」のを確かめればいいわけだけど…。前者のアサーションが分からず悩みました。
 結局調べたところ、「user_path(@non_activated)のリンクが表示されていない(0である)」のを確かめればいいと。なるほど。ということで下記です。テスト名もてきとうに追記しました。あと、setupに@non_activatedとして、uses.ymlの3番目のユーザーをactivated: falseに書き換えてから追加しています。

users_index_test.rb
  def setup
    @admin     = users(:michael)
    @non_admin = users(:archer)
    @non_activated = users(:lana)
  end

  test "index as admin including pagination and delete links, 
  not to show non activated user" do
    log_in_as(@admin)
    get users_path
    assert_template 'users/index'
    assert_select 'div.pagination'
    first_page_of_users = User.where(activated: true).paginate(page: 1)
    first_page_of_users.each do |user|
      assert_select 'a[href=?]', user_path(user), text: user.name
      assert_select 'a[href=?]', user_path(@non_activated), count: 0
      unless user == @admin
        assert_select 'a[href=?]', user_path(user), text: 'delete'
      end
    end
    assert_difference 'User.count', -1 do
      delete user_path(@non_admin)
    end
    get user_path(@non_activated)
    assert_redirected_to root_url
  end

 

【11.4 本番環境でのメール送信 グチと演習】

 ついにきた!!SendGrid!!
これ酷くないですか?登録してもすぐにアカウント凍結されてメール飛ばせなくなるんですよ。前回こいつに時間取られた挙句、結局解決するにはサポートに連絡しないといけないらしく、やってられるかって放置しました。そんなもんチュートリアルに使うなよな…。
 ということで、別の手段を導入しようしていろいろ試したのですがどれもうまくいかず…。不服ですが今回はsendgridで済まします。これは今後の課題として置いておきます。
……結局そっこーで凍結されました。もうええわ。

1. 実際に本番環境でユーザー登録をしてみましょう。ユーザー登録時に入力したメールアドレスにメールは届きましたか?
→ うん、無理でした。

 
2. メールを受信できたら、実際にメールをクリックしてアカウントを有効化してみましょう。また、Heroku上のログを調べてみて、有効化に関するログがどうなっているのか調べてみてください。ヒント: ターミナルからheroku logsコマンドを実行してみましょう。
→ やりたいんやけどね、無理ですわ。

 

第11章まとめ

・終わり悪ければすべて悪い。SendGrid嫌い。
・今後は純粋にActionMailerでメール送信に取り組みたい。
・URLに有効化トークンとエスケープしたメールアドレスを盛り込む。

 
 SendGrid問題、なんとか解決したかったんやけどなあ…。これ以上時間かけるのももったいないので、次に進めます。今後絶対なんとかしてやる!
 さて、次は第12章、パスワードの再設定です。認証システム開発の最終章ですね!

 
⇨ 第12章へ!
⇦ 第10章はこちら
学習にあたっての前提・著者ステータスはこちら

 

なんとなくイメージを掴む用語集

・クエリパラメータ
 URLの末尾で疑問符「?」に続けてキーと値のペアを記述したもの。ユーザーがどこから来たのか解析する手段(パッシブパラメータ)や、指定された変数によってコンテンツの内容を変化させたりできる(アクティブパラメータ)。クエリのこと全般も含めて詳しくはこちら。

・CGI(Common Gateway Interface)
 クライアント側のWebブラウザの要求に応じてWebサーバが外部プログラムを呼び出して、その実行結果がHTTPを介してクライアントのWebブラウザに送信される仕組みのこと。掲示板、アクセスカウンター、アンケートフォームなどを実装できる。

・assert_mutch
 与えられた文字列が与えられた正規表現にマッチした場合、検査にパス。

・SMTP(Simple Mail Transfer Protocol)
 インターネットで電子メールを転送するプロトコル。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails5】Railsで新規アプリを作る【初心者】

概要

『どうも皆さん、おはこんばんにちは』

(…一度言ってみたかった)

筆者は最近RoRアプリを作り始めた、思いっっきり初心者です。
Qiita初投稿という事で、めちゃくちゃ初歩的な「Railsで新規アプリを作る過程」について投稿したいと思います!:writing_hand:

前提条件

  • OS: MacOS
  • Rubyバージョン: 2.5.1
  • Railsバージョン: 5.2.3
  • データベース: MySQL
  • Rubyバージョン管理: rbenv
  • IDE: VisualStudioCode

上記の環境でアプリを作っていきます!:fist:

Railsで新規アプリを作る

ターミナル上で
rbenv local 2.5.1
rails _5.2.3_ new app_name --database=mysql --skip-bundle
を実行します。

何を行っているのか

  • rbenv rbenvコマンド実行の宣言
  • local 2.5.1 このプロジェクトで使用するRubyのバージョン指定(今回はver.5.2.3)
  • rails railsコマンド実行の宣言
  • _5.2.3_ rubyのバージョン指定(今回はver.5.2.3)
  • new 新しいアプリの作成コマンド
  • app_name 作りたいアプリの名前の定義
  • --database=mysql 使いたいデータベースの指定(今回はmysql)
  • --skip-bundle bundle installをスキップする指示
  • app_name .とすると、カレントディレクトリ上に作られる
  • --database=mysql は、-d mysqlでもOK:ok_hand:
  • --skip-bundle は、-BでもOK:ok_hand:

rbenvの使い方と仕組みについては、以下の記事がわかりやすいので参照してください。
rbenvの使い方と仕組みについて - Qiita

アプリ内のファイル

上記のようにターミナル上でrails newをすると、たくさんのフォルダやファイルが作られます。中身は以下のようになっているはずです。

app

アプリケーションのコントローラ、モデル、ビュー、ヘルパー、メイラー、チャンネル、ジョブズ、アセットが置かれています。

bin

アプリケーションを起動・アップデート・デプロイするためのRailsスクリプト等のスクリプトファイルが置かれています。

config

アプリケーションの設定ファイル (ルーティング、データベース等) がここに置かれています。

db

現時点のデータベーススキーマと、データベースマイグレーションファイルが置かれています。

lib

アプリケーションで使う拡張モジュールが置かれています。

log

アプリケーションのログファイルが置かれています。

public

このフォルダの下にあるファイルは外部 (インターネット) からそのまま参照できます。静的なファイルやコンパイル済みアセットをここに置きます。

storage

Diskサービスで用いるActive Storageファイルが置かれています。

test

Unitテスト、フィクスチャなどのテスト関連ファイルをここに置きます。

tmp

キャッシュ、pidなどの一時ファイルが置かれます。

vendor

サードパーティによって書かれたコードはすべてここに置きます。通常のRailsアプリケーションの場合、外部からのgemファイルをここに置きます。

.gitignore

Gitに登録しないファイル(またはパターン)をこのファイルで指定します。

.ruby-version

デフォルトのRubyバージョンがこのファイルで指定されています。

config.ru

アプリケーションの起動に必要となる、Rackベースのサーバー用のRack設定ファイルです。

Gemfile

Railsアプリケーションで必要となるgemの依存関係を記述します。このファイルはBundler gemで使われます。

package.json

Railsアプリケーションで必要なnpm依存関係をこのファイルで指定できます。このファイルはYarnで使われます。

Rakefile

このファイルには、コマンドラインから実行できるタスクを記述します。ここでのタスク定義は、Rails全体のコンポーネントに対して定義されます。

独自のRakeタスクを定義したい場合は、Rakefileに直接書くと権限が強すぎるので、なるべくlib/tasksフォルダの下にRake用のファイルを追加するようにしましょう。

README.md

アプリケーションの概要を説明するマニュアルをここに記入します。このファイルにはアプリケーションの設定方法などを記入し、これさえ読めば誰でもアプリケーションを構築できるようにしておく必要があります。

gemのインストール

Rubyにおけるgemは、下記2つの役割を持ちます。

  1. パッケージ
  2. パッケージ管理ツール

パッケージを使うことで開発を効率的に進めることができるので、実際の現場でも使用されることが多いようです。
簡単にパッケージをインストール可能なので、Ruby on Rails 初心者でも素早く本格的なアプリ機能を装備することができるんですね。:relaxed:

アプリ内で使いたい機能を持つgemを Gemfileに、

Gemfile
gem 'gem名'

のように記述します。(ここでは、具体的なgemの種類については割愛します。)

また、Gemfile内のrailsの記述を、

Gemfile
~
gem 'rails', '5.2.3'
~

のように使いたいバージョン(今回はver.5.2.3)で固定します。

先ほど--skip-bundleコマンドでgemのインストールをスキップしたので、ターミナル上で
bundle install
を実行します。

何を行っているのか

  • bundle install bundler というgemを使って、Gemfileの記載内容に従ってgemをインストールするためのコマンド

サーバーの立ち上げ

実際にサーバーを立ち上げてみましょう!ターミナル上で
rails server
を実行します。

何を行っているのか

  • rails server rails newコマンド で作ったRailsアプリケーションを、local環境でサーバーに繋ぐ指示
  • rails server は、rails s でもOK:ok_hand:

ターミナル上で以下のようなログが流れればサーバーとの接続が完了です!

=> Booting Puma
=> Rails 5.2.3 application starting in development 
=> Run `rails server -h` for more startup options
Puma starting in single mode...
* Version 3.12.6 (ruby 2.5.1-p57), codename: Llamas in Pajamas
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

ブラウザ上で確認してみましょう!
localhost:3000 にアクセスします。

Yay!

無事にサーバーへ接続できました!:sunglasses:

終わりに

最後までご覧いただきありがとうございました。
今後はdeviseでのユーザー登録方法、Docker上でのRoR環境構築なども記事にしたいなあと考えています(いつになるかは未定)。

また、この記事に関して不明な点等ありましたら、お知らせくださると幸いです。
早くエンジニアとして「駆け出したい」ですね。:walking_tone1::walking_tone1::walking_tone1:
それでは!

参考にしたWebサイト

Railsガイド
CodeCampus

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】deviseの会員登録に確認画面と完了画面を加える。

環境

Ruby 2.5.7
Rails 5.2.4

gem

gem 'devise'

前提

deviseの新規登録フォームがカスタムできる状態(デフォルトではない状態)
※事前に、新規登録フォームがカスタムでき問題なく動作することをご確認ください。

やりたいこと

deviseのデフォルト設定ではsign_upページで情報が入力され送信されると、そのまま登録されると共にあらかじめ指定した(されている)ページに遷移します。
登録の前に入力された情報が確認できるページを挟んだり、登録が完了したことをお知らせするページがあると、より親切かと思いますので、今回は「入力フォーム」に加えて、「入力された情報の確認画面」、「登録完了画面」を作成していきます。
今回の作成で完成するページの流れとしては、
新規会員情報の入力フォームusers/new.html.erb(新規作成)→入力情報の確認画面users/registrations/new.html.erb→登録完了画面users/completion(新規作成)という順番で画面が遷移していきます。

少し違和感があるかもしれませんが、deviseで用意されているusers/registrations/new.html.erbは前のページで入力された情報の確認のみで、見た目上はその前のページで入力された値を表示するだけになります。

手順

1.views/usersに入力画面(new.html.erb)、完了画面(completion.html.erb)を用意
2.controllers(controllers/users/registrations_controller.rb)に、会員登録後のリダイレクト先(users/completion.html.erb)を追記
3.routes.rbに、1.で追加したviewファイルのルーティング、registrations_controller.rbの使用を追記
4.(controllers/users_controller.rb)にrender用newとcompletionを作成

1.確認画面、完了画面の作成

冒頭で記した通り、新規登録時の画面の流れとしては、
新規登録入力フォームusers/new.html.erb→入力確認画面users/registrations/new.html.erb→登録完了画面users/completionとなります。
確認画面で"登録する"ボタンを押すとdeviseの標準機能でユーザー情報が登録されます。

入力画面

users/new.html.erb
<div>

  <h2>新規会員登録</h2>

  <%= form_with url: new_user_registration_path, method: :get, local: true do |f| %>

    <%= f.label :name %>
    <%= f.text_field :name %>

    <%= f.label :phone_number %>
    <%= f.number_field :phone_number %>

    <%= f.label :email %>
    <%= f.email_field :email %>

    <%= f.label :password %>
    <% if @minimum_password_length %>
      (<%= @minimum_password_length %>文字以上)
    <% end %>

    <%= f.password_field :password %>
    <%= f.password_field :password_confirmation %>

    <%= render "users/shared/links" %>

    <%= f.submit "確認" %>
  <% end %>
</div>

元々用意されているregistrations/new.html.erbのフォームを参考に新規作成します。
登録情報に名前や電話番号情報を追加する方法は割愛します。
追加される場合は先にそちらの設定をして動作が確認できた上で、今回の方法を試していただくか、カラムは増やさずにデフォルトの設定(メールアドレス、パスワードのみ)でお試しください。

ポイントとしてはフォームタグです。

users/new.html.erb
  ...

  <%= form_with url: new_user_registration_path, method: :get, local: true do |f| %>

    ...

  <% end %>

ここでは保存するモデルは指定せず、urlだけを指定しています。
つまり、フォーム内でsubmitされると、ここで指定したurlに遷移され、フォームに入力されている値も"パラメータ"として遷移先に渡されます。
ここで指定するurlは次の確認画面のurl: new_user_registration_pathを指定します。
このurlはdeviseがデフォルトで用意しているregistrations/new.html.erbです。

このフォームはurlを遷移するだけなので、HTTPメソッドは"POST"ではなく"GET"を指定します。
フォームのHTTPメソッドはデフォルトが"POST"になっているので、method: :getを明示する必要があります。

今回使うフォームタグはform_withです。
form_withはデフォルトの送信方法がAjax(remote: true)で、画面遷移が行われない仕様なので、local: trueも明示する必要があります。
form_forの場合はデフォルトがlocal: trueなので、普段は意識する必要はありません。

確認画面

users/registration/new.html.erb
<div>

  <h2>入力情報の確認</h2>

  <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>

    <p><%= params[:name] %></p>

    <p><%= params[:phone_number] %></p>

    <p><%= params[:email] %></p>

    <% unless params[:password].nil? %>
      <%
        i = 0
        password = ""
        while i < params[:password].length
          password << "*"
          i += 1
        end
      %>
    <% end %>
    <p><%= password %></p>

    <%# 実際に送信するパラメータ %>
    <%= f.hidden_field :name_family, value: params[:name] %>
    <%= f.hidden_field :phone_number, value: params[:phone_number] %>
    <%= f.hidden_field :email, value: params[:email] %>
    <%= f.hidden_field :password, value: params[:password] %>

    <%= f.submit "登録する" %>

  <% end %>
</div>

この画面がdeviseで用意されているusers/registrations/new.html.erbです。
元々のこのページには入力フォームがありますが、今回は確認だけなので、フォームは表示しません。
form_forから始まるフォームタグは元からあるものをそのまま利用しています。

前のページから渡されてるパラメータをparams[:name]などを用いて取得し、確認できるように表示します。

パスワード部分については入力されたものが何なのかわからない状態にしています。
(パラメータには載っているのですが、ここの隠し方がわからなかったので、詳しい方ご教授いただけると幸いです。)

users/registrations/new.html.erb
<%
  i = 0
  password = ""
  while i < params[:password].length
    password << "*"
    i += 1
  end
%>

params[:password].lengthでパスワードの文字数を取得しています。
while i < params[:password].length ... endで処理の中でiが1ずつカウントアップしていきます。
同時に、直前で定義した変数password = """*"が足されていき、文字数に達したら処理が終了します。
<%= password %>変数の中身を表示します。

users/registration/new.html.erb
<%= f.hidden_field :name_family, value: params[:name] %>
...

f.hidden_fieldは表示はされませんが、そこに指定されているvalue: params[:name]などはフォームとして送信されます。
今回のフォームは保存するモデルも(resourceでdeviseが自動的に)指定されているので、このf.hidden_fieldで指定された値はそのまま保存される値になります。

完了画面

users/completion.html.erb
<div>
  <% if request.referrer.include?('users/sign_up') %>
    <p>登録が完了しました!</p>
  <% else %>
    <p>既に登録済みです。</p>
  <% end %>
</div>

前のページで登録するが押されると、確認した会員情報が保存されると同時に、この画面に遷移します。

request.referrerを使うと、どのページからここに遷移してきたか、前のページのURLが取得できます。
request.referrer.include?('users/sign_up')のinclude?メソッドは()の中の値がそこに含まれているかどうかを返すメソッドです。
ここでusers/sign_upが前のページのURLに含まれていたかどうかを判断して、そのページで表示するテキストを変えています。
users/registrations/new.html.erb以外からの遷移であれば、登録済みと表示され、そもそも未登録(未ログイン)の場合に表示しようとするとdeviseのbefore_action :authenticate_user!で、他のページにリダイレクトが行われます。

2.users_controller.rbにnewとcompletionを追加

前の手順ではviewファイルを作成しましたが、このままでは画面表示ができないので、コントローラーでアクションを記述する必要があります。

users_controller.rb
class UsersController < ApplicationController

  before_action :authenticate_user!, except: %i[new]

  ...

  def new
  end

  def completion
  end

  ...

end

newページは新規会員登録フォームなので、下記のように記述する必要があります。
before_action :authenticate_user!, except: %i[new]
before_action :authenticate_user!, except: [:new]
before_action :authenticate_user!, except: :new
どれも意味は一緒ですが、記述するアクションが増えた時に、よりコードが短くなるのは1行目のものです。

ちなみに%iで複数指定するときはbefore_action :authenticate_user!, except: %i[new action1 action2 action3]という感じでスペースで区切るだけです。

completionは未ログインの場合はリダイレクトさせたいのでexceptに含めません。

3.新たに作成したviewとusers/registrations_controller.rbをroutes.rbに記述する

routes.rb
Rails.application.routes.draw do

  ...

  # registrations_controller.rbを有効にします。
  devise_for :users, controllers: {
    ...
    registrations: 'public/users/registrations'
  }

  # 作成したusers/new.html.erbとusers/completion.html.erbをルーティングに追加します。
  resources :users, only: %i[new] do
    get 'completion', to: 'users#completion'
  end

end

4.登録後のリダイレクト先を変更

確認画面で登録するが押されたときに、前の手順で用意したusers/completion.html.erbに遷移させる必要があります。

users/registrations_controller.rb
class Public::Users::RegistrationsController < Devise::RegistrationsController

  ...

  def after_sign_up_path_for(resource)
    public_user_completion_path(resource)
  end

  ...

end

デフォルトか、もしくは任意で別のページがリダイレクト先になっているかと思いますが、そのpathを前の手順で作成したpublic_user_completion_path(resource)(users/completion.html.erbのpath)に設定します。

まとめ

より実用的な使い方については、私のGitHubに実際に使っているファイルを公開しているのでそちらも参考にしていただければと思います!
GitHub - MasaoSasaki/matchi

質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

最後まで読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Active Storage】ファイルアップロード時のバリデーション設定

概要

Active Storageにはデフォルトのバリデーションがないため、自前でバリデーション設定をした時のことを備忘録として記録します。

環境

・ruby '2.5.7'
・rails '5.2.3'

過程

1.準備

Active Storageを導入するために以下のコマンドを実行します。

$ rails active_storage:install

以下の2つのテーブルを作成するマイグレーションファイルが作成されます。
・active_storage_attachments
・active_storage_blobs

rails db:migrateしてテーブルを作成します。

$ rails db:migrate

2.モデルへの関連付け

モデルへの関連付けをしていきます。
ここでは、Userモデルに複数のプロフィール画像を設定する場合とします。

models/user.rb
class User < ApplicationRecord
  has_many_attached :avatars
end

:avatarsはファイルの呼び名で、:photos:imagesなどファイルの用途に合わせて好きなものが指定出来ます。Userモデルに画像用のカラムを用意する必要はありません。

3.バリデーションの設定

Userモデルにバリデーションの設定をしていきます。
ここでは、次の3つの設定をしていきます。

・avatar_type
アップロード出来るファイル形式を指定します。

・avatar_size
アップロード出来る1ファイルの大きさ(容量)を指定します。

・avatar_length
アップロード出来るファイルの数を指定します。

models/user.rb
class User < ApplicationRecord
(省略)
  validate :avatar_type, :avatar_size, :avatar_length

  private

  def avatar_type
    avatars.each do |avatar|
      if !avatar.blob.content_type.in?(%('image/jpeg image/png'))
        avatar.purge
        errors.add(:avatars, 'はjpegまたはpng形式でアップロードしてください')
      end
    end
  end

  def avatar_size
    avatars.each do |avatar|
      if avatar.blob.byte_size > 5.megabytes
        avatar.purge
        errors.add(:avatars, "は1つのファイル5MB以内にしてください")
      end
    end
  end

  def avatar_length
    if avatars.length > 4
      avatars.purge
      errors.add(:avatars, "は4枚以内にしてください")
    end
  end
end

ちなみに、avatars.purgeで一時データを削除しています。
他の入力項目でバリデーションエラーが起きてしまうと、Rails5.2系〜6.0系以前ではモデルのインスタンスのattributeに代入した時点で、アップロードしたファイルがストレージに保存されてしまい、その結果として、blobにエラー用の一時データが溜まってしまう可能性があるためです。
(ただ、Rails6.0へのアップデートでActive Storageの機能変更があり、saveメソッドが実行された後に、ストレージに保存される仕様に変更されたので、6.0以降であれば不要です)

結果

これで、次のようにバリデーションの設定が出来ました!
200919_バリデーションエラー.png

参考

ActiveStorageがCarrierWaveの代用として使えるか考える
Rails5 Active Storageを使って画像アップロード機能を実装する

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】Action Cableについて

Action Cableが誕生した背景

ユーザーの入力なしに、最新の情報をWebに表示する「Realtime性」をもつリッチな体験を実現するために誕生しました。

Action Cableは、RailsのRESTとWebSocketのシームレスな統合です。
例えばAction Cableを使うことで、みなさんが使っているようなリアルタイムチャットアプリを
作成することもできます。

スクリーンショット 2020-09-19 14.53.39.png

用語について

Websocket

・Web上でクライアント、サーバ間の双方向通信を実現するための通信規格
・送信できるデータはテキストまたはバイナリ
・HTTPとは別のプロトコルなのでCookieは送られない
・通信が確立した後はクライアント、サーバーどちらからでもメッセージを送ることが可能

Connection

・Websocketとしてのコネクションを表すクラス
・Websocketとして通信が確立した時点でインスタンスが生成される
・開始時のリクエストはふつうのHTTPなのでCookieも送られる
・認証処理はこのクラス内に記述

Consumer

・Websocketコネクションのクライアント(ブラウザなど)
・同じブラウザの異なるタブは完全に別のクライアントとして扱う

サーバー側(Rails)の構成要素

Conection

コネクションは、ApplicationCable::Connectionのインスタンスです。
このクラスでは、受信したコネクションを承認し、ユーザーを特定できた場合にコネクションを確立します。
そのための仕組みとして、identified_byを使います。
identified_byはコネクションIDであり、後で特定のコネクションを見つけるときに利用できます。

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
    end

    private
      def find_verified_user
        if verified_user = User.find_by(id: cookies.encrypted[:user_id])
          verified_user
        else
          reject_unauthorized_connection
        end
      end
  end
end

Channel

チャネルはMVCでいうコントローラーの役割を担います。

Subscription

コンシューマーは、チャネルを購読するサブスクライバ側(Subscriber)の役割を果たします。
そして、コンシューマーのコネクションはサブスクリプション(Subscription: 購読)と呼ばれます。

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  # コンシューマーがこのチャネルのサブスクライバ側になると
  # このコードが呼び出される
  def subscribed
  end
end

例1. ユーザーアピアランスの表示

これはユーザーがオンラインかどうかという情報を追跡するチャネルの例が以下です(オンラインユーザーの横に緑の点を表示する機能を作成する場合などに便利)。

まずは、サーバー側のアピアランスチャンネルを作成します。

# app/channels/appearance_channel.rb
class AppearanceChannel < ApplicationCable::Channel
  def subscribed
    current_user.appear
  end

  def unsubscribed
    current_user.disappear
  end

  def appear(data)
    current_user.appear(on: data['appearing_on'])
  end

  def away
    current_user.away
  end
end

サブスクリプションが開始されると、subscribedコールバックがトリガーされ、
そのユーザーがオンラインであることを示します。

例2. 新しいWeb通知を受信する

Websocketコネクションを使って、サーバーからクライアント側の機能をリモート実行させます。

このWeb通知チャネルは、正しいストリームにブロードキャストを行ったときに、クライアント側でWeb通知を表示します。

サーバー側のWeb通知チャネルは以下です。

# app/channels/web_notifications_channel.rb
class WebNotificationsChannel < ApplicationCable::Channel
  def subscribed
    stream_for current_user
  end
end

アプリケーションのどこからでも、web通知チャネルのインスタンスにコンテンツをブロードキャストできます。

# このコードはアプリケーションのどこか(NewCommentJob あたり)で呼び出される
WebNotificationsChannel.broadcast_to(
  current_user,
  title: 'New things!',
  body: 'All the news fit to print'
)

参考

Railsガイド
描いて理解する Action Cable

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テスト

テストです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

改めてRSpecの目的、方針をまとめてみた

RSpecをしっかり学び直そうと思い、再度RSpecでテストを書く目的や、方針をまとめてみました。
@jnchito さんの記事やYouTubeをベースでの発信をベースに、自分の好みや状況を織り込んだ感じです。

なぜRSpecか

minitestより広く普及していて、情報量が多いから

前提

良いRSpecとは何かを突き詰めて議論すると、「正しい/間違い」ではなく、「好き/嫌い」の世界になる。自分やチームにとっての落としどころを決めることが大切。

何のためにテストを書くか

以下の負担を軽減して自分が楽をするため

  • リリース前の動作確認
  • 機能追加時の動作確認
  • gemやライブラリをアップデートする時の動作確認

基本方針

費用対効果が高いテストを書く。
読みやすいテストを書く。

やらないこと

  • 通常の運用では起こりえないことはテストしない
  • 「RSpecらしいコード」は追い求めない
    • subject使わない。
    • ワンライナー構文(it { is_expected...})は使わない
    • 厳格な教義に従わない(1つのitの中に必ず1つのアサーション、など)
    • describe、contextでのグループ分けも基本しない
  • 費用対効果の低いテストコードは書かない(自作のロジック以外の部分のテスト)
    • 単純な関連づけ(has_many)
    • 単純なバリデーション(presence: true)
    • 自動生成されたコード
  • controller specrequest spec
    • system specで検証できる
    • RailsをAPIサーバーとして使う場合のみrequest specを書く
  • 過度なDRYは求めない
    • テストコードはDRYより読みやすさを優先
  • プライベートメソッドはテストしない
    • プライベートメソッドもちゃんと実行されるようなテストをパブリックメソッドに対して行う
    • そうすることでプライベートメソッドをいくらリファクタリングしてもテストは壊れなくなる
  • テスト駆動開発
    • RSpecに十分に慣れてから検討する
  • テストコードの中で、ループ等プログラミングっぽいことはなるべくしない。

やること

  • 使うのはrspecfactorybotcapybara
  • system specmodel specを書く
    • なるべくcontrollerが薄くなるように処理を書く
    • controller内の分岐の数の数だけsystem specを書く
    • リファクタリングして、modelのメソッドをちょうど良い粒度にする
    • modelのメソッドの単体テストで、想定されるいろんなパターンのテストを書く
  • 慣れるまでは、処理を実装するたびにテストを書く
    • 慣れてきたら「この処理はテスト不要」みたいな勘所がわかりそう
  • 十分にメリットがある時だけbeforeletを使う
    • 乱用はしない
    • 同じコードをベタ書きした方が良い場合はそうする
  • 期待値はベタ書きする
    • eq price/2じゃなくてeq 499
    • 修正の手間は増えるけど、読みやすくなる
  • system specでクリックするボタン等はテスト用のdata属性をつけて対応する
  • セットするデータは「テストデータ」などとは書かず、実運用で想定される値を書く
  • 「◯◯の場合」のような説明はコメントで書く
    • 本来contextに書くものだけど、めんどい
    • ひたすらitを繰り返し、ネストを減らす
  • ログイン用のヘルパーメソッドを作る
定義
module LoginMacros
  def login(user)
    visit sign_in_path
    fill_in 'email', with: user.email
    fill_in 'password', with: '12345678'
    click_button 'ログイン'
    expect(page).to have_content 'ログインしました。'
  end

  def logout
    click_link 'ログアウト'
    expect(page).to have_content 'ログアウトしました。'
  end

  def act_as(user)
    login user
    yield
    logout
  end
end
呼び出し
describe '掲示板' do
  let(:alice) { create :user }
  let(:bob) { create :user }

  it 'トピックの投稿とコメントの返信' do
    # aliceとして操作
    act_as alice do
      visit root_path
      ...
    end
    # bobとして操作
    act_as bob do
      visit root_path
      ...
    end
  end
end

その他

  • テスト内でidは決め打ちしない
  • 境界値をテストする
    • 20歳以上が大人」という仕様なら、19歳のときと20歳のときを検証する
  • nezumiというchrome拡張を使うと、capybaraの記法を確認に便利
  • Better Specsという「RSpecはかくあるべき」がまとまっているサイトがある
    • 厳し過ぎたりするから従う必要は無いけど、どこかで目を通してみても良いかも
  • exampleitspecifyは同じメソッド
  • let(:user)を使った方が、before内で@user = User.createとするよりtypoに気付きやすくて良い
  • エラーメッセージのテストは、文字列を検証する以外にも、個数やキーを検証する方法もある
  • system specとmodel specの特徴、使い分け
    • system spec: controllerもmodelもviewも全部テストできる
    • model spec: 動作が速い、必要なコード量が少ない
    • よくあるパターンをsystem specで書き、それで担保できない部分をmodel specで書く

TODO

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails6 ArgumentError: Invalid formats: "json"エラー

この記事について

APIの勉強をしているとエラーが出たので、その解決方法を投稿

環境

・Ruby 2.6.6, Rails 6.0.3.2
・Docker,Docker-compose(開発環境)

発生したエラー

スクリーンショット 2020-09-19 15.36.58.png

該当コード

api/books_controller.rb
class Api::BooksController < ApplicationController
  def show
    @book = Book.find(params[:id])
    render 'show', formats: 'json', handlers: 'jbuilder'
  end
end

原因

https://railsguides.jp/layouts_and_rendering.html#formats%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3
railsガイドではformats: 'json'ではなく、render formats: :xmlのようにシンボルで記述するようになっている。rails6からはシンボルになったのかな?

コードを修正

api/books_controller.rb
class Api::BooksController < ApplicationController
  def show
    @book = Book.find(params[:id])
-   render 'show', formats: 'json', handlers: 'jbuilder'
+   render "show", formats: :json, handlers: "jbuilder"
  end
end

シンボルに変更すると、エラーが解消されました。

まとめ

記事を参考にするときはしっかり環境が同じかどうか、確認をした上で勉強をしていく。
全く同じように進めているのにエラーが出るのであれば、バージョン起因を考えるようにする。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

実務経験1週間のまとめ

【何をやったか】

1. psotmanを利用したAPIテスト

→今回のプロジェクトでは、フロント開発とバックエンド開発が分かれており、
 バックエンド側だけで修正し、テストを実施するために必要

2. 軽微な修正(API返答レスポンスの修正など)

→イシューに上げられた、レスポンスがおかしいところや
 HTTPステータスがおかしい点の修正

【実務で使うため、学習すべき内容】

下記は、それぞれ別の記事でまとめていこうと思う。

SELECT文

Docker

Git

細かい便利技

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

《未経験→webエンジニア》実務5日目

昨日投稿できなかったので、今日投稿します。

この記事の目的

自分がやったこと、知らなかったこと、やるべきことを明確にし
1日あたりの成長速度を速める。

【今日やったこと】

APIテスト
イシューに上がっている軽微な内容の修正

【知らなかったこと】

・APIのレスポンスは、コントローラーに種別で作られている
・メモ帳のデフォルト設定で””が逆さになっており、エラーが起きる
・現在いるブランチ名はVScodeの左下に表示されている!

知らなかったテストbreakman→セキュリティ系のテストを行ってくれる
今回の案件では、breakman,rspec,rubocopの3段階でテストする
expected: "期待されている文言" got: "実際の文言"

ここが違わないかをチェックする
API文言の修正だけでなく、ステータスコードもセットで直すようにする
HTTPのステータスコードhttps://qiita.com/terufumi1122/items/997e24dde87f807e3944

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails 6で認証認可入り掲示板APIを構築する #14 seed実行時間の表示

Rails 6で認証認可入り掲示板APIを構築する #13 認証ヘッダの付与

完成図

$ rails db:seed 
-----user start-----
count from: 0
count to: 10
3.4542s
-----user end-----
-----post start-----
count from: 0
count to: 13
0.1095s
-----post end-----
3.5883s

seedsファイルの編集

まずはcontrollerの修正と同じく、postはuserから生成するため、user_seeds.rbから作っていきます。

db/seeds/user_seeds.rb
# frozen_string_literal: true

unless User.exists?
  10.times do |i|
    email = "test#{i + 1}@example.com"
    User.create!(email: email, password: "password",
                 uid: email, provider: "email", name: Faker::Name.name)
  end
end

とりあえず10ほぼユーザーを生成してみましょう。

続いて、post_seeds.rbを上記で生成したユーザーに所属させる形で生成します。

db/seeds/post_seeds.rb
# frozen_string_literal: true

unless Post.exists?
  users = User.all
  users.each do |user|
    Random.rand(0..3).times do
      user.posts.create!(subject: Faker::Lorem.word, body: Faker::Lorem.paragraph)
    end
  end
end

全ユーザーを取得し、0から3件ランダムで投稿を生成します。
Random.rand(x..x)はseedでよく使う手法なので覚えておくと良いです。

db/seeds.rbの編集

db/seeds.rb
# frozen_string_literal: true

seed_models = %i[user post]

all_process_time = Benchmark.realtime do
  seed_models.each do |model|
    puts "-----#{model} start-----"
    puts "count from: #{model.to_s.classify.constantize.count}"
    process_time = Benchmark.realtime do
      require "./db/seeds/#{model}_seeds"
    end
    puts "count to: #{model.to_s.classify.constantize.count}"
    puts "#{format('%.4<time>f', time: process_time)}s"
    puts "-----#{model} end-----"
  end
end
puts "#{format('%.4<time>f', time: all_process_time)}s"
$ rails db:seed 
-----user start-----
count from: 0
count to: 10
3.4542s
-----user end-----
-----post start-----
count from: 0
count to: 13
0.1095s
-----post end-----
3.5883s

modelが増えてきて処理時間が伸びてきたら、どこがボトルネックになっているのか調査しやすくなるはずです。

続き

Rails 6で認証認可入り掲示板APIを構築する #15 pundit導入
連載目次へ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails] User検索機能を実装する

概要

Railsプロジェクトで掲示板サイトを作成中、User検索機能を実装する機会がありました。
備忘録としてここに手順を残しておきます。

ルーティングの追加

GET /users/searchでUsersコントローラのsearch アクションにルーティングされるようにconfig/routes.rbを編集します。

config/routes.rb
resources :users do
  get :search, on: :collection
end

usersコントローラーの編集

searchアクションを作成します。
検索文字列はsearch_keywordフィールドへの入力とするので、params[:search_keyword]で取得します。

app/controllers/users_controller.rb
def search
    if params[:search_keyword].present?
      @users = User.where('name LIKE ?', "%#{params[:search_keyword]}%")
    else
      @users = User.none
    end
  end

ビューの追加

app/views/users/search.html.erb を作成します。

views/users/search.html.erb
<h1>User search</h1>
<%= form_tag search_users_path, method: :get do %>
  <%= text_field_tag :search_keyword %>
  <%= submit_tag "Search", username: :nil, class: "button is-info" %>
<% end %>
<%= render 'users/users', users: @users %>

User の情報が入っている app/views/users/_users.html.erb を既に作っていたので render で呼び出しました。

完成

スクリーンショット 2020-09-19 10.44.17.png

検索するとこんな感じです。
スクリーンショット 2020-09-19 10.56.58.png

以上でRailsプロジェクトにUser検索機能を実装することができました。
ありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]ストロングパラメーターにuser_id(外部キー)を記述する方法

投稿内容

今回はストロングパラメータに外部キーを記述する方法をアウトプット。

実装方法

controller.rb
private

def item_params
  params.require(:item).permit(:name, :description, :category, :brand, :condition, :shipping_cost, :prefecture_id, :shipping_day, :price, images_attributes: [:image]).merge(user_id: current_user.id)
end

このようにmergeメソッドを使い記述。
今回の場合は、ログインしているユーザーのidがuser_idとしてパラメーターに保存される。

最後に

今回は簡潔にアウトプット!参考にして下さい!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker rails + Vue.js localhostで接続が拒否された

この記事について

Dockerの理解が乏しいが故に解決に時間がかかったことを忘れないために投稿する記事となっております。

Vue.jsの導入にあたって参考にした記事

https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253

この記事で関連するファイル

  • Dockerfile
FROM ruby:2.6

# install package to docker container
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev \
    && apt-get install apt-transport-https \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update && apt-get install -y yarn \
    && curl -sL https://deb.nodesource.com/setup_10.x | bash - \
    && apt-get install -y nodejs mariadb-client \
    && mkdir /vue_app

WORKDIR /vue_app
COPY Gemfile /vue_app/Gemfile
COPY Gemfile.lock /vue_app/Gemfile.lock

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
  • docker-compose.yml
compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3320:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bundle exec puma -C config/puma.rb
    environment:
      RAILS_ENV: development
    volumes:
      - .:/vue_app
      - bundle:/usr/local/bundle
      - /app/vendor
      - /app/log
      - /app/.git
    ports:
      - "3000:3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
volumes:
  mysql-data:
    driver: local
  bundle:
    driver: local

起こった問題

https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253#-foreman%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E8%A8%AD%E5%AE%9A
参考URLと同じようにforemanを導入して2つのサーバーが同時に立ち上げられるように設定を行い、docker-copmpose upを実行してブラウザでlocalhost:5000を見にいくと以下が表示された。スクリーンショット 2020-09-11 13.46.23.png

やったこと

①Procfile.devに -b 0.0.0.0を追記して docker-compose run --rm web bin/serverを実行

Procfile.dev
+ web: bundle exec rails s -b 0.0.0.0
- web: bundle exec rails s

②そもそもdocker-compose.ymlのwebのポート番号を変更するのを忘れていたので、ポート番号を変更して docker-compose run --rm web bin/serverを実行

docker-compose.yml
ports:
-      - "3000:3000"
+      - "5000:5000"

しかし、解決ができない。。。

これでやっと解決した

https://docs.docker.jp/compose/reference/run.html
ポート番号までコマンドで改めて指定してあげて、サーバーの立ち上げに成功

docker-compose run -p 5000:5000 --rm web bin/server

ただ、なぜポート番号の指定を明示的にしないと繋がらないのかが分からないよ。。。
これまでにこんなコマンド使ったことなかったし。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker + rails + Vue.js localhostで接続が拒否された

この記事について

Dockerの理解が乏しいが故に解決に時間がかかったことを忘れないために投稿する記事となっております。
foremanによるサーバの設定をし、Ruby on RailsにVue.jsを導入しようとしたが、localhost:5000に接続ができず、どん詰まりしてしまった。

Vue.jsの導入にあたって参考にした記事

https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253

この記事で関連するファイル

  • Dockerfile
FROM ruby:2.6

# install package to docker container
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev \
    && apt-get install apt-transport-https \
    && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
    && echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list \
    && apt-get update && apt-get install -y yarn \
    && curl -sL https://deb.nodesource.com/setup_10.x | bash - \
    && apt-get install -y nodejs mariadb-client \
    && mkdir /vue_app

WORKDIR /vue_app
COPY Gemfile /vue_app/Gemfile
COPY Gemfile.lock /vue_app/Gemfile.lock

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000
  • docker-compose.yml
compose.yml
version: '3'
services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3320:3306'
    command: --default-authentication-plugin=mysql_native_password
    volumes:
      - mysql-data:/var/lib/mysql
  web:
    build: .
    command: bundle exec puma -C config/puma.rb
    environment:
      RAILS_ENV: development
    volumes:
      - .:/vue_app
      - bundle:/usr/local/bundle
      - /app/vendor
      - /app/log
      - /app/.git
    ports:
      - "3000:3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
volumes:
  mysql-data:
    driver: local
  bundle:
    driver: local
  • Procfile.dev
Procfile.dev
web: bundle exec rails s
webpacker: ./bin/webpack-dev-server
  • bin/server
bin/server
#!/bin/bash -i
bundle install
bundle exec foreman start -f Procfile.dev

起こった問題

https://qiita.com/Moo_Moo_Farm/items/afacfe4349af6a106253#-foreman%E3%81%AB%E3%82%88%E3%82%8B%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E8%A8%AD%E5%AE%9A
参考URLと同じようにforemanを導入して2つのサーバーが同時に立ち上げられるように設定を行い、docker-copmpose upを実行してブラウザでlocalhost:5000を見にいくと以下が表示された。スクリーンショット 2020-09-11 13.46.23.png

やったこと

①Procfile.devに -b 0.0.0.0を追記して docker-compose run --rm web bin/serverを実行

Procfile.dev
+ web: bundle exec rails s -b 0.0.0.0
- web: bundle exec rails s

②foremanのポート番号指定

foremanのポート番号はデフォルトで5000番が設定されているようですが、改めて明示的に5000番を指定。

bin/server
#!/bin/bash -i
bundle install
+ bundle exec foreman start -p 5000 -f Procfile.dev

③そもそもdocker-compose.ymlのwebのポート番号を変更するのを忘れていたので、ポート番号を変更して docker-compose run --rm web bin/serverを実行

docker-compose.yml
ports:
-    - "3000:3000"
+    - "5000:5000"

しかし、解決ができない。。。

これでやっと解決した

https://docs.docker.jp/compose/reference/run.html
ポート番号までコマンドで改めて指定してあげて、サーバーの立ち上げに成功

docker-compose run -p 5000:5000 --rm web bin/server

ただ、なぜポート番号の指定を明示的にしないと繋がらないのかが分からないよ。。。
これまでにこんなコマンド使ったことなかったし。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

bundle updateをしても、繰り返し促される時の解決法

To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:2.1.4`

このようなエラーが繰り返し表示されたときは、

gem update --system  

を実行すると解決する場合があります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

form_withメソッドの使用方法

【概要】

1.結論

2.どのように使用するか

1.結論

<%= form_with(model or URL, ) do |f| %>と使用する!

2.どのように使用するか

自分はこのように使用しました!

views/time/new.html.erb
<%= form_with(model: @time, local: true) do |f| %>

自分は”model”に@timeでインスタンス変数を入れました。
modelの後にurlでpath指定してあげるとpathによってmethodも自動で振り分け機能になり、そしてアクションも指定できます。またpathの中に(****.id)と指定するとidを引っ張った状態でアクションを行えます!

class=""指定してあげて、cssにあてることもできます!

|f|はヘルパーメソッドに適用でき、form_withの中身を"f."がついているものに入れ込むことができます!
|f|は変数のようなものなので使いやすい名前で統一したもらえばOKです!

f.label
f.checkout
f.password_field
f.email_field等のヘルパーメソッドに適用できます!


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む