20201116のRubyに関する記事は25件です。

エラーメッセージの重複をなくしたい

エラーメッセージの重複①

form_withメソッドを使って、
form.number_fieldで数値を入力させるフォームを作った。
正しい数値を入力すればエラーは起きないし、バリデーションを超える数値を入力しようとすると、そのタイミングでエラーが出る。
しかし、空のまま入力フォームを送ると、


○○を入力してください
○○は不正な値です
○○は一覧にありません


と、エラーが3つも出てしまう。
空を送信しただけで、3つもいらん!!!

解決方法

model
with_options presence: true do
    validates :grade, format: { with: /\A[0-9]+\z/, allow_blank: true}, inclusion: { in: 1..7, allow_blank: true }
    validates :class_number, format: { with: /\A[0-9]+\z/, allow_blank: true }, inclusion: { in: 1..10, allow_blank: true }
end

formatは正規表現のバリデーション。
inclusionは数値の範囲のバリデーション。

allow_blank: trueは値が空の場合はバリデーションを実行しないメソッド。
冗長だが、これで空のときにはformatとinclusionのバリデーションは反応しない。

改めてエラーメッセージを見ると、
「○○は不正な値です」はformatに反応していて、
「○○は一覧にありません」はinclusionに反応していることがわかる。

エラーメッセージの重複②

外部キーを使っている状態で、そこにpresence: trueのバリデーションをかけると、同じような内容で2つのエラーメッセージが出る。

解決方法

モデルに記述している、外部キーのバリデーションを消す。

【余談】エラーメッセージの順番を変えたい

順番はバリデーションとアソシエーションの記述位置を変える事で解消できる。記述の位置を変えるだけなので、特段問題が起こらないことが有難い。
これは、コードが上から読み込まれていることに起因すると考えられる。ちょっとしたことだけど、入力フォームの順に、エラーメッセージを出してあげたい。

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

deviseをRailsで扱う

まず、こちらではデバイス導入の一連の流れ(自分が行った)ことを復習として、また忘れないようこちらに記載させていただこうかと思います。
初学者なので間違ったことを伝えていた場合は、都度ご連絡お願い申し上げます。

deviseについて

まず、デバイスの概要ですが、こちらはユーザー管理機能を実装するためのGemです。
新規登録やログインログアウトなんかが簡単に実装できます。

deviseの導入

Gemfileを編集しましょう

gem 'devise'(一番最後の行に追記しましょう)

これで、ローカル、テスト、本番環境でデバイスが扱えます。

ローカルサーバーを再起動

Gemをインストールした後はターミナルで起動しているローカルサーバーを一度再起動しましょう。
インストールしたGemの反映のタイミングが、サーバー起動時のためです。

deviseの設定ファイルを作成

deviseを使用するためには、Gemのインストールに加え、devise専用のコマンドで設定ファイルを作成する必要があります。
こちらのコマンドでファイルを作りましょう

% rails g devise:install

以下のようなログが流れると成功!

      create  config/initializers/devise.rb
      create  config/locales/devise.en.yml
===============================================================================

Depending on your application's configuration some manual setup may be required:

  1. Ensure you have defined default url options in your environments files. Here
     is an example of default_url_options appropriate for a development environment
     in config/environments/development.rb:

       config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

     In production, :host should be set to the actual host of your application.

     * Required for all applications. *

  2. Ensure you have defined root_url to *something* in your config/routes.rb.
     For example:

       root to: "home#index"

     * Not required for API-only Applications *

  3. Ensure you have flash messages in app/views/layouts/application.html.erb.
     For example:

       <p class="notice"><%= notice %></p>
       <p class="alert"><%= alert %></p>

     * Not required for API-only Applications *

  4. You can copy Devise views (for customization) to your app by running:

       rails g devise:views

     * Not required *

===============================================================================

デバイスでUserモデルを作成

deviseを利用する際には、アカウントを作成するためのUserモデルを新しく作成する必要があります。
作成には通常のモデルの作成方法ではなく、deviseのモデル作成用コマンドでUserモデルを作成します。
下記のコマンドでモデルとそれに付随するマイグレーションファイル、ルーティングの設定をまとめて生成でき
routes.rbにはdeviseに関連するパスが追加されます。

 rails g devise user

以下のログにて成功

  invoke  active_record
      create    db/migrate/20200309082300_devise_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml
      insert    app/models/user.rb
       route  devise_for :users

また、routes.rbに以下のルーティングが自動的に追記されます。

Rails.application.routes.draw do
  devise_for :users
  root to: 'tweets#index'
  resources :tweets
end

devise_forは、ユーザー機能に必要な複数のルーティングを一度に生成してくれるdeviseのメソッド。

テーブルの作成

先ほどのコマンドでマイグレーションファイルも生成されたのでマイグレーションを行います。

rails db:migrate

こちらでテーブルの作成が完了しました。

テーブルの情報を変更(マイグレーション)したので、ローカルサーバーを起動時には、再起動しましょう。

deviceのビューファイルの作成

deviseでログイン機能を実装すると、ログイン/サインアップ画面が自動的に生成されますがビューファイルとしては生成されません。
これは、deviseのGem内に存在するビューファイルを読み込んでいるためです。

deviseのビューファイルに変更を加えるためには、deviseのコマンドを利用して、ビューファイルを生成する必要があります。

コマンドを実行してdevise用のビューを作成

% rails g devise:views

こちらで作成されたログイン画面のビュー、新規登録画面のビューファイルを編集して見た目を作っていきます。

こちらでdeviceの基礎的な導入は完了です。
簡単なものであればこちらで大丈夫かなと言う認識ですが、
他にdeviseで用いられる

rails g devise:contoroller users

があったりします。
そちらの使い方は、以下別の記事でまとめたいと思います。
(必要になる基準を理解していないため)

ストロングパラメーターの設置

サインアップ時に入力する情報はパラメーターとしてサーバーに送信されます。deviseを使わない通常のリクエストの場合は、コントローラーにストロングパラメーターを記述し、受け取れるパラメーターを制限していました。

deviseに関しても、同様にストロングパラメーターをコントローラーに記述します。しかし、deviseの処理を行うコントローラーはGem内に記述されているため、編集することができません。
また、deviseでログイン機能を実装した場合は、paramsの他に、paramsとは異なる形のパラメーターも受け取っています。

以上から、deviseのコントローラーにストロングパラメーターを反映する方法と、devise特有のパラメーターを取得する方法が、必要になります。

devise_parameter_sanitizerメソッド

deviseにおけるparamsのようなメソッドです。deviseのUserモデルに関わる「ログイン」「新規登録」などのリクエストからパラメーターを取得できます。

このメソッドとpermitメソッドを組み合わせることにより、deviseに定義されているストロングパラメーターに対し、自分で新しく追加したカラムも指定して含めることができます。

devise_parameter_sanitizerメソッドは、これまでのストロングパラメーターと同じく、新たに定義するプライベートメソッドの中で使用します。deviseの提供元では、新たに定義するメソッド名をconfigure_permitted_parametersと紹介していることから、慣習的にこのメソッド名で定義することが多いです。

private
def configure_permitted_parameters  # メソッド名は慣習
  # deviseのUserモデルにパラメーターを許可
  devise_parameter_sanitizer.permit(:deviseの処理名, keys: [:許可するキー])
end

deviseのpermitは、第一引数にdeviseの処理名、第二引数にkeysというキーに対し、配列でキーを指定することで、許可するパラメーターを追加します。

第一引数の処理名には、deviseですでに設定されているsign_in, sign_up, account_updateが使用でき、それぞれサインイン時やサインアップ時、アカウント情報更新時の処理に対応しています。

処理名   役割
:sign_in        サインイン(ログイン)の処理を行うとき
:sign_up        サインアップ(新規登録)の処理を行うとき
:account_update アカウント情報更新の処理を行うとき

これらの記述をapplication_controller.rbに記述します。

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  before_action :configure_permitted_parameters, if: :devise_controller?

  private
  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname])
  end
end

before_actionのifというオプションを使用している点に注目してください。
これは、値にメソッド名を指定することで、その戻り値がtrueであったときにのみ処理を実行するよう設定するものです。

今回は:devise_controller?というdeviseのヘルパーメソッド名を指定して、もしdeviseに関するコントローラーの処理であれば、そのときだけconfigure_permitted_parametersメソッドを実行するように設定しています。他のtweetsコントローラーなどでは処理は読み込まれても、実行まではされません。

その他のコマンドについて

rails g migrationコマンド

マイグレーションを生成するコマンドです。
マイグレーションはこれまで、rails g modelコマンドでモデルと一緒に生成されていましたが、すでに作成されたテーブルの内容を変更する際などに使用します。

このコマンドは、指定するファイルの名前によって、どのようなテーブル操作を行うかを自動で記述します。

