20200922のRubyに関する記事は30件です。

each_with_indexメソッドを使ってみる

表題の通りです。
each_with_indexメソッドを使ってコードを書いてみます。

each_with_indexメソッドとは

要素の繰り返し処理と同時に、その要素が何番目に処理されたのか表すことができるメソッド。
要素の繰り返し処理であればeachメソッドもできるが、
each_with_indexメソッドは、何番目に処理されたものかも表示できるのが特徴。
下記のように書く。

配列名.each_with_index do |item, i|
  # 処理
end

使ってみた(その1)

fruits = ["みかん", "パイナップル", "りんご"]

fruits.each_with_index do |fruit, i|
 puts "#{i}番目のフルーツは、#{fruit}です。"
end

出力結果は下記のようになる。

0番目のフルーツは、みかんです。
1番目のフルーツは、パイナップルです。
2番目のフルーツは、りんごです。

使ってみた(その2)

配列に、ある値が含まれているかを検索して、結果を出力するメソッドを
each_with_indexメソッドを使って書いてみます。
わざわざeach_with_indexメソッド使わなくてもバイナリーサーチ使えば良いのでは

配列は
input = [3, 6, 9 ,12, 15, 21, 29, 35, 42, 51, 62, 78, 81, 87, 92, 93]
検索したい値は変数target_numに代入するとします。
下記のような検索結果が出るようにします。

# 検索したい値が配列の中にあったとき。たとえば検索したい値が12だった場合
左から4番目にあります

# 検索したい値が配列の中にないとき
その数は含まれていません

回答例

def search(target_num, input)
  input.each_with_index do |num, index|
    if num == target_num
      puts "左から#{index + 1}番目にあります"
      return
    end
  end
  puts "その数は含まれていません"
end

input = [3, 6, 9 ,12, 15, 21, 29, 35, 42, 51, 62, 78, 81, 87, 92, 93]

target_num = gets.to_i
search(target_num, input)

解説

今回は検索する処理を行うメソッドをsearchメソッドと定義しました。

そのsearchメソッドの中で、each_with_indexメソッドを使い、
配列inputに格納されている値をnumとして1つずつ取り出し、取り出された順で番号付けをします。その番号はindexに入ります。

numと、target_num(検索したい値が入っている)が同じか比べます。
もし同じであれば、その値が配列の左から数えて何番目にあるのかを出力したいので、
puts "左から#{index + 1}番目にあります"とします。
そして、もう検索結果が出てしまって、これ以上の検索が必要なくなるのでreturnで終わりにします。

配列に格納されているすべての値と、検索したい値が同じにならなかった場合は、
puts "その数は含まれていません"とします。

おわり

以上です。
本題から少しずれますが解説部分を書くのがなかなか難しい・・。
最後まで読んでくださった方ありがとうございました。

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

未経験からエンジニア就職するために一生懸命作ったクソアプリを晒していく。

はじめに

未経験からエンジニアになる方法として、プログラミングスクールに通う人は多いと思います。
ただスクールで作った物を面接で見せても、採用サイドからすると「またそれね。」って感じらしいです。

某有名スクールのなんとかエキスパートだと、メルカリのクローン作りますよね。
僕は実際の面接で、「それの説明はいいよ」って言われました。

就職するためにはプラスアルファでどれだけ勉強したかが重要となってきます。
ただ、どんなものを作ればよいか悩む人は多いのではないかなと思います。

当時、僕が就職するために作ったクソアプリたちが出てきたので、参考にして頂ければ幸いです。
「あ、こんなのでいいんだ」と就職活動の緊張から開放されることを願っております。

経歴

2018年9月〜12月 プログラミングスクール
2019年1月 クソアプリ作り始める
2019年2月 内定が出る
2019年4月 東京で就職

作ったクソアプリたち

※ スマホで動くか微妙です。

神になって惑星を消すアプリ

スクリーンショット 2020-09-22 23.01.34.png
惑星の画像が並んでいて、クリックすると惑星が消える。
JavaScriptでクリックした要素をRemoveしてるだけだと思います。
このアプリのURL

目指せホームラン王!!江越大賀の挑戦

スクリーンショット 2020-09-22 23.01.48.png

江越大賀という当たれば飛ぶと言われている選手が阪神タイガースにいて、
その選手がホームラン王になれるのは何年後かを計算するアプリ。

おそらくif文でぐちゃーっと計算してる。

このアプリのURL

あいあい傘メーカー

スクリーンショット 2020-09-22 23.02.49.png
スクリーンショット 2020-09-22 23.02.55.png

二人分の名前を入力したら相合い傘に名前が書かれる。
たしか、全部Railsで作っていて、POSTとか使いたいなと思って作った。

全部上書きで一番上のレコード取得してくるだけの仕様。
文字を縦にするのが難しかった記憶もある。
このアプリのURL

ジョブスの発表会

スクリーンショット 2020-09-22 23.03.49.png

ジョブスが発表したっぽい画像を作るアプリ。
入力した文字の画像を生成できる。
Canvasに文字を入れるだけなんだけど、今見たら日本語に対応してなかった。
テキストエリアの文字消してもCanvasから消せなかったから、「TextReset」っていうボタンを付けたみたい。
「CreateImage」押したら下に、画像が貼付けされる。

このアプリのURL

おのののかを探せ

スクリーンショット 2020-09-22 23.04.12.png

「おのののか」っていう文字列を探すアプリ。
本当のランダムに生成していなくて、一列分の「おのののか」っぽい文字列を配列で登録しておいて、
それを並び替えているっぽい。

このアプリのURL

その他

他にも色々ありましたのでよかったら見てみてください。

結論

誰がどう見てもクソアプリでした。
でも、コレ見せて就職できたのだから作ることって大事なんだと思います。

質はともかくとりあえず作ってみるのが良いです。

自己紹介

今更ツイッタはじめました。
よかったらフォローしたり、質問してください。

ツイッター←こちら

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

link_toで遷移した時に1度リロードしないとJSが上手く表示されない問題を解決したい

目次

  1. はじめに
  2. JSの表示を邪魔するのは・・・
  3. Turbolinksって何者?
  4. 解決策
  5. 最後に

はじめに

現在、ポートフォリオを作成中であり、
その中でJavaScriptを用いたカレンダー機能を実装していたのですが、

link_toからカレンダーページに遷移した際に、
1度リロードしないとJavaScriptが適用されない問題が生じた
ので、

この辺の原因を明確にして、記録を残そうということでこの記事を執筆しています。
本記事は、プログラミング初心者によるものであることをご了承ください。

JSの表示を邪魔するのは・・・

ずばり、Turbolinksです。
コイツのせいで、頑張って書いたJSが遷移先で表示されないのです。

このように考えると何だか憎たらしいヤツに思えてきちゃいますが、
色々調べていると、実は良いヤツらしい。

良いヤツなんだけど、たまに余計なお節介をして、
うっかり我々の大事な大事なJSを隠してしまうようです。

Turbolinks、意外と憎めないやつかもしれない。
ってことでもっと知りたくなってきましたね(無理矢理)。

次項で詳しく見ていきます。

Turbolinksって何者?

Turbolinksとは、リンククリックに対して、ページ遷移を自動的にAjax化してくれるライブラリです。

①aタグをクリックすると、遷移先のページをAjaxで取得する

②その取得したページが要求するCSSやJSが、現在のページのものと同じである場合、titleやbodyのみ(画面の一部だけ)を置き換える

というのがTurbolinksの主な仕組みです。

Turbolinks導入のメリット

Turbolinks導入の最大のメリットは、画面表示を高速化してくれることです。

通常であれば、リンクをクリックすると、指定されたページに遷移するため、その分時間がかかりますが、Turbolinksを導入している場合、ページの遷移は行われず、Ajaxにより画面の一部だけを更新する仕組みになっているので、結果的に読み込みに時間がかからず、ページの表示が高速化するというわけです。

サイトを見ている側からすると、少しでも待たされる時間をなくしたいわけですから、
TurbolinksはUXを向上させる救世主って感じですね。

何だ、やっぱり良いヤツじゃん:laughing:
最初から導入されているってことは、それなりに便利で使えるからなんですよね。

とは言いながらも、どんなに良いヤツでも、時と場合によっては煩わしく感じてしまうものです。

Turbolinks導入のデメリット

Turbolinks導入のデメリットは、JSやCSSの適切な表示を妨げてしまう場合があることです。

メリットのところで、Turbolinksは画面の一部だけを更新させると説明しましたが、
この仕組みこそが、JavaScriptが上手く反映しない問題を生じさせていると言えます。
上記で、Turbolinksを用いると、ページの遷移は行われず、Ajaxにより画面の一部だけを更新する事になると述べました。

ブラウザのページ遷移が行われていないので、JavaScriptのイベントが発火せず、結果的にJavaScriptが上手く反映しないというわけです。

これが噂のTurbolinksの余計なお節介ってやつか。なるほど。

解決策

Turbolinksを導入しつつ、必要なところではそれを無効化し、
何とかJavaScriptの動作を上手く反映させたい。

そういう人向けに、どうやら2つの解決策があるみたいです。

① Turbolinksが提供するイベントを追加

JSファイルに、Turbolinks専用のイベントturbolinks:loadを追加することでJSを正常に動作させます。

test.js
document.addEventListner('turbolinks:load', function() {
  console.log('Loaded');
});

② 特定のリンクでTurbolinksを無効化

aタグ(link_to)にdata: {"turbolinks" => false}を加えることによって、
Turbolinksを無効化し、遷移先に飛ぶようにすることでJSを正常に動作させます。

test.html.slim
=link_to "テスト", tests_path, data: {"turbolinks" => false}

最後に

ポートフォリオ作成をきっかけにたまたまTurbolinksを調べることになったわけですが、
調べれば調べる程、まだまだ奥が深そうな感じがしました。
今回は、基本の部分で留めておこうと思いますが、また機会があれば調べてみたいところです。

尚、本記事の内容で間違っている点や不足している点などがありましたら、ご教示いただけると幸いです。

参考:Railsガイド Turbolinks動作原理
   Ruby on Rails 速習ガイド p.340-p.344

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

Turbolinks : link_toで遷移した時に1度リロードしないとJSが上手く表示されない問題を解決したい

目次

  1. はじめに
  2. JSの表示を邪魔するのは・・・
  3. Turbolinksって何者?
  4. 解決策
  5. 最後に

はじめに

現在、ポートフォリオを作成中であり、
その中でJavaScriptを用いたカレンダー機能を実装していたのですが、

link_toからカレンダーページに遷移した際に、
1度リロードしないとJavaScriptが適用されない問題が生じた
ので、

この辺の原因を明確にして、記録を残そうということでこの記事を執筆しています。
本記事は、プログラミング初心者によるものであることをご了承ください。

JSの表示を邪魔するのは・・・

ずばり、Turbolinksです。
コイツのせいで、頑張って書いたJSが遷移先で表示されないのです。

このように考えると何だか憎たらしいヤツに思えてきちゃいますが、
色々調べていると、実は良いヤツらしい。

良いヤツなんだけど、たまに余計なお節介をして、
うっかり我々の大事な大事なJSを隠してしまうようです。

Turbolinks、意外と憎めないやつかもしれない。
ってことでもっと知りたくなってきましたね(無理矢理)。

次項で詳しく見ていきます。

Turbolinksって何者?

Turbolinksとは、リンククリックに対して、ページ遷移を自動的にAjax化してくれるライブラリです。

①aタグをクリックすると、遷移先のページをAjaxで取得する

②その取得したページが要求するCSSやJSが、現在のページのものと同じである場合、titleやbodyのみ(画面の一部だけ)を置き換える

というのがTurbolinksの主な仕組みです。

Turbolinks導入のメリット

Turbolinks導入の最大のメリットは、画面表示を高速化してくれることです。

通常であれば、リンクをクリックすると、指定されたページに遷移するため、その分時間がかかりますが、Turbolinksを導入している場合、ページの遷移は行われず、Ajaxにより画面の一部だけを更新する仕組みになっているので、結果的に読み込みに時間がかからず、ページの表示が高速化するというわけです。

サイトを見ている側からすると、少しでも待たされる時間をなくしたいわけですから、
TurbolinksはUXを向上させる救世主って感じですね。

何だ、やっぱり良いヤツじゃん:laughing:
最初から導入されているってことは、それなりに便利で使えるからなんですよね。

とは言いながらも、どんなに良いヤツでも、時と場合によっては煩わしく感じてしまうものです。

Turbolinks導入のデメリット

Turbolinks導入のデメリットは、JSやCSSの適切な表示を妨げてしまう場合があることです。

メリットのところで、Turbolinksは画面の一部だけを更新させると説明しましたが、
この仕組みこそが、JavaScriptが上手く反映しない問題を生じさせていると言えます。
上記で、Turbolinksを用いると、ページの遷移は行われず、Ajaxにより画面の一部だけを更新する事になると述べました。

ブラウザのページ遷移が行われていないので、JavaScriptのイベントが発火せず、結果的にJavaScriptが上手く反映しないというわけです。

