Ruby on Railsポートフォリオでつまりました。

僕がポートフォリオでつまった事象をまとめました。
記事を分けるべきだと感じましたが、備忘録ついでに何かの役に立てばと記事にしたものなので
怒らないでね♥







# ランキング機能

~~~index.html.erb
def index
    @tweets = Tweet.includes(:user).limit(10).sort {|a,b|
    b.likes.size <=>
    a.likes.size
    }
end
~~~

これでいいのかな。。。。


# bootstrapのセレクトボックスがきかない！なんで！

誤り
~~~
<%= f.select :subject, [["通常のお問い合わせ", "normal"], ["通報", "report"], ["その他", "other"]], class: 'form-control' %>
~~~

正解
~~~
<%= f.select :subject, [["通常のお問い合わせ", "normal"], ["通報", "report"], ["その他", "other"]], {}, class: 'form-control' %>
~~~

# 本番環境でmigrationできない！
~~~
bundle exec rails db:migrate RAILS_ENV=production
== 20220305031225 Posts: migrating ============================================
-- drop_table(:posts)
rails aborted!
StandardError: An error has occurred, all later migrations canceled:
~~~

前に消したモデルのファイルがエラーを出してる！！
postsテーブルを消したいけどpostsテーブルがないよ！！って意味らしい！！たぶん！

この2022うんたらかんたらを削除して、まずはgitへ！更新！
そしたらEC2インスタンスでもう一回
~~~
bundle exec rails db:migrate RAILS_ENV=production
~~~
出来ました。




# 本番環境にseedファイルをよみこみたい！
アプリケーション上で
~~~"EC2"
rails db:seed RAILS_ENV=production
~~~


# 入力フォームをjsファイルでバリデーションチェック！

~~~
document.addEventListener('DOMContentLoaded', () => {
  // .validationForm を指定した最初の form 要素を取得
  const validationForm = document.querySelector('.validationForm');
  // .validationForm を指定した form 要素が存在すれば
  if(validationForm) {
    // エラーを表示する span 要素に付与するクラス名（エラー用のクラス）
    const errorClassName = 'error';

    // required クラスを指定された要素の集まり
    const requiredElems = document.querySelectorAll('.required');
    // email クラスを指定された要素の集まり
    const emailElems =  document.querySelectorAll('.email');

    // エラーメッセージを表示する span 要素を生成して親要素に追加する関数
    // elem ：対象の要素
    // errorMessage ：表示するエラーメッセージ
    const createError = (elem, errorMessage) => {
      // span 要素を生成
      const errorSpan = document.createElement('span');
      // エラー用のクラスを追加（設定）
      errorSpan.classList.add(errorClassName);
      // aria-live 属性を設定
      errorSpan.setAttribute('aria-live', 'polite');
      // 引数に指定されたエラーメッセージを設定
      errorSpan.textContent = errorMessage;
      // elem の親要素の子要素として追加
      elem.parentNode.appendChild(errorSpan);
    }

    // form 要素の submit イベントを使った送信時の処理
    validationForm.addEventListener('submit', (e) => {
      // エラーを表示する要素を全て取得して削除（初期化）
      const errorElems = validationForm.querySelectorAll('.' + errorClassName);
      errorElems.forEach( (elem) => {
        elem.remove();
      });

      // .required を指定した要素を検証
      requiredElems.forEach( (elem) => {
        // 値（value プロパティ）の前後の空白文字を削除
        const elemValue = elem.value.trim();
        // 値が空の場合はエラーを表示してフォームの送信を中止
        if(elemValue.length === 0) {
          createError(elem, '入力は必須です');
          e.preventDefault();
        }
      });

      // .email を指定した要素を検証
      emailElems.forEach( (elem) => {
        // Email の検証に使用する正規表現パターン
        const pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
        // 値が空でなければ
        if(elem.value !=='') {
          // test() メソッドで値を判定し、マッチしなければエラーを表示してフォームの送信を中止
          if(!pattern.test(elem.value)) {
            createError(elem, 'メールアドレスの形式が正しくないようです。');
            e.preventDefault();
          }
        }
      });

      // エラーの最初の要素を取得
      const errorElem =  validationForm.querySelector('.' + errorClassName);
      // エラーがあればエラーの最初の要素の位置へスクロール
      if(errorElem) {
        const errorElemOffsetTop = errorElem.offsetTop;
        window.scrollTo({
          top: errorElemOffsetTop - 40,  // 40px 上に位置を調整
          // スムーススクロール
          behavior: 'smooth'
        });
      }
    });
  }
});
~~~