rails g migration Addカラム名To追加先テーブル名 追加するカラム名:型とすることで、テーブルにカラムを追加する際に必要なコードが記述された状態で、マイグレーションが生成されます。

スネークケースとキャメルケース

スネークケースとキャメルケースは、それぞれ単語の区切り方を表したものです。

スネークケースは、単語の区切りをアンダースコアで表す
キャメルケースは、単語の区切りを大文字で表す
変数名や関数名の単語が連立する場合、単語を区切る方法がいくつかあります。
以下の表をみてください。

表記方法    説明  例
キャメルケース   先頭が小文字で、単語の区切りを大文字で表す adminUserCommentCreator
アッパーキャメルケース   キャメルケースの1つ。先頭から単語の区切りを大文字で表す  AdminUserCommentCreator
スネークケース   単語の区切りをアンダースコアで表す admin_user_comment_creator

Railsの慣習的な命名規則として、下記のように使い分けます。

命名の対象 慣習的な命名規則
クラス名    アッパーキャメルケース
メソッド名 スネークケース
変数名   スネークケース

自分でクラスやメソッドを作成する際には、これらの命名規則は守るようにしましょう。

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

アソシエーションの理解〜初心者の備忘録〜

アソシエーションの理解〜初心者の備忘録〜

1.アソシエーションとは?

 簡単に表現すると「各モデル同士を関連付ける」ということである。つまり、テーブル同士で関連付けておき、一方のモデルからもう一方のモデルにアクセスできるようにすることである。

例. 1人のuserが複数のコメントを所有するパターン(1対多の関係)
aso0.png
 →「Aさん」とAさんのコメントである「おはよう」、「こんばんは」を結びつける。
 →「Bさん」とBさんのコメントである「こんにちは」、「さようなら」を結びつける。

2.has_manyメソッドとbelongs_toメソッド

では、どのようにアソシエーションをコードで再現するのか示してみる。
上記の1人のuserが複数のコメントを所有するパターン(1対多の関係)で考えてみる。

(1) has_many

aso01.png

  ※上記の図はAさんAさんが所有するコメントの関係であるが、Bさんも上記の図のように考える。

#Userモデル  
class User < ApplicationRecord
 has_many :comments       #1人のuserが複数のcommentを所有するので複数形のcommentsになる。
end

(2) belongs_to

aso02.png
  ※上記の図は「おはよう」のコメントAさんに所有されている関係であるが、その他のコメントも上記の図のように考える。

#Commentモデル
class Comment < ApplicationRecord
 belongs_to :user      #1つcommentは1人のuserが所有するので単数形のuserになる。
end

3.さいごに

 アソシエーションの1対多における基本的な考え方を説明しました。今後も、多対多の関係など少し応用的な投稿にも継続的にしていきたいと思います。また、。誤り等ご指摘ありましたらコメントお願いします!

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

アソシエーション(1対多の関係)

アソシエーション(1対多の関係)

1.アソシエーションとは?

 簡単に表現すると「各モデル同士を関連付ける」ということである。つまり、テーブル同士で関連付けておき、一方のモデルからもう一方のモデルにアクセスできるようにすることである。

例. 1人のuserが複数のコメントを所有するパターン(1対多の関係)
aso0.png
 →「Aさん」とAさんのコメントである「おはよう」、「こんばんは」を結びつける。
 →「Bさん」とBさんのコメントである「こんにちは」、「さようなら」を結びつける。

2.has_manyメソッドとbelongs_toメソッド

では、どのようにアソシエーションをコードで再現するのか示してみる。
上記の1人のuserが複数のコメントを所有するパターン(1対多の関係)で考えてみる。

(1) has_many

aso01.png

  ※上記の図はAさんAさんが所有するコメントの関係であるが、Bさんも上記の図のように考える。

#Userモデル  
class User < ApplicationRecord
 has_many :comments       #1人のuserが複数のcommentを所有するので複数形のcommentsになる。
end

(2) belongs_to

aso02.png
  ※上記の図は「おはよう」のコメントAさんに所有されている関係であるが、その他のコメントも上記の図のように考える。

#Commentモデル
class Comment < ApplicationRecord
 belongs_to :user      #1つcommentは1人のuserが所有するので単数形のuserになる。
end

3.さいごに

 アソシエーションの1対多における基本的な考え方を説明しました。今後も、多対多の関係など少し応用的な投稿にも継続的にしていきたいと思います。また、。誤り等ご指摘ありましたらコメントお願いします!

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

【RSpec】WebMockを利用して外部APIを利用したロジックのテストをする

【Ruby】faradayを利用した外部API連携クラスの作成手順の記事で、Faradayを利用した外部APIとの連携方法について紹介しました。

外部APIを利用したロジックのテストコードでは、実際のAPIを利用するのではなくHTTPリクエストをスタブ化するのが一般的です。
Railsではwebmockというgemを利用することでHTTPリクエストのスタブ化ができます。

今回はWebMockを利用して、外部APIと連携したロジックのテストコードを作成する方法について紹介します。

テストコードの対象となるクラス

今回はQiitaClientAPIというQiita APIと連携するクラスのテストコードを作成します。
クラスにはget_itemsという記事一覧をQiitaから取得するメソッドが実装されています。

具体的なソースコードは以下の通りです。

lib/qiita_api_client.rb
class QiitaApiClient
  class HTTPError < StandardError
    def initialize(message)
      super "connection failed: #{message}"
    end
  end

  class << self
    def connection
      Faraday::Connection.new('https://qiita.com') do |builder|
        builder.authorization :Bearer, "#{Rails.application.credentials.qiita[:token]}"
        builder.request  :url_encoded # リクエストパラメータを URL エンコードする
        builder.response :logger # レスポンスを標準出力する
        builder.adapter Faraday.default_adapter # アダプターの選択。デフォルトはNet::HTTP
        builder.response :json, :content_type => "application/json" # レスポンスボディをJSONパースする
      end
    end

    def get_items
      begin
        response = connection.get(
          '/api/v2/items'
        )
        response.body
      rescue Faraday::ConnectionFailed => e
        raise QiitaApiClient::HTTPError.new(e.message)
      end
    end
  end
end

下準備

テストコードを実行するための準備を行ます。

RSpecのセットアップ

今回はテスティングフレームワークにRSpecを利用します。
rspec-railsをGemfileに追加します。

Gemfile
group :development, :test do
  gem 'rspec-rails'
end

gemのインストールとRSpecのセットアップを行ます。

$ bundle install
$ rails generate rspec:install

webmockのインストール

webmockをGemfileに追加し、インストールします。

Gemfile
gem 'webmock'
$ bundle install

テストコードの実装

以下ではWebMockを利用したテストコードの記述方法について紹介します。

正常系のテスト

正常系のテストコードは以下の通りです。

spec/lib/qiita_api_client_spec.rb
require 'rails_helper'

describe 'QiitaApiClient' do
  before do
    WebMock.enable! # WebMockの有効化
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end
  describe '.get_items' do
    let(:response_body) { [{ "title": "test" }].to_json }
    before do
      WebMock.stub_request(:get, "https://qiita.com/api/v2/items").
        with(
          headers: {'Authorization' => "Bearer 123"}
        ).
        to_return(
          body: response_body,
          status: 200,
          headers: { 'Content-Type' => 'application/json' }
        )
    end
    it 'データが取得できること' do
      expect(QiitaApiClient.get_items).to eq(JSON.parse(response_body))
    end
  end
end

WebMock.enable!でWebMockを有効化し、WebMock.stub_requestの箇所でHTTPリクエストのスタブを作成しています。

WebMockを利用することで「どういったリクエストに対して、どういったレスポンスを返す」という挙動が記述できます。これにより、外部API経由でデータのやりとりを行う状況が擬似的に作成できます。

なおbeforeに記載されているallow(Rails.application.credentials)の箇所はアプリケーションコードに記載されているRails.application.credentials.qiita[:token]に対してダミー値をセットするためのものです。

異常系(例外発生)のテスト

アプリケーションコードでは外部APIと正常に通信できなかった場合、エラーメッセージを添えてQiitaApiClient::HTTPErrorという例外を発生させています。

例外が発生したことを確認する異常系のテストコードは以下の通りです。

spec/lib/qiita_api_client_spec.rb
require 'rails_helper'

describe 'QiitaApiClient' do

  before do
    WebMock.enable!
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end

  describe '.get_items' do
    let(:response_body) { { "message": "error_message", "type": "error_type" }.to_json }
    before do
      WebMock.stub_request(:get, "https://qiita.com/api/v2/items").
        to_raise(Faraday::ConnectionFailed.new("some error"))
    end
    it '例外が発生すること' do
      # 例外のテストはexpect()ではなくexpect{}なので注意
      expect{QiitaApiClient.get_items}.to raise_error(QiitaApiClient::HTTPError, "connection failed: some error")
    end
  end
end

