20200329のRubyに関する記事は24件です。

Ruby on Railsインストール(Windows10)

はじめに

Ruby on Railsの環境を自前のPCに作りたかったのですが、ネットで検索して出てくるインストール手順「Ruby+Rails+SQLite3」のみだと正常にRailsサーバが立てられませんでした。
事前にNode.js、yarnのインストールをしておく必要がありました。
環境構築はそう何度も行うことはないので、備忘録として手順を残しておきます。

環境

Windows 10 Home 64ビット

Rubyインストール

1.Rubyのダウンロード

https://rubyinstaller.org/downloads/
このリンクからインストーラダウンロードページに行き、WITH DEVKITの中で推奨されている最新版をダウンロードします。
記事を書いている2020年3月時点では「Ruby+Devkit 2.6.5-1」が推奨されている最新版のようです。

2.Rubyのインストール

ダウンロードしたインストーラをダブルクリックし、ぽちぽちNextを押していくだけです。



→この後インストールが開始されます。

MSYS2をインストールするための画面です。"1,2,3"と入力し、Enterキーを押します。

→この後インストールが開始されます。5分弱くらいかかります。

→「succeeded」が表示されれば、MSYS2のインストール完了です。Enterキーを押すと画面が閉じられます。

3.Rubyの動作確認

①Rubyのバージョン確認
まずは正常にインストールされているかどうか確認するため、Rubyのバージョンを確認します。
コマンドプロンプトを起動し、以下のコマンドを実行します。

ruby -v


→バージョンが正常に表示されたので、インストールは正常に行われました。

②Rubyのプログラム実行
Rubyの簡単なプログラムを実行します。
メモ帳を開き、以下のコードを記述します。

puts "Hello World"

任意のファイル名で、拡張子を"rb"とし、保存します。

コマンドプロンプトで以下のコマンドを実行します。

ruby 拡張子rbのファイルパス

コマンドプロンプト上で"Hello World"が出力されたらOKです。

Railsインストール

1.Railsのインストール

コマンドプロンプトを起動し、以下のコマンドを実行します。

gem install rails

インストールが完了したら、以下のコマンドを実行し、Ruby on Railsのバージョンを確認します。

rails -v


→バージョンが正常に表示されたので、インストールは正常に行われました。

SQLite3インストール

1.SQLite3ライブラリのインストール

コマンドプロンプトを起動し、以下のコマンドを実行します。

gem install sqlite3

2.SQLite3の実行ファイルとDLLファイルのダウンロード

https://www.sqlite.org/download.html
このリンクからSQLite3公式のダウンロード用ページに行き、sqlite3.exeとsqlite3.dllをダウンロードします。

①sqlite-dll-win64-x64-3310100.zip の展開
展開されるファイルのうち「sqlite3.dll」をRubyをインストールしたディレクトリの「bin」ディレクトリに移動します。

②sqlite-dll-win64-x64-3310100.zip の展開
展開されるすべてのファイルをRubyをインストールしたディレクトリの「bin」ディレクトリに移動します。

Node.jsインストール

1.node.jsのダウンロード

https://nodejs.org/ja/
このリンクからインストーラダウンロードページに行き、推奨版をダウンロードします。
記事を書いている2020年3月時点では「12.16.1」が推奨されている最新版のようです。

2.node.jsのインストール

ダウンロードしたインストーラをダブルクリックし、ぽちぽちNextを押していくだけです。






3.インストール後のバージョン確認

①node.js実行ファイルパスを通す
「Windowsキー+E」でエクスプローラを開き、PCを選択した状態でプロパティを選択し、システムのプロパティを開きます。

システムプロパティから「システムの詳細設定」を開きます。

システム詳細設定から「環境変数」を開きます。

環境変数設定画面 > publicユーザー環境変数 > Path をダブルクリックします。

新規ボタンをクリックし、node.jsの実行ファイルパスを入力し、OKボタンをクリックします。

環境変数設定画面に戻り、OKボタンをクリックします。

②node.jsのバージョン確認
以下のコマンドを実行し、node.jsのバージョンを確認します。

node -v


→バージョンが正常に表示されたので、インストールは正常に行われました。

yarnインストール

1.yarnインストーラのダウンロード

https://nodejs.org/ja/
このリンクからインストーラダウンロードページに行き、インストーラをダウンロードします。

2.yarnのインストール

ダウンロードしたインストーラをダブルクリックし、ぽちぽちNextを押していくだけです。




3.インストール後のバージョン確認

①yarn実行ファイルパスを通す
設定手順はnode.jsと同様なので省略します。

②yarnのバージョン確認
以下のコマンドを実行し、yarnのバージョンを確認します。

yarn -v


→バージョンが正常に表示されたので、インストールは正常に行われました。

Railsアプリ起動

1.Railsアプリの新規作成

以下のコマンドを実行し、Railsアプリを新規作成します。

cd アプリケーションを配置するファイルパス
rails new アプリケーション名



→「successfully」が表示されれば正常にアプリが作成されました。

2.Railsサーバの起動

以下のコマンドを実行し、Railsサーバを起動します。

cd アプリケーションフォルダ
rails s


http://localhost:3000
にアクセスします。

→このページが表示されたらサーバーが正常に起動されたことになります。

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

nokogiri関連でエラーが出ちゃった人は、、、

エラー


An error occurred while installing nokogiri (1.6.7.rc4), and Bundler cannot continue.
Make sure that gem install nokogiri -v '1.6.7.rc4' succeeds before bundling.


Library not loaded: /usr/local/opt/openssl/lib/libssl.1.0.0.dylib (LoadError)

対処法

色々やったけど無理でした。

最終的にrubyをインストールしなおしました

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

Railsのバージョンを確認したい

$ gem info -e rails

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

Rails をアンインストールする時にエラーが出た場合の対処方法

エラー発生

$ gem uninstall railties -v '6.0.2.2'

を実行するとこんなエラーが

ERROR: While executing gem ... (Errno::ENOTEMPTY)
Directory not empty @ dir_s_rmdir - /Users/ユーザ名/.rbenv/versions/2.6.3/lib/ruby/gems/2.6.0/gems/rails-6.0.2.2

原因はインストールの際に管理者権限で作成していたから

解決方法

$ sudo gem uninstall railties -v '6.0.2.2'

これだけでした。

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

[Rails]DM機能に送信日時を日本時間で表示させる

本記事投稿のいきさつ

現在作成中のアプリでメッセージ機能を作成しました。
その中で日本時間で送信日時を表示させる際に、初めて知った内容があったため備忘録として書きたいと思います。
内容自体はとても簡単なものとなっています。
今回は以下のアプリで実装していきます。
スクリーンショット 2020-03-29 21.43.31.png

ビューに追記

メッセージを表示させるビューに以下を追記

message.html.haml
= message.created_at

しかし、これだけでは以下の表示となってしまいます。
スクリーンショット 2020-03-29 21.49.55.png

とても見づらい表示となってしまうため、先ほどのコードに追記をし、表示の方法を指定します

message.html.haml
= message.created_at.strftime("%Y年%m月%d日 %H時%M分")

すると、こんな感じに。
スクリーンショット 2020-03-29 21.50.35.png

日本時間の設定

ここが自分が知らなかった点なのですが、Railsのアプリケーションの時間基準は、デフォルトでは協定時(UTC)となっています。
そのため、日本時間で表示をするためには、config/application.rbを以下のように編集する必要があります。

config/application.rb
class Application < Rails::Application
#以下を追記
  config.time_zone = 'Asia/Tokyo'

終わり

これでメッセージアプリに日本時間での表示ができるようになりました。
最後まで見ていただきありがとうございました。

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

【Ruby】社内報を作成してくれるクラスを作成してみた。

はじめに

毎月先輩から出していただいた課題に取り組んでいます、 mi0です。
2月は社内報を作成するクラスの実装を行いました。
この記事は要件定義〜レビューをいただくまでの過程を纏めた備忘録です。
こうやったらもっとよくなる、などのご指摘があればコメント頂けると嬉しいです!

過去の記事はこちら!↓

登場人物

    • なんとなく技術力の上昇を感じ始めた。慢心ではないと思いたい。自作のWebアプリを作ってみたい気持ちが湧いてきた。
  • 小えびのかき揚げ先輩
    • 最近自宅の開発環境を整えて嬉しそうにしていらっしゃる。プログラミングが得意。
  • なんこつ唐揚げちゃん
    • 私の心の中の妖精。「やっぱりカロリーは正義よね」が口癖。

要件を定義する

私「今回は社内報を作成するクラスの要件定義から行うよ!初めての試み!」

私「今回は普段使っている社内報を作ってくれるアプリのロジック部分を実装したいんだよね」

