20210430のRubyに関する記事は15件です。

[Rails]ransackインストール

はじめに ransackを利用したのでインストール方法を記しておきます。 ransackのインストール ransackをGemfileに追加します。 Gemfile gen 'ransack' $ bundle install
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Ruby初心者向け】 if文とCase文を使い分けよう!

はじめに この記事は、Ruby/Railsの学習期間約1ヶ月の著者が書いた初投稿ものです。 謝った解釈があるかもしれませんので、その際は修正コメント等いただけると幸いです。 今回の記事は、条件分岐を学習している際に、ifとcaseのどちらを使えばいいか悩んだため、これから学習を始める方々に少しでもお力になれればと思い書きました。 (※一部訂正致しました) 環境 macOS Big Sur 11.3 Rubyバージョン:2.7.2 if文について if文は、「もし〜だったら〜の処理をする」といいった条件に一致(true)した時に処理を実行します。 イメージは複数です。 メリットは3つ - 書きやすい - 複数の比較対象を組み合わせることができる - 複雑な条件を扱うのに適している if "条件" p "true" else p "false" end elseは省略可で、なくても大丈夫です。 elsifを使用して、複数の条件を書くこともできます。 if "条件" p "条件1がtrueの時" elsif "条件2" p "条件2がtrueの時" elsif "条件3" p "条件3がtrueの時" else p "上記の条件に該当しないfalseの時" end case文について case文は、1つの比較対象に対して複数の条件を判定する時に実行します。 イメージは単体です。  メリットは2つ - シンプルなコードを実現できる - 可読性の高い書き方ができる case "1つの比較対象" when "値1" p "値1に一致" when "値2" p "値2に一致" wehn "値3" p "値3に一致" else p "一致しない時" end if文とcase文の比較 ここで3つの例を比較してみましょう。 1、まずは年齢です if文の場合 age = 20 if age >= 65 p "高齢者です" elsif age >= 20 p "成人です" elsif age >= 2 p "子供です" else p "幼児です" end caseの場合 age = 20 case age when 65..100 p "高齢者です" when 20..64 p "成人です" when 2..19 p "子供です" else p "幼児です" end その年齢1つに絞ることで、シンプルで可読性がありますね。 2、次に信号の色を例にしてみます。 if文の場合 color = "red" if color == "red" p "止まれ" elsif color == "yellow" p "注意して進むか止まれ" elsif color == "blue" p "進め" else "該当しません" end case文の場合 color = "red" case color when "red" p "止まれ" when "yellow" p "注意して進むか止まれ" when "blue" p "進め" else p "該当なし" end if文はコードの量が多いですが、case文はシンプルですね。 3、時間(24時間)を例にすると... if文の場合(訂正致しまいた) time = 7 if (0..5) === time p "深夜です" elsif (6..11) === time p "午前中です" elsif (12..18) === time p "午後です" elsif (9..23) === time p "夜です" else p "そんな時間ありません" end case文の場合 time = 7 case time when 0..5 p "深夜です" when 6..11 p "午前中です" when 12..18 p "午後です" when 19..23 p "夜です" else p "そんな時間ありません" end こちらは、1つの比較対象(ここでは6という時間)に対して判定しているcase文の方がシンプルですね! 結論 if文とcase文にはどっちが正しいと言った明確なルールはなく、どちらでも使うことができます。 ただし、今回の3つの例からもわかる通り、条件によっては可読性が変わっていきます。 けれども、おおよそは比較対象の数複数(if)or単体(case)で判別することができると思います。 また、現職のエンジニアの人の話によると、使い分けは人それぞれの好みで、書きやすい方で書くのが良いとのことでした。 基本的には可読性を重視して書く事を意識するが大事だと思いました。 さいごに 今後もRuby/Railsを学習する過程で、理解を深めていき、有益な情報を発信できればと思いますので、良ければLGTMお願いします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rspecでメール内にあるURLを取得する

メールについてのRspecを書くときに詰まったので備忘録代わりに記事にします! やりたいこと SystemSpeec内でAction Mailerを使って送信されたメール本文のリンクURLを取得して利用したい ヘルパーを使ったxxx_pathをしようと思ったが、今回はパスワードリセットで、deviseの処理で生成されたパスワードリセットトークンを含んだURLがほしかった。 結論 URI.extract(str)を使って文字列からURLを抽出する https://docs.ruby-lang.org/ja/latest/method/URI/s/extract.html 実際のコード(deviseを利用したパスワードリセットのテスト) visit 'members/password/new' expect(page).to have_content("パスワードを忘れましたか?") find('#xxxxx').set('example@example.com') click_button("パスワードの再設定方法を送信する") # システム上クリックされた後にタイミングでメールが送信される expect(page).to have_content('xxxxxxx') 本題部分 # これで配信したメールのインスタンスを取得できる password_reset_mail = ActionMailer::Base.deliveries.last # 本文をエンコードして取得 mail_body = password_reset_mail.body.encoded # 文字列からURLの配列で取得。今回はURLは1つだけなので1番目を取得 password_reset_url = URI.extract(mail_body)[0] visit password_reset_url expect(page).to have_content('パスワードを変更') この後実際にフォームを入力させたりする もっといい方法があればぜひコメントいただければと思います!! 参考 Action Mailer の基礎 | Rails ガイド Mailer specs - RSpec Rails - RSpec - Relish ingleton method URI.extract
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

seedを使って、DBにデータをインポートする

seedファイルとは seedファイルは、初期データをDBに入れる時に使うファイルのことです。 何かのアプリを作成する場合に、作成したDBにデータ(ユーザ登録データなど)を入れる際に使います。 1件づついれる方法もあれば、CSVファイルなどでデータをまとめてインポートすることもできるので、何かと重宝するファイルです。 この記事では、私が学習した基本的なseedの使い方とCSVファイルをインポートする方法をアウトプットしていきます。 なお、テーブルの作成やマイグレーションファイルの作成方法などは省略します。 学習環境 MacOS BigSur Ruby 2.6.5 Ruby on rails 6.0.3.6 Mysql 5.6.51 基本形 ここでは、「Hansoku」という名前のモデルを作成しており、マイグレーションファイルには、 20210416141031_create_hansokus.rb class CreateHansokus < ActiveRecord::Migration[6.0] def change create_table :hansokus do |t| t.string :hansoku_name, null: false t.integer :damage, null: false t.string :status, null: false t.timestamps end end end と記載しているとします。 そのモデルに対して、カラム名に応じて、保存したい値をseed.rbに以下のようにcreateメソッドの引数に保存したい値を定義してあげます。 seed.rb Hansoku.create( :hansoku_name => "金テキ", :damage => 100, :status => "動けない" ) この形が基本的な形です。 個別に複数作成する場合は、配列形式にしてやり、 seed.rb Hansoku.create[( :hansoku_name => "金テキ", :damage => 100, :status => "動けない" ),( :hansoku_name => "目つぶし", :damage => 60, :status => "動けない" )] などと記載してあげます。 CSVファイルをインポートする 次に、大量のデータをDBに初期データとして導入する方法として、CSVファイルをインポートする方法があります。 seedファイルにCSVファイルを読み込み、DBにインポートするのです。 require 'csv' CSV.foreach('db/hansoku.csv', encoding: 'Shift_JIS:UTF-8') do |hansoku| Hansoku.create( :hansoku_name => hansoku[0], :damege => hansoku[1], :status => hansoku[2], ) end 標準ライブラリのCSVライブラリを呼び出し、.foreachメソッドforeachメソッドについてはこちらを使用します。 このメソッドは、CSVファイルの各行がブロック変数に渡され、モデルに定義したカラムを「,」区切りのCSVファイルの左から順番に配列の添字で指定することができます。 上記コードで説明すると、 :hansoku_name => hansoku[0],   # インポートするCSVファイルの最初のカラムはモデルの「:hansoku_name」へ :damage => hansoku[1],   # インポートするCSVファイルの2番目のカラムはモデルの「:damage」へ :status => hansoku[2] # インポートするCSVファイルの3番目のカラムはモデルの「:status」へ といった感じでインポートされることになります。 なお、CSVファイルは指定されていないと文字コードのエンコード方式がshift-jisになっているので、エンコーディングが必要な場合は、変換させないとエラーが発生します。 最後にこのコマンド seedファイルに記載した後は、 rails db:seed のコマンドを実行してあげると、seedに書かれた内容をDBに反映させることができます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails】Error: unrecognized cop Enabled found in .rubocop.ymlのエラー(メモ)

