20200328のRailsに関する記事は19件です。

【Ruby on Rails】Ajax通信で特定の要素を変更する

はじめに

RailsのAjax通信使って特定の要素を編集・更新する方法について記載します。
例で簡単なカテゴリー一覧画面の1つのカテゴリーの名前を更新する処理を使います。

バージョン

  • ruby 2.6.3
  • rails 5.2.1

画面概要

以下のような画面です。
スクリーンショット 2020-03-28 22.13.26.png

この右のほうにある鉛筆マークの編集アイコンボタンを押すと、該当の行のカテゴリー名がフォームに変わります。
スクリーンショット 2020-03-28 19.32.07.png

カテゴリー名を変更し、「更新」を押すと、
スクリーンショット 2020-03-28 19.33.11.png

変更後のカテゴリー名にデータが更新され、表示を非同期で変更します。
例では、「交通」→「交通費」に変更します。
スクリーンショット 2020-03-28 22.13.58.png

処理概要

  1. 要素にAjax通信するオプションを追加
    link_toにremote: trueオプションをつけます。

  2. Ajax通信するためのルーティングを追加

  3. コントローラやモデルでオブジェクトの取得または更新

  4. 追加したアクションのためのJavaScript用のviewファイルを用意し、画面の該当の要素を変更するためのJavaScriptを記載

実装前

実装前の主要なコード部分は以下になります。

app/controllers/categories_controller.rb
def index
  @categories = Category.where(user: current_user).order(:created_at)
end
app/views/categories/index.html.erb
<ul class="todo-list" id="own-categories">
  <% @categories.each do |category| %>
    <li id="category-list-id-<%= category.id %>">
      <%= render 'a_category_list', category: category %>    
    </li>
  <% end  %>
</ul>
app/views/categories/_a_category_list.html.erb
<span class="handle ui-sortable-handle">
  <i class="fa fa-ellipsis-v"></i>
  <i class="fa fa-ellipsis-v"></i>
</span>
<span class="text"><%= category.name %></span>
<%= category.common_mark %>
<div class="tools">
  <%= link_to edit_category_path(category), class: "text-redpepper space-left" do %>
    <i class="fa fa-lg fa-edit"></i>
  <% end %>
  <%= category.common_btn %>
</div>

カテゴリー名を編集するフォームに変更するAjax処理の実装

鉛筆マークの編集アイコンボタンを押下して、カテゴリー名の要素をフォーム要素に変更する処理を記載していきます。
※注意:説明しないclass属性やid属性などがありますが、Ajax通信処理とは関係ありません。

この処理にはgem rails-ujsが必要なので追加してbundle installします。

Gemfile
gem 'rails-ujs'

1. Ajax通信するオプションを追加

編集アイコンボタンにremote: trueを仕込む。

app/views/categories/_a_category_list.html.erb
<%= link_to edit_category_path(category), remote: true, class: "text-redpepper space-left" do %>
  <i class="fa fa-lg fa-edit"></i>
<% end %>

2. Ajax通信するためのルーティングを追加

Ajax通信するときでも、ルーティングのresourcesメソッドが柔軟に対応してくれます。
ここでは、editアクションを追加します。

config/routes.rb
resources :categories, only: [:index, :edit]

3. コントローラでオブジェクトの取得

app/controllers/categories_controller.rb
def edit
  @category = Category.find(params[:id])
end

4. 追加したアクションのためのJavaScript用のviewファイルを用意し、画面の該当の要素を変更するためのJavaScriptを記載

editアクションなので、app/views/categories/edit.js.erbを作成します。

app/views/categories/edit.js.erb
id = "<%= @category.id %>";
target = document.querySelector(`#category-list-id-${id}`);
html = "<%= j(render partial: 'form', locals: { category: @category }) %>";
target.innerHTML = html;
app/views/categories/_form.html.erb
<%= form_with(model: category) do |form| %>
  <div class="row">
    <div class="col-xs-10">
      <div class="input-group input-group-sm">
        <%= form.text_field :name, value: category.name, required: true, class:"form-control", max: 15 %>
        <span class="input-group-btn">
          <%= form.submit submit_btn_letters, class: "btn btn-brown" %>
        </span>
      </div>
    </div>
    <div class="col-xs-1">
      <%= category.cancel_btn %>
    </div>
  </div>
<% end %>

これで、カテゴリー名を編集するためのフォームが表示できます。
スクリーンショット 2020-03-28 19.32.07.png

カテゴリー名を更新するAjax処理の実装

編集フォームに変更できたので、次に、変更したい文字「交通費」に変更して、
更新ボタンを押下し、更新後のカテゴリー名を表示する処理を記載していきます。

1. Ajax通信するオプションを追加

form_withにremote: trueオプションを追加します。
(明示的に記載しなくてもデフォルトでremote: trueにはなっている。)

app/views/categories/_form.html.erb
<%= form_with(model: category, remote: true) do |form| %>
  <!-- 省略 -->
<% end %>

2. Ajax通信するためのルーティングを追加

updateアクションを追加

config/routes.rb
resources :categories, only[:index, :edit, :update]

3. コントローラでオブジェクトの更新

カテゴリー名を「交通費」で更新します。

app/controllers/categories_controller.rb
def update
  @category = Category.find(params[:id])
  @category.update(category_params)
end

private
def category_params
  params.require(:category).permit(:name, :is_common)
end

4. 追加したアクションのためのJavaScript用のviewファイルを用意し、画面の該当の要素を変更するためのJavaScriptを記載

編集フォームを更新したカテゴリー名に変更する処理を行います。
updateアクションなのでapp/views/categories/update.js.erbを作成します。

app/views/categories/update.js.erb
id = "<%= @category.id %>";
target = document.querySelector(`#category-list-id-${id}`);
html = "<%= j(render partial: 'categories/a_category_list', locals: { category: @category }) %>";
target.innerHTML = html;

以上で、カテゴリー名を非同期で更新することができました。
スクリーンショット 2020-03-28 22.13.58.png

おわりに

JavaScriptでAjax処理を書いてもいいのですが、シンプルなAjax通信はRailsであれば簡単にできます。
ただ、〇〇.js.erbの書き方は癖が強いので要注意です。

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

Docker上のRailsアプリにWebpackerを導入してみる

はじめに

この記事では、既存のRailsアプリにWebpackerを導入する手順を書いていきます。
WebpackerやWebpackを使用したことがない方向けです。

開発環境

Rails 5.2.3
ruby 2.5
mariadb 10.1
docker

Webpackerとは

Railsでwebpackを使用するためのgemパッケージです。
複数のjs,css,画像などを一つにまとめることにより、
ファイルの取得にかかる時間を短縮できるというメリットがあります。

実装

gemのインストール

まずアプリにgemパッケージをインストールします。

Gemfile
gem 'webpacker', '~> 4.x' 

Gemfileを更新したら、bundle installします。

yarnのインストール

Webpackerはyarnというnode.jsのパッケージマネージャを使用するそうなので、
Dockerにyarnをインストールするためのコマンドを追記します。

Dockerfile
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install yarn

Webpackerの起動

docker-compose.yml
  webpacker:
    build: .
    environment:
      NODE_ENV: development
      RAILS_ENV: development
      WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
    command: ./bin/webpack-dev-server
    ports:
      - '3035:3035'

アプリ用のコンテナにWebpackerホスト名の環境変数を追加します。

docker-compose.yml
  web:
    build:
      context: .
      dockerfile: Dockerfile
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    tty: true
    stdin_open: true
    depends_on:
      - db
    ports:
      - '3000:3000'
    volumes:
      - .:/usr/src/app
  # 以下を追記
    environment:
      WEBPACKER_DEV_SERVER_HOST: webpacker

これでdocker-compose up --buildをすると、Webpackerが起動してくれます。
このあたりは、公式ドキュメントにわかりやすく記載されています。

基本的な使い方

Webpackerをインストールすると、config/webpacker.ymlが作成されます

config/webpacker.yml
default: &default
  source_path: app/javascript
  source_entry_path: packs # この配下のファイルをエントリーポイントとして読み込む
  ...
構成
app/javascript:
  ├── packs:
  │   # ここに置いたファイルが読み込まれる
  │   └── application.js
  └── javascripts:
  │   └── application.js
  └── stylesheets:
      └── application.scss

javascripts/application.js, stylesheets/application.css
アプリケーション全体に適用させたい場合は以下のように書きます。

packs/application.js
import '../javascripts/application.js'
import '../stylesheets/application.scss'
app/views/layouts/application.html.erb
<%= javascript_pack_tag 'applocation' %>
<%= stylesheet_pack_tag 'application' %>

これでビューに反映することができます。

ちなみに、今回はWebpackerの実行コマンドに./bin/webpack-dev-serverを指定しているため、
jsファイルの中身を更新するとブラウザをリロードすることなく自動的に変更を反映してくれます。
ただ、ファイル自体を追加するなど、構成を変更した場合はrailsを再起動する必要があります。

ページ毎に適用するファイルを分ける場合

構成
app/javascript:
  ├── packs:
  │   └── application.js
  └── javascripts:
  │   └── application.js
  │   └── tasks:
  │       └── application.js
  │       # 実際の処理が書かれているjavascriptファイル
  │       └── index.js
  │       └── show.js
  └── stylesheets:
      └── application.scss

エントリーポイントであるpakcs配下にjsファイルを読み込むためのエントリーファイルを作成します。

構成
app/javascript:
  ├── packs:
  │   └── application.js
  │   # 追加
  │   └── tasks:
  │       └── index.js
  │       └── show.js
  └── javascripts:
  │   └── application.js
  │   └── tasks:
  │       └── index.js
  │       └── show.js 
  └── stylesheets:
      └── application.scss

追加したファイルにインポート文を記述します。