WebMockのto_raiseで例外が発生するスタブを作成し、expectのraise_errorで例外が発生していることを確認しています。

最終的なテストコード

今回紹介した正常系と異常系をまとめた最終的なテストコードは以下のようになります。

spec/lib/qiita_api_client_spec.rb
require 'rails_helper'

describe 'QiitaApiClient' do
  let(:qiita_base_uri) { 'https://qiita.com/api/v2' }
  before do
    WebMock.enable!
    allow(Rails.application.credentials).to receive(:qiita).and_return({token: '123'})
  end

  describe '.get_items' do
    subject { QiitaApiClient.get_items }
    context '成功' do
      let(:response_body) { [{ "title": "test" }].to_json }
      before do
        WebMock.stub_request(:get, "#{qiita_base_uri}/items").
          with(
            headers: {'Authorization' => "Bearer 123"}
          ).
          to_return(
            body: response_body,
            status: 200,
            headers: { 'Content-Type' => 'application/json' }
          )
      end
      it 'データが取得できること' do
        expect(subject).to eq(JSON.parse(response_body))
      end
    end
    context '失敗' do
      before do
        WebMock.stub_request(:get, "#{qiita_base_uri}/items").
          to_raise(Faraday::ConnectionFailed.new("some error"))
      end
      it '例外が発生すること' do
        expect{subject}.to raise_error(QiitaApiClient::HTTPError, "connection failed: some error")
      end
    end
  end
end

まとめ

WebMockを利用して、外部APIと連携したロジックのテストコードを作成する方法について紹介します。

WebMockを利用したテストコードのフローは以下のようになります。

  1. Webmock.enable!でWebMockを有効化する
  2. WebMock.stub_requestでHTTPリクエストのスタブを作成する
  3. 外部APIを利用するメソッドを実行し、実行結果の検証を行う

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

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

Webデザイン 色のイメージ

はじめに

現在オリジナルアプリを作成中です。トップページのフロント実装時に色合いの考え方を調べました。忘れても思い出せるよう書き記します。

開発環境
ruby 2.6.5
Rails 6.0.3.4

目次

1.色の効果
2.オリジナルアプリの配色

1.色の効果

Webデザインで使う色は目的別で選ぶ必要がある。Webサイトを利用するユーザー目線に立つことが重要である。

赤系

熱い、生命力、警告

オレンジ系

近親感、陽気

黄系

好奇心、協力

緑系

自然、リラックス

青系

冷たい、静けさ、誠実

紫系

後期、威厳

ピンク系

柔らかい、可愛い

茶色系

安定、信頼、歴史

2.オリジナルアプリの配色

オリジナルアプリは大きく分けて下記2つの機能を有する。トップページからそれぞれリンク先に遷移できるように実装した。
・ユーザーへの掃除提案
・掃除したデスク画像の共有

「ユーザーへの掃除提案」リンクを赤系にした。これは清掃を促す(警告)するためである。「掃除したデスク画像の共有」は青にした。これは清掃後のデスクを想定したため、清潔感をイメージする青を選択した。
image.png

参考
1冊ですべて身につくHTML-CSSとWebデザイン入門講座
Qiitaのマークダウンで色をつける方法[140色]

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

RailsとClean Architectureに関する考察

Railsのアプリケーションの設計について考える機会があったのでその過程で考えたこととをまとめておきます。

背景

Railsをメインで使用している企業で新規のWebアプリケーションを0ベースで作ることになった。
その企業はRailsでかなり大規模なアプリを運用しており、Rails自体の知見は十分。

一方で伝統的なRailsでの開発手法に大規模開発の限界も感じており、この機会に設計のPrcaticeを模索したいという要望もあります。

筆者の方はアプリケーションの設計にはそれなりに自信がありますが、普段使用している言語はScalaやNodeJS(TypeScript)がメインです。

Railsでの開発は何度もやっており、特に開発に不自由することはありませんが、フレームワークの細かい癖などはあまり把握していません。

今回の要件では元々のチームのスキルを活かすために、Rails自体を別の言語やフレームワークに置き換えることは考えません。

Railsの良さを活かしつつモダンな設計手法を取り入れるにはどうすれば良いかを考察するのがこの文書の主目的になります。

基本設計方針

Clean Architectureを下敷きにします。

Clean Architecture(以下CA)はここ数年に急速に人気の出てきた設計手法で、日本でも日本語版の書籍発行をきっかけ多くのエンジニアがそれに言及するようになりました。

筆者自身も2、3、CAをベースとした開発を経験(自分で設計したものも、人の設計に乗っかって開発したものもあります)しており、このやり方はとてもイケてると感じています。

一方でCAを既存のWebアプリケーションフレームワークに載せる際の課題もいくつか感じており、そこに考察を加えて自分なりの勝ちパターンを見つけたい、と考えたこともこれをわざわざ文章化しようと思った動機の一つです。

ちなみに今の今、この節を書いている時点ではまだモヤモヤしているポイントがいくつかあるので、それらが文章化することによって整理されると良いなという願いもあります。

CAとRailsの相性の悪さ

さて、RailsにCAのエッセンスを適用とするにはどうすれば良いのか?という命題についてこうして考え始めたわけですが、いくらもしないうちにその相性の悪さに躓くことになります。

これはよく考えると当たりまえの話です。

何故なら、Clean Architectureは「使用するフレームワークに関わらず通用する普遍的な設計手法」を目指したものであり、一方でRailsなどのアプリケーションフレームワークは「フレームワークに依存させることによって開発コストを最小にする」ことを目指したものだからです。

つまり、最初から目指している方向が真逆なので相性が良いはずがありません。

特にRailsは「フレームワークに依存させることによって開発コストを最小にする」ことを極限まで追求したような化け物フレームワークなので相性は最悪と言えます。

CAではこの問題に対する解として、同心円の内側(EntityとUsecase)ではフレームワークの機能を使わないで実装すべし、としています。

実際これまで関わったプロジェクトでは(完全ではないにしても)そういうアプローチを取っていました。

Railsでも同じアプローチを取ることはできますが、そうするとRailsの良さを完全にスポイルすることになるので、それが良いとは正直言い切れません。

実際、Rails Wayでやれば3行で済むような処理がCAの作法にのっとると複数ファイルにまたがって数十行のコードが必要になったりすることもあるので、それがコストに見合っているかどうかは疑問です。

ていうか、自分ひとりで開発するのであれば間違いなくRails Wayを選択すると思うんですよね、この場合。。。。

おそらく世のRailsプログラマも同じように感じる人は多いはずです。
予想ですが完全にCA準拠したやりかたはRailsエンジニアには受け入れられない可能性が高い。

つまり、この場合Railsの良さを残しつつCAのエッセンスを取り入れる新しい方法を考える必要がある、というのがこの節の結論です。
(CA準拠のやり方を採用するのであればそれを納得させるだけの強い理由が必要と思いますが、ちょっと思いつきません。前述の通り目指しているところが違うので、各々の軸における優位性を並べることはできてもそれは一種の宗教戦争のようなもので、他方を納得させることができる気はしないです。また、個人的にもどちらか一方が正しくて他方が間違っているとは思いません。)

CA EntityとActiveRecord

RailsといえばActiveRecordです。
Railsの利便性はActiveRecordの存在によるところが大きく、ActiveRecordを使わないのであればRailsを使う意味がないと言っても過言ではありません。

CAに則るならEntityはActiveRecordとは無関係な形で再定義するべきですが、RailsにおいてはここでActiveRecordを排除するのは得策ではないと思っています。

動的型付け言語であるRubyではinterfaceは作れず、DuckTypingだよりでObjectを扱うだけなので、単純なモデルの場合は再定義したところで、結局ActiveRecordと交換可能なオブジェクトになる可能性が高い気がするんですよね。。。

メソッドに引数として渡すオブジェクトがCAのEntityであってもActiveRecordであっても動いて、かつそれを制限する方法もないのであれば、再定義が徒労感が伴うだけの苦痛な作業になってしまうことを危惧します。

であれば、この際単純なテーブルマッピングで済むようなEntityはActiveRecordのモデルをそのまま使うのもアリではないかと。

ただし、ActiveRecordに直接ビジネスロジックに関わるメソッドを定義するのが良いとは思わないので、そうしたメソッド定義が必要な場合は再定義します。

また、例えば請求書のようなDB上は複数のテーブルにまたがって保存されるけれどビジネス上のエンティティとしては不可分なものも再定義した方が良いと思っています。

再定義が必要なものとそうでないものの境界がイマイチあいまいですが、とりあえずは最初はActiveRecordを直接使っても良くて、必要を感じた時に再定義するというルールでスタートしてもまぁ良いかと思っています。(これは開始時点のプロジェクトメンバーが少ないことを踏まえての選択です。最初からメンバーの多いプロジェクトの場合はもう少し慎重に考えた方が良いとも思いますが、そんな大規模のプロジェクトとはそもそも縁がないのでこれ以上の考察はここではしません。)