サイトから持ってきたやつをそのまま使おう。

ん？

~~~
<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="name">名前 </label>
    <input class="required maxlength" data-maxlength="10" type="text" name="name" id="name">
  </div>

  <div>
    <label for="email1">メールアドレス </label>
    <input class="required email" type="email" id="email1" name="email1" size="30">
  </div>

  <div>
    <label for="inquiry">お問い合わせ内容</label>
    <textarea class="required maxlength" data-maxlength="100" name="inquiry" id="inquiry" rows="5" cols="50"></textarea>
  </div>
  <button name="send">送信</button>
</form>
~~~

これをどうやって応用させるんだ？？？

しらなかった！！ばか！！form_withにもclassをつけることができる！！

~~~
 <%= form_with model: @contact, url: confirm_path, class: 'validationForm' do |f| %>
        <div class="form-group">
          <%= f.label :name, 'お名前' %>
          <%= f.text_field :name, autofocus: true, class: 'form-control required' %>
        </div>

        <div class="form-group">
          <%= f.label :email, 'メールアドレス'%>
          <%= f.text_field :email, class: 'form-control required email' %>
        </div>

        <div class="form-group">
          <%= f.label :subject, 'ご用件' %>
          <%= f.select :subject, [["通常のお問い合わせ", "normal"], ["通報", "report"], ["その他", "other"]], {}, class: 'form-control' %>
        </div>

        <div class="form-group">
          <%= f.label :message, 'メッセージ' %>
          <%= f.text_area :message, size: '10x10', class: 'form-control required' %>
        </div>

        <div>
          <%= f.submit '入力内容確認', class: "btn btn-primary mb-2" %>
        </div>
      <% end %>
~~~

でもこれリロードしないと動かん。
は？なんで？？
まあターボリンクスだわな。
この場合ただくくるだけではだめで。
jsをこうやって書き換える！

~~~
$(document).on ("turbolinks:load", function(){
  // .validationForm を指定した最初の form 要素を取得
  const validationForm = document.querySelector('.validationForm');
  // .validationForm を指定した form 要素が存在すれば
  if(validationForm) {
    // エラーを表示する span 要素に付与するクラス名（エラー用のクラス）
    const errorClassName = 'error';

    // required クラスを指定された要素の集まり
    const requiredElems = document.querySelectorAll('.required');
    // email クラスを指定された要素の集まり
    const emailElems =  document.querySelectorAll('.email');

    // エラーメッセージを表示する span 要素を生成して親要素に追加する関数
    // elem ：対象の要素
    // errorMessage ：表示するエラーメッセージ
    const createError = (elem, errorMessage) => {
      // span 要素を生成
      const errorSpan = document.createElement('span');
      // エラー用のクラスを追加（設定）
      errorSpan.classList.add(errorClassName);
      // aria-live 属性を設定
      errorSpan.setAttribute('aria-live', 'polite');
      // 引数に指定されたエラーメッセージを設定
      errorSpan.textContent = errorMessage;
      // elem の親要素の子要素として追加
      elem.parentNode.appendChild(errorSpan);
    }

    // form 要素の submit イベントを使った送信時の処理
    validationForm.addEventListener('submit', (e) => {
      // エラーを表示する要素を全て取得して削除（初期化）
      const errorElems = validationForm.querySelectorAll('.' + errorClassName);
      errorElems.forEach( (elem) => {
        elem.remove();
      });

      // .required を指定した要素を検証
      requiredElems.forEach( (elem) => {
        // 値（value プロパティ）の前後の空白文字を削除
        const elemValue = elem.value.trim();
        // 値が空の場合はエラーを表示してフォームの送信を中止
        if(elemValue.length === 0) {
          createError(elem, '入力は必須です');
          e.preventDefault();
        }
      });

      // .email を指定した要素を検証
      emailElems.forEach( (elem) => {
        // Email の検証に使用する正規表現パターン
        const pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
        // 値が空でなければ
        if(elem.value !=='') {
          // test() メソッドで値を判定し、マッチしなければエラーを表示してフォームの送信を中止
          if(!pattern.test(elem.value)) {
            createError(elem, 'メールアドレスの形式が正しくないようです。');
            e.preventDefault();
          }
        }
      });

      // エラーの最初の要素を取得
      const errorElem =  validationForm.querySelector('.' + errorClassName);
      // エラーがあればエラーの最初の要素の位置へスクロール
      if(errorElem) {
        const errorElemOffsetTop = errorElem.offsetTop;
        window.scrollTo({
          top: errorElemOffsetTop - 40,  // 40px 上に位置を調整
          // スムーススクロール
          behavior: 'smooth'
        });
      }
    });
  }
});
~~~