packs/tasks/index.js
import '../../javascripts/tasks/index.js'
packs/tasks/show.js
import '../../javascripts/tasks/show.js'

それぞれのページでjavascriptを呼び出します。

app/views/tasks/index.html.erb
<%= javascript_pack_tag 'tasks/index.js' %>
app/views/tasks/show.html.erb
<%= javascript_pack_tag 'tasks/show.js' %>

そのほか

Bootstrap4を適用させる

Webpackerを導入したところ、Bootstrapが効かなくなりました。
こちらのサイトを参考に設定。
https://qiita.com/rhistoba/items/f724dae231d7e28bf477

confirmを動作させる

Webpackerを導入したところ、confirmダイアログが効かなくなりました。
こちらのサイトを参考に設定。
https://qiita.com/mokuo/items/a50a27a83c3328c116a7

Github ActionsでWebpackerを動作させる

Github Actionsで用意されているUbuntuにはyarnがデフォルトで入っているようですが、
そのままだとエラーが出てしまいました。
yarn upgradeを追記することで動作するようになりました。

.github/workflows/ruby.yml
    - name: Compile with Webpacker
      run: |
        yarn upgrade
        bundle exec rake webpacker:compile
      env:
        NODE_ENV: test 

最後に

ご指摘、コメントお待ちしております。

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

指定したエリアだけを印刷する処理

こんな場面で使えます。

 例えば、ユーザーに何らかのアンケートに回答をしてもらうサイトで
ユーザーが自分の回答結果の部分だけを印刷したい場合などに使えます。
普通の印刷では余計な箇所も入ってくるので、必要なエリアだけを印刷できるようにします。
※実際の挙動が確認できるwebアプリのURLを一番下に載せてますので、そちらもご確認ください。
11printImg1.jpg

実装の流れ

 1:印刷ボタンを作成
 2:プリントしたいエリアの取得の指定
 3:2のエリア全体のコピーを作成
 4:3以外を非表示にする処理を作成
 5:印刷ダイアログの呼び出し➡︎印刷する
 6:3で作成した印刷用エリアを削除 ・ 4で非表示にした要素を表示する

実装スタート

1:印刷ボタンを作成

print.html
<button class="print-btn">
 <span>結果だけを印刷する</span>
</button>

2:プリントしたいエリアの取得

 プリントしたい範囲を以下の「divタグ」で囲む。

print.html
<div class="print-area">
 <!-- この中に要素を入れる。印刷しないものは、後で非表示にする処理で消します。 -->
</div>

3:2のエリア全体のコピーを作成

 1:プリントしたい範囲を取得
 2:プリント用の要素「#print」を作成。
 3:2の子要素に1を入れる。

print.js
$(function(){
//個別印刷ボタンをクリックした時
$('.print-btn').on('click', function(){

//プリントしたいエリアの取得
var printArea = document.getElementsByClassName("print-area");

//プリント用の要素「#print」を作成し、上で取得したprintAreaをその子要素に入れる。
$('body').append('<div id="print" class="printBc"></div>');
$(printArea).clone().appendTo('#print');

//この下に、以降の処理が入ります。

 });
});

4:3以外を非表示にする処理を作成

print.js
//プリントしたいエリア意外に、非表示のcssが付与されたclassを追加
$('body > :not(#print)').addClass('print-off');
print.css
.print-off {
 display: none;
}

5:印刷ダイアログの呼び出し➡︎印刷する

print.js
window.print();

6:3で作成した印刷用エリアを削除 ・ 4で非表示にした要素を表示して終了です。

print.js
//window.print()の実行後、作成した「#print」と、非表示用のclass「print-off」を削除
$('#print').remove();
$('.print-off').removeClass('print-off');

終わりに。

いかがでしょうか。
印刷したいエリアだけを取り出せるのでユーザーにとって不要なエリアが無くなり使い勝手の良いページが作成できると思います。
自作の家庭用備蓄をチェックできるアプリでも、チェックした備蓄情報を印刷できるようにしてありますので
良かったら作業の合間にそちらを見ていただけると具体的な挙動を確認していただけます。
https://saku2stockcheck.herokuapp.com/

では、最後まで見ていただいてありがとうございました!

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

AWS SNS から送られてくる POST リクエストは text/plain なので body パラメーターをパースするときは注意する

概要

AWS SNS から来る POST リクエストの body パラメーターをパースしようとしたときに、上手く行かなくてはまったので、解決策を共有します。

解決策

AWS SNS は HTTPS POST リクエストでアプリケーションにメッセージを送信してくれます。
参考記事: 受信者が HTTP/S エンドポイントの場合のシステム間メッセージングに Amazon SNS を使用する

その際に、body パラメーターにメッセージの内容が入っているのですが、Rails の params メソッドを使用してもパラメーターの中身を取得できませんでした。

class SnsController < ApplicationController
  def message
    params # メッセージがない
  end
end

リクエストの中身を調べたところ、content-type に text/plain が設定されていたことが原因でした。
もし content-type に application/json が設定されていれば、パラメーターを params メソッドで取得できたはずです。

なので、下記のようにすることででパラメーターを取得できます。

class SnsController < ApplicationController
  def message
    raw_body = request.body.read
    body_params = JSON.parse(raw_body)
  end
end

メッセージの文章はあくまで単純なテキストで、そのテキストが json の形式になっていることもあれば XML かもしれないし、あるいは何の形式にも当てはまらないテキストかもしれない、というのが AWS SNS の考え方なのだと思います。

考えてみれば AWS SNS はメッセージを送信するのが役割で、そのメッセージ文字列の形式を固定するのは責務の対象外なんだなと納得しました。

参考記事:
https://github.com/s12v/sns/issues/45

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

【Ruby on Rails】Rails超入門1!マジで凄い!scaffoldで恐竜登録アプリ作成1

1.scaffoldとは

Webアプリケーションに必要なビュー(画面)、コントローラー(制御部分)、モデル(データベース)をコマンド一つで提供してくれるコマンド。
さらにwebアプリに共通するCRUDを提供してくれる。

2.scaffoldによるアプリの作成

(1)rails new 作成したいアプリ名
でアプリケーションを作成する。

make_scaffold2_dino1.jpg

(2)下記コマンドをアプリケーションに移動して入力する。

rails generate scaffold モデル名 カラム名1:データ型1 カラム名2:データ型 2 …

例)
rails generate scaffold dinosor2 dname0:string dtype0:string sname1:string stype1:string sname2:string stype2:string hflg:boolean

ここで、dname0:作成恐竜名,dtype0:作成恐竜タイプ
sname1:生成元恐竜名1,stype1:生成元恐竜タイプ1,sname1:生成元恐竜名2,stype1:生成元恐竜タイプ2,hflg:ハイブリッドフラグを持つdinosor2テーブルを作成している。

下記コマンドを入力すると、scaffoldアプリ実行に必要な様々なファイルが作られる。

c:\RailsInstaller\Ruby2.3.3\rails_app2>rails generate scaffold dinosor2 dname0:string dtype0:string sname1:string stype1:string sname2:string stype2:string hflg:boolean

      invoke  active_record
      create    db/migrate/20200320132820_create_dinosor2s.rb
      create    app/models/dinosor2.rb
      invoke    test_unit
      create      test/models/dinosor2_test.rb
      create      test/fixtures/dinosor2s.yml
      invoke  resource_route
       route    resources :dinosor2s
      invoke  scaffold_controller
      create    app/controllers/dinosor2s_controller.rb
      invoke    erb
      create      app/views/dinosor2s
      create      app/views/dinosor2s/index.html.erb
      create      app/views/dinosor2s/edit.html.erb
      create      app/views/dinosor2s/show.html.erb
      create      app/views/dinosor2s/new.html.erb
      create      app/views/dinosor2s/_form.html.erb
      invoke    test_unit
      create      test/controllers/dinosor2s_controller_test.rb
      invoke    helper
      create      app/helpers/dinosor2s_helper.rb
      invoke      test_unit
      invoke    jbuilder
      create      app/views/dinosor2s/index.json.jbuilder
      create      app/views/dinosor2s/show.json.jbuilder
      create      app/views/dinosor2s/_dinosor2.json.jbuilder
      invoke  test_unit
      create    test/system/dinosor2s_test.rb
      invoke  assets

(3)アプリケーションディレクトリに移動してテーブル作成をするために下記コマンドを入力する。
rake db:migrate

make_scaffold2_db_migrate.jpg

3.scaffoldアプリの実行(自動生成された画面達)

rails serverでサーバーの起動を確認する。

(1)http://localhost:3000/モデル名s
を入力すると、一覧画面が表示される。

make_scaffold2_dino2_new1.jpg

make_scaffold2_dino2_index.jpg

(2)[New モデル名]リンクを押すと新規作成画面が表示される。

make_scaffold2_dino2_new2.jpg

なんとboolean型のhflgがチェックボックスになっていて、他の入力項目はテキストボックスになっている!!!

(3)データを登録して編集画面を表示する。

make_scaffold2_dino2_update.jpg

4.ルーティングの確認

アプリケーションディレクトリ内で下記コマンドを入力する。
rake routes

「erbファイルで記載されているpath(prefix)とURLパターン、contollerでのメソッド
の対応一覧のようなものが確認できる。」
(と思う)

c:\RailsInstaller\Ruby2.3.3\rails_app2>rake routes
       Prefix Verb   URI Pattern                   Controller#Action
    dinosor2s GET    /dinosor2s(.:format)          dinosor2s#index
              POST   /dinosor2s(.:format)          dinosor2s#create
 new_dinosor2 GET    /dinosor2s/new(.:format)      dinosor2s#new
edit_dinosor2 GET    /dinosor2s/:id/edit(.:format) dinosor2s#edit
     dinosor2 GET    /dinosor2s/:id(.:format)      dinosor2s#show
              PATCH  /dinosor2s/:id(.:format)      dinosor2s#update
              PUT    /dinosor2s/:id(.:format)      dinosor2s#update
              DELETE /dinosor2s/:id(.:format)      dinosor2s#destroy