私「とりあえず今使ってるアプリがしていることをまとめてみよう」

使用しているもの

  1. テンプレート

    • 社内報の元になるテンプレート。テンプレートには[名前]といった風に、各メンバーの本文挿入箇所が指定されている。
  2. メンバー表

    • メンバーの名前、メールアドレスが定義されている。メールアドレスの形式は名前@ドメイン
  3. 各メンバーの原稿

    • ファイル名が年月 + 名前.txtというフォーマットのテキストファイルが毎月送付されてくる。

機能

  • 社内報の生成
    1. ディレクトリに、メンバーから提出された原稿を全て格納する。
    2. 1のディレクトリを指定する。
    3. アプリの作成ボタンを押下すると、テンプレートの内容を元に社内報が生成される

私「こんなところかな?…………。」

なんこつ「急に黙ってどうしたの?」

私「いや……なんかさ、このアプリを初めて見た時……一年半くらい前なんだけどね?なんかめちゃくちゃ凄いアプリだな〜って思って、こんなの私に作れるわけない!って思ってたんだよ。私も結構へっぽこだったからさ……。」

私「でも、こうやって見ると そんなに難しいことはやってないんだなって感じて……あ、勿論機能が単純とかそういう話じゃなくて、ロジックをイメージしたときに凄いシンプルだなって思って。」

なんこつ「(静かに微笑む)」

私「ちょっとその顔やめてもらっていいです???」

なんこつ「いやぁ……なんか……ねぇ。いいねぇ。」

私「時を戻そう」

なんこつ「最近のブームをしれっと混ぜ込んで来ないで」

私「今回はとりあえず、現行のアプリで最低限必要なところの設計・実装を行うって話だったから、現行の内容で十分なんじゃないかなって思うんだよね。次回で欲しい機能を追加する、って話をかき揚げ先輩としてるしね。」

なんこつ「じゃあ、一旦内容を纏めて先輩に見ていただきましょうよ」

私「そだね、もしかしたら何かアドバイスいただけるかも!」

〜〜〜そして先輩との打ち合わせ後にできた今回の仕様が以下〜〜〜

社内報作成アプリ

問題

  • テンプレートに原稿を挿入し、社内報を作成する。

社内報の元データ

  • app/fixture/template.txt ・・・社内報のテンプレート。
  • app/fixture/member_list.csv ・・・各メンバーの名前とメールアドレスが記載されている。
  • app/fixture/manuscripts ・・・ 年月 毎にディレクトリを作成し、直下にその月の各メンバーの原稿を格納する。

要求仕様

  • 引数には文字列で 年月 を渡す(例: 201902)
  • 引数に該当するディレクトリ内にある原稿を取得し、テンプレートに当て嵌めて社内報を作成する。社内報は文字列として返す。
  • 指定したディレクトリが存在しない場合は例外を返す
  • テンプレートの [user_name] の部分は ・ユーザ名 に変換し、その次の行から本文を記載する。
  • 原稿と原稿の間には必ず 1行分の改行を含む こと
  • 原稿の末尾に不要な改行が含まれていたら取り除く
  • 原稿が未提出の社員については本文に 未提出 と記載する
例) 202002を引数に指定した場合、以下のように出力される

・ラテ太郎
お疲れ様です、ラテ太郎です。
サンプル
サンプル
サンプル

・ラテ子
お疲れ様です、ラテ子です。
サンプル
サンプル
サンプル
サンプル

・沖漬け先輩
サンプル
サンプル
サンプル
サンプル
サンプルサンプルサンプルサンプルサンプルサンプルサンプル

・ラテ二郎
未提出

テンプレート

db/template.txt
社内報

[rate_taro]

[rate_ko]

[okiduke_senpai]

[rate_jiro]

db/member_list.csv
ラテ太郎,rate_taro@sample.com
ラテ子,rate_ko@sample.com
沖漬け先輩,okiduke_senpai@sample.com
ラテ二郎,rate_jiro@sample.com
app/internal_newsletter.rb
class InternalNewsletter
  def generate(datestr)
    ''
  end
end

※ディレクトリが存在しないとき、既存アプリではエラーにならずに処理を完了してしまっていたため、
例外を起こすことでエラーがわかるようにする、という仕様が増えました。

作成した仕様を元に実際に解いていく

私「よし!実際に作っていくよ〜〜〜!」

私「まずは私のイメージを#generateにメモしていこう」

app/internal_newsletter.rb
require 'csv'

class InternalNewsletter
  def initialize
   # テンプレートとメンバー表を読み込む
  end

  def generate(datestr)
    # 読み込んだテンプレートを一行ずつ取得し、配列を作成する
    # 読み込んだ行が[名前]にマッチする場合、[名前]を ・名前\n本文 に置換する
    # 最後に配列を\nで結合する
  end
end

私「こうやって書くとやりたいことってほんとシンプルだよね〜。次、具体的にコードをかけそうなところを埋めていく。」

app/internal_newsletter.rb
require 'csv'

class InternalNewsletter
  def initialize
    @template = File.open('db/template.txt').readlines(chomp: true)
    @member_list = CSV.read('db/member_list.csv')
  end

  def generate(datestr)
    @template.each_with_object([]) do |row, array|
      # 読み込んだ行が[名前]にマッチする場合、[名前]を ・名前\n本文 に置換する
    end.join("\n")
  end
end

なんこつ「凄いそれっぽいじゃない。」

私「いやいや、問題はここからなんだよね……」

私「テンプレートを一行ずつ読み込んでいくと、rowに入り得る内容は以下の3パターンある。」

  1. 見出し(社内報など)
  2. 改行のみ
  3. [名前](置換対象)

私「今回でいうと、3の場合だけ置換対象にしたい。それ以外の場合は手を加えたくない。」

私「だから、3以外は早期リターンでスルーしたいから、次はそこを作ろう」

app/internal_newsletter.rb
 require 'csv'

 class InternalNewsletter
   def initialize
     @template = File.open('db/template.txt').readlines(chomp: true)
     @member_list = CSV.read('db/member_list.csv')
   end

   def generate(datestr)
     @template.map do |row|
+      member_name_en = row[/\[(.*?)\]/, 1]
+      next row if member_name_en.nil?

       # 読み込んだ行が[名前]にマッチする場合、[名前]を ・名前\n本文 に置換する
     end.join("\n")
   end
 end

なんこつ「なにそのmember_name_enに代入してるやつ!」

私「ふっふっふ……やばいでしょやばいでしょやばいでしょ」

私「私は自称正規表現ザコなので、今回は調査を頑張りました。」

私「知ってるかもしれないけど、これはstr[]メソッドってやつでね」

私「第2引数に渡す値で振る舞いが変わるんですよ。」

私「0の時は第2引数無しの場合と一緒の振る舞い。つまり今回だと [名前]が返ってくる。」

私「0以外の場合は、第一引数に渡した正規表現の(第2引数に渡した値)番目の括弧にマッチする最初の部分文字列が返ってくるのね。」

私「今回だと1番目の括弧、つまり(.*?)にマッチした文字列が返ってくる。括弧の中には[]が含まれていないでしょ?だから、名前だけが返ってくる。超かっこよくない?」

なんこつ「なるほどね……これ、結構使えそうな気がするわ。例えば一つの文字列から、3箇所部分的に取得したい内容がある場合、正規表現でそれぞれを括弧で囲っておくと、第2引数の値を変えるだけでそれぞれが取得できるってことだものね」

私「そういうこと!結構便利な気がする!」

[1] pry(main)> '[aaa] [bbb] [ccc]'[/\[(.*?)\]\s\[(.*?)\]\s\[(.*?)\]/, 0]
=> "[aaa] [bbb] [ccc]"

[2] pry(main)> '[aaa] [bbb] [ccc]'[/\[(.*?)\]\s\[(.*?)\]\s\[(.*?)\]/, 1]
=> "aaa"

[3] pry(main)> '[aaa] [bbb] [ccc]'[/\[(.*?)\]\s\[(.*?)\]\s\[(.*?)\]/, 2]
=> "bbb"

[4] pry(main)> '[aaa] [bbb] [ccc]'[/\[(.*?)\]\s\[(.*?)\]\s\[(.*?)\]/, 3]
=> "ccc"

※参考
[Ruby]特定の文字列の抽出
Ruby 2.7.0 リファレンスマニュアル

私「と、これで取得した行が[名前]以外の場合はスルーできるようになった!」

私「次、実際に原稿を挿入していく処理を作るよ!」

