20210325のRubyに関する記事は25件です。

Rooで取り込んだExcelの日時のデータがうまく反映されない

こんにちは

エラーが出た背景

rooを使ってexcelの日時のデータを取り込むよう開発を進めました。
ある程度できあがって確認しようとすると、下記のようなエラーが出ました。
おそらく, in_time_zoneメソッドがinteger型に対応できていないと
下記に
- エラーメッセージが出た部分
- 対応するメソッド(controller)
- エラーが発生したファイル
を載せています。

Screen Shot 2021-03-22 at 1.02.11 PM.png

app/controller/ir/meeting_logs_controller.rb
  def time_from_excel_date_time(excel_date, excel_time)
    date = excel_date.in_time_zone

    if excel_time.is_a?(String)
      time = excel_time.to_time
      hour = time.hour
      minute = time.min
    elsif excel_time.is_a?(Integer)
      hour = (excel_time / 3600)
      minute = (excel_time % 3600 / 60)
    end

    date + hour.hour + minute.minute
  end

普段取り込むexcelファイル(該当箇所のみ)

id 日付 開始時間
12/9 1:00
12/8 1:00
11/27 1:00
11/26 1:00

今回エラーが発生したexcelファイル(該当箇所のみ)

id 日付 開始時間
12月9日 1:00
12月8日 1:00
11月27日 1:00
11月26日 1:00

原因

上の部分を見ていただければわかる通り、日付の区切られている部分が/月日かの違いによるものでした。
この部分が違うと、
/だとそのまま(String型)、
月日だと数字(Integer型)
が変数として取り込まれるので、String型にしか対応していなかったメソッドでエラーが発生してしまいました。

改善策

そのためcontrollerを書き換えました。

app/controller/ir/meeting_logs_controller.rb
  def time_from_excel_date_time(excel_date, excel_time)
  EXCEL_EPOCH = 2209078800
  TICKS_PER_DAY = 60 * 60 * 24
    if excel_date.is_a?(String) || excel_date.is_a?(Date)
      date = excel_date.in_time_zone
    elsif excel_date.is_a?(Integer)
      # 日付が漢字の場合、integer型で受け取る => 1899/12/31 からどれくらいの日数がたったかを表している
      # Unixのゼロタイム(基準)が "1970/1/1" に設定されている為、下記ステップで日付を求める
      # 1. TICKS_PER_DAY をかけて、1899/12/31 からどれくらいの秒数がたったかを計算
      # 2. 1から EXCEL_EPOCH 秒をを引き、1970/1/1 からどれくらいの秒数がたったかを計算

      unix_timestamp = excel_date * TICKS_PER_DAY - EXCEL_EPOCH
      date = Time.at(unix_timestamp).to_datetime - 8.hours - 1.day
    end

    if excel_time.is_a?(String)
      time = excel_time.to_time
      hour = time.hour
      minute = time.min
    elsif excel_time.is_a?(Integer)
      hour = (excel_time / 3600)
      minute = (excel_time % 3600 / 60)
    end

    date + hour.hour + minute.minute
  end

excelから取り込んだ日付の変数が入っているexcel_dateに対して、if文でString型とInteger型に分けて処理を走らせることにしました。
また、unix_timestampの部分が複雑なので下記に説明をしたいと思います。

unixとexcel

このコントローラーの記述は主にunixとexcelの基準の時間を合わせるために書きました。
コメントアウトでも書いてありますが、
excelは基準が1899/12/31なので帰ってくる変数は、1899/12/31から何日たっているかを表します。
しかし、Rubyに限らずunixを基準としているプログラミング言語は1970/1/1(ゼロタイム)を基準に作動します。
なので、excelの変数をunixの基準に合わせるために

unix_timestamp = excel_date * TICKS_PER_DAY - EXCEL_EPOCH

の記述をしています。

最後に

今回僕の初めての投稿なので至らない点があると思いますが、
僕なりに一生懸命頑張ったつもりです。笑
もし、何かアドバイスなどありましたらぜひ教えていただきたいです!

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

mimemagicエラー

gemを入れてbundle install を行ったところ、以下のエラーが出ました。

Your bundle is locked to mimemagic (0.3.5), but that version could not be found
in any of the sources listed in your Gemfile. If you haven't changed sources,
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

んん、よく分からん。。とりあえずGemfile.lockで関係してそうな内容を消して再度bundle install をしたら直りました。

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

Twitter API 登録の仕方(2021年3月17日)

【欲しいもの】
bot を作るために必要なもの。
1. Access Token
2. Access Token Secret
3. Consumer Key
4. Consumer Secret
これらを以下で取得していく。

準備編

1

ここにアクセスする

2

Apply(右上)

3

Apply for a developer account

4

目的を選ぶ(今回はbotを作りたいのでhobbyist > making a bot)

5

get start

6

名前とかemailとか入力する

7

何に使うのか入力する
利用規約に同意する
色々進めて、、、

8

submit application
メールが送られたっぽい

9

メールにある[Confirm your email]をクリック
メールアドレス、パスワードを打って認証完了する。

10

「今あなたのレビューをしているよ!!」的なメールが届く。
他の人の記事を見ているとレビュー完了まで数時間かかるそう。

###11 メールがかえってくる。
今レビューしていますが、もう少し用途について聞かせてください的な内容。
これは、7.で英文と全く同じのでもOK

12

30程度待っていると、twitterからメールが来る
登録申請がapproveされたメールがきました。

13

送られてきたリンクにアクセスして、アプリ名を決める。
Get keyを押す。

14

【api key】
【API secret key】
【Bearer token】
を保存しておく。

15

作ったアプリの中からAccess token & secretをgenerateする。
そこに、Access token、Access token secretがあるからコピーしてどこかに記録しておく。

16

これで最初に述べたAccess Token、Access Token Secret、Consumer Key、Consumer Secretの4つがゲットできた。

ここからは本格的にTwitter bot を作っていく。

https://qiita.com/ryoya-s/items/bc8a0e39716bb2844f0b
の記事を参考にした。

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

User.allって良くないの?って話

こんにちは。たにーです。

今回は、チーム開発で少し議論した

「アクション内でのでのallは良くない?」についてです。

items_controller.rb
 @items_all = Item.all

結論

結論から言うと、悪いことはないが
場面によっては使い方を考えなくてはいけないということです。

カリキュラムで作成したアプリでは、
レコード数がmax10個ぐらいで試していたこともあり、取り出すデータ量が少ないため問題なかった。

もし、その数が100個、1000個、1万個あった場合だと、
情報量が多すぎて、処理速度が遅くなり、ページのロードが遅いなどが起きるかもしれません。

なので、その処理速度をより早くする書き方をご紹介します。

状況について

  • railsでwebアプリケーションを開発中。
  • 販売している商品数をviewに表示させたい。
    例:( 商品一覧(全:〇〇件) )
  • viewとcontrollerには下記のように書いていた。
index.html.erb
  <div class="container">
  <div class="row">
    <div class="col-sm-12 px-sm-0">
      <h2>商品一覧(<%= @items_all.count %>件)</h2>
    </div>
  </div>
  
items_controller.erb
  class Public::ItemsController < ApplicationController
  def index
    @items_all = Item.all
  end

実際にターミナルを見て確認します。

まずは、そのままでページを開いてみます。
そうすると、、、、、

(item.allの場合)
 Started GET "/" for 106.180.147.162 at 2021-03-25 11:43:45 +0000
 Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
 Processing by Public::HomesController#top as HTML
   Rendering public/items/index.html.erb within layouts/application
   (0.2ms)  SELECT COUNT(*) FROM "items"
  ↳ app/views/public/items/index.html.erb:4
  Item Load (0.1ms)  SELECT  "items".* FROM "items" LIMIT ? OFFSET ?  [["LIMIT", 8], ["OFFSET", 0]]
  ↳ app/views/public/items/index.html.erb:8

見て欲しいところはここです。

(item.allの場合)
  Item Load (0.3ms)  SELECT  "items".* FROM "items" ORDER BY "items"."created_at" DESC LIMIT ?  [["LIMIT", 4]]

Item Loadの(0.3ms)、と書いてあります。

この数値が低ければ低いほど応答速度が速い(タイムラグが少ない)と言われています。

もしかしたら、不要なデータを取得してきているから
0.3なのか?書き方でより少なくできるのでは?と気になったところでチームメンバーで解決策を探しました。

実際に調べて試しました

selectで試した

items_controller.erb
  def index
    @items_all = Item.select(:id)
  end
(Item.selectの場合)
Started GET "/items" for 106.180.147.162 at 2021-03-25 12:13:24 +0000
Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Public::ItemsController#index as HTML
  Rendering public/items/index.html.erb within layouts/application
   (0.1ms)  SELECT COUNT("items"."id") FROM "items"
  ↳ app/views/public/items/index.html.erb:4
  Item Load (0.1ms)  SELECT  "items".* FROM "items" LIMIT ? OFFSET ?  [["LIMIT", 8], ["OFFSET", 0]]
  ↳ app/views/public/items/index.html.erb:8

2箇所あるますが、それでも0.2msで早くなっているのがわかります。

countで試した

items_controller.erb
class Public::ItemsController < ApplicationController
  def index
    @items_all = Item.count
  end
(Item.count)
Started GET "/items" for 106.180.147.162 at 2021-03-25 12:22:09 +0000
Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Public::ItemsController#index as HTML
   (0.1ms)  SELECT COUNT(*) FROM "items"
  ↳ app/controllers/public/items_controller.rb:4
  Rendering public/items/index.html.erb within layouts/application
  Item Load (0.2ms)  SELECT  "items".* FROM "items" LIMIT ? OFFSET ?  [["LIMIT", 8], ["OFFSET", 0]]

こちらは,0.3msであまり変わっていない、、、

結果としては、

メソッド 応答速度
all 0.3ms
select 0.2ms
count 0.3ms

selectで特定のカラムを指定する「select」で件数を取得する方法が今の段階では良さそうです!!
もし、違った方法、間違っているなどあれば教えていただけますと幸いです。

以上、たにーでした。

参考文献

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

allメソッドって良くないの?って話

こんにちは。たにーです。

今回は、チーム開発で少し議論した

「アクション内でのでのallは良くない?」についてです。

items_controller.rb
 @items_all = Item.all

結論

結論から言うと、悪いことはないが
場面によっては使い方を考えなくてはいけないということです。

カリキュラムで作成したアプリでは、
レコード数がmax10個ぐらいで試していたこともあり、取り出すデータ量が少ないため問題なかった。

もし、その数が100個、1000個、1万個あった場合だと、
情報量が多すぎて、処理速度が遅くなり、ページのロードが遅いなどが起きるかもしれません。

なので、その処理速度をより早くする書き方をご紹介します。