エラー文詳細 ターミナルにて% bundle exec rubocopを実行時に以下が出力。 Error: unrecognized cop Enabled found in .rubocop.yml .rubocop.yml内で何かが有効になっていない? 解決方法 Gemfileにrubocop-railsを追加しターミナルで% bundle installを行なった場合、 .rubocop.ymlにrequire: rubocop-railsを追加する。 以下、例 AllCops: TargetRubyVersion: 2.6.5 DisabledByDefault: true Exclude: - db/schema.rb - vendor/bundle/**/* - node_modules/**/* require: - rubocop-rails Bundler/OrderedGems: Enabled: true Layout/EmptyLines: Enabled: true Layout/TrailingEmptyLines: Enabled: true Layout/TrailingWhitespace: Enabled: true Style/MethodDefParentheses: Enabled: true Style/StringLiterals: EnforcedStyle: single_quotes Style/StringLiteralsInInterpolation: EnforcedStyle: single_quotes 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【新人プログラマ応援】メソッドは呼ぶ側を手前に、呼ばれる側を後ろに定義しよう

はじめに これはQiitaで開催されている「新人プログラマ応援 - みんなで新人を育てよう!」イベントの投稿記事です。 今回はメソッドの定義順に関する小ネタを書いてみようと思います。 なお、この記事はRuby版とJavaScript版の2部構成になっています。 JavaScriptを使ったコード例はRuby版のあとに載せています。 ?JavaScript版を読む おことわり この記事ではメソッドの定義順を変えてもエラーが出ないケースを議論の対象とします。 文法の制約上、特定の定義順で書かないと動かないケースは議論の対象としません。 Ruby版 メソッドの定義順は基本的に自由、だが・・・ メソッドの定義順は基本的に自由です。 class Foo def a end def b end def c end end と書いてもいいですし、 class Foo def b end def c end def a end end と書いてもエラーにはなりません。 しかし、コードを読むときは呼ぶメソッドを手前に、呼ばれるメソッドを後ろに定義した方が読みやすいです。 つまり、こういうことです。 # 呼ぶ側を手前に、呼ばれる側を後ろに定義した場合 class Foo def a b end def b c end def c end end これがバラバラになっていると、コードを読むときのリズムが崩れて読みづらくなります。 # 呼ぶ側を手前に、呼ばれる側を後ろに定義した場合 class Foo def b c end def c end def a b end end 童謡を使って考えてみる とはいえ、上のような意味のないコード例だといまいちピンとこないかもしれません。 そこで「あんたがたどこさ」という童謡の歌詞を拝借して、メソッドの定義順の良し悪しを考えてみることにします。 メソッドの定義順がバラバラの場合 最初はメソッドの定義順がバラバラになっているケースです。 以下のコードでplayer.playを実行したときの実行結果がどうなるか、ぱっとわかるでしょうか? playメソッドから順に最後までメソッド呼び出しがどのように進むか、ぜひ目で追いかけてみてください。 class MusicPlayer def play あんたがたどこさ end private def 肥後どこさ 熊本さ end def あんたがたどこさ 肥後さ end def 熊本さ puts "熊本どこさ?" end def 肥後さ 肥後どこさ end end player = MusicPlayer.new player.play どうですか?実行結果がわかりましたか? はい、そうですね、playメソッドを呼び出すと"熊本どこさ?"の文字列が出力されます。 player.play #=> 熊本どこさ? 「呼ぶ側を手前に、呼ばれる側を後ろ」のルールで定義した場合 ですが、もしメソッドの定義順が次のようになっていたらどうでしょうか? 先ほどと同じようにplayメソッドから順に最後までメソッド呼び出しがどのように進むか、目で追いかけてみてください。 class MusicPlayer def play あんたがたどこさ end private def あんたがたどこさ 肥後さ end def 肥後さ 肥後どこさ end def 肥後どこさ 熊本さ end def 熊本さ puts "熊本どこさ?" end end player = MusicPlayer.new player.play playメソッドを呼び出すと"熊本どこさ?"の文字列が出力されるのは先ほど同じですが、「呼ぶ側を手前に、呼ばれる側を後ろ」のルールで定義した方がコードが読みやすくなったのではないでしょうか? Ruby版のまとめ というわけで、この記事では「メソッドは呼ぶ側を手前に、呼ばれる側を後ろに定義しよう」という話を書いてみました。 もちろん、メソッドの定義順は絶対的な正解があるわけではないですし、読みやすさは主観によるので「ワシはメソッドの定義順なんて何でもかまわん!」という人も中にはいるでしょう。 とはいえ、僕の周りの熟練プログラマに話を聞くと、ほとんどの人が「呼ぶ側を手前に、呼ばれる側を後ろ」で書いた方がわかりやすいと答えています。 そもそもRubyだって普通はpublicメソッドを手前に、privateメソッドを後ろに定義にするので、これも「呼ぶ側を手前に、呼ばれる側を後ろ」のルールになっていますよね。 class MusicPlayer # publicメソッドはprivateメソッドを呼ぶ(手前に定義) def play あんたがたどこさ end private # privateメソッドはpublicメソッドに呼ばれる(後ろに定義) def あんたがたどこさ 肥後さ end # 以下略 end というわけで、新人プログラマの方で「メソッドってどういう順番で定義したらいいのかな〜??」と悩んでいる人はとりあえず「呼ぶ側を手前に、呼ばれる側を後ろ」の順番で定義してみることをオススメします! JavaScript版 メソッドの定義順は基本的に自由、だが・・・ メソッド(または関数)の定義順は基本的に自由です。 class Foo { a() { } b() { } c() { } } と書いてもいいですし、 class Foo { b() { } c() { } a() { } } と書いてもエラーにはなりません。 しかし、コードを読むときは呼ぶメソッドを手前に、呼ばれるメソッドを後ろに定義した方が読みやすいです。 つまり、こういうことです。 // 呼ぶ側を手前に、呼ばれる側を後ろに定義した場合 class Foo { a() { this.b() } b() { this.c() } c() { } } これがバラバラになっていると、コードを読むときのリズムが崩れて読みづらくなります。 // 呼ぶ側を手前に、呼ばれる側を後ろに定義した場合 class Foo { b() { this.c() } c() { } a() { this.b() } } 童謡を使って考えてみる とはいえ、上のような意味のないコード例だといまいちピンとこないかもしれません。 そこで「あんたがたどこさ」という童謡の歌詞を拝借して、メソッドの定義順の良し悪しを考えてみることにします。 メソッドの定義順がバラバラの場合 最初はメソッドの定義順がバラバラになっているケースです。 以下のコードでplayer.play()を実行したときの実行結果がどうなるか、ぱっとわかるでしょうか? playメソッドから順に最後までメソッド呼び出しがどのように進むか、ぜひ目で追いかけてみてください。 class MusicPlayer { play() { this.あんたがたどこさ() } 肥後どこさ() { this.熊本さ() } あんたがたどこさ() { this.肥後さ() } 熊本さ() { console.log("熊本どこさ?") } 肥後さ() { this.肥後どこさ() } } const player = new MusicPlayer() player.play() どうですか?実行結果がわかりましたか? はい、そうですね、playメソッドを呼び出すと"熊本どこさ?"の文字列が出力されます。 player.play() //=> 熊本どこさ? 「呼ぶ側を手前に、呼ばれる側を後ろ」のルールで定義した場合 ですが、もしメソッドの定義順が次のようになっていたらどうでしょうか? 先ほどと同じようにplayメソッドから順に最後までメソッド呼び出しがどのように進むか、目で追いかけてみてください。 class MusicPlayer { play() { this.あんたがたどこさ() } あんたがたどこさ() { this.肥後さ() } 肥後さ() { this.肥後どこさ() } 肥後どこさ() { this.熊本さ() } 熊本さ() { console.log("熊本どこさ?") } } const player = new MusicPlayer() player.play() playメソッドを呼び出すと"熊本どこさ?"の文字列が出力されるのは先ほど同じですが、「呼ぶ側を手前に、呼ばれる側を後ろ」のルールで定義した方がコードが読みやすくなったのではないでしょうか? JavaScript版のまとめ というわけで、この記事では「メソッドは呼ぶ側を手前に、呼ばれる側を後ろに定義しよう」という話を書いてみました。 もちろん、メソッドの定義順は絶対的な正解があるわけではないですし、読みやすさは主観によるので「ワシはメソッドの定義順なんて何でもかまわん!」という人も中にはいるでしょう。 とはいえ、僕の周りの熟練プログラマに話を聞くと、ほとんどの人が「呼ぶ側を手前に、呼ばれる側を後ろ」で書いた方がわかりやすいと答えています。 というわけで、新人プログラマの方で「メソッドってどういう順番で定義したらいいのかな〜??」と悩んでいる人はとりあえず「呼ぶ側を手前に、呼ばれる側を後ろ」の順番で定義してみることをオススメします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【新人プログラマ応援】開発タスクをアサインされたらどういう手順で進めるべきか