app/internal_newsletter.rb
 require 'csv'

 class InternalNewsletter
   def initialize
     @template = File.open('db/template.txt').readlines(chomp: true)
     @member_list = CSV.read('db/member_list.csv')
   end

   def generate(datestr)
     @template.map do |row|
       member_name_en = row[/\[(.*?)\]/, 1]
       next row if member_name_en.nil?

+      "・#{@member_list.find { |arr| arr[1].match(/\A#{member_name_en}/) }[0]}\n#{load_manuscript(datestr, member_name_en)}"
     end.join("\n")
   end

+  private

+  def load_manuscript(datestr, member_name_en)
+    file_path = "db/manuscripts/#{datestr}/#{datestr}#{member_name_en}.txt"
+    return '未提出' unless File.exist?(file_path)

+    File.open(file_path).read.strip
+  end
 end

私「[名前]の場合、・名前\n本文を返す。けど、もしその人が原稿を提出していなかったら、本文は未提出という文言を出したい。」

私「条件によって、本文の内容が変わるから、そこをよしなにできるメソッドとして#load_manuscriptをつくったよ。」

なんこつ「へぇ……なかなかいいんじゃないの」

私「だよねだよねだよね〜?ちょっとこれで動かしてみようかな!」

社内報

・ラテ太郎
吾輩は猫である。
名前はまだ無い。
どこで生れたかとんと見当がつかぬ。
何でも薄暗いじめじめした所でニャー

・ラテ子
恥の多い生涯を送って来ました。
自分には、人間の生活というものが、見当つかないのです。
自分は東北の田舎に生れましたので、汽車をはじめて見たのは、
よほど大きくなってからでした。

・沖漬け先輩
木曾路はすべて山の中である。
あるところは岨づたいに行く崖の道であり、
あるところは数十間の深さに臨む木曾川の岸であり、
あるところは山の尾をめぐる谷の入り口である。

・ラテ二郎
未提出

私「待って」

私「最後の改行が足りないよ〜〜〜!?!?!?」

私「ぴえんぴえんのぴえんだわ」

私「ぴえんすぎる」

私「そっか……joinでくっつけてるから最後の改行がくっつかないのか……」

私「う〜〜〜〜〜ん……リファクタしながら考えるか……。」

〜〜〜リファクタリング後〜〜〜

require 'csv'

class InternalNewsletter
  def initialize
    @template = File.open('db/template.txt').readlines(chomp: true)
    @member_list = CSV.read('db/member_list.csv')
  end

  def generate(datestr)
    @template.each_with_object([]) do |row, array|
      member_name_en = row[/\[(.*?)\]/, 1]
      next array << row if member_name_en.nil?

      array << "・#{@member_list.find { |arr| arr[1].match(/\A#{member_name_en}/) }[0]}"
      array << load_manuscript(datestr, member_name_en)
    end.join("\n") + "\n\n"
  end

  private

  def load_manuscript(datestr, member_name_en)
    file_path = "db/manuscripts/#{datestr}/#{datestr}#{member_name_en}.txt"
    return '未提出' unless File.exist?(file_path)

    File.open(file_path).read.strip
  end
end

なんこつ「ちょっと……」

私「完全敗北しました……文字列結合で\n\nとかはちゃめちゃにダサダサなのわかってるんですけど……勝てなかった……ごめんなさい……!!!」

私「代わりと言ってはなんだけど、mapの中で・名前\n本文みたいに改行が出てくるのが嫌だったから、改行コードはmapで配列を作り終わった後に一括で付けるようにしました。」

私「いや〜〜でもやっぱり一番最後で文字列結合した改行×2がやっぱり嫌だ……許せれん……。どうしたらいいんだろうね……」

レビューをいただく

かき揚げ先輩「じゃあレビューしようか」

私「お願いします!」

かき揚げ先輩「まず残念なお知らせです。」

私「ひゃい」

かき揚げ先輩「ディレクトリが存在しない時ってどうするんだっけ?」

私「」

私「」

私「あ!?!?!?!!??!?!?!?!?!?!」

私「例外!!!!!!!!!!!!」

かき揚げ先輩「漏れてました」

私「ああああああああああ」

私「今回割と自信あったのに!!!!!!!!!!」

私「穴があったら……ってやつですウワ……しくった」

かき揚げ先輩「次はコードね」

かき揚げ先輩「まずね、[名前]名前だけ抜き出すやつ」

かき揚げ先輩「matchを使わないあの書き方は初めて見た。」

かき揚げ先輩「短く書けていいと思いました:thumbsup:

私「(やった〜〜〜〜〜〜)」

かき揚げ先輩「次、テンプレートを元にする処理でeach_with_objectを使ってたよね?」

私「はい」

かき揚げ先輩「あそこを一行で書けてたらmapでもよかったんだよね」

私「そうなんです……」

かき揚げ先輩「今回で言うとフォーマットがきっちり決まっていたから、そこを共通化できたらよかったかな」

かき揚げ先輩「あとで俺の書いたコード見せるけど、こういう時こそヒアドキュメントを使うといいよね。ヒアドキュメントの場合、文末に改行が必ず付くから。」

私「!!!!!!」

私「な、なるほど……確かに。しかもヒアドキュメントなら、実際に表示される文章とほぼ同じ形式で書くから可読性も高いですよね」

かき揚げ先輩「そういうこと。ハードコーディングな部分をメソッドに切り出すこともできるから、そういう意味でもより可読性は上がるね。

かき揚げ先輩「次は実際に俺の書いたコードを見てもらおうかな。」

require 'csv'

class InternalNewsletter
  MEMBER_LIST = CSV.read('db/member_list.csv')
  NEWSLETTER = File.read('db/template.txt')
  NOT_SUBMITTED = '未提出'.freeze

  def initialize(datestr)
    @datestr = datestr
    @dirpath = "db/manuscripts/#{@datestr}"
  end

  def generate
    raise ArgumentError unless Dir.exist?(@dirpath)

    MEMBER_LIST.inject(NEWSLETTER, &method(:newsletter)) + "\n"
  end

  private

  def newsletter(text, member)
    name, keyword = split_name_keyword(member)
    fullpath = "#{@dirpath}/#{@datestr}#{keyword}.txt"
    body = File.exist?(fullpath) ? File.read(fullpath).strip : NOT_SUBMITTED

    text.gsub(
      /\[#{keyword}\]/,
      <<~NEWSLETTER.chomp#{name}
        #{body}
      NEWSLETTER
    )
  end

  def split_name_keyword(member)
    name, email = member
    _, keyword = email.match(/\A(\w+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/).to_a
    [name, keyword]
  end
end

私「はぁ〜〜〜〜なるほど……」

かき揚げ先輩「俺の処理だと早期リターンはなくて、該当箇所を置き換えていくだけ。」

かき揚げ先輩「次のステップとして、私ちゃんに意識して欲しいのは共通部分異なる部分をそれぞれ見つけて、共通化していくことだね」

かき揚げ先輩「共通化することによって、記述を減らすことができるし、コードの量が少ないって事は読む量が少ないってこと。共通化して、総コード数を減らせるようになるとより良くコーディングができるようになると思うので、意識してみてください」

私「分かりました!ありがとうございます!」

最後に

自分なりにより良い書き方を考えて書いたつもりだったのですが、ヒアドキュメントは盲点でした……。先輩のコードを拝見した際に思わず唸ったほどです。
あと例外の話をすっかり忘れていたといううっかりは本当に悔やんでも悔やみきれませんでした。こういううっかりを減らしていくためにも、最初のメモ書きを行った時点で仕様を満たせているか確認した方がいいですね。

一方で、正規表現周りで先輩に「知らなかった」と言っていただけた事、また「ロジック自体はそんなに難しくないな」と思えた事は私的に成長を感じられる嬉しみポイントでした。

上手く出来なかったところは改善しつつ、よかった所も噛み締めながら、この調子で引き続き頑張りたいと思います!

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

読みやすいコードを書きたい!

この記事の趣旨

 自分の学習のメモとして残します。
 どなたかのお役に立てたら光栄です。

こんな方におすすめです

 ・コードが見づらいと言われる
 ・初心に返りたい
 このような方には何か発見があると思います。

読みやすいコードとは?

 ズバリ『良いコード』のこと。
 良いコードは、他人がそのコードを見た時に短時間で理解できるコードのことを言います。

 逆に、分かりづらい・理解しづらいコードは解読に時間がかかります。それだけ開発の工数もかかってしまい、効率が良くありません。

 では、良いコードの条件・要素を紐解いていきましょう!

●コードの命名に規則を

 変数やメソッドは好きなように命名ができます。
 ルールがありませんので、個人の好きなようにできます。
 特に共同開発の現場などでは、「他人が見てわかる」を意識する必要があります。

 ◎命名のポイント

  【目的がわかる単語を使う】
    例)new → new_account

  【汎用的な名前は避ける】
   ・一時的な変数などは避ける
   ・可読性を意識して

  【名前に情報を含める】
   大文字、小文字をルールに沿って活用
 
  【誤解されない名前を使う】
   ・何がしたいかが明確な名前
   ・説明的に長くなっても良いので、可読性重視
     例)read_books → already_read_books

