20201117のRailsに関する記事は20件です。

いらないコメントを一括削除する方法

これを覚えると時間の節約になるのでおすすめです。

前提として、環境はMacでエディターはVSCodeです。(Atomでや他のエディターでも同様のコマンドで可能かと思います)

Railsでテストアプリを作っている時などによく出る長文のコメント結構邪魔ですよね。

スクリーンショット 2020-11-17 23.19.03.png

そんな時は 

  • command+Fで任意のページで検索して
# .*\n
  • このコマンドを打ちますと(今回はVScode使ってます)

スクリーンショット 2020-11-17 23.27.46.png

  • 結果はありませんとなってしまいましたが、正規表現を表す

スクリーンショット 2020-11-17 23.31.44.png
↑こちらのボタンを押すと25個ヒットしました!

スクリーンショット 2020-11-17 23.29.24.png

  • 全て置換すると

スクリーンショット 2020-11-17 23.33.52(2).png

スクリーンショット 2020-11-17 23.36.15.png

コメントが一気に消えます。

インデントを揃えるとだいぶ見やすくなりました!
スクリーンショット 2020-11-17 23.41.40.png

ちなみにバックスラッシュの打ち方は日本語キーボードですと

Option + ¥  = \

です。

是非試してみてくださいね。

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

LINEみたいに自分のメッセージと自分以外のメッセージの表示場所を変えたい

はじめに

 現在作成中のチャットアプリのメッセージ画面をLINEのように自分の送ったメッセージは右、自分以外のメッセージは左に表示させよと一日苦戦した。ググってもあまり記事が見つからなかったので、ここに記録しておく。

ポイントとして使ったもの

  • renderメソッド
  • if文
  • deviseのメソッド

コントローラー

messages_controller.rb
def index
    @class_room = ClassRoom.find(params[:class_room_id])
    @messages = @class_room.messages.includes(:user)
  end

@class_roomにチャットルームをparamsから呼び出し代入
@messagesにそのチャットルームのメッセージを全て入れる。
.includes(:user)はN+1問題を解消するため。

ビュー

※divタブはコードが読みにくくなるため、ここでは割愛。

<% if message.user.id == current_user.id %>
    <%= l message.created_at %>
    <%= message.content %>
<% else %>
    <%= message.user.last_name %>
    <%= l message.created_at %>
    <%= message.content %>
<% end %>

if文で、メッセージのユーザーIDと現在のログインしているユーザーが同じか照会し、同じであれば(つまり、自分の送ったメッセージ)上の処理が行われ、違えば、下の処理が行われる。
自分のメッセージに送り主の表示は必要ないので、上の処理にmessage.user.last_nameがない。

その他

 CSSを整える。

終わりに

 他のやり方として、コントローラーの時点で、自分のメッセージと自分以外のメッセージを別の変数に入れる方法も考えられる。

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

【初心者向け】Rails6で作られたWebアプリをCircleCIを使いAWS ECR・ECSへ自動デプロイする方法②-1 インフラ構築編【コンテナデプロイ】

記事の続きをご覧いただきありがとうございます。
前回の下準備編の続きになります。今回はインフラ構築編②-1です。

タイトル
① 下準備編
②-1 インフラ構築編 ←今ここ!
②-2 インフラ構築編(執筆中)
自動デプロイ編(執筆中)

少し長いので覚悟してください!笑

それでは、早速やってきましょう!

インフラ構築編②-1

最初に流れを説明します。ここでやることは主に2つだけですが、色々と設定する項目があります。地道にやっていきましょう。

  • クラスターの作成
  • RDSの設置

このような構成になってます。

クラスターの作成

クラスターの作成をawsコマンドを使って作成します。クラスターというのはコンテナインスタンスの集合体の名称です。
(クラスターという単語は、今年話題になったので日本語の意味からなんとなく察しが付いた方も多いと思います)

ざっくりいうと、このクラスターの中にRailsとNginxのDockerコンテナ(このDockerコンテナの集まりをServiceと呼びます)が配置されるといった感じです。

以下のコマンドを順番に実行して作成していきます。

$ ecs-cli configure profile --profile-name 任意のプロフィール名 --access-key アクセスキー --secret-key シークレットアクセスキー
$ ecs-cli configure --cluster 任意のクラスター名 --default-launch-type EC2 --config-name 任意の設定名 --region ap-northeast-1
$ ecs-cli up --keypair キーペア名 --capability-iam --size 2 --instance-type t2.small --cluster-config 任意の設定名 --ecs-profile 任意のプロフィール名

私の場合は、アプリ名(protuku-app)を名前にしたので、

$ ecs-cli configure profile --profile-name protuku-app --access-key xxxxxxxxxxxxxxxx --secret-key xxxxxxxxxxxxxx
$ ecs-cli configure --cluster protuku-app-cluster --default-launch-type EC2 --config-name protuku-app-cluster --region ap-northeast-1
$ ecs-cli up --keypair protuku-app --capability-iam --size 2 --instance-type t2.small --cluster-config protuku-app-cluster --ecs-profile protuku-app

のような感じでコマンドを実行しました。

また、私の場合、最後のecs-cli upコマンドを実行したとき

time="2020-10-22T20:41:46+09:00" level=fatal msg="Error executing 'up': describe instance type offerings: AuthFailure: AWS was not able to validate the provided access credentials\n\tstatus code: 401, request id: xxxxxxxxx

のようなエラーが出ました。もし、このようなエラーが起きたら、キーペアやアクセスキーの設定が間違っている可能性があります。私は、IAMの作成からやり直したらうまくいったのでこの辺の設定が間違っていた可能性があります。

また、ローカルのPCの時刻がずれていてもこのようなエラーが起きる可能性がありますので、PCの時刻がずれていないかどうか確認してください。
詳しくはこちらの記事を参考にしてみるといいかもしれません。

最後のecs-cli upコマンドの実行に成功すると次のように、クラスター用のVPC、サブネット、セキュリティグループなどが作成されます!

$ ecs-cli up --keypair protuku-app --capability-iam --size 2 --instance-type t2.small --cluster-config protuku-app-cluster --ecs-profile protuku-app
INFO[0000] Saved ECS CLI cluster configuration protuku-app-cluster-4
INFO[0000] Using recommended Amazon Linux 2 AMI with ECS Agent 1.46.0 and Docker version 19.03.6-ce
INFO[0000] Created cluster                               cluster=protuku-app-cluster region=ap-northeast-1
INFO[0001] Waiting for your cluster resources to be created...
INFO[0001] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0062] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
INFO[0122] Cloudformation stack status                   stackStatus=CREATE_IN_PROGRESS
VPC created: vpc-xxxxxxxxxxxxxxxxxx
Security Group created: sg-xxxxxxxxxxxxxxxxxx
Subnet created: subnet-xxxxxxxxxxxxxxxxxx
Subnet created: subnet-xxxxxxxxxxxxxxxxxx
Cluster creation succeeded.

ServicesからElastic Container Service(ECS)を選択し、Clusterをクリックすると、以下のようにクラスターが生成されているのが確認できると思います。(こちらはデプロイ後の画面なのでService、Taskが1になってますが、実際はまだ作成していないので0になっているはずです。)

スクリーンショット 2020-11-16 154912.png

RDSの作成

次にRDSを作成していきます。DBサーバーにはMySQLを利用します。
RDSというのは、 AWSのフルマネージドなリレーショナルデータベースのサービスになります。バックアップ、DBのアップデート、スケーリングなどをAWSがすべて自動でやってくれるので、よりコアな開発に集中することができる!といったものです。

RDSにはインターネット上からアクセスできないようにしたいのでプライベートサブネットに配置します。また、冗長化のために複数のアベイラビリティゾーンにRDSを設置していきます。

まず、事前準備として、RDS用のプライベートサブネットを2つ(リージョンが1aと1cのものでいいと思います)と、DBパラメータグループ、DBオプショングループを作成していきます。

RDS用のプライベートサブネットの作成

他記事で恐縮ですが、プライベートサブネットの作成に関してはこちらの記事が大変わかりやすいので、こちらを見て作成してみてください。VPCはすでにあるのでサブネットの作成だけでOKです。
一番近い東京リージョンであるap-northeast-1aと1cのふたつのプライベートサブネットを作りこのサブネットにRDSを設置していきます。

RDS用のサブネットグループを作成する

サブネットグループというのは、VPC内にあるサブネットを複数指定して、RDSインスタンスが起動するサブネットを指定した設定のことです。マルチAZを実現させるために、この設定をする必要があります。

まず、ServicesからRDSのコンソールへいき、Subnet groupsを選択し、Create DB Subnet Groupをクリックします。
以下のように任意の名前を入れます。VPCには作成したVPCを選択します。
スクリーンショット 2020-11-16 162151.png

次に最初に作った2つのプライベートサブネットを追加していきます。
Avalilability Zonesは1aと1c選択し、作成したサブネットを2つ選択して、Createをクリックして作成完了です。
スクリーンショット 2020-11-16 162416.png

DBパラメータグループの作成

RDSではDBの設定ファイルを直接編集するといったことができないので、代わりにパラメータグループというのを使って設定値を編集することができます。
デフォルトでパラメータグループは作成されるのですが、こちらは設定値を変えることができないので、自分で作成する必要があります。

RDSのコンソールからParameter groupsをクリックし、Create parameter groupをクリック
group familyにmysql5.7を選択し、任意の名前を入れてCreateして完了です。
スクリーンショット 2020-11-16 235242.png

DBオプショングループの作成

オプショングループはDBの機能的な部分を設定します。プラグインを導入したいとかそういったときに利用します。
こちらもデフォルトで設定されるのですが、デフォルトのものを編集するのではなく、自分で作成したものを編集するのがセオリーのようです。後々変更したいといったときのために作成しておきます。

RDSのコンソールからOption groupsを選択し、Create groupをクリック
こちらも任意の名前を入力して、mysqlの5.7を選択し、createをクリックして作成完了です。
スクリーンショット 2020-11-17 001027.png

RDSインスタンスの設置

さて、下準備が整ったのでやっとインスタンスを作成できます!
RDSのコンソールからDatabaseを選択し、Create databaseをクリック。
スタンダードを選択し、DBはMySQLを選択
スクリーンショット 2020-11-16 155517.png
今回は無料タイプを選択します。
スクリーンショット 2020-11-16 155601.png
次に、任意のDBインスタンス名を入力します。

Credentials Settingsをクリックし、DBに接続するときの任意のユーザー名とパスワードを設定します。
db_name.png