これが噂のTurbolinksの余計なお節介ってやつか。なるほど。

解決策

Turbolinksを導入しつつ、必要なところではそれを無効化し、
何とかJavaScriptの動作を上手く反映させたい。

そういう人向けに、どうやら2つの解決策があるみたいです。

① Turbolinksが提供するイベントを追加

JSファイルに、Turbolinks専用のイベントturbolinks:loadを追加することでJSを正常に動作させます。

test.js
document.addEventListner('turbolinks:load', function() {
  console.log('Loaded');
});

② 特定のリンクでTurbolinksを無効化

aタグ(link_to)にdata: {"turbolinks" => false}を加えることによって、
Turbolinksを無効化し、遷移先に飛ぶようにすることでJSを正常に動作させます。

test.html.slim
=link_to "テスト", tests_path, data: {"turbolinks" => false}

最後に

ポートフォリオ作成をきっかけにたまたまTurbolinksを調べることになったわけですが、
調べれば調べる程、まだまだ奥が深そうな感じがしました。
今回は、基本の部分で留めておこうと思いますが、また機会があれば調べてみたいところです。

尚、本記事の内容で間違っている点や不足している点などがありましたら、ご教示いただけると幸いです。

参考:Railsガイド Turbolinks動作原理
   Ruby on Rails 速習ガイド p.340-p.344

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

Turbolinks : link_toで遷移した時にリロードしないとJSが上手く表示されない問題を解決したい

目次

  1. はじめに
  2. JSの表示を邪魔するのは・・・
  3. Turbolinksって何者?
  4. 解決策
  5. 最後に

はじめに

現在、ポートフォリオを作成中であり、
その中でJavaScriptを用いたカレンダー機能を実装していたのですが、

link_toからカレンダーページに遷移した際に、
1度リロードしないとJavaScriptが適用されない問題が生じた
ので、

この辺の原因を明確にして、記録を残そうということでこの記事を執筆しています。
本記事は、プログラミング初心者によるものであることをご了承ください。

JSの表示を邪魔するのは・・・

ずばり、Turbolinksです。
コイツのせいで、頑張って書いたJSが遷移先で表示されないのです。

このように考えると何だか憎たらしいヤツに思えてきちゃいますが、
色々調べていると、実は良いヤツらしい。

良いヤツなんだけど、たまに余計なお節介をして、
うっかり我々の大事な大事なJSを隠してしまうようです。

Turbolinks、意外と憎めないやつかもしれない。
ってことでもっと知りたくなってきましたね(無理矢理)。

てなわけでもう少し詳しく見ていきます。

Turbolinksって何者?

Turbolinksとは、リンククリックに対して、ページ遷移を自動的にAjax化してくれるライブラリです。

①aタグをクリックすると、遷移先のページをAjaxで取得する

②その取得したページが要求するCSSやJSが、現在のページのものと同じである場合、titleやbodyのみ(画面の一部だけ)を置き換える

というのがTurbolinksの主な仕組みです。

Turbolinks導入のメリット

Turbolinks導入の最大のメリットは、画面表示を高速化してくれることです。

通常であれば、リンクをクリックすると、指定されたページに遷移するため、その分時間がかかりますが、Turbolinksを導入している場合、ページの遷移は行われず、Ajaxにより画面の一部だけを更新する仕組みになっているので、結果的に読み込みに時間がかからず、ページの表示が高速化するというわけです。

サイトを見ている側からすると、少しでも待たされる時間をなくしたいわけですから、
TurbolinksはUXを向上させる救世主って感じですね。

何だ、やっぱり良いヤツじゃん:laughing:
最初から導入されているってことは、それなりに便利で使えるからなんですよね。

とは言いながらも、どんなに良いヤツでも、時と場合によっては煩わしく感じてしまうものです。

Turbolinks導入のデメリット

Turbolinks導入のデメリットは、JSやCSSの適切な表示を妨げてしまう場合があることです。

メリットのところで、Turbolinksは画面の一部だけを更新させると説明しましたが、
この仕組みこそが、JavaScriptが上手く反映しない問題を生じさせていると言えます。
上記で、Turbolinksを用いると、ページの遷移は行われず、Ajaxにより画面の一部だけを更新する事になると述べました。

ブラウザのページ遷移が行われていないので、JavaScriptのイベントが発火せず、結果的にJavaScriptが上手く反映しないというわけです。

これが噂のTurbolinksの余計なお節介ってやつか。なるほど。

解決策

Turbolinksを導入しつつ、必要なところではそれを無効化し、
何とかJavaScriptの動作を上手く反映させたい。

そういう人向けに、どうやら2つの解決策があるみたいです。

① Turbolinksが提供するイベントを追加

JSファイルに、Turbolinks専用のイベントturbolinks:loadを追加することでJSを正常に動作させます。

test.js
document.addEventListner('turbolinks:load', function() {
  console.log('Loaded');
});

② 特定のリンクでTurbolinksを無効化

aタグ(link_to)にdata: {"turbolinks" => false}を加えることによって、
Turbolinksを無効化し、遷移先に飛ぶようにすることでJSを正常に動作させます。

test.html.slim
=link_to "テスト", tests_path, data: {"turbolinks" => false}

最後に

ポートフォリオ作成をきっかけにたまたまTurbolinksを調べることになったわけですが、
調べれば調べる程、まだまだ奥が深そうな感じがしました。
今回は、基本の部分で留めておこうと思いますが、また機会があれば調べてみたいところです。

尚、本記事の内容で間違っている点や不足している点などがありましたら、ご教示いただけると幸いです。

参考:Railsガイド Turbolinks動作原理
   Ruby on Rails 速習ガイド p.340-p.344

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

ruby Hash#==の挙動について

ふとrubyにてHash同士の比較についてどこまで比較してるのか?と気になったので検証してみた。

結果としては公式のドキュメントに記載があるが
自身と other が同じ数のキーを保持し、キーが eql? メソッドで比較して全て等しく、値が == メソッドで比較して全て等しい場合に真を返します。

参考:instance method Hash#==

という動作をするようだ、検証してみる。

require 'securerandom'

keys   = Array.new
values = Array.new

# 1000個のテストデータを作成
1000.times do
  keys << SecureRandom.alphanumeric
end

1000.times do
  values << SecureRandom.base64(100)
end

# valueの中身がばらばらの比較
a = Hash.new
b = Hash.new
keys.each do |key|
  a[key] = values.sample
  b[key] = values.sample
end

puts a == b ? "match!!" : "unmatch..."

# valueの中身を同一にした比較
a = Hash.new
b = Hash.new
keys.each_with_index do |key, i|
  a[key] = values[i]
  b[key] = values[i]
end

puts a == b ? "match!!" : "unmatch..."

結果

$ >> ruby test.rb
unmatch...
match!!

ちゃんと比較してる模様、ちゃんと公式に書いてある通りだった。

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

Ruby on Rails 環境構築(Windows10)で苦労したこと(SQLite3)

Railsのインストールがやっとできました。
苦労した点1つめをまとめます。

RubyとRailsをインストール後
Railsアプリを新規作成したところ、こんな感じのエラーがたくさんでました。

エラー: mingw32: キー "AD351C50AE085775EB59333B5F92EFC1A47D45A1" は不明です
・・・
エラー: データベース 'mingw64' は無効です (無効または破損したデータベース (PGP
鍵))

Gemfileで
gem 'sqlite3', '~> 1.4'
となっていたので
いろいろ修正してみたけれどだめ。

そこで
sqlite.dllをRubyのbinへ配置
node.jsのインストール
yarnのインストール
を行い

gem install sqlite3

を実施。それでもエラーが出てしまい・・
エラー内容をよく見てみると、こんな記述が・・

checking for sqlite3.h... no
sqlite3.h is missing. Install SQLite3 from http://www.sqlite.org/ first.

sqlite3.hのチェックで引っかかっている?!

そこで、sqlite3.hをRubyのbinへ置き、引数にsqlite.hの場所、sqlite3.dllの場所を指定してgem install sqlite3を実行。

それでもエラーが出て・・
引数にsqlite3のバージョンまで指定するとうまくいきました。

こんな感じです。

gem install sqlite3 --version 1.3.13 --platform=ruby -- --with-sqlite3-include=C:\Ruby27-x64\bin --with-sqlite3-lib=C:\Ruby27-x64\bin

最後に
Gemfileを
gem 'sqlite3', '~> 1.3.13'
に修正して
bundle install
を実行。
Railsアプリの新規作成を再度行うと無事に作成できました。

環境

Windows10 home

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

Ruby学習6

演習で間違えた問題復習

現在、Ruby技術者認定試験silverを取得するべく勉強中です。
そろそろ試験なので、模擬試験で引っかかった部分を復習します。

定数への破壊的メソッド

HOGE = "hoge"
HOGE.gsub!("hoge", "piyo")
print HOGE

この出力結果に警告が出る(定数の変更が行われる為)と思ってが、違った。
警告文は、定数の「再代入」に対して行われるので、破壊的メソッドの呼び出しには警告なく値を変更できる。

?を用いた文法

p ?A

短いけど、なんだこれな文法。?の後の1文字を文字列で出力する。2文字以上書くとエラー。僕の脳では何に使うか全くわからない。

クラスの継承

class Hoge < Kernel ; end

これを実行するとエラーを吐くんですが、理由は、Kernelがモジュールだから。モジュールを継承するにはincludeを使う。

to_iメソッド

p "123abc".to_i
=> 123

文字列を整数にする、は理解してたものの、整数以外がある場合を把握していなかった。
整数以外が混ざっている場合、整数以外の1文字手前までを整数として出力する。
なので、"123abc123"to_iの場合でも出力するのは123。

演算子||

a = [1,2,3,4]
b = [1,3,5,7]

p a || b

=> [1,2,3,4]

出力が[1,2,3,4,5,7]だと思ったんですが、それは a | b の場合。
a || b の場合、aがtrueである時、bを評価せずにaを吐いて終わり。

正規表現

puts "0123456789-".delete("^13-56-")

=> 13456-

「deleteでマッチするのが1と、3から5と6と-だから、これを消せば良いのね、完全に理解した」←分かってない
正規表現に^が含まれたら「〇〇以外」になるので、上記にマッチした文字列「以外」を削除する。

chrメソッド ordメソッド

p 75.chr
p "K".ord

=> "K"
   75

chrメソッドは整数を文字コードとして文字変換
ordメソッドは逆に文字を文字コード対応の整数に変換

To_Be_Continued...

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

本番環境で画像が表示されない

デプロイ後画像が表示されなくて困ったのでまとめてみました。

どなたかのお役に立てたら嬉しいです。

ruby '2.6.5'
rails '6.0.0'

オリジナルアプリケーションを作っています。

本番環境はHerokuを使用します。
Herokuにデプロイして動作確認したらエラーログの出現しました。

12.png

エラーログから本番環境ですと画像へのパスが変わってしまうのが原因と考えました。

ローカルだとassets/images/直下におけばそのまま表示されますが
本番環境だとプリコンパイルされ表示されない為エラーになりました。

調べると、
productionでは、Railsはプリコンパイルされたファイルをpublic/assetsに置きます。プリコンパイルされたファイルは、Webサーバーによって静的なアセットとして扱われます。
app/assetsに置かれたファイルがそのままの形でproduction環境で使用されることは決してありません。

参考記事はこちら

上記の記事を参考に本番環境のパスを作ります。

_medicine.html.erb
   <%= image_tag asset_path('medicine3.jpeg'), class:"med-pic" %>

このようにしてパス再指定しましたが私の場合はまたエラーになりました。

本番環境でのasset precompileの設定がされていないのではないかとご指摘いただき確認。

変更前

production.rb
     config.assets.compile = false

変更後

production.rb
     config.assets.compile = true

こちらを有効にしてみたら無事画像表示できました。

まとめ

<<初心者向け>>本番環境で画像表示されない場合のやってみて損はない対処法

①本番環境でのasset precompileの設定が有効か確認

29
   config.assets.compile = true

②プリコンパイルでPublic内にAssetsファイルを作成

   % rails assets:precompile

③assets_pathを記述する

   <%= image_tag asset_path('medicine3.jpeg'), class:"med-pic" %>

   

④デプロイ完了して無事、画像表示完了!!

asset precompileのやってることとか軽く調べておくとより良いかもです!

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

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

前提

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

基本方針

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

 
 さてさて第13章。早いもので残り2章となりました。今回はマイクロポストというユーザーのメッセージ投稿機能を実装していきます。要はTwitterよね。気張っていきましょう!!
 
本日のBGMはこちら。
ARIA vocal collection
ネオ・ヴェネツィアでカフェラテ飲みながらリモートワークできる時代が来ねえかなあ…

 

【13.1.1 Micropostモデル 演習】

1. RailsコンソールでMicropost.newを実行し、インスタンスを変数micropostに代入してください。その後、user_idに最初のユーザーのidを、contentに "Lorem ipsum" をそれぞれ代入してみてください。この時点では、 micropostオブジェクトのマジックカラム (created_atとupdated_at) には何が入っているでしょうか?