c:\RailsInstaller\Ruby2.3.3\rails_app2>

5.自動生成されたcontrollerのコード

「各画面のボタンやリンクをクリックしたときに、どのようなDB処理やページを読みこむかを記述してあるコード」

dinosor2_controller.rb
class Dinosor2sController < ApplicationController
  before_action :set_dinosor2, only: [:show, :edit, :update, :destroy]

  # GET /dinosor2s
  # GET /dinosor2s.json
  def index
    @dinosor2s = Dinosor2.all
  end

  # GET /dinosor2s/1
  # GET /dinosor2s/1.json
  def show
  end

  # GET /dinosor2s/new
  def new
    @dinosor2 = Dinosor2.new
  end

  # GET /dinosor2s/1/edit
  def edit
  end

  # POST /dinosor2s
  # POST /dinosor2s.json
  def create
    @dinosor2 = Dinosor2.new(dinosor2_params)

    respond_to do |format|
      if @dinosor2.save
        format.html { redirect_to @dinosor2, notice: 'Dinosor2 was successfully created.' }
        format.json { render :show, status: :created, location: @dinosor2 }
      else
        format.html { render :new }
        format.json { render json: @dinosor2.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /dinosor2s/1
  # PATCH/PUT /dinosor2s/1.json
  def update
    respond_to do |format|
      if @dinosor2.update(dinosor2_params)
        format.html { redirect_to @dinosor2, notice: 'Dinosor2 was successfully updated.' }
        format.json { render :show, status: :ok, location: @dinosor2 }
      else
        format.html { render :edit }
        format.json { render json: @dinosor2.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /dinosor2s/1
  # DELETE /dinosor2s/1.json
  def destroy
    @dinosor2.destroy
    respond_to do |format|
      format.html { redirect_to dinosor2s_url, notice: 'Dinosor2 was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_dinosor2
      @dinosor2 = Dinosor2.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def dinosor2_params
      params.require(:dinosor2).permit(:dname0, :dtype0, :sname1, :stype1, :sname2, :stype2, :hflg)
    end
end

6.自動生成されたviewのコード

(1)新規作成画面
form.html.erbを読みこんでいる。
backリンクで一覧画面に戻る。

new.html.erb
<h1>New Dinosor2</h1>

<%= render 'form', dinosor2: @dinosor2 %>

<%= link_to 'Back', dinosor2s_path %>

(2)詳細画面
指定した恐竜の情報を表示する画面。
editリンクで編集画面へ、backリンクで一覧画面へ遷移。

show.html.erb
<p id="notice"><%= notice %></p>

<p>
  <strong>Dname0:</strong>
  <%= @dinosor2.dname0 %>
</p>

<p>
  <strong>Dtype0:</strong>
  <%= @dinosor2.dtype0 %>
</p>

<p>
  <strong>Sname1:</strong>
  <%= @dinosor2.sname1 %>
</p>

<p>
  <strong>Stype1:</strong>
  <%= @dinosor2.stype1 %>
</p>

<p>
  <strong>Sname2:</strong>
  <%= @dinosor2.sname2 %>
</p>

<p>
  <strong>Stype2:</strong>
  <%= @dinosor2.stype2 %>
</p>

<p>
  <strong>Hflg:</strong>
  <%= @dinosor2.hflg %>
</p>

<%= link_to 'Edit', edit_dinosor2_path(@dinosor2) %> |
<%= link_to 'Back', dinosor2s_path %>

(3)編集画面
_form.html.erbを読みこんで表示。
showリンクで詳細画面へ、backリンクで一覧画面へ遷移。

edit.html.erb
<h1>Editing Dinosor2</h1>

<%= render 'form', dinosor2: @dinosor2 %>

<%= link_to 'Show', @dinosor2 %> |
<%= link_to 'Back', dinosor2s_path %>
index.html.erb
<p id="notice"><%= notice %></p>

<h1>Dinosor2s</h1>

<table>
  <thead>
    <tr>
      <th>Dname0</th>
      <th>Dtype0</th>
      <th>Sname1</th>
      <th>Stype1</th>
      <th>Sname2</th>
      <th>Stype2</th>
      <th>Hflg</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @dinosor2s.each do |dinosor2| %>
      <tr>
        <td><%= dinosor2.dname0 %></td>
        <td><%= dinosor2.dtype0 %></td>
        <td><%= dinosor2.sname1 %></td>
        <td><%= dinosor2.stype1 %></td>
        <td><%= dinosor2.sname2 %></td>
        <td><%= dinosor2.stype2 %></td>
        <td><%= dinosor2.hflg %></td>
        <td><%= link_to 'Show', dinosor2 %></td>
        <td><%= link_to 'Edit', edit_dinosor2_path(dinosor2) %></td>
        <td><%= link_to 'Destroy', dinosor2, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Dinosor2', new_dinosor2_path %>

(3)_form.html.erb
下記のコードが新規作成、編集画面の元となる_form.html.erbだ。
ここでdname0,dtype0,sname1,stype1,sname2,stype2の入力がテキストボックスに、
hflgの入力部分がチェックボックスになっていることが確認できる。

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

      <ul>
      <% dinosor2.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

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

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

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

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

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

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

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

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

上記画面を見るとわかるのだが、データを入れるテーブルのコマンド一つでCRUDアプリが本当に簡単に作れるという事だ。(色々な課題はあるけど)

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

応用 Lesson2 検索力についてのまとめ

応用カリキュラム Lesson2対応 リンク集

エンジニアにとって検索力がとても重要なことを学び、
テキストにあるワードを検索してみました。

初投稿

まずQiitaの使い方からよく分かりませんので、その辺は徐々に。
とりあえず自身のアウトプットとして書きます。
どなたかの何かのお役に立てたら光栄です。

では、ワードごとにリンクを貼っていきます。
あえて、ワードに対する答えは載せません。
あくまでリンク集です。(自分のためにも…)

【リンク集 ~Link Page〜】

1. jQueryのメリット・デメリットは簡単に言うと?

<検索ワード>
 jQuery メリット

<参考サイト>
 AtoOne 様
 https://www.atoone.co.jp/column/10176/

2. Queryは最近のトレンドの中でどのような立ち位置か?

<検索ワード>
 jQuery トレンド 立ち位置

<参考サイト>
 Hideout der Eremit 様
 https://dereremit.jp/archives/260

3. deviseとは簡単に言うと?

<検索ワード>
 deviceとは

<参考サイト>
 キツネの惑星 様
 https://kitsune.blog/rails-devise

4. deviseと同様な機能で他によく使われるものは何か?違いは何か?なぜdevise以外にそれが生まれたか?

これ、めちゃくちゃ苦戦しました。
もっと分かりやすい、的確なサイトがあると思います。

<検索ワード>
 rails 認証 gem トレンド …他多数?

<参考サイト>
 Tech Blog 様
 https://www.for-engineer.life/entry/rails-sorcery/

5. unicornとは簡単に言うと何か?

6. unicornと同様な機能で他によく使われるものは何か?違いは何か?なぜunicorn以外にそれが生まれたか?

<検索ワード>
 unicornとは

<参考サイト>
 @takahiro1127
 https://qiita.com/takahiro1127/items/fcb81753eaf381b4b33c

〜気づき〜

検索力にはある程度自信を持っていましたが、
信憑性のある、確からしい情報を得るためには
テクニックがあることを知りました。

同時に自分の無力さに愕然(笑)

今後も学習のノート代わり、アウトプットとして活用させていただきます。

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

Rails × Docker ログに「Cannot render console from Allowed networks」と出力される対処法

ログに「Cannot render console from Allowed networks」と出力される対処法

環境

ruby 2.5.1
rails 5.2.4
docker 19.03.8

Ruby on Railsの開発環境で、DockerかVagrantを使っていて、”Cannot render console from 172.20.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255″ 「このIPアドレスではレンダリングできない」と言われた場合

うぉおおん?

Started GET "/" for 172.20.0.1 at 2020-03-28 09:15:23 +0000
web_1  | Cannot render console from 172.20.0.1! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255

Image from Gyazo

対処法は簡単だった

Webコンソールの設定で172.20.0.1ネットワークをホワイトリストに登録する

ホワイトリストとは?

config/environments/development.rbconfig.web_console.whitelisted_ips = ‘172.20.0.1’を追加(IPアドレスは自身のログに書いてあるIPアドレスを記述)

config/environments/development.rb
config.web_console.whitelisted_ips ='172.20.0.1'

Image from Gyazo

これで解決しました

docker → kubernetesに関してこれから投稿していきます。

docker公式

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

#Rails / Controller 以外で ActiveModelSerializers の動作確認、JSON出力をする ( rails console とか )

モデルのインスタンスを Serializer に与えて to_json するだけで良さげ

Serializer

class UserSerializer < ActiveModel::Serializer
  attributes :id, :name
end

動作

空のインスタンス

user = User.new

# <User:0x000055cfd1dcf6f8
#   id: nil,
#   name: nil,
#   email: nil,
#   created_at: nil,
#   updated_at: nil>

serialized_user = UserSerializer.new(user)

serialized_user.to_json
# => "{\"id\":null,\"name\":null}"

DBからインスタンスを得る例

user = User.last

# #<User:0x000055cfd1ea0730
#  id: 19,
#  name: "吾輩",
#  created_at: Fri, 20 Mar 2020 01:02:10 UTC +00:00,
#  updated_at: Fri, 20 Mar 2020 01:02:10 UTC +00:00>


serialized_user = UserSerializer.new(user)

serialized_user.to_json
# => => "{\"id\":19,\"name\":\"吾輩\"}"

Original by Github issue

https://github.com/YumaInaura/YumaInaura/issues/3050

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

【ruby,rails】環境変数から読み込んだ秘密鍵の文字列の改行コードがエスケープされていて秘密鍵としてパースできないときの対処法

概要

環境変数から読み込んだ秘密鍵の文字列の改行コードがエスケープされてしまうことによって、秘密鍵としてパースできないときの文字列処理を共有します。

解決策

たとえば下記のような秘密鍵があったとき、

-----BEGIN RSA PRIVATE KEY-----
MIGrAgEAAiEAvnrd8LBnzAGxCW+i7KtVQSiTsssMtbwcs5styeKsn2kCAwEAAQIh
AKBF8glb5Xqa0cQG0ygg4hIFdipmvEJhiCuhX93krDCBAhEA51bAM0gFPvxyk9Xe
ioIOBQIRANLJEv4Xw7MwT7EEEARL5RUCEBa8bu1bUbCsDPK8nT+NoqUCEQCIzFCU
MY4j7BW8N3vBnhPlAhBgs4tSfe6RbpertixmCygk
-----END RSA PRIVATE KEY-----

環境変数に設定するときは下記のように改行コードを使って、文字列を1行にして設定すると思います。

-----BEGIN RSA PRIVATE KEY-----\nMIGrAgEAAiEAvnrd8LBnzAGxCW+i7KtVQSiTsssMtbwcs5styeKsn2kCAwEAAQIh\nAKBF8glb5Xqa0cQG0ygg4hIFdipmvEJhiCuhX93krDCBAhEA51bAM0gFPvxyk9Xe\nioIOBQIRANLJEv4Xw7MwT7EEEARL5RUCEBa8bu1bUbCsDPK8nT+NoqUCEQCIzFCU\nMY4j7BW8N3vBnhPlAhBgs4tSfe6RbpertixmCygk\n-----END RSA PRIVATE KEY-----\n

この1行にした秘密鍵をアプリケーション内で読み込むと、下記のようにバックスラッシュが2つになってしまいます。

 "-----BEGIN RSA PRIVATE KEY-----\\nMIGrAgEAAiEAvnrd8LBnzAGxCW+i7KtVQSiTsssMtbwcs5styeKsn2kCAwEAAQIh\\nAKBF8glb5Xqa0cQG0ygg4hIFdipmvEJhiCuhX93krDCBAhEA51bAM0gFPvxyk9Xe\\nioIOBQIRANLJEv4Xw7MwT7EEEARL5RUCEBa8bu1bUbCsDPK8nT+NoqUCEQCIzFCU\\nMY4j7BW8N3vBnhPlAhBgs4tSfe6RbpertixmCygk\\n-----END RSA PRIVATE KEY-----\\n"

このまま秘密鍵オブジェクトを作成しようとすると、エラーになります。

OpenSSL::PKey::RSA.new(str)
=> OpenSSL::PKey::RSAError: Neither PUB key nor PRIV key: nested asn1 error

そこで、この2つのバックスラッシュを1つのバックスラッシュに置換することで、秘密鍵オブジェクを作成することができるようになります。

str.gsub(/\\n/, "\n")
=> "-----BEGIN RSA PRIVATE KEY-----\nMIGrAgEAAiEAvnrd8LBnzAGxCW+i7KtVQSiTsssMtbwcs5styeKsn2kCAwEAAQIh\nAKBF8glb5Xqa0cQG0ygg4hIFdipmvEJhiCuhX93krDCBAhEA51bAM0gFPvxyk9Xe\nioIOBQIRANLJEv4Xw7MwT7EEEARL5RUCEBa8bu1bUbCsDPK8nT+NoqUCEQCIzFCU\nMY4j7BW8N3vBnhPlAhBgs4tSfe6RbpertixmCygk\n-----END RSA PRIVATE KEY-----\n"

OpenSSL::PKey::RSA.new(str.gsub(/\\n/, "\n"))
=> #<OpenSSL::PKey::RSA:0x00007f9d7dd734e0>

もちろん、秘密鍵だけではなく、改行コードのバックスラッシュがエスケープされたすべての文字列に対しても同様の解決策が有効です。

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

ユーザー管理に必須!Gemの『devise』を使おう!

Railsにおいてユーザーを管理する際に必要となるのが『devise』と呼ばれるGemです。

68747470733a2f2f7261772e6769746875622e636f6d2f706c617461666f726d617465632f6465766973652f6d61737465722f6465766973652e706e67.png

deciseの設定を忘れてしまわないように備忘録として記述します。

deviseの機能

このGemを使用することによって、ユーザーを管理する機能を追加する際に簡単に管理を行うことができるようになります。

deviseの設定

1.Gemfileへの記述

# 省略
gem 'devise'  # 最終行に追記

2.ターミナルにてbundle install

$ bundle install

bundle installでGemを更新・反映する。

3.deviseの設定ファイルを作成

$ rails g devise:install

4.deviseの機能を持ったUserモデルの作成

注意したいのがここ!通常のrails g model 〇〇ではないという点です。

ここでは下記のように記述してUserモデルを作成します。

# deviseコマンドでモデルを作成
$ rails g devise user

これでdevise機能を持ったUserモデルが作成されました。

またroutes.rbには自動的に下記のコードが追加されます。

config.routes.rb
Rails.application.routes.draw do
  devise_for :users
#以下略

devise_for :usersの記述によりユーザーのログイン・ログアウト、新規登録で必要なルーティングが作成されます。

5.rails db:migrateを行う

ターミナル上でrails db:migrateを行います。

# 作成されたマイグレーションファイルを実行
$ rails db:migrate

上記を行うことでデータベース上にusersテーブルが作成されます。

まとめ

deviseの設定をスムーズに行えるようになりました。ただしユーザーのログイン・ログアウト・新規登録に関するバックエンドの実装に関しては曖昧な部分もあるため、追記で記述しアウトプットの機会を持ちたいと考えています。

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

dockerでよく使うコマンド一覧【Rails】

土台
$ docker-compose run web ~

MySQLを使う場合

$ docker-compose run web rails new . --force --database=mysql
$ docker-compose build

model作成

docker-compose run web bundle exec rails g model (モデル名) (カラム名)

バックグラウンドでコンテナ起動

docker-compose up -d

コンテナ再起動

docker-compose restart

Gemfile変更時(基本的には)

$ docker-compose build

DBへテストデータ投入

$ docker-compose run web bundle exec rake db:seed

DBへテストデータを一から入れ直したい時
(テーブル削除、作成、マイグレーション)

$ docker-compose run web bundle exec rake db:drop
$ docker-compose run web bundle exec rake db:create
$ docker-compose run web bundle exec rake db:migrate
$ docker-compose run web bundle exec rake db:seed

dockerコンテナ、イメージ削除

$ docker rm $(docker ps -q -a)
$ docker rmi $(docker images -q)

補足: kaminari設定ファイル生成

$ docker-compose run web bundle exec rails g kaminari:config

補足: kaminari(bootstrap4)ファイル生成(bootstrap4)

$ docker-compose run web bundle exec rails g kaminari:views bootstrap4

補足: kaminariページネーションファイル生成

$ docker-compose run web bundle exec rails g kaminari:config

Rspecを使う場合(Gemfileにはrspecの記述をする)

$ docker-compose run web bundle exec rails g rspec:install 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

dockerでよく使うコマンド一覧

MySQLを使う場合

$ docker-compose run web rails new . --force --database=mysql
$ docker-compose build

model作成

docker-compose run web bundle exec rails g model (モデル名) (カラム名)
バックグラウンドでコンテナ起動

docker-compose up -d
コンテナ再起動

docker-compose restart

Gemfile変更時(基本的には)

$ docker-compose build

DBへテストデータ投入

$ docker-compose run web bundle exec rake db:seed

DBへテストデータを一から入れ直したい時
(テーブル削除、作成、マイグレーション)

$ docker-compose run web bundle exec rake db:drop
$ docker-compose run web bundle exec rake db:create
$ docker-compose run web bundle exec rake db:migrate
$ docker-compose run web bundle exec rake db:seed

dockerコンテナ、イメージ削除

$ docker rm $(docker ps -q -a)
$ docker rmi $(docker images -q)

補足: kaminari設定ファイル生成

$ docker-compose run web bundle exec rails g kaminari:config

補足: kaminari(bootstrap4)ファイル生成(bootstrap4)

$ docker-compose run web bundle exec rails g kaminari:views bootstrap4

補足: kaminariページネーションファイル生成

$ docker-compose run web bundle exec rails g kaminari:config

Rspecを使う場合(Gemfileにはrspecの記述をする)

$ docker-compose run web bundle exec rails g rspec:install 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsチュートリアル〜6章〜ついにモデル作成へ突入

userモデルの作成

5章を足早に終わらせて続いて6章に入っていきます。
ここではユーザー登録ページを作成していく。
・ユーザー用のデータモデルの作成
・データを保存する手段の確保について学ぶ。

Railsではデータモデルとして扱うデフォルトのデータ構造のことをモデルと呼ぶ。Railsでは、データを永続化するデフォルトの解決策としてデータベースを使ってデータを長期間保存する。データベースとやり取りをするデフォルトのライブラリはActive Recordという。
Active Recordはデータオブジェクトの作成・保存・検索のためのメソッドを持っている。

データベースの移行

今回の目的は永続性のあるモデルを構築することである。
Railsでユーザーをモデリングする時は属性を明示的に識別する必要がなく、デフォルトでリレーショナルデータベースを使用する。リレーショナルデータベースはデータ行で構成されるテーブルからなり、各行はデータ属性のカラム(列)を持つ。nameとemailのカラムをもつusersテーブルを作成する。

Userモデルを生成する
$ rails generate model User name:string email:string
      invoke  active_record
      create    db/migrate/20160523010738_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

generateコマンドの新しい結果の一つとして、マイグレーションと呼ばれるファイルが生成される。マイグレーションはデータベースの構造をインクリメンタルに変更する手段を提供する。(インクリメンタル=次第に増やす)要するにマイグレーションはデータベースの構造増やす手段を提供すること。

マイグレーション自体は、データベースに与える変更を定義したchangeメソッドの集まりである。changeメソッドはcreate_tableというRailsのメソッドを呼びユーザーを保存するためのテーブルをデータベースに作成する。
このメソッドはブロック変数を1つ持つブロックを受け取る。このブロックの中でnameとemailカラムをデータベースに作る。
モデル名は単数形(User)だがテーブル名は複数型(users)である。
ブロックの最後の行t.timestampsは特別なコマンドで、created_atとupdated_atという2つの「マジックカラム (Magic Columns)」を作成します。これらは、あるユーザーが作成または更新されたときに、その時刻を自動的に記録するタイムスタンプである。

マイグレーションは、次のようにdb:migrateコマンドを使い実行することができる。これを「マイグレーションの適応」と呼ぶ。

$ rails db:migrate

db:migrateが実行されると、db/development.sqlite3という名前のファイルが生成されます。これはSQLite5の実体である。development.sqlite3ファイルを開くためのDB Browser for SQLiteというツールを使うと、データベースの構造を見ることができる。

ほぼすべてのマイグレーションは、元に戻すことが可能
元に戻すことを「ロールバック (rollback)と呼び、Railsではdb:rollbackというコマンドで実現できます。

  $ rails db:rollback

modelファイル

ユーザーオブジェクトの検索

User.find(1)
User.findにユーザーのidを渡し,その結果Active Recordはそのidのユーザーを返します。

一般的なfindメソッド以外に、Active Recordには特定の属性でユーザーを検索する方法もあります。
 User.find_by(email: "mhartl@example.com")

>> User.first
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 19:05:58", updated_at: "2016-05-23 19:05:58">
firstはデータベースの最初のユーザーを返す。

>> User.all
=> #<ActiveRecord::Relation [#<User id: 1, name: "Michael Hartl",
email: "mhartl@example.com", created_at: "2016-05-23 19:05:58",
updated_at: "2016-05-23 19:05:58">, #<User id: 2, name: "A Nother",
email: "another@example.org", created_at: "2016-05-23 19:18:46",
updated_at: "2016-05-23 19:18:46">]>
User.allでデータベースのすべてのUserオブジェクトが返ってくることがわかります。また、返ってきたオブジェクトのクラスがActiveRecord::Relationとなっています。これは、各オブジェクトを配列として効率的にまとめてくれるクラスである。

ユーザーオブジェクトの更新

基本的な更新の方法は2つである。

>> user           # userオブジェクトが持つ情報のおさらい
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 19:05:58", updated_at: "2016-05-23 19:05:58">
>> user.email = "mhartl@example.net"
=> "mhartl@example.net"
>> user.save
=> true

変更をデータベースに保存するために最後にsaveを実行する必要があることを忘れない。
保存を行わずにreloadを実行すると、データベースの情報を元にオブジェクトを再読み込みするので失敗する。

属性を更新するもう1つの方法は、update_attributesを使うケース

>> user.update_attributes(name: "The Dude", email: "dude@abides.org")
=> true
>> user.name
=> "The Dude"
>> user.email
=> "dude@abides.org"

update_attributesメソッドは属性のハッシュを受け取り、成功時には更新と保存を続けて同時に行う。
特定の属性のみを更新したい場合は、次のようにupdate_attributeを使います。このupdate_attributeには、検証を回避するといった効果もある。

>> user.update_attribute(:name, "El Duderino")
=> true
>> user.name
=> "El Duderino"

ユーザーの検証

UserモデルにNameとemailの属性を与えた。ここに制限を加えてデータを登録する際に適切な管理が行われるようにする。(具体的には名前を空にしない、emailはアドレスのフォーマットに従う。メールアドレスがデータベース内で重複しない。等)

Active Recordでは検証(バリデーション)という機能を通して、こういった制約を課すことができる。
よく使われるケース
存在性(presence)
長さ(length)
フォーマット(format)
一意性(uniqueness)
確認(confirmation)
の検証である。

有効性の検証

有効なモデルのオブジェクトを作成しその属性のうちの一つを有効でない属性に意図的に変更する。→バリデーション機能で失敗するかどうかテストする。
デフォルトのUserテスト

require 'test_helper'

class UserTest < ActiveSupport::TestCase
  # test "the truth" do
  #   assert true
  # end
end

有効なオブジェクトに対してテストを書くためにsetupという特殊なメソッドを使って有効なUserオブジェクト(@user)を作成する。setupメソッド内に書かれた処理は各テストが走る直前に実行される。@userはインスタンス変数だが、setupメソッド内で宣言しておけばすべてのテスト内でインスタンス変数が使用できる。

require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  test "should be valid" do
    assert @user.valid?
  end
end

assertメソッドを使ってテストする。
@user.valid?がtrueを返すと成功し、falseを返すと失敗する。

$ rails test:models
modelに関するテストはこのコマンドを使用する。

存在性の検証

最も基本的なバリデーションである。
渡された属性が存在することを検証する。ここではユーザーがデータベースに保存される前にname emailフィールドの両方が存在することを保証する。

require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end

  test "should be valid" do
    assert @user.valid?
  end

  test "name should be present" do
    @user.name = "     "
    assert_not @user.valid?
  end
  test "email should be present" do
    @user.email = "     "
    assert_not @user.valid?
end
end

name属性の存在性に関するテストを追加します。具体的には@user変数のname属性に対して空白の文字列をセット
assert_notメソッドを使って Userオブジェクトが有効でなくなったことを確認する。

name属性の存在性を検証する。

class User < ApplicationRecord
  validates :name, presence: true
end

長さの検証

ここではユーザーモデル上の名前の長さを50字を上限とし、51文字以上の名前
emailの文字を255字を上限とするように長さの制限をかける。
nameの長さの検証に用いるテスト

require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "name should not be too long" do
    @user.name = "a" * 51
    assert_not @user.valid?
  end

  test "email should not be too long" do
    @user.email = "a" * 244 + "@example.com"
    assert_not @user.valid?
  end
end

長さの検証を追加する

class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 50 }
  validates :email, presence: true, length: { maximum: 255 }
end

フォーマットの検証

name属性の検証には、空文字でない、名前が51文字未満であるという最小限の制約しかいまのところない。
email属性の場合は、有効なメールアドレスかどうかを判定するために、もっと厳重な要求を満たさなければなりません。ここではメールアドレスにおなじみのパターンuser@example.comに合っているかどうかも確認する。(なお今回ここで使うテストや検証は、形式があからさまに無効なものを拒否するだけであり、すべての有効なメールアドレスを受け入れられるものではない点に注意してください。)

>> %w[foo bar baz]
=> ["foo", "bar", "baz"]
>> addresses = %w[USER@foo.COM THE_US-ER@foo.bar.org first.last@foo.jp]
=> ["USER@foo.COM", "THE_US-ER@foo.bar.org", "first.last@foo.jp"]
>> addresses.each do |address|
?>   puts address
>> end
USER@foo.COM
THE_US-ER@foo.bar.org
first.last@foo.jp``

メールアドレスのバリデーションは扱いが難しく、エラーが発生しやすい部分なので、有効なメールアドレスと無効なメールアドレスをいくつか用意して、バリデーション内のエラーを検知していきます。具体的には、user@example,comのような無効なメールアドレスが弾かれることと、user@example.comのような有効なメールアドレスが通ることを確認しながら、バリデーションを実装していきます (ちなみに今の状態では、空でない文字列はすべてメールアドレスとして通ってしまいます) 。

require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email validation should accept valid addresses" do
    valid_addresses = %w[user@example.com USER@foo.COM A_US-ER@foo.bar.org
                         first.last@foo.jp alice+bob@baz.cn]
    valid_addresses.each do |valid_address|
      @user.email = valid_address
      assert @user.valid?, "#{valid_address.inspect} should be valid"
    end
  end
end
ここでは、assertメソッドの第2引数にエラーメッセージを追加していることに注目してください。これによって、どのメールアドレスでテストが失敗したのかを特定できるようになります。

assert @user.valid?, "#{valid_address.inspect} should be valid

次に、user@example,com (ドットではなくカンマになっている) やuser_at_foo.org (アットマーク ‘@’ がない) といった無効なメールアドレスを使って 「無効性 (Invalidity)」についてテストしていく。

require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email validation should reject invalid addresses" do
    invalid_addresses = %w[user@example,com user_at_foo.org user.name@example.
                           foo@bar_baz.com foo@bar+baz.com]
    invalid_addresses.each do |invalid_address|
      @user.email = invalid_address
      assert_not @user.valid?, "#{invalid_address.inspect} should be invalid"
    end
  end
end

メールフォーマットを正規表現で検証する。

class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX }
end