●コードレイアウト

 プログラムの挙動に影響はないが、可読性を大幅にあげることができる

 ◎レイアウトのポイント

   ・整列    :縦列を揃える。イコールの位置など縦が揃うと見やすい
   ・一貫性   :似たような構造は同じフォーマットに統一できないか検討
   ・ブロック化 :同じ系統の変数などをまとめてグループ化すること

●コメント

 ・プログラムの動作を説明
 ・他の開発者がコードを読む際の理解を助ける
  ※多すぎても読むのに時間がかかるため、簡潔に

 ◎コメントのポイント

   ・理由をコメントする  :なぜそのコードを書いたか
   ・他の開発者へメモを残す:開発中のメモとして
   ・実際の例を記入する  :コメントでは伝わりづらい時は、コメントとしてコードを記載

まとめ

 結局大事なことは
  「人に対する思いやり」だなぁと。

 複数人で仕事をする以上、「自分だけ良ければそれで良い」という考えはNG。
 誰もが見やすく、仕事をしやすい状況を自分が作り出す意識が大切。

 そのための知識や技術であると思う。

 これからしっかり学んでいきましょう。

   

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

Rails 自作アプリへの道 Part1

Rails 自作アプリを作った時の経過をまとめていきます。

参考:https://kitsune.blog/rails-install

環境
| 1 | 2 |
|:-:|:-:|
| | |
| | |
| | |
| OS | Mac Mojave 10.14.16 |
| Ruby | 2.6.3p62 |
| Rails | 6.0.2.2 |

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

Catalinaのバージョンアップしたらrails sできなくなったけど、Node.jsのインストールで解決!

筆者の環境

macOS Catalina バージョン 10.15.4
使用言語:Ruby、JavaScript

エラー内容

不注意により、macOSがCatalina バージョン10.15.4に上がってしまった。

それから実装中のアプリでrails sすると、以下のエラーメッセージが表示されサーバーが起動しなくなった。

Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)

これで解決!

Node.jsをインストール

左側の12.16.1の方はインストールしても開けず、
右側(最新版)をインストールしたらrails s成功しました!

スクリーンショット 2020-03-29 19.12.24.png

番外編

他にも対処法はいくつかあり、therubyracerというGemのインストールでも解決できるようです。(参考リンクご参照ください)

現在、チーム開発中だったので、自分以外の複数端末への影響を考え今回はGem以外の方法を選択しました。

参考

にさせていただきました。ありがとうございます。
https://qiita.com/azusanakano/items/771dc9919f347de061d7

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

活動記録(2020.3.29)

今週やったこと

・DBのエラーでつまずき解明と環境変数設定周りで時間を使う羽目に…この辺は別記事にまとめている。
Ruby on Rails 学習メモ.2

・GitHubのこれまでのプルリク等の基本的活用に加えて、issues(とmilestone)でプロジェクトを管理するようにした。
なかなか進まないがこうやってプロジェクトを可視化・効率化させていくのはとても楽しい。

・3月最後の追い込みをしたかったが高熱が出てしまい進捗が芳しくなかった。

課題・不明点

・テスト環境の理解が浅い。rubocopを導入すれば自動で警告文が出るらしい。RSpec、Jenkinsについても調べておく。
・ルート、ポート、DNS等ネットワーク周りの知識が弱い→基本情報技術者テキスト
・AWSは公式のレクチャーを見るべき。
・デプロイはCircleCIではなくGitHubActionsを採用するのもいいかも。

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

プログラミング初心者がgitでつまずいた。英語の意味を調べて、専門用語を深く理解しました。

gitについて1から復習してみました。

Git

gitは、プログラムのソースコードなどの変更履歴を記録・追跡するための分散型バージョン管理システムである。

要は、複数人で開発するときに、誰がどれをいじったのか分からなくてグチャグチャになるのを防止するためのシステムのことなんだなーてことか。よし。

Github

これはgitを扱うためのツールのことですな。

っと書き始めてたら、最強に分かりやすく説明している記事を見つけてしまいました。

こちらを読めば完璧。

https://qiita.com/nnahito/items/e546b27f73e7be131d4e

でも疑問点が二つ湧いてくる。

pullとbranchって一緒じゃね?あと、pushとmargeって一緒じゃね?

すごい調べたけど、概念が違うっぽい

branchとmargeは、masterから枝分かれを作ったり、合体したりする行為

pullとpushはそれを包括して取ってきたり送ったりするからconflictが起こりやすい。

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

<前のページ  次のページ>の実装

前後のレコードを取得して、前後ページ移動する

はじめに

よくブログなどで見る、
<前のページ  次のページ>
でページ遷移をする機能を現在制作中のプロダクトに実装します。
調べた内容を極力シンプルにまとめました。

プロダクトは個人間取引を行うもので、今回実装するものは
商品を見ていくときの利便性を向上させるものです。
今回は出品された商品の詳細を表示するページに前後のリンクを実装します。
特にカラム別に分けることなく、全てのレコードを新着順で並べて前後していきます。

狙い

出品された順にページを前後するリンクを実装します。
<前のページ  次のページ>
といった表示で、ページ遷移をします。

実装

今回はitems_controllerのshowアクションにて行います。
まず商品の情報を取得します。

app/controllers/itmes_controller.rb
def show
  @item = Item.find(params[:id])
end

次にitemモデルにて、メソッドを定義します。
where,order,firstメソッドを用いてpreviousメソッドとnextメソッドを定義して使用します。

app/models/item.rb
def previous 
  Item.where("id < ?", self.id).order("id DESC").first 
end 

def next 
  Item.where("id > ?", self.id).order("id ASC").first 
end

whereメソッド
https://railsguides.jp/active_record_querying.html#%E6%9D%A1%E4%BB%B6

orderメソッド
https://railsguides.jp/active_record_querying.html#%E4%B8%A6%E3%81%B3%E9%A0%86

firstメソッド
https://railsguides.jp/active_record_querying.html#first

ビューは以下の通りです。
最初の前のレコードと最後の次のそれは存在しないので条件分岐で分けましょう。

app/views/items/show.html.haml
- if @item.next.present?
  = link_to "次のページ>", item_path(@item.next)

- if @item.previous.present?
  = link_to "<前のページ", item_path(@item.previous)

拡張機能 blank? present?
https://railsguides.jp/active_support_core_extensions.html#blank-questionmark%E3%81%A8present-questionmark

実際の実装では商品の名前を表示させたので以下のようにしました。
また、itemテーブルにnameカラムを設定しています。

app/views/items/show.html.haml
- if @item.next.present?
  = link_to item_path(@item.next) do
    <
    = @item.next.name

- if @item.previous.present?
  = link_to item_path(@item.previous) do
    = @item.previous.name
    >

※途中にある<>は<前のページ 次のページ>、の両端に置いてあるものです。特に指定はありません。

完成例

スクリーンショット 2020-03-29 16.21.00.png
前後の商品の名前が表示され、実際にリンクを実装できました。

おわりに

今回はテーブルにある全てのレコードの新着順で前後ページのリンクを実装しました。
ユーザー別、グループ別など持っている属性のまとまりの中で実装されている方がいらっしゃたのでリンクを載せておきます。
また参考にさせていただいた記事の作成者の方々、感謝申し上げます。
ありがとうございました。

参考リンク

https://qiita.com/hrtkmztn/items/df09584cfc621699532c
こちらはグループ内で投稿されたもので実装されてます。
アソシエーションや変数などに手を加える必要があるようです。
それぞれのユーザーが出品した商品一覧の前後を遷移するリンクを試作しましたが、実装できました。

https://easyramble.com/active-record-prev-next.html
previous, nextメソッドの実装に取り入れました。

https://www.for-engineer.life/entry/get-record/
こちらもよくまとめられています。

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

Rails探検録 Tips: private メソッドになぜ underscore が付くのか

【本文】

最近、Rails のソースコードを暇つぶしに読んでいたら、ふと private メソッドの枠組みで underscore の有無があることが気になりました。

参考 URL を読むと、Ruby では private と protected メソッドに使われることが多いそうです。

他のプログラミング言語でも( JavaScript, PHP, Phython etc...)同様のコーディング規約が存在します。これは、boolean を返すメソッドは末尾に?を付けたり、破壊的なメソッドは!を付けたり、getter メソッドに=を付けることと同じ意味ですね。