はじめに これはQiitaで開催されている「新人プログラマ応援 - みんなで新人を育てよう!」イベントの投稿記事です。 前回は「学習用のプログラムと仕事で書くプログラムは何が違うか」というタイトルで、お勉強用に作るプログラムと仕事で書くプログラムはこんなところが違うんだよ〜、というお話を書いてみました。 今回の記事ではみなさんが無事にプログラマとして就職できたと仮定して、「○○さん、このタスクお願いね」と開発タスクをアサインされたときの対応手順を説明してみます。 この記事を書いている人 仕事で20年近くプログラムを書いているプログラマ 現在は株式会社ソニックガーデンでRubyプログラマをやっている Rubyの入門書「プロを目指す人のためのRuby入門」を出版している プログラミングスクール「フィヨルドブートキャンプ」のメンターでもある 対象読者 新卒、または業界未経験の中途入社で最近プログラマになった人 その上で、開発タスクをアサインされたものの、「全然成果が出せなくて辛い・・・?」と落ち込んでいる人 僕が普段Railsを使っているため、この記事ではRailsを使う開発の現場を想定していますが、大半の内容はWeb系企業であれば言語やフレームワークを問わず参考になるはずです。 想定する開発タスク ここでは以下のような開発タスクがアサインされたと仮定します。 対象となるアプリケーションは運用されてすでに数年が経っている既存のRailsアプリケーション 商品一覧画面に「CSVダウンロードボタンを付けてほしい」というのがアサインされたタスクの内容 ローカルの開発環境はすでにセットアップが完了している TL;DR(長いので最初に結論) タスクは食べられるサイズに小さく切ってから口に運べ! アサインされたタスクをそのままほおばると喉に詰まって死ぬぞ!! ・・・と言われても何の話かわからないと思うので、さっそく本編に進みます。 なお、この手順は新人プログラマ専用の手順ではなく、僕自身も普段から実践している手順です。 手順1. 現行の仕様(システムの挙動)を確認する まずはローカル環境でRailsアプリケーションを動かしてみて、現行のアプリケーションがどういう仕様で動いているのかを確認します。 たとえば、今回の開発タスクであれば「CSVダウンロードボタン」を追加する画面にどうやってアクセスすればいいのか確認します。 手順2. 機能追加の要件や具体的な変更内容を確認する 次に、機能追加の要件や具体的な変更内容を確認します。 「CSVダウンロードボタンを付けてCSVをダウンロードできるようにすればいいんでしょ?」と考えるだけでは甘いです。 画面のどの位置にどんなボタンを付ければいいのか? ボタンをクリックしたときの挙動はどうなるのか? 確認ダイアログを表示する必要はないか? ボタンをクリックしたあとにボタンをdisableにしなくてよいか? CSVファイルにはどんなデータを出力するのか? どのカラムにどんなデータを出力するのか? データの出力順はどうなるのか? 日付のフォーマットはどうするのか?"yyyy/mm/dd"か、"yyyy-mm-dd"か、それともまた別のフォーマットか? CSVファイルのエンコーディングは何にするのか?UTF-8でいいのか、それともShift_JISでないとダメなのか? などなど、単なるCSVダウンロード機能でも考慮すべきポイントはたくさんあります。 また、すでに他の画面にCSVダウンロードボタンが付いているのであれば、その挙動もチェックしましょう。 システムの挙動には一貫性があった方がいいので、既存のCSVダウンロード機能があるのであればその挙動にあわせる方がベターです。 正常系だけでなく異常系やセキュリティ面の仕様も検討しよう 新人プログラマのうちはついつい正常系の仕様ばかり考えてしまい、異常系の考慮が不十分なことがよくあります。 異常系というのは「場合によっては起こりうる、あまり嬉しくない動作パターン」のことです。 CSVダウンロードで言えば、次のような異常系が想定できます。 出力するデータがゼロ件だったらどうするのか?ヘッダ行だけをダウンロードさせるのか、それとも何か特別な方法でユーザーに知らせるのか? ダウンロードしようとしていたデータにカンマや改行が含まれていた場合(つまりCSVファイルのフォーマットを壊すようなデータが含まれていた場合)、どういう形式で出力するのか? 他にもログインしていないユーザーや権限がないユーザーが間違って(もしくは悪意をもって)直接ダウンロード用URLにアクセスしてきた場合など、セキュリティ面の考慮もきちんと検討しておく必要があります。 タスクの背景やユースケースも確認しよう 「何を作ればいいのか」だけではなく、「なぜそのタスクが必要になったのか」という背景までもしっかり確認しましょう。 また、それに加えて、追加する新機能がどういうユースケースで使われるのかも確認しておきましょう。すなわち、 どういう立場のユーザーが いつ、どんな頻度でその機能を使い その機能を使って何をするのか(例:ダウンロードしたCSVファイルを何に使うのか) といった点も確認しておいた方がよい、ということです。 背景やユースケースを知っておけば、「そういう目的なのであれば、こういうふうに動いた方がいいだろうな」とか、「そもそもユーザーが必要な機能はCSVダウンロード機能ではなくて、画面に表示されているデータのソート順を変更できる機能なのでは?」といった観点でタスクの内容を見直すことができます。 いわゆる「顧客が本当に必要だったもの」ってやつですね。 Image: 顧客が本当に必要だったものとは (コキャクガホントウニヒツヨウダッタモノとは) [単語記事] - ニコニコ大百科 要件や仕様で不明な点が出てきたり、提示された仕様の見直しを相談したくなったりした場合はタスクをアサインしてきた開発リーダーに質問しましょう。 手順3. 現行のロジックがどのように実装されているのかを確認する 次にソースコードを覗いて現行のロジックがどのように実装されているのかを確認します。 商品一覧画面はいったいどのように実装されているのでしょうか? サーバーサイドでRailsがHTMLをレンダリングしてレスポンスとして返しているだけ? それともReactやVue.jsといったフロントエンドフレームワークを使って描画している? その場合、APIはどこでどうやってどんなデータを返している? CSVに出力する項目はどのテーブルのどのカラムから出力する? モデルとモデルの関連はどのようになっている? 既存のCSVダウンロード機能があるのであれば、それはどのような実装になっている? 共通部品や共通ロジックがすでにあって、差分だけ実装すればいいような作りになってたりしないか? アサインされたタスクの内容や画面の動きだけ見るとすごくシンプルなのに、ロジックを覗いて見ると「げげっ、なんでこんなややこしいことやってるの!?」って思うことは結構あります。(僕は「ふたを開けたらビックリ☆パターン」と呼んでいます) 思った以上に現行ロジックが複雑でどこで何がどう動いてるのかさっぱりわからん、という場合は先輩プログラマに声をかけてそのカラクリを説明してもらってください。 本番環境のデータ量やパフォーマンス目標も確認する 今回例で挙げたCSVダウンロード機能であれば、本番環境のデータベースにはどれくらいデータがあって、一回あたり最大何件ぐらいのデータをダウンロードするのか?といった確認も必要です。 でないと、開発環境では数秒でダウンロードできたのに(=5件しかデータがなかったから)、本番環境では10分以上待ってもダウンロードが終わらない(=100万件データがあったから)、みたいな問題が起きたりします。 時間がかかりそうな場合は、 ストリーミング形式でダウンロードする 非同期でCSVファイルを生成する(あとからダウンロード用のリンクをメールで通知する等) バッチ処理で決められた時刻にCSVファイル用のデータを生成する SQLをチューニングする or 特定のカラムにindexを貼る 検索条件を厳しくして一回あたりの取得件数を抑える といった対策が考えられます。 テストコードの有無も確認する 実装コードだけでなく、テストコードの有無も確認しておきましょう。 商品一覧画面にはすでにテストが用意されているか?用意されているならそこにCSVダウンロードのテストを簡単に追加できそうか? CSVダウンロードのテストは他の画面で書かれているか? 既存のテストコードがあればそのテストコードを流用して今回実装するCSVダウンロード機能のテストが書けますが、そうでない場合はゼロからテストコードを書く必要があります。 テストコードの書き方に慣れていない場合は、テストコードを書く時間も開発工数に含める必要があるでしょう。 手順4. 機能追加するにはどこをどう変更すればよいのかリストアップする(=タスクばらし) ここまでで開発に必要なインプットはそろったはずなので、具体的に何をどうやるのかをリストアップします。 「CSVダウンロードをボタンを追加してCSVをダウンロードできるようにする」みたいな粒度ではダメです。 タスクが大きすぎるので新人プログラマはタスクを喉に詰まらせて死にます。 そうではなく、もっともっと小さい単位にタスクを分解しましょう。 1つ1つのタスクは大きくても30分から1時間程度で終わる内容にしてください。 この作業を「タスクばらし」と言います。 たとえばCSVダウンロード機能であれば以下のようなタスクに分解できそうです。 CSVダウンロード用のルーティングをroutes.rbに追加する 画面上にCSVダウンロードボタンを配置してCSVダウンロード用のパス(URL)にリクエストを送れるようにする コントローラにCSVダウンロード用のアクションを追加する CSVの生成ロジックを実装する。CSVダウンロード機能はすでに共通ロジックがあるので、今回はデータの取得とファイル名作成のロジックだけを実装し、それ以外の処理は共通ロジックに任せる CSSを使ってボタンの見た目を整える CSVダウンロード機能のテストを書く タスクが小さければ小さいほど「食べやすいタスク」になります。 タスクを喉に詰まらせないよう、タスクをできるだけ小さく分解してください。 また、ここでリストアップした小さなタスクはTODOリストであり、自分自身への作業指示書でもあります。 NotionやEvernoteのようなツールでTODOリスト化して、作業が終わったらチェックを付ける、というような使い方もオススメです。 各タスクの作業時間も見積もってみよう ひととおりタスクを分解したら、それぞれのタスクにどれくらいの時間がかかりそうかざっくりと時間を見積もってみましょう。 これまでに使ったことのあるライブラリを利用する場合や、すでにお手本となるコードがある場合は比較的簡単に終わると思いますが、反対に「今まで使ったことがないライブラリを使うコード」や「お手本がなく自分がゼロから書かなければならないコード」は思った以上に時間がかかるリスクがあります。 タスクが大きいままだと不明な点も多いので見積もりの誤差も大きくなりますが、タスクを細かく分解すれば各タスクの見積もりの精度が上がり、その結果タスク全体の見積もりの精度も向上します。 手順5. タスクばらしの結果を先輩プログラマにレビューしてもらう タスクばらしが終わったら、その結果を先輩プログラマや開発リーダーにレビューしてもらいましょう。 「今回はこんな手順で、こんなふうに実装しようと思っています」という内容を先輩プログラマに伝え、認識の齟齬はないか、難しく考えすぎてないか等々、大きな手戻りが発生しそうなポイントがないことを確認してもらってください。 また、だいたいの見積もり時間も一緒に伝えてください。 明らかに手間がかかりそうなタスクや、リスクが高いタスク(例:実務でVue.jsのコードを書くのは今回が初めてですんなり実装できるか不安、等)は予め正直に伝えておくことが大事です。 もしかすると「そんなに時間がかかるならその部分だけ別タスクで対応しよう」とか、「もしハマったら手伝うから声をかけて」というように先輩プログラマからのアドバイスやサポートが受けられるかもしれません。 【重要】ここまでまだコードはひとつも書いていません! さて、現時点で手順5まで説明しましたが、実はここまでコードは一切書いていない点にお気づきでしょうか? ここまでやったのはひたすら前準備です。 「タスクをアサインされたから早くコードを書かなきゃ!」と焦ってコードを書き始めるのは間違いです。 まずは仕様や実装方法の疑問点を全部潰して、「あとは手を動かすだけ」という状態にすることが大事です。 ゴールまでの道筋がハッキリと見えていないのに慌てて出発すると、すぐに迷子になって右往左往することになります。 ただし、コードを書くなと言っても、スパイク(技術検証のためだけに使う小さなプログラム)を作るのは問題ありません。 手順6. 実装を開始する お待たせしました。ここからようやく楽しいコーディングの時間です。 先ほどのタスクばらしで作ったTODOリストに従って実装を進めていきましょう。 仕様と実装方針が事前に明確になっていれば、そこまで苦しむことなくコードを書き進められるはずです! 実装を開始したらWIP(work in progress=作業途中)のプルリクエストを作って、開発中のコードの差分を他の人が確認できるようにしておきましょう。 ハマったら制限時間を決めて、それを超えたら助けを求める そうは言ってもいざ実装を始めると思い通りに動かない部分が出てきてハマってしまうことがあるかもしれません。 そういうときは必ず時間を区切って対応するようにしてください。 目安としては30分です。 「ヤバい、これはハマってるぞ」と思ったら時計を見て、30分後にスマホのアラームをセットしましょう。 もしアラームが鳴ってもまだ問題が解決しないようであればタイムオーバーです。 先輩プログラマをつかまえて「すいません、ハマったんでちょっと助けてください」と声をかけてください。 先輩の時間を奪ってしまうかも、というような遠慮は不要です。 なぜならあなたが1人で悩みに悩んで3日かけてタスクを完了させるよりも、先輩に相談して2時間で完了させる方がチーム全体として見たときに効率がいいからです。 それでも「嫌な顔をされたらどうしよう」と不安になる人は、先ほど書いたタスクばらしのレビューのタイミングで先輩プログラマに「もしハマったら声をかけるんでよろしくお願いします」とひとこと伝えておけば大丈夫なはずです。 正常系の実装が終わったら途中経過を見てもらって手戻りを防ぐ 大きなタスクの場合は最後まで一気に完成させようとせずに、ある程度動くようになった段階でタスクをアサインしてきた開発リーダーに実際の動きを軽くレビューしてもらってください。 これは何のためかというと、手戻りを防ぐためです。 せっかく最後まで作りこんだのに仕様を勘違いしてて作りなおしになってしまうと、時間的ロスがめちゃくちゃ大きくなります。 加えて、時間的ロスだけでなく精神的な落ち込みも半端ないものになるで、手戻りはできる限り避けたいところです。 そのためにも最初は細かい点の作り込みは後回しにします。 細かい点とはたとえば入力値のバリデーションやデザインの調整、イレギュラーパターンの実装等です。 正常系の最もシンプルな処理フローが一通り動くようになったあたりで途中経過を報告するのが一番良いと思います。 手順7. 実装が終わったらプルリクエストのWIPを外して完了! タスクばらしで作ったTODOリストが全部完了済みになれば、タスクそのものの実装も完了します。 つまり、TODOリストがこの状態になればOKですね。 実装が全部終わったらプルリクエストのWIPを外して、コードレビューを依頼しましょう。 場合によっては一部コードの修正や画面の動きの修正を求められるかもしれませんが、致命的な問題はおそらくないはずです。 プルリクエストが承認され、mainブランチにマージされたらアサインされたらタスクは完了です。 どうもお疲れ様でした! まとめ というわけでこの記事では実務に入って間もない新人プログラマを想定して、「開発タスクをアサインされたらどういう手順で進めるべきか」という話を書いてみました。 この記事で述べたようにCSVダウンロード機能の実装をアサインされたからといって、「なるほどCSVダウンロード機能を作ったらいいんだな。よし!」でいきなりコードを書き始めるのはNGです!?‍♂️ そうではなく、コードを書き始めるのはしっかり事前調査をして、大きなタスクを小さく分解して、「これならもう迷うことはない。あとは手を動かすだけ!」と言えるようになってからです。 僕はかれこれ20年近くプログラマとして働いてきていますが、僕自身もこのスタイルで開発を進めています。 こうやって手順を文章化するとすごく時間がかかるように見えるかもしれませんが、「急がば回れ」で結局こういうやり方が一番早く終わります。 つまるところ、「最短経路を見つけるために時間をかけましょう。最短経路を見つけたら、その経路に沿って一気にゴールに向かいましょう。最短経路を確認しないまま歩き始めても、迷子になって余計に時間がかかるだけですよ」ということです。 特に業務で扱う大きくて複雑なプログラムになればなおさらです。 なんの準備も無く真っ暗な樹海に足を踏み込んだら二度と帰ってこれません……? 新人プログラマとして就職したけどなかなか成果が出せずに困っている人は、ぜひこの手順を実践してみてください!? おまけ プログラミングの理想と現実はこんな感じですよね〜。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails でPAY.JPを用いての決済機能