こちらはなるべく安く済ませたいので、バースト可能クラスを選び、t3.microを選択します。
スクリーンショット 2020-11-16 155920.png
マルチAZの設定は今回は無しにします。作成すると自動でマルチAZにできるのですが、料金がかかってしまうため設定しません。デフォルトだと作成するにチェックが入っているので気を付けましょう。(英語版だけかも)
スクリーンショット 2020-11-17 002910.png
作成したVPCとサブネットグループを選択します。パブリックアクセスは無しを選択します。
スクリーンショット 2020-11-17 002925.png
ここで新規にRDSのセキュリティグループを作成します。任意の名前を入力し、アベイラビリティゾーンは1aを選択します。
ポート番号はデフォルトの3306のままいきます。
スクリーンショット 2020-11-17 003027.png
次に追加の接続設定を開き、database nameを入力します。
これは、Railsアプリのconfig/database.ymlproduction:にあるdatabaseの名前と同じ名前を入力しましょう。
たとえば私の場合は、webapp_productionと入力しました。
その後、上記で作成したパラメータグループとオプショングループを選択します。(画像は諸事情によりデフォルトのままになってますので気をつけてください。)
スクリーンショット 2020-11-17 004248.png

その他色々設定がありますが、他はデフォルトのままでとりあえずはOKだと思います!

設定があっていることを確認してCreateDatabaseをクリックしてRDSの作成は完了です。

RDSのセキュリティグループを設定する

現状のままではセキュリティグループが設定されていないので、DBにすべてのトラフィックを許可してしまってる状態です。これではセキュリティー上よろしくないので、DBにはサーバーからのSSH接続のみ許可するように設定していきます。

EC2のコンソールへいき、Security Groupsを選択し、Edit inbound rulesを選択します。
スクリーンショット 2020-11-17 214920.png
設定は

  • タイプ: MYSQL/Aurora
  • プロトコル: SSH
  • ポート: 3306
  • ソース: クラスターのセキュリティグループ(最初にawsコマンドを実行したとき自動で作られてるはずです)

設定を入力したら、Createをクリックします。これでセキュリティグループの作成は完了です。
スクリーンショット 2020-11-17 215127.png
セキュリティグループの作成が完了したら、実際にDBに接続できるか確認してみましょう。
サーバーにSSHログインして、以下のコマンドを実行します。サーバーへのSSHログインの方法は割愛します。
windowsならRLoginやPuttyなどを使ってログインできるのでググってみてください。

[ec2-user@ip-xxxxxxxx ~]$ mysql -u 設定したユーザー名 -h RDSのエンドポイント -p

以下のような画面になったら成功です。(mysql not found とかでてきたらmysqlをインストールする必要があります。ref: https://hacknote.jp/archives/51267/)
スクリーンショット 2020-11-17 215459.png

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

今回はここで一旦区切ります。次回はインフラ構築編②-2として、Webからのアクセスを負荷分散するために、ALB(アプリケーションロードバランサー)を作成する方法を書いていこうと思いますので、乞うご期待ください!

思い出しながら調べつつ記事を書いているので、ご指摘や、ご不明な点などあればコメントいただけますと嬉しいです!

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

未経験のおっさんがプログラミングを学ぶまで

はいどうも!
某プログラミングスクールに通い始めた37歳のおっさんです。
プログラミングは完全に未経験。
そんなおっさんが転職するまでの軌跡(奇跡?)をぼちぼち綴っていこうと思います。

投稿は今日が初めてですがスクールは今日で9日目。
HTML&CSSをやってRubyに入り、Railsをやって試験を受けて、というところです。

簡単に私の経歴を
↪️
地方出身、東京の文系私立大学卒、東京で就職し営業職(toC & toB)でマネジメント経験あり。
インディーズですが学生時代にやっていたバンドでCDを1枚だけ出しています。
キャンプとジム通い、音楽掘りが趣味。
ジャンルはRock、JazzからEDMまで幅広く聴きます。
今はちょっと難しくなってしまいましたが海外旅行もそこそこ行きました。




なぜプログラミングを始めたのか、どうして転職を決意したのかなどは後々ゆっくりとupしていこうと思います。
とりあえずまだ始めたばかりですが感想としては
「めちゃくちゃやることがいっぱいある」
「勉強すること、覚えることが今までやってきたことの比じゃないレベルで多い」
この二つですね。
日々思ったこと、できなかったこと、疑問点などなど
自分が見返した時に
「あ〜こんなことあったな」とか、
「こんなことで悩んでたんだな」とかとか、
あとはこれを読んだ他の方に少しでも役に立てればなと思います。

そんな具合ですが本日は頭がパンクしているのでこれにて:wave_tone1:

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

Deviseでログイン機能

はじめに

今回はDeviseを使ってログイン機能を作成していきます。
またDeviseの日本語化やBootstrap4の適用までを行っていきます。

準備

$ rails new devise-sample 
$ rails g controller homes index
config/routes.rb
Rails.application.routes.draw do
  root 'homes#index'
end

ナビバーを追加し,レスポンシブ対応のためのmetaタグを追加

app/views/layouts/application.html.erb
 (略)
     <%= csrf_meta_tags %>
     <%= csp_meta_tag %>
     <meta name="viewport" content="width=device-width,initial-scale=1">  //追加
 (略)
  <body>
     <%= render 'shared/header' %>     //追加
     <%= yield %>
   </body>
app/views/homes/index.html.erb
<%= render 'shared/flash_messages' %>

新しくsharedフォルダと各renderファイルを作成

フラッシュメッセージ

app/views/shared/_flash_messages.html.erb
<% flash.each do |msg_type, msg| %>
  <div class="alert alert-<%= msg_type %>" role="alert" id="alert">
    <a href="#" class="close" data-dismiss="alert">×</a>
    <%= msg %>
  </div>
<% end %>

ヘッダー

app/views/shared/_header.html.erb
<header>
  <nav class="navbar navbar-expand navbar-light">
    <%= link_to "Deviseサンプル", root_path, class: 'navbar-brand' %>
    <div id="Navber">
      <ul class="navbar-nav">
        <% if user_signed_in? %>
          <li class="nav-item active">
            <%= link_to 'アカウント編集', edit_user_registration_path, class: 'nav-link' %>
          </li>
          <li class="nav-item active">
            <%= link_to 'ログアウト', destroy_user_session_path, method: :delete, class: 'nav-link' %>
          </li>
      <% else %>
          <li class="nav-item active">
            <%= link_to "新規登録", new_user_registration_path, class: 'nav-link' %>
          </li>
          <li class="nav-item active">
            <%= link_to "ログイン", new_user_session_path, class: 'nav-link' %>
          </li>
        <% end %>
      </ul>
    </div>
  </nav>
</header>

Gemの追加

Gemfileに以下を追加して$ bundle install

# ログイン機能に必要なGem
gem 'devise'

# 日本語化に必要なGem
gem 'rails-i18n', '~> 5.1'
gem 'devise-i18n'

# Bootstrapに必要なGem
gem 'bootstrap', '~> 4.4.1'
gem 'jquery-rails'
gem 'devise-bootstrap-views', '~> 1.0'
$ bundle install

Bootstrapの導入

  • application.cssの拡張子をscssに変更

  • application.scssから,*= require_tree .*= require_selfを削除

  • application.scss@import "bootstrap";を追加

  • スタイルも追加

app/assets/stylesheets/application.scss
@import "bootstrap";

// ログイン画面

.container-login {
  @extend .container-fluid;
  max-width: 576px;
  padding: 2rem;
}

// 「ログインしました」などのフラッシュ用スタイル

.alert-notice {
  @extend .alert-info;
}

.alert-alert {
  @extend .alert-danger;
}

application.jsに3つ追加

Bootstrapと依存関係をapplication.jsに追記する

app/assets/javascripts/application.js
//= require jquery3
//= require popper
//= require bootstrap-sprockets

Deviseの導入

Deviseをインストール(userの箇所は,任意のモデル名でOKです)

$ rails g devise:install
$ rails g devise user
$ rails db:create db:migrate

補足
- Bootstrapのtooltipsやpopoverはpopper.jsに依存している
- bootstrapの依存gemにpopper_jsが指定されているため新たにインストールは不要
- コンパイルを高速化したい場合はbootstrap-sprocketsの代わりにbootstrapを指定する

問題がなければ,$ rails sの後,http://localhost:3000からログインボタンを押せば,ログイン画面が表示されます。

スクリーンショット 2020-11-16 15.19.55.png

Deviseの日本語化

config/application.rb
module AssociationTutorial
  class Application < Rails::Application
    # 以下を追加すれば日本語に
    config.i18n.default_locale = :ja 
    # タイムゾーンも変更するなら,以下を追加
    config.time_zone = 'Asia/Tokyo'   
  end
end

サーバーを落として$ rails s
で再起動すれば日本語に変更されます。

スクリーンショット 2020-11-16 15.28.53.png

日本語訳を変更

日本語訳を変更したい場合は,次のコマンドでconfig/locales/devise.views.ja.ymlを作成し,編集すればOKです。

$ rails g devise:i18n:locale ja

例えばアカウント登録を新規登録に変更したい場合は,config/locals/devise.views.ja.ymlの該当文字を置換すればOKです。

パスワードを忘れましたか?も違和感がありますので,パスワードの再設定に置換するのがよいと思います

ログイン画面などの変更

まず,次のコマンドでビューファイルを作成します。

$ rails g devise:i18n:views
$ rails g devise:views:bootstrap_templates -f

【参考】それぞれのコマンドの最後に例えばuserをつけることで,usersディレクトリ内にファイルを作成することもできますが,その場合は,次の3つの作業を行わないと反映されません。

devise.views.ja.yml30行目のdeviseusersに変更
config/initializers/devise.rbにあるconfig.scoped_views = falseのコメントアウトを外してtrueに変更
サーバーを落として$ rails sで再起動

お好みで変えていく
例えば

<div class="container-login">
  # 元のプログラム
</div>

スクリーンショット 2020-11-17 21.05.45.png

エラーメッセージの変更

エラーメッセージの表示がいまいちなので変更するためにオーバーライドします。
app/helpers/devise_helper.rbファイルを作成し、以下を記述。

app/helpers/devise_helper.rb
module DeviseHelper
  def bootstrap_devise_error_messages!
    return "" if resource.errors.empty?

    html = ""
    resource.errors.full_messages.each do |error_message|
      html += <<-EOF
      <div class="alert alert-danger alert-dismissible" role="alert">
        <button type="button" class="close" data-dismiss="alert">
          <span aria-hidden="true">&times;</span>
          <span class="sr-only">close</span>
        </button>
        #{error_message}
      </div>
      EOF
    end
    html.html_safe
  end
end

スクリーンショット 2020-11-17 21.16.46.png

ログイン画面にもエラーメッセージを追加

app/views/devise/sessions/new.html.erb
 <div class="container-login">
   <h1><%= t('.sign_in') %></h1>
   <%= render 'shared/flash_messages' %>    //追加

   <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
  • app/views/deviseディレクトリ内のファイルのbtn btn-primarybtn btn-primary btn-blockに置換すればボタンの横幅が自然になります。

スクリーンショット 2020-11-17 21.21.35.png

バリデーション

バリデーションはフロント側にも簡単に入れられます。

  • f.email_field,f.password_fieldrequired: trueを入れることで空欄投稿できなくなります。
  • 新規登録(アカウント登録)画面では,例えば,f.password_fieldrequired: true, minlength: @minimum_password_length, maxlength: '30'を追加すれば,文字数のバリデーションを追加できます。
app/views/devise/registrations/new.html.erb
 <div class="form-group">
       <%= f.label :password %>
-      <%= f.password_field :password, autocomplete: 'current-password',
-                                      class: 'form-control' %>
+      <%= f.password_field :password, autocomplete: 'current-password',
+                                      class: 'form-control',
+                                      required: true,
+                                      minlength: @minimum_password_length,
+                                      maxlength: '30' %>

スクリーンショット 2020-11-17 21.33.29.png

_links.html.erbを編集して,一番下のリンクをボタンにしてみます。

<hr class="border-dark my-5">
<div class="form-group">
  <%- if controller_name != 'sessions' %>
    <%= link_to t(".sign_in"), new_session_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
    <%= link_to t(".sign_up"), new_registration_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
    <%= link_to t(".forgot_your_password"), new_password_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %>
    <%= link_to t('.didn_t_receive_confirmation_instructions'), new_confirmation_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %>
    <%= link_to t('.didn_t_receive_unlock_instructions'), new_unlock_path(resource_name), class: 'btn btn-secondary btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.omniauthable? %>
    <%- resource_class.omniauth_providers.each do |provider| %>
      <%= link_to t('.sign_in_with_provider', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider), class: 'btn btn-info btn-block' %><br />
    <% end -%>
  <% end -%>
</div>

スクリーンショット 2020-11-17 21.37.44.png

さらに,リンクのログイン,新規登録(アカウント登録)を次のように変更してみます

# 上2つを次に置き換え

  <%- if controller_name != 'sessions' %>
    <%= link_to "アカウントをお持ちの方", new_session_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>

  <%- if devise_mapping.registerable? && controller_name != 'registrations' %>
    <%= link_to "アカウントをお持ちでない方", new_registration_path(resource_name), class: 'btn btn-info btn-block' %><br />
  <% end -%>
app/views/devise/registrations/edit.html.erb
<!-- ここから -->
  <p><%= t('.unhappy') %>
    ? <%= link_to t('.cancel_my_account'), registration_path(resource_name), data: {confirm: t('.are_you_sure')}, method: :delete %>
    .</p>

  <%= link_to t('.back'), :back %>
<!-- ここまでを次に置き換える -->

  <hr class="devise-link my-5">
  <div class="form-group">
    <%= link_to "トップページ", root_path, class: 'btn btn-info btn-block mb-4' %>
    <%= link_to t('.cancel_my_account'), registration_path(resource_name), data: {confirm: t('.are_you_sure')}, method: :delete, class: 'btn btn-danger btn-block' %>
  </div>

スクリーンショット 2020-11-17 21.47.19.png

これで一応単純なスタイルは完成です。
もし間違っているところがあればご教授いただけると幸いです。

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

混乱しがちなアソシエーションのclass_nameやforeign_keyを理解する

Railsでお知らせ機能やフォロー機能を実装しているとclass_nameやforeign_keyが出てくると思います。これらを理解するのに時間がかかったので、備忘録もかねて投稿します。

今回はお知らせ機能を例に挙げる。
イメージしやすいようER図を載せる。(最低限のカラムしか書いてません。)

お知らせ機能ER図.png

お知らせ機能を実装していると@user.passive_notificationsのようにして自分に来ている通知のレコードをまとめて取得したいことがある。そういう場合、下のように書く。

user.rb
has_many :passive_notifications, class_name: "Notification", foreign_key: "visited_id"

少し複雑なので、簡単な例を挙げる。

Userモデルで下記のように定義した場合

よく使うhas_many :tweetsは実は色々省略されている。

user.rb
has_many :tweets

# 同じ意味
has_many :tweets, class_name: "Tweets", foreign_key: "user_id"

Railsで開発をするとhas_manyやbelongs_toを使ってモデルでリレーションを組むが、これを書くことによって、Userクラスへのticketsメソッドの定義が裏で行われている。例でいうと、ticketsメソッドの対象となるClassはTweetクラスで、ticketsテーブルのuser_idの値を参照してレコードを取得する。

Railsの規則によりモデルの複数形でメソッドを定義する場合、クラス名、外部キーを省略することができる。なぜならメソッド名により暗黙的にクラス名、外部キーが決定されるからである。

上記のようにメソッドを定義する(リレーションを組む)ことで、コントローラーなどで下のメソッドが使えるようになる。

user.rb
@user = User.find(params[:id])
@user.tweets

少し脱線したが、このことを頭に入れてお知らせ機能のリレーションをもう一度見てみる。

user.rb
has_many :passive_notifications, class_name: "Notification", foreign_key: "visited_id"

ここの記述では、Userモデルにpassive_notificationsメソッドを定義している。
メソッド名からはクラス名が不明なため、class_name: "Notification"として対象のクラスを明示している。また、外部キーを設定しない場合、notificationsテーブルからuser_idのカラムを探しにいってしまうため、foreign_key: "visited_id"として定義する。

これにより、@userのidとnotificationsテーブルのvisited_idとが一致したレコード、つまり@user宛の通知のレコードがまとめて取得できるようになる。

user.rb
@user = User.find(params[:id])
@user.passive_notifications #ユーザ宛の通知のレコードを全件取得

以上です。
まちがっていたらご指摘お願いします。

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

Railsのユーザ入力情報の保護機能

本投稿の目的

・Rails学習の議事録です。


学習に使った教材

Udemyの "はじめてのRuby on Rails入門-RubyとRailsを基礎から学びWebアプリケーションをネットに公開しよう" を教材として使用しました。


①Validates(バリデート)

・modelファイルに対して実施
・ユーザーが入力した値をdb保存する際の条件を指定できる
・Ex)空入力禁止,最大文字数10文字まで...