【あとがき】

private メソッドでも underscore が付かないものもある?

underscore はコーディング規約の一種と考えても、Rails のソースコードでは、 underscore が付くものと付かないものが存在します。

Rails と言う巨大なフレームワークなら一定のコーディング規約があると思うのですが、なぜ付かないものがあるのでしょうか?
結論は違いがわかりませんでしたでしたが...

全ての unserscore 付きメソッドに言えることではありませんが、_assign_attributesにしろ、_find_allにしろ、呼び出し元のメソッドと強く依存しているケースが散見されます。

rails/activemodel/lib/active_model/attribute_assignment.rb
...
def assign_attributes(new_attributes)
    unless new_attributes.respond_to?(:each_pair)
        raise ArgumentError, "When assigning attributes, you must pass a hash as an argument, #{new_attributes.class} passed."
    end
    return if new_attributes.empty?

    _assign_attributes(sanitize_for_mass_assignment(new_attributes))
end

alias attributes= assign_attributes

private
    def _assign_attributes(attributes)
        attributes.each do |k, v|
            _assign_attribute(k, v)
        end
    end

    def _assign_attribute(k, v)
        setter = :"#{k}="
        if respond_to?(setter)
            public_send(setter, v)
        else
            raise UnknownAttributeError.new(self, k.to_s)
        end
    end
    ...
rails/actionpack/lib/action_dispatch/routing/url_for.rb
...
def route_for(name, *args)
    public_send(:"#{name}_url", *args)
end

protected
    def optimize_routes_generation?
        _routes.optimize_routes_generation? && default_url_options.empty?
    end

private
    def _with_routes(routes) # :doc:
        old_routes, @_routes = @_routes, routes
        yield
    ensure
        @_routes = old_routes
    end

    def _routes_context # :doc:
        self
    end
...
rails/actionview/lib/action_view/path_set.rb
...
%w(<< concat push insert unshift).each do |method|
  class_eval <<-METHOD, __FILE__, __LINE__ + 1
    def #{method}(*args)
      paths.#{method}(*typecast(args))
      end
  METHOD
end

...

def find_all(path, prefixes = [], *args)
  _find_all path, prefixes, args
end

...

private
  def _find_all(path, prefixes, args)
    prefixes = [prefixes] if String === prefixes
    prefixes.each do |prefix|
      paths.each do |resolver|
        templates = resolver.find_all(path, prefix, *args)
        return templates unless templates.empty?
      end
    end
    []
  end

  def typecast(paths)
    paths.map do |path|
      case path
      when Pathname, String
        OptimizedFileSystemResolver.new path.to_s
      else
        path
      end
    end
  end

sanitize とは一体?

全くの余談ですが、#assign_attributesを見ると、入力値と仮定する new_attributes をsanitize_for_mass_assignmentを通していることがわかります。つまるところ、new_attributes は、比喩表現として消毒される必要があることですよね。

メソッドを見てみると、Storong Parameter 等で許可されていない params が合ったら例外を出すようです。

rails/activemodel/lib/active_model/forbidden_attributes_protection.rb
def sanitize_for_mass_assignment(attributes)
    if attributes.respond_to?(:permitted?)
        raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
        attributes.to_h
    else
        attributes
    end
end

他にも activemodel の中を探検すると、dirty(汚れた)と呼ばれる module が目につきます。この module は、DB 保存前の入力値を扱う機能であり、例え assign_attributes でインスタンス変数に入力値を割り当てても、元の DB 保存値を取り出せるメソッドが定義されています。

つまるところ、データベースにとって、ユーザの入力値は、粗く汚く悪意ある情報と見ており、それを考慮した上で対応する必要性があることが読み取れます。まぁ読めばわかることではあるのですが、オブジェクトやドメインに対する共通認識を理解することで、コードの理解度を深められるなと思ったところです。

@_のようにインスタンス変数にも underscore が使われてる?

メモ化等で使用範囲を内部で留めるインスタンス変数に対して、使用されているようです。
今回の private に似た目的ですね。

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

redirect_toとrender, flashとflash.nowの違い

ポートフォリオ作成中、バリデーションのエラーメッセージが表示できずに苦戦してました
redirect_toとrenderの違いを理解していなかったのが原因です。
redirect_toとrender, ついでにflashとflash.nowの違いについても今更理解したのでまとめます。

参考

https://railsguides.jp/layouts_and_rendering.html#redirect-to%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%99%E3%82%8B

https://railsguides.jp/action_controller_overview.html#flash

renderとredirect_toの違い

  • render => ビューを描画するだけで、新たにhttpリクエストを送信するわけではない
  • redirect_to => 新たにhttpリクエストを送信して、その結果コントローラからビューが描画される
app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  def new
    @tweet = Tweet.new
  end

  def create
    @tweet = Tweet.new(tweet_params)
    if @tweet.save
      # 成功時の処理
    else
      flash[:danger] = '失敗しました'
      redirect_to new_tweet_path
    end
  end

  private

    def tweet_params
      params.require(:tweet).permit(:content)
    end
end
app/views/tweets/new.html.slim
= form_with model: @tweet, local: true do |f|
  - if @tweet.errors.any?
    .alert.alert-danger
      ul
        - @tweet.errors.full_messages.each do |message|
         li= message
  = f.label :content
  = f.text_area :content
  = f.submit 'tweet'

こんな感じの設定があったとします(tweetモデルはpresence: trueのconent: stringカラムを持つ)
この場合、空白で送信してもエラーメッセージは表示されません
redirect_toでリダイレクトするとコントローラを経由してしまうため、新たに@tweetが生成され、エラーメッセージを含んだ@tweetが消えてしまうから…だと考えています

ここで、コントローラを経由せずにページを描画するrenderを使用します

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  def new
    @tweet = Tweet.new(tweet_params)
  end

  def create
    @tweet = Tweet.new(tweet_params)
    if @tweet.save
      # 成功時の処理
    else
      flash[:danger] = '失敗しました'
      render action: :new # ここを変更
    end
  end

  private

    def tweet_params
      params.require(:tweet).permit(:content)
    end
end

renderを使用すればコントローラを経由せずにページを描画することができるため、エラーメッセージを含んだ@tweetがビューに渡されif @tweet.errors.any?に引っ掛かり、エラーメッセージを表示することができます

これで無事エラーメッセージを表示することができました!

flashとflash.nowの違いについて

  • flash => 次のリクエスト終了時まで表示される
  • flash.now => 次のリクエスト開始時まで表示される

コントローラーが上の最終状態のまま空白で送信した場合、失敗しましたというflashメッセージが表示され、失敗します。
しかし、このままでは次別のページに遷移した時flashが残ったままになってしまいます。

renderを使うとリクエストを送信しないため次にリクエストを送信した時もflashが残り、その次のリクエストを送信した時点でやっとflashが消えるからだと思われます

そこでflash.nowを使ってみましょう

app/controllers/tweets_controller.rb
class TweetsController < ApplicationController
  def new
    @tweet = Tweet.new(tweet_params)
  end

  def create
    @tweet = Tweet.new(tweet_params)
    if @tweet.save
      # 成功時の処理
    else
      flash.now[:danger] = '失敗しました' # ここを変更
      render action: :new
    end
  end

  private

    def tweet_params
      params.require(:tweet).permit(:content)
    end
end

flash.nowは次リクエストを送信した時点で消えるため、これでflashを残さずに次のページへ移動することができます

それぞれの組み合わせ

  • flash[]とredirect_to => 次のページにリダイレクトした時点でflashは消える
  • flash[]とrender => renderはリクエストを送信しないため、次のページに移動してもflashは残る
  • flash.now[]とredirect_to => redirect_toの時点でflashが消えるため、flash自体表示されない
  • flash.now[]とrender => 次のページへリダイレクトした時点でflashは消える

最後に

Railsチュートリアルで丁寧に説明されていた箇所なんですが、実際に自分で試行錯誤して初めて理解できました

何かおかしい部分があれば指摘して頂けると嬉しいです

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

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

Ruby ◯◯進数の扱いについて

10進数から(n)進数へ変換

10進数.to_s(n)

255.to_s(2) => "11111111"
255.to_s(8) => "377"
255.to_s(16) => "ff"

(n)進数から10進数へ変換

"n進数".to_i(n)

"11111111".to_i(2) => 255
"377".to_i(8) => 255
"ff".to_i(16) => 255

8進数を10進数に => oct
16進数を10進数に => hex

8進数.oct
16進数.hex

"377".oct => 255
"ff".hex => 255

(n)進数の頭に付けて10進数に変換
0b => 2進数
0 => 8進数
0x => 16進数