クレジットカード決済機能を実装する場合にユーザーのクレジットカードの情報を受取カード会社へ連絡という仕組みが思いうかびまいすがそれだと 1.それぞれのカード会社との事務手続き 2.セキュリティー基準の審査通過 をクリアしなければいけない そこで「クレジットカード決済代行サービス」を介してすれば上記の問題がクリアになる その中の「PAY.JP」をやっていくときの工程をメモ サーバーサイドにカード情報があるとそこから漏洩があると大変なので、トークン化しておくる トークンとは一度だけ使える暗号みたいなもの トークンを取得するのにJava.scriptを使用するのでJavaScriptの処理を無効にする機能を取り除く Ruby on Railsでは、turbolinksという機能がデフォルトで備わっています。こちらは簡単にAjax機能などを実装する際に使用されるものです。しかし、今回のように手作業でJavaScriptを記述してフォーム送信処理などを実装する場合は、このturbolinksがその処理を無効にしてしまうことがあるみたいです。 app/javascript/packs/application.js // 省略 require("@rails/ujs").start() // require("turbolinks").start() // コメントアウトする require("@rails/activestorage").start() require("channels") // 省略 そしてapplication.html.erbのhead要素内の記述を、以下のように編集。 app/views/layouts/application.html.erb <%# 省略 %> <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_pack_tag 'application' %> <%# 省略 %> そして、今度はPAY.JPのライブラリを読み込むために  <script type="text/javascript" src="ライブラリのURL"></script> を書き込む app/views/layouts/application.html.erb <%# 省略 %> <%= csrf_meta_tags %> <%= csp_meta_tag %> <script type="text/javascript" src="https://js.pay.jp/v1/"></script> <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_pack_tag 'application' %> <%# 省略 %> そしてトークン化を行うファイルを作成。 javascriptの下にファイルを作成 そして、application.jsに作成したファイルを読み込むための記述をしましょう。 app/javascript/packs/application.js[例] // 省略 require("@rails/ujs").start() // require("turbolinks").start() require("@rails/activestorage").start() require("channels") require("../card") // 省略 正しく読み込まれるか確認しましょう 作成したファイルに以下のように記述しましょう。ページが読み込まれた時に、payという変数に代入したアロー関数が実行されるようにします。 const pay = () => { console.log("カード情報トークン化のためのJavaScript"); }; window.addEventListener("load", pay); ブラウザの検証ツールで確認 フォーム送信時にイベントが発火するようにしましょう 検証ツールでidを確認する const pay = () => { const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); console.log("フォーム送信時にイベント発火") }); }; window.addEventListener("load", pay); PAY.JPアカウントを作成します。 そしてPAY.JPのテスト公開鍵を取得し設定します。 公開鍵はPAY.JPのサイトからとってきます const pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); console.log("フォーム送信時にイベント発火") }); }; window.addEventListener("load", pay); なお、鍵情報は公開してはいけません。このコードの状態でGitHubにPushするなどしてしまうと、コードに含まれる鍵情報が公開されてしまい、不正請求などの被害にあうリスクがあります。この鍵の情報は後のステップで環境変数に設定するようにします。 トークン化の処理 const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; }); }; window.addEventListener("load", pay); また、cardの()の中の値は検証ツールでカードのそれぞれのname属性の値です。 カードの情報をトークン化しましょう Payjp.createToken(card, callback)というオブジェクトとメソッドを使用します。 第一引数のcardは、PAY.JP側に送るカードの情報で、直前のステップで定義したカード情報のオブジェクトが入ります。 第二引数のcallbackには、PAY.JP側からトークンが送付された後に実行する処理を記述します。 const pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; console.log(token) } }); }); }; window.addEventListener("load", pay); アロー関数の引数としてはstatusとresponseを定義しました。statusはトークンの作成がうまくなされたかどうかを確認できる、HTTPステータスコードが入ります。responseはそのレスポンスの内容が含まれ、response.idとすることでトークンの値を取得することができます。 HTTPステータスコードが200のとき、すなわちうまく処理が完了したときだけ、トークンの値を取得するように実装していきます。 トークンの情報をフォームに追加 const pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; const renderDom = document.getElementById("charge-form"); const tokenObj = `<input value=${token} name='token'>`; renderDom.insertAdjacentHTML("beforeend", tokenObj); debugger; } }); }); }; window.addEventListener("load", pay); type属性の値にhiddenを指定します。 トークンを表示させない為です Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; const renderDom = document.getElementById("charge-form"); const tokenObj = `<input value=${token} name='token' type="hidden"> `; renderDom.insertAdjacentHTML("beforeend", tokenObj); クレジットカードの情報を取り除く サーバーサイドにはカード情報は送らないようにする そしてサーバーサイドへフォームの情報を送信する記述する const pay = () => { Payjp.setPublicKey("pk_test_******************"); // PAY.JPテスト公開鍵 const form = document.getElementById("charge-form"); form.addEventListener("submit", (e) => { e.preventDefault(); const formResult = document.getElementById("charge-form"); const formData = new FormData(formResult); const card = { number: formData.get("order[number]"), cvc: formData.get("order[cvc]"), exp_month: formData.get("order[exp_month]"), exp_year: `20${formData.get("order[exp_year]")}`, }; Payjp.createToken(card, (status, response) => { if (status == 200) { const token = response.id; const renderDom = document.getElementById("charge-form"); const tokenObj = `<input value=${token} name='token' type="hidden"> `; renderDom.insertAdjacentHTML("beforeend", tokenObj); } document.getElementById("order_number").removeAttribute("name"); document.getElementById("order_cvc").removeAttribute("name"); document.getElementById("order_exp_month").removeAttribute("name"); document.getElementById("order_exp_year").removeAttribute("name"); document.getElementById("charge-form").submit(); }); }); }; window.addEventListener("load", pay); サーバーサイドでトークンの情報を受け取れるようにしよう ストロングパラメーターを編集 #省略 private def order_params params.require(:order).permit(:price).merge(token: params[:token]) end end 上記のように設定することで、order_params[:price]としてpriceの情報が、order_params[:token]としてtokenの情報が取得できるようになります。 paramsを確認する binding.pryをつかって、paramsを確認する def create binding.pry @order = Order.new(order_params) if @order.valid? @order.save return redirect_to root_path else render 'index' end end #省略 再度フォームの送信処理を行い、paramsの中にそれぞれの情報が含まれていることを確認しましょう。 binding.pryで停止した処理をexitすると、エラー画面が表示されます。 tokenの値もモデルで取り扱えるようにしましょう 以下をモデルに追加 attr_accessor :token payjp(Gem)の導入 Gemfileの下に以下を記述 gem 'payjp' bundle installを実行し、導入しましょう。 実際にコントローラーに決済処理を記述 class OrdersController < ApplicationController def index @order = Order.new end def create @order = Order.new(order_params) if @order.valid? Payjp.api_key = "sk_test_***********" # 自身のPAY.JPテスト秘密鍵を記述しましょう Payjp::Charge.create( amount: order_params[:price], # 商品の値段 card: order_params[:token], # カードトークン currency: 'jpy' # 通貨の種類(日本円) ) @order.save return redirect_to root_path else render 'index' end end private def order_params params.require(:order).permit(:price).merge(token: params[:token]) end end リファクタリングしたほうが読みやすくなる class OrdersController < ApplicationController def index @order = Order.new end def create @order = Order.new(order_params) if @order.valid? pay_item @order.save return redirect_to root_path else render 'index' end end private def order_params params.require(:order).permit(:price).merge(token: params[:token]) end def pay_item Payjp.api_key = "sk_test_***********" # 自身のPAY.JPテスト秘密鍵を記述しましょう Payjp::Charge.create( amount: order_params[:price], # 商品の値段 card: order_params[:token], # カードトークン currency: 'jpy' # 通貨の種類(日本円) ) end end 今回は、ここまでにします。 時間あったら、環境変数にかえるのもかこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