状況について

  • railsでwebアプリケーションを開発中。
  • 販売している商品数をviewに表示させたい。
    例:( 商品一覧(全:〇〇件) )
  • viewとcontrollerには下記のように書いていた。
index.html.erb
    <div class="col-sm-12 px-sm-0">
      <h2>商品一覧(<%= @items_all.count %>件)</h2>
    </div>
items_controller.erb
  class Public::ItemsController < ApplicationController
  def index
    @items_all = Item.all
  end

実際にターミナルを見て確認します。

まずは、そのままでページを開いてみます。
そうすると、、、、、

(item.allの場合)
 Started GET "/" for 106.180.147.162 at 2021-03-25 11:43:45 +0000
 Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
 Processing by Public::HomesController#top as HTML
   Rendering public/items/index.html.erb within layouts/application
   (0.2ms)  SELECT COUNT(*) FROM "items"
  ↳ app/views/public/items/index.html.erb:4

見て欲しいところはここです。

(item.allの場合)
   (0.2ms)  SELECT COUNT(*) FROM "items"
  ↳ app/views/public/items/index.html.erb:4

(0.2ms)、と書いてあります。

この数値が低ければ低いほど応答速度が速い(タイムラグが少ない)と言われています。

もしかしたら、不要なデータを取得してきているから
0.2なのか?書き方でより少なくできるのでは?と気になったところでチームメンバーで解決策を探しました。

実際に調べて試しました

selectで試した

items_controller.erb
  def index
    @items_all = Item.select(:id)
  end
(Item.selectの場合)
Started GET "/items" for 106.180.147.162 at 2021-03-25 12:13:24 +0000
Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Public::ItemsController#index as HTML
  Rendering public/items/index.html.erb within layouts/application
   (0.1ms)  SELECT COUNT("items"."id") FROM "items"
  ↳ app/views/public/items/index.html.erb:4

それでも0.2msで早くなっているのがわかります。

countで試した

items_controller.erb
class Public::ItemsController < ApplicationController
  def index
    @items_all = Item.count
  end
(Item.count)
Started GET "/items" for 106.180.147.162 at 2021-03-25 12:22:09 +0000
Cannot render console from 106.180.147.162! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by Public::ItemsController#index as HTML
   (0.1ms)  SELECT COUNT(*) FROM "items"

こちらも、0.1msと早くなってるのかな?

結果としては、

メソッド 応答速度
all 0.2ms
select 0.1ms
count 0.1ms

もし、違った方法、間違っているなどあれば教えていただけますと幸いです。

以上、たにーでした。

参考文献

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

N + 1問題を解決する

はじめに仕込んでいたgem bulletが警告ログを発したので解決してみます。
スクリーンショット 2021-03-25 19.32.01.png

N + 1問題とは?

データベースへのアクセス回数が必要以上に多くなってしまう現象の事。モデル間のアソシエーションで発生します。

gem bulletとは?

N + 1問題が発生してる箇所を警告で教えてくれるgemです。

現状