0b2進数
08進数
0x16進数

0b11111111 => 255
0377 => 255
0xff => 255

計算方法

10進数  2進数
0        0
1        1
2        10
3        11
4        100
5        101
6        110
7        111
8        1000
9        1001
10       1010
11       1011
12       1100

2進数1100を10進数に

(1 * 2**3) + (1 * 2**2) + (0 * 2**1) + (0 * 2**0)

= 8 + 4 + 0 + 0

= 12

12を10進数に

12 / 2 = 6..0
6 / 2 = 3..0
3 / 2 = 1..1
1 / 2 = 0..1

余りを下から取って1100

8進数と16進数の計算方法は後日また記載します。

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

【Ruby】外部ファイル読み込み require・require_relative・load・autoloadの違い

外部ファイル読み込みの違いを明確にしたかったのでまとめてみました。

require("絶対パスまたは相対パス")

ライブラリや外部ファイルを絶対パス、相対パスで読み込む(1回のみ読み込み)

require_relative("絶対パス")

ライブラリや外部ファイルを絶対パスで読み込む(1回のみ読み込み)
 

load("絶対パスまたは相対パス")

ライブラリや外部ファイルをファイルを絶対パス、相対パスで読み込む(再読み込み、拡張子は省略できない)
loadはrequireやrequire_relativeよりも再読み込みするため遅くなる
 

autoload("クラス/モジュールの定数", "絶対パスまたは相対パス")

定数を最初に参照した時に第2引数を絶対パス、相対パスで読み込む(1回のみ読み込み)
クラス・モジュールの遅延ロードを実現

まとめ

モジュール関数 パスの仕方 読み込み回数    遅延ロード       
require 絶対・相対パス 1回 ✖︎
require_relative 絶対パス 1回 ✖︎
load 絶対パス、相対パス 再読み込み ✖︎
autoload 絶対パス、相対パス 1回

参考
Ruby 2.7.0 リファレンスマニュアル

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

【Rails】SQliteでのDBの内容確認周りのコマンドまとめ(追加していく所存)

【Rails】SQliteでのDBの内容確認周りのコマンド

モデルのカラム数の確認

terminal
>rails console

>モデル名.new

↓こんな感じ
ちゃんとカラム作れてるかとかの確認に良きです
スクリーンショット 2020-03-29 14.11.43.png

モデルの内容確認(全ては見れません)

terminal
> rails console

> モデル名.all

↓こんな感じ
スクリーンショット 2020-03-29 14.15.08.png

モデルの特定のidをもつデータのもつ内容の確認

terminal
> rails console

> モデル名.find(特定のid)

↓こんな感じ
スクリーンショット 2020-03-29 16.16.49.png

モデルの特定のカラム内容をもつ行の確認

terminal
> rails console

> モデル名.find_by(カラム名: "文字列や数字")

↓こんな感じ
スクリーンショット 2020-03-29 17.22.40.png

投稿の数やユーザーの数の確認

terminal
> rails console

> モデル名.select("調べたいカラム名").count

例えば、Userモデルに登録されているユーザーの数を知りたいとき

terminal
> rails c

> User.select("id").count

この結果
irb(main):007:0> User.select("id").count
   (1.5ms)  SELECT COUNT("users"."id") FROM "users"
=> 1
↑ユーザーの数が1だとわかる

例えば、Postモデルに登録されている投稿の数を知りたいとき

terminal
> rails c

> Post.select("id").count

この結果
irb(main):007:0> User.select("id").count
   (1.5ms)  SELECT COUNT("posts"."id") FROM "users"
=> 27
↑ユーザーの数が1だとわかる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby on Rails】Rails超入門2❗️凄くて震えるvalidation‼️scaffoldで恐竜登録アプリ作成2

1.validationとは

コマンド一つでcrudアプリが作れる時点で十分凄いのだが、
この恐竜登録には、少し問題がある。
それは、何回でも同じ名前の恐竜が登録できてしまったり、
全て空のデータが登録できてしまったりする事だ。

その理由として、モデル生成の時、rails側で用意したこちらIDという項目がモデルで定義したテーブルに付与され、さらにこのIDがキーになっているためだと思われる。

上記の事態を避けるために、データ登録時に制約を追加しなくてはならない。

例えば以下のルールを与えてみる。

  • 生成恐竜名(dname0)と生成タイプ(dtype0)は入力必須
  • hflgにチェックを入れた時(ハイブリッド型)は、生成元恐竜1(sname1)、生成元恐竜2(sname2)、生成元タイプ1(stype1)、生成元タイプ2(stype2)は必須
  • hflgにチェックを入れない時(非ハイブリッド型)は、生成元恐竜1(sname1)、生成元恐竜2(sname2)、生成元タイプ1(stype1)、生成元タイプ2(stype2)は空にする
  • 登録済み生成恐竜名と生成タイプを入力した時は登録しない。
  • 生成恐竜タイプは、normal,rare,epic,regend,uniqueとする。
  • 生成元恐竜タイプ1と生成元恐竜タイプ2は、normal,rare,epic,regendとする。

このモデルで定義したテーブルに登録するために入力データに設けるルールをvalidationというらしい。

2.validation実装

ActiveRecordバリデーション
を参考に実装してみた。

railsアプリのディレクトリ\app\models\dinosor2.rb
class Dinosor2 < ApplicationRecord
# dname0は入力必須。
    validates :dname0, presence: true
# dtype0はnormal,rare,epic,regendの何れかを指定
    validates :dtype0, inclusion: { in: %w(normal rare epic regend unique) }
# dname0とdtype0の組み合わせでユニーク
    validates :dname0, uniqueness: { scope: :dtype0 }
# hflgにチェックが入っているとき、stype1はnormal,rare,epic,regendの何れかを指定
    validates :stype1, inclusion: { in: %w(normal rare epic regend) } , if: '!hflg.blank?'
# hflgにチェックが入っているとき、sname1は入力必須
    validates :sname1, presence: { if: '!hflg.blank?' }
# hflgにチェックが入っているとき、stype2はnormal,rare,epic,regendの何れかを指定
    validates :stype2, inclusion: { in: %w(normal rare epic regend) } , if: '!hflg.blank?'
# hflgにチェックが入っているとき、sname2は入力必須
    validates :sname2, presence: { if: '!hflg.blank?' }
# hflgにチェックが入っていないとき、stype1,sname1,stype2,sname2は空にする。
    validates :stype1,:sname1,:stype2,:sname2, absence: { if: 'hflg.blank?' }
end

3.viewの変更

下記のviewを修正したのはdtype0,stype1,stype2の入力欄の前に、
タイプ欄を表示するようにした。
エラーメッセージを見るとわかるのだがリスト内の定義が見えないと入力が難しいためだ。

_form.html.erb
<%= form_with(model: dinosor2, local: true) do |form| %>
  <% if dinosor2.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(dinosor2.errors.count, "error") %> prohibited this dinosor2 from being saved:</h2>

      <ul>
      <% dinosor2.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>
  <% d_type = "(normal,rare,epic,regend,unique)" %>
  <% s_type = "(normal,rare,epic,regend)" %>
  <div class="field">
    <%= form.label :dname0 %>
    <%= form.text_field :dname0, id: :dinosor2_dname0 %>
  </div>

  <div class="field">
    <%= form.label :dtype0 %>
    <%= d_type %><br>
    <%= form.text_field :dtype0, id: :dinosor2_dtype0 %>
  </div>

  <div class="field">
    <%= form.label :sname1 %>
    <%= form.text_field :sname1, id: :dinosor2_sname1 %>
  </div>

  <div class="field">
    <%= form.label :stype1 %>
    <%= s_type %><br>
    <%= form.text_field :stype1, id: :dinosor2_stype1 %>
  </div>

  <div class="field">
    <%= form.label :sname2 %>
    <%= form.text_field :sname2, id: :dinosor2_sname2 %>
  </div>

  <div class="field">
    <%= form.label :stype2 %>
    <%= s_type %><br>
    <%= form.text_field :stype2, id: :dinosor2_stype2 %>
  </div>

  <div class="field">
    <%= form.label :hflg %>
    <%= form.check_box :hflg, id: :dinosor2_hflg %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

4.検証してみた

ハイブリッド恐竜登録成功
make_scaffold2_resist_hibrid.jpg

(1)登録済みの恐竜チェック
scaffold_dino2_unique.jpg

「同じものを登録したらいけないよ。」

(2)非ハイブリッド恐竜のチェック

scaffold_dino2_nohibrid_err.jpg
「stype1,stype2,sname2,sname1は空であるべきだ。そうは思わないか?」

(3)ハイブリッド恐竜のチェック