capistrano-db-tasksのdb:pullでpg_dump「サーババージョンの不整合のため処理を中断しています」を解決

この記事の意図 WebサーバーとDBサーバーが別の環境で capistrano-db-tasks の pg:pull を実行したときに pg_dump の バージョンがそれぞれ違うということでエラーになり、解決したので共有します。 環境 EC2(amazon linux)からRDS(PostgreSQL)接続のRailsアプリケーションです。 サーババージョン(RDS): 12.5、pg_dump バージョン(EC2): 9.2.24 解決方法 EC2のpg_dumpのバージョンを12にすればOKなはずです。 amazon linuxにはこの時点でpostgresqlの12は用意されていないようなので下記を参考に12をインストールしました。 今回pg_dumpさえ動けばいいのでサーバーは立てる必要はないと思います。 このままですとpg_dumpがバージョン9を見てしまうのでcapistranoでログインしているユーザーの.bashrcにパスを追加します。 export PATH=/usr/pgsql-12/bin/:$PATH .bashrcを読み直します。 $ source .bashrc これでローカルからpg:pullを実行して動きました。 注意 .bash_profileでは動きませんでした。理由は.bashrcがSHELL_VARIABLE、.bash_profileはENVIRONMENT_VARIABLE、という違いがあるからのようです。capistranoでアクセスする時はsshなのでSHELL_VARIABLEしか見てくれないという理解をしています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Rails]Formオブジェクト内でuniquenessを使用してエラーが発生した件

はじめに 初めてパターンのエラーと本日遭遇したため記録のため記述します。 正しいかどうか大変不安ですので、間違い等ご指摘を下さると幸いです エラー内容 Formオブジェクト内でこんな感じにバリデーションを記述していたところ、エラーが発生しました。 with_options presence: true do validates :title,length:{maximum:25} validates :content validates :name,uniqueness:true end 原因 結論から言えば、uniquenessはFormオブジェクト内では使用できないからです。 Formオブジェクトはつい勘違いしてしまいますが、モデルとは違い直接モデルを触れることができないものです。ゆえに、以下のようにモジュールを読み込んでいます。 class PracticesPtag include ActiveModel::Model #省略 解消方法 私の場合は、正しいかわかりませんが、uniquenessのみ直接モデルに書いてやることでエラーを解消させました。 終わりに 正直。まだ腑に落ちない部分もあるため、より詳しい説明ができる方はコメントにて教えてくださると大喜びです!!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tailscale + mqttでリモート間ファイル共有