再定義する場合も完全にActiveRecordを排除する必要はなく、ラップする形で定義すれば十分です。
例えばこんな感じ

# 請求書
class Bill

  initialize(ar_model)
    @ar_model = ar_model
  end

  def corp_name
    @ar_model.corp_name
  end

  # 請求明細
  def bill_details
    # Relationを表に出したくないのでto_aする
    # 必要ならコンストラクタで再定義したEntityにmapする用に修正する
    @ar_model.bill_details.to_a 
  end
end

問題点はActiveRecordに山盛りある副作用を伴うメソッド(saveとかupdateなど)が、どこからでも呼び出せてしまうことですが、これもとりあえずは「repository以外のファイルからは副作用のあるメソッドの使用を禁止する」をルール化していおけば十分な気がしています。(これも少人数ならではの選択です。実際にやるかどうかは別としてこのルールはLintでチェックすることも可能だと思います。)

正直ここはやってるうちに気が変わるかも、と思わないでもないですがとりあえずスタートはこんな感じで。(少人数以下略)

ディレクトリ構成

RailsではないプロジェクトでCA構成をやった時のディレクトリ構成は以下のような感じでした。

- domain
  - <ドメイン名1>
    - <Entityファイル>
    - <Entityファイル>
  - <ドメイン名2>
    - <Entityファイル>
    - <Entityファイル>
  - ...
- repository
  - <ドメイン名1>
    - <Repositoryファイル>
  - <ドメイン名2>
    - ...
- usecase
  - <ドメイン名1>
    - <Usecaseファイル>
    - <Usecaseファイル>
  - <ドメイン名2>
    - ...

ドメイン名のところには「User」とか「Order」のようなアプリケーションが扱う領域を大きく区切った名前が入ります。

Scalaのプロジェクトの場合はサブプロジェクトを使うことによって、domain層のファイルが絶対にusecaseやrepositoryに依存できないようにもできるのでこの構成には意義があります。

では、Railsの場合はどうか?

残念ながらRailsではそのような依存関係の制限はできないように思います。

問題点はもう一つあって、Railsではautoloadに沿った命名が強く推奨されているので上記構成の場合、Module名+Class名が

  • Domain1::Entity1
  • Domain1::Domain1Repository
  • Domain1::XXXXUsecase

のようにEntity, Repository, Usecaseのすべてが同じ名前空間になってしまいます。
これは非常によろしくない。

もう一段、階層をさげて、

  • Entity::Domain1::Entity1
  • Repository::Domain1::Domain1Repository
  • Usecase::Domain1::XXXXUsecase

としても良いですが、日本語話者的には主たる修飾が前に来る方が自然なので、

  • Domain1::Entity::Entity1
  • Domain1::Repository::Domain1Repository
  • Domain1::Usecase::XXXXUsecase

の方がしっくりきます。

なので、これを踏まえてディレクトリ構成は以下のようにしてはどうかと思っています。

- app
  - domain
    - <ドメイン名1>
      - entity
        - <Entityファイル>
      - repository
        - <Repositoryファイル>
      - usecase
        - <Usecaseファイル>
    - <ドメイン名2>

この方針を取ると仮定して、事前に思いついた問題点と回答は以下です。

「User」とか「Order」のようなありふれた名前がトップレベルのModule名になる。

扱う領域名がトップレベルに現れること自体はわかりやすいのでむしろ良いことだと思っています。

ただ、Railsでは慣習的にActiveRecordをmodels直下に定義することが多く、「User」や「Order」などは思いっきり名前がかぶりそうなので、ActiveRecord側を「AR::User」とするなど、module配下に置く必要があります。

ドメイン定義の方を「Domain::User::Entity::User」とする、という案もあるんですが、「DomainのUser」ではなく、「DomainがUser」なので、修飾としてしっくりこず冗長な気がしています。(個人的にはこうした日本語的にしっくりくる、みたいな感覚は大事にしています。外国人メンバがいる場合はまた別ですが。)

あと、ActiveRecordのモデルがそれに属することがわかる命名であることも優位があると思うので、可能であればActiveRecord側の命名を変える方が良いです。

ちなみに前節で「ActiveRecordを直接DomainのEntityとして使うのもアリ」と書きましたが、ActiveRecord自体は一覧性を重視して従来どおりmodels以下に置くつもりです。
(これを書いている時点ですでに、やっぱりDomain/Entityとして薄いラッパーを必ず定義するようにした方が良いのでは。。。と思い始めてますが、そこは作りながら考えます。)

Usecaseは複数のドメインオブジェクトをまたがって扱うことがある

そうですね。別にいいんじゃないでしょうか。

EntityとRepositoryはそのドメイン内で完結するべきと思いますが、Usecaseは複数ドメインを扱っても特に問題はないと思います。

ファイルの置き場所としてしっくりこないなら、EntityやRepsitoryの無い別ドメインを定義してUsecaseだけを置いても良いです。

蛇足ですが、Repositoryでは同じドメインのEntityモジュールをincludeしても良いと思っています。
ですが、Usecaseはダメです。(というか、コンストラクタで各種Repositoryを渡すならばModule名修飾の名前が出てくる場面はほぼ無いとも思います。)

簡便化

以下、CAの教科書的ではないですが開発速度をあげるためにやろうと思っている内容です。

IDでのEntity取得はドメインModuleのmodule functionにして良い

こんな感じ

module Order extend self

  order_repository
    @order_repository ||= Order::Repository::OrderRepository.new
  end

  module_function
  def get_order_by_id(order_id)
    order_repository.get(order_id)
  end
end

ID指定でのEntity取得は、デバッグの際にも使用したい場面があるので必要ならそのショートカットを作っても良いというルールです。(すべてのEntityに対して必ず作るということではありません。)

routesのGET /XXXXs/:idに相当する処理が、これで間に合うのであればUsecaseを定義する必要すらないと思いますが、実際にはほとんどの場合Permissionのチェック(上記Orderの場合は自分のOrder以外は不可視)が必要になると思うので、その場合はUsecaseが必要です。

usecaseをドメインModuleのmodule functionとして定義する

一般にUsecaseはそのコンストラクタで各種Repositoryを引数に取ります。
これはテスト時にRepositoryを差し替えられないとテストが書きにくいからです。

しかし、ControllerからUsecaseを使う際に毎回Repositoryを明示するのは面倒なので、ドメインModuleにそのショートカットを作ります。

こんな感じ

module Order extend self

  order_repository
    @order_repository ||= Order::Repository::OrderRepository.new
  end

  module_function
  def create_order_usecase
    Order::Usecase::CreateOrder.new(
      order_repository
    )
  end
end

使う側は

  val res = Order.create_order_usecase.run(...)

となって、やりたい内容が明快です。

Usecase側でデフォルト引数を指定するでも良いと思いますが、そこはまぁ好みの問題です。

テスト

正直Railsでのテストの知見はあまりなく、手探り状態です。
また、工数的にもフルでテストを書く余裕はなく、必要なところから部分的に書いていく、という形になると思っています。

なので基本的には、

  • ロジックのあるEntity
  • Usecase

を中心にテストが書ける形になってさえいれば、実際にテストを書くのは後回しになっても良いと割り切ります。
Repositoryが単純なActiveRecordのラッパーであるならそのテストは優先度最下位で良いです。
(外部サービスに依存するものであれば別途テストやMockが必要です。)

Entityのテストは特に問題になるところはないでしょう。
Usecaseの方は物によってはテストを書くのは面倒なケースがありますが、一般にUsecaseは

  • validate - 入力パラメータのValidation
  • collect - 必要なEntityをRepositoryから収集
  • execute - Usecaseで行いたい処理の実行

の3ステップからなることが多く、テストしたいのはexecuteの部分だけだったりもするので必要に応じてexecuteの部分だけテストできるようになっていれば良いと思います。

こんな感じ

class Domain1::Usecase::HogeHogeUsecase

  initialize(domain1_repository)
    @domain1_repository = domain1_repository
  end

  def run(params)
    error = validate(params)
    return ErrorCase.new(error) if error

    [v1, v2, error] = collect(params)
    return ErrorCase.new(error) if error

    return execute(v1, v2)
  end

  def validate(params)
    ...
  end

  def collect(params)
    ...
  end

  def execute(v1, v2)
    ...
  end
end

一応これで永続化されたデータの状態を気にせずテストを書けるようになるはずです。

まとめ

以上、開発初期に考えたことをまとめてみました。
文章化したことによって自分の中での整理が進んだことが最大の収穫ですかね。

自分のRails力は決して高くはないし、賛否の分かれる部分もあると思います。
でも、まぁそんなのは割とどうでも良いです。現場での設計は万人受けする必要はないのです。

ただチームのメンバにこのプロジェクトは「こういう方針で作っている」という思想が共有され、一貫性が保たれていればそれで十分だと思います。