一意性を検証する

メールアドレスの一意性を強制するために (ユーザー名として使うために)、validatesメソッドの:uniqueオプションを使う。
今回のテストではRubyのオブジェクトを作るだけでなく、実際にメモリ上にレコードをデータベースに登録する必要がある。

重複するメールアドレス拒否のテスト

require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com")
  end
  .
  .
  .
  test "email addresses should be unique" do
    duplicate_user = @user.dup
  duplicate_user.email = @user.email.upcase
    @user.save
    assert_not duplicate_user.valid?
  end
end

上のコードでは、@userと同じメールアドレスのユーザーは作成できないことを、@user.dupを使ってテストしています。dupは、同じ属性を持つデータを複製するためのメソッドです。@userを保存した後では、複製されたユーザーのメールアドレスが既にデータベース内に存在するため、ユーザの作成は無効になるはずである。

メールアドレスの一意性を検証する
emailのバリデーションにuniqueness: trueというオプションの追加

class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: true
end

メールアドレスの大文字小文字を無視した一意性の検証

app/models/user.rb
class User < ApplicationRecord
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end
trueをcase_sensitive: falseに置き換え

データベースのインデックス

データベースにカラムを作成するとき、そのカラムでレコードを検索する (find) 必要があるかどうかを考えることは重要です。例えば、リスト 6.2のマイグレーションによって作成された email属性について考えてみましょう。第7章ではユーザーをサンプルアプリにログインできるようにしますが、このとき、送信されたものと一致するメールアドレスのユーザーのレコードをデータベースの中から探しだす必要があります。
インデックスなどの機能を持たない素朴なデータモデルにおいてユーザーをメールアドレスで検索するには、データベースのひとりひとりのユーザーの行を端から順に読み出し、そのemail属性と渡されたメールアドレスを比較するという非効率的な方法しかない。