CareRecipitent Load (0.4ms)  SELECT "care_recipitents".* FROM "care_recipitents"
  ↳ app/views/caregiver/tops/index.html.erb:1
  Caregiver Load (0.5ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  CACHE Caregiver Load (0.0ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  CACHE Caregiver Load (0.0ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:37
  Rendered caregiver/tops/index.html.erb within layouts/caregiver (Duration: 16.2ms | Allocations: 6553)
  CACHE Caregiver Load (0.1ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

はじめに親モデルに検索がかけられ、その後に子モデルに4回クエリが発行されていることがわかります。

解決策

子モデル.includes(:親モデル)

  def index
    #@caregiver = Caregiver.find(params[:staff_member_id])
  -  @care_recipitents = CareRecipitent.all
  +  @care_recipitents = CareRecipitent.includes(:caregiver)
  end

コントローラーのindexアクションを書き換えます。

CareRecipitent Load (1.2ms)  SELECT "care_recipitents".* FROM "care_recipitents"
  ↳ app/views/caregiver/tops/index.html.erb:1
  Caregiver Load (0.6ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1  [["id", 1]]
  ↳ app/views/caregiver/tops/index.html.erb:1
  Rendered caregiver/tops/index.html.erb within layouts/caregiver (Duration: 75.3ms | Allocations: 19989)
  Caregiver Load (0.3ms)  SELECT "caregivers".* FROM "caregivers" WHERE "caregivers"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]

クエリの回数が減り、無事ログを消えました。

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

jsファイル デバックする方法(binding.pry)

はじめに

railsではよくbinding.pryを使用して値が入っているか確認する際によく使いますが、
jsはどうやって調べられるのか?
可能であれば動きをみた方が理解度も上がりますよね!
とても簡単です^^それでは行きましょう!

手順

1.検証ツールを開き。
Sourcesを選択します。スクリーンショット 2021-03-25 18.54.41.png

2.jsファイルを選択して開きます。
スクリーンショット 2021-03-25 18.57.58.png

3.処理を止めたい行を選択します。
スクリーンショット 2021-03-25 19.00.40.png

あとはbinding.pryの時と同じようにブラウザで操作します。

選択した場所で処理が止まり、

その状態で、検証ツールのコンソールで変数の中身を確認することもできます!

誰かの参考に慣れば幸いです^^

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

Rubyでビンゴカードを作るコードを書く

はじめに

この記事は以下のサイトにあった問題を解いてみたものです。大変に工夫の余地があるコードになってしまいましたが、一応完成したので初心者だった頃の自分をいつか省みるために作成しました。
アウトプットのネタに困ったらこれ!?Ruby初心者向けのプログラミング問題を集めてみた(全10問)

問題

上記サイトより引用

1から75までの数字をランダムに配置して、ビンゴカードを作成するプログラミング問題です。
ただし、実際のビンゴカードのルールに沿って、各列は以下のような仕様で数字を出力する必要があります。

B:1~15のどれか
I:16~30のどれか
N:31~45のどれか
G:46~60のどれか
O:61~75のどれか

CodeIQに「ビンゴカード作成問題」を出題しました。みなさんの挑戦をお待ちしてます!
に詳細が記されています。

完成したコード

class Bingo
  def self.generate_card
    array = (1..15).to_a
    b_num = array.sample(5)
    array = (16..30).to_a
    i_num = array.sample(5) 
    array = (31..45).to_a
    n_num = array.sample(5)
    array = (46..60).to_a 
    g_num = array.sample(5)
    array = (61..75).to_a 
    o_num = array.sample(5) 

    puts " B |  I |  N |  G |  O"
    5.times do |i|
      unless i == 2
        puts "#{b_num[i].to_s.rjust(2)} | #{i_num[i]} | #{n_num[i]} | #{g_num[i]} | #{o_num[i]} "  
      else
        puts "#{b_num[i].to_s.rjust(2)} | #{i_num[i]} |    | #{g_num[i]} | #{o_num[i]} "  
      end
    end
  end
end
Bingo.generate_card

手順

まず、それぞれの列の数値を取得することから始めようと考えました。例えばBの列には、1~15の範囲で、違う数字をランダムに5つ選ぶ必要があります。これを、配列に対して使うsampleメソッドを利用して実現しました。

b_num
array = (1..15).to_a
b_num = array.sample(5)
p b_num

実行結果

>ruby b_num
[7, 3, 14, 15, 10]

これを、5列分用意することで必要な数値を準備しました。(改善の余地がありそうなポイント1)

次に考えたのは、数値を1行ずつ表示することです。ここで注意するべきなのは、右詰で表示させる仕様になっていることでした。1桁の数字が出ると、Bの列の表示が崩れてしまう問題が発生しました。なので、rjustメソッドを用いてその問題を解決しています。
また、真ん中は空白にする必要があるので、それにも留意して繰り返しのメソッドを用いました。

5.times do |i|
  unless i == 2
    puts "#{b_num[i].to_s.rjust(2)} | #{i_num[i]} | #{n_num[i]} | #{g_num[i]} | #{o_num[i]} "  
  else
    puts "#{b_num[i].to_s.rjust(2)} | #{i_num[i]} |    | #{g_num[i]} | #{o_num[i]} "  
  end
end

ただこの記述の仕方だと、横に非常に長くなってしまっている点が気になります。(改善の余地ポイント2)

感想、まとめ

現状の自分のプログラミングスキルではこのコードが限界です。いつかよりすっきりとしたコードをかけるよう精進したいと強く思える結果となりました。

参考にしたサイト

String#rjust (Ruby 3.0.0 リファレンスマニュアル)
Array#sample (Ruby 3.0.0 リファレンスマニュアル)
Object#to_a (Ruby 3.0.0 リファレンスマニュアル)

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

【初学者向け】uninitialized constantエラー

uninitialized constantエラーが起きる原因

uninitialized constant errorを直訳すると「初期化されていない定数のエラー」という意味:expressionless:
Railsでは「定数やclassが定義されていない」ことを意味:thinking:
uninitialized constantは名前が間違っているという意味のNameErrorということ:rolling_eyes:
つまり、ファイル名の記述間違いなどで、呼びたいクラス名を記述出来ていない場合などでエラーが発生します。

実際に出たエラー

NameError in Users::RegistrationsController#create
uninitialized constant User::GenderId

ここから考えたこと

う〜ん:thinking:User::GenderIdでエラーが発生してるみたいだから、GenderIdに関わる内容を確認してみたほうがよさそうだな。Railsでは「定数やclassが定義されていない」ことを意味する。。。ということはclass名やモデル名で定義しないといけないところをGenderIdで定義しているのでは。。。:thinking:おっ!!

原因

user.rb
(省略)
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :gender_id
(省略)

アソシエーションを間違えていたみたいです。。。
アソシエーションはテーブル同士で関連付けておき、一方のモデルからもう一方のモデルにアクセスできるようへするためのものなので、
今回の場合だと「GenderIdにアクセスしようとしたけどそんなものないですよ」と伝えてくれていたみたいです。 ⬅︎ という認識であってます??

user.rb
(省略)
  extend ActiveHash::Associations::ActiveRecordExtensions
  belongs_to :gender
(省略)

上記のように編集したことで解決しました!!(Genderは定義されているので)
完全な凡ミスでした。。。

参考

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

RailsのGPL混入問題についてまとめ(mimemagic)

!!New!!mimemagicがMITに戻った(3/26追記)

概要

RailsのGPL混入問題についてまとめました。間違いがあればご指摘ください。(2021/3/25現在)
https://github.com/rails/rails/issues/41750

ここには3つの問題がある。

  • Railsが依存しているmimemagicのライセンスがMITからGPL2.0になった
    • もともとGPLライセンスのものが混入していたのにMITになってしまっていた
  • これにより、Railsのbuildができなくなった
    • Railsが依存しているmimemagic0.3.5が削除されたことが原因
  • Railsの依存モジュールにGPLライセンスのものが混入することとなった
    • mimemagicを0.3.6以降にすればbuildはできるが、GPL問題は解決しない

mimemagicのライセンスがMITからGPL2.0になった

mimemagicはMITからGPLに変わった。
mimemagicのissues #97によると、

shared-mime-infoはGPLライセンス。
mimemagicの以下のファイルがshared-mime-info依存している。
https://github.com/minad/mimemagic/blob/master/script/freedesktop.org.xml

これまでmimemagicはMITだったが、shared-mime-infoがGPLなので、mimemagicもGPLにすべきであり修正された。
変更内容:https://github.com/minad/mimemagic/commit/c0f7b6b21a192629839db87612794d08f9ff7e88

Railsがbuildできない問題

前述のGPL問題修正により、Railsのbuildができない問題が起きた。
https://github.com/minad/mimemagic/issues/98

bundle installすると以下のエラーが発生する。

Installing dependencies using bundler 2.2.1
Running: bundle install --jobs=4 --retry=4

Your bundle is locked to mimemagic (0.3.5), but that version could not be found
in any of the sources listed in your Gemfile. If you haven't changed sources,
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

Railsが依存しているmiemagicは0.3.5。これがbut that version could not be foundと言われる。削除されたみたい。

Railsの依存モジュールにGPLライセンス混入

0.3.6以上にすればbuildはできるようになるみたいだが、0.3.6も0.4.0もGPLなのでライセンス問題は解決されない。

GPLのソフトウェアをサーバサイドで使う場合の著作権表示について

GPLのソフトウェアをサーバサイドで使う場合の著作権表示について

GPLのライブラリをサーバサイドで使う場合、ソースコード公開義務はないという解釈が一般的です。

「サーバサイドで使えばプログラムの頒布じゃないからソースコード公開しなくていいじゃん!」という解釈のもと、ウェブサービスではGPLものが結構使われています。

どういうことかというと、ウェブサービスはプログラムの出力結果の頒布であって、プログラムの頒布ではないのでソースコードの公開義務はないという解釈です。
GCCでコンパイルされたバイナリを頒布してもソース公開義務がないのと同じ解釈です。

という解釈があるようだが、どうなんだろうか。

リンク

mimemagicがMITに戻った(3/26追記)

mimemagicの修正PR:Externalise source data #3

this PR removes it from the gem, and instead requires the user to provide one themselves.
Otherwise an environment variable will need to be set to point it in the right direction.

mimemagicに含まれるshared-mime-infoがGPL2.0だっため、これを取り除いたことによって、MITになった。
しかし、それを自分でインストールしないといけなくなった。

Place the file freedesktop.org.xml in an appropriate location, and then set the environment variable FREEDESKTOP_MIME_TYPES_PATH to that path.

How to fix

shared-mime-infoのインストール

shared-mime-info => MIME typesのデータベース。これがGPL2.0。
http://ftp.riken.jp/Linux/cern/centos/7/updates/x86_64/repoview/shared-mime-info.html

Linux

Linuxならshared-mime-infoはインストールされてるみたい。CentOS7ではインストールされていた。

$ yum list installed | grep shared-mime-info
shared-mime-info.x86_64         1.8-5.el7                 @centos-base

macOS

shared-mime-infoをインストールする。

$ brew install shared-mime-info
$ bundle update mimemagic

もしmimemagicのupgradeだけで動かない人は、nokogiriとmarcelもupgradeするといいかも。
参照:https://github.com/rails/rails/issues/41757#issuecomment-806938727

$ bundle update nokogiri marcel mimemagic

mimemagicを0.3.9にupgrade

$ bundle update mimemagic
$ git diff
diff --git a/Gemfile.lock b/Gemfile.lock
index cef0127..4a0ef93 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -133,7 +133,9 @@ GEM
     marcel (0.3.3)
       mimemagic (~> 0.3.2)
     method_source (1.0.0)
-    mimemagic (0.3.5)
+    mimemagic (0.3.9)
+      nokogiri (~> 1)
+      rake
     mini_mime (1.0.2)
     mini_portile2 (2.4.0)
     minitest (5.14.3)

shared-mime-infoのPATHを環境変数にセット

PATHは以下のあたりにある。参考:possible_paths

  • /usr/local/share/mime/packages/freedesktop.org.xml
  • /opt/homebrew/share/mime/packages/freedesktop.org.xml
  • /usr/share/mime/packages/freedesktop.org.xml

CentOS7では以下にあった。

$ ls -l /usr/share/mime/packages/freedesktop.org.xml
-rw-r--r-- 1 root root 2196823 Apr  1  2020 /usr/share/mime/packages/freedesktop.org.xml

Rails起動時にこれを環境変数 FREEDESKTOP_MIME_TYPES_PATH としてセットする。

$ FREEDESKTOP_MIME_TYPES_PATH=/usr/share/mime/packages/freedesktop.org.xml \
    bundle exec pumactl start

一応これで起動したし、問題はなさそうに見える。

これでよいのかrailsのIssueで聞いてみてる。(3/26 12:00現在)
https://github.com/rails/rails/issues/41757#issuecomment-807898051

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

RailsのGPL混入問題についてまとめ

!!New!!mimemagicがMITに戻った(3/26追記)

概要

RailsのGPL混入問題についてまとめました。間違いがあればご指摘ください。(2021/3/25現在)
https://github.com/rails/rails/issues/41750

ここには3つの問題がある。

  • Railsが依存しているmimemagicのライセンスがMITからGPL2.0になった
    • もともとGPLライセンスのものが混入していたのにMITになってしまっていた
  • これにより、Railsのbuildができなくなった
    • Railsが依存しているmimemagic0.3.5が削除されたことが原因
  • Railsの依存モジュールにGPLライセンスのものが混入することとなった
    • mimemagicを0.3.6以降にすればbuildはできるが、GPL問題は解決しない

mimemagicのライセンスがMITからGPL2.0になった

mimemagicはMITからGPLに変わった。
mimemagicのissues #97によると、

shared-mime-infoはGPLライセンス。
mimemagicの以下のファイルがshared-mime-info依存している。
https://github.com/minad/mimemagic/blob/master/script/freedesktop.org.xml

これまでmimemagicはMITだったが、shared-mime-infoがGPLなので、mimemagicもGPLにすべきであり修正された。
変更内容:https://github.com/minad/mimemagic/commit/c0f7b6b21a192629839db87612794d08f9ff7e88

Railsがbuildできない問題

前述のGPL問題修正により、Railsのbuildができない問題が起きた。
https://github.com/minad/mimemagic/issues/98

bundle installすると以下のエラーが発生する。

Installing dependencies using bundler 2.2.1
Running: bundle install --jobs=4 --retry=4

Your bundle is locked to mimemagic (0.3.5), but that version could not be found
in any of the sources listed in your Gemfile. If you haven't changed sources,
that means the author of mimemagic (0.3.5) has removed it. You'll need to update
your bundle to a version other than mimemagic (0.3.5) that hasn't been removed
in order to install.

Railsが依存しているmiemagicは0.3.5。これがbut that version could not be foundと言われる。削除されたみたい。

Railsの依存モジュールにGPLライセンス混入

0.3.6以上にすればbuildはできるようになるみたいだが、0.3.6も0.4.0もGPLなのでライセンス問題は解決されない。

GPLのソフトウェアをサーバサイドで使う場合の著作権表示について

GPLのソフトウェアをサーバサイドで使う場合の著作権表示について

GPLのライブラリをサーバサイドで使う場合、ソースコード公開義務はないという解釈が一般的です。

「サーバサイドで使えばプログラムの頒布じゃないからソースコード公開しなくていいじゃん!」という解釈のもと、ウェブサービスではGPLものが結構使われています。

どういうことかというと、ウェブサービスはプログラムの出力結果の頒布であって、プログラムの頒布ではないのでソースコードの公開義務はないという解釈です。
GCCでコンパイルされたバイナリを頒布してもソース公開義務がないのと同じ解釈です。

という解釈があるようだが、どうなんだろうか。

リンク

mimemagicがMITに戻った(3/26追記)

mimemagicの修正PR:Externalise source data #3

this PR removes it from the gem, and instead requires the user to provide one themselves.
Otherwise an environment variable will need to be set to point it in the right direction.

mimemagicに含まれるshared-mime-infoがGPL2.0だっため、これを取り除いたことによって、MITになった。
しかし、それを自分でインストールしないといけなくなった。

Place the file freedesktop.org.xml in an appropriate location, and then set the environment variable FREEDESKTOP_MIME_TYPES_PATH to that path.

How to fix

shared-mime-infoのインストール

shared-mime-info => MIME typesのデータベース。これがGPL2.0。
http://ftp.riken.jp/Linux/cern/centos/7/updates/x86_64/repoview/shared-mime-info.html

Linux

Linuxならshared-mime-infoはインストールされてるみたい。CentOS7ではインストールされていた。

$ yum list installed | grep shared-mime-info
shared-mime-info.x86_64         1.8-5.el7                 @centos-base

macOS

shared-mime-infoをインストールする。

$ brew install shared-mime-info
$ bundle update mimemagic

もしmimemagicのupgradeだけで動かない人は、nokogiriとmarcelもupgradeするといいかも。
参照:https://github.com/rails/rails/issues/41757#issuecomment-806938727

$ bundle update nokogiri marcel mimemagic

mimemagicを0.3.9にupgrade

$ bundle update mimemagic
$ git diff
diff --git a/Gemfile.lock b/Gemfile.lock
index cef0127..4a0ef93 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -133,7 +133,9 @@ GEM
     marcel (0.3.3)
       mimemagic (~> 0.3.2)
     method_source (1.0.0)
-    mimemagic (0.3.5)
+    mimemagic (0.3.9)
+      nokogiri (~> 1)
+      rake
     mini_mime (1.0.2)
     mini_portile2 (2.4.0)
     minitest (5.14.3)

shared-mime-infoのPATHを環境変数にセット

PATHは以下のあたりにある。参考:possible_paths

  • /usr/local/share/mime/packages/freedesktop.org.xml
  • /opt/homebrew/share/mime/packages/freedesktop.org.xml
  • /usr/share/mime/packages/freedesktop.org.xml

CentOS7では以下にあった。

$ ls -l /usr/share/mime/packages/freedesktop.org.xml
-rw-r--r-- 1 root root 2196823 Apr  1  2020 /usr/share/mime/packages/freedesktop.org.xml

Rails起動時にこれを環境変数 FREEDESKTOP_MIME_TYPES_PATH としてセットする。

$ FREEDESKTOP_MIME_TYPES_PATH=/usr/share/mime/packages/freedesktop.org.xml \
    bundle exec pumactl start

一応これで起動したし、問題はなさそうに見える。

これでよいのかrailsのIssueで聞いてみてる。(3/26 12:00現在)
https://github.com/rails/rails/issues/41757#issuecomment-807898051

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

Dockerでgemを追加しbundle installでエラーになる

エラーの状況

gemを追加後、反映させるため以下のコマンドを実行したがエラーになる

ターミナル
$ docker-compose exec web bundle install
ERROR: No container found for web_1

原因

前回の時はdocker-compose run webの使い方だったが、docker-compose execをあまりわからず使っていたのが原因

run:コンテナ起動してコマンド実行
exec:起動中のコンテナでコマンド実行 <= 重要!

なのでexecで実行する場合は予めdocker-compose upなりでコンテナ起動させてないと使えないとのこと

エラーは勉強になるな、解決しないと焦るけど笑
いうてまだまだ表面の理解だけなので手を動かして覚える

参考にさせていただいたURL

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

投稿機能の実装 方法①

前提モデル
Tweet 投稿したツイートを保存するテーブルの管理
User ユーザー機能(ログインなど)で使用するユーザー情報を保存するテーブルの管理
Comment ツイートに行うコメントを保存するテーブルの管理

1.newアクションのルーティング設定
config/routes.rb

Rails.application.routes.draw do
  中略
  resources :tweets, only: [:index, :new]
end

2.newアクションをコントローラーに定義
app/controllers/tweets_controller.rb

class TweetsController < ApplicationController
  中略

  def new
    @tweet = Tweet.new
  end

end

3.投稿画面のビューを作成
1.app/views/tweetsにnew.html.erbというビューファイルを作成
2.ビューファイルを編集してフォームを作成
form_with
app/views/tweets/new.html.erb

<div class="contents row">
  <div class="container">
    <%= form_with(model: @tweet, local: true) do |form| %>
      中略
    <% end %>
  </div>
</div>

4.createアクションのルーティング設定
ストロングパラメーター
requireメソッド
permitメソッド
プライベートメソッド
app/controllers/tweets_controller.rb

class TweetsController < ApplicationController
  中略

  def create
    Tweet.create(tweet_params)
  end

  private
  def tweet_params
    params.require(:tweet).permit(:name, :image, :text)
  end

end

5.投稿完了画面のビューを作成
1.app/views/tweetsにcreate.html.erbというビューファイルを作成
2.create.html.erbを編集

6.空のツイート投稿ができないようにバリデーションの設定
バリデーション
validates :カラム名, バリデーションの種類
app/models/tweet.rb

class Tweet < ApplicationRecord
  validates :text, presence: true
end
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

seedで10人ユーザーを作って1人5つの記事を持たせるユーザーを作成する

開発環境

環境
ruby:2.6.4
rails:6.0.3
userにはusersモデル、username,email,password,password_confirmation(確認用)を持たせ、
投稿にはarticlesモデル、title,textカラムを持たせています。
画像のアップロードはactivestorageを使用してavatarとしています。
gem 'Faker'を使用しています。

題名の通り早速作っていきます

アカウント=10人
1人のアカウント=5つのarticle(記事)
Faker::Name.nameはランダムに名前を作成してくれるfakerの機能、
Faker::Internet.emailはランダムにemailを作成してくれる機能、
Faker::Hacker.say_something_smartはランダムで文字を作成してくれる機能だと思います。
これに関してはあまり理解しておらずとりあえず文字を作成してくれたので使用しています
指定の文字を入れたい場合は
titel: "任意の文字"
text: "任意の文字"
とすれば良いと思います。
avatarには/assets/imagesに保存してある画像を登録するようにしてあります。

seed.rb
puts 'users ...'
10.times do
  user = User.create!(
    username: Faker::Name.name,
    email: Faker::Internet.email,
    password: 'foobar',
    password_confirmation: 'foobar',
  )
    user.avatar.attach(io: File.open('app/assets/images/cat.jpg'),filename: 'cat.jpg')
end

puts 'articles ...'
5.times do |n|
  User.all.each do |user|
    user.articles.create!(
      title: Faker::Hacker.say_something_smart,
      text: Faker::Hacker.say_something_smart
    )
  end
end
user.avatar.attach(io: File.open('app/assets/images/cat.jpg'),filename: 'cat.jpg')

上のavatarを登録するコードですが必ず()の外で書くようにしてください。
()の中で書くと誤字のエラーがでます。僕はこのエラーに5時間ほどハマりました。
それにactivestorageの場合あまり情報が少ないので本当にこの記述で正解なのかもわかりませんがとりあえず成功できているので良いと思います。
これで内容がよければ

rails db:seed

と打てば完了になります。

最後にいつでもログインできるアカウントを作る

migrate:resetしたときにいちいちアカウントを作り直すのもめんどくさいのでseedにいつでもログインできるようにメール認証もいらないアカウントを作ります。
人気俳優のお二人に参加してもらいます。
user1.skip_confirmation!
これはメール認証はスキップするコードみたいです。
しかしこのまま実行するとアカウント作成は成功したもののメール認識のスキップはできていませんでした。
そこで最後に
user1.save!と保存し、実行してみると見事成功していました。
今回は画像を保存するのは面倒だったのでコードは書きませんでしたが上と同じようにすることでできるかと思います。

seed.rb
user1 = User.create!(
  username: "横浜流星",
  email: "任意のアドレス",
  password: '123456',
  password_confirmation: '123456',
)
user1.skip_confirmation!
user1.save!

user2 = User.create!(
  username: "成田凌",
  email: "任意のアドレス",
  password: 'abcdef',
  password_confirmation: 'abcdef',
)
user2.skip_confirmation!
user2.save!

rails初学者が書いた記事なので間違いがあるかもしれません
もし間違いがある場合報告してもらえますと助かります。

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

README 画像挿入の仕方

README 画像挿入

Qiita記事では多くのREADMEについての画像の挿入の手順を解説している記事があります。
今回私は、上記記事でのやり方とは違った、googleの拡張機能を使用した画像作成・READMEに画像挿入までの手順を解説します!!

というのも、閲覧記事で参考になったものは多くありましたが、googleの拡張機能を使用した画像挿入についての記事は、調べたところでは見当たらなかったので、
自身で挿入から、解決まで行いました。

ご参考までにしていただけたら幸いです。

なぜ他記事を参考にしなかったか

今回、自身で作成したポートフォリオでは、トップページだけでもかなり下にスクロールして全体像が見えるという、課題がありました。
通常のスクリーンショットでは、一画面に収まりせんでした。
gif画像でもgyazoを使用して全体を撮影することも実行しましたが、
撮影秒数が決められたなか全てを撮影するのは困難且、見にくい画像となってしまいました。

そこでgoogle拡張機能の「Awesome Screenshot」を使用し、全体を撮影できるようにしました。

Awesome Screenshot

googleの検索で「Awesome Screenshot 拡張機能」と検索していただいたらでききます。
インストールを行ったら順位完了です!

Awesome Screenshotではフルページ画像がとる事ができ、非常に使い勝手が便利な拡張機能となっています。

画像撮影手順

インストールしたAwesome Screenshotを撮影したいHPでクリックします。

1.クリック後上部バーで「レコード」「キャプチャ」の選択蘭があるので、「キャプチャ」をクリック

2.「キャプチャ」をクリックしたら、「フルページ」をクリック

3.「フルページ」をクリック後、自動でページを撮影できます

4.撮影した画像をダウンロードして完了

非常に簡単な手順でフルページの撮影ができるのでおすすめです!!

READMEに画像情報記載

ダウンロードした画像を、自身の開発中コードのディレクトリに挿入します。
※画像専用のディレクトリを作成しておくことをおすすめします

私の場合、ダウンロードした画像を
public/imagesディレクトリに格納しています。

後は、格納した画像をREADMEに記述するだけで画像挿入ができます。

<img src="public/images/画像名">

まとめ

非常に簡単に且フルページでの画像挿入ができるので、スクリーンショットで何枚も画像をとる手間を省けます!!

今回掲載した内容は他のqiita記事には調べた限りなかった為、
共有させていただきます!!

issueを利用した画像挿入の仕方もあるので、その際は他の記事にも乗っているのでそちら参考にしていただけたらと思います!!

宜しくお願いします。

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

Ruby on Railsの日付操作まとめ

現在日付/時刻

pry(main)> Time.current
=> Thu, 25 Mar 2021 11:36:35 JST +09:00
pry(main)> 0.days.ago
=> Thu, 25 Mar 2021 12:16:53 JST +09:00

nowよりcurrentを使うほうが良いらしい。
https://qiita.com/kodai_0122/items/111457104f83f1fb2259

特定の日付

pry(main)> Time.new(2021, 3, 25, 11, 22, 33, 00)
=> 2021-03-25 11:22:33 +0000

●日前/●日後

[9] pry(main)> Time.current.yesterday
=> Wed, 24 Mar 2021 11:47:08 JST +09:00
[10] pry(main)> Time.current.tomorrow
=> Fri, 26 Mar 2021 11:47:16 JST +09:00
[11] pry(main)> Time.current.ago(3.days)
=> Mon, 22 Mar 2021 11:47:28 JST +09:00
[12] pry(main)> Time.current.since(3.days)
=> Sun, 28 Mar 2021 11:47:43 JST +09:00
[13] pry(main)>

●ヶ月前/●ヶ月後

[13] pry(main)> Time.current.last_month
=> Thu, 25 Feb 2021 11:50:33 JST +09:00
[14] pry(main)> Time.current.next_month
=> Sun, 25 Apr 2021 11:50:41 JST +09:00
[15] pry(main)> Time.current.ago(3.month)
=> Fri, 25 Dec 2020 11:50:52 JST +09:00
[16] pry(main)> Time.current.since(3.month)
=> Fri, 25 Jun 2021 11:50:59 JST +09:00
[17] pry(main)>

●年前/●年後

[17] pry(main)> Time.current.last_year
=> Wed, 25 Mar 2020 11:52:07 JST +09:00
[18] pry(main)> Time.current.next_year
=> Fri, 25 Mar 2022 11:52:12 JST +09:00
[19] pry(main)> Time.current.ago(3.year)
=> Sun, 25 Mar 2018 11:52:26 JST +09:00
[20] pry(main)> Time.current.since(3.year)
=> Mon, 25 Mar 2024 11:52:34 JST +09:00

0:00/23:59

[23] pry(main)> Time.current.beginning_of_day
=> Thu, 25 Mar 2021 00:00:00 JST +09:00
[24] pry(main)> Time.current.end_of_day
=> Thu, 25 Mar 2021 23:59:59 JST +09:00

月初/月末

[25] pry(main)> Time.current.beginning_of_month
=> Mon, 01 Mar 2021 00:00:00 JST +09:00
[26] pry(main)> Time.current.end_of_month
=> Wed, 31 Mar 2021 23:59:59 JST +09:00

年始/年末

[27] pry(main)> Time.current.beginning_of_year
=> Fri, 01 Jan 2021 00:00:00 JST +09:00
[28] pry(main)> Time.current.end_of_year
=> Fri, 31 Dec 2021 23:59:59 JST +09:00

週明け/週末

[29] pry(main)> Time.current.beginning_of_week
=> Mon, 22 Mar 2021 00:00:00 JST +09:00
[30] pry(main)> Time.current.end_of_week
=> Sun, 28 Mar 2021 23:59:59 JST +09:00

先週/来週(何故か時間は月曜0:00になるよう・・・)

[31] pry(main)> Time.current.last_week
=> Mon, 15 Mar 2021 00:00:00 JST +09:00
[32] pry(main)> Time.current.next_week
=> Mon, 29 Mar 2021 00:00:00 JST +09:00

●曜日

[38] pry(main)> Time.current.beginning_of_week(:wednesday)
=> Wed, 24 Mar 2021 00:00:00 JST +09:00
[39] pry(main)> Time.current.last_week(:wednesday)
=> Wed, 17 Mar 2021 00:00:00 JST +09:00
[40] pry(main)> Time.current.next_week(:wednesday)
=> Wed, 31 Mar 2021 00:00:00 JST +09:00

フォーマット

[41] pry(main)> Time.current.strftime("%Y-%m-%d %T")
=> "2021-03-25 12:09:26"

範囲
範囲をwhere句に渡すとbetweenで検索してくれるらしい

[50] pry(main)> Time.current.all_day
=> Thu, 25 Mar 2021 00:00:00 JST +09:00..Thu, 25 Mar 2021 23:59:59 JST +09:00
[51] pry(main)> Time.current.all_month
=> Mon, 01 Mar 2021 00:00:00 JST +09:00..Wed, 31 Mar 2021 23:59:59 JST +09:00
[52] pry(main)> Time.current.all_year
=> Fri, 01 Jan 2021 00:00:00 JST +09:00..Fri, 31 Dec 2021 23:59:59 JST +09:00
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RSpec 多対多関係 モデルテスト(例.Tagモデル)

はじめに

rspecのTagのmodelテストをする際、中間テーブル(post_tag)を介したやり方に苦戦したため、
factory_botを用いて、多対多関係 (has_many through) のテスト作成方法をご説明致します。

テーブル

posttagの間にpost_tagテーブルがある状態です。

スクリーンショット 2021-02-27 11.08.49.png

到達点

以下の2点を達成する
・中間テーブルを介したmodelテストのFactoryBotを理解する
・中間テーブルを介したmodelテストの記述方法を理解する

流れ

① 各モデルのvalidatesを確認
② FactoryBotの記述
③ modelテストの記述

① 各モデルのvalidatesを確認

app/models/post.rb
class Post < ApplicationRecord
  has_many :post_tags, dependent: :destroy
  has_many :tags, through: :post_tags

  validates :title,
            presence: true,
            length: { maximum: 60 }
  validates :body,
            presence: true,
            length: { maximum: 2000 }
end
app/models/post.rb
class Tag < ApplicationRecord
  has_many :post_tags, dependent: :destroy
  has_many :posts, through: :post_tags

  validates :name, presence: true, length: { maximum: 50 }
end
app/models/post.rb
class PostTag < ApplicationRecord
  belongs_to :post
  belongs_to :tag

  validates :post_id, presence: true
  validates :tag_id, presence: true

② FactoryBotの記述

app/spec/factories/post.rb
FactoryBot.define do
  factory :post do
    sequence(:title) { |n| "title-#{n}" }
    sequence(:body) { |n| "body-#{n}" }

    after(:create) do |post|
      create_list(:post_tag, 1, post: post, tag: create(:tag))
    end
  end
end

after(:create)を使用することで、post生成後に、tagとpost_tagが生成されます。

app/spec/factories/tag.rb
FactoryBot.define do
  factory :tag do
    sequence(:name) { |n| "tag-#{n}" }
  end
end

sequenceでユニークnameを生成できます。

app/spec/factories/post_tag.rb
FactoryBot.define do
  factory :post_tag do
    association :post
    association :tag
  end
end

association :post association :tagとすることで、
post_tagのmodelテストにおいて
let(:post_tag) { create(:post_tag) }と記述するだけで
postとtagも生成できます。

ただし、associationはhas_many側(今回の場合,post,tag)では記述せず、
belong_to側でのみ使用しましょう。

③ modelテストの記述

app/spec/requests/post.rb
RSpec.describe Post, type: :model do
  let(:post) { create(:post) }

  it "タイトル、本文、user_idがある場合、有効であること" do
    expect(post).to be_valid
  end

  it "user_idがない場合、無効であること" do
    post.user_id = nil
    expect(post).to be_invalid
  end

  describe "タイトル" do
    it "タイトルがない場合、無効であること" do
      post.title = nil
      expect(post).to be_invalid
      expect(post.errors[:title]).to include("を入力してください")
    end

    context "タイトルが60文字以下の場合" do
      it "有効であること" do
        post.title = "1" * 60
        expect(post).to be_valid
      end
    end

    context "タイトルが61文字以上の場合" do
      it "無効であること" do
        post.title = "1" * 61
        expect(post).to be_invalid
      end
    end
  end

  describe "本文" do
    it "本文がない場合、無効であること" do
      post.body = nil
      expect(post).to be_invalid
      expect(post.errors[:body]).to include("を入力してください")
    end

    context "本文が2000文字以下の場合" do
      it "有効であること" do
        post.body = "1" * 2000
        expect(post).to be_valid
      end
    end

    context "本文が2001文字以上の場合" do
      it "無効であること" do
        post.body = "1" * 2001
        expect(post).to be_invalid
      end
    end
  end
end

app/spec/requests/tag.rb
RSpec.describe Tag, type: :model do
  let(:tag) { create(:tag) }

  describe "name" do
    it "タグ名がある場合、有効であること" do
      expect(tag).to be_valid
    end

    it "タグ名がない場合、無効であること" do
      tag.name = nil
      expect(tag).to be_invalid
      expect(tag.errors[:name]).to include("を入力してください")
    end

    context "タグ名が50文字以下の場合" do
      it "有効であること" do
        tag.name = "1" * 50
        expect(tag).to be_valid
      end
    end

    context "タグ名が51文字以上の場合" do
      it "無効であること" do
        tag.name = "1" * 51
        expect(tag).to be_invalid
        expect(tag.errors[:name]).to include("は50文字以内で入力してください")
      end
    end
  end
end

app/spec/requests/post_tag.rb
RSpec.describe PostTag, type: :model do
  let(:post_tag) { create(:post_tag) }

  it "post_idとtag_idがある場合、有効であること" do
    expect(post_tag).to be_valid
  end

  it "post_idがない場合、無効であること" do
    post_tag.post_id = nil
    expect(post_tag).to be_invalid
  end

  it "tag_idがない場合、無効であること" do
    post_tag.tag_id = nil
    expect(post_tag).to be_invalid
  end
end

associationによってlet(:post_tag) { create(:post_tag) }が一文で済みました。
なお、itやcontext内の文章は、英語だとスペルミス等が出る可能性があるため、基本日本語にしております。

間違い等がありましたらご指摘の方よろしくお願いします。

参考記事

FactoryBot(FactoryGirl)チートシート
factory_girl で最低限知っておきたい4つの使い方
FactoryBot(旧FactoryGirl)で関連データを同時に生成する方法いろいろ

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

バリデーションの書き方(Rails)

バリデーションとは

バリデーションは、正しいデータだけをデータベースに保存するために制約をかける事です。
modelに書くことでデータベースに保存する前に受け取った情報を正しいのか判定させます。

空でないこと

validates :name, presence: true

空であること

validates :name, absence: true

一意性であること(重複していないこと)

validates :name, uniqueness: true

文字数制限

validates :name, length: { minimum: 2 }  # 2文字以上
validates :name, length: { maximum: 50 } # 50文字以下
validates :name, length: { in: 2..50 }   # 2文字以上50文字以下
validates :name, length: { is: 6 }       # 6文字のみ

Boolian型

validates :publish, inclusion: { in: [true, false] }

参考

Active Record バリデーション Railsガイド v.6.1

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

本番環境でS3に保存されない件について

本番環境にデプロイしたときにAWSのS3に保存されずに、アプリケーション上に保存されてしまう件について改善したので記述しておきます。

どこが間違っていたのか

config/development.rb
# 上記省略
  config.active_storage.service = :amazon
config/production.rb
# 上記省略
  config.active_storage.service = :local

間違っていたのは上記の記述で、開発環境ではS3に保存できるように記述を変更していたのですが本番環境でlocalに保存されるようになっていました。
production.rbには本番環境での設定、development.rbでは開発環境での設定を記述しているので漏れのないように変更加えておかないとな、、と反省いたしました。
なのでproduction.rbの記述を下記のように変更。

config/production.rb
# 上記省略
  config.active_storage.service = :amazon

これで開発環境と同様にS3に保存できるようになりました。

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

Rails6でaction_textを使う

action_textを使いたい。

一. action_textのマイグレーションファイルを生成してマイグレート

rails action_text:install
rails db:migrate

二. gem file内のimage_processingのコメントアウトを外してbundle install

gem 'image_processing', '~> 1.2'
bundle install

三. scaffoldでモデルを作成(ここではstring型のtitleカラムだけを持ったpostモデルを想定)してマイグレート

rails g scaffold post title:string
rails db:migrate

四. Postモデルにhas_rich_text :contentを追記

class Post < ApplicationRecord
  has_rich_text :content
end

五. posts.controllerのストロンパラメーターに:contentを追記

def post_params
  params.require(:post).permit(:title, :content)
end

六. app/views/posts/_form.html.erbにコンテントの部分を追記

<div class="field">
  <%= form.rich_text_area :content %>
</div>

七. app/views/posts/show.html.erbにコンテントの表示領域を追加

<%= @post.content %>

これでざっくり完成。

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

【Cloud9】failed to write new configuration file /home/ubuntu/.gitconfig.lock

Error

Rails チュートリアルをやるために Cloud9 を開いたところ下記のエラーが発生。
Tab 補完が効かないなど変な挙動になった。

error: failed to write new configuration file /home/ubuntu/.gitconfig.lock

┌──────────────────────────────────────────────────────────┐
│                 npm update check failed                  │
│           Try running with sudo or get access            │
│           to the local update config store via           │
│ sudo chown -R $USER:$(id -gn $USER) /home/ubuntu/.config │
└──────────────────────────────────────────────────────────┘

Translate

failed to write new configuration file /home/ubuntu/.gitconfig.lock

新しい設定ファイルの書き込みに失敗しました /home/ubuntu/.gitconfig.lock

Resolve

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
・
・
・
/dev/xvda1      9.7G  9.7G     0 100% /

ディスク容量が圧迫されて書き込み処理等の操作が正しく行われなくなっていると予想。
今まで作成した別のアプリの削除を実施したところ、元の挙動に戻った。

xxx:~/environment $ rm -rf hello_app
xxx:~/environment $ rm -rf toy_app
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RubyとPHPは何が違うのか、と聞かれたら

私は未経験からエンジニアを目指している人間だが、もし同じ境遇であればちょっと読んでみて欲しい、、、。

まず、なぜこんな記事を書くのかというところからだが、とある企業の面接を受けた時にこう聞かれて、上手く答えられなかったからだ。

「今まではRubyを勉強してきて、現在はPHPを勉強しているということですが、RubyとPHPの言語の違いは何かありますか?」

質問された瞬間、「あ、これ答えられない。」と思ってしまって、本当にこんな質問だったか、答えた内容が的を得ていたかもはっきり思い出せない。。。
学んでいる上では、Rubyの方が可読性が高い、、気がします。みたいなことを言っていた気がする。

そもそも、自分がRubyからPHPを学び始めたのは、PHPのシェアが多いから、使えて損はないだろうという浅い考えだった。

エンジニアを目指すと言いながら、言語の違いすらも説明できなかった。そんな反省の思いからこの記事を書いている。ショックのあまりポエムっぽくなっているかもしれないが、許して欲しい。。。

とりあえず答えから

あくまで、「私の考え方で、聞かれた質問の記憶から推測するに」なのでもっと良い答え方があるかもしれないが、もう一度同じ質問をされた場合はこう答えると思う。

「Rubyは、設計思想として「新しさ」よりも「気持ちよさ」や「自由度」を重視して設計されている、と開発者のまつもとゆきひろ氏がおっしゃられているように、学んでいて、実際にストレスフリーで、常に楽しく学ぶことができました。PHPは、勉強する上ではコードも非常にわかりやすく、学びやすいと感じました。しかし、プログラムを改めて見たときに、Rubyと比べるとわずかですが、可読性に欠けるのではないかと感じました。」

どうだろうか、まだ浅いかもしれない。というか、PHPの経験が浅過ぎてこれ以上深くならない。ぁぁ。
しかし、今回の質問で聞かれているのは、自分が学んできて、何か違いを感じているか?という趣旨に聞こえた。学んできたことから今のところ感じているのはこのくらいなのだ、、、。
と言いつつ質問の趣旨が違ったら申し訳ないのだが。違う場合は、「RubyとPHPを学ぶんだったらその違いくらい理解した上で目的があって勉強してるんだよね?だったらその違いは説明できるよね?」という趣旨になると思う。
というか、こっちの可能性の方が高い!!

じゃあ可能性が高かった方で

今、この記事を書きながら、何もソースがない状態で少し考えてみた。
思ったことを、そのまま書いてみようと思う。

どちらも、Webページ・Webアプリが作れる。RubyはPHPほどWebに強くはないが、汎用性が高い。PHPは、基本的にはWebに特化している。RubyもPHPも日本語ドキュメントが豊富なので、学びやすい。。。シェアで言えばPHPの方が高い、、、。つまり、Webならどっちでも良い!?かといって、Web以外の用途の場合、今度はRubyじゃなくても良い?Rubyじゃなくても良いものをRubyで開発する理由をあげるなら、やっぱり設計思想から、、?

私の知見からは以上(終わった)。

記事を漁っていると、こんな情報が。

大規模になると採用するフレームワークが重要になるので、言語云々よりも、フレームワークの選択肢を含めてみてみないと意味が無いかも知れません。
PHPかRubyで迷っています。

な、なにぃぃぃ!!

、、、いや、確かにそういわれると納得できる気がする。
そうなるとここで、開発言語を選ぶ基準を誰もが知りたくなるはずだ。
誰もが知りたくなるはずだ。(大事なことなので)

そこで、こんな面白い記事があった。(一番上に出てきたがな)
プロダクトの開発言語ってどうやって選ぶの?

この記事は、
・schoo
・Socket
・chatwork
・Talknote
のそれぞれのサービスがどのような基準で言語を選んできたのかを解説している。
特に気になったのは、この部分だ。

PHPはデザイナーでも読み書きできる人が多く、バックエンドとフロントエンドの接合点での調整がしやすいのも選定理由の一つです。

「あ、作るシステムにマッチしているかとか、そういうのだけでなくこんなことも考えた上で選別するのか!」
これは面白いと思った。今までは、実際に開発言語を選ぶ時は、「早く開発したいから」とか、「この言語は処理はやいじゃん?」とか(流石にそこまで適当ではないが)、なんとなく、あくまでも作る対象に合わせた決め方・言語そのものの特徴による決め方をしているのだろうと考えていた。
もちろん重要な部分ではあるだろうし、そこに重点を置いている開発もあると思う。
ただ、実務未経験の自分からすれば、そういう想定外な視点はとても面白い。

Socketの場合だと、

ノンフレームワークなPHPは、サービスの本当の初期にとりあえず実証できるものをささっと1人で作る、といったことに非常に向いているので、モックアップを作るときにはいつもPHPを使っています。WebネイティブでありながらCに近いノリでOSの様々な部分に割と気軽にアクセスできるので、「フレームワークの仕様的にこれはできない(時間がかかる)」といった障壁に初期の事業コンセプトが惑わされにくいんです。
初期の事業検証が終わったら、サービスプロトタイプをチームで開発するフェーズに入ります。まだ社内エンジニアがいなかった僕らにとっては「外部の人にも手伝ってもらいやすい」ことが、言語を選ぶ上で何より重要な指標でした。時流的にも、僕らの周りにも、優れたRailsのエンジニアが多くいました。Railsを選んだ理由はそこです。ただ、僕自身のRailsの経験値は当時そこまで高くなかったので、個人的にはそれなりに労を割きましたが(笑)

とあるように、とりあえずさくっと作るためにPHP、外部の人にも手伝ってもらいやすいからRailsみたいな選び方もある。
一言で言ってしまえば、「選び方は様々」ということで。
こういう言い方をすると、そりゃそうだろって思ってしまうのだが、実際こういう選び方もしますし、こういう場合もありますって言われたら、「様々」の中で想像していなかったことが必ずあると思う。

結局は?

で、結局質問されたらどう答えれば良いのか?
ここにきてふと気付いたのだが、趣旨として、「RubyとPHPの言語としての違い」を問われたのなら、とにかく違いを述べれば良いのではないだろうか。

具体的にいうと、
Rubyはnewline terminated方式(?)で波かっこやセミコロンが必要ない。
PHPはsemicolon terminated方式で、波かっこやセミコロンが必要。
TruthyとFalsyにおいて、
Rubyはfalseとnilがfalseになり、0や空文字列、空配列はtrueになる。
PHPはfalseとnullがfalseなのは自明だが、0、空文字列、空配列もfalseになる。
というような、プログラムを書いている上で気を付けるべき違いが述べられれば良かったのだろうか?

そもそも質問がはっきり思い出せないことからも、質問の意図を汲み取れていないというのがはっきりわかるので、本当はどういう質問だったのかが明らかになることはないが、自分が勉強する・していることくらいはある程度根拠と知識を持っておくべきだと反省した。

経験者やこれがすでに当たり前になっている人からすればつまらない話だったかもしれないが、最後まで読んでくれてありがとうと言いたい。

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

Ruby on Rails 製 Web アプリケーションのセキュリティテストガイド (静的テスト:SAST)

これなに?

Ruby on Rails 製 Webアプリケーションのセキュリティテストをするためのガイドです。本ガイドは脆弱なRailsアプリである RailsGoat を題材にセキュリティテストの手法を解説します。

本記事ではソースコードをレビューして脆弱性を発見する手法である 静的アプリケーションセキュリティテスト(SAST) を扱います。

対象読者

Ruby on Rails でプロダクト開発している組織のセキュリティチームを想定していますが、セキュリティに関心がある開発者テスターにとっても役立つしれません。

前提知識・スキル

  • 情報セキュリティに関する基本的な用語を知ってること
  • 基本的なWebアプリケーションの脆弱性について知ってること
  • Ruby on Rails のコードが読めること
  • Linux のコマンドを叩けること

目次

  1. 静的アプリケーションセキュリティテストの準備
  2. Rails のコードをBrakemanで静的解析し、結果を精査する
  3. Rails のコードを独自のルールでレビューする

静的アプリケーションセキュリティテストの準備

静的アプリケーションセキュリティテストはソースコードをツールで解析したり、目視でレビューしたりすることにより脆弱性を発見します。

テストを始める前に、Rails アプリによくあるプログラミングミスを把握しておいたり、コードを読みやすい環境を整えておいたりしておくと効率よくテストを進められます。

セキュリティコードレビューのための前提知識

Railsアプリの開発者とセキュリティテスターにとって Securing Rails Applications (Ruby on Rails Guides) は必読書です。Rails アプリによくあるセキュリティの問題と、その対応方法が充実しています。

Securing Rails Applications に基づいてコードレビューするだけでも多くの脆弱性をカバーできると思います。読んだことない人はすぐ読みましょう!

英語版より更新は遅れますが、日本語版もあります。

コードレビューの環境

ソースコードを見る環境を整えましょう。小規模プロジェクトならGitHubをWebブラウザで眺めてもよいですが、ある程度の規模なら高機能なテキストエディタやIDEを使うことお勧めします。なお、本記事では Visual Studio Code を使います。

コードレビューの記録

コードレビューにおいて記録はとても重要です。似たようなコードが大量にあるときに「どこまで見たっけ?」となるのを防いだり、レビュアー※が確認するエビデンスになるためです。

※ややこしいですが、セキュリティテストをチームでやる場合、テストの実行者とそのレビュアーがいるはずです

記録方法は好きなやりかたでよいと思いますが、参考までに筆者の記録方法も合わせて紹介します。

Brakeman を使ったソースコードの静的解析

Rails には Brakeman というソースコードの静的解析ツールがあります。Brakeman は自動かつ高速にソースコードを検査できるため、セキュリティテストでは重宝します。

RailsGoat を Brakeman で解析すると、次のような結果が得られます。

本節では Brakeman を使ってコードの欠陥を検出し、その結果を目視で精査します。

Brakeman の注意点

Brakeman はとても便利なツールですが、次の3点を認識しておく必要があります。

1. Brakeman では検出できない脆弱性がある

Brakeman はすべての脆弱性※に対応しているわけではありません。また、対応していても漏れなく検出してくれるわけではありません。Brakeman が何も報告しなかった=脆弱性ゼロとは思わないでください。

様々な脆弱性を網羅的にテストするには、Brakeman だけでなく目視によるコードレビューや動的アプリケーションセキュリティテストで補完する必要があります。

※Brakeman が対応している脆弱性の種類は次のページにあります:Brakeman Warning Types

2. 検出されたアラートの中には誤報(False Positive)が含まれる

Brakeman が報告するのはあくまで「疑わしいコード」なので、実際は問題がないコードが検出されることもあります。

本当に問題があるコードなのかを判断するために、結果を目視で精査する必要があります。

3. 誤報でなかったとしても、実際に悪用可能かは分からない

Brakeman が検出するのはソースコード上の問題なので、Web アプリケーションとして動くときにどのような挙動をするかは、実際に動かしてみるまで分かりません。

その問題が悪用可能であるかを確かめるには、動的アプリケーションセキュリティテストが必要です。動的アプリケーションセキュリティテストは次回扱います。

Brakeman の使い方

Brakeman を使うには、Railsアプリケーションのソースコードがあるディレクトリに移動して brakeman -A コマンドを実行します。 -A は追加の検査をしてくれるオプションです。付けて困ることはないと思うので、基本的には -A をつけておいてよいと思います。

brakeman の実行例:

user@sectest:~/railsgoat$ brakeman -A
Loading scanner...
Processing application in /home/user/railsgoat
Processing gems...

======

== Warnings ==

Confidence: High
Category: Cross-Site Scripting
Check: CrossSiteScripting
Message: Unescaped cookie value
Code: cookies[:font]
File: app/views/layouts/application.html.erb
Line: 12

Confidence: High
Category: Dangerous Send
...

通常、結果は標準出力に吐き出されますが、-o オプションを付けるとファイルに出力でき、-o filename.html-o filename.csv のように使います。CSVファイルはスプレッドシートに貼り付けやすいのでおすすめです。

user@sectest:~/railsgoat$ brakeman -A -o result.csv
======
user@sectest:~/railsgoat$ more result.csv
Confidence,Warning Type,File,Line,Message,Code,User Input,Check Name,Warning Code,Fingerprint,Link
High,Cross-Site Scripting,app/views/layouts/application.html.erb,12,Unescaped cookie value,cookies[:font],,CrossSiteScripting,2,febb2
1e45b226bb6bcdc23031091394a3ed80c76357f66b1f348844a7626f4df,https://brakemanscanner.org/docs/warning_types/cross-site_scripting/
...

出力されたCSVファイルをスプレッドシートにインポートした例:

このスプレッドシートは精査の記録に役立ちます。

Brakeman の結果を精査する

Brakeman のアラートはすべてが脆弱性というわけではなく、誤報の場合もあります。特にインジェクション系の脆弱性は誤報が多いです。誤報が多いレポートは役に立たないので精査しましょう。

Confidence は誤報度合いの参考にはなりますが、High で誤報のこともあれば、逆に Weak でもホンモノのこともあります。アラートが多くてすべてを見切れない場合を除き、すべてのアラートを精査することをお勧めします。

精査は FileLine が指し示しているソースコードを参照しながら、パラメータである CodeUser Input が安全な値であるかを目視で確認していきます。

安全であるかの判断方法はいろいろあると思いますが、例を次に挙げます。

パラメータが検証されている場合

Brakeman はパラメータが検証されていたりサニタイズされている場合でもアラートを出すことがあります。この場合、アラートは False Positive と判断できます。

ただし、検証やサニタイズの妥当性は確認しておきましょう。もし安全である確信が持てない場合は、動的アプリケーションセキュリティテストで有効性を確認するのも良いでしょう。

パラメータの作成者が信頼できる場合

パラメータの作成者が信頼できるとき、そのアラートは False Positive と判断できる場合があります。Webアプリケーションで処理されるパラメータの参照元や作成者は様々です。

パラメータの参照元のと作成者例:

例えば Brakeman はクロスサイトスクリプティングのアラートを挙げており、実際に入力値がサニタイズされていない場合であっても、コンテンツ作成者が職務上パラメータにスクリプトを埋め込むことを許可されていれば問題と判断できるでしょう。

ただし、パラメータの作成者が信頼できるからと言って、パラメータの検証やサニタイズが無意味かというとそうでもありません。作成時点ではパラメータが安全な値であっても、使用されるまでの間に改ざんされていた場合、その値は安全とは言えません。

パラメータの参照元データが何らかの脆弱性によって改ざんされることが想定される場合、検証やサニタイズはリスクを緩和します。

精査結果の記録

先に出力したCSVファイルを整形し、精査結果を追記すると良いです。

RailsGoat はわざと脆弱性に作られたアプリなのでほぼすべてホンモノっぽいです。一部、セキュリティにかかわる設定不備を指摘しているものもあり、実害に発展しうるか現時点では判断をつけがたいアラートはとりあえず?としてます。

※このような「実害に発展するかわからないが、セキュリティ上は不適切」を報告するか否かは、セキュリティテストの目的に沿って決めれば良いと思います。

Brakeman まとめ

Brakeman を使ってソースコードにあるセキュリティ上の欠陥を検出しました。

Brakeman は自動かつ高速にソースコードを検査できますが、すべての脆弱性を発見できるわけでもなく、また、誤報もあります。

そのため、Brakemanの結果を目視で精査したり、網羅できていない脆弱性を別のテストで補完することが必要です。

目視によるコードレビュー

Brakeman は脆弱性の発見にとても役立ちますが、Brakemanでは発見できない脆弱性もあります。特にアプリ固有の脆弱性を発見するにはソースコードを目視でレビューすることも必要です。

コードをなんとなく眺めて脆弱性を発見することもありますが、あらかじめレビューの観点やルールを定めておくと、再現性のあるテストを効率よく実行できるようになります。

レビューのルールに決まったものはなく、またコードには組織や開発チームのクセみたいなものもありますので、テストしながらセキュリティチームの中で育てていくのが良いです。

本節では筆者のオレオレルールを3つ紹介しますので参考にしてください。

Mass Assignment / 不適切な Strong Parameters の設定

概要

Update 系の処理で使われる Strong Parameters が適切に設定されていない場合、攻撃者によってデータを改ざんされる可能性があります。

あからさまな Mass Assignment は Brakeman でも検出できますが、アラートが出ないパターンもあるので目視でもチェックしておくとよいです。

ルール

コントローラー全体を .permit で検索し、Strong Parameters で permit している属性が必要最小になっていることを確認しましょう。

必要最小とは、「アクションを呼び出すユーザが変更できる属性」であることです。これを厳密に判断するにはアプリケーションの仕様を理解しておく必要がありますが、直感的には「Web画面で編集できる項目以外にpermitされている属性があればNG」と判断してよいと思います。

RailsGoat の場合

RailsGoat には該当するコードが4か所ありました。

1つ目:

params.require(:message).permit(:creator_id, :message, :read, :receiver_id)

permitのパラメータはユーザに変更されても問題なさそうな属性です。大丈夫そうですね。

補足:このパラメータは create メソッドで使われるため問題ないですが、もし update で使われる場合はNGかもしれません。更新時に creator_id を変更できるのは変ですよね。

2つ目:

params.require(:schedule).permit(:date_begin, :date_end, :event_desc, :event_name, :event_type)

同様に大丈夫そう。

3つ目:

params.require(:user).permit!

permit!はすべての属性をpermitしちゃいます。高確率でダメです。

4つ目:

params.require(:user).permit(:email, :admin, :first_name, :last_name)

admin が非常に怪しいです。もしこれが管理者権限フラグなら権限昇格できてしまうかもしれません。

※ちなみに3つ目と4つ目のパターンはBrakemanでも検出できます

検索結果の Open in editor をクリックすると、該当する行の前後n行も合わせて確認できます。

これをスプレッドシートに貼り付けるとレビューの記録を付けることができます。

権限昇格 (IDOR)

概要

あるWebサイトの My Account をひらいたら URL が http://example.com/customers/1234 だった時を想像してください。URL の 1234 は自分のユーザIDと推測できます。この 12341 に変えてアクセスしたらどうなるでしょうか。

もし他の人のアカウント情報が参照できたら、そのサイトはアクセス制御に問題があります。そしてこのような攻撃手法を Insecure Direct Object Reference (IDOR) と言い、権限昇格につながる脆弱性です。参照だけでなく、更新、削除についても同様です。

アクセス制御はアプリケーション固有の仕様となるため、Brakemanで検知することはできません。つまりソースコードから権限昇格の脆弱性を検出するには、目視によるレビューが必要です。

ルール

コントローラを正規表現 params\[:.*id\] で検索し、リソースにアクセス制御が必要な場合、アクションまたはクエリにアクセス権が含まれているかを確認します。

わかりづらいので具体例を挙げます。ブログ記事(article)を修正できるのは自分だけ、という仕様を想像しつつ次のパターンAとパターンBのコードを観察してください。

A. アクセス権が含まれていないクエリ:

def update:
  @article = Articles.find(params[:article_id])
  @article.update!(article_params)
  redirect_to @article
end

B. アクセス件が含まれているクエリ:

def update:
  @article = @current_user.articles.find(params[:article_id])
  @article.update!(article_params)
  redirect_to @article
end

パターンAの場合、article_id パラメータを改ざんすれば他人の記事を更新できてしまいます。対してパターンBは他人の article_id を使ったところで @article は nil となり更新には失敗するため安全です。

RailsGoat の場合

RailsGoat には該当するコードが10か所ありました。

いくつかのパターンがあるので、それぞれ見てみます。

1つ目のパターン:IDOR

class MessagesController < ApplicationController
# ...省略...
  def show
    @message = Message.where(id: params[:id]).first
  end

IDパラメータを別の番号変えると、他人のメッセージも見れてしまいそうです。NGです。(もし他人のメッセージも見れるという仕様であればOKです)

2つ目のパターン:IDORかつSQLインジェクションっぽい

class UsersController < ApplicationController
# ...省略...
  def update
    message = false

    user = User.where("id = '#{params[:user][:id]}'")[0]

IDパラメータを別の番号変えると他人のユーザ情報を更新できてしまいそうです。さらにプレースホルダを使ってないので、SQLインジェクションもできそうですね。

このように本来の観点とは異なる問題が見つかることもあります。

3つ目のパターン:IDORっぽいけど違う

class AdminController < ApplicationController
# ...省略...
  def get_user
    @user = User.find_by_id(params[:admin_id].to_s)
    arr = ["true", "false"]
    @admin_select = @user.admin ? arr : arr.reverse
  end

一見怪しいですが、問題ないかもしれません。AdminControlerという名前から、このアクションは管理機能に見えます。管理者はすべてのユーザ情報を参照できるという仕様であれば、IDORではありません。

ただしこのアクションが本当に管理者からのみ呼ばれる仕様なのかは確認しましょう。

4つ目のパターン:IDORではないが別の問題

def admin_param
  params[:admin_id] != "1"
end

クエリではないため IDOR ではありません。しかしクエリパラメータ admin_id1 の時を特別に扱っていることから、別の問題がありそうです。

このようにレビュー中に気になったことも記録しておくとよいでしょう。作業記録の例:

TIPS

実際のセキュリティテストでも、この例のようにレビュー中気になることが次々と出てくることはよくあります。

すぐに済むならその場で確認しても良いですが、あまり深追いし過ぎると自分が今何をやってるかわからなくなってきます。そのため、確認事項があってもその場ではメモするにとどめておき、実際に確認するのは作業途中のレビューが片付いてからにしたほうが良いです。

レースコンディション

概要

レースコンディションとは、同一のリソースに同時にアクセスした場合に想定外の動作が発生する事象をいいます。

レースコンディションには様々なパターンが有りますが、ここでは現在時刻をもとにIDやファイル名を決定している場合に問題が発生するケースを挙げます。

ルール

ソースコード全体を正規表現 Time\.(now|current|zone\.now) で検索し、IDやファイル名などを現在時刻だけで作成している場合、NGと判断します。

RailsGoat の場合

RailsGoat には1つありました。

def self.make_backup(file, data_path, full_file_name)
  if File.exist?(full_file_name)
    silence_streams(STDERR) { system("cp #{full_file_name} #{data_path}/bak#{Time.zone.now.to_i}_#{file.original_filename}") }
  end
end

現在時刻と original_filename を組み合わせたファイル名でバックアップを作成しているようです。可能性は低いですが、original_filename が同一のリクエストが同時に2回以上実行されたら、1回目に作成されたバックアップファイルが2回目以降に作成されるバックアップファイルで上書きされてしまいそうです。

次にこのメソッドがどこから呼ばれるかを突き止めます。make_backup メソッドを呼出してるメソッドを呼出してるメソッド...と辿った結果、BenefitFormsControllerupload メソッドに行きつきました。

upload はWebサイトの利用者から呼ばれるアクションなので、顕在化する可能性があると判断できます。

コードレビューのまとめ

独自のルールに基づいたコードレビューの例を3つ挙げました。

  1. .permit で検索し、不適切な Strong Parameters 設定を発見する
  2. params\[:.*id\] で検索し、権限昇格(IDOR)の問題を発見する
  3. Time\.(now|current|zone\.now)で検索し、ID重複によるレースコンディションを発見する

上記以外にもいろいろなルールが考えられますので、セキュリティチームでレビュールールを育てていくとよいです。

今回は予めルールを定めたうえでレビューしましたが、ルールは無くても勘や思い付きに頼って脆弱性を探索することもできます。(未知の脆弱性を発見するには探索的なテストも必要です)

静的アプリケーションセキュリティテストまとめと補足

本記事では Rails アプリケーションの静的アプリケーションテストの手法として、Brakemanを使ったソースコードの静的解析と、目視によるセキュリティコードレビューの手法を紹介しました。

また、紹介の中で様々なテスト手法を挙げ、静的手法だけでは網羅できない領域があることにも触れました。

  • 静的テスト・動的テスト (SAST vs DAST)
  • 手動テスト・自動テスト (Brakeman vs 目視)
  • 手順が定められたテスト・アドホックなテスト (ルール vs 勘)

セキュリティテストに限らずテスト全般に言えることですが、費用対効果の高いテストをするには、多様なテスト手法をいい感じに取り入れることも重要です。

最後に

本記事は現在執筆中の Ruby on Rails 製 Web アプリケーションのセキュリティテストガイド を Qiita 向けに改編したものです。

今後、別記事で下記のトピックについても扱う予定です。

  • セキュリティテスト計画や準備
  • 静的アプリケーションセキュリティテスト(SAST):javascript
  • 静的アプリケーションセキュリティテスト(SAST):既知の脆弱性
  • 動的アプリケーションセキュリティテスト(DAST)
  • セキュリティテスト結果報告書の作成

本ガイドは常に改善を必要としています。お気づきの点があったらフィードバックを頂けるととても喜びます。

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

RubocopがCircleCI上でのみエラーになる事象について

事象

ローカルでは問題なくrubocopを実行できたのですが、
Githubにプッシュ、CircleCI上でrubocopを実行すると以下のようなエラーが起きました。

#!/bin/bash -eo pipefail
mkdir -p /tmp/rubocop-results
bundle exec rubocop . --out /tmp/rubocop-results/check-results.xml --format progress
/home/circleci/electronote/.rubocop.yml: Warning: no department given for AsciiComments.
404 "Not Found" while downloading remote config file http://shopify.github.io/ruby-style-guide/rubocop.yml
/usr/local/lib/ruby/2.7.0/net/http/response.rb:124:in `error!'

結論

原因

ローカルとCIで、bundle installのインストール先パスが異なることが原因でした。

ローカル:
/usr/local/bundle

CI:
{プロジェクトディレクトリ}/vendor/bundle

CIではプロジェクトディレクトリ下にインストールされるため、
rubocop実行時に、このディレクトリ内にある.rubocop.ymlを読み込んでいたものと思われます。

対策

vendorディレクトリをrubocopのチェック対象外に追加すると解決しました。

project_dir/.rubocop.yml
inherit_from: .rubocop_todo.yml

AllCops:
  Exclude:
    - 'db/**/*'
    - 'node_modules/**/*'
    - 'bin/*'
    - 'log/**/*'
    - 'public/**/*'
    - '.git/**/*'
    - 'tmp/**/*'
    - 'vendor/**/*'

解決までに試したこと

いずれも効果ありませんでしたが、以下の方法を試してみました。

(1) CircleCI設定ファイル書き換え

変更前:

circleci/config.yml
version: 2.1
orbs:
  ruby: circleci/ruby@1.1.0
  ...

jobs:
  test:
    ...
    steps:
      ...
      - ruby/rubocop-check
      ...

変更後:

circleci/config.yml
version: 2.1
orbs:
  ruby: circleci/ruby@1.1.0
  ...

jobs:
  test:
    ...
    steps:
      ...
      - run:
          name: Rubocop check
          command: bundle exec rubocop
      ...

結果:

変化なし

(2) 不要なrubocop.ymlをコメントアウト

上述のエラー文を見ると、

404 "Not Found" while downloading remote config file http://shopify.github.io/ruby-style-guide/rubocop.yml

「何でshopifyが出てくんねん!」

となったので、このURLがどこから湧いて出てきたのか調べてみました。

$ grep -r http://shopify.github.io/ruby-style-guide/rubocop.yml ./*
./vendor/bundle/ruby/2.7.0/gems/bootsnap-1.4.5/.rubocop.yml:#   - http://shopify.github.io/ruby-style-guide/rubocop.yml

どうやらbootsnap-1.4.5のディレクトリにもRubocopの設定ファイルがあり、そこで呼び出されているらしい。
というわけで、呼び出している箇所をコメントアウト。

変更前:

project_dir/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.4.5/.rubocop.yml
inherit_from:
  - http://shopify.github.io/ruby-style-guide/rubocop.yml

AllCops:
  Exclude:
    - 'vendor/**/*'
    - 'tmp/**/*'
  TargetRubyVersion: '2.2'

...

変更後:

project_dir/vendor/bundle/ruby/2.7.0/gems/bootsnap-1.4.5/.rubocop.yml
# inherit_from:
#   - http://shopify.github.io/ruby-style-guide/rubocop.yml

AllCops:
  Exclude:
    - 'vendor/**/*'
    - 'tmp/**/*'
  TargetRubyVersion: '2.2'

...

変更してからCI環境にSSH接続し、rubocopを実行。

参考:
https://qiita.com/yu-croco/items/41d2114c94e3a6ea748a

結果:

何か違うエラーが出てきた?

$ bundle exec rubocop
/home/circleci/electronote/.rubocop.yml: Warning: no department given for AsciiComments.
Error: The `Lint/HandleExceptions` cop has been renamed to `Lint/SuppressedException`.
(obsolete configuration found in vendor/bundle/ruby/2.7.0/gems/bootsnap-1.4.5/.rubocop.yml, please update it)

(3) vendorディレクトリをrubocopのチェック対象外に追加

上述の通り、上手く動きました。
めでたしめでたし。

参考

CircleCIでのみrubocopがエラーになる
https://qiita.com/kabi5/items/40ea864757162e931be1

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

【Gem】deviseの導入

devise

ユーザー管理機能を簡単に実装するためのGem。
ユーザー新規登録やログイン機能を簡単に実装することができる:v:(とても便利)

deviseの使い方

Gemfile編集

Gemfileの最後の行に追記する。
(開発環境、テスト環境、本番環境すべてで使用するため!と認識してます:thinking:

Gemfile
(省略)
gem 'devise'

コマンドを実行してGemインストール

ターミナル
bundle install

ローカルサーバーを起動している場合はサーバーの再起動を忘れずに!!

少し脱線

ローカルサーバーを起動した状態でコードを編集しても、ブラウザを更新すれば、その編集内容はブラウザに反映されます。しかし、開発環境で以下の変更をした場合、必ずローカルサーバーを再起動する必要があります。

  • Rubyバージョンの変更
  • テーブル・カラム情報の変更
  • Gemの導入状況の変更 ⬅︎ 今回はこれ:eyes:

これらに関わる情報は、「ローカルサーバーを起動するタイミングに1度だけ読みこまれる」ためサーバーの再起動が必要になります。

deviseの設定ファイル作成

deviseを使用するためには、Gemのインストールに加え、 devise専用のコマンドで設定ファイルを作成する必要があります。

ターミナル
rails g devise:install

このコマンドは、追加したdeviseというGemの「設定関連に使用するファイル」を自動で生成するコマンド。
deviseの設定ファイルがrailsアプリケーションにインストールされます。

deviseのモデル作成

アカウントを作成するためのモデルを新しく作成する必要があり、作成には通常のモデルの作成方法ではなく、deviseのモデル作成用コマンドでモデルを作成します。

ターミナル
rails g devise モデル名

コマンドを実行後は、routes.rbに以下のルーティングが自動的に追記されます。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
end

devise_forは、ユーザー機能に必要なルーティングを生成してくれるdeviseのメソッドです。

テーブル作成

emailとpasswordはデフォルトで入っているが、追加したいカラムがある場合は、マイグレーションファイルを編集。
テーブルの設計が確認できたらマイグレーションを実行します。

ターミナル
rails db:migrate

指定したモデル名のテーブルが作成されていれば成功:v:

最後に、サーバーの再起動を忘れずに実行しておく!!

参考

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