scaffold_dino2_hibrid_err.jpg

「stype1,sname1,stype2,sname2は、入力必須だ。ハイブリッドを手に入れたいだろ?」

(4)非ハイブリッド恐竜のタイプチェック
scaffold_dino2_nohibrid_inclusion_err.jpg
「恐竜のタイプはリスト内のものと決まっているんだ。これはルールさ」

(5)ハイブリッド恐竜の生成元恐竜タイプチェック
scaffold_dino2_hibrid_inclusion_err.jpg
「生成元恐竜のタイプはリスト内のものと決まっているんだ。これはルールさ」

①弾いてる!!条件に合わないって!!!

②しかもなんだか、エラー画面もスタイリッシュ!!

私の大好きな赤というのがいい。Red is god!!

③声の低いイケメンに注意されているみたい←!?

4.まとめ

PHP版恐竜登録ツール
と比較すると、作成工数が大幅に削減されている。
たった一つのコマンドと、少しのvalidationでwebアプリが作れる。
『世界よ。これがRuby on Railsだ』と言ったところだろう。

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

部分テンプレートを使ってみよう!

部分テンプレートに関してまだまだ知識量が足りていないと感じたため備忘録として記述します。

部分テンプレートとは?

みなさんはビューを作成する際にこんなことって考えたことありますか?

「ここの要素は別ページの要素と同じだから切り取って使いたいな〜」

コピペすれば良いじゃないかと思うかもしれませんがプログラミングにあたってより少ないコードでシンプルにかければ素晴らしいことこの上ないです。

そんな時に部分テンプレートを使用してスッキリとしたコードを記述しましよう!

部分テンプレートの構成に関して