emailカラムにインデックスを追加することで、この問題を解決することができます。(インデックスは本の索引機能のようなもの)

emailインデックスを追加すると、データモデリングの変更が必要になります。Railsではマイグレーションでインデックスを追加します。Userモデルを生成すると自動的に新しいマイグレーションが作成されたことを思い出してください。
今回の場合は、既に存在するモデルに構造を追加するので、次のようにmigrationジェネレーターを使ってマイグレーションを直接作成する必要があります。

$ rails generate migration add_index_to_users_email

ユーザー用のマイグレーションと異なり、メールアドレスの一意性のマイグレーションは未定義になっています。リスト 6.29のように定義を記述する必要があります16。

メールアドレスの一意性を強制するためのマイグレーション

db/migrate/[timestamp]_add_index_to_users_email.rb
class AddIndexToUsersEmail < ActiveRecord::Migration[5.0]
  def change
    add_index :users, :email, unique: true
  end
end

上のコードでは、usersテーブルのemailカラムにインデックスを追加するためにadd_indexというRailsのメソッドを使っています。インデックス自体は一意性を強制しませんが、オプションでunique: trueを指定することで強制できるようになります。

最後に、データベースをマイグレートします。

$ rails db:migrate

この時点では、テストDB用のサンプルデータが含まれているfixtures内で一意性の制限が保たれていないため、テストは red になります。つまり、リスト 6.1でユーザー用のfixtureが自動的に生成されていましたが、ここのメールアドレスが一意になっていないことが原因です (リスト 6.30) (実はこのデータはいずれも有効ではありませんが、fixture内のサンプルデータはバリデーションを通っていなかったので今まで問題にはなっていなかっただけでした)。