正解を求めるのは学問の分野の仕事で、現場のエンジニアがそれを求めてしまうとドツボにはまるだけだとも思います。

すべてを肯定的に捉えることは無理でも、何かしら参考になる点があれば幸いです。

作っている過程で、何か変える部分がでてくればまた追記します。
(何故なら、この文書こそがチームメンバに「こういう方針で作っている」を共有するためのドキュメントそのものだからです。)

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

オブジェクト指向って結局なんなん?

とりあえずオブジェクト指向ってなんなん?

モノ(オブジェクト)の組み合わせでプログラムを動かしていくという考え方です。
アプリケーションは部品から構成されており、部品同士が相互に作用し合うことで全体が動く(歯車が噛み合う)ようなイメージです。

部品が「オブジェクト」であり、相互作用はオブジェクト間で受け渡される「メッセージ」であると考え、
正しいメッセージを正しいオブジェクトへ届けるには『メッセージの送り手が受け手のことを知っている』必要があります。
そして、相手のことを知っている、つまり2つのオブジェクト間に依存関係が発生するということです。
この「依存関係」を管理することこそオブジェクト指向設計であるということです。
087582.jpg
キャッチボールでいうのであれば、大人と子供はお互いの位置を理解しており、
どのくらいの力加減で投げれば相手がキャッチできるかを知っているということになります。(イメージ)

なんでオブジェクト指向が良いん?

アプリケーションは事あるごとに変更が施されます。
『メッセージの送り手が受け手のことを知っている』という依存関係において変更というものは厄介なものです。

依存関係って?

あなたが複数のサービスにキャリアメールでアカウント登録している場合、
キャリアメールアドレスと各サービスは依存関係であるといえます。

例えば、あなたがケータイのキャリアを変えるとき、電話番号はそのままで変更できるので便利ですが、
キャリアメールは使えなくなってしまいますよね?この変更によってあなたはどのような修正が必要でしょうか?

→キャリアのメールアドレスを使用しているサービス全てにログインしメールアドレスの変更

考えただけでもゾッとしますよね。そしてもっと最悪なのは、あるサービスでメールアドレスの変更し忘れて2年後、passwordも忘れてしまった。passwordを再設定したい、そんな時に再設定用のメールが届くのは、、、
見ることのできない前のメールアドレス…

こんな最悪の事態を防ぐにはどうすれば良かったのか、
・そもそもキャリアメールは使わず、gmailにしとけば良かった。
・ある転送サービス(妄想)にメールアドレスを設定し、各サービスはそこからメールアドレスを取得するような仕組みにしておけばよかった。

ということですよね?これら両方に言えること
それは、変更があることを考慮した設定にしておけばよかったということです。

つまり、転送サービスのメールアドレス1箇所を変更するだけで各サービスのアカウントのメールアドレスが変更されるように、オブジェクト指向のポイントは変更時のことを考えた設計であれということです。

あとでコードとかも追加しますね

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

第5回

Ubuntu-20.04.1 ruby-2.7.1p83

やったこと

  • README.orgを編集してemacsに慣れる
  • gitの操作を知る
  • 入出力を取り扱う(puts)

入出力を取り扱う(puts)

工事中…


  • source ~/MasahiroOba/grad_members_20f/members/MasahiroOba/chapter5.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第5回

Ubuntu-20.04.1 ruby-2.7.1p83

やったこと

  • README.orgを編集してemacsに慣れる
  • gitの操作を知る
  • 入出力を取り扱う(puts,ARGV)

putsと出力メソッド

rubyには出力を行うmethodがいろいろあるらしいので色々試してみた

method 出力結果
print 出力後改行されない
puts 普通の出力
p ダブルクォーテーションが付く
pp ダブルクォーテーションが付く
printf 出力後改行されない

pとかppでダブルクォーテーションが付くのは型情報を一緒に出力してるかららしく、数字だと何もつかない

ARGV

ARGV…配列(argument vector=引数の配列)
これを使って入力を受け付けるプログラムを作ってみようというわけで

name = ARGV[0]
puts "Hello #{name}"

というプログラムを書いて

> ruby hello_name.rb Oba

と打ち込むと

Hello Oba

と帰ってくる

参考記事


  • source ~/MasahiroOba/grad_members_20f/members/MasahiroOba/chapter5.org
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

この問題地味に難しい...(Ruby)

0から9までのカウント(Ruby編)

問題

0から9までを数えるカウンタを考えます。

0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9

このカウンタの拡張として、任意の数から始められるカウンタを考えました。

2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 0 → 1
(2から始める例、カウンタは9まで進んだら0に戻ります)

入力から数字nを受け取り、nからカウンタをスタートさせて、

カウンタの値を順番に10個出力するプログラムを実装してください。

入力される値

入力は以下のフォーマットで与えられます

n

・nはカウンタの最初の値

期待する出力

nから始まるカウンタの10個の値を、順番に改行区切りで出力してください。

入力例1

0

出力例1

0
1
2
3
4
5
6
7
8
9

入力例2

2

出力例2

2
3
4
5
6
7
8
9
0
1

私の回答(結局解けなかった)

n = 2
# nが10まで進んだら0にリセットするそれ以外はパスして10個出力
for i in n..9 do
if n == 9
   n = 0
   puts i
   n += 1
end
end

色々弄りすぎて多分2時間くらいやってたので最終的には諦めたコードになります笑

なんせ 10にいこうとしたら0に戻ってリスタートさせる的なコードが必要なのですが、そのコードを書こうともがいたのが n ==9 の部分です。こういうところがプログラミング脳が足りてないところなんですよね。。。そしてなんと言ってもググる際の言語化がこういうときかなり難しい。

以上!

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

【Ruby On Rails】ActionView::Template::Error (ActiveStorage::InvariableError)の原因と対処法(仮説・検証)

備忘録です。
ActionView::Template::Error (ActiveStorage::InvariableError)の原因と、その対処法について仮説・検証しました。

エラー背景・経緯

デプロイしたアプリの動作確認を行うことにしました。今回は画像がアップロードされているかどうかを、簡易的に手動で確認したいため、単体テストや結合テストを行わずに動作確認を行うことにしました。

今回は周りの友達に動作確認テストの協力をしてもらいました。
協力内容としては、画像を投稿できるフォームにファイルを添付して、投稿したファイルがビューに表示できるかどうかを確認して欲しいと依頼しました。

エラー内容

協力者である友達から、画像ファイルを添付したと報告をもらったので、デプロイしたアプリの詳細画面を確認しました。すると、"We're sorry but something went wrong"と表示されてしまいました。

まずは、ターミナルでログとそのエラー内容を確認しました。(今回はheroku)

% heroku logs --tail

(中略)

ActionView::Template::Error (ActiveStorage::InvariableError)
    34: <div>
    35:   画像
    36: </div>
    37: <%= image_tag @question.image.variant(resize: '200x200'), class: 'question-image' if @question.image.attached? %>
    38: <% end %>
    39:
    40: <%= render 'shared/error_messages', model: @comment %>
app/views/questions/show.html.erb:37

調べたこと

エラー内容に関する記事を調べてみました。このエラーを解決する鍵は「variable?メソッドを使って、blobが変数であるかどうかを区別する」とのことでした。
ここで言う変数は添付される画像ということになりますが、正しい変数(画像)が与えられているかが鍵となることがわかりました。

「そもそもvariable?とはなんぞや」

ということでネットで調べてみると、variable?を使ってTrueの時のみ画像を呼び出せる様にする時に使うとのこと。そして、ActiveStorage.variable_content_typesメソッドと組み合わせることで、指定した拡張子のみの画像を添付できる様に制限をかけることがわかりました。

仮説

そもそも、友達が投稿したファイルは画像ファイルではない、もしくはactive_storageがサポートしている拡張子の画像ではない。という仮説を立てました。

行ったこと

友達に画像以外を載せた可能性を聞いてみました。すると、「画像も他の拡張子のファイルも混在しているファイルの中からテキトーに確認せずに添付した」と回答が返ってきました。つまり、画像以外が添付されている可能性大です。

友達から聞いた内容と、自分で立てた仮説をもとに、自分で誤った拡張子のファイルを添付し保存ができるかどうか、詳細ページはエラーになるかどうかを試しました。

結果

友達の時と同様に、全く関係のない拡張子でデータの保存ができました。そして、詳細ページに遷移すると同様に"We're sorry but something went wrong"という表示になりました。

つまり「正しい拡張子のファイルが送信されていなかった」ということがわかりました。

今後は特定の拡張子以外の保存ははじけるように、ActiveStorage.variable_content_typesメソッド等を使用して、今回の様なエラーが起きない様にします。

補足