問題 リモート端末がある場合、ダイナミックDNSやらVPNやら色々設定した挙げ句、リモートとローカルのIPセグメントが同じだと(例: 192.168.0.xxx)IPがぶつかって通信できないとか、色々面倒。 プログラム的にファイルを共有するにもscpだとなんだし、手っ取り早くdropboxでやり取りしていた。ヘッドレスなlinux側なのでotherguy/dropboxを使ってみたが、意外とCPUを常時食う。 リモート端末への接続解決 VPNだと色々な呪縛から逃れられないので、tailscaleに変える。 リモート側はRPiだったので、直接ダウンロードに書いてあるようにそのまま入れた。 ローカル側はdocker上に構成しているので、network_mode: hostでも良いが、mosquittoをローカルでも使ってるし、mqttポートをデフォルトのままで使いたかったのでdocker上にtailscaleを置いて、そこにつなぐmosquittoブローカーを置いて、リモートからのデータを受けるようにしてみた。 richnorth/tailscaleをまんまコピー。 docker-compose.yml tailscale: image: richnorth/tailscale:v0.99.1 volumes: - "./tailscale/tailscale_var_lib:/var/lib" # State data will be stored in this directory - "/dev/net/tun:/dev/net/tun" # Required for tailscale to work cap_add: # Required for tailscale to work - net_admin - sys_module command: tailscaled $ docker-compose up -d $ docker-compose exec tailscale tailscale up とするとリンクが表示されるので、ログインしてこのコンテナがtailscaleのプライベートネットワークに入るようにする。volumeで/var/libへマウントしてるので永続化出来る。 dropboxからmqttへ RPi側ではDropbox-Uploaderを使ってプログラム的にdropboxへファイルを送っていたが、これを止めてmqttでデータを送るようにした。 画像データはbase64に一回変えて文字として それ以外はJSONにして 本来はローカルにmqttブローカーを建てずにshiftr.ioを使って視覚化をしたかったが、publish出来るデータサイズが64KBに制限されているので諦めた(画像が300KBぐらいある)。 Enforced Limits The client identifier length is limited to 64 characters. The topic length is limited to 128 characters. The topic format is limited to letters, numbers and the following special characters: .,:;-_$/+#. The payload size of publish messages is limited to 64 KB. The active subscriptions per connection are limited to 100. The minimum keep alive interval is 1 second while the maximum is 5 minutes. docker-compose.yml mosquitto: image: eclipse-mosquitto restart: always volumes: - ./mosquitto/config:/mosquitto/config - ./mosquitto/data:/mosquitto/data - ./mosquitto/log:/mosquitto/log user: "1000:1000" network_mode: service:tailscale network_mode: service:tailscaleとしてやることでtailscaleネットワーク上で動作することになり、リモートからtailscale内のプライベートIPで1883ポートにデータが流せる。 解決したこと OpenVPNを除去 ダイナミックDNSを除去 dropboxを除去 otherguy/dropboxを除去することによりCPU使用率低下(15%程度削減) listen gem除去 以前まではdropboxでファイルがsyncした際にlistenで反応させていたが、mqttでメッセージを受け取ることをイベントと出来るので、全体がよりシンプルになった。(以前だと何にしてもファイルに落とし込んで、dropboxで同期させる必要があった)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails初学者によるRailsチュートリアル学習記録⑥ 第4章

目次 1. はじめに 2. 第4章の概要 3. 学習内容 4. 終わりに 1. はじめに この記事は、Rails初学者の工業大学三年生がRailsチュートリアルの学習記録をつけるための記事です。 筆者自体がRailsやWebについて知識が少ないので、内容の解釈などに間違いがある可能性があります。(その時はコメントで指摘してくださると助かります!) Railsチュートリアル内ではRailsの内容以外にも、gitでのバージョン管理やHerokuを使ったデプロイも学習しますが、gitに関しては既に私が学習済みのため学習記録には記述しません。 演習の記録も省略します。 2. 第3章の概要 この章ではRailsを扱う上で重要なRubyの要素について学習します。 アプリケーションへの変更はあまり多くなく、どちらかというとRubyの言語仕様をRails consoleを使用して 学ぶことが中心だったので、その内容は私が初めて知った内容や後から見返せるようにしたい内容のみを記述していきます。 カスタムヘルパーの定義 カスタムヘルパーとは タイトルが未定義だった場合の動作を定義する Rails風味のRuby クラスを定義する 3. 学習内容 1. カスタムヘルパーの定義 1-1. カスタムヘルパーとは カスタムヘルパーとは、ビュー内で使用する関数の内、開発者自身で新しく作成した関数のことです。 Railsによって自動的に作成される関数は組み込み関数と呼ばれます。  カスタムヘルパーは、コントローラの作成時に同時に作成されるヘルパーファイルに記述していきます。 1-2. タイトルが未定義だった場合の動作を定義する ここで定義するカスタムヘルパーは、タイトルが定義されていないときに、 表示されるタイトルを変えるカスタムヘルパーです。 第3章で、それぞれのページごとに自動的にタイトルが変わるようにビューの設定を行いました。 app/views/static_pages/home.html.erb <% provide(:title, "Home") %> <h1>Sample App</h1> <p> This is the home page for the <a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a> sample application. </p> 前回、変更を加えたビューの1つです。 最初の行の<% provide(:title, "Home") %>で、ビューのタイトルを定義し、application.html.erbというファイルで その値を受け取ることで「Home | Ruby on Rails Tutorial Sample App」というタイトルを表示しています。 しかし、もしビューファイルにタイトルの定義が無ければ 「| Ruby on Rails Tutorial Sample App」というタイトルになってしまいます。 余分な縦棒(|)が初めに表示されてしまうため、カスタムヘルパーによってタイトルが定義されていないときは、 縦棒なしの「Ruby on Rails Tutorial Sample App」というタイトルが表示されるようにします。 カスタムヘルパーの名前はfull_titleと定義して、内容は以下のコードを使用して説明します。 app/helpers/application_helper.rb module ApplicationHelper def full_title(page_title = '') base_title = "Ruby on Rails Tutorial Sample App" if page_title.empty? base_title else page_title + " | " + base_title end end end このカスタムヘルパーはデフォルト値がnilの引数を1つ取ります。 この引数はページごとのタイトルと対応してif page_title.empty?で条件分岐に利用されます。 この条件式がtrueだった場合、つまりページごとのタイトルが定義されていない場合に、 base_titleという変数を返します。 base_titleの内容は「Ruby on Rails Tutorial Sample App」という文字列です。 そして、条件式がfalseだった場合、つまりページごとのタイトルが定義されている場合には、 ページごとのタイトル、縦棒(|)、Ruby on Rails Tutorial Sample Appの3つの文字列が結合された文字列が返されます。 このヘルパーを作成すればapplication.html.erbのtitleタグを書き換えることができます。 <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>から、 <title><%= full_title(yield(:title)) %></title>というコードになります。 変更後では、各ビューのタイトルが引数として渡されています。 2. Rails風味のRuby ここでは、Rubyの言語仕様やメソッドの使用方法について初めて知ったことや、 見返せるように記録しておきたいと思ったものを書いていきます。 ①「後置 if」でif文を一行で書く if文は通常、以下のように最低でも3行必要です。 if 条件式 処理 end しかし、Rubyの後置ifというものを使えば、処理が1行のみの場合にif文を1行で書くことができます。 処理 if 条件式 このように記述すると条件式がtrueの時のみ処理が実行されます。 後置ifは便利ですが返り値が特殊らしいので注意が必要らしいです。 参考:【Ruby】後置ifが末尾にあるメソッドの返り値はなに...? ②範囲オブジェクトの対応範囲 範囲オブジェクトとは1..10と記述することで生成される順番に値が取得できるオブジェクトです。 上記の場合1~10の整数が1つずつ取得できます。 この範囲オブジェクトは文字にも対応しており、 "a".."g"や、"あ".."お"、"一".."九"などでもオブジェクトが生成されました。内容は以下の通りです。 ・('a'..'g').to_a => ["a", "b", "c", "d", "e", "f", "g"] ・('あ'..'お').to_a => ["あ", "ぃ", "い", "ぅ", "う", "ぇ", "え", "ぉ", "お"] ・('一'..'九').to_a ["一", "丁", "丂", "七", "丄", "丅", "丆", "万", "丈", "三", "上", "下", "丌", "不", "与", "丏", "丐", "丑", "丒", "专", "且", "丕", "世", "丗", "丘", "丙", "业", "丛", "东", "丝", "丞", "丟", "丠", "両", "丢", "丣", "两", "严", "並", "丧", "丨", "丩", "个", "丫", "丬", "中", "丮", "丯", "丰", "丱", "串", "丳", " 临", "丵", "丶", "丷", "丸", "丹", "为", "主", "丼", "丽", "举", "丿", "乀", "乁", "乂", "乃", "乄", "久", "乆", "乇", "么", "义", "乊", "之", "乌", "乍", "乎", "乏", "乐", "乑", "乒", "乓", "乔", "乕", "乖", "乗", "乘", "乙", "乚", "乛", "乜", "九"] 漢数字で1~9が取得できるかと思ったら、辞書順?で漢字が取得できました。 ③ブロックとは ブロックとは配列や範囲の値を1つずつ取得して、処理を行う仕組みです。 >> (1..5).each { |i| puts 2 * i } 2 4 6 8 10 上のコードは1~5の整数を1つずつ取り出して i に代入、そして i を2倍した値を出力しています。 この i のことをブロック変数と呼びます。 ④ハッシュとシンボル ハッシュとは配列と似たオブジェクトで、キーと値がペアになったものです。 ハッシュを定義するときの書き方にはリテラル表現を使用した書き方と、シンボルを使用した書き方があります。 ・リテラル表現を使用した書き方 user = { "first_name" => "Michael", "last_name" => "Hartl" } {"last_name"=>"Hartl", "first_name"=>"Michael"} #結果 ・シンボルを使用した書き方 user = { :first_name=>"Michael", :last_name=>"Hartl" } { :first_name=>"Michael", :last_name=>"Hartl" } #結果 #シンボルは以下のように書くこともできる user = { first_name: "Michael", last_name: "Hartl" } { :first_name=>"Michael", :last_name=>"Hartl" } #結果 リテラル表現ではハッシュロケットという=>を使ってキーと値を記述します。左辺がキー、右辺が対応する値です。 シンボルを使用した表記方法は、コロンをシンボル名の前においてハッシュロケットを使用する書き方と、 シンボル名の後にコロンをおく書き方があります。 ⑤Rubyにおける関数の書き方 ここではサンプルアプリケーションのレイアウトファイルの以下の一文を用いて、Ruby特有の関数の書き方を説明します。 <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> このコードはスタイルシートを追加する「stylesheet_link_tag」というメソッドですが、書き方がRuby特有です。 1つ目のポイントは引数の丸カッコがありません。 Rubyではメソッド呼び出しの丸カッコを省略できます。 2つ目のポイントは2つ目の引数のmedia: 'all', 'data-turbolinks-track': 'reload'という部分です。 この引数はハッシュですがハッシュを表す波カッコがありません。 Rubyではハッシュが最後の引数であれば、波カッコを省略できます。 3つ目のポイントは途中に開業が含まれている点です。 Rubyは開業と空白を区別しないため、コードの途中での改行時に折り返し用の文字列を入れる必要がありません。 以上のポイントをまとめると、最初のコードは以下の全てのコードと等価と言えます。 # 元のコード stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' # 丸カッコあり stylesheet_link_tag('application', media: 'all', 'data-turbolinks-track': 'reload') # 波カッコあり stylesheet_link_tag 'application', { media: 'all', 'data-turbolinks-track': 'reload' } # 改行なし stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' # 省略、改行なし stylesheet_link_tag('application', { media: 'all', 'data-turbolinks-track': 'reload' }) 3. クラスの作成方法 Rubyではあらゆるものがオブジェクトであり、何らかのクラスに属しています。 それぞれのクラスの継承関係はsuperclassメソッドで確認できます。 ここでは自分でクラス定義のこーどについて説明します。 ここで定義するUserクラスはこの章でしか使用しないので、ルートディレクトリ直下に「examole_user.rb」というファイルを作成します。 クラス定義のコードを以下に示します。 class User attr_accessor :name, :email def initialize(attributes = {}) @name = attributes[:name] @email = attributes[:email] end def formatted_email "#{@name} <#{@email}>" end end まず、class クラス名でクラス名を定義します。この時クラス名の頭文字は大文字にします。 その次のattr_accessorではnameとemailという2つの属性に対応するアクセサーを作成しています。 アクセサーが作成されるとインスタンス変数が使用できるようになります。 つまり、@name, @emailという2つのインスタンス変数をクラスメソッドやビューで使用できるようになるということです。 その下のdefで始まる2つの文がクラスメソッドです。 1つ目のクラスメソッドはRubyの特殊なメソッドで、User.newを実行してインスタンスを作成すると 自動的に実行されるメソッドです。 このメソッドはattributesという空のハッシュを引数として持ちます。 よって、User.newを実行したときに:name. :emailとラベルを指定して値を渡すと、 userインスタンスのインスタンス変数に初期値として入力されます。 また、User.newを実行したときに値を渡さないと初期値はnilとなります。 formatted_emailというクラスメソッドはRailsチュートリアルないで学習のために作成したメソッドで、 名前とメールアドレスを式展開を使用してユーザ名とメールアドレスを一緒に返すメソッドです。 4. 終わりに 第4章では、Rubyの言語仕様を中心に学習しました。 私はRubyはProgateくらいでしか学んでいないので知らないことがまだ多く、抽象的な概念に関しては Rubyのコードを書いた経験が少ないゆえに理解が浅い状態です。 これ以降アプリケーションの機能の追加やテストの実装を行っていくので、 意味が理解できないコードは一つ一つリファレンスマニュアルを参照して、時間をかけてでも理解しながら進めていこうと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Could not find generator '○○:install'. の解決策 (springの停止)