リスト 6.30: Userのデフォルトfixture red
test/fixtures/users.yml
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/
# FixtureSet.html

これで1つの問題が解決されましたが、メールアドレスの一意性を保証するためには、もう1つやらなければならないことがあります。それは、いくつかのデータベースのアダプタが、常に大文字小文字を区別するインデックス を使っているとは限らない問題への対処です。例えば、Foo@ExAMPle.Comfoo@example.comが別々の文字列だと解釈してしまうデータベースがありますが、私達のアプリケーションではこれらの文字列は同一であると解釈されるべきです。この問題を避けるために、今回は「データベースに保存される直前にすべての文字列を小文字に変換する」という対策を採ります。例えば"Foo@ExAMPle.CoM"という文字列が渡されたら、保存する直前に"foo@example.com"に変換してしまいます。これを実装するためにActive Recordのコールバック (callback) メソッドを利用します。このメソッドは、ある特定の時点で呼び出されるメソッドです。今回の場合は、オブジェクトが保存される時点で処理を実行したいので、before_saveというコールバックを使います。これを使って、ユーザーをデータベースに保存する前にemail属性を強制的に小文字に変換します17。作成したコードをリスト 6.32に示します。(本チュートリアルで初めて紹介したテクニックですが、このテクニックについては11.1でもう一度取り上げます。そこではコールバックを定義するときにメソッドを参照するという慣習について説明します。)

email属性を小文字に変換してメールアドレスの一意性を保証する

app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name,  presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
end

before_saveコールバックにブロックを渡してユーザーのメールアドレスを設定します。設定されるメールアドレスは、現在の値をStringクラスのdowncaseメソッドを使って小文字バージョンにしたもの。

セキュアなパスワードを追加する

セキュアパスワードという手法では、各ユーザーにパスワードとパスワードの確認を入力させ、それを (そのままではなく) ハッシュ化したものをデータベースに保存します。要するにハッシュ関数を使って、入力されたデータを元に戻せない (不可逆な) データにする処理を指す。

ユーザーの認証は、パスワードの送信、ハッシュ化、データベース内のハッシュ化された値との比較、という手順で進んでいきます。比較の結果が一致すれば、送信されたパスワードは正しいと認識され、そのユーザーは認証されます。。こうすることで、生のパスワードをデータベースに保存するという危険なことをしなくてもユーザー登録できる。

ハッシュ化されたパスワード
セキュアなパスワードの実装は

class User < ApplicationRecord
  .
  .
  .
  has_secure_password
end

上のようにモデルにこのメソッドを追加すると、次のような機能が使えるようになる。

・セキュアにハッシュ化したパスワードを、データベース内のpassword_digestという属性に保存できるようになる。
・2つのペアの仮想的な属性 (passwordとpassword_confirmation) が使えるようになる。存在性と値が一致するかどうかのバリデーションも追加される。
・authenticateメソッドが使えるようになる (引数の文字列がパスワードと一致するとUserオブジェクトを、間違っているとfalseを返すメソッド) 。

has_secure_password機能を使えるようにするには、モデル内にpassword_digestという属性を含める。
password_digestカラム用の適切なマイグレーションを生成。
マイグレーション名は自由に指定できる。add_password_digest_to_usersというマイグレーションファイルを生成するためには、次のコマンドを実行します。

$ rails generate migration add_password_digest_to_users password_digest:string

上のコマンドではpassword_digest:stringという引数を与えて、今回必要になる属性名と型情報を渡している。

password_digestカラムを追加するマイグレーション

db/migrate/[timestamp]_add_password_digest_to_users.rb
class AddPasswordDigestToUsers < ActiveRecord::Migration[5.0]
  def change
    add_column :users, :password_digest, :string
  end
end

リスト 6.35では、add_columnメソッドを使ってusersテーブルpassword_digestカラムを追加しています。これを適用させるには、データベースでマイグレーションを実行します。

$ rails db:migrate

また、has_secure_passwordを使ってパスワードをハッシュ化するためには、最先端のハッシュ関数であるbcryptが必要になります。パスワードを適切にハッシュ化することで、たとえ攻撃者によってデータベースからパスワードが漏れてしまった場合でも、Webサイトにログインされないようにできます。サンプルアプリケーションでbcryptを使うために、bcrypt gemをGemfileに追加します。

リスト 6.36: bcryptをGemfileに追加する

source 'https://rubygems.org'

gem 'rails',          '5.1.6'
gem 'bcrypt',         '3.1.12'
.
.
.

最後にbundle installを実行

6.3.2 ユーザーがセキュアなパスワードを持っている
Userモデルにpassword_digest属性を追加し、Gemfileにbcryptを追加したことで、ようやくUserモデル内でhas_secure_passwordが使えるようになりました

Userモデルにhas_secure_passwordを追加する

app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
end

次にユーザーモデルにパスワードとパスワード確認を追加する。

def setup
  @user = User.new(name: "Example User", email: "user@example.com")
end

テストをパスさせるために、パスワードとパスワード確認の値を追加します。

リスト 6.39: パスワードとパスワード確認を追加する

test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end
  .
  .
  .
end

リスト 6.41: パスワードの最小文字数をテストする red

test/models/user_test.rb
require 'test_helper'

class UserTest < ActiveSupport::TestCase

  def setup
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end
  .
  .
  .
  test "password should be present (nonblank)" do
    @user.password = @user.password_confirmation = " " * 6
    assert_not @user.valid?
  end

  test "password should have a minimum length" do
    @user.password = @user.password_confirmation = "a" * 5
    assert_not @user.valid?
  end
end
ここで、次のような多重代入 (Multiple Assignment) を使っていることに注目してください。

@user.password = @user.password_confirmation = "a" * 5

これはpasswordとpasswordconfirmationに対して同時に代入をしています

リスト 6.16ではmaximumを使ってユーザー名の最大文字数を制限していましたが、これと似たような形式のminimumというオプションを使って、最小文字数のバリデーションを実装することができます。

validates :password, length: { minimum: 6 }

また、空のパスワードを入力させないために、存在性のバリデーションも一緒に追加します。結果として、Userモデルのコードはリスト 6.42のようになります。ちなみにhas_secure_passwordメソッドは存在性のバリデーションもしてくれるのですが、これは新しくレコードが追加されたときだけに適用される性質を持っています。したがって、例えばユーザーが ' ' (6文字分の空白スペース) といった文字列をパスワード欄に入力して更新しようとすると、バリデーションが適用されずに更新されてしまう問題が発生してしまうのです。

セキュアパスワードの完全な実装

app/models/user.rb
class User < ApplicationRecord
  before_save { self.email = email.downcase }
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i
  validates :email, presence: true, length: { maximum: 255 },
                    format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, presence: true, length: { minimum: 6 }
end

この時点では、テストは greenになるはずです。

ユーザーの作成と認証
以上でUserモデルの基本部分が完了しましたので、今度は7.1でユーザー情報表示ページを作成するときに備えて、データベースに新規ユーザーを1人作成しましょう。また、Userモデルにhas_secure_passwordを追加した効果についても (例えばauthenticateメソッドの効果なども) 見ていきましょう。