おい、ディセイブルしろよ。
なんかエラーは出せたんだけど続けてボタンが押せないようになってませんか？
そんなあなたは、こちら！！
~~~
<%= f.submit '入力内容確認', class: "btn btn-primary mb-2", "data-disable-with": false %>
~~~

Rails の仕様でデータを二重に送信させないようにするための仕様でこうなるらしいです。
最後に"data-disable-with": falseを記述することで、ボタンを活性化させる（？）らしい。





# おかあさーん！！開発環境ではうまくいったのにメールがうまくうごかないよーーーーー！！！
~~~log/production.log
ArgumentError (SMTP To address may not be blank: []):
~~~
サーバーがないとかそんな感じでしょう。
それはわかる。
じゃあ。どうするか。
確かメールを送るためにはサーバー的なものが必要で、Googleアカウントのユーザーネームとパスワード(自分で作るんじゃなくて設定して勝手に作られる)をファイルに書いたよね。直接gitにあがるとまずいから.envファイルに書いたんだ。
それからどうやらdevelopment.rbに書いたサーバーの内容をproduction.rbにも書かなきゃいけないっぽい。
production.rbはdevelopment.rbと同じディレクトリにあるファイルだよ。

~~~production.rb
config.action_mailer.perform_caching = false #?

  config.action_mailer.default_url_options = { host: 'ドメインの名前'} #?

  config.action_mailer.perform_deliveries = true #?

  config.action_mailer.delivery_method = :smtp
    config.action_mailer.smtp_settings = {
      port: 587,
      address: 'smtp.gmail.com',
      domain: 'smtp.gmail.com',
      user_name: ENV['SMTP_USERNAME'],
      password: ENV['SMTP_PASSWORD'],
      enable_starttls_auto: true
  }

  config.action_mailer.perform_caching = false  #?

  config.action_mailer.raise_delivery_errors = true #?
~~~

すまんお前ら。#?って書いてるところは何で書いてるか知らん。
少なくとも開発環境では書いてなくても動いた。

次に.envファイルの書き換えはこう！
~~~.cloud9ターミナル
scp -i ~/.ssh/practice-aws.pem .env ec2-user@パブリックＩＰアドレス:アプリの名前/
~~~

だが。俺氏ここで躓く。不覚。
実はうまくいくはずなのだが
~~~
Permission denied
~~~


### おっと～～～～？？？

なんだこれ。とりあえずうまくいってないわ。それはわかる。
原因はこれでした。

~~~.ec2
$ ls -la
  |
  |
 省略
　|
-rw-r--r--   1 aaaaa aaaaa    249 Mar 31 10:11 .env
  |
  |
~~~

aaaaaとしてますが違う名前が入ってました！なんで！
詳しく説明できませんが変更の方法はこうです。

~~~.ec2
$sudo chown 変えたい名前:変えたい名前 .env
~~~

これで変更おっけー！

そしてさっきの変更コマンド。起動しなおせば大丈夫！