はじめに プログラミング初学者のため、自分の理解できている範囲内で言語化しています。 何か間違っている情報や改善点などありましたら、コメントいただけますと幸いです。?‍♂️ springとは Springは、アプリケーションをバックグラウンドで実行し続けることで開発をスピードアップする Rails アプリケーションプリローダーです。これは、変更を加えるときにサーバーを再起動する必要がないことを意味します。 引用:https://pleiades.io/help/ruby/spring.html 簡単にいうと、アプリケーションを効率よくスピーディーに動かしてくれる役割をになっている。 注意点として開発環境で使うのはいいが、商用環境で使うのは公式でも推奨されていないそう、、、 実際にspringが原因でエラーになる事例もあるとか、、、 まあ、エラーが出なかったらオールオッケー?‍♂️ Could not find generator '○○:install'.エラー rspecの導入でテストコード書くためのディレクトリを生成するために ターミナル. % rails g rspec:install を行った際に ターミナル. Running via Spring preloader in process 38602 Could not find generator 'rspec:install'. Run `rails generate --help` for more options. とエラーが出た。 解決策 調べたところ、springを停止することで解決できるみたいなので ターミナル. % spring stop Spring stopped. spring stopコマンドで停止させて、インストールすることに成功した? spring stop してもどこかのタイミングで、すぐ再起動するが、 stopした直後は止まっているので、問題なくinstallできると思う *これでもうまくいかない人は、下記の参考文献の1つ目のURLをみて、springの無効化などをお試しください 補足 (springの再起動) 起動の確認 ターミナル. % spring status Spring is running など状況が記述される 再起動 ターミナル. % spring start --- springが使えるコマンドなどが表示され起動となる --- 参考文献 こちらの文献ではspringについてかなり細かく記載されているので、しっかりと理解した方は、 下記の一つ目のURLをご覧ください↓ url:https://blog.aiampy.net/20191130-ruby-on-rails-stop-spring/ url:https://pleiades.io/help/ruby/spring.html
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails Formオブジェクトパターン

勉強用です。 初心者なので間違えている箇所などあるかもしれないのでその時はコメントなどで教えて下さい。 今回は、フリマアプリ購入機能をつくる時に、1つのフォームから送られてきた情報を複数のテーブルに分けて保存」する必要がありました。 従来では、1つのフォームの情報を1つのテーブルに保存でしたが、この方法で複数のテーブルに保存するやり方だと バリデーションの問題などいろいろと問題があります。 そこで使うのがFormオブジェクトです。 個人的のイメージになりますが、モデル同士をくっつけるって感じです。 それをすることにより、バリデーションも簡単につけやすく、コントローラーでの定義もしやすくなります。 やり方としては、まず 新たにmodelsディレクトリ直下にファイルを作成し、クラスを定義 app/modelsディレクトリ配下に自分でファイルをつくります ファイル名.rb 作成できたら、作成したファイルに以下のように記述してクラスを定義。 今回は、donation_address.rbです. app/models/donation_address.rb class DonationAddress end Form_withメソッドに対応する機能とバリデーションを行う機能を、作成したクラスにもたせる。 DonationAddressクラスにActiveModel::Modelをincludeします。 そして保存したいカラム名を属性値として扱えるようにします。 DonationAddressクラス内でattr_accessorを使用します。 donationsテーブルとaddressesテーブルに保存したいカラム名を、すべて指定。 app/models/donation_address.rb class DonationAddress include ActiveModel::Model attr_accessor :postal_code, :prefecture, :city, :house_number, :building_name, :price, :user_id end ActiveModel::Modelとattr_accessorを活用することで、Formオブジェクトの属性をform_withメソッドの引数に指定できるようになります。 バリデーションの処理を書く Formオブジェクトのインスタンスに対してバリデーションを実行します。 そのために、Formオブジェクトにバリデーションを定義しておく必要があります。 app/models/donation_address.rb class DonationAddress include ActiveModel::Model attr_accessor :price, :user_id, :postal_code, :prefecture, :city, :house_number, :building_name with_options presence: true do validates :price, numericality: {only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 1000000, message: 'is invalid'} validates :user_id validates :postal_code, format: {with: /\A[0-9]{3}-[0-9]{4}\z/, message: "is invalid. Include hyphen(-)"} end validates :prefecture, numericality: {other_than: 0, message: "can't be blank"} end user_idには、本来belongs_to :userのアソシエーションにより、バリデーションが設定されています。 が!! Fromオブジェクトでつくったモデルには組み込まれてないので再度定義が必要。 データをテーブルに保存する処理を書く Formオブジェクトに、フォームから送られてきた情報をテーブルに保存する処理を記述します。 app/models/donation_address.rb class DonationAddress include ActiveModel::Model attr_accessor :price, :user_id, :postal_code, :prefecture, :city, :house_number, :building_name with_options presence: true do validates :price, numericality: {only_integer: true, greater_than_or_equal_to: 1, less_than_or_equal_to: 1000000, message: "is invalid"} validates :user_id validates :postal_code, format: {with: /\A[0-9]{3}-[0-9]{4}\z/, message: "is invalid. Include hyphen(-)"} end validates :prefecture, numericality: {other_than: 0, message: "can't be blank"} def save # 寄付情報を保存し、変数donationに代入する donation = Donation.create(price: price, user_id: user_id) # 住所を保存する # donation_idには、変数donationのidと指定する Address.create(postal_code: postal_code, prefecture: prefecture, city: city, house_number: house_number, building_name: building_name, donation_id: donation.id) end end コントローラーでFormオブジェクトのインスタンスを生成する new, createアクションで、Formオブジェクトのインスタンスを生成します。 理由は2つある。 1. Formオブジェクトのインスタンスをform_withのmodelオプションに指定するため form_withのmodelオプションに指定できる。これは、個人的に有り難いです!! 自動でアクションを振り分けてくれる 2. 入力した内容やエラーメッセージをフォームで表示させるため エラーメッセージがでるおかげで、こっちも助かりました。(笑) 購入ボタンをおして保存できるか試したが、「商品の情報がない」エラーがでてなるほど!!と助かった実体験。 app/controllers/donations_controller.rb class DonationsController < ApplicationController before_action :authenticate_user!, except: :index def index end def new @donation_address = DonationAddress.new end def create @donation_address = DonationAddress.new(donation_params) if @donation_address.valid? @donation_address.save redirect_to root_path else render :new end end private def donation_params params.require(:donation_address).permit(:postal_code, :prefecture, :city, :house_number, :building_name, :price).merge(user_id: current_user.id) end あとは、viewでform_withを使って表示していく感じになると思います。 以上です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