テストをしていく段階で、画像の拡張子が.heicだとアップロードできない場合がありました(できる場合もありましたが、原因は不明です)。
参考記事2番目の記事に記載されている通りですが、現在は.heic拡張子のファイルのサポートはしていないみたいですので、避けた方が吉 or heicだけをはじく記述をした方が良いかもしれないですね。

英語の記事読むのはかなり重要ですね

参考記事

https://edgeapi.rubyonrails.org/classes/ActiveStorage/InvariableError.html
https://stackoverflow.com/questions/60686249/activestorageinvariableerror-in-homeindex
https://qiita.com/xusaku_/items/36a61e35b6cd863bbf9d

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

日本時刻設定と表示方法まとめ

はじめに

自分用の備忘録
忘れがちなのでまとめてみる

手順

日本時刻設定

アプリケーションファイル編集

config/application.rb
# 中略

module App
  class Application < Rails::Application
    # Initialize configuration defaults for originally generated Rails version.
    config.load_defaults 6.0 

    # 日本語の言語設定
    config.i18n.default_locale = :ja
    # 日本時刻の設定
    config.time_zone = 'Tokyo'

    # 中略

時刻のフォーマットを設定

config/locales/ja.yml => 作成
config/locales/ja.yml
ja:
  time:
    formats:
      default: "%Y/%m/%d %H:%M:%S"
      # あくまでデフォルトなのでよく使いそうなパターンで設定

時刻の表示

l(エル)メソッド

sample.rb
l sample.created_at => デフォルトの設定で表示

表示方法を指定

sample.rb
sample.created_at.strftime('20%y年%m月') => "20xx年yy月"

おわりに

l(エル)メソッドって響きがかっこいいと思うのは私だけでしょうか

✔︎

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

「(二桁整数)/10%10」で10の位の数字が得られることについて

10の位

二桁整数を29とすると
29/10とした場合、小数点が切り捨てられることを利用する。
つまり29/10の答えが2.9ではなく2と出力される。
次の計算として2%10を行う。10で割れないのでそのまま余りは2。
つまり10の位は2。

100の位

同じように100の位の数を取ってこようとすると、
378/100%10で得られる。
378/100=3
3%10は3。

それ以降も
1の位 整数/1%10
10の位 整数/10%10
100の位 整数/100%10
1000の位 整数/1000%10


と使えそうです。

%10いるのか?

結論としては一番大きい桁の数字を持ってきたい時には不要。
なぜならすでに一桁の数字になっているから。

23の10の位を持ってくる
→23/10=2
で完了

123の10の位を持ってくる
123/10=12
12%10=2
で完了

以上

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

Ruby on Railsでのテーブル作成→レコード作成、削除→テーブル削除の一連の流れ

はじめに

Ruby on RailsのDB操作に慣れるための覚え書きも兼ねた完結型の筋トレセットです。

テーブル削除の手順などは自分も含めた初心者にとって少し複雑だと思いますので、繰り返し練習などでお役に立てば幸いです。

すでにお使いのRailsアプリにデータベースがインストールされていることを前提にしているので、データベース作成がまだの方は「rails db:create」でデータベースをご用意ください。

環境

Ruby 2.7.2
Rails 6.0.3

テーブルの新規作成

まず、ターミナルからRailsアプリのあるディレクトリに移動する。

console
> cd (フォルダのアドレス)

モデル作成のためのマイグレーションファイルを作成。
(例:モデル名「User」、フィールド名「name」、「address」、「age」)

console
> rails g model User name:text address:text age:integer

この時点で「User」モデルを定義したクラスファイルとマイグレーションファイルが作成されますが、テーブルは生成されません。

テーブルの生成にはマイグレーションを行う必要があります。

console
> rails db:migrate

これで「User」モデルを基にした「users」テーブルが作成されます。

※テーブル名がモデル名と違うのはrailsの命名規則に拠る
参考:「RailsのDBモデルの命名規則をまとめてみた」https://qiita.com/seri1234/items/8ca4b52d82390929195f

レコード追加~削除まで

Railsコンソールを起動します。

Railsコンソール
> rails console

新しい行にレコードを追加します。

Railsコンソール
irb(main):001:0> user = User.new
irb(main):002:0> user.name = "John Smith"
irb(main):003:0> user.save

レコードが追加されていることを確認します。

Railsコンソール
irb(main):004:0> users = User.all

確認ができたら、作成したレコードを削除します。
(find()の引数はレコードのインデックス)

Railsコンソール
irb(main):005:0> User.find(1).destroy

レコードを削除したら、Railsコンソールを終了してターミナルに戻ります。

Railsコンソール
irb(main):006:0> exit

モデルの削除からテーブルの削除まで

まず、モデル作成時に作られたファイルを削除します。

console
> rails destroy model User

これにより「User」モデルが削除されます。

この時点ではまだ「users」テーブルが残っていることに注意して下さい。

次にテーブル削除用マイグレーションファイルを作成します。
(「drop_table_users」は任意の名前でOK)

console
> rails generate migration drop_table_users

「アプリ名/db/migrate/」以下に先頭に数字が並んだ「~drop_table_users.rb」というマイグレーションファイルが作られます。

このファイルをテキストエディタで開き、「def change」内にテーブルを削除するための命令を追加して上書き保存する。

1**********9drop_table_users.rb
class DropTableUsers < ActiveRecord::Migration[6.0]
  def change
    # 以下1行を追加
    drop_table :users
  end
end

マイグレーションを実行します。

console
> rails db:migrate

テーブルが削除され、これで一連の流れが完了となります。

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

[Ruby編]等差数列

[Ruby編]等差数列を解いてみる

問題

以下のような数列を出力するプログラムを作りましょう。

5 8 11 14 17 20 23 26 29 32

最初の数字が5で、3ずつ増加していく事がわかります。
一般的にこれを初項5、公差3の等差数列といいます。

このような数列を出力するプログラムを作りましょう。
初項 m が与えられ、公差 n が与えられるので 10番目までの数字をスペース区切りで出力するプログラムを作成してください。

入力される値

入力は以下のような初項 m、公差 nが半角スペース区切りのフォーマットで与えられます。

入力値最終行の末尾に改行が1つ入ります。

m n

期待する出力

初項 m ,公差 n の等差数列を1〜10番目までスペース区切りで出力して下さい。

入力例1

3 3

出力例1

3 6 9 12 15 18 21 24 27 30

入力例2

5 10

出力例2

5 15 25 35 45 55 65 75 85 95

入力例3

1 3

出力例3

1 4 7 10 13 16 19 22 25 28

私の答え

num = gets.chomp.split(" ").map(&:to_i)
x = num[0]
i = 1
array = []
while i <= 10
  array << x
  x = x + num[1]
  i += 1
end
print array.join(" ")

今回のポイント

1行目の入力値はスペース区切りがあるので split と map で[3, 3]のような配列にする

2行目で先ほどの配列の一部を指定して x変数に代入しておく

3行目で i = 1 としておく事で1回ずつカウントさせる

4行目でスコープ外でもarrayを使えるようにし、また繰り返し結果を配列に格納する

5~9行目で i が10回を超えるまで繰り返される。 array[] に x = num[0]を配列演算子 << を使って連結している。while文の場合、 i += 1 の自己代入演算子を忘れてはいけない

最終行でjoinメソッドを用いて (" ") とする事で配列間にスペースを開けられる

以上!

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

downcase,sliceメソッドの応用

「a=abc」,「b=HiAbc」という二つの変数がある時、a,bいずれかの文字列がもう片方の文字列の最後にある場合にTrueを返すプログラムの作成方法です。
今回の場合、aのabcがbの後ろ三文字に存在するためTrueとなります。

以下二つのメソッドを使用することで作成することができます。

メソッドの使用例

  • 大文字を小文字に変換するためにはdowncaseメソッド
ruby
a = "AbC"
a_down = a.downcase
// これにより a_down には abc が入る
  • 特定の位置にある文字列などを切り抜きたい場合はsliceメソッド
ruby
a = "AbC"
a_slice = a.slice(1)
// これにより a_slice には b が入る

回答例

ruby
def compare(a, b)
  a_down = a.downcase
  b_down = b.downcase
  a_len = a_down.length
  b_len = b_down.length
  if b_down.slice(-(a_len)..- 1) == a_down || a_down.slice(-(b_len)..- 1) == b_down
    puts "True"
  else
    puts "False"
  end
end

解説

2~5行目でこの後に使用する変数の定義をしています。

ruby
a_down = a.downcase    // aの文字列を全て小文字化してa_downに代入
b_down = b.downcase    // bの文字列を全て小文字化してa_downに代入
a_len = a_down.length  // a_downの文字列の長さをa_lenに代入
b_len = b_down.length  // a_downの文字列の長さをa_lenに代入

その後、以下のif文で判定しています。

ruby
if b_down.slice(-(a_len)..- 1) == a_down || a_down.slice(-(b_len)..- 1) == b_down
  puts "True"
else
  puts "False"
end