qiita.rb
def model
  validates :column, 条件: 値の指定
end

【解説】
○validates :column名
⇒条件を課したいcolumn名を記載

○条件: 値の指定
⇒実施したい制約に応じた記述法を記述
⇒以下に2つ例を記述する

・空入力禁止

qiita.rb
presence: true

・最大文字数10文字まで

qiita.rb
length: { maximum: 10 } 

②params

・配列を入れるための箱(ハッシュ)
・ユーザーが送信したデータを一時的にparamsに格納
・その中から,必要なハッシュにヒットする値を取り出す

【例: ユーザーが名前と年齢をフォームから送信した場合】
・フォームからparams へ次のように格納される

qiita.rb
params = [:name,'naoto', :age,24]

・名前を取得したい場合

qiita.rb
 params[:name]

・年齢を取得したい場合

qiita.rb
params[:age]

③strong_parameters

・指定したcolumn情報以外のフォーム受信値を無視するフィルター

【いつ役に立つ?】
・ECサイトを作成したと想定
・ユーザーがフォームでソースを操作
・ポイントcolumnを修正し残高を高めに変更
・こういった改ざん処理を止めるための設定

【使いかた】
・controller中に記述(フォーム送信後のインスタンス生成時)
・引数にこのメソッドを()で指定する
・フィルタされた値がdbへ格納

【例:question_paramsメソッド】

qiita.rb
params.require(:question).permit(:name, :title, :content)

【paramsには以下が格納された場合を想定】

qiita.rb
params = [
question={name: 'naoto',age: 24, content: '質問内容'},
answer={name: 'kanopyo',age: 27, content: '回答内容'}
]

【解説】
〇.requireについて
⇒モデル名のキーを指定

〇.permitについて
⇒.requierで指定したモデルのプロパティのキーを指定

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

logを確認しながらrakeタスクを実装して、対象appを指定して起動しよう(Heroku)

こんにちは、株式会社ベストティーチャーでサーバーサイドエンジニアとして働き出したばかりのタワラです。

業務ではじめて学んだTipsを紹介したいと思います。
今回はrakeタスクを作成してHerokuで動作確認をする手順です。

Rakeタスクを実装する手順

Tips1. ローカルでRakeタスクを作成する際には、Rails.loggerを活用する

こんな感じでコードを書いていたのですが、、、キャッシュを問い合わせるだけだから、これではログに何の情報も出ないな、動作確認はどうしよう、、、と思っているところ、、、

 namespace :hogehoge do
   desc "適当な説明をいれる"
   task :foobar => :environment do
     Rails.cache.fetch('hoge')
   end
 end

先輩「Rails.loggerを使うんだ!」
ボク「なるほど!」

 namespace :hogehoge do
   desc "適当な説明をいれる"
   task :foobar => :environment do
     Rails.logger.info('foobar started!')
     Rails.cache.fetch('hoge')
     Rails.logger.info('foobar finished!')
   end
 end

このように記述しておけば、例えばログに結果が出ない場合の処理でも、rakeタスクの作業確認ができます。こんなふうに↓

log/development.log
略

foobar started!
foobar finished!

略

きちんと動作していることが一目瞭然なのですごい便利!

Tips2. Heroku上で確認するときはアプリ名を指定する

続いて、Heroku上で動作確認をする必要があります、↓のコマンドでrakeタスクを走らせることはできますが、、、

heroku run rake hogehoge:foobar 

先輩「ちょっとお待ち!」
先輩「オプションでプロジェクト名を指定するのを忘れないで!」

heroku run rake hogehoge:foobar -a omosiro-project

先輩「きちんとrakeタスクを起動する対象のプロジェクトなのかを確認しよう!」
先輩「こういうことでバグが起きる可能性はゼロじゃないからね」
ボク「なるほど!」

終わりに

今回のTipsはこの2つ!

Tips1. ローカルでRakeタスクを作成する際には、Rails.loggerを活用する
Tips2. Heroku上で確認するときはアプリ名を指定する