ただしWebからのユーザー登録はまだできない (第7章で完成させます) ので、今回はRailsコンソールを使ってユーザーを手動で作成することにしましょう。6.1.3で説明したcreateを使いますが、後々実際のユーザーを作成する必要が出てくるので、今回はサンドボックス環境は使いません。したがって、今回作成したユーザーを保存すると、データベースに反映されます。それでは、まずrails consoleコマンドを実行してセッションを開始し、次に有効な名前・メールアドレス・パスワード・パスワード確認を渡してユーザーを作成してみましょう。

$ rails console
>> User.create(name: "Michael Hartl", email: "mhartl@example.com",
?>             password: "foobar", password_confirmation: "foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 20:36:46", updated_at: "2016-05-23 20:36:46",
password_digest: "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3...">

うまくデータベースに保存されたかどうかを確認するために、開発環境用のデータベースをDB Browser for SQLiteで開き、usersテーブルの中身を見てみましょう (図 6.9)21。もしクラウドIDEを使っている場合は、データベースのファイルをダウンロードして開いてください (図 6.5)。このとき、先ほど定義したUserモデルの属性 (図 6.8) に対応したカラムがあることにも注目しておいてください。

images/figures/sqlite_user_row_with_password_4th_edition
図 6.9: SQLiteデータベースdb/development.sqlite3に登録されたユーザーの行
コンソールに戻ってpassword_digest属性を参照してみると、リスト 6.42のhas_secure_passwordの効果を確認できます。

>> user = User.find_by(email: "mhartl@example.com")
>> user.password_digest
=> "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3yOcVFzb6oK"

これは、Userオブジェクトを作成したときに、"foobar"という文字列がハッシュ化された結果です。bcryptを使って生成されているので、この文字列から元々のパスワードを導出することは、コンピュータを使っても非現実的です

また6.3.1で説明したように、has_secure_passwordをUserモデルに追加したことで、そのオブジェクト内でauthenticateメソッドが使えるようになっています。このメソッドは、引数に渡された文字列 (パスワード) をハッシュ化した値と、データベース内にあるpassword_digestカラムの値を比較します。試しに、先ほど作成したuserオブジェクトに対して間違ったパスワードを与えてみましょう。

>> user.authenticate("not_the_right_password")
false
>> user.authenticate("foobaz")
false

間違ったパスワードを与えた結果、user.authenticateがfalseを返したことがわかります。次に、正しいパスワードを与えてみましょう。今度はauthenticateがそのユーザーオブジェクトを返すようになります。

>> user.authenticate("foobar")
=> #<User id: 1, name: "Michael Hartl", email: "mhartl@example.com",
created_at: "2016-05-23 20:36:46", updated_at: "2016-05-23 20:36:46",
password_digest: "$2a$10$xxucoRlMp06RLJSfWpZ8hO8Dt9AZXlGRi3usP3njQg3...">

第8章では、このauthenticateメソッドを使ってログインする方法を解説します。なお、authenticateがUserオブジェクトを返すことは重要ではなく、返ってきた値の論理値がtrueであることが重要です。4.2.3で紹介した、!!でそのオブジェクトが対応する論理値オブジェクトに変換できることを思い出してください。この性質を利用すると、user.authenticateがいい感じに仕事をしてくれるようになります。

>> !!user.authenticate("foobar")
=> true

今日はここまで

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

Railsの既存AppをDockerに切り替える際に役立つ記事

既存のAppをDockerに切り替える

過去に制作したRailsアプリをDockerで構築したいと思います。

参考になる記事

基本的にこの記事を使って、進めていきました。
Ruby on Rails 「途中まで作ったアプリにDockerを導入したい」に挑戦してみる(MySQL / Sequel Pro)

build時のエラーが出たら、下記の資料が役立ちます。
Docker docker-compose up時、You must use Bundler 2 or greater with this lockfile. という地獄のエラー

  1. Gemfile.lockの記述を削除して、空状態にする。(Dockerに切り替える際に阻害してしまう)
  2. Dockerfileのbundlerのバージョンを指定(RUN gem install bundler -v 1.3.0)
  3. $ docker-compose run web bundle install
  4. 改めてbuildする

これで進めました

docker
FROM ruby:2.5.1

RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs      

RUN mkdir /app_name 

ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN gem install bundler -v 1.3.0
RUN bundle install
ADD . $APP_ROOT

Ruby | bundler を特定のバージョンに切り替えて実行する

起動する際にエラーが出た場合
docker-compose up したらdriver failed programming external connectivity on endpointが出てきた

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

RailsアプリにDockerとCircleCIを導入した際、DB周りでエラーになったときの対処法

こんにちは、ペーパーエンジニアのよしこです。

CircleCIでgit push時に自動テスト(RSpec)しているRailsアプリに、Dockerを導入しました。

その際にデータベース周りで躓いたエラーがあります。

同じエラーの報告が少なかったので、エラー解消した対処法を共有します。

エラー文

git pushをトリガーにCircleCIがテストを実行するのですが、その前にデータベースを構築します。
そこでエラーが出ていました。

circleci/config.yml
# DBをセットアップ
- run:
    name: DBをセットアップ
    command: bin/rails db:schema:load --trace

※全文は下記

  #!/bin/bash -eo pipefail
bin/rails db:schema:load --trace
** Invoke db:schema:load (first_time)
** Invoke environment (first_time)
** Execute environment
** Invoke db:load_config (first_time)
** Execute db:load_config
** Invoke db:check_protected_environments (first_time)
** Invoke environment 
** Invoke db:load_config 
** Execute db:check_protected_environments
rails aborted!
PG::ConnectionBad: could not translate host name "db" to address: Name or service not known
/home/circleci/project/vendor/bundle/gems/pg-0.20.0/lib/pg.rb:56:in `initialize'
.
.  省略
.
bin/rails:4:in `require'
bin/rails:4:in `<main>'
Tasks: TOP => db:schema:load => db:check_protected_environments

Exited with code exit status 1
CircleCI received exit code 1

環境

Ruby : 2.6.3
Rails : 5.1.6
postgres : 12.2
CircleCI : 2.1
Docker-compose version: '3'

結論

database.ymlに、test環境でhost: localhostを追加することで解決しました。

config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

  username: postgres
  password: 
  host: db
  timeout: 5000

development:
  <<: *default
  database: haito_notice_development

test:
  <<: *default
  database: haito_notice_test
  host: localhost    # <<<<<追加<<<<<

production:
  <<: *default
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>

Dockerのimage上ではdbとしてDB構築していますので、develop環境やproduction環境ではhost: dbとする必要があります。

しかし私の場合は、CircleCIのDocker imageをtest環境かつhost: 127.0.0.1 (localhost)で構築しているため、これに合わせてあげる必要があったのではと理解しています。

CircleCI

基本、公式のドキュメントを元に記載していますが、
別のエラー対処のためにworkflowsやcommandsを現在は適用していないため参考程度に。

circleci/config.yml
version: 2.1
jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.3-stretch-node
        environment:
          BUNDLE_JOBS: 3
          BUNDLE_RETRY: 3
          BUNDLE_PATH: vendor/bundle
          PGHOST: 127.0.0.1
          PGUSER: postgres
          RAILS_ENV: test
      - image: circleci/postgres:12-alpine
        environment:
          POSTGRES_USER: postgres
          POSTGRES_DB: app_test
    steps:
      - checkout
      - restore_cache:
          name: 依存関係キャッシュを復元
          keys:
            - v1-dependencies-{{ checksum "Gemfile.lock" }}
            - v1-dependencies-
      - run:
          name: Bundler を指定
          command: bundle -v
      - run:
          name: バンドルをインストール
          command: bundle check || bundle install
      - save_cache:
          key: v1-dependencies-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - run:
          name: 静的コード解析を実行(RuboCop)
          command: bundle exec rubocop
      - run:
          name: DBの起動まで待機
          command: dockerize -wait tcp://localhost:5432 -timeout 1m
      - run:
          name: DBをセットアップ
          command: bin/rails db:schema:load --trace
          # ここでエラーが起こっていました!!!
      - run:
          name: テストを実行(RSpec)
          command: |
            bundle exec rspec --profile 10 \
                              --format RspecJunitFormatter \
                              --out test_results/rspec.xml \
                              --format progress \
                              $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
      - store_test_results:
          path: test_results

Docker

念の為記載します。
こちらもDocker公式のドキュメントを参考に作っています。

# Dockerfile

FROM ruby:2.6.3
RUN apt-get update -qq && \
    apt-get install -y nodejs \
                      postgresql-client

RUN mkdir /app
ENV APP_ROOT /app
WORKDIR $APP_ROOT

COPY ./Gemfile $APP_ROOT/Gemfile
COPY ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN bundle install
COPY ./ $APP_ROOT

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
docker-compose.yml
version: '3'
services:
  db:
    image: postgres:12-alpine
    environment:
      POSTGRES_HOST_AUTH_METHOD: 'trust'
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db

ご指摘やご不明な点などがございましたらお気軽にご連絡ください。

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

どこのページからでもリンクさせたい時

はじめに

この記事が、初めての投稿なので見にくかったらすみません。。
自分の備忘録も兼ねて、学んだことを書いていきます!

トップページからしか、リンク出来ない!!

フリマアプリの作成中のことです。

ヘッダーに、ユーザー新規登録ボタンを置いていました。
トップページでは、そのボタンから登録ページへ遷移できます。
ですが、商品検索ページや商品詳細ページでは、ボタンを押してもエラーになってしまいました!!image.png

絶対パスを相対パスに変更

結論から言うと、絶対パスを相対パスに変えたら解決しました。

絶対パス↓

header.html.haml
 %li.listsRight__item.listsRight__item--new
   = link_to "新規登録",  "users/sign_up"

相対パス↓

header.html.haml
 %li.listsRight__item.listsRight__item--new
   = link_to "新規登録",  "/users/sign_up"

"users" の前に、"/(スラッシュ)" を付けただけですね。

絶対パスになっていたため、トップページ(http://localhost:3000/) を基準にしてしか飛ぶ事ができませんでした。
それを相対パスにする事で、商品ページ(http://localhost:3000/items/) など、どのページからでも遷移できるようになりました!

最後に

絶対パス/相対パスは、ただのターミナルでのディレクトリ指定方法だと考えていました。
ですが、実際の挙動にも影響を与えることを知り、基本が一番大切だなと改めて思いました。

もし解釈が間違えていたら、コメントをお願いします!

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

日々のうんちをシェアするアプリを作る~その1~

うんちしぇあ。作ります

技術の勉強をかねて、何か作りたいなあ、、、なんか良いアイディアないかなあ、、、
そんな思いを日々持っていました。
しかし特に決まらず、ふと彼女に「なんかほしいアプリない?」って聞いてみました。
すると
「毎日何回うんちしたかシェアするアプリかなあ、、、、」

は!?
こいつ天才か!?

僕はそう思いました。
だから作ります。うんちしぇあ。

使う技術

僕はReactというものが書けます。少しですが。
だからNext.jsやってみたいなーっていう安直な理由でNext.js書きます。
あとRailsもかけるからAPI的に使います。

とまあ、明らかに技術力は低いので、間違っているところ・アドバイス・情報共有などあればバンバンください。絶対に受け入れます。一緒に成長していきましょう。女性ならプライベートでの勉強会も

早速作って行くよん

まずはサクッとNext.jsのチュートリアルを終わらせました。。。しかし!!良さが全然わからん。なんか英語だし。

はい。もう良いです。作りながら勉強します。

やっぱうんちしぇあ。だしうんちのコンポーネントからだよね。

pages/unchis/index.ts
// packages
import moment from "moment";

// types
import {IUnchi} from "../../types/unchi";

//components
import { Calendar } from "../components/Calendar";
import {useState} from "react";
import UnchiLists from "../components/UnchiLists";

const today: string = moment().format("YYYY-MM-DD").toString();

const Index = () => {
  const searchUnchi = (date: string): IUnchi => {
    const emptyUnchi: IUnchi = { date: date, count: 0 }
    return emptyUnchi;
  };

  const [unchi, setUnchi] = useState<IUnchi>(searchUnchi(today));
  const handleClick = (date: string) => {
    setUnchi(searchUnchi(date))
  };

  return (
    <>
      <h1>今日のうんち</h1>
      <Calendar onClick={handleClick} />
      <UnchiLists
        {...unchi}
      />
      <style>
        {`
          html {
            width: 100%;
            height: 100%;
          }

          body {
            width: 100%;
            height: 100%;
            position: relative;
          }
        `}
      </style>
    </>
  )
}

export default Index
pages/components/UnchiLists.tsx
import {IUnchi} from "../../types/unchi";
import moment from "moment";
import { Card } from "./Cards"

const UnchiLists = (unchi: IUnchi) => {
  return (
    <Card>
      {moment(unchi.date).format('M月D日')}のうんち
      <ul>
        <li>
          {unchi.count}
        </li>
      </ul>
    </Card>
  )
}

export default UnchiLists

Calendarはネットにあるコピペで使えるデザイン付きのやつを利用させてもらいました。

そして今こんな感じ
image.png

いきなりちょっと本気出しちゃったかな・・・

これで一旦日付に対してうんちの回数を確認できるようになったから次はうんちの登録画面かな。今回は特に難しいことや困ったことがなかったので技術についてあまり触れてませんが、詰まったところなどあれば適宜共有していこうかと思っています。

その2もお楽しみに!!

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

Dockerを学習するための記事一覧

Dockerを学習するための記事をまとめてみました

エラーの対策も記述してます。

Running bundle update will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.
ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 6

参考になる記事一覧

今回の動画ではインストール完了済みなので、この記事は飛ばしています。
DockerをMacにインストールする(更新: 2019/7/13)

今回実施する記事は下記になります。
DockerでRailsの環境構築

Mysqlの設定方法は下記が参考になります。
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)

下記で $ docker-compose run web bundle installの方法が記載されてました。
DockerでRailsのプロジェクトを立ち上げるまで

下記のエラーが出た場合

Running `bundle update` will rebuild your snapshot from scratch, using only
the gems in your Gemfile, which may resolve the conflict.
ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 6

bundle updateを実施します

ターミナル
$ docker-compose run web bundle update

docker-composeでよく使うコマンド(Ruby on Rails)

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

[HowTo]マッチングアプリ的な友達機能を実装してみる。

現在、個人アプリを作成しているのですが、その中の機能として「友達機能」を実装することにしました。
スクールではカリキュラムになかった内容であったため、自身で色々と調べて実装したので、備忘録として、
以下のように記事にさせていただきます。
皆様の実装に少しでも役立てていただければ、幸いです。

はじめに-機能に対する考え方

今回の「友達機能」実装にあたり、当初、「友達申請=>承認=>友達になる」というよくある流れでの実装を考えてました。
しかしながら、少し堅苦しい感じがしたので、もう少し気軽に友達申請を実現するために、最近流行っているマッチングアプリ的に「いいね」を友達申請の代わりに使用し、お互いに「いいね」されたら「友達」にするというやり方での実装を考えました。
以降、その考えをベースに実装をしております。

実装イメージ

今回の機能の実装イメージは以下となります。

友達申請(いいね)の動作イメージ

友達申請(いいね)以下のようにハートマークをクリックすると完了します。
この動作をお互いに行うことで”友達”として認定します。

demo

お互いにいいねがされると友達として認識されます。

demo

テーブル準備

まず、今回の友達機能を実装するにあたり"relationship"テーブルを準備いたします。

ターミナル
rails g model relationship

マイグレーションファイルは以下のように作成します。

migration_file
class CreateRelationships < ActiveRecord::Migration[5.2]
  def change
    create_table :relationships do |t|
      t.integer :follower_id
      t.integer :following_id

      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :following_id
    add_index :relationships, [:follower_id, :following_id], unique: true
  end
end

マイグレーションファイルへの記述完了次第、ターミナルでrails db:migrateをしておきましょう。

ターミナル
$ rails db:migrate

モデル編集

テーブルが作成できましたので、続いてモデルを編集していきます。
今回は、友達申請(いいね)する人を"following”、友達申請(いいね)される人を"follower"とし、
友達となっているかどうかを"matchers"メソッドを作って判別します。

User(一部)
class User < ApplicationRecord
  has_many :following_relationships, foreign_key: "follower_id", class_name: "Relationship", dependent: :destroy
  has_many :followings, through: :following_relationships
  has_many :follower_relationships, foreign_key: "following_id", class_name: "Relationship", dependent: :destroy
  has_many :followers, through: :follower_relationships

  def following?(other_user)
    following_relationships.find_by(following_id: other_user.id)
  end

  def follow!(other_user)
    following_relationships.create!(following_id: other_user.id)
  end

  def unfollow!(other_user)
    following_relationships.find_by(following_id: other_user.id).destroy
  end

#友達判定
  def matchers
    followings & followers
  end
end

Relationshipモデルについては、follower/followingをUserに帰属させます。

relationship
class Relationship < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :following, class_name: "User"
  validates :follower_id, presence: true
  validates :following_id, presence: true
end

以上でモデルに関しての編集は完了となります。

ビューの編集

今回の”友達申請(いいね)”に関してのビューを作成します。
(以下は”友達申請(いいね)”に関してのみの記述となりますので、必要に応じて肉付けしてください)

今回のポイントは以下の通りです。
- if文で既に友達申請(いいね)をしているか否かを見極めます。
- 既に友達申請(いいね)をしている場合は、ハートを赤くしておき、ハートを押すと未申請状態(白色)にします。
- 逆に未申請の場合は、ハートを白くしておき、ハートを押すと申請済み状態(赤色)にします。
- これらの処理をform_withで情報を飛ばすことで実装してます。

view(一部)
- if current_user.following?(@user)
 = form_with model: @relationship,url: relationship_path, method: :delete, remote: true  do |f|
  = button_tag type: 'submit', class: 'btn-liked',id: "unfollow_form" do
    %i.fas.fa-heart.fa-3x
- else
 = form_with model: @relationship, remote: true  do |f|
  %div= hidden_field_tag :following_id, @user.id
   = button_tag type: 'submit', class: 'btn-likes',id: "follow_form" do
    %i.far.fa-heart.fa-3x

コントローラ

上記にてビューも完成したので、そちらに合わせてRelationshipコントローラを編集していきます。
友達申請(いいね)をしたときは、”create"メソッドを使用し、
友達申請(いいね)を取り消すときは、"destroy"メソッドを使用してます。
今回、基本的に申請時・申請取り消し時にページ遷移の必要はなかったので、Userページにredirectしてます。

RelationshipsController
class RelationshipsController < ApplicationController

  def create
    current_user.following_relationships.create(create_params)
    redirect_to user_path(params[:following_id])
  end

  def destroy
    @user=current_user
    @relationship =  Relationship.where(following_id: params[:id],follower_id:@user.id)
    @relationship.destroy_all
    redirect_to user_path(params[:id])
  end

  private

  def create_params
    params.permit(:following_id)
  end
end

以上で、上記イメージの友達機能が実装できます!
今回はいいねボタンを友達申請ボタンの代わりに使用しましたが、viewを変更するだけで実装できます!
色々と試してみていただけますと幸いです。

参照

Railsでマッチング機能を作ってみる
https://qiita.com/Utr/items/da03bf4f23aba03da656

Ruby on Rails ~フォロー(友達申請)機能の実装(コードメモ)
https://qiita.com/wtb114/items/dbba4364871aacf520cd

以上となります。最後までご覧いただき、ありがとうございました!
今後も学習した事項に関してQiitaに投稿していきますので、よろしくお願いします!
記述に何か誤りなどございましたら、お手数ですが、ご連絡いただけますと幸いです。

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