「b_down.slice(-(a_len)..-1 == a_down」を日本語訳すると、「b_downの右から1〜a_len部分を切り取る」となります。
仮にa_lenが「3」、b_downが「hiabc」という5文字だったとして、「hiabcの右から1〜3文字を切り取る」となります。すると切り取った文字列は「abc」となります。
sliceメソッドは正の値を渡すと先頭から計算し、負の値を渡すと末尾から計算しますので、これを覚えておいてください。

b_downの場合とa_downの条件を「または」という演算子を使っていることで、どちらの変数からも確認できるようにしています。(正直最初見たときは訳せなくて落ち込みました。)

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

[Rails]redirect_toとrenderの違いについて[初心者]

はじめに

いきなりですが、redirect_torenderって、初めて見たときは、両者の違いがよく分からなかったですよね。
深掘りして、一緒に学んでいきましょう!

redirect_toとrenderの違い

redirect_to: HTTPリクエストをサーバーに送り、ユーザーはそこから返ってくるHTMLが表示される。
render: アクションの中で、呼び出すViewファイルを指定するメソッド。

言葉だけだと、分かりづらい部分があると思うので、それぞれの処理の流れについて解説します。

それぞれの処理の流れについて

redirect_toの処理の流れ
controllerの処理でredirect_toを実行
redirect_toの引数で指定したURLHTTPリクエスト(GET!!)を実行
HTTPリクエスト(GET!!)されたURLに対応するルーティング処理を実行
④ルーティング処理に対応したcontroller、アクションが呼び出され処理を実行
⑤処理に応じたviewのレンダリングを実行

①controller(redirect_to) → ②HTTPリクエスト(GET!!) → ③ルーティング → ④controller → ⑤view

renderの処理の流れ
controllerの処理でrenderを実行
renderのオプションで指定したviewファイルを表示する

①controller(render) → ②view

ここで注意が必要なのは、redirect_toの指定先は、必ずGETメソッドのルーティングになるということです。

redirect_toやrenderの指定がない場合

たまに見かけますよね。
下の状態のようなコントローラーを。

controllers/books.rb
class BooksController < ApplicationController
  def index
  end
end

アクション内に、何も記載がない場合は、自動的にrenderが実行されます!!
つまり、上の場合だと、render 'index'が省略されているものと考えられ、このアクションからviews/books/index.html.erbが呼び出されます。

よく見る記載方法

それではここで、よく使われているredirect_toとrenderの記述例について見てみましょう!

controllers/books.rb
class BooksController < ApplicationController
  def create
    @book = Book.new(book_params)
    @book.user_id = current_user.id
    if @book.save
      redirect_to books_path, flash[:notice] = "投稿成功!!"
    else
      flash.now[:alert] = "投稿失敗!!"
      render 'new'
    end
  end
end

投稿を行う際に、saveメソッドの結果がtrueならば投稿一覧ページにリダイレクトし、falseなら新規投稿用のページを再表示するように設定しています。
render 'アクション名'で同じコントローラの別アクションのViewを表示できます。
 コントローラ名を指定することで、他のコントローラのViewも表示できます。

なぜfalseの場合はrenderを使用するのか

renderを利用して、viewファイルを表示する際に、アクションを呼び出していないことにお気づきでしょうか。
↑のrender 'new'は、books/new.html.erbを表示させているだけであり、newアクションを経由しているわけではないのです。

今回のパターンだと、投稿に失敗しているからnewページを表示させたいのですが、redirect_toを使用してしまうと、newアクションを呼び込み、インスタンス変数に空のメソッドを渡してしまうことで、newアクションがリセットされた状態になるので、ユーザーが打ち込んだデータが消えます。
よって、エラーメッセージを打ち出す為の情報も消えてしまう為、ユーザーは「なぜ投稿できなかったのか」を窺い知ることができなくなります。

ややこしいですが、大事な分岐点を決めるものなので、腹落ちするまで向き合ってもらえればと思います。

(補足)render時のフラッシュメッセージについて

本筋とは離れてしまうので、詳細は述べませんが、表示したビュー内にメッセージを出したい場合、以下のように記述します。

redirect_toの場合
flash[:notice] = "メッセージ"
 または
notice: "メッセージ"
renderの場合
flash.now[:alert] = "メッセージ"  #nowがついている!!!!

おわりに

初心者の方が間違いやすいredirect_toとrenderの違いについて説明しました!!
解説している中で、自分自身勉強になることが多く、アウトプットの重要性を痛感しています。

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

Rails6 チェックボックスにチェックが付いている値を配列にしたい

フォームのチェックボックスにチェックが付いている値を取得して配列にする。

ActionController::Parameters {"authenticity_token"=>"DoVsq/+dLxgEzm0kXJUo4SWmHx5CBR1GmeV+yNaAeYcDmyMg8nWcMDNSWeMY**************==", "hogehoge"=><ActionController::Parameters {"aaa"=>"0", "bbb"=>"1", "ccc"=>"1", "ddd"=>"1",} permitted: false>, "commit"=>"検索", "controller"=>"distributions", "action"=>"search"} permitted: false>

こんな感じでフォームがくるので

params[:hogehoge].permit!.select { |key, value| value == "1" }.keys
["bbb","ccc", "ddd"]

として取得できました。

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

【AtCoder】ABC183のD問題をRubyで解く

茶色コーダーがABC183のD問題をRubyで解いてみました。

問題

AtCoder Beginner Contest 183

D - Water Heater

給湯器が $1$つあり、毎分$W$リットルのお湯を供給することができます。
$N$人の人がいます。$i$番目の人は時刻$S_i$から$T_i$までの間(時刻$T_i$ちょうどを除く)、この湯沸かし器で沸かしたお湯を毎分 $P_i$リットル使おうと計画しています。お湯はすぐ冷めてしまうので、溜めておくことはできません。
すべての人に計画通りにお湯を供給することはできますか?

制約

$1 \leq N \leq 2 \times 10^5$
$0 \leq S_i \leq 2 \times 10^5$
$1 \leq W, P_i \leq 10^9$
入力はすべて整数

入力

$N$ $W$
$S_1$ $T_1$ $P_1$

$S_N$ $T_N$ $P_N$

回答

nw = gets.chomp.split.map(&:to_i)
n = nw[0]
w = nw[1]

# 時間ごとの使用水量を格納する配列
a = Array.new(200005, 0)

n.times do
    stp = gets.chomp.split.map(&:to_i)
    s = stp[0]
    t = stp[1]
    p = stp[2]

    # 「累積和を格納する配列」にするための準備
    a[s] += p
    a[t] -= p
end

result = true

# 累積和を格納する
200001.times do |i|
    if a[i] > w
        result = false
        break
    end
    a[i+1] += a[i]
end

puts result ? "Yes" : "No"

考え方

累積和を使います。
ただし初めから求めるのではなく、後でやるという点に注意します。

  1. 時間ごとの使用水量を格納する配列を用意する
  2. 使い始め時点と使い終わり時点のみ使用水量を増減させる
  3. 時間ごとに使用水量の累積和を計算する

時間ごとの使用水量を格納する配列を用意する

$T_i \leq 2 \times 10^5$ なので、$200001$個以上の要素を持つ配列を用意します。

使い始め時点と使い終わり時点のみ使用水量を増減させる

前述した通り、初めは累積和を求めないことがポイントです。
最初から求めてしまうと、実行時間が足りなくなります。

まずは$N$人分の情報が入力される際、使い始め時点と使い終わり時点の使用水量を増減させるのみに留めます。
具体的には以下のようにします。

  • 使い始める時点の使用水量を増やす
  • 使い終わる時点の使用水量を減らす

時間ごとに使用水量の累積和を計算する

あとは普段通りに累積和を求めていきます。
$W$を超えていないか判定をしていけばOKです。

感想

自分は初めから累積和を求めにいって、実行時間が足りずACできませんでした。泣
使用水量を使い始めだけ増やして、使い終わりだけ減らすというメモのような形にして後でまとめて帳尻合わせする。
なるほどな~その手があったか~という感じです。

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

RSpecで「undefined method `create'」というエラーが出た

Everyday Rails – RSpecによるRailsテスト入門でコントローラーにおけるRSpecのテストを学んでいたときに、自分にとって新しい学びがあったのでアウトプットしたいと思います。

問題

本日もRSpecについて勉強していたところ、

reviews_spec.rb
 review = FactoryBot.create(:review)

という記述を

reviews_spec.rb
  review = create(:review)

という省略系にできることを知り、自分もやってみました。

しかし、「bundle exec rspec」を実行した後、以下のようなエラーが出てしまいました。

ターミナル
Failure/Error: review = create(:review)

     NoMethodError:
       undefined method `create' for……(以下省略)

「セットアップがうまく行ってなかったのかなぁ…」
と思ったので、伊藤淳一さんのブログ(リンクはこちら)を参考にさせていただきながらRailsのセットアップを見直したり、他にもいろいろ調べていました。

解決策

その結果、rails_helperに次のような記述が必要だったことを知りました。
参考にさせていただいた記事はこちら

rails_helper.rb
 #一番下に記述してください

 RSpec.configure do |config|
    config.include FactoryBot::Syntax::Methods
  end

この記述が抜けていたために、今回のようなエラーが出ていたようです。

これにより、冒頭にあった

reviews_spec.rb
 review = FactoryBot.create(:review)

という記述を

reviews_spec.rb
  review = create(:review)

という省略系にすることができました。

今回はcreateメソッドでしたが、buildメソッドで省略系を書きたい時も同様です。

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

リファクタリングをいつやるか?

普段は Rails を書いていますが、リファクタリングっていつやるのがいいのかと言うのを考えていました。

リファクタリング:Rubyエディション を読んでいていろいろと気づきがあったのでそのまとめです。

リファクタリングとは

ありきたりですが、忘れるので書いておきます。

プログラムの外部から見た動作を変えずにソースコードの内部構造を整理することである。

wiki:リファクタリング 2020/11/15

いつリファクタリングするのか?

リファクタリングは常にやりつつ開発をしていくというのがよく言われてますが、具体的にいつやっていくのか?がよく分かっていませんでした。

まずは既存のコードを改修、機能追加することを例に考えます。

上記実装を行うとき、実装→テストコード→リファクタリング といった感じでリファクタリングをすると考えていました。

実際に機能を追加する場合、既存コードの理解から入ります。

リファクタリングはコードを読みやすくすると言うことも含んでいるため、最初の既存コードの理解のときにリファクタリングするのも正しい選択かなと。

なので、 既存コードの理解→リファクタリング→実装→テストコード→リファクタリング という流れで実装していくといいと考えています。

この方法をやると、最初のリファクタリングでわかりやすいコードになって理解が進み、この時点で Pull Request を出しておくと他の開発者も理解が進みます。

テストコードがある場合は最初のリファクタリングも上手くいくと思うし、テストがなければコード理解も含めてその時に書いてしまえばいい気がします。

まとめ

リファクタリングをやるときは以下のタイミングでやるのが良さそう。

既存コードの理解→リファクタリング→実装→テストコード→リファクタリング

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

文字のだるま落とし(ruby編)

[Ruby編]文字のだるま落とし

問題

だるま落としは 5 段の木片からなり、その木片には 1 つにつき 1 つの英字小文字が書いてあります。
木片を飛ばす前の文字列と、飛ばしたのが何段目の木片であったかが与えられるので、木片を飛ばした後の文字列を求めるプログラムを作成してください。

入力される値

入力は以下のフォーマットで与えられます。

S
N
・1 行目には、木片を落とす前の文字列を表す S が与えられます。
・2 行目には、何段目の木片を落とすかを表す整数 N が与えられます。
・入力は全部で 2 行となり、最後に改行が一つ入ります。

期待する出力

木片を落下させた後にできる文字列を出力してください。

末尾に改行を入れ、余計な文字、空行を含んではいけません。

入力例1

apple
2

出力例1

aple

入力例2

water
5

出力例2

wate

私の答え

a = gets.chomp
b = gets.to_i
str = a.slice!(b - 1)
print a

今回のポイント

1行目で文字列の呼び出し

2行目で数値の呼び出し

3行目でsliceメソッドを用いて b - 1で文字列を指定して呼び出している

4行目でstrにすると a という文字列のみ出力されるので print a でsliceした文字列を除外した文字列を出力した

以上!

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

RailsにおけるModelの関連付け

概要

モデル間の関連付けは、アプリケーションを作る上で基本中の基本になると思うので、復習の意味を込めてまとめてみます。

関連付けとは

モデル間の関連付けとは、データベースのテーブル間を外部キーを使用し関連付けることです。
これを行うことで、必要なデータを簡単な記述で呼び出したり、データを削除する際、関連したデータを同時に削除できたり非常に便利です。

一対多の関連付け

関連付けの中で代表的なのは、一対多の関連付けだと思われます。
一対多の関連付けとは、あるテーブルの一つのレコードが別のテーブルの複数のレコードと関連付くことです。
例:UserモデルとPostモデルを関連付ける

app/models/user.rb
class User < ApplocationRecord
  has_many :posts
end
app/models/user.rb
class User < ApplocationRecord
  belongs_to :user
end

has_manyメソッドを使用すると「〜をたくさん持っている」、belongs_toメソッドを使うと「〜に属している」という意味になり、二つのモデルが関連付けます。
上記の場合、「UserはPostをたくさん持っている」のような意味合いになります。

これらのメソッドには命名規約があり、has_manyメソッドではモデル名は複数形とし、belongs_toメソッドではモデル名は単数形を使います。
また外部キーのカラム名は参照先モデル名(単数形)+_idと指定します。(例:user_id)外部キーを指定する場合は、オプションでforeign_keyを指定します

app/models/user.rb
class User < ApplocationRecord
  has_many posts, foreign_key: "client_id"
end

メソッド名を変える場合はclass_nameオプションを指定します。has_manyの後ろは新たなメソッド名に変更し、class_nameに本来のモデル名を指定します。

app/models/user.rb
class User < ApplocationRecord
  has_many tweets, class_name: "Post"
end

has_manyにdependentオプションを指定し:destroyと指定すると、参照先レコードが削除された際、参照元のレコードも自動で削除します。

app/models/user.rb
class User < ApplocationRecord
  has_many posts, dependent: :destroy

関連付けによってできること

モデルが関連付くと以下の記述で参照元のデータを取り出せます。

example
@posts = @user.posts

has_manyにて使えるようになったメソッドは集計メソッドやクエリーメソッドを使うことができます。

example
@posts = @user.posts.count
@posts = @user.posts.order("")

参照先へ関連付けるデータを設定する場合、buildメソッドを使用して関連づけることができます。また、引数にモデルの属性を指定できます。

example
@user.posts.build(name: "taro")

<<と記載すると、レコードの保存まで行うことができます。

example
@user.posts << @post

まとめ

モデル間の関連付けに関して復習の意味も込めて、再確認できました。
まだまだ基本的な部分であり、一対一だったり、多対多の関連付けなどもあるので、別の機会にまとめていきたいです。

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

deviseでログアウト時に確認ページに遷移させる

はじめに

railsでdeviseをしようしたログインログアウト機能を実装していて、
ログアウトした際に確認ページに遷移するように実装していたところ
つまずいたので記事にしようと思います。
さらに良い方法とかがあればコメント欄か編集リクエストに記載願います。:bow_tone2:

バージョン情報

  • ruby 2.6.5
  • rails 6.0.3.3

前提条件

  • hamlでの記載(gem 'haml-rails')
  • deviseが導入済みでログインができている

コントローラーの変更

sessions_controllerのrespond_to_on_destroyがデフォルトで記述されていて
root_pathに遷移されるようになっていました。
なのでここのコードを削除します。
そこにrender '遷移先URL'を記述することでURL先のページに遷移することができます。

# controllers/users/sessions_controller.rb

def destroy
  signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
  set_flash_message! :notice, :signed_out if signed_out
  yield if block_given?
  respond_to_on_destroy #デフォルトの記述 ← コード削除
  render '遷移先URL' #renderで遷移先のURLを記述  コード記入
end

Viewの作成

私はusers/destoroyにdestroy.html.hamlファイルを作成しました。

%h1 ログアウトしました。
= link_to "マイページに戻る", "/"

destroy.png

コントローラーのカスタマイズ

sesstions: "users/sessions"を追記して、sessionsをカスタマイズしました!!

  devise_for :users, controllers: {
    registrations: "users/registrations",
    sessions: "users/sessions" #カスタマイズ
  }

ルーティングのカスタマイズ

ルーティングもカスタマイズします!
HTTPメソッド:delete, URL, コントローラー名#アクション名

  devise_scope :user do
    get 'profiles', to: 'users/registrations#new_profile'
    post 'profiles', to: 'users/registrations#create_profile'
    get 'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
    delete 'users/destroy', to: 'devise/sessions#destroy' #追記
  end

二つをあわせて
routes.rb

  devise_for :users, controllers: {
    registrations: "users/registrations",
    sessions: "users/sessions" #カスタマイズ
  }
  devise_scope :user do
    get 'profiles', to: 'users/registrations#new_profile'
    post 'profiles', to: 'users/registrations#create_profile'
    get 'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
    delete 'users/destroy', to: 'devise/sessions#destroy' #追記
  end

まとめ

まだまだMVCの理解がないと感じました。
冷静になって考えたらすぐに解決できた問題でした。
そして、マークダウン の書き方にも苦戦してます。
記事書くのにすごい時間かかってしまっているので早くなれなければ…
もっとアウトプットしていきます。

しょうま

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