部分テンプレートファイルを作成する際には『_(アンダーバー』をファイル名のトップに記述しましょう。要するにこちらのアンダーバーが表記あるファイルが子ファイルになり、親ファイルから呼び出されます。

親ファイルへの記述方法

親ファイルへの記述方法としては以下のメソッドとオプションを使用して記述して部分テンプレートを呼び出します。

renderメソッド

一番メインになる記述です。renderメソッドは、部分テンプレートを呼び出す際に利用するメソッドです。

partialオプション

renderメソッドとセットで使用することが多いです。明示的に部分テンプレートを指定して、表示させる役割を持っています。以下の場合は"_sample.html.erb"という部分テンプレートファイルを呼び出しています。

<% render partial: "sample" %>

localsオプション

localsオプションを使用することで部分テンプレートファイル内で設定したその変数を使用できるようになります。

<% render partial: "sample", locals: { post: "hello!" } %>

local部分に記載がある変数postは、post = "hello!"と同様の意味を持つ記述です。

まとめ

部分テンプレートを使用するメリットとしては

・繰り返しかくコード記述を減らすことができる

・修正時に修正箇所が少なくて済む

ある意味変数的役割を果たしてくれているのが部分テンプレートなんですね!偉大!

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

【Rails】お気に入り登録をした順に投稿を表示する方法

はじめに

本を投稿するアプリケーションにおいて、お気に入り機能を実装しました。
お気に入り本リストでの表示において、本の投稿順ではなく、
「お気に入り登録をした順番」で表示する方法をまとめます。
※お気に入り機能自体の実装には触れません。

本投稿順での表示になるパターン

usersコントローラーにおいて@favorite_booksを取得し、
ユーザー詳細ページで表示するためにビューに渡します。

users_controller.rb(抜粋)
def show
  @user = User.find(params[:id])
  @favorite_books = @user.favorite_books
end

今回は記載を省略していますが、UserモデルとFavoriteモデルの関連付けができているため、
@user.favorite_booksにより、そのユーザーがお気に入りした本を取得することができます。
参考:【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】?
↑この記事に沿って中間テーブルを設けて実装を行いました。

ビューにおいて@favorite_booksを展開します。
_book.html.slimのパーシャルを準備し、@favorite_booksの中身を順に表示する形です。

show.html.slim(抜粋)
- if @favorite_books.any?
  - @favorite_books.each do |book|
    = render 'books/book', { book: book }

上記のように実装を行うと、ユーザー詳細ページにおけるお気に入り本リストの表示順が、
本の投稿順となります。
.favorite_booksにより一発でBookオブジェクトに変換されており、
primary_keyであるid(ここではBookオブジェクトのid)の順番で表示がされるためです。
よって、@favorite_books = @user.favorite_books.order(created_at: "DESC")
といった風に順番を指定しても、あくまで本の投稿順が降順に変更されるだけです。

お気に入り登録をした順に表示させる方法

.favorite_booksにより一発でBookオブジェクトに変更するのではなく、
一度Favoriteオブジェクトへ変換しそこで順番の調整を行った上で、
FavoriteオブジェクトからBookオブジェクトを取り出す方法により実現できます。

users_controller.rb(抜粋)
def show
  @user = User.find(params[:id])
  @favorite_books = @user.favorites.order(created_at: "DESC").map{|favorite| favorite.book}
end

まず、@user.favoritesによりそのユーザーに関連付いたFavoriteオブジェクトを取り出し、
order(created_at: "DESC")でそれを降順に並び替えた上で、
mapメソッドを使用することによりBookオブジェクトへの変換を行なっております。

上記方法により、お気に入りリストにおいて「お気に入り登録をした順」にて本が表示されるようになります。
(他にもっといい方法がある気がしてなりません。コメントいただければ幸いです!)

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

if文を1行で書く場合

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

例えば配列に対して条件分岐をさせたい場合

numbers.each do |n|
  if n % 2 == 0
    'OK'
  else
    'NG'
  end
end

こんな感じになるかと思いますが

numbers.each do |n|
 n % 2 == 0 ? 'OK' : 'NG'
end

これでも同じ結果が得られます。

条件 ? true : false

if else end を記述しないのでリファクタリングにも使えますね。

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

インストールするgemのリモートリポジトリを追加する

環境

Ubuntu 16.04.5 LTS
Windows10
Vagrant

手順

gemをインストールする時、gem installでインストールするが、どこからインストールしているのだろうか?ダウンロード元のリモートリポジトリを確認するコマンドがあります。

$gem sources -l
https://rubygems.org/

では、rubygems にないgemをインストールしたい場合はどうすればいいのだろうか?
インストールしたいgemが、GitHubにある場合、リモートリポジトリのリストにGitHubを追加します。

$gem sources -a http://gems.github.com/
$gem sources -l
https://rubygems.org/
http://gems.github.com/

これで、インストールしたいgemが、rubygemsになかった場合は、githubを検索して、ダウンロードしてくれるようになります。

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

【Rails】SorceryでTwitter認証

はじめに

Sorceryを使ったTwitterログイン認証の設定です。
途中までは【Rails】Sorceryでfacebook認証 Sorceryの設定と同じなので、そちらをご覧ください。

動作環境

ruby 2.5.3
Rails 5.2.4.2
sorcery 0.14.0

前提

SSL化はしなくていいです。

設定

Sorceryの設定

config/sorcery.rb
  config.twitter.key = Rails.application.credentials.dig(:twitter, :key)
  config.twitter.secret = Rails.application.credentials.dig(:twitter, :secret_key)
  config.twitter.callback_url = 'http://localhost:3000/oauth/callback?provider=twitter'
  config.twitter.user_info_mapping = {
    twitter_id: 'id',
    name: 'name',
    description: 'description'
  } # Userモデルの属性名: 'twitterのパラメータ'

ちなみに、今回はTwitter認証しか使わないので、Userモデルにはパスワードカラムは用意していません。
Twitterのscreen_name(@usernameというやつ)は変わる可能性があるので、twitter_idカラムにはidを格納することにしました。

Twitter Developperの設定

「Enable Sign in with Twitter」にチェックを入れます。
Callback URLsは以下のベストプラクティスに則り、http://127.0.0.1:3000/oauth/callbackにします。

Do not add query strings to your callback URLs in your Twitter app’s configuration
Don’t use localhost as a callback URL
Callback URLs — Twitter Developers

image.png

上のCallback URLではクエリ文字列が使えないので、Sorceryの設定のcallback_urlでクエリを渡します。
すると、認証画面のURLにoauth_callbackというクエリが渡され、クエリを持ったコールバックURLを設定されていることがわかります。
image.png

認証画面のURL
https://api.twitter.com/oauth/authenticate?oauth_callback=http%3A%2F%2Flocalhost%3A3000%2Foauth%2Fcallback%3Fprovider%3Dtwitter&oauth_token=EHBQ4wAAAAABDRHmAAABcRr4LwQ

キャンセル後のリダイレクト

今の状態では、「連携アプリをキャンセル」を押したときにエラーが出てしまうので、それを解消します。
スクリーンショット 2020-03-28 23.41.01.png
「(アプリ名)に戻る」を押すと401エラーになってしまいます。

Started GET "/oauth/callback?provider=twitter&denied=gZtRIQAAAAABDRHmAAABcSN_g4w" for 127.0.0.1 at 2020-03-29 08:34:55 +0900
Processing by OauthsController#callback as HTML
  Parameters: {"provider"=>"twitter", "denied"=>"gZtRIQAAAAABDRHmAAABcSN_g4w"}
Unpermitted parameter: :denied
Completed 500 Internal Server Error in 400ms (ActiveRecord: 0.0ms)

OAuth::Unauthorized - 401 Authorization Required:
  app/controllers/oauths_controller.rb:12:in `callback'
キャンセル後のparams
<ActionController::Parameters {"provider"=>"twitter", "denied"=>"PsylCwAAAAABDRHmAAABcSGWp1Q", "controller"=>"oauths", "action"=>"callback"} permitted: false>

キャンセルした場合はparams[:denied]が存在するので、条件分岐で処理します。

app/controllers/oauths_controller.rb
class OauthsController < ApplicationController
  def oauth
    login_at(auth_params[:provider])
  end

  def callback
    provider = auth_params[:provider]
    if auth_params[:denied].present?  # ここの節を追加
      redirect_to root_path, notice: 'ログインをキャンセルしました'
      return
    end
    if (@user = login_from(provider))
      redirect_to root_path, notice: "#{provider.titleize}でログインしました"
    else
      begin
        @user = create_from(provider)
        reset_session
        auto_login(@user)
        redirect_to root_path, notice: "#{provider.titleize}でログインしました"
      rescue StandardError
        redirect_to root_path, alert: "#{provider.titleize}でのログインに失敗しました"
      end
    end
  end

  private

  def auth_params
    params.permit(:code, :provider, :denied)
  end
end

以上で設定は終了です!
以下は、ちょっと試してみたことやエラーとの奮闘になります。

callback_urlを設定しない場合

For OAuth 1.0a compliance this parameter(注釈:oauth_callbackのこと) is required .
POST oauth/request_token — Twitter Developers

ということなので、callback_urlを設定しないとどうなるのか試してみた。

config/sorcery.rb
  # config.twitter.callback_url = 'http://localhost:3000/oauth/callback?provider=twitter'
認証画面のURL
https://api.twitter.com/oauth/authenticate?oauth_callback&oauth_token=vyYZbQAAAAABDRHmAAABcRsW6zQ

クエリを見ると、oauth_callbackに値が入っていない。
認証を押すと、PINコードの入力を求められ、リダイレクトしなくなってしまった。
image.png

認証について

ところで、認証画面のURLを見ると、request_tokenというクエリも渡されていることがわかります。このrequest_tokenPOST oauth/request_tokenによって取得されています。

Allows a Consumer application to use an OAuth request_token to request user authorization.
参考:GET oauth/authenticate — Twitter Developers

深掘りしようとすると沼にはまりそうなので、この辺りにとどめておきます。Sorceryがよしなにやっている部分です。
参考:[Python] OAuth認証でTwitter連携/ログインを実装する
   Twitter REST APIの使い方 アクセストークンの取得

エラーいろいろ

NoMethodError at /oauth/callback

認証後に/oauth/callbackに行かない。
image.png

NoMethodError at /oauth/callback
undefined method `original_callback_url' for nil:NilClass
コールバック時のURL
http://127.0.0.1:3000/oauth/callback?provider=twitter&oauth_token=EHBQ4wAAAAABDRHmAAABcRr4LwQ&oauth_verifier=zFvTO1ay9G316hmbPMcbexTfckA45M4j

画面ばかり見て「どうしてoauthsアクションに飛んでいるのだろう」と思っていたのですが、そもそもNoMethodErrorでした。

解決法

config/routes.rb
  post "oauth/callback", to: "oauths#callback"
  get 'oauth/callback', to: 'oauths#callback' # この行がなかったので追加
  get "oauth/:provider", to: "oauths#oauth", as: :auth_at_provider

Mysql2::Error - Field 'twitter_id' doesn't have a default value:

Started GET "/oauth/callback?provider=twitter&oauth_token=qvWE_gAAAAABDRHmAAABcSFi3bQ&oauth_verifier=nUb5kYYSryTHA03FepYw2xSLNzVFMmTc" for 127.0.0.1 at 2020-03-28 22:44:22 +0900
Processing by OauthsController#callback as HTML
  Parameters: {"provider"=>"twitter", "oauth_token"=>"qvWE_gAAAAABDRHmAAABcSFi3bQ", "oauth_verifier"=>"nUb5kYYSryTHA03FepYw2xSLNzVFMmTc"}
Unpermitted parameters: :oauth_token, :oauth_verifier
  Authentication Load (0.8ms)  SELECT  `authentications`.* FROM `authentications` WHERE `authentications`.`uid` = '1048451188209770497' AND `authentications`.`provider` = 'twitter' ORDER BY `authentications`.`id` ASC LIMIT 1
  ↳ app/controllers/oauths_controller.rb:8
   (0.2ms)  BEGIN
  ↳ app/controllers/oauths_controller.rb:13
  User Exists (3.4ms)  SELECT  1 AS one FROM `users` WHERE `users`.`uuid` = '7gl0ZsQ_tTbl' LIMIT 1
  ↳ app/models/user.rb:21
  User Create (1.5ms)  INSERT INTO `users` (`uuid`, `name`, `description`, `created_at`, `updated_at`) VALUES ('7gl0ZsQ_tTbl', 'aiandrox', '小学校の先生やってた。エンジニア目指してRailsとか頑張ってる。#RUNTEQ 1月生。謎解き好き。', '2020-03-28 22:44:24', '2020-03-28 22:44:24')
  ↳ app/controllers/oauths_controller.rb:13
   (0.2ms)  ROLLBACK
  ↳ app/controllers/oauths_controller.rb:13
Completed 500 Internal Server Error in 1349ms (ActiveRecord: 42.5ms)

Mysql2::Error - Field 'twitter_id' doesn't have a default value:
  app/controllers/oauths_controller.rb:13:in `callback'

データベース側でtwitter_idカラムにnull: false制約をかけていたので、このエラーが出ました。
確かにログを見るとtwitter_idカラムには何も入っていない。

解決法

config/sorcery.rb
  config.twitter.user_info_mapping = {
-   twitter_id: 'user_id',
+   twitter_id: 'id',
    name: 'name',
    description: 'description'
  }

Twitterから取得するデータのパラメータが間違っていました。
参考:User Object(ユーザーオブジェクト)の説明
(メールアドレスは取得できないのだろうか。どなたかご存知でしたら、ご教示ください)

おわりに

特別な内容ではありませんが、参考になれば幸いです。
ご指摘などがございましたら、コメントか編集リクエストでお願いいたします。

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

ActiveRecord のコールバックを特定のコントローラーの処理のときのみ実行する

はじめに

ActiveRecord のコールバックを、特定のモデルの状態というよりは、特定のコントローラーの処理の場合のみ実行したい場合について考えたので、簡単にやり方をまとめます。

tl;dr

方針としては、コントローラーにモデルのインスタンス変数でフラグを付けてあげて、そのフラグが立っている場合のみ実行するという形にしました。

今回は Post モデルの statuscreate 時にのみ created とつけることを考えます。

やり方

モデルにattr_accessor と after_save の設定

まず、Post モデルにステータス更新用の update_status メソッドを定義します。

また、インスタンス変数のフラグを立てるメソッドcreate_executed! メソッドとフラグ判定メソッド create_executed? メソッドも作っておきます。

そして、after_save コールバックで、create_executed?true のときだけ update_status メソッドを実行する形にしました。

class Post < ApplicationRecord
  after_save :update_status, if: :create_executed?

  def create_executed!
    @create_executed = true
  end

  def create_executed?
    @create_executed == true
  end

  def update_status
    update_columns(status: 'created') 
  end 
end

コントローラーに設定

コントローラー側では、create メソッドのときに、フラグを立てるメソッドを実行すればOKです。
これにより、create メソッドの場合のみ、after_save のコールバックを呼ぶことができます。

class PostsController < ApplicationController
  # 中略

  def create
    @post = Post.new(post_params)
    @post.create_executed!
    if @post.save 
      redirect_to posts_path
    else
      render :new
    end
  end

  # 中略
end

おわりに

after_save コールバックには、validates メソッドのon: のようなコンテキストが使えなかったのでどうしようか悩んでいたのですが、一旦この形でできました。

他にいい方法ご存知でしたら教えて下さい!

参考

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