業務ではやはり学ぶことが多いです。
本記事が業務駆け出しの方などの役に立てば幸いです。

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

Railsを使ったToDoリストの作成(3.Railsの基本概念)

概要

本記事は、初学者がRailsを使ってToDoリストを作成する過程を記したものです。
私と同じく初学者の方で、Railsのアウトプット段階でつまづいている方に向けて基礎の基礎を押さえた解説をしております。
抜け漏れや説明不足など多々あるとは思いますが、読んでくださった方にとって少しでも役に立つ記事であれば幸いです。

環境

  • Homebrew: 2.5.10 -> MacOSのパッケージ管理ツール
  • ruby: 2.6.5p114 -> Ruby
  • Rails: 6.0.3.4 -> Rails
  • node: 14.3.0 -> Node.js
  • yarn: 1.22.10 -> JSのパッケージ管理ツール
  • Bundler: 2.1.4 -> gemのバージョン管理ツール
iTerm
$ brew -v => Homebrew 2.5.10
$ ruby -v => ruby 2.6.5p114
$ rails -v => Rails 6.0.3.4
$ npm version => node: '14.3.0'
$ yarn -v => 1.22.10
$ Bundler -v => Bundler version 2.1.4

第3章 Railsの基本概念

第3章では、Railsで本格的にアプリ開発をする前にRailsにおいて重要な概念を説明していきます。

1 Railsを使ってWebサイトを表示する

まずは、RailsがどのようにWebサイトを表示しているのか説明します。
Webサイトは、クライアント(ブラウザ)がリクエストを送り、サーバ(ここではRails)がレスポンス(HTMLやCSSファイルをブラウザに送る)したものを、ブラウザが解釈することで表示されています。

ではRailsでは具体的にどのようなことが行われているか見ていきます。

①まず、ブラウザからGETリクエストが飛んできたらroutes.rbで処理をします。
routes.rbはURLを作る場所です。

config/routes.rb
Rails.application.routes.draw do
  root to: 'boards#index'
end

?‍♂️root(localhost:3000)を表示してくださいというリクエストが飛んできたらBoardsControlerのindexメソッドを実行してください

②次に、コントローラです。
コントローラはroutes.rbからブラウザからのリクエストを受け取り、モデルやビューなどと連携し結果をブラウザに返す役割を担っています。
routes.rbの「BoardsControlerのindexメソッドを実行してください」という情報をもとに、コントローラが処理を行います。

app/controllers/boards_controller.rb
class BoardsController < ApplicationController
    def index
      render 'boards/index' #この部分は省略可能
    end   
end

?‍♂️viewsのboardsのindex.html.hamlを表示してください

ちなみにコントローラには命名規則があります。

名前
コントローラ名 boards
コントローラのクラス名 BoardsController
コントローラのファイル名 boards_controller.rb

上記のように、コントローラ名を「boards」とした場合、コントローラのクラス名は「BoardsController」のようにコントローラ名の先頭を大文字にしてControllerを付けます。このクラスが記載されているファイル名は「コントローラ名_controller.rb」になります。

③最後に、viewsです、viewsにはブラウザに表示したいことを記述します。

app/views/boards/index.html.haml
%h1 Webサイトの表示

スクリーンショット 2020-11-17 12.58.51.png

上の画像のように表示されていれば完了です。
以上が、RailsがWebサイトを表示する仕組みです。

2 MVC

1 MVCとは

MVCとは、Webフレームワークで一般的に取り入れられているアプリケーションの設定を整理するための概念の一つです。
「Model」「Views」「Controller」の頭文字をとって名付けられています。
それぞれに役割があり、ControllerがModelからデータを取得してViewsに表示するという処理が行われています。