草野球の出欠確認Webアプリを作ろう! part.4

これから作っていく簡単なWebアプリの作成メモ(自分の備忘)です。 自分用なのであまり凝りすぎないように書いていきたい。 <<前回の記事 今回やったこと スケジュールのデータを作って見た目を確認 コンソール経由でSchedulesモデルのデータを準備する。 (date型やtime型は手入力できるか自信なかったので、Dateクラスなどを利用した) ※以下の記事を参考にした。 Railsタイムゾーンまとめ config/application.rb class Application < Rails::Application (略) config.time_zone = "Tokyo" config.active_record.default_timezone = :local (略) end $ rails c > date = Date.today + 365 > time1 = Time.zone.now > time2 = time1 + (3600 * 3) > time3 = time1 - 1800 > schedule1 = Schedule.new(title:"テスト予定1",date_of:date,start_time:time1,end_time:time2,meeting_time:time3) > schedule1.save (titleを変えてコンソールでの入力を繰り返すことで、スケジュールのデータを複数作れる) (私はそこまで徹底できないけど、大量のスケジュールデータを作成したい方向けにコピペできるスクリプト例も用意した) app/helpers/schedules_helper.rb module SchedulesHelper # 動作確認用のスケジュールデータを複数個作成するメソッド def create_demo_schedules(n) # 引数が数字以外だったら終了 if n =~ /^\d+$/ n = n.to_i elsif n.class == "String".class return false end count = 0 n.times do count += 1 date1 = Date.today + 364 + count time1 = Time.zone.now time2 = time1 + (3600 * 3) time3 = time1 - 1800 title = "テスト予定#{count.to_s.tr("0-9","0-9")}" demo_schedule = Schedule.new( title: title, date_of: date1, start_time: time1, end_time: time2, meeting_time: time3 ) demo_schedule.save end return true end end $ rails c > include SchedulesHelper > create_demo_schedules(100) 上のようにして100件のダミーデータを作成できる。 ※ほんとうは自動テストとして、RspecのFactoryBotで作成して動かすのがいいのでしょう。  あとでやるつもりです。 この状態で動作確認すると、以下のようになった。 時間が確認できないので、すこしviewを変更する。 時間の表示について以下の記事を参考にした。 Time型のデータの値を、時刻部分だけ表示する views/schedules/index.html.erb <table align="center"> <thead> <tr> <th>件名</th> <th>予定日</th> <th>予定の時間</th> </tr> </thead> <tbody> <% @schedules.each do |lst| %> <tr> <td><%= lst.title %></td> <td><%= lst.date_of %></td> <td><%= lst.start_time.strftime('%H:%M') %> ~ <%= lst.end_time.strftime('%H:%M') %></td> </tr> <% end %> </tbody> </table> これで以下のような見た目になる。 まあ最低限ならこれでよしとする。 つぎは、予定の新規作成や編集をさせたい。 スケジュールの新規作成   まずはviewから、つながりを用意しておく。 views/schedules/index.html.erb <h1>チームの予定一覧</h1> <div class="row_line"> <%= link_to '新規作成', new_schedule_path, class: 'btn primary-btn' %> </div> <% if @schedules.blank? %> (以下略) 新規作成側のviewも作成する。 views/shedules/new.html.erb <h1>予定の新規作成</h1> <div class="row_line"> <%= link_to '予定一覧へ', schedules_path, class: 'btn primary-btn' %> </div> <%= form_for @schedules, url: {action: "create"} do |f| %> <div class="row_line"> <label>件名:</label> <%= f.text_field :title %> </div> <div class="row_line"> <label>予定の日付:</label> <%= f.date_select :date_of %> </div> <div class="row_line"> <label>開始時間:</label> <%= f.time_select :start_time %> </div> <div class="row_line"> <label>終了時間:</label> <%= f.time_select :end_time %> </div> <div class="row_line"> <label>集合時間:</label> <%= f.time_select :meeting_time %> </div> <div class="row_line"> <%= f.submit "新規作成する" %> </div> <% end %> controllers/schedules_controller.rb def new @schedules = Schedule.new end これだと表示が落ち着かないので、さすがに私でもパッとできる程度に整える。 ※以下の記事を参考にした。 【Rails】date_selectタグの使い方メモ Rails の date_select でつくるセレクトボックスを「年」「月」「日」で区切る views/shedules/new.html.erb <%= form_for @schedules, url: {action: "create"} do |f| %> <div class="row_line"> <label>件名:</label> <%= f.text_field :title %> </div> <div class="row_line"> <label>予定の日付:</label> <%= raw sprintf( f.date_select(:date_of, use_month_numbers: true, order: [:year,:month,:day], selected: Time.zone.now, start_year: Time.zone.now.year + 5, end_year: Time.zone.now.year, date_separator: '%s' ),'年','月') + '日' %> </div> <div class="row_line"> <label>開始時間:</label> <%= f.time_select :start_time %> &nbsp;~&nbsp; <label>終了時間:</label> <%= f.time_select :end_time %> &nbsp;( <label>集合時間:</label> <%= f.time_select :meeting_time %> ) </div> <div class="row_line"> <%= f.submit "新規作成する" %> </div> <% end %> CSSやbootstrapを入れていないなりに、最初よりはマシになった(感じ方には個人差があります)。 あとはviewに入力した値をDBに登録できるようにする。 そのために以下を実施した。 ※以下は参考にしたWeb記事。 【Rails】permitメソッドを使ってストロングパラメーターにしよう controllers/schedules_controller.rb (略) def create @schedules = Schedule.new(schedules_params) if @schedules.save redirect_to schedule_path(@schedules), notice: "予定を新規作成しました。" else render :new end end (略) private def schedules_params params.require(:schedule).permit( :title, :date_of, :start_time, :end_time, :meeting_time ) end (略) せっかくなので、登録後に表示するshowアクションとそのviewも整える。 controllers/schedules_controller.rb (前後略) def show @schedules = Schedule.find(params[:id]) end views/schedules/show.html.erb <h1>予定の詳細</h1> <div class="row_line"> <%= link_to '予定一覧へ', schedules_path, class: 'btn primary-btn' %> </div> <div class="row_line"> <label>件名:</label> <%= @schedules.title %> </div> <div class="row_line"> <label>予定の日付:</label> <%= @schedules.date_of.strftime("%Y年%m月%d日") %> </div> <div class="row_line"> <label>開始時間 ~ 終了時間: </label> <%= @schedules.start_time.strftime("%H:%M") %> &nbsp;~&nbsp; <%= @schedules.end_time.strftime("%H:%M") %> &nbsp;( <label>集合時間:</label> <%= @schedules.meeting_time.strftime("%H:%M") %> ) </div> showアクションまわりの動作を確認する。 まあ動いたので、ひとまずよしとする。 (個人的には「04月」の表記が気に入らないが、修正の労力があったら機能実装に振りたい気持ち) 疲れたので今日はここまで。 (BootstrapやRspecをはやく導入しなければ...) 次回の記事>>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む