2. 表 12.1の名前付きルートでは、_pathではなく_urlを使うように記してあります。なぜでしょうか? 考えてみましょう。ヒント: アカウント有効化で行った演習 (11.1.1.1) と同じ理由です。

3. 先ほど作ったmicropostオブジェクトをデータベースに保存してみましょう。この時点でもう一度マジックカラム
→ まとめてドン

>> micropost = Micropost.new(user_id: 1, content: "Lorem ipsum")
=> #<Micropost id: nil, content: "Lorem ipsum", user_id: 1, created_at: nil, updated_at: nil>
>> micropost.created_at
=> nil
>> micropost.updated_at
=> nil

>> micropost.user
  User Load (0.2ms)  SELECT  "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-09-17 08:34:09", updated_at: "2020-09-17 08:34:09", password_digest: "$2a$10$.EZN.AXBx91cG82BFOaKp.qpuwRpmG5N1JASh6KIBnv...", remember_digest: nil, admin: true, activation_digest: "$2a$10$mQpfXtRYM2s5JyNF243gYOln7RRrGaHHlilpOHouLfk...", activated: true, activated_at: "2020-09-17 08:34:09", reset_digest: nil, reset_sent_at: nil>
>> micropost.user.name
=> "Example User"

>> micropost.save
   (0.1ms)  SAVEPOINT active_record_1
  SQL (1.3ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2020-09-21 02:33:40.343372"], ["updated_at", "2020-09-21 02:33:40.343372"]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> true
>> micropost.created_at
=> Mon, 21 Sep 2020 02:33:40 UTC +00:00
>> micropost.updated_at
=> Mon, 21 Sep 2020 02:33:40 UTC +00:00

 

【13.1.2 Micropostのバリデーション 演習】

1. Railsコンソールを開き、user_idとcontentが空になっているmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?
→ 下記

>> micropost = Micropost.new(user_id: " ", content: " ")
=> #<Micropost id: nil, content: " ", user_id: nil, created_at: nil, updated_at: nil>
>> micropost.valid?
=> false
>> micropost.errors.messages
=> {:user=>["must exist"], :user_id=>["can't be blank"], :content=>["can't be blank"]}

 
2. コンソールを開き、今度はuser_idが空でcontentが141文字以上のmicropostオブジェクトを作ってみてください。このオブジェクトに対してvalid?を実行すると、失敗することを確認してみましょう。また、生成されたエラーメッセージにはどんな内容が書かれているでしょうか?
→ 下記

>> micropost = Micropost.new(user_id: " ", content: "a" * 141)
=> #<Micropost id: nil, content: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", user_id: nil, created_at: nil, updated_at: nil>
>> micropost.valid?=> false
>> micropost.errors.messages=> {:user=>["must exist"], :user_id=>["can't be blank"], :content=>["is too long (maximum is 140 characters)"]}

 

【13.1.3 User/Micropostの関連付け メモと演習】

belongs_to: to以下に属する。これを設定した側はメソッド的に使えるようになる。
has_many: 紐付きの親になる側に設定。

1. データベースにいる最初のユーザーを変数userに代入してください。そのuserオブジェクトを使ってmicropost = user.microposts.create(content: "Lorem ipsum")を実行すると、どのような結果が得られるでしょうか?
→ 下記

>> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-09-17 08:34:09", updated_at: "2020-09-17 08:34:09", password_digest: "$2a$10$.EZN.AXBx91cG82BFOaKp.qpuwRpmG5N1JASh6KIBnv...", remember_digest: nil, admin: true, activation_digest: "$2a$10$mQpfXtRYM2s5JyNF243gYOln7RRrGaHHlilpOHouLfk...", activated: true, activated_at: "2020-09-17 08:34:09", reset_digest: nil, reset_sent_at: nil>
>> micropost = user.microposts.create(content: "Lorem ipsum")                       
   (0.1ms)  SAVEPOINT active_record_1
  SQL (1.8ms)  INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["content", "Lorem ipsum"], ["user_id", 1], ["created_at", "2020-09-21 03:35:41.329488"], ["updated_at", "2020-09-21 03:35:41.329488"]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2020-09-21 03:35:41", updated_at: "2020-09-21 03:35:41">

 
2. 先ほどの演習課題で、データベース上に新しいマイクロポストが追加されたはずです。user.microposts.find(micropost.id)を実行して、本当に追加されたのかを確かめてみましょう。また、先ほど実行したmicropost.idの部分をmicropostに変更すると、結果はどうなるでしょうか?
→ 下記

>> user.microposts.find(micropost.id)
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? LIMIT ?  [["user_id", 1], ["id", 1], ["LIMIT", 1]]
=> #<Micropost id: 1, content: "Lorem ipsum", user_id: 1, created_at: "2020-09-21 03:35:41", updated_at: "2020-09-21 03:35:41">
>> user.microposts.find(micropost)
Traceback (most recent call last):
        1: from (irb):6
ArgumentError (You are passing an instance of ActiveRecord::Base to `find`. Please pass the id of the object by calling `.id`.)

 
3. user == micropost.userを実行した結果はどうなるでしょうか? また、user.microposts.first == micropost を実行した結果はどうなるでしょうか? それぞれ確認してみてください。
→ 下記

>> user == micropost.user
=> true
>> user.microposts.first == micropost
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."id" ASC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> true

 

【13.1.4 マイクロポストを改良する メモと演習】

default_scope: あるスコープをモデルのすべてのクエリに適用したい場合に使用。
dependent: :destroy:has_manyに設定。親が削除されたときに、紐づいている子側のデータベースがすべて削除される。
 その他、いろいろ出てきたので用語集にまとめています。

1. Micropost.first.created_atの実行結果と、Micropost.last.created_atの実行結果を比べてみましょう。
→ 適当にmicropostを2つ作ってやってから下記(なんとなくsandbox使ってるので以前の演習データが残ってません。)

>> Micropost.first.created_at
  Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> Mon, 21 Sep 2020 04:56:04 UTC +00:00
>> Micropost.last.created_at
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> Mon, 21 Sep 2020 04:55:35 UTC +00:00

 
2. Micropost.firstを実行したときに発行されるSQL文はどうなっているでしょうか? 同様にして、Micropost.lastの場合はどうなっているでしょうか? ヒント: それぞれをコンソール上で実行したときに表示される文字列が、SQL文になります。
→ 下記

>> Micropost.first
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" DESC LIMIT ?  [["LIMIT", 1]]
=> #<Micropost id: 2, content: "jaoivhiua", user_id: 2, created_at: "2020-09-21 04:56:04", updated_at: "2020-09-21 04:56:04">
>> Micropost.last
  Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" ORDER BY "microposts"."created_at" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Micropost id: 1, content: "krutinb", user_id: 1, created_at: "2020-09-21 04:55:35", updated_at: "2020-09-21 04:55:35">

 
3. データベース上の最初のユーザーを変数userに代入してください。そのuserオブジェクトが最初に投稿したマイクロポストのidはいくつでしょうか? 次に、destroyメソッドを使ってそのuserオブジェクトを削除してみてください。削除すると、そのuserに紐付いていたマイクロポストも削除されていることをMicropost.findで確認してみましょう。
→ 下記

>> user = User.first
  User Load (0.1ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-09-17 08:34:09", updated_at: "2020-09-17 08:34:09", password_digest: "$2a$10$.EZN.AXBx91cG82BFOaKp.qpuwRpmG5N1JASh6KIBnv...", remember_digest: nil, admin: true, activation_digest: "$2a$10$mQpfXtRYM2s5JyNF243gYOln7RRrGaHHlilpOHouLfk...", activated: true, activated_at: "2020-09-17 08:34:09", reset_digest: nil, reset_sent_at: nil>

>> user.microposts.first.id
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["user_id", 1], ["LIMIT", 1]]
=> 1

>> user.destroy
   (0.1ms)  SAVEPOINT active_record_1
  Micropost Load (0.1ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC  [["user_id", 1]]
  SQL (0.1ms)  DELETE FROM "microposts" WHERE "microposts"."id" = ?  [["id", 1]]
  SQL (0.7ms)  DELETE FROM "users" WHERE "users"."id" = ?  [["id", 1]]
   (0.1ms)  RELEASE SAVEPOINT active_record_1
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-09-17 08:34:09", updated_at: "2020-09-17 08:34:09", password_digest: "$2a$10$.EZN.AXBx91cG82BFOaKp.qpuwRpmG5N1JASh6KIBnv...", remember_digest: nil, admin: true, activation_digest: "$2a$10$mQpfXtRYM2s5JyNF243gYOln7RRrGaHHlilpOHouLfk...", activated: true, activated_at: "2020-09-17 08:34:09", reset_digest: nil, reset_sent_at: nil>

>> Micropost.find(1)
  Micropost Load (0.2ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ?  [["id", 1], ["LIMIT", 1]]
Traceback (most recent call last):
        1: from (irb):13
ActiveRecord::RecordNotFound (Couldn't find Micropost with 'id'=1)

 

【13.2.1 マイクロポストの描画 演習】

1. 7.3.3で軽く説明したように、今回ヘルパーメソッドとして使ったtime_ago_in_wordsメソッドは、Railsコンソールのhelperオブジェクトから呼び出すことができます。このhelperオブジェクトのtime_ago_in_wordsメソッドを使って、3.weeks.agoや6.months.agoを実行してみましょう。

2. helper.time_ago_in_words(1.year.ago)と実行すると、どういった結果が返ってくるでしょうか?
→ まとめて下記

>> helper.time_ago_in_words(3.weeks.ago)
=> "21 days"
>> helper.time_ago_in_words(6.months.ago)
=> "6 months"
>> helper.time_ago_in_words(1.year.ago)
=> "about 1 year"

 
3. micropostsオブジェクトのクラスは何でしょうか? ヒント: リスト 13.23内のコードにあるように、まずはpaginateメソッド (引数はpage: nil) でオブジェクトを取得し、その後classメソッドを呼び出してみましょう。
→ 下記

s' for #<Class:0x0000000005968db8>)
>> user = User.first
  User Load (0.2ms)  SELECT  "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2020-09-17 08:34:09", updated_at: "2020-09-17 08:34:09", password_digest: "$2a$10$.EZN.AXBx91cG82BFOaKp.qpuwRpmG5N1JASh6KIBnv...", remember_digest: nil, admin: true, activation_digest: "$2a$10$mQpfXtRYM2s5JyNF243gYOln7RRrGaHHlilpOHouLfk...", activated: true, activated_at: "2020-09-17 08:34:09", reset_digest: nil, reset_sent_at: nil>
>> microposts = user.microposts.paginate(page: nil)
  Micropost Load (0.1ms)  SELECT  "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC LIMIT ? OFFSET ?  [["user_id", 1], ["LIMIT", 11], ["OFFSET", 0]]
=> #<ActiveRecord::AssociationRelation []>
>> microposts.class
=> Micropost::ActiveRecord_AssociationRelation

 

【13.2.2 マイクロポストのサンプル 演習】

1. (1..10).to_a.take(6)というコードの実行結果を推測できますか? 推測した値が合っているかどうか、実際にコンソールを使って確認してみましょう。
→ [1,2,3,4,5,6]という配列ができる。予想どおり。

>> (1..10).to_a.take(6)
=> [1, 2, 3, 4, 5, 6]

 
2. 先ほどの演習にあったto_aメソッドの部分は本当に必要でしょうか? 確かめてみてください。
→ なくてもできるね。

>> (1..10).take(6)
=> [1, 2, 3, 4, 5, 6]

 
3. Fakerはlorem ipsum以外にも、非常に多種多様の事例に対応しています。Fakerのドキュメント (英語) を眺めながら画面に出力する方法を学び、実際に大学名や電話番号、Hipster IpsumやChuck Norris facts (参考: チャック・ノリスの真実) を画面に出力してみましょう。(訳注: もちろん日本語にも対応していて、例えば沖縄らしい用語を出力するfaker-okinawaもあります。ぜひ遊んでみてください。)
→ 下記(そのままfaker-okinawaをbudleするとエラーが出るので、通常のfakergemをコメントアウトしてからbundleしましょう。)

>> Faker::University.name
=> "East Abshire University"
>> Faker::PhoneNumber.cell_phone
=> "1-315-982-9239"
>> Faker::Hipster.sentence
=> "Mumblecore pug tilde marfa drinking 8-bit."
>> Faker::ChuckNorris.fact
=> "Chuck Norris can binary search unsorted data."

>> Faker::Okinawa::Awamori.name
=> "瑞穂"

 

【13.2.3 プロフィール画面のマイクロポストをテストする メモと演習】

再び登場response.body。完全なHTMLが含まれる。assert_match でそのHTMLから該当の要素を探し出す。assert_selectよりも抽象的なメソッド。
'h1>img.gravatar'⇨h1タグの内側にあるgravatarクラス付きのimgタグ というネスト。

1. リスト 13.28にある2つの'h1'のテストが正しいか確かめるため、該当するアプリケーション側のコードをコメントアウトしてみましょう。テストが green から redに変わることを確認してみてください。
→ showビューのh1の中身をコメントアウトしよう。

show.thml.erb
      <h1>
        <%#= gravatar_for @user %>
        <%#= @user.name %>
      </h1>

 
2. リスト 13.28にあるテストを変更して、will_paginateが1度のみ表示されていることをテストしてみましょう。ヒント: 表 5.2を参考にしてください。
→ count; 1 を追加するだけ。

users_profile_test.rb
  test "profile display" do
    get user_path(@user)
    assert_template 'users/show'
    assert_select 'title', full_title(@user.name)
    assert_select 'h1', text: @user.name
    assert_select 'h1>img.gravatar'
    assert_match @user.microposts.count.to_s, response.body
    assert_select 'div.pagination', count: 1
    @user.microposts.paginate(page: 1).each do |micropost|
      assert_match micropost.content, response.body
    end
  end

 

【13.3.1 マイクロポストのアクセス制御 演習】

1. なぜUsersコントローラ内にあるlogged_in_userフィルターを残したままにするとマズイのでしょうか? 考えてみてください。
→ 重複をなくすためですね。DRYの原則に反する。

 

【13.3.2 マイクロポストを作成する 演習】

1. Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。
→ こんなかんじでしょうか。

home.thml.erb
<% if logged_in? %>
  <%= render 'logged_in' %>
<% else %>
  <%= render 'not_logged_in' %>
<% end %>
_logged_in.html.erb
  <div class="row">
    <aside class="col-md-4">
      <section class="user_info">
        <%= render 'shared/user_info' %>
      </section>
      <section class="micropost_form">
        <%= render 'shared/micropost_form' %>
      </section>
    </aside>
  </div>
_not_logged_in.html.erb
  <div class="center jumbotron">
    <h1>Welcome to the Sample App</h1>

    <h2>
      This is the home page for the
      <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
      sample application.
    </h2>

    <%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
  </div>

  <%= link_to image_tag("rails.png", alt: "Rails logo"),
            'http://rubyonrails.org/' %>

 

【13.3.3 フィードの原型 演習】

1. 新しく実装したマイクロポストの投稿フォームを使って、実際にマイクロポストを投稿してみましょう。Railsサーバーのログ内にあるINSERT文では、どういった内容をデータベースに送っているでしょうか? 確認してみてください。
→ INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?) [["content", "good morning."], ["user_id", 1], ["created_at", "2020-09-22 01:20:32.232820"], ["updated_at", "2020-09-22 01:20:32.232820"]]

 
2. コンソールを開き、user変数にデータベース上の最初のユーザーを代入してみましょう。その後、Micropost.where("user_id = ?", user.id)とuser.microposts、そしてuser.feedをそれぞれ実行してみて、実行結果がすべて同じであることを確認してみてください。ヒント: ==で比較すると結果が同じかどうか簡単に判別できます。
→ 全部一緒ですね。

>> Micropost.where("user_id = ?", user.id) == user.microposts
  Micropost Load (0.9ms)  SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? ORDER BY "microposts"."created_at" DESC  [["user_id", 1]]
  Micropost Load (0.2ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true
>> Micropost.where("user_id = ?", user.id) == user.feed
=> true
>> user.microposts == user.feed
  Micropost Load (0.3ms)  SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true

 

【13.3.4 マイクロポストを削除する メモと演習】

request.referrerメソッド: 一つ前のURLを返す。

1. マイクロポストを作成し、その後、作成したマイクロポストを削除してみましょう。次に、Railsサーバーのログを見てみて、DELETE文の内容を確認してみてください。
→ DELETE FROM "microposts" WHERE "microposts"."id" = ? ["id", 302] commit transaction

 
2. redirect_to request.referrer || root_urlの行をredirect_back(fallback_location: root_url)と置き換えてもうまく動くことを、ブラウザを使って確認してみましょう (このメソッドはRails 5から新たに導入されました)。
→ 動きます。

 

【13.3.5 フィード画面のマイクロポストをテストする 演習】

1. リスト 13.55で示した4つのコメント (「無効な送信」など) のそれぞれに対して、テストが正しく動いているか確認してみましょう。具体的には、対応するアプリケーション側のコードをコメントアウトし、テストが redになることを確認し、元に戻すと greenになることを確認してみましょう。
→ Yes, GREEN.

 
2. サイドバーにあるマイクロポストの合計投稿数をテストしてみましょう。このとき、単数形 (micropost) と複数形 (microposts) が正しく表示されているかどうかもテストしてください。ヒント: リスト 13.57を参考にしてみてください。
→ これでよかろうか。

microposts_interface_test.rb
  test "micropost sidebar count" do
    log_in_as(@user)
    get root_path
    assert_match "#{@user.microposts.count} microposts", response.body
    other_user = users(:malory)
    log_in_as(other_user)
    get root_path
    assert_match "0 microposts", response.body
    other_user.microposts.create!(content: "A micropost")
    get root_path
    assert_match "1 micropost", response.body
  end

 

【13.4.1 基本的な画像アップロード 演習】

1. 画像付きのマイクロポストを投稿してみましょう。もしかして、大きすぎる画像が表示されてしまいましたか? (心配しないでください、次の13.4.3でこの問題を直します)。
→ まあまあでした。

 
2. リスト 13.63に示すテンプレートを参考に、13.4で実装した画像アップローダーをテストしてください。テストの準備として、まずはサンプル画像をfixtureディレクトリに追加してください (コマンド例: cp app/assets/images/rails.png test/fixtures/)。リスト 13.63で追加したテストでは、Homeページにあるファイルアップロードと、投稿に成功した時に画像が表示されているかどうかをチェックしています。なお、テスト内にあるfixture_file_uploadというメソッドは、fixtureで定義されたファイルをアップロードする特別なメソッドです18 。ヒント: picture属性が有効かどうかを確かめるときは、11.3.3で紹介したassignsメソッドを使ってください。このメソッドを使うと、投稿に成功した後にcreateアクション内のマイクロポストにアクセスするようになります。
→ 下記。ここでテストがGREENにならない。間違ってそうな箇所が見当たらないので、調べたら解決。こちらの記事を参考にspringを再起動したらGREENになりました。

microposts_interface_test.rb
  test "micropost interface" do
    log_in_as(@user)
    get root_path
    assert_select 'div.pagination'
    assert_select 'input[type="file"]'
    # 無効な送信
    post microposts_path, params: { micropost: { content: "" } }
    assert_select 'div#error_explanation'
    # 有効な送信
    content = "This micropost really ties the room together"
    picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
    assert_difference 'Micropost.count', 1 do
      post microposts_path, params: { micropost: { 
                                      content: content,
                                      picture: picture } }
    end
    assert assigns(:micropost).picture?
    follow_redirect!
    assert_match content, response.body
    # 投稿を削除する
    assert_select 'a', text: 'delete'
    first_micropost = @user.microposts.paginate(page: 1).first
    assert_difference 'Micropost.count', -1 do
      delete micropost_path(first_micropost)
    end
    # 違うユーザーのプロフィールにアクセス (削除リンクがないことを確認)
    get user_path(users(:archer))
    assert_select 'a', text: 'delete', count: 0
  end

 

【13.4.2 画像の検証 演習】

1. 5MB以上の画像ファイルを送信しようとした場合、どうなりますか?

2. 無効な拡張子のファイルを送信しようとした場合、どうなりますか?
→ 両方とも、該当するファイルがあらへんよ…。多分エラーが出るでしょう。

 

【13.4.3 画像のリサイズ 演習】

1. 解像度の高い画像をアップロードし、リサイズされているかどうか確認してみましょう。画像が長方形だった場合、リサイズはうまく行われているでしょうか?
→ OK

 
2. 既にリスト 13.63のテストを追加していた場合、この時点でテストスイートを走らせると紛らわしいエラーメッセージが表示されることがあります。このエラーを取り除いてみましょう。ヒント: リスト 13.68にある設定ファイルを修正し、テスト時はCarrierWaveに画像のリサイズをさせないようにしてみましょう。
→ エラー吐いてなかったけど一応やっときました。

 

【13.4.4 本番環境での画像アップロード 演習】

1. 本番環境で解像度の高い画像をアップロードし、適切にリサイズされているか確認してみましょう。長方形の画像であっても、適切にリサイズされていますか?
→ よっしゃ!いけた!

 

第13章まとめ

・マイクロポストもリソース化。
・belongs_toとhas_manyでデータテーブル同士を紐付け。子側がメソッド的に使えるようになる。
・デフォルトスコープで表示順序を変更。使い方は気をつけた方がいいっぽい記事が調べると出てきた。
・dependent: :destroyオプションを使うと、関連付けされたオブジェクトと自分自身を同時に削除する。
・ActiveRecordの使用により、生のSQLを使うことはほとんどない。
・CarrierWaveで画像アップロードを実装。(今は標準がActive Storage?)

 
 マイクロポスト実装終わり〜〜。ここはけっこう分量ありました。特にテスト、やってること自体は復習が多いけど、量がありましたね。これでsample_appもそれらしくなってきました。
 さあいよいよラスト、第14章に入ります。ユーザーフォローからステータスフィードの実装まで、ラストスパートです!

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

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

・Proc
 ブロック( { }とかdo ~ end)をオブジェクト化したもの。ブロックはオブジェクトではないので、procでオブジェクト化する必要がある。こちらを参照

・scope(メソッド)
特定のクエリをまとめたもの。何回も使用するものをまとめてしまって、繰り返し長ったらしいコードを書くことを避ける。

・ラムダ式
 無名関数。その名のとおり、名前付けされていない関数のこと。

・SQLインジェクション
 アプリケーションのセキュリティ上の不備を意図的に利用し、アプリケーションが想定しないSQL文を実行させることにより、データベースシステムを不正に操作する攻撃方法のこと。また、その攻撃を可能とする脆弱性のこと。

・MIME(Multipurpose Internet Mail Extension(多目的インターネットメール拡張))
 規格上US-ASCIIのテキストしか使用できないインターネットの電子メールでさまざまなフォーマット(書式)を扱えるようにする規格。

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

【Ruby】エラー文に出てくる例外クラスまとめ【NameErrorとかの意味 】

エラー名 エラー内容
NameError 未定義のローカル変数/定数(又はprivateメソッド)とかを呼び出しちゃってる
NoMethodError 存在しないメソッドを呼び出しちゃった時に発生
TypeError 期待してたのと違う型(クラス)がメソッドの引数になってるよ!
ArgumentError 引数の数・型が違う
ZeroDivisionError 整数を0で割り算しようとしちゃった
SystemStackError システムスタックがあふれちゃてる。間違ってメソッド再帰呼び出しした場合など
LoadError requireやloadの実行に失敗したときに発生
SyntaxError 構文エラー。 endない時とか
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テーブルのデータ型とコマンドについての備忘録(Rails)

はじめに

テーブルを作る際、この場合データ型なんだったっけ?コマンドなんだったっけ?と調べ直してしまうことがままあるのでまとめることにしました。

データ型

データ型 種類
integer 数値(整数)
decimal 数値(精度の高い小数)
float 数値(浮動小数)
string 文字(短い文字列)
text 文字(長い文字列
date 日付
datetime 日時
time 時刻
timestamp タイムスタンプ
binary バイナリ
boolean 真偽

テーブル、カラム作成の際に使うコマンド

テーブル、カラムを作る時

$ rails g model モデル名 カラム名:データ型

使用例:Userモデルの作成と名前(name)と自己紹介文(introduction)のためのカラムを作成したいとき

$ rails g model User name:string introduction:text

テーブルを削除

$ rails d model モデル名

使用例

$ rails d model User

既存のテーブルに対しカラムの追加/削除するコマンド

カラムの追加

$ rails g migration Addカラム名Toテーブル名 カラム名:型名

使用例:Userテーブルにtitleカラムを追加したいとき

$ rails g migration AddTitleToUsers title:string

カラムの削除

$ rails g migration Removeカラム名Fromテーブル名 カラム名:型名

使用例:Userテーブルにtitleカラムを削除したいとき

$ rails g migration RemoveTitleFromUsers title:string

マイグレーション実行

作成、変更、削除が出来たらDBには反映させるため、db:migrateコマンドを実行する。

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

投稿に対していいね機能を実装する

概要

今回、twitterのような投稿アプリに対して
いいねを押したり、取り消したりできる機能と、
いいねをカウントして件数を表示できる機能と、
いいねするリンクをハートにする実装についてまとめておきます。

ちなみに、投稿にいいねをする機能に関しては、progateにカリキュラムがありますが、自分はそのまま実装しても思うような機能にならなかったので、qiitaをはじめとするサイトをたくさん検索しました。

それも踏まえてまとめようと思います。

機能の見た目

like-qiita.png

実装

likeモデルを用意する

rails g model like

マイグレーションファイルに
t.references, foreign_key: trueとして、user_idとpost_idのカラムを設定します。

models/like.rb
class Like < ApplicationRecord
  validates :user_id, presence: true
  validates :post_id, presence: true
end

ルーティングを設定する

routes.rb
post "likes/:post_id/create" => "likes#create"
post "likes/:post_id/destroy" => "likes#destroy"

これによってrails routesをすると次の画像のようになります。
これは、ビューファイルにリンクを指定するときに必要になるのであとで間違えないようにしましょう。

like-routes.png

コントローラーを作成する

controllers/likes_controller.rb
class LikesController < ApplicationController
  before_action :authenticate_user!



  def create
    @like = Like.new(user_id: current_user.id, post_id: params[:post_id])
    @like.save
    redirect_to "/posts/#{@like.post_id}"
  end

  def destroy
    @like = Like.find_by(user_id: current_user.id, post_id: params[:post_id])
    @like.destroy
    redirect_to("/posts/#{params[:post_id]}")
  end


end

保存や削除をした後にその投稿の詳細ページにリダイレクトされるようになっています。

ビューファイルを用意しよう

views/posts/show.html.erb
<div class="like-btn">
    <% if Like.find_by(user_id: @current_user.id, post_id: @post.id) %>

      <%=link_to("/likes/#{@post.id}/destroy", {method: :post}) do %>
        <span class="fa fa-heart like-btn-unlike"></span>
      <% end %>

    <% else %>

      <%= link_to("/likes/#{@post.id}/create", {method: :post}) do %>
        <span class="fa fa-heart like-btn"></span>
      <% end %>

    <% end %>
  </div>

まずは条件分岐でユーザーがいいねしているかしていないかを分岐し、
ボタンを押したときlikeをcreateするか、destroyするかを指定します。

そして、次に書きますが、link_to ~~~ doとする事で間にHTML文を挟むことができます。(理解が浅ければprogateのrailsカリキュラムを参照)

link_toのURLは間違えないようにしましょう。

先ほどルーティングでのせた画像の通り、/likes/post_id/create(またはdestroy)と記述します。

いいねをハートアイコンのボタンにする

この部分はprogateを参考にしたのでこれ以外知らないんですけど、
font-awesomeというものを使って、いいねするリンクをボタンにしていきます。

まずはfont-awesomeを使えるようにhead部分にリンクを読み込みます。

views/layouts/application.html.erb
 ---省略---

  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
 </head>

ビューファイルはもう適した形式で記述しているので、
最後にいいねしていたらピンク、していなかったらグレーとなるようにcssを記述します。

先ほど記述したviews/posts/show.html.erbを確認すればわかりますが、いいねするボタンと取り消すボタンでlike-btnとlike-btn-unlikeが分かれています。

assets/stylesheets/likes.scss
.like-btn {
  color: #8899a6;
}


.like-btn-unlike {
  color: #ff2581;
}


.posts-show-item .fa {
  font-size: 16px;
  margin-right: 3px;
}

これでハートボタンでいいねしたり取り消したりすることが可能になっているはずです。

最後に、いいねの件数をカウントして表示しましょう。

件数をカウントして表示する

postsコントローラーのshowメソッドに@like_countを定義してあげて、ビューファイルで表示するような手順になります。

controllers/posts_controller.rb
def show
    @post = Post.find(params[:id])
    @comment = Comment.new
    @comments = @post.comments.includes(:user)
    @like_count = Like.where(post_id: @post.id).count
 end

whereでその投稿についてるいいねをデータベースから検索して、countで数えていくように定義しています。

views/posts/show.html.erb
<div class="like-btn">

  <h3>いいね件数:<%= @like_count %></h3>

    <% if Like.find_by(user_id: @current_user.id, post_id: @post.id) %>
      <%=link_to("/likes/#{@post.id}/destroy", {method: :post}) do %>
---省略---

以上で完成です。

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

SQLite3からPostgreSQLに変更

背景

プロジェクトの作成時に

rails new sample_app -d postgresql

とするところを

rails new sample_app -D postgresql

としていたので、ポスグレが設定されていたなかった。

Gemfileで

# Use postgresql as the database for Active Record
gem 'pg'

となっていてほしいところが、

# Use sqlite3 as the database for Active Record
gem 'sqlite3'

となっていた。

sqlite3ってのがデフォルトのやつなのかな。

SQLite3からPostgreSQLに変更

https://qiita.com/rubys8arks/items/0749d6fa73e88d3d381c
これを参考に変更していこう。

ポスグレ自体はインストールされているので、
Gemfileのsqlite3のところを

# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'

に変更して、bundle installする。

この状態だと、rails db:createできない

bin/rails db:create
rails aborted!
LoadError: Error loading the 'sqlite3' Active Record adapter. Missing a gem it depends on? sqlite3 is not part of the bundle. Add it to your Gemfile.

database.ymlの編集

before

# SQLite version 3.x
#   gem install sqlite3
#
#   Ensure the SQLite 3 gem is defined in your Gemfile
#   gem 'sqlite3'
#
default: &default
  adapter: sqlite3
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000

development:
  <<: *default
  database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: db/test.sqlite3

production:
  <<: *default
  database: db/production.sqlite3

after

default: &default
  adapter: postgresql
  encoding: unicode
  # For details on connection pooling, see Rails configuration guide
  # https://guides.rubyonrails.org/configuring.html#database-pooling
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development:
  <<: *default
  database: sample_app_development

test:
  <<: *default
  database: sample_app_test

production:
  <<: *default
  database: sample_app_production
  username: sample_app
  password: <%= ENV['SAMPLE_APP_DATABASE_PASSWORD'] %>

.envにパスワード

ここは省略

データベースの作成

-> % bin/rails db:create
Created database 'sample_app_development'
Created database 'sample_app_test'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails6]devise + paranoia + MySQL8系で実現するユーザー論理削除と一意制約の両立

はじめに

タイトルの環境にて、ユーザーの論理削除および一意制約の両立にかなり苦戦したので
解決方法を軽くまとめます。
deviseの一意制約をオーバーライドして独自の制約を持たせる方法などが出ていましたが
どれも面倒でもっと簡単にできないかと思い調べた結果です。

やりたいこと

先の環境にて(特にMySQL)、deviseデフォルトのユーザー物理削除ではなく論理削除を行い、
かつ退会したユーザーが同じアドレスやIDで再登録できるよう一意制約を担保したい。

PostgreSQL, SQLite

こちら2つのDBでは部分indexを活用することで簡単に実装できるそうです。
・実装記事
・add_index仕様1
・add_index仕様2

この、部分indexをMySQLは採用しておらず簡単に扱えない模様。
(情報が5.7ばかりだから8系では採用しているのか?どちらにしろRailsのmigrateからは無理そう)
軽くアンチMySQLになるかも…

結論(MySQLでの実現方法)

この記事みたらいけると思います。
https://vanhuyz.com/how-to-apply-unique-restriction-with-soft-delete-in-rails/
1. deleted_atカラムを追加後、
2. deleted_atにNullではなくアクティブユーザーにも特定の値をもたせることで、
 2カラムでのUNIQUEを実現し(dleted_atがNULLだと実現しない)
3. シンプルに論理削除と一意制約を両立

最高に優良な記事をどうもありがとう!!

実装

[環境]
- Ruby:2.6.6p146
- Rails:6.0.3.3
- MySQL:8.0.21
- mysql2:0.5.3
- devise:4.7.2
- paranoia:2.4.2

準備

環境構築やdevise, paranoia導入等の基本部分の実装は他に沢山の記事が出ているので割愛します。
devise標準の会員登録・退会機能等が動作する前提です。

1. 設定ファイルparanoia.rbを作る

config/initializers/paranoia.rb
# ユーザー退会後の論理削除・一意制約を両立させるための処理
# 非削除レコードはdeleted_at = '0000-01-01 00:00:00'
# 削除済みレコードはdeleted_at != '0000-01-01 00:00:00'
Paranoia.default_sentinel_value = DateTime.new(0)

2. migrationでのindex変更

デフォルトのUsersテーブルのindexを変更するマイグレーションファイルを作成

rails g Change_Index_To_Users

作成したマイグレーションファイルを以下のように変更

一度デフォルトで作成されたemailインデックスを削除し
新たに[email, deleted_at]でのuniqueインデックスを作成

db/migrate/202009220000000.rb
class ChangeIndexUniuqueToUsers < ActiveRecord::Migration[6.0]
  def change
    remove_index :users, :email
    add_index :users, [:email,:deleted_at], unique: true
  end
end

migration

rails db:migrate

3. Validation追加

アプリケーションレベルでのバリデーションを追加

app/models/user.rb
class User < ActiveRecord::Base
  acts_as_paranoid
  validates :email, uniqueness: { scope: :deleted_at }  

4. 動作確認

railsコンソールもしくはrailsサーバーを立ち上げ実際にユーザー作成、退会、同アドレスでの再登録を実施してDBを確認します。
論理削除_一意制約.PNG

無事同じアドレスでの再登録に成功しました!!
他何かいい方法などあれば是非教えてくださいませ~!

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

Railsで時刻だけを比較する方法(何時〜何時までの間は、みたいなやつ)

問題の背景

深夜営業で23時〜翌5時までは15%増し、みたいなやつをどう書くか?という話。
「23時〜翌5時」という条件を上手く判定したい。

解決方法

Time型で比較しようとすると日付情報まで入ってきて面倒くさいので、strftimeto_iで時刻部分だけ数値化してあげてから比較すれば良い。

def late_time?(time)
  num = time.strftime('%H%M').to_i
  [*0..459, *2300..2359].include?(num)
end

target_time = Time.zone.parse('2020/9/1 23:00')
p late_time?(target_time) #=> true

target_time = Time.zone.parse('2020/9/2 0:00')
p late_time?(target_time) #=> true

target_time = Time.zone.parse('2020/9/2 4:59')
p late_time?(target_time) #=> true

target_time = Time.zone.parse('2020/9/2 5:00')
p late_time?(target_time) #=> false

target_time = Time.zone.parse('2020/9/2 6:00')
p late_time?(target_time) #=> false

target_time = Time.zone.parse('2020/9/2 22:59')
p late_time?(target_time) #=> false
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby学習5

メソッドとか色々5

現在、Ruby技術者認定試験silverを取得するべく勉強中です。
言語に対する理解がまだまだなので、基本的な事からアウトプットしていきます。

小数の切り捨て、切り上げ、四捨五入

ceil = 小数点以下切り上げ

irb(main):001:0> 1.9.ceil
=> 2

floor = 小数点以下切り捨て

irb(main):004:0> 1.9.floor
=> 1

round =小数点以下四捨五入(厳密には最も近い整数を返す)

irb(main):006:0> 1.5.round
=> 2
irb(main):007:0> 1.4.round
=> 1

joinメソッド

配列の要素同士を、引数指定した文字で区切り、結合した文字列を返す。

arr = ["無駄", "無駄", "無駄", "無駄ァ", "ッ!"]
p arr.join("!")

=> "無駄!無駄!無駄!無駄ァ!!"

injectメソッドと条件演算子

模擬問題で詰まったところ。まずはコードから。

numbers = [3,89,40,39,29,10,50,59,69]
num = numbers.inject do |i,j|
  i > j ? i : j
end

injectメソッドは、引数をつけた場合は引数を一つ目のブロック変数(i)に、配列の最初の要素を二つ目のブロック変数(j)に代入。do以降の式を実行。実行が終わったら計算結果をiに、配列の次の要素をjに代入。式を実行。以降繰り返して、配列の要素が最後までいったら結果を返す。引数をつけなかった場合は配列の最初と2番目の要素を代入してから開始する。

条件演算子

 i > j ? i : j

真偽を問う左辺(?から左側)の式を実行後、trueの時に返す値を(:)の左に、falseの時に返す値を(:)の右に置く。なのでこの式は、「iがjより大きい場合はiを、そうでない場合はjを返す」という意味。
で、
これをinjectメソッドで実行するので、「配列の要素を比較して、大きい数値を残し、最終的に配列内で最も大きい数値を返すコード」になる。

stepメソッド

1.step(100, 2) do |n|
  puts n
end

レシーバの数値を起点として、第二引数の数値刻みで、数値が第一引数になるまで、式を実行する。ブロック変数nにはレシーバの数値が初期値として入る。コードを翻訳すると、
「レシーバの数値「1」を出力(puts n)した後、数値に2を足し再度実行(3を出力)。この数値が100に到達するまで実行する。」
今回は奇数が出力されていき、最後は99。2を足すと100を超えてしまうため、ここで終了。やってる事自体は、1から100までで奇数の出力。

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

sum と inject(:+) の結果が違うだと?

まあ知ってる人には驚く話ではないのだけれども。

Ruby で数値の総和を求める

Ruby で数値の配列が与えられていて,その要素の総和を求めたいとき,Array#sum を使うのが鉄則である。

以下のコードを比較してみよう。

numbers = Array.new(1000){ rand }

# (1)
numbers.inject(&:+)

# (2)
numbers.inject(:+)

# (3)
numbers.sum

速度は (1) → (2) → (3) の順に速くなる。
これらの中で (3) は最も高速かつ簡素であり,それだけでも sum を採用すべき理由になる。
しかし,それだけではない。

精度

数値が Integer や Rational の場合はそもそも演算の誤差が生じないのでよいが,数値が Float の場合,演算の誤差を考えなければならない。

浮動小数点数の演算には誤差を伴うことが多い。それ自体は仕方がないのだが,2 回以上演算を繰り返す場合は,誤差の累積を問題にしなければならない。

結論を先に書くと,sum を採用すべき三つ目の理由は,sum には誤差の累積を抑える「Kahan の加算アルゴリズム」が採用されていることである。
参考:カハンの加算アルゴリズム - Wikipedia

誤差の例

Float が表す数について

本題に入る前に,少し予備知識の整理をしておきたい。

2 進法に基づく浮動小数点数で 0.1 という数値が表せないことはよく知られている。簡単に言えば,0.1 という数値は 2 進法では無限小数になるので,それを有限桁で表すからである。

では,Ruby で

puts 0.1

を実行すると端末に 0.1 と表示されるのはどういうことか。

まず Ruby の処理系は 0.1 という浮動小数点数リテラルを見て,Float オブジェクトを生成する。
これは「0.1 に極めて近いが同じではない」浮動小数点数を表わしている。
大雑把に言って,(10 進で)15 桁くらいの精度をもった数値である。いや,実は Ruby の Float オブジェクトがどんなビット列で表されているかは環境依存なのだが,多くの環境で C 言語の double という数値型に基づいているらしく,その場合はそうなる。本記事の内容はすべてこの前提で書かれており,該当しない環境の場合は結果が異なりうる。

0.1 ではない数値を表示させているのになぜ 0.1 と表示されるのか。
それは,puts に Float オブジェクトを与えたとき,まず to_s メソッドによって「10 進法に基づく数字列」という String オブジェクトに変換されるのだが,その結果が "0.1" という文字列だからである。
10 進 → 2 進と違って,2 進 → 10 進の変換だと,有限小数が無限小数になったりはしない1。実際,浮動小数点数リテラル 0.1 が表す Float オブジェクト(が表す数値)は,10 進法で

0.1000000000000000055511151231257827021181583404541015625

という,小数点以下 55 桁の有限小数になるようだ(私がなんかどこかでミスしてなければ)。
しかし,Ruby の Float#to_s メソッドはそんな文字列を返さない。
Float オブジェクトが持ちうる精度に見合った桁数だけを取り,"0.1" という文字列を返すのである。これは to_s の仕様である。
(このあたり,あまり詳しくないので,誤りの指摘や補足があればお願いします)

誤差の累積

では本題である suminject(:+) で結果が異なる例を見よう。

numbers = [0.1] * 6

puts numbers.sum == numbers.inject(:+)
# => false

6 個の 0.1 の総和が一致していない(繰り返すが,環境によっては結果が異なりうる)。
それぞれの結果を puts で表示させてみよう。

numbers = [0.1] * 6

puts numbers.sum        # => 0.6000000000000001
puts numbers.inject(:+) # => 0.6

これを見て,「えっ? sum より inject(:+) のほうが精度が高いじゃん! 嘘つき!」と早合点してはいけない2

既に見たように Ruby の浮動小数点数リテラル 0.1 によって生成される Float オブジェクトが表している数値は 0.1 ではないのである。
ちなみに,inject 版の結果が 0.6 と表示されているが,返り値が 0.6(という数を表す Float オブジェクト)であることを意味しない。あくまで返り値を to_s した結果が "0.6" という文字列だというだけである。

誤差の評価

では,誤差がどうなっているかを正確に知るにはどうすればいいか。
それには Float#to_r を用いて Float オブジェクトを Rational オブジェクトに変換する。
このメソッドは,Float が表している数値に厳密に一致する有理数オブジェクトを返す。

こんな具合である。

puts 0.1.to_r
# => 3602879701896397/36028797018963968

なんかスゴイ分数が表示されたが,何かの間違いではない。
0.1 というリテラルに基づく Float オブジェクトはこのような数値を表しているのである。
一見わけが分からない数字列に見えるが,よくよく見えば分子と分母は末尾以外は数字が一致しており,分子のほうが一桁少ない。つまり,0.1 という数値に非常に近いことが見て取れる。

Rational に持ち込めば加減乗除で演算誤差は生じない。
こんなふうにしよう。
Rational にしてから取った総和を「真の和」と呼ぶことにする。
suminject で作った和と真の和の差の絶対値を「誤差」と呼ぶことにする。
どちらが誤差が小さいか比べるため,sum 版の誤差から inject 版の誤差を引いたものを計算する。これが正なら sum 版の誤差が大きく,負なら inject 版の誤差が大きい。

# 足すべき数
x = 0.1

# 足し合わせる個数
n = 6

# 真の和(Rational)
exact_sum = x.to_r * n

# n 個の x からなる配列
numbers = [x] * n

# sum による和(Rational)
sum_by_sum = numbers.sum.to_r

# その誤差
error_by_sum = (exact_sum - sum_by_sum).abs

# inject による和(Rational)
sum_by_inject = numbers.inject(:+).to_r

# その誤差
error_by_inject = (exact_sum - sum_by_inject).abs

# 比較
puts sum_by_sum - sum_by_inject     # => 1/9007199254740992
puts error_by_sum - error_by_inject # => 0/1

あれれ? sum 版の和と inject 版の和は確かに微妙に違っているのだが,それぞれの誤差は完全に一致している?
一見不可解だが,何も不思議なことはない。「誤差」を計算するときに絶対値を取っているからこうなる。
sum 版と inject 版の和は,真の和の左右に同じ距離だけ離れて存在しているのだ。和そのものの値は違っているが真の和とのズレ量は同じだったということ。

「ん? じゃあ,suminject より精度が高いとか言ってたのは嘘だったのかよ」
話を最後まで聞いてほしい3

今の場合,つまり,0.1(というリテラルによる Float)を 6 個足し合わせた場合は,精度に違いがなかった。
これは inject 版にとって幸運なケースだったということだ。
では,実際に sum 版のほうが真の和に近いケースはあるのか。あるいは逆のケースは無いのか。
以下のようにして調べてみよう。
足すべき数は 0.1(というリテラルによる Float)とし,足し合わせる個数を変えていく。

x = 0.1

1.upto(100) do |n|
  exact_sum = x.to_r * n
  numbers = [x] * n

  sum_by_sum = numbers.sum.to_r
  error_by_sum = (exact_sum - sum_by_sum).abs

  sum_by_inject = numbers.inject(:+).to_r
  error_by_inject = (exact_sum - sum_by_inject).abs

  puts "%4d %2d" % [n, (error_by_sum <=> error_by_inject)]
end

error_by_sum <=> error_by_inject は,もし sum 版の誤差のほうが大きければ 1 を,その逆であれば -1 を,同じ誤差の大きさなら 0 を返すはずである。

結果は以下のようになった。

   1  0
   2  0
   3  0
   4  0
   5  0
   6  0
   7 -1
   8 -1
   9 -1
  10 -1
  11 -1
  12  0
  13  0
  14  0
  15 -1
  16 -1
  17 -1
  18 -1
  19 -1
  20 -1
  21 -1
  22 -1
  23 -1
  24 -1
  25 -1
  26 -1
  27 -1
  28 -1
  29 -1
  30 -1
  31 -1
  32 -1
  33 -1
  34 -1
  35 -1
  36 -1
  37 -1
  38 -1
  39 -1
  40 -1
  41 -1
  42 -1
  43 -1
  44  0
  45  0
  46 -1
  47 -1
  48 -1
  49 -1
  50 -1
  51 -1
  52 -1
  53 -1
  54 -1
  55 -1
  56 -1
  57 -1
  58 -1
  59 -1
  60 -1
  61 -1
  62 -1
  63 -1
  64 -1
  65 -1
  66 -1
  67 -1
  68 -1
  69 -1
  70 -1
  71 -1
  72 -1
  73 -1
  74 -1
  75 -1
  76 -1
  77 -1
  78 -1
  79 -1
  80 -1
  81 -1
  82 -1
  83 -1
  84 -1
  85 -1
  86 -1
  87 -1
  88 -1
  89 -1
  90 -1
  91 -1
  92 -1
  93 -1
  94 -1
  95 -1
  96 -1
  97 -1
  98 -1
  99 -1
 100 -1

見てのとおり,1 個から 100 個までの中に,誤差の大きさが同じだったものが 11 あった。
それ以外はすべて sum 版のほうが誤差が小さい。
結局,この実験の範囲では,

  • sum 版と inject 版の誤差が同じケース → 少数
  • sum 版のほうが誤差が小さいケース → 多数
  • sum 版のほうが誤差が大きいケース → 無し

であった。
しかし,もちろんこんな簡単な実験の結果を一般化するわけにはいかない。今の私にできるのは,Kahan のアルゴリズムを信じることと,上記の実験の範囲内では期待される結果が得られたことを確認することだけである。

ちなみに,sum 版が真の和と一致したものは 7 個あった。このうち,3 個は inject 版も一致していた。

まとめ

総和を得るなら inject(:+) ではなく sum を使おう。
そのほうが簡素で高速。そして,数値が浮動小数点数の場合は誤差の累積も抑えられる。


  1. これが基数 10 が 2 の倍数であることによる。 

  2. 10 個の 0.1 を足し合わせた結果を puts して sum のほうが精度が高いと説明しているサイトがあるが,これも妥当でない。 

  3. 正直に告白すれば,私自身もこの結果を見て,何かの間違いではないかと一瞬思った。 

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

Ruby on Railsにて、Blocked Host:"ホスト名"が出てしまう時の対処法

背景

Ruby on Railsチュートリアルの1章の演習で、ずっと「Blocked Host:自分のホスト名」というエラーが出てしまい、長時間色々なことを試した結果、ある根本的なことをしていなかったことに気づき解決できたため、この投稿をさせてもらいました。

試したこと

Web上でよく書かれていることとしては、
1.environment/development.rbにて、「config.hosts.clear」を入れることです。
しかし、これを試しても、エラーが発生したままでした。
2.environment/development.rbにて「config.hosts<<"自分のホスト名"」を入れることです。
しかし、これを試しても、またしてもエラーが発生したままでした。

原因

(Ⅰ).違うアプリケーションの中の、development.rbに1と2のプログラムを追加していた。
例えば、自分が作っているアプリケーションがhello_appだとすると、違うアプリケーション(例えば、a_app)の中のdevelopment.rbに1と2のプログラムを追加していたということです。
これでは、a_appのホストが許可されただけで、hello_appのホストは許可されません。
これを間違えた理由としましては、階層構造がenvironment/hello_app/config/environment/development.rb、とenvironmentが2つあることにより、同じ環境内であれば、一つのdevelopment.rbを書き直せば、全てに反映されると勘違いしたことによるものでした。
development.rbの階層は上の通りであり、1つのアプリケーションにつき一つのファイルとなります。
決して、共通ではありません。
自分が作成しているアプリケーション内の、config/environment内のdevelopment.rbを1と2のように書き直すようにしましょう。

(Ⅱ).development.rbを書き直した後に、「rails server」を実行しなかった。
順序としては、「development.rb内のプログラムを書き直す→rails serverを実行する」という手順を踏まないと、いくら(Ⅰ)の手順を踏んだとしましても、エラーが発生したままとなります。

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

プロジェクトのrubyとrailsのバージョンアップ

参考

https://qiita.com/_kanacan_/items/c1499f6c13b1c41da982

現状

Gemfile

ruby '2.6.3'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.2.4', '>= 5.2.4.3'

最新調査

リストの中確認,2.7.1が最新?

-> % rbenv install --list
2.5.8
2.6.6
2.7.1
jruby-9.2.12.0
maglev-1.0.0
mruby-2.1.1
rbx-5.0
truffleruby-20.1.0
truffleruby+graalvm-20.1.0

rbenvをアップデート?

-> % brew upgrade rbenv ruby-build
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 4 taps (heroku/brew, homebrew/core, homebrew/cask and homebrew/services).
==> New Formulae

最新版2.7.1だったわ
1個上の手順意味なかったかも
https://www.ruby-lang.org/ja/downloads/
うん、2.7.1はいってるやん。だから、N(->No)。

-> % rbenv install 2.7.1
rbenv: /Users/(username)/.rbenv/versions/2.7.1 already exists
continue with installation? (y/N) N

でも、プロジェクトは2.6.3になってる。。。

-> % rbenv versions
  system
  2.3.7
  2.3.8
  2.5.1
  2.5.3
* 2.6.3 (set by /Users/(username)/projects/import_agent_app/.ruby-version)
  2.6.5
  2.6.6
  2.7.1
-> % git checkout -b feature/version_up
Switched to a new branch 'feature/version_up'
-> % rbenv local 2.7.1 
[feature/version_up *]
-> % rbenv versions
  system
  2.3.7
  2.3.8
  2.5.1
  2.5.3
  2.6.3
  2.6.5
  2.6.6
* 2.7.1 (set by /Users/(username)/projects/import_agent_app/.ruby-version)

-> % bundle install
Traceback (most recent call last):
        2: from /Users/(username)/.rbenv/versions/2.7.1/bin/bundle:23:in `<main>'
        1: from /Users/(username)/.rbenv/versions/2.7.1/lib/ruby/2.7.0/rubygems.rb:294:in `activate_bin_path'
/Users/(username)/.rbenv/versions/2.7.1/lib/ruby/2.7.0/rubygems.rb:275:in `find_spec_for_exe': Could not find 'bundler' (1.17.2) required by your /Users/(username)/projects/import_agent_app/Gemfile.lock. (Gem::GemNotFoundException)
To update to the latest version installed on your system, run `bundle update --bundler`.
To install the missing version, run `gem install bundler:1.17.2`

これでたから、下記のどちらか打った。

bundle update --bundler
gem install bundler:1.17.2

なんか色々やってるうちにこれが邪魔しだした。

-> % bundle install
/Users/(username)/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-1.17.2/lib/bundler/rubygems_integration.rb:200: warning: constant Gem::ConfigMap is deprecated
Your Ruby version is 2.7.1, but your Gemfile specified 2.6.3

いろいろ、調べて参照してるバージョンが違うからみたいな記事が多かったけど、私の場合は、
Gemfileを

ruby '2.7.1'

に書き換えたらいいだけだったぽい。bundle installは通るようになった。

次はRailsのバージョンアップ
これもGemfileを

gem 'rails', '~> 6.0.3', '>= 6.0.3.3'

にしてbudle installしたら行けるでしょ。

-> % bundle install
/Users/(username)/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/bundler-1.17.2/lib/bundler/rubygems_integration.rb:200: warning: constant Gem::ConfigMap is deprecated
The dependency tzinfo-data (>= 0) will be unused by any of the platforms Bundler is installing for. Bundler is installing for ruby but the dependency is only for x86-mingw32, x86-mswin32, x64-mingw32, java. To add those platforms to the bundle, run `bundle lock --add-platform x86-mingw32 x86-mswin32 x64-mingw32 java`.
Fetching gem metadata from https://rubygems.org/............
Fetching gem metadata from https://rubygems.org/.
Resolving dependencies...
Bundler could not find compatible versions for gem "activesupport":
  In snapshot (Gemfile.lock):
    activesupport (= 5.2.4.3)

  In Gemfile:
    rails (~> 6.0.3, >= 6.0.3.3) was resolved to 6.0.3.3, which depends on
      activesupport (= 6.0.3.3)

    web-console (>= 3.3.0) was resolved to 3.7.0, which depends on
      railties (>= 5.0) was resolved to 5.2.4.3, which depends on
        activesupport (= 5.2.4.3)

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.

怒られた。

bundle update

して

bundle install

したら行けた。

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

めっちゃ便利なRubyのStructクラスのお話

きっかけ

とちぎRuby会議09にリモートで参加し、そこで見たLTに今更聞けない! Struct の使い方と今後の可能性についてでRubyのStructクラスについて初めて知った。
なにこれめっちゃ便利じゃん!となったので啓蒙も兼ねて記事を書こう、となったのがきっかけ。
Rubyはかれこれ休み休み10年間は触っているけれど未だに発見がある。素敵!学んでいないだけでは??

Structクラスとは

Ruby 2.7.0 リファレンスマニュアル Structクラス

構造体クラス。Struct.new はこのクラスのサブクラスを新たに生成します。
個々の構造体はサブクラスから Struct.new を使って生成します。個々の構造体サブクラスでは構造体のメンバに対するアクセスメソッドが定義されています。

自分の解釈で説明すると

任意の名前のプロパティ(とメソッド)を持つオブジェクトをお手軽に作成できる便利構造体クラス

使い方

# 4つのパラメーターを持ったStructのサブクラスを生成する
BreakwaterClub = Struct.new(:id, :name, :grade, :age)
# 生成したサブクラスのインスタンスを作成
bucho = BreakwaterClub.new(1, 'kuroiwa', 3)

# 作成したインスタンスはパラメーター名のアクセサメソッドを持っている
p bucho.name
#=> "kuroiwa"
bucho.age = 17

# 初期化時にセットしていなかったageがセットされている
p bucho
#=>#<struct BreakwaterClub id=1, name="kuroiwa", grade=3, age=17>
# keyword_init: true を指定することで初期化時にキーワード引数を渡せるようになる
# キーワード引数のほうがわかりやすいけど文字数は多くなるのでどちらを使うかはお好みで
BreakwaterClub = Struct.new(:id, :name, :grade, :age, keyword_init: true)

hina = BreakwaterClub.new(name: 'tsurugi', grade: 1)

Hashや配列と比べて何が嬉しいの?

Rubyでちょっとした処理を書く時によく使いがちな配列やHash。
まだ頭の中で整理できてない処理とかをアウトプットしながら整理する時とかにも使ったりする。

# 配列
bucho = [1, 'kuroiwa', 3]
bucho[0] #=>1
bucho[1] #=>'kuroiwa'
bucho[2] #=>3

# Hash
bucho = {id: 1, name: 'kuroiwa', grade: 3}
bucho[:id]    #=>1
bucho[:name]  #=>'kuroiwa'
bucho[:grade] #=>3

定義してないパラメーターを指定するとエラーになる

Hashで定義してたりするとTypoしているのにそれに気付かず「なぜ動かない…合っているはずなのに…」とかあるからこれはありがたい。

senpai = BreakwaterClub.new(name: 'ohno', grade: 2)
senpai[:height] #=>NameError (no member 'height' in struct)
senpai.height   #=>NoMethodError
# Hashだと定義してなくても参照できてしまう
senpai[:height] #=>nil

Structクラスは配列、Hashと同じようにアクセス可能で型変換も可能

つまり上位互換って考えていいと思う。

natsumi = BreakwaterClub.new(name: 'hodaka', grade: 1)
# 配列のようにアクセスできる。indexの順番は`Struct.new`で定義した順番になる
natsumi[0] #=>nil
natsumi[1] #=>'hodaka'
# Hashのようにもアクセスできる
natsumi[:id]    #=>nil
natsumi[:grade] #=>1
# 配列にもHashにも変換できる
natsumi.to_a #=>[nil, "hodaka", 1, nil]
natsumi.to_h #=>{:id=>nil, :name=>"hodaka", :grade=>1, :age=>nil}

メソッド定義が可能

Struct.newの際ににブロックを指定することでメソッドを定義可能

BreakwaterClub = Struct.new(:id, :name, :grade, :age, keyword_init: true) do
  def gakunen
    "#{grade}年生"
  end
end
# `Struct.new`で生成したStructクラスを継承したサブクラスを作成することでも可能
Class BreakwaterClub < Struct.new(:id, :name, :grade, :age, keyword_init: true)
  def gakunen
    "#{grade}年生"
  end
end

Structクラス自体を継承したサブクラスを定義することは非推奨らしい。ここまだちょっとよく理解できてない。
継承元となるStructクラスが動的に生成した無名クラスなので不定なことに起因していると思う。
参考:
- 無名クラスから継承すると、何が問題なのか(Ruby)
- irbで2回以上loadすると失敗する
- 以下ドキュメント引用

ブロックを指定した場合

Struct.new にブロックを指定した場合は定義した Struct をコンテキストにブロックを評価します。また、定義した Struct はブロックパラメータにも渡されます。

Customer = Struct.new(:name, :address) do
  def greeting
    "Hello #{name}!"
  end
end
Customer.new("Dave", "123 Main").greeting # => "Hello Dave!"

Structをカスタマイズする場合はこの方法が推奨されます。無名クラスのサブクラスを作成する方法でカスタマイズする場合は無名クラスが使用されなくなってしまうことがあるためです。
[SEE_ALSO] Class.new

まとめ

Structクラスは便利。多種多様な記述方法があるRubyらしいクラスだと思う。
Structクラスまとめ
- 任意のパラメーター、メソッドを定義できる構造体クラス
- 配列やHashのようにアクセスでき、型変換も可能
- 初期化時に指定していないパラメーター名だとエラーになる(Hashだとnilになる)

どういう時に使うと便利?
- 配列やHashですませちゃってるけどClassとして定義したほうがいいよなってとき
- ちょっとコードを書いて検証とかしたいとき
- Classを定義するほど考えがまとまってないとき
- おいそれと叩けないAPIを介したテストをやりたいとき

まだ試してませんが、RailsのRspecでのテスト時などでも使えそうだと感じました。
irbとかでも気軽に試せるので興味が出た人は是非試してみてください。

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

マルチスケールシミュレーション特論

Mac OS X-10.15.6 ruby-2.7.1p83

イントロ

20年秋にさらに驚異的な事態が発生.履修者が80人を超えた.これは,4回生で院への進学が決まっている学生の先取りが可能になったため.今から制限するのも面倒なので,そのままします.

内容は,

  • wsl + emacs + ruby

です.他学科の人がいたらちょっと問題かも.聞いてみよ.

基本方針

  • wsl + emacs + ruby
  • org, qiita, my_help
  • Qiitaでrubyのマニュアルを仕上げる
  • lgtmで評価

  • source ~/Desktop/lecture_20f/grad_lec/./README.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】エラーの日本語表記

目標

日本語.gif

開発環境

ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

前提

※ ▶◯◯ を選択すると、説明等が出てきますので、
  よくわからない場合の参考にしていただければと思います。

【Ruby on Rails】エラーメッセージの個別表示
こちらの記事と対比させながら見るとわかりやすいです。

流れ

1 gemの導入
2 config/application.rbの編集
3 config/locales/ja.ymlの作成、編集

gemの導入

Gemfile
gem 'rails-i18n'
ターミナル
$ bundle install

補足【rails-i18n】
rails g devise:install は deviseの初期設定を行います。

config/application.rbの編集

下記2行を追加。

config/application.rb
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 5.2
    config.i18n.default_locale = :ja # ←追加
    config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # ←追加
    # Settings in config/environments/* take precedence over those specified here.
    # Application configuration can go into files in config/initializers
    # -- all .rb files in that directory are automatically loaded after loading
    # the framework and any gems in your application.
  end

config/locales/ja.yml

config/locales/ja.yml
ja:
  activerecord:
    attributes:
      user:
        email: 'メールアドレス'
        password: 'パスワード'
        password_confirmation: 'パスワード(確認用)'
        name: '名前'

補足【user:】
モデル名となります。同じインデントで記述すれば、
他のモデルの日本語表記も可能です。

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

【Rails】[0日目]分からないところを無視しないオリジナルサービス制作

始めに

初めまして。名古屋在住のしんやです。転職で東京に行きたいです。
未経験からの転職のためにテックアカデミーのWebアプリケーションコースを受講し、2020年9月20日に受講終了しました。

僕としてはスクールのカリキュラムは非常に分かりやすく、サポート体制も整っていたので快適に学習を進めることができました。

唐突に自分の過去ツイートを引用しましたが、このスタンスで進めて受講終了に至りました。

受講終了した今気付いたのが、上記スタンスで進めるのはいいが、絶対に理解してから進めないといけないこともあるという当然のことでした。
簡単に言うと、とりあえずで進めたとこが多数あるが故に、カリキュラムは終えたけど全然理解できてねーなってとこが多数あるってことです。

もちろんスクールの受講を終了した程度で全部理解できるとは思っていなかったですが、悔しいです。

今後の内容

悔しがっていてもしょうがないので、復習を兼ねたアウトプットをここでします。
一番重要視するのは、「分からないところを無視しない」という点です。

ただただ自分のためのアウトプットであり、カリキュラムがあるわけではありません。
分からないところは徹底的に調べ、調べた内容はここにアウトプットします。

自分だけのメモでやってろって話かもしれませんが、自分の認識などに誤りがある場合、ここでアウトプットすることで指摘をいただけるという可能性があります。
淡い期待です。

最後に

現状、オリジナルサービスのワイヤーフレーム、サイトマップ、ER図の作成中です。
次回投稿を1回目とし、ここから進めていこうと思っています。

よろしくお願いします!

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

【Rails】[0日目]分からないところを無視せずオリジナルサービス制作

始めに

初めまして。名古屋在住のしんやです。転職で東京に行きたいです。
未経験からの転職のためにテックアカデミーのWebアプリケーションコースを受講し、2020年9月20日に受講終了しました。

僕としてはスクールのカリキュラムは非常に分かりやすく、サポート体制も整っていたので快適に学習を進めることができました。

唐突に自分の過去ツイートを引用しましたが、このスタンスで進めて受講終了に至りました。

受講終了した今気付いたのが、上記スタンスで進めるのはいいが、絶対に理解してから進めないといけないこともあるという当然のことでした。
簡単に言うと、とりあえずで進めたとこが多数あるが故に、カリキュラムは終えたけど全然理解できてねーなってとこが多数あるってことです。

もちろんスクールの受講を終了した程度で全部理解できるとは思っていなかったですが、悔しいです。

今後の内容

悔しがっていてもしょうがないので、復習を兼ねたアウトプットをここでします。
一番重要視するのは、「分からないところを無視しない」という点です。

ただただ自分のためのアウトプットであり、カリキュラムがあるわけではありません。
分からないところは徹底的に調べ、調べた内容はここにアウトプットします。

自分だけのメモでやってろって話かもしれませんが、自分の認識などに誤りがある場合、ここでアウトプットすることで指摘をいただけるという可能性があります。
淡い期待です。

最後に

現状、オリジナルサービスのワイヤーフレーム、サイトマップ、ER図の作成中です。
次回投稿を1回目とし、ここから進めていこうと思っています。

よろしくお願いします!

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

【Rails】[第0回]分からないところを無視しないオリジナルサービス制作

始めに

初めまして。名古屋在住のしんやです。転職で東京に行きたいです。
未経験からの転職のためにテックアカデミーのWebアプリケーションコースを受講し、2020年9月20日に受講終了しました。

僕としてはスクールのカリキュラムは非常に分かりやすく、サポート体制も整っていたので快適に学習を進めることができました。

唐突に自分の過去ツイートを引用しましたが、このスタンスで進めて受講終了に至りました。

受講終了した今気付いたのが、上記スタンスで進めるのはいいが、絶対に理解してから進めないといけないこともあるという当然のことでした。
簡単に言うと、とりあえずで進めたとこが多数あるが故に、カリキュラムは終えたけど全然理解できてねーなってとこが多数あるってことです。

もちろんスクールの受講を終了した程度で全部理解できるとは思っていなかったですが、悔しいです。

今後の内容

悔しがっていてもしょうがないので、復習を兼ねたアウトプットをここでします。
一番重要視するのは、「分からないところを無視しない」という点です。

ただただ自分のためのアウトプットであり、カリキュラムがあるわけではありません。
分からないところは徹底的に調べ、調べた内容はここにアウトプットします。

自分だけのメモでやってろって話かもしれませんが、自分の認識などに誤りがある場合、ここでアウトプットすることで指摘をいただけるという可能性があります。
淡い期待です。

最後に

現状、オリジナルサービスのワイヤーフレーム、サイトマップ、ER図の作成中です。
次回投稿を第1回とし、ここから進めていこうと思っています。

よろしくお願いします!

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

Rails 6で認証認可入り掲示板APIを構築する #17 管理者権限の追加

Rails 6で認証認可入り掲示板APIを構築する #16 policyの設定

管理者権限を用意する

前回までの実装で、投稿の編集や削除は投稿者本人だけできるようになりました。
ですがそこに拡張し、管理者としてログインしている時は誰の投稿でも編集・削除できるようにしてみます。

そのためにはuserモデルにadminカラムを追加しましょう。

$ rails g migration AddAdminToUsers admin:boolean
db/migrate/xxxxxxxxxxxxxx_add_admin_to_users.rb
# frozen_string_literal: true

class AddAdminToUsers < ActiveRecord::Migration[6.0]
  def change
    add_column :users, :admin, :boolean, default: false, null: false
  end
end

さて、今までは機能を実装してからテストを書いていましたが、punditの基本挙動は理解できたと思うので、先にテストから書いてみます。

管理者権限のない別ユーザーでログインしていた際に編集削除できないテストはすでに書かれているので、

  • 管理者権限のある際に別ユーザーの投稿を編集できること
  • 管理者権限のある際に別ユーザーの投稿を削除できること

を実装してみます。
厳密にはpolicyテストとrequestテスト両方実装すると万全ですが、ここはpolicyだけ実装してみます。

もし良ければここから書いてあるサンプルを見ずに、まずは自力で実装してみて見比べてみると、コードを書く練習になるのでやってみてください。

post_policy_spec.rbの編集

spec/policies/post_policy_spec.rb
...
 RSpec.describe PostPolicy, type: :policy do
   let(:user) { create(:user) }
+  let(:admin_user) { create(:user, admin: true) }
   let(:post) { create(:post) }

...
     it "ログインしているが別ユーザーの時に不許可" do
       expect(subject).not_to permit(user, post)
     end
+    it "adminユーザーでログインしている時に許可" do
+      expect(subject).to permit(admin_user, post)
+    end
...
spec/factories/posts.rb
...
     remember_created_at { nil }
     name { "MyString" }
     tokens { nil }
+    admin { false }
   end
...

ここまで実装したらrubocopとrspecを動かして確認。
まだpolicyファイルを実装していないのでテストはコケます。

policyの実装

まずは管理者であることを判定するprivateメソッドをapplication_policy.rbに生やします。

app/policies/application_policy.rb
...
   private

   def mine?
     @record.user == @user
   end
+
+  def admin?
+    @user.present? && @user.admin?
+  end
+

あとはpost_policy.rbのupdate?destroy?の2つの判定を変えるだけですね。

app/policies/post_policy.rb
   def update?
-    mine?
+    mine? || admin?
   end

   def destroy?
-    mine?
+    mine? || admin?
   end

なんとこれだけです。

traitを使ってadminを簡単に作る

これだけだと少し中身の薄い記事なので、factoryBotを触ってadminユーザーをもっと簡単に作れるようにします。

spec/factories/users.rb
     name { "MyString" }
     tokens { nil }
     admin { false }
+
+    trait :admin do
+      admin { true }
+    end
   end
spec/policies/post_policy_spec.rb
 RSpec.describe PostPolicy, type: :policy do
   let(:user) { create(:user) }
-  let(:admin_user) { create(:user, admin: true) }
+  let(:admin_user) { create(:user, :admin) }
   let(:post) { create(:post) }

これでrspecを動かしてみるとどうでしょうか。
ミスなく書けていればテスト通過するはずです。

traitはご覧の通り、factoryBotのcreate等の第2引数に別名として渡すことで使うことができます。
adminフラグを立てるだけなら恩恵も少なく意味もあまりないのですが、例えばadminユーザーの場合に必ずセットで初期値を持っておきたいカラムがあったりすれば、いちいちcreateのたびに複数カラムの初期値セットをしなくて済みます。

ぜひご活用ください。

続き


連載目次へ

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

Ruby による2021年の週カレンダー(システム手帳用リフィル)の作成

目的:

週カレンダーの作成です。

背景:

システム手帳のリフィルのサイズ(82mmW x 140mmL)が特殊で、使い始めたものの、入手困難であったため、いっそ手作りしてしまおうと考えたのは過去のことです。その際、リフィル用紙を特注で作ったことがありまして、その時の白紙が余っているので、来年は、久しぶりに手作りしようと考えたこと。
年に一度のことですので、手作りでも良いのですが、せっかくのプログラミング環境があるので、毎年、自動でカレンダーが出力できるような仕組みを作成してみようじゃあないか。という理由で、プログラミングしました。

印刷:

ワープロソフトの「差し込み印刷」の機能を用います。従って、差し込み印刷用のカレンダーデータを作れば良い。「差し込み印刷」するテンプレートは、下図のような、見開きで1週間表示となるテンプレートとしました。能率手帳のように片側に1週間を詰め込むというテンプレートでも同様です。
L_andR_.png
データ:1週間が1レコードになりますので、下図のようなカレンダーデータベースができれば、差し込み印刷に使用できます。この表を、ruby で作ってみました。
weekly.png

ruby のコードは、下記になります。

weekly_calendar.rb
# weekly

require 'date'
day = Date.new(2020,12,28)  # 月曜日始まりの12月最終月日
last = Date.new(day.year+1, day.month, day.mday)
weekdays = {0 => '日', 1 => '月', 2 => '火', 3 => '水', 4 => '木', 5 => '金', 6 => '土'}
 while day < last do
  records=Array.new
  records.push(day.strftime("%Y%W,%Y,%W"))
  dday = day
    while dday < day+7 do 
     wday = weekdays[dday.wday]
     records.push(dday.strftime("%b,%-m,%d,%w,#{wday},%a,"))
     dday = dday+1
    end
  print records.join(',')
  print  "\n"
  day = day+7
 end

ヘッダーのファイルは下記コードで出力し、上の出力の上に貼り付けました。csvを 出力して、libreoffice calc に読み込ませれば、「差し込み印刷」のデータとなります。

header.rb
# weekly header

printf "%s,%s,%s,","yrwk","yr","wkwk"
printf "%s,%s,%s,%s,%s,%s,%s,","mte1","mtn1","dyn1","wkn1","wkj1","wke1","s1"
printf "%s,%s,%s,%s,%s,%s,%s,","mte2","mtn2","dyn2","wkn2","wkj2","wke2","s2"
printf "%s,%s,%s,%s,%s,%s,%s,","mte3","mtn3","dyn3","wkn3","wkj3","wke3","s3"
printf "%s,%s,%s,%s,%s,%s,%s,","mte4","mtn4","dyn4","wkn4","wkj4","wke4","s4"
printf "%s,%s,%s,%s,%s,%s,%s,","mte5","mtn5","dyn5","wkn5","wkj5","wke5","s5"
printf "%s,%s,%s,%s,%s,%s,%s,","mte6","mtn6","dyn6","wkn6","wkj6","wke6","s6"
printf "%s,%s,%s,%s,%s,%s,%s\n","mte7","mtn7","dyn7","wkn7","wkj7","wke7","s7"

祝日情報は、手入力で修正になります。データを揃えたら自動的に差し込めるように改良したいですが、年に一回の作業ですので予定は未定です。

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

剰余とべき乗(冪乗)の演算子

【概要】

1.結論

2.○○になるのはどういう時か

3.ここから学んだこと

1.結論

剰余:%
べき乗:**
を使う!


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

今回は2桁の整数(桁数制限するプログラムはしていません。)入力「の10の位と1の位を足した合計を求めています。

def addition(a, b)
  a + b
end

def calculation(num)
 #10の位
 no1 = (num / 10) % 10
 #1の位
 no2 = (num / 1) % 10
  return no1,no2
end

puts "2桁の整数を入力"
num = gets.to_i
a, b = calculation(num)
add_sum = addition(a, b)
puts "10の位と1の位の合計は#{add_sum}"

10の位を出すために10で割った数値をさらに10で割った余りを出しています
1の位も同様に1で割った数値をさらに10で割った余りを出しています。その際に"%"を使ってあまりを出しています。

(例)28の場合
(num / 10)・・・・28 / 10 = 2.8
2.8 % 10 ・・・・0余り2 の"2"を返す。

(num / 1)・・・・28 / 1 = 28
28 % 10・・・・2余り8 の"8"を返す。


3.ここから学んだこと(エラーの時に使用)

算数要素が混ざったプログラムは、プログラムの知識はもちろんのこと、工夫して計算する発想が求められることを学びました。工夫した計算にプログラムの知識も必要ですし、「このメソッドは一体どこで使うんだろう?」と思っているとピンポイントで使うこともあるので非常に大事です。

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

Rubyで割り算! なぜ0になる?

割り算(除算)の落とし穴をはじめから丁寧に。Rubyを例に扱います。前置きも説明も非常に長いです。結論はこちら

答えが少数になるときは要注意

たとえば「80÷100」をしたい、とします。
期待する答えは「0.8」(ないしはそれと等しい数)ですが…

80 / 100
=> 0

0が返ってきますね。なぜでしょうか?

プログラムとはそういうもの

先ほどの計算が"整数同士の計算"とみなされているからです。「整数同士であってるじゃん」と思うのですが、整数と整数の計算は整数でしか返ってきません。

たとえば「101÷100」もそうです。答えとして「1.01」を返してほしいところですが、次のようになってしまいます。

101 / 100
=> 1

どうやって計算するの?

期待する答えを得るためには、少数の桁まで計算に含める必要があります。「整数としてではなく、少数として計算してね!」と伝えなくてはいけないわけです。日常的には特段意識してやっていないような計算も、プログラムではこれらが違うことを意識して書いてあげる必要があります。

たとえば、次のように書けば、少数の桁も計算のうちであることを伝えることができます。

80.0 / 100
=> 0.8

と書いても良いし、

80 / 100.0
=> 0.8

と書いても良いし、もちろん

80.0 / 100.0
=> 0.8

でも良いですね。

なるほど。でも、これが変数だったら? そういった状況に対応するためにも、次のメソッドを知っておきたいです。

to_fメソッドを使う

Rubyで"少数"として扱いたい時に使えるのがto_fメソッドです。「f」はfloatの「f」、"浮動小数点数"を意味します。(こちらもまた独特の概念ですが、参考にできる記事がたくさんあるのでそちらに譲ります。)
さて、使い方ですが、数値(変数でもOK)の後にto_fをつけるだけです。

80.to_f / 100
=> 0.8

と書いても良いし、

80 / 100.to_f
=> 0.8

と書いても良いし、もちろん

80.to_f / 100.to_f
=> 0.8

でも良いですね。

この方法なら変数がきても大丈夫です。

n = 80

n.to_f / 100
=> 0.8

これで期待する結果が得られましたね。

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