スクリーンショット 2020-11-17 16.13.44.png
(画像の引用元:https://snome.jp/framework/mvc-model/)

2 モデルの作成

実際に、操作の対象となる「モデル」を作成します。
モデルを作成するためにはターミナルにて$rails g model [モデル名(単数形)]のコマンドを実行します。

iTerm
$ rails g model Board
=>create db/migrate/20201117041911_create_boards.rb
  create app/models/board.rb

?‍♂️Boardモデルを作成してください
?Boardに対応するデータベースのテーブル(migrationファイル)を作成しました
?Boardに対応するRubyのクラスを作成しました

モデルの作成が完了していたら以下のようなファイルが作成されているはずです。
コメントアウトされている部分は現在のデータベースの構造(schema)のメモであり、第1章で'annotate'をインストールしたため表示されている。

app/models/board.rb
# == Schema Information
#
# Table name: boards
#  id          :bigint           not null, primary key
#  created_at  :datetime         not null
#  updated_at  :datetime         not null
#
class Board < ApplicationRecord
end

モデルが作成できたら、migrationファイルにcolumnを追加していきます。migrationファイルはデータベースのテーブルを作成するファイルです。
今回はボード(タスクのまとまり)の名前と説明をデータとして扱いたいので、'name'と'description'という2つのcolumnを追加します。
columnを追加する際はt.[データ型] :[column名]と入力します。

20201115023512_create_boards.rb
class CreateBoards < ActiveRecord::Migration[6.0]
  def change
    create_table :boards do |t|
      t.string :name, null: false
      t.text :description, null: false
      t.timestamps
    end
  end
end

null: falseオプション→このcolumnには絶対に値が入っていないといけないという指定ができます。今回はタスク名とタスクの説明は必須項目のため指定しています。

マイグレーションをデータベース(PostgreSQL)に適用するには、ターミナルにて$rails db:migrateを忘れずに実行しましょう。

3 ActiveRecord

ActoveRecordとは、Railsで採用されているORマッパーです。
ORマッパーとは、オブジェクトとデータベース間の関係を定義するだけで、データベースへのアクセスが行えるシステムのことです。
つまりRubyを使うことでSQLを書かなくてもデータベースにアクセスすることができるということです。

ActiveRecordはRailsにデフォルトでインストールされており、モデルの中で使うことができます。
では、なぜモデルの中で使うことができるのでしょうか?
以下2つのファイルを見てみましょう。

app/models/board.rb
class Board < ApplicationRecord
end
app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

BoardクラスはApplicationRecordクラスを継承しています。
ApplicationRecordクラスはActiveRecord::Baseクラスを継承しています。
つまりBoardクラスがActiveRecord::Baseクラスを継承しているため、モデルの中でActiveRecordを使うことができます。

ActiveRecord::Baseクラスには様々なメソッドが定義されており、それらのメソッドを実行することにより、データベースにアクセスすることができます。

では具体的にどのようなメソッドが定義されているのか?
代表的なものを見ていきましょう。

◆レコードを取得する

Board.find(1)
#テーブルから引数に入っているidのレコードを取得する

Board.find_by({column: ''})
#テーブルからcolumn名でレコードを取得する
#Board.find_by(column名: '') ->  {}を省略しても良い

Board.first,second,third
#テーブルからidが一番若いレコードを取得する

Board.last
#テーブルからidが一番大きいレコードを取得する

Board.all
#テーブルから全てのレコードをを配列として取得する

Board.all.order(:id)
#レコードををid順に並び替えて取得する
#逆順に並べ替えて取得する時は(id: :desc)

Board.all.limit(3)
#テーブルから引数に入っている数だけidの若い順にレコードを取得する

Board.where('id > ?', 2)
#引数の条件に合ったレコードを全て取得する
#条件は文字列で渡す必要がある

Board.count
#データの件数を数える

◆データの作成・削除

Board.create({name: '', description: ''})
#引数の値を元にインスタンスを作成し、DBに保存する
#board = Board.new({name: 'new', description: 'new'}) -> 空の箱を作る
#board.save -> レコードをDBに保存する

Board.save
#createとは違って保存機能のみ

Board.update
#取得したレコードを更新し保存する
#Board.last -> 対象となるデータを取ってくる
#Board.last.update({column名: ''}) -> 更新する

Board.assign_attributes
#取得したレコードを更新するが保存はしない
#board = Board.last
#board.assign_attributes(title: 'assigned')
#board.save

Board.destroy
#取得したレコードを削除する
#board = Board.last -> 削除する対象のデータを取得する
#board.destroy`

では実際に、ターミナルでコンソールを立ち上げてデータを作成してみましょう。
ターミナルでコンソールを立ち上げるためには以下のコマンドを実行します。

iTerm
$ rails c

コンソールが立ち上がったら、createメソッドを実行しましょう。

iTerm
irb(main):001:0> Board.create(name: 'console-name1', description: 'console-description1')

   (0.2ms)  BEGIN
  Board Create (4.4ms)  INSERT INTO "boards" ("name", "description", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id"  [["name", "console-name1"], ["description", "console-description1"], ["created_at", "2020-11-17 05:38:06.272799"], ["updated_at", "2020-11-17 05:38:06.272799"]]
   (1.5ms)  COMMIT
=> #<Board id: 1, name: "console-name1", description: "console-description1", created_at: "2020-11-17 05:38:06", updated_at: "2020-11-17 05:38:06">

createメソッドを実行することにより、SQLが実行され、テーブルにデータが保存されます。
では、実際に保存されているのかActiveRecord::Baseクラスのallメソッドを使って確かめてみましょう。

iTerm
irb(main):002:0> Board.all
  Board Load (0.9ms)  SELECT "boards".* FROM "boards" LIMIT $1  [["LIMIT", 11]]
=> #<ActiveRecord::Relation [#<Board id: 1, name: "console-name1", description: "console-description1", created_at: "2020-11-17 05:38:06", updated_at: "2020-11-17 05:38:06">]>

下記のようにデータが作成されていることがわかると思います。

Board id: 1,
name: "console-name1",
description: "console-description1",
created_at: "2020-11-17 05:38:06",
updated_at: "2020-11-17 05:38:06">

以上でActiveRecordの説明は終わりです。
コンソールを終了したい時はexitと入力しましょう。

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

Rails覚書

shuffle

要素をシャッフルしてくれる

[1] pry(main)> [1, 2, 3].shuffle
=> [2, 3, 1]
[2] pry(main)> [1, 2, 3].shuffle
=> [3, 2, 1]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS メモ

ElastiCacheにおけるRadis/Memcache

EastiCacheにおけるradis

・複雑なデータ型が必要である。
・インメモリデータセットをソートまたはランク付けする必要がある。
・読込処理の負荷に対して、リードレプリカにレプリケートする必要がある。
・pub/sub機能が必要
・自動的なフェイルオーバーが必要である
・キーストアの永続性が必要である。
・バックアップと復元の機能が必要である。

・複数のデータベースをサポートする必要がある
*提供/シングルスレッドで動作 インメモリDB
 すべてのデータ操作は排他的

ElastiCacheにおけるMemcached

・シンプルなデータ型が必要である
・複数のコアまたはスレッドを持つ大きなノードを実行する必要がある。
・システムでの需要の増減に応じてノードを追加または削除するスケールアウトおよびスケールイン機能が必要である。
・データベースなどのオブジェクトをキャッシュする必要がある。
・キーストアの永続性は必要ない
・バックアップと復元の機能が必要でない
・複数のデータベースを利用できない

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

【Rails6.0】ポートフォリオに必須な「かんたんログイン」実装手順

概要

Railsでアプリを作成する際、deviseを用いてログイン機能を実装する方は多いかと思います。
中でも、ポートフォリオには「かんたんログインをつけよう!」と耳にした方も多いかと。

しかし、私の経験談として、実装には結構苦労しました
理由としては、シンプルに「devise関連で、知らなかったことをやる必要があったから」です。

なので今回の記事では、

  • deviseのコントローラをいじらなきゃいけないの?
  • ルーティングはどうやって設定する?

このあたりを整理しつつ、「かんたんログイン実装手順」をアウトプットしたいと思います。

なお、この記事はこちらの素晴らしい記事を参考にしています。
YouTubeでの詳しい解説もありますので、ぜひ御覧ください。

Qiita | 簡単ログイン・ゲストログイン機能の実装方法(ポートフォリオ用)


環境

  • macOS Catalina 10.15.6
  • ruby 2.6.5
  • Rails 6.0.3.4
  • MySQL : 5.6.47
  • devise : 4.7.3


実装の流れを確認

実装に入る前に、まずかんたんログインの流れをおさえましょう。

かんたんログインの流れ
1. かんたんログインボタンクリック
2-1. 任意のメアド(guest@gmail.com)がusersテーブルにない場合は他カラムを追加しユーザー作成
2-2. 任意のメアド(guest@gmail.com)がusersテーブルにある場合はそれを取得
3. 作成・取得したユーザーでログイン
4. 好きなページにリダイレクト(大体はトップページ)

つまり、必要なのは次の工程です。

必要な工程
1. ルーティングにかんたんログイン用コントローラのパスを設定
2. かんたんログイン用コントローラーを作成
3. リンクをビューに設置

この工程に沿って実装していきます。


実装 : ファイルを3つ編集すればOK

それでは、ルーティング→コントローラ→ビューの順番で実装していきます


1. かんたんログイン用ルーティングの設定

ルーティングファイルに以下の記述を追加して下さい。

routes.rb
devise_scope :user do
  post 'users/guest_sign_in', to: 'users/guest_sessions#new_guest'
end

これにより、次のルーティングが生成します。

Prefix Verb URI Pattern Controller#Action
users_guest_sign_in POST /users/guest_sign_in users/guest_sessions#new_guest

この内容を整理します。

  • post, users_guest_sign_in_pathで
  • app/controllers/users/guest_sessions_controller.rb
  • new_guestアクションを指定

というわけで、指定したディレクトリにコントローラを作成して、かんたんログインアクションを実装しましょう。


2. かんたんログイン用コントローラの作成

app/controllers/users/guest_sessions_controller.rbnew_guestアクションを実装していきます。

  • 名前空間とRailsの決まりごとに従ってクラス名を選択
  • DeviseのSessionsControllerを継承
  • guest@gmail.comの有無でユーザーの作成or取得を変更

以上の点に注意しましょう。

app/controllers/users/guest_sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  def new_guest
    user = User.find_or_create_by!(email: "guest@gmail.com") do |user|
      # ブロックで必要カラムを追加(自分の場合はnicknameを追加)
      user.nickname = "テストユーザー"
      user.password = SecureRandom.urlsafe_base64
      # user.confirmed_at = Time.now  # Confirmable を使用している場合は必要
    end
    # ログイン(deviseのメソッド)
    sign_in user
    # トップページへリダイレクト
    redirect_to root_path
  end  
end

find_or_create_by!は次のようなメソッドです。

  • find_by(カラム: 値)で該当するレコードを取得
  • 見つからない場合は、createで新規レコードを作成
  • ! は例外を発生させる記述 (! がない場合は、ログインされないままただリダイレクトされる)

ちなみに、今回は実装するコードについての示すのが目的なので、名前空間とパスワードの部分については以下の記事を見て補完して下さい (なお、Qiitaは僕が書いた記事です)。


3. ビューにリンクを設置

かんたんログインボタンを実装したいビューにリンクを設置しましょう。
僕のアプリの場合は、トップページに以下のコードを追加しました。

トップページ
<%= link_to 'ゲストログイン', users_guest_sign_in_path, method: :post %>

これで完了です!
(以下のGIFでは自分のアプリ用に詳細を変更しています)
Image from Gyazo


4. (補足) 一部をUserモデルに移植する

DBとやりとりしてレコードを取得・生成するのはモデルの仕事です。
なので次のように切り離すとベターかと思います。

app/controllers/users/guest_sessions_controller.rb
class Users::SessionsController < Devise::SessionsController
  def new_guest
    user = User.guest
    # ログイン(deviseのメソッド)
    sign_in user
    # トップページへリダイレクト
    redirect_to root_path
  end  
end
app/models/user.rb
class User < ApplicationRecord

# (略)

  def self.guest
    find_or_create_by!(email: "guest@gmail.com") do |user|
      user.nickname = "テストユーザー"
      user.password = SecureRandom.urlsafe_base64
    end
  end
end


まとめ

  • かんたんログイン実装には、以下のファイルを作成or編集すればOK
    • かんたんログインアクションへのルーティング
    • deviseコントローラを継承した「かんたんログイン用コントローラ」
    • ボタンを設置したいビュー

いろんな記事があると思いますが、基本はこちらに示したとおりなので、ぜひ参考にして下さい!

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

[Rails]カテゴリー機能

はじめに

アプリ開発において、ancestryというgemを用いてカテゴリー機能を加えたのでまとめました。

目次

  1. カテゴリー選択・保存
  2. カテゴリー検索表示
  3. カテゴリー検索結果表示

1. カテゴリー選択・保存

カテゴリー選択・保存

categoriesテーブルの作成

ancestryをインストールします。

gemfile
gem 'ancestry'

次にcategoryモデルを作成します。

ターミナル
rails g model category

has_ancestryを記述します。

app/models/category.rb
class Category < ApplicationRecord
  has_many :posts
  has_ancestry
end

以下のようにマイグレーションファイルに記述します。
indexについてはこちら

db/migrate/20XXXXXXXXXXXX_create_categories.rb
class CreateCategories < ActiveRecord::Migration[6.0]
  def change
    create_table :categories do |t|
      t.string :name,     index: true, null: false
      t.string :ancestry, index: true
      t.timestamps
    end
  end
end

googleスプレッドシートにカテゴリーを記述していきます。
Aの列がid、Bの列がname(カテゴリー名)、Cの列がancestry(親子孫を見分ける数値)となります。
データの保存方法は、ファイル → ダウンロード → カンマ区切りの値(.csv 現在のシート) の手順で保存できます。

カテゴリー記述例
カテゴリー記述例

ダウンロードしたcsvファイルはdbフォルダに配置します。

seeds.rbファイル内へ以下の通り記述します。

db/seeds.rb 
require "csv"

CSV.foreach('db/category.csv') do |row|
  Category.create(:id => row[0], :name => row[1], :ancestry => row[2])
end 

ターミナルでrails db:seedコマンドを実行するとcsvファイルを読み込み自動でDBのレコードが生成されます。
foreachの後に読み込みたいファイルの指定を行います。
その下の記述については、モデル名.create(カラム名 => 読み込みたい列)となります。
row[0] → Aの列がid
row[1] → Bの列がname(カテゴリー名)
row[2] → Cの列がancestry(親子孫を見分ける数値)

ルーティング

子、孫カテゴリーをjson形式でルーティングを設定します。

config/routes.rb
Rails.application.routes.draw do
  ~~
  resources :posts do
    collection do
      get 'top'
      get 'get_category_children', defaults: { format: 'json' }
      get 'get_category_grandchildren', defaults: { format: 'json' }
      get 'name_search'
    end
  ~~
end

コントローラー

postsコントローラーに親カテゴリーを定義します。
複数箇所で使用するためbefore_actionを使って定義します。

app/controllers/posts_controller.rb
def set_parents
  @parents = Category.where(ancestry: nil)
end

postsコントローラーに子、孫カテゴリーのメソッドを定義します。

app/controllers/posts_controller.rb
def get_category_children
  @category_children = Category.find("#{params[:parent_id]}").children
end

def get_category_grandchildren
  @category_grandchildren = Category.find("#{params[:child_id]}").children
end

json.jbuilderファイルを作成し、jsonデータへ変換します。

app/views/posts/get_category_children.json.jbuilder 
json.array! @category_children do |child|
  json.id child.id
  json.name child.name
end
app/views/posts/get_category_grandchildren.json.jbuilder 
json.array! @category_grandchildren do |grandchild|
  json.id grandchild.id
  json.name grandchild.name
end

ビュー

javascriptでカテゴリー選択時の動作を設定します。

:app/javascript/category_post.js
$(function(){
  function appendOption(category){
    var html = `<option value="${category.id}">${category.name}</option>`;
    return html;
  }
  function appendChildrenBox(insertHTML){
    var childSelectHtml = "";
    childSelectHtml = `<div class="category__child" id="children_wrapper">
                        <select id="child__category" name="post[category_id]" class="serect_field">
                          <option value="">---</option>
                          ${insertHTML}
                        </select>
                      </div>`;
    $('.append__category').append(childSelectHtml);
  }
  function appendGrandchildrenBox(insertHTML){
    var grandchildSelectHtml = "";
    grandchildSelectHtml = `<div class="category__child" id="grandchildren_wrapper">
                              <select id="grandchild__category" name="post[category_id]" class="serect_field">
                                <option value="">---</option>
                                ${insertHTML}
                                </select>
                            </div>`;
    $('.append__category').append(grandchildSelectHtml);
  }

  $('#item_category_id').on('change',function(){
    var parentId = document.getElementById('item_category_id').value;
    if (parentId != ""){
      $.ajax({
        url: '/posts/get_category_children/',
        type: 'GET',
        data: { parent_id: parentId },
        dataType: 'json'
      })
      .done(function(children){
        $('#children_wrapper').remove();
        $('#grandchildren_wrapper').remove();
        var insertHTML = '';
        children.forEach(function(child){
          insertHTML += appendOption(child);
        });
        appendChildrenBox(insertHTML);
        if (insertHTML == "") {
          $('#children_wrapper').remove();
        }
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else{
      $('#children_wrapper').remove();
      $('#grandchildren_wrapper').remove();
    }
  });
  $('.append__category').on('change','#child__category',function(){
    var childId = document.getElementById('child__category').value;
    if(childId != ""){
      $.ajax({
        url: '/posts/get_category_grandchildren',
        type: 'GET',
        data: { child_id: childId },
        dataType: 'json'
      })
      .done(function(grandchildren){
        $('#grandchildren_wrapper').remove();
        var insertHTML = '';
        grandchildren.forEach(function(grandchild){
          insertHTML += appendOption(grandchild);
        });
        appendGrandchildrenBox(insertHTML);
        if (insertHTML == "") {
          $('#grandchildren_wrapper').remove();
        }
      })
      .fail(function(){
        alert('カテゴリー取得に失敗しました');
      })
    }else{
      $('#grandchildren_wrapper').remove();
    }
  })
});

新規投稿ページにカテゴリーセレクトボックスを表示させます。

app/views/posts/new.html.erb
<div class="append__category">
  <div class="category">
    <div class="form__label">
      <div class="weight-bold-text lavel__name ">
        カテゴリー
      </div>
      <div class="lavel__Required">
        <%= f.collection_select :category_id, @parents, :id, :name,{ include_blank: "選択してください"},class:"serect_field", id:"item_category_id" %>
      </div>
    </div>
  </div>
</div>

2. カテゴリー検索表示

カテゴリー検索表示

コントローラー

app/controllers/posts_controller.rb
def top
  respond_to do |format|
    format.html
    format.json do
      if params[:parent_id]
        @childrens = Category.find(params[:parent_id]).children
      elsif params[:children_id]
        @grandChilds = Category.find(params[:children_id]).children
      elsif params[:gcchildren_id]
        @parents = Category.where(id: params[:gcchildren_id])
      end
    end
  end
end

ビュー

javascriptでどの親カテゴリーにの上にマウスがいるのか、それに属する子カテゴリーや孫カテゴリーを取得しています。

:app/javascript/category.js
$(document).ready(function () {
  // 親カテゴリーを表示
  $('#categoBtn').hover(function (e) {
    e.preventDefault();
    e.stopPropagation();
    $('#tree_menu').show();
    $('.categoryTree').show();
  }, function () {
    // あえて何も記述しない
  });

  // 非同期にてヘッダーのカテゴリーを表示
  function childBuild(children) {
    let child_category = `
                        <li class="category_child">
                          <a href="/posts/${children.id}/search"><input class="child_btn" type="button" value="${children.name}" name= "${children.id}">
                          </a>
                        </li>
                        `
    return child_category;
  }

  function gcBuild(children) {
    let gc_category = `
                        <li class="category_grandchild">
                          <a href="/posts/${children.id}/search"><input class="gc_btn" type="button" value="${children.name}" name= "${children.id}">
                          </a>
                        </li>
                        `
    return gc_category;
  }

  // 親カテゴリーを表示
  $('#categoBtn').hover(function (e) {
    e.preventDefault();
    e.stopPropagation();
    timeOut = setTimeout(function () {
      $('#tree_menu').show();
      $('.categoryTree').show();
    }, 500)
  }, function () {
    clearTimeout(timeOut)
  });

  // 子カテゴリーを表示
  $('.parent_btn').hover(function () {
    $('.parent_btn').css('color', '');
    $('.parent_btn').css('background-color', '');
    let categoryParent = $(this).attr('name');
    timeParent = setTimeout(function () {
      $.ajax({
          url: '/posts/top',
          type: 'GET',
          data: {
            parent_id: categoryParent
          },
          dataType: 'json'
        })
        .done(function (data) {
          $(".categoryTree-grandchild").hide();
          $(".category_child").remove();
          $(".category_grandchild").remove();
          $('.categoryTree-child').show();
          data.forEach(function (child) {
            let child_html = childBuild(child)
            $(".categoryTree-child").append(child_html);
          });
          $('#tree_menu').css('max-height', '490px');
        })
        .fail(function () {
          alert("カテゴリーを選択してください");
        });
    }, 400)
  }, function () {
    clearTimeout(timeParent);
  });

  // 孫カテゴリーを表示
  $(document).on({
    mouseenter: function () {
      $('.child_btn').css('color', '');
      $('.child_btn').css('background-color', '');
      let categoryChild = $(this).attr('name');
      timeChild = setTimeout(function () {
        $.ajax({
            url: '/posts/top',
            type: 'GET',
            data: {
              children_id: categoryChild
            },
            dataType: 'json'
          })
          .done(function (gc_data) {
            $(".category_grandchild").remove();
            $('.categoryTree-grandchild').show();
            gc_data.forEach(function (gc) {
              let gc_html = gcBuild(gc)
              $(".categoryTree-grandchild").append(gc_html);
              let parcol = $('.categoryTree').find(`input[name="${gc.root}"]`);
              $(parcol).css('color', 'white');
              $(parcol).css('background-color', '#b1e9eb');
            });
            $('#tree_menu').css('max-height', '490px');
          })
          .fail(function () {
            alert("カテゴリーを選択してください");
          });
      }, 400)
    },
    mouseleave: function () {
      clearTimeout(timeChild);
    }
  }, '.child_btn');

  // 孫カテゴリーを選択時
  $(document).on({
    mouseenter: function () {
      let categoryGc = $(this).attr('name');
      timeGc = setTimeout(function () {
        $.ajax({
            url: '/posts/top',
            type: 'GET',
            data: {
              gcchildren_id: categoryGc
            },
            dataType: 'json'
          })
          .done(function (gc_result) {
            let childcol = $('.categoryTree-child').find(`input[name="${gc_result[0].parent}"]`);
            $(childcol).css('color', 'white');
            $(childcol).css('background-color', '#b1e9eb');
            $('#tree_menu').css('max-height', '490px');
          })
          .fail(function () {
            alert("カテゴリーを選択してください");
          });
      }, 400)
    },
    mouseleave: function () {
      clearTimeout(timeGc);
    }
  }, '.gc_btn');


  // カテゴリー一覧ページのボタン
  $('#all_btn').hover(function (e) {
    e.preventDefault();
    e.stopPropagation();
    $(".categoryTree-grandchild").hide();
    $(".categoryTree-child").hide();
    $(".category_grandchild").remove();
    $(".category_child").remove();
  }, function () {
    // あえて何も記述しないことで親要素に外れた際のアクションだけを伝搬する
  });

  // カテゴリーを非表示(カテゴリーメニュから0.8秒以上カーソルを外したら消える)
  $(document).on({
    mouseleave: function (e) {
      e.stopPropagation();
      e.preventDefault();
      timeChosed = setTimeout(function () {
        $(".categoryTree-grandchild").hide();
        $(".categoryTree-child").hide();
        $(".categoryTree").hide();
        $(this).hide();
        $('.parent_btn').css('color', '');
        $('.parent_btn').css('background-color', '');
        $(".category_child").remove();
        $(".category_grandchild").remove();
      }, 800);
    },
    mouseenter: function () {
      timeChosed = setTimeout(function () {
        $(".categoryTree-grandchild").hide();
        $(".categoryTree-child").hide();
        $(".categoryTree").hide();
        $(this).hide();
        $('.parent_btn').css('color', '');
        $('.parent_btn').css('background-color', '');
        $(".category_child").remove();
        $(".category_grandchild").remove();
      }, 800);
      clearTimeout(timeChosed);
    }
  }, '#tree_menu');

  // カテゴリーボタンの処理
  $(document).on({
    mouseenter: function (e) {
      e.stopPropagation();
      e.preventDefault();
      timeOpened = setTimeout(function () {
        $('#tree_menu').show();
        $('.categoryTree').show();
      }, 500);
    },
    mouseleave: function (e) {
      e.stopPropagation();
      e.preventDefault();
      clearTimeout(timeOpened);
      $(".categoryTree-grandchild").hide();
      $(".categoryTree-child").hide();
      $(".categoryTree").hide();
      $("#tree_menu").hide();
      $(".category_child").remove();
      $(".category_grandchild").remove();
    }
  }, '.header__headerInner__nav__listsLeft__item');
});

トップ画面にカテゴリー選択ウィンドウをセットします。

app/views/posts/top.html.erb
  <div class="item-categories">
    <h2>
      カテゴリー一覧
    </h2>
    <%= link_to  posts_path, class: "category-button", id: 'categoBtn' do %>
      カテゴリーから探す
    <% end %>
    <div id="tree_menu">
      <ul class="categoryTree">
        <% @parents.each do |parent| %>
          <li class="category_parent">
            <%= link_to search_post_path(parent) do %>
              <input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn">
            <% end %>
          </li>
        <% end %>
      </ul>
      <ul class="categoryTree-child">
      </ul>
      <ul class="categoryTree-grandchild">
      </ul>
    </div>
  </div>

3. カテゴリー検索結果表示

カテゴリいー検索結果表示

ルーティング

カテゴリーをidで区別するため、memberを用いてsearchアクションを定義しています。

config/routes.rb
resources :posts do
    ~~
    member do
      get 'search'
    end
   ~~
end

コントローラー

クリックしたカテゴリーが、親カテゴリー、子カテゴリー、孫カテゴリーのどれなのかで条件分岐しています。

app/controllers/posts_controller.rb
  def search
    @category = Category.find_by(id: params[:id])

    if @category.ancestry == nil
      category = Category.find_by(id: params[:id]).indirect_ids
      if category.empty?
        @posts = Post.where(category_id: @category.id).order(created_at: :desc)
      else
        @posts = []
        find_item(category)
      end

    elsif @category.ancestry.include?("/")
      @posts = Post.where(category_id: params[:id]).order(created_at: :desc)

    else
      category = Category.find_by(id: params[:id]).child_ids
      @posts = []
      find_item(category)
    end
  end

  def find_item(category)
    category.each do |id|
      post_array = Post.where(category_id: id).order(created_at: :desc)
      if post_array.present?
        post_array.each do |post|
          if post.present?
            @posts.push(post)
          end
        end
      end
    end
  end

ビュー

app/views/posts/search.html.erb
  <div class="item-categories">
    <h2>
      カテゴリー一覧
    </h2>
    <%= link_to  posts_path, class: "category-button", id: 'categoBtn' do %>
      カテゴリーから探す
    <% end %>
    <div id="tree_menu">
      <ul class="categoryTree">
        <% @parents.each do |parent| %>
          <li class="category_parent">
            <%= link_to search_post_path(parent) do %>
              <input type="button" value="<%= parent.name %>" name="<%= parent.id %>" class="parent_btn">
            <% end %>
          </li>
        <% end %>
      </ul>
      <ul class="categoryTree-child">
      </ul>
      <ul class="categoryTree-grandchild">
      </ul>
    </div>
  </div>

参考リンク

https://qiita.com/k_suke_ja/items/aee192b5174402b6e8ca
https://qiita.com/Sobue-Yuki/items/9c1b05a66ce6020ff8c1
https://qiita.com/dr_tensyo/items/88e8ddf0f5ce37040dc8
https://qiita.com/ATORA1992/items/bd824f5097caeee09678
https://qiita.com/misioro_missie/items/175af1f1678e76e59dea
https://qiita.com/Rubyist_SOTA/items/49383aa7f60c42141871

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

HTML.CSS.JavaScript.Ruby.Rails概要

HTML

 HTML(Hyper Text Markup Language)とはWEBページを作成する際に使用されるマークアップ言語である。
 WEBページのほとんどにHTMLが使用されている。
 HTMLはタグを使いコンピューターに命令を出す事により見出しを付けたり段落を付けたりと、WEBページのレイアウト、構成を形作ることができる。

CSS

CSS(Cascading Style Sheets)は先ほどのHTMLと組み合わせて使用する言語である。
CSSは文字の色やサイズ、レイアウトを変えたり、WEBページを装飾する言語である。
HTMLでもWEBページの装飾をすることは出来るが、CSSの役割なので分けて使う必要がある。

JavaScript

WEBサイトに動きをつけるためのプログラミング言語である。
具体的には文章や画像を拡大表示したり、より動的なWEBサイトを作ることができる。
サーバーを介さずにブラウザ上で動かすことができる。またこのようなプログラムをクライアントサイド・スクリプトという

Ruby

Rubyとは日本人であるまつもとゆきひろ氏によって作成されたオブジェクト指向スクリプト言語である。
WEBサイトやECサイトなどの製作、SNS開発など様々なことができる。有名なサイトではぐるなび食べログなど
Rubyは他の言語に比べシンプルなコードのため開発スピードが早く読みやすい

Rails

Ruby on RailsとはRubyを使用したフレームワークである。
フレームワークとは雛形のことで、一からプログラミングをしなくても枠組みが用意されているため開発時間を大幅に削減することができる。他にもSinatraやHANAMIなどがある

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

モデルとテーブル作成

モデルとテーブルの作成を備忘録として残す。

ターミナルでモデルを作成

rails g model モデル名

下記のログが出ればOK

      invoke  active_record
      create    db/migrate/000000000_create_tweets.rb
      create    app/models/モデル名.rb
      invoke    test_unit
      create      test/models/モデル名_test.rb
      create      test/fixtures/モデル名.yml

モデル名.rbが作成される。

テーブルの作成

モデルを生成すると一緒にマイグレーションファイルが作られる。
dbフォルダのmigrateファイルにある。これを編集する。

class Createモデル名(頭文字は大文字) < ActiveRecord::Migration[6.0]
  def change
    create_table :モデル名 do |t|
      t.string :name
      t.string :text
      t.text :image
      t.timestamps
    end
  end
end

t.あとが「型」、:に続くのが「カラム名」です。

型の種類

string : 文字列
text : 長い文字列
integer : 整数
float : 浮動小数
decimal : 精度の高い小数
datetime : 日時
timestamp : タイムスタンプ
time : 時間
date : 日付
binary : バイナリデータ
boolean : Boolean

マイグレーションの実行

rails db:migrate

以下のようなログが出ればOK

== 20xxxxxxxxxx CreateTweets: migrating =====================================
-- create_table(:tweets)
   -> 0.0148s
== 20xxxxxxxxxx CreateTweets: migrated (0.0149s) ============================

最後に、rails s でローカルサーバを再起動
rails c でデータが保存されていることを確認

[1] pry(main)> Tweet.create(name: "take", text: "apple")

以下のようにログが出てデータの保存ができているのを確認

  (4.4ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
   (0.3ms)  BEGIN
  Tweet Create (0.7ms)  INSERT INTO `tweets` (`name`, `text`, `created_at`, `updated_at`) VALUES ('take', 'apple', '2020-11-17 03:06:52.758040', '2020-11-17 03:06:52.758040')
   (3.2ms)  COMMIT
=> #<Tweet:0x00007ff6e6ca6770
 id: 1,
 name: "take",
 text: "apple",
 image: nil,
 created_at: Tue, 17 Nov 2020 03:06:52 UTC +00:00,
 updated_at: Tue, 17 Nov 2020 03:06:52 UTC +00:00>

確認できたら完了!!

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

Rails5.1+puma ローカルのproduction環境でSSL接続する

Rails5.0から5.1にアップグレードし、ローカルでproductionの動作確認しようとしたら、何かとつまずいたので諸問題の解決方法をまとめておきます。

まず、 rails s -e production を叩いたところ、この接続にはセキュリティの問題があるのでWEBページを表示できません的なエラーが出ました。httpsにしないとダメなようです。

SSLで接続する

基本的なやり方は、こちらの記事を参照させていただきました。
Rails5 + pumaのローカル環境でSSL/HTTPSを有効にする

上記記事は、opensslで証明書発行してましたが、エラーが出たので証明書の発行をmkcertでやりました。
mkcertでの証明書の発行のやり方は、こちらの記事が参考になりました。
ローカル環境でSSLをオレオレ証明書で行っていて警告が出てる人に朗報

まず、上記記事に従ってmkcertで証明書を発行、appフォルダ以下の適当な場所にserver.key、server.crtファイルを置きます。

Rails5 + pumaのローカル環境でSSL/HTTPSを有効にする を参考にpuma.rbを設定。
bundle exec pumactl start -e production でサーバーを起動。

css/jsが表示されなくなった問題の解決

SSLで接続できたものの、 HTTP parse error, malformed request () というエラーが出て、application.js、application.cssが読み込まれませんでした。
jsとcssのサーバーがhttp://0.0.0.0:3000 になっていたので、asset_hostの設定を以下のように修正。ローカルと本番環境で変えられるよう、環境変数で定義しました。

config/environments/production.rb
config.action_controller.asset_host = "https://#{ENV['HOST_URI']}"

参考
- StackOverflow::Ruby on Railsの本番環境でpublic/assetsが参照できない

public/images以下のファイルの表示問題解決

上記の対応でcssとjsは出るようになったけれど、public/images以下に置いていた画像ファイルがまだ表示されませんでした。
Rails5.1からは、asset_pipelineでプリコンパイルしない静的なファイルは、ヘルパーで呼び出すときには以下の用に書きます。

= image_tag 'hogehoge.png', skip_pipeline: true

しかし、これが設定してあってもダメだったので、別の問題のようです。

対応1
config.public_file_server.enabledをtrueに

config/environments/production.rb
- config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
+ config.public_file_server.enabled = true

対応2
assets:precompileをやり直す。

# 思い切って一度全部消したければ、先にこちら
RAILS_ENV=production bundle exec rake assets:clobber

# assets:cleanは古いバージョンのファイルを消す。clobberをやってたらcleanは不要
RAILS_ENV=production bundle exec rake assets:precompile assets:clean

上記対応で、assetすべて表示されるようになりました。

参考

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

Railsを使ったToDoリストの作成(2.hamlの導入と使い方)

概要

本記事は、初学者がRailsを使ってToDoリストを作成する過程を記したものです。
私と同じく初学者の方で、Railsのアウトプット段階でつまづいている方に向けて基礎の基礎を押さえた解説をしております。
抜け漏れや説明不足など多々あるとは思いますが、読んでくれた方にとって少しでも役に立つ記事であれば幸いです。

環境

  • Homebrew: 2.5.10 -> MacOSのパッケージ管理ツール
  • ruby: 2.6.5p114 -> Ruby
  • Rails: 6.0.3.4 -> Rails
  • node: 14.3.0 -> Node.js
  • yarn: 1.22.10 -> JSのパッケージ管理ツール
  • Bundler: 2.1.4 -> gemのバージョン管理ツール
iTerm
$ brew -v => Homebrew 2.5.10
$ ruby -v => ruby 2.6.5p114
$ rails -v => Rails 6.0.3.4
$ npm version => node: '14.3.0'
$ yarn -v => 1.22.10
$ Bundler -v => Bundler version 2.1.4

第2章 hamlの導入

第2章では、Railsでアプリ開発をする時にhamlテンプレートを使えるように設定していきます。
Railsはデフォルトはerbテンプレートが使われていますが、erbテンプレートは使いづらい上に、現場レベルでもあまり使われていないみたいなので、今回の開発においてはhamlテンプレートを使っていきたいと思います。

1 hamlの導入

まずは、hamlテンプレートをRailsで使えるようにしていきます。
hamlitというgemとerb2hamlというgemをインストールします。

Gemfile
gem 'hamlit'

group :development do
  gem 'erb2haml'
end

?‍♂️hamlitというgemをインストールしてください
?‍♂️erb2hamlという変換ツールのgemをインストールしてください

ターミナルで$bundle installを行えばgemのインストールは完了です。

既存のerbファイルをhamlに置き換えるためにはターミナルにて以下のコマンドを実行する必要があります。

iterm
$ bundle exec rake haml:replace_erbs

⚠︎hamlitをインストールしてエラーが起こってしまった場合は$rails sでサーバを立ち上げ直しましょう


以上でRailsの開発においてhamlテンプレートを使うことができるようになりました。
次は、実際にhamlの書き方を見ていきます。

2 hamlの基本ルール①

① タグの書き方

erb
<header>
  <p></p>
</header>
haml
%header
  %p
  • hamlでは閉じタグが必要ない
  • 入れ子構造にする際はスペースキーを2回押すこと

② class/id/attributeの書き方

erb
<header class="header">
  <p id="id"></p>
    <a href="https://haml.com"></a>
</header>
haml
%header.header
  %p#id
    %a{href: "https://haml.com"}
  • classは.class名で指定する
  • idは#id名で指定する
  • attributeはハッシュで指定する

③divタグの書き方

erb
<div class="container"></div>
haml
.container
  • divタグを作成したい時はタグの表記は必要ない。hamlが勝手に察してdivタグを付けてくれる

3 hamlの基本ルール②

①Rubyのコードの埋め込み(画面に表示する場合)

erb
<%= board.title %>
haml
= board.title
  • <%=%>を表現する際は=を使う

②Rubyのコードの埋め込み(画面に表示しない場合)

erb
<% Board.all.each do |board| %>
  <%= board.title %>
<% end %>
haml
- Board.all.each do |board|
  = board.title
  • <%%>を表現する際は-を使う
  • <% end %>はhamlが勝手に付けてくれる

※もしhamlの書き方がわからなくなったら下記のサイトで確認しましょう。

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

Railsを使ったToDoリストの作成(2.hamlの導入と書き方)

概要

本記事は、初学者がRailsを使ってToDoリストを作成する過程を記したものです。
私と同じく初学者の方で、Railsのアウトプット段階でつまづいている方に向けて基礎の基礎を押さえた解説をしております。
抜け漏れや説明不足など多々あるとは思いますが、読んでくださった方にとって少しでも役に立つ記事であれば幸いです。

環境

  • Homebrew: 2.5.10 -> MacOSのパッケージ管理ツール
  • ruby: 2.6.5p114 -> Ruby
  • Rails: 6.0.3.4 -> Rails
  • node: 14.3.0 -> Node.js
  • yarn: 1.22.10 -> JSのパッケージ管理ツール
  • Bundler: 2.1.4 -> gemのバージョン管理ツール
iTerm
$ brew -v => Homebrew 2.5.10
$ ruby -v => ruby 2.6.5p114
$ rails -v => Rails 6.0.3.4
$ npm version => node: '14.3.0'
$ yarn -v => 1.22.10
$ Bundler -v => Bundler version 2.1.4

第2章 hamlの導入と書き方

第2章では、Railsでアプリ開発をする時にhamlテンプレートを使えるように設定していきます。
Railsはデフォルトはerbテンプレートが使われていますが、erbテンプレートは使いづらい上に、現場レベルでもあまり使われていない(らしい)ので、今回の開発においてはhamlテンプレートを使っていきたいと思います。

1 hamlの導入

まずは、hamlテンプレートをRailsで使えるようにしていきます。
hamlitというgemとerb2hamlという変換ツールのgemをインストールします。

Gemfile
gem 'hamlit'

group :development do
  gem 'erb2haml'
end

?‍♂️hamlitというgemをインストールしてください
?‍♂️erb2hamlというgemをインストールしてください

ターミナルで$bundle installを行えばgemのインストールは完了です。

既存のerbファイルをhamlに置き換えるためにはターミナルにて以下のコマンドを実行する必要があります。

iterm
$ bundle exec rake haml:replace_erbs

⚠︎hamlitをインストールしてエラーが起こってしまった場合は$rails sでサーバを立ち上げ直しましょう


以上でRailsの開発においてhamlテンプレートを使うことができるようになりました。
次は、実際にhamlの書き方を見ていきます。

2 hamlの基本ルール①

① タグの書き方

erb
<header>
  <p></p>
</header>
haml
%header
  %p
  • hamlでは閉じタグが必要ない
  • 入れ子構造にする際はスペースキーを2回押すこと

② class/id/attributeの書き方

erb
<header class="header">
  <p id="id"></p>
    <a href="https://haml.com"></a>
</header>
haml
%header.header
  %p#id
    %a{href: "https://haml.com"}
  • classは.class名で指定する
  • idは#id名で指定する
  • attributeはハッシュで指定する

③divタグの書き方

erb
<div class="container"></div>
haml
.container
  • divタグを作成したい時はタグの表記は必要ない。hamlが勝手に察してdivタグを付けてくれる

3 hamlの基本ルール②

①Rubyのコードの埋め込み(画面に表示する場合)

erb
<%= board.title %>
haml
= board.title
  • <%=%>を表現する際は=を使う

②Rubyのコードの埋め込み(画面に表示しない場合)

erb
<% Board.all.each do |board| %>
  <%= board.title %>
<% end %>
haml
- Board.all.each do |board|
  = board.title
  • <%%>を表現する際は-を使う
  • <% end %>はhamlが勝手に付けてくれる

※もしhamlの書き方がわからなくなったら下記のサイトで確認しましょう。

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

Rails SecureRandom.alphanumericを使ってテストアカウントのパスワードを生成する

railsでアプリケーションを作成しています。
テストアカウントでのログイン機能を実装したときにエラーが発生したため、自分が行った解決方法を記録として残しておきます。

開発環境

Ruby 2.6.5
Rails 6.0.3.3

実装内容

以下のコードでテストログイン機能の実装を行いました。

コントローラー

class Users::SessionsController < Devise::SessionsController
  # ゲストユーザーとしてログイン
  def new_guest
    user = User.find_or_create_by!(nickname: 'ゲストユーザー', email: 'user@example.com') do |user|
    user.password = SecureRandom.alphanumeric
    end
    sign_in user
    redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。'
  end
end

モデル

 #半角英数字のみを許可するバリデーションを設定
  PASSWORD_REGEX = /\A(?=.*?[a-z])(?=.*?[\d])[a-z\d]+\z/i.freeze
  validates_format_of :password, with: PASSWORD_REGEX, on: :create, message: 'には半角英字と半角数字の両方を含めて設定してください'

開発環境でもエラーが発生することなく、テストも通ったため、本番環境にデプロイをしたところ「Sorry, something went wrong.」が表示されてしまいました。

原因

SecureRandom.alphanumericで英字のみのパスワードが発行されていたため。
そもそもSecureRandomはレファレンスでは以下のように説明がされています。

安全な乱数発生器のためのインターフェースを提供するモジュールです。 HTTP のセッションキーなどに適しています。

そしてalphanumericはSecureRandomモジュールのメソッドの一種であり、ランダムな英数字を生成してくれます。しかし必ずしも英数字混合で生成されるわけではありません。

コンソール
pry(main)> SecureRandom.alphanumeric
=> "NNCMHbfUbHRQmbwW"

確率は高くはないですが、上記のように英字のみのパスワードが生成されてしまうことがあります。

解決策

class Users::SessionsController < Devise::SessionsController
  # ゲストユーザーとしてログイン
  def new_guest
    user = User.find_or_create_by!(nickname: 'ゲストユーザー', email: 'user@example.com') do |user|
    user.password = SecureRandom.alphanumeric(10) + [*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).join
    end
    sign_in user
    redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。'
  end
end

力技になってしまいましたが、[*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).joinとすることでパスワードの最後に必ず英字+数字の2文字が入るようにして英字のみor数字のみのパスワードが生成されないようにしました。

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

【Rails】カラムの追加・データ型/カラム名の変更

はじめに

カラムの追加・カラム名、テータ型の変更をする手順として代表的なのが、
1⃣rails db:migrate:statusで現状の確認
2⃣マイグレーションファイルの作成or追加コマンドをターミナルに入力(行いたい操作でコマンドは異なります)
3⃣マイグレーションファイルに変更内容を記述(追加の場合は内容の確認だけになります)
4⃣rails db:migrate
だと思います(違ってたらすみません)。もちろんこれも正解だと思います。(rails db:rollback使うこともあります)毎度コマンドを調べたり誤字があるとテーブルが謎に消えてしまうことがあるかと思います、、、

レコードの内容が消えていいような場合は楽な方法があるそうで、、(ご存じの方はすみません)

※レコードの内容が少なかったり消えても良い状態、seeds.rbで必要なデータを作成しているとき向けの内容です。

上記の楽な方法(複数同時変更も可能)

1⃣rails db:migrate:statusで現状の確認(問題なければ2⃣へ)
2⃣既に作成してあるマイグレーションファイルの変更したい部分を直接書き換えます(追加の場合は書き加えてください)
3⃣rails db:migrate:resetを実行して完了
schema.rbを確認してみてください!

rails db:migrate:resetを行うことで、既存のマイグレーションファイルを全て利用して再度テーブルを作成しなおします。なので、書き換えた部分や追記した部分も全て反映されます(誤字でerrorが出ても大体直ります)
ただし、場合によって使い分ける必要があるのでレコードを削除したくない場合は丁寧にコマンドを打ち込みましょう。

1⃣状態確認

rails db:migrate:status実行
コマンド実行時に出力された分だけマイグレーションファイルが存在して、この後の工程でそれらを一斉に再度migreateしなおすようなイメージになります。

ターミナル
vocstartsoft:~/environment/bookers2-task (master) $ rails db:migrate:status #ここで実行

database: /home/ec2-user/environment/bookers2-task/db/development.sqlite3

 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20200830060820  Devise create users      #upなのかdownなのか状態が出てきます。
   up     20200830062142  Create books             #upは基本的にmigrate済みということになります
   up     20201101080413  Create book comments
  #以下省略

2⃣マイグレーションファイルに追記・変更

追記・変更したい内容の含まれるマイグレーションファイルをdb/migrateフォルダ内から持ってきます。
今回は投稿に使うtitleカラムbodyカラム共にデータ型がおかしいのでそこを修正します(booleanとかどんなミスだよ。)

db/migrate/20201115102020_create_books.rb
class CreateBooks < ActiveRecord::Migration[5.2]
  def change
    create_table :books do |t|
      t.integer :title          #←integerをstringにしたい
      t.boolean :body           #←booleanをtextにしたい
      t.integer :user_id
      t.timestamps
    end
  end
end

書き換えます(追加したい場合は追記してください)

db/migrate/20201115102020_create_books.rb
      #↑省略
      t.string :title          #integer→string
      t.text :body             #boolean→text
      #↓省略

3⃣rails db:migrate:resetを実行して一気にDBを削除→作成します

ターミナル
$ rails db:migrate:reset

ターミナルに普段のrails db:migrate時と同じような内容が出力されていればおそらく問題ありません。
schema.rbを確認してちゃんと変更・追加ができているか確認してみましょう。
くどいようですが、作成していたuser情報(名前とか)や投稿内容などは消えているはずなので、注意してください。

おまけ

rails db:resetとrails db:migrat:resetの違いについて

どちらのコマンドもDBを削除して作成しなおすコマンドですが、大きな違いがあるようです。
rails db:resetはDBをdropして、現在の scheme.rb をロードして DB を作り直します。db/migrate/~.rb は使われないようです。
rails db:migrate:resetはDBをdropした後、通常通りのマイグレート(db:migrate)が行われます。つまりdb/migrate/~.rb が古い順から全て実行されます。
最後に上記の方法以外のカラム追加記事はこちら
参考にもなったし、逆にrails db:migrate:resetできなかったときの内容の記事はこちら
長くなりましたが、個人的にDB周りは意外とややこしいエラーが多い気がします、この記事で解決すると嬉しいです!(改善されない方はすみません、、)

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