- 投稿日:2020-11-16T23:23:57+09:00
エラーメッセージの重複をなくしたい
エラーメッセージの重複①
form_withメソッドを使って、
form.number_fieldで数値を入力させるフォームを作った。
正しい数値を入力すればエラーは起きないし、バリデーションを超える数値を入力しようとすると、そのタイミングでエラーが出る。
しかし、空のまま入力フォームを送ると、
○○を入力してください
○○は不正な値です
○○は一覧にありません
と、エラーが3つも出てしまう。
空を送信しただけで、3つもいらん!!!解決方法
modelwith_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 } endformatは正規表現のバリデーション。
inclusionは数値の範囲のバリデーション。
allow_blank: true
は値が空の場合はバリデーションを実行しないメソッド。
冗長だが、これで空のときにはformatとinclusionのバリデーションは反応しない。改めてエラーメッセージを見ると、
「○○は不正な値です」はformatに反応していて、
「○○は一覧にありません」はinclusionに反応していることがわかる。エラーメッセージの重複②
外部キーを使っている状態で、そこに
presence: true
のバリデーションをかけると、同じような内容で2つのエラーメッセージが出る。解決方法
モデルに記述している、外部キーのバリデーションを消す。
【余談】エラーメッセージの順番を変えたい
順番はバリデーションとアソシエーションの記述位置を変える事で解消できる。記述の位置を変えるだけなので、特段問題が起こらないことが有難い。
これは、コードが上から読み込まれていることに起因すると考えられる。ちょっとしたことだけど、入力フォームの順に、エラーメッセージを出してあげたい。
- 投稿日:2020-11-16T22:44:03+09:00
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 enddevise_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: [:許可するキー]) enddeviseの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 endbefore_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_creatorRailsの慣習的な命名規則として、下記のように使い分けます。
命名の対象 慣習的な命名規則 クラス名 アッパーキャメルケース メソッド名 スネークケース 変数名 スネークケース自分でクラスやメソッドを作成する際には、これらの命名規則は守るようにしましょう。
- 投稿日:2020-11-16T21:57:06+09:00
【アウトプット】データベースについて
データベースの振り返りをしたのでここでも書いていく
ターミナルでテーブルを作るとき
$ rails g model Post content :text
Postはモデル名 contentはカラム名 textはデータ型
textは長い文字列という意味、つまりcontentに入るのはtext(長い文字)ということになる。
「こんにちは」とかこれをターミナルで実行するとmodelとmigrateに2つのファイルが入る
1.migrateには「2020..._create_posts.rb」というものが作られているファイルを開くとこんな感じなのがある
dif change
create_table posts do |t|
t.text:contentt.timestamps
2つめはあることをしないと作成されない
rails db:migrate と書く。以上
- 投稿日:2020-11-16T21:39:24+09:00
アソシエーションの理解〜初心者の備忘録〜
アソシエーションの理解〜初心者の備忘録〜
1.アソシエーションとは?
簡単に表現すると「各モデル同士を関連付ける」ということである。つまり、テーブル同士で関連付けておき、一方のモデルからもう一方のモデルにアクセスできるようにすることである。
例. 1人のuserが複数のコメントを所有するパターン(1対多の関係)
→「Aさん」とAさんのコメントである「おはよう」、「こんばんは」を結びつける。
→「Bさん」とBさんのコメントである「こんにちは」、「さようなら」を結びつける。2.has_manyメソッドとbelongs_toメソッド
では、どのようにアソシエーションをコードで再現するのか示してみる。
上記の1人のuserが複数のコメントを所有するパターン(1対多の関係)で考えてみる。(1) has_many
※上記の図はAさんとAさんが所有するコメントの関係であるが、Bさんも上記の図のように考える。
#Userモデル class User < ApplicationRecord has_many :comments #1人のuserが複数のcommentを所有するので複数形のcommentsになる。 end(2) belongs_to
※上記の図は「おはよう」のコメントがAさんに所有されている関係であるが、その他のコメントも上記の図のように考える。#Commentモデル class Comment < ApplicationRecord belongs_to :user #1つcommentは1人のuserが所有するので単数形のuserになる。 end3.さいごに
アソシエーションの1対多における基本的な考え方を説明しました。今後も、多対多の関係など少し応用的な投稿にも継続的にしていきたいと思います。また、。誤り等ご指摘ありましたらコメントお願いします!
- 投稿日:2020-11-16T21:39:24+09:00
アソシエーション(1対多の関係)
アソシエーション(1対多の関係)
1.アソシエーションとは?
簡単に表現すると「各モデル同士を関連付ける」ということである。つまり、テーブル同士で関連付けておき、一方のモデルからもう一方のモデルにアクセスできるようにすることである。
例. 1人のuserが複数のコメントを所有するパターン(1対多の関係)
→「Aさん」とAさんのコメントである「おはよう」、「こんばんは」を結びつける。
→「Bさん」とBさんのコメントである「こんにちは」、「さようなら」を結びつける。2.has_manyメソッドとbelongs_toメソッド
では、どのようにアソシエーションをコードで再現するのか示してみる。
上記の1人のuserが複数のコメントを所有するパターン(1対多の関係)で考えてみる。(1) has_many
※上記の図はAさんとAさんが所有するコメントの関係であるが、Bさんも上記の図のように考える。
#Userモデル class User < ApplicationRecord has_many :comments #1人のuserが複数のcommentを所有するので複数形のcommentsになる。 end(2) belongs_to
※上記の図は「おはよう」のコメントがAさんに所有されている関係であるが、その他のコメントも上記の図のように考える。#Commentモデル class Comment < ApplicationRecord belongs_to :user #1つcommentは1人のuserが所有するので単数形のuserになる。 end3.さいごに
アソシエーションの1対多における基本的な考え方を説明しました。今後も、多対多の関係など少し応用的な投稿にも継続的にしていきたいと思います。また、。誤り等ご指摘ありましたらコメントお願いします!
- 投稿日:2020-11-16T21:17:41+09:00
【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.rbclass 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に追加します。Gemfilegroup :development, :test do gem 'rspec-rails' endgemのインストールとRSpecのセットアップを行ます。
$ bundle install $ rails generate rspec:installwebmockのインストール
webmockをGemfileに追加し、インストールします。
Gemfilegem 'webmock'$ bundle installテストコードの実装
以下ではWebMockを利用したテストコードの記述方法について紹介します。
正常系のテスト
正常系のテストコードは以下の通りです。
spec/lib/qiita_api_client_spec.rbrequire '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.rbrequire '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 endWebMockの
to_raise
で例外が発生するスタブを作成し、expectのraise_error
で例外が発生していることを確認しています。最終的なテストコード
今回紹介した正常系と異常系をまとめた最終的なテストコードは以下のようになります。
spec/lib/qiita_api_client_spec.rbrequire '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を利用したテストコードのフローは以下のようになります。
- Webmock.enable!でWebMockを有効化する
- WebMock.stub_requestでHTTPリクエストのスタブを作成する
- 外部APIを利用するメソッドを実行し、実行結果の検証を行う
Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!
- 投稿日:2020-11-16T19:54:54+09:00
Webデザイン 色のイメージ
はじめに
現在オリジナルアプリを作成中です。トップページのフロント実装時に色合いの考え方を調べました。忘れても思い出せるよう書き記します。
開発環境
ruby 2.6.5
Rails 6.0.3.4目次
1.色の効果
2.オリジナルアプリの配色1.色の効果
Webデザインで使う色は目的別で選ぶ必要がある。Webサイトを利用するユーザー目線に立つことが重要である。
赤系
熱い、生命力、警告
オレンジ系
近親感、陽気
黄系
好奇心、協力
緑系
自然、リラックス
青系
冷たい、静けさ、誠実
紫系
後期、威厳
ピンク系
柔らかい、可愛い
茶色系
安定、信頼、歴史
2.オリジナルアプリの配色
オリジナルアプリは大きく分けて下記2つの機能を有する。トップページからそれぞれリンク先に遷移できるように実装した。
・ユーザーへの掃除提案
・掃除したデスク画像の共有「ユーザーへの掃除提案」リンクを赤系にした。これは清掃を促す(警告)するためである。「掃除したデスク画像の共有」は青にした。これは清掃後のデスクを想定したため、清潔感をイメージする青を選択した。
参考
1冊ですべて身につくHTML-CSSとWebデザイン入門講座
Qiitaのマークダウンで色をつける方法[140色]
- 投稿日:2020-11-16T18:03:48+09:00
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 endID指定での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力は決して高くはないし、賛否の分かれる部分もあると思います。
でも、まぁそんなのは割とどうでも良いです。現場での設計は万人受けする必要はないのです。ただチームのメンバにこのプロジェクトは「こういう方針で作っている」という思想が共有され、一貫性が保たれていればそれで十分だと思います。
正解を求めるのは学問の分野の仕事で、現場のエンジニアがそれを求めてしまうとドツボにはまるだけだとも思います。
すべてを肯定的に捉えることは無理でも、何かしら参考になる点があれば幸いです。
作っている過程で、何か変える部分がでてくればまた追記します。
(何故なら、この文書こそがチームメンバに「こういう方針で作っている」を共有するためのドキュメントそのものだからです。)
- 投稿日:2020-11-16T17:43:58+09:00
【Git/GitHub 共同開発】知っておきたいコンフリクト解消とコードレビューの基本
概要
現在所属中のオンラインサロン「転職クエスト」にて、Railsアプリ共同開発を行っています。
恐れながらファシリテーターを務めさせてもらっているのですが、開発をすすめる上で全員のネックになっていたことがありました。
Git/GitHubの共同開発的な使い方がわからない。。。
なので、開発を進めるファシリテーターとして、
Git/GitHubをこうやって使おうぜ!
というマニュアルを用意しておこうと考えました。本記事では
共同開発 初心者さんたちに向けたGit/GitHubのフロー
について紹介します!想定読者1. アプリの個人開発のためにGit/GitHubを使ったことはある 2. コンフリクト解消のやり方を知りたい! 3. コードレビューのやり方を知りたい!
そもそも、どこがわかっていない?
Git/GitHubを使って共同開発をする上で、ネックになる部分はどこか。
考えるために、これまで行ってきたGit/GitHubの使い方を振り返ってみます。個人開発でのGit/GitHubの使い方1. ローカルのmasterでブランチ切る 2. ブランチで作業しコミット 3. リモートにブランチをプッシュして、プルリク作成 4. GitHub上でひとりLGTMを出して、ブランチをmasterにマージ 5. リモートのmasterをローカルのmasterにプル 6. 1~5を繰り返すさて、
このフローでしか開発をしてこなかった人が、共同開発でGit/GitHubを使う際に、初めて経験するのはどんなことでしょうか?
僕は、次のことだと考えました。
初めて共同開発をする人がやったことがないこと1. 他の人のプルリクに対するコードレビュー 2. リモートのmasterの変更分を、ローカルの作業中ブランチにマージ 3. その際に起きるコンフリクト解消特に2, 3については、
最新に更新されたmasterからしかブランチを切ったことがない
というのが不安の理由として挙げられます。
共同開発では、ブランチのマージによって常にmasterが更新されていきますので、その更新分を自分の作業中ブランチに反映する必要があります。
ここに焦点を当てて、
共同開発でのコンフリクト解消、コードレビューの手順を紹介していきます!
コンフリクト解消の例
場面設定
設定masterから同じタイミングで2つのブランチを切る user1.branch1 : git checkout -b new,createアクションを実装 user2.branch2 : git checkout -b edit,updateアクションを実装ちなみに今回は
「ルーティングの設定」のみを取り扱っていきます
のであしからず。
1. [user1] branch1でnew,createのルーティングを設定
branch1→routes.rb+ resources :tweets, only: [:new, :create]
2. [user1] ローカルでコミット→リモートにブランチをプッシュ
branch1(new,createアクションを実装)% git add . % git commit -m "ルーティングにnew,createを追加" % git push origin new,createアクションを実装
3. [user1] GitHubでプルリク作成→LGTM→マージ
ここは省略します。
4. [user2] branch2でedit,updateのルーティングを設定
branch2→routes.rb+ resources :tweets, only: [:edit, :update]
5. [user2] branch2でコミット
branch2(edit,updateアクションを定義)% git add . % git commit -m "ルーティングにedit,updateを追加"
※次にmasterブランチに切り替えますが、その前に必ずここでコミットをしてください!
6. [user2] リモートmaster(branch1 merged)→ローカルmasterにプル
# masterブランチに移動 % git checkout master # リモートのmasterをローカルのmasterに反映 % git pull origin master【今はこういう状態】
7. [user2] ⚠コンフリクト ローカルmaster→branch2にマージ
# 作業中ブランチに移動 % git checkout edit,updateアクションを定義 # ローカルのmasterを作業中ブランチにマージ % git merge master【コンフリクト発生の様子】
- Head : 現在、作業中のブランチ (branch2)
- master : マージしたローカルのmasterブランチ
8-1. [user2] コンフリクトの解消
masterを基準に修正し、コンフリクト解消したらコミット# 編集後 % git add . % git commit (デフォルト名 : Merge branch 'master' into edit,updateアクションを定義)基本的に、
masterと作業中ブランチのログをどちらも残し、必要な形に変えればそれでOKです。
コミットするとデフォルトでこのブランチがマージされたよ!
というコミット名になります。【※コンフリクト解消後のGitHub Desktopの画面】
▶commit Mergeを押せば、デフォルトのコミット名でコミットされる...はず
8-2. [user2] わからないのでとりあえずmergeを取り消したい!
もし
「コンフリクトこわい! 一旦戻したい!」
という方にはこちら。merge取り消し方法% git merge --abort【merge取り消しの様子】
▶コンフリクト状態がリセットされます
9. [user2] branch2をリモートにプッシュ→プルリク
% git push origin edit,updateアクションを定義
小括 : コンフリクト解消
- ローカルの作業中ブランチで最新の変更をコミット
- 誰かがブランチをマージしたリモートのmasterをローカルのmasterにプル
- ブランチを切り替え、masterをマージ
- 落ち着いてコンフリクト解消→修正したらコミット
続いて、コードレビューの仕方についてです。
branch2「edit,updateアクションを定義」のプルリクに対してコードレビューをしましょう!
コードレビューの例
1. [レビュアー] プルリクURL→File changedタブ
2. [レビュアー] 変更希望箇所をクリック(複数行を選択も可能)
3. [レビュアー] コメントをする→Start a review
選択した部分についてのコメントを記載して下さい。
4. [レビュアー] 複数ファイルにコメント→Finish your review
そのプルリクで気になった箇所にコメントを残し終えたら、Finish your reviewを押して下さい。
▶今回、レビュアー側として「showアクションを追加して下さい」とコメントをしてみました。これに沿って「プルリク依頼者側」での対応をしてみましょう。
5. [依頼者側] ローカルで編集してpush
routes.rb#showを追加 + resources :tweets, only: [:new, :create, :show, :edit, :update]local-branch2% git add . % git commit -m "showアクションを追加" % git push origin edit,updateアクションを定義
6.[依頼者側] 修正報告
レビューに対して「showアクションを追加しました!」とコメントしましょう。
7. [レビュアー] LGTM→Resolve conversation(レビュー完了)
8. 全部でLGTMならマージ
以上のフローを繰り返し、LGTMをもらえたらマージしましょう!
9.ローカルのmasterにプル
% git checkout master % git pull origin mastermasterの変更分 (mergeした部分) が次のように反映されます。
小括 : コードレビュー
- プルリクのFile Changedで変更差分を確認
- 必要に応じてコメント
- コメントに対してローカルブランチで作業し再レビュー
- LGTMならマージ
以上が、GitHub flowによるコードレビューの基本手順です。
まとめ
- Git/GitHubを共同開発で使うときは、最新masterを作業中ブランチにマージする場面がくる
- 変わった部分を冷静に判断し、コンフリクトを解消
- プルリクに対してはファイルの変更差分を確認し、レビューを行う
個人とは違う部分が多く、例を出して説明するのに苦労しました。
しかし、この機会に学んでみるとGitってすごいな!
と改めて思いました。引き続き、共同開発がスムーズに進むよう、学習を進めていきたいです!
参考
- 投稿日:2020-11-16T15:38:22+09:00
この問題地味に難しい...(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 の部分です。こういうところがプログラミング脳が足りてないところなんですよね。。。そしてなんと言ってもググる際の言語化がこういうときかなり難しい。
以上!
- 投稿日:2020-11-16T15:36:06+09:00
Rails 正規表現での^,$
例
郵便番号を正規表現でチェックする
エラーが出た実装
user.rbclass User < ActiveRecord::Base validates :zip_code, format: { with: /^\d{3}\-?\d{4}$/ } endこれでアプリケーションを実行すると以下のエラーが出ました。
The provided regular expression is using multiline anchors (^ or $), which may present a security risk. Did you mean to use \A and \z, or forgot to add the :multiline => true option?セキュリティリスクがあるので
- 行頭の
^
は\A
- 行末の
$
は\z
を使え、と言われた。
修正した実装
user.rbclass User < ActiveRecord::Base validates :zip_code, format: { with: /\A\d{3}\-?\d{4}\z/ } endこれでエラーは出なくなりました。
- 投稿日:2020-11-16T15:26:50+09:00
EC2に自動デプロイ後、変更が反映されていないときの対処法
内容としてはタイトルにある通りですが、EC2に自動デプロイを行った際に変更が反映されておらず、めちゃめちゃ焦りました。
今後同じことがあったときに焦ることがないよう、備忘録として残しておきます。環境
AWS EC2
Ruby 2.6.5
Rails 6.0.3.3
capistrano結論
EC2インスタンスを再起動する。
自動デプロイを複数回行うと変更が反映されないことがあるためその際はインスタンスの再起動を行います。
再起動手順についてはこちらの記事にわかりやすく解説されています。最後に
インスタンスの再起動を行った際はデータベースとNginxの再起動も忘れずに行うこと!
1.EC2インスタンスにログイン
ターミナルhoge@MacBook ~ % cd .ssh hoge@MacBook .ssh % ssh -i ダウンロードした鍵の名前.pem ec2-user@該当EC2インスタンスと紐付けたElastic IP2.データーベース(今回はmariaDB)とNginxの再起動を行う
ターミナル[ec2-user@ip-173-41-45-198 ~]$ sudo systemctl restart mariadb [ec2-user@ip-173-41-45-198 ~]$ sudo systemctl restart nginx
- 投稿日:2020-11-16T15:10:43+09:00
【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
- 投稿日:2020-11-16T14:21:03+09:00
日本時刻設定と表示方法まとめ
はじめに
自分用の備忘録
忘れがちなのでまとめてみる手順
日本時刻設定
アプリケーションファイル編集
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.ymlja: time: formats: default: "%Y/%m/%d %H:%M:%S" # あくまでデフォルトなのでよく使いそうなパターンで設定時刻の表示
l(エル)メソッド
sample.rbl sample.created_at => デフォルトの設定で表示表示方法を指定
sample.rbsample.created_at.strftime('20%y年%m月') => "20xx年yy月"おわりに
l(エル)メソッドって響きがかっこいいと思うのは私だけでしょうか
✔︎
- 投稿日:2020-11-16T12:57:10+09:00
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.rbclass DropTableUsers < ActiveRecord::Migration[6.0] def change # 以下1行を追加 drop_table :users end endマイグレーションを実行します。
console> rails db:migrateテーブルが削除され、これで一連の流れが完了となります。
- 投稿日:2020-11-16T11:34:43+09:00
[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メソッドを用いて (" ") とする事で配列間にスペースを開けられる
以上!
- 投稿日:2020-11-16T11:03:05+09:00
downcase,sliceメソッドの応用
「a=abc」,「b=HiAbc」という二つの変数がある時、a,bいずれかの文字列がもう片方の文字列の最後にある場合にTrueを返すプログラムの作成方法です。
今回の場合、aのabcがbの後ろ三文字に存在するためTrueとなります。以下二つのメソッドを使用することで作成することができます。
メソッドの使用例
- 大文字を小文字に変換するためにはdowncaseメソッド
rubya = "AbC" a_down = a.downcase // これにより a_down には abc が入る
- 特定の位置にある文字列などを切り抜きたい場合はsliceメソッド
rubya = "AbC" a_slice = a.slice(1) // これにより a_slice には b が入る回答例
rubydef 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行目でこの後に使用する変数の定義をしています。
rubya_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文で判定しています。
rubyif 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の条件を「または」という演算子を使っていることで、どちらの変数からも確認できるようにしています。(正直最初見たときは訳せなくて落ち込みました。)
- 投稿日:2020-11-16T11:00:07+09:00
[Rails]redirect_toとrenderの違いについて[初心者]
はじめに
いきなりですが、
redirect_to
とrender
って、初めて見たときは、両者の違いがよく分からなかったですよね。
深掘りして、一緒に学んでいきましょう!redirect_toとrenderの違い
redirect_to: HTTPリクエストをサーバーに送り、ユーザーはそこから返ってくるHTMLが表示される。
render: アクションの中で、呼び出すViewファイルを指定するメソッド。言葉だけだと、分かりづらい部分があると思うので、それぞれの処理の流れについて解説します。
それぞれの処理の流れについて
redirect_toの処理の流れ①controllerの処理でredirect_toを実行 ②redirect_toの引数で指定したURLにHTTPリクエスト(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.rbclass BooksController < ApplicationController def index end endアクション内に、何も記載がない場合は、自動的にrenderが実行されます!!
つまり、上の場合だと、render 'index'
が省略されているものと考えられ、このアクションからviews/books/index.html.erb
が呼び出されます。よく見る記載方法
それではここで、よく使われているredirect_toとrenderの記述例について見てみましょう!
controllers/books.rbclass 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の違いについて説明しました!!
解説している中で、自分自身勉強になることが多く、アウトプットの重要性を痛感しています。
- 投稿日:2020-11-16T10:56:12+09:00
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"]として取得できました。
- 投稿日:2020-11-16T10:47:02+09:00
GoogleMapに複数のマーカーを表示する
GoogleMapに対象店舗と周辺店舗を表示させたいと思ったため調べたことを記録します。
当記事は、主にJavascriptの記述についての記載になるためGoogleMapのAPIキー取得等に関する記述は省略します。
条件としては以下のとおりです。・地図の中心に対象店舗が表示される。(マーカーを立てる)
・対象店舗を中心として半径5km以内にある店舗にもマーカーを表示する。
・対象店舗と周辺店舗はマーカーの色を分ける。
・周辺店舗のマーカーをクリックしたときには吹き出しが表示される。
・吹き出しには店舗名が入っており、店名をクリックすると該当店舗の詳細ページに遷移する。開発環境
ruby 2.6.5
Ruby on Rails 6.0.3.3
OS: macOS Catalinaはじめに
GoogleMapApiの使い方に関してこちらの記事がわかりやすくまとめてあり参考にさせていただきました。
Google Maps APIの使い方実装コード
コードは以下のとおりです。
let map; let mainMarker; let marker =[]; let infoWindow = []; function initMap(){ let markerData = gon.places; let latlng = {lat: gon.latitude, lng: gon.longitude}; // 初期位置の指定 map = new google.maps.Map(document.getElementById('map'), { center: latlng, zoom: 14 }); // 初期位置にマーカーを立てる mainMarker = new google.maps.Marker({ map: map, position: latlng }); // 近隣店舗にマーカーを立てる for (var i = 0; i < markerData.length; i++) { const image = "https://maps.google.com/mapfiles/ms/icons/yellow-dot.png"; const id = markerData[i]['id']; // 緯度経度のデータを作成 let markerLatLng = new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']}); // マーカーの追加 marker[i] = new google.maps.Marker({ position: markerLatLng, map: map, icon: image, }); // 吹き出しの追加 infoWindow[i] = new google.maps.InfoWindow({ // 吹き出しに店舗詳細ページへのリンクを追加 content: `<a href=/laundries/${id}>${markerData[i]['name']}</a>` }); markerEvent(i); } // マーカークリック時に吹き出しを表示する function markerEvent(i) { marker[i].addListener('click', function() { infoWindow[i].open(map, marker[i]); }); } } document.addEventListener('turbolinks:load', function(){ initMap(); });1つずつ見ていきます。
1.変数の定義let map; let mainMarker; let marker =[]; let infoWindow = [];対象店舗と周辺店舗のマーカーを分けたかったため2つ変数を用意しました。
marker
には周辺店舗の情報が複数入るため配列にしています。infoWindow
も同様に、各店舗のマーカーに対して吹き出しを付けたいため配列にしています。2.地図の作成〜マーカーを立てる
let markerData = gon.places; let latlng = {lat: gon.latitude, lng: gon.longitude}; // 初期位置の指定 map = new google.maps.Map(document.getElementById('map'), { center: latlng, zoom: 14 }); // 初期位置にマーカーを立てる mainMarker = new google.maps.Marker({ map: map, position: latlng });latlngという変数を定義して店舗の緯度と経度を指定しています。
gon
というのはgemの1つでrailsで定義した変数を、javascriptで使える変数に変換してくれます。
(https://github.com/gazay/gon)
new google.maps.Map
とすることで新しく地図を作成し、オプションで中心地やズームレベルを指定します。
地図の作成に関してはLet'sプログラミングやMaps JavaScriptAPIのドキュメントを参考にさせていただきました。次に作成した地図にマーカーを立てていきます。
こちらもnew google.maps.Marker
でマーカーを作成してどのmapにマーカーをたてるか、どこにマーカーを立てるかを指定することでマーカーを立てることができます。3.周辺店舗にマーカーを立てる + 吹き出しを追加する
for (var i = 0; i < markerData.length; i++) { const image = "https://maps.google.com/mapfiles/ms/icons/yellow-dot.png"; const id = markerData[i]['id']; // 緯度経度のデータを作成 let markerLatLng = new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']}); // マーカーの追加 marker[i] = new google.maps.Marker({ position: markerLatLng, map: map, icon: image, }); // 吹き出しの追加 infoWindow[i] = new google.maps.InfoWindow({ // 吹き出しに店舗詳細ページへのリンクを追加 content: `<a href=/laundries/${id}>${markerData[i]['name']}</a>` }); markerEvent(i); } // マーカークリック時に吹き出しを表示する function markerEvent(i) { marker[i].addListener('click', function() { infoWindow[i].open(map, marker[i]); }); }行っていることとしては
let markerData = gon.places;
で定義した変数の値を1つずつ取り出してマーカーと吹き出しを追加しています。
そしてマーカーの色を変えたかったためimage
という定数にアイコンを定義してicon: image
で定義したアイコンを使用しています。
new google.maps.LatLng({lat: markerData[i]['latitude'], lng: markerData[i]['longitude']});
とすることでそれぞれの要素の緯度経度に対してマーカーを立てるように指定します。続いて吹き出しを追加します。
content
で吹き出しの中に入れる文字を指定できます。
今回は各店舗名を表示+クリックすることで詳細ページに遷移をさせたかったため、aタグを使いました。
最後に吹き出しを作成しただけでは表示されないためaddListener
を利用してマーカーがクリックされたときに吹き出しが開くようにしました。以上、GoogleMapに複数マーカーを表示する方法でした。
- 投稿日:2020-11-16T01:04:04+09:00
文字のだるま落とし(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した文字列を除外した文字列を出力した
以上!
- 投稿日:2020-11-16T00:13:16+09:00
deviseでログアウト時に確認ページに遷移させる
はじめに
railsでdeviseをしようしたログインログアウト機能を実装していて、
ログアウトした際に確認ページに遷移するように実装していたところ
つまずいたので記事にしようと思います。
さらに良い方法とかがあればコメント欄か編集リクエストに記載願います。バージョン情報
- 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を記述 ← コード記入 endViewの作成
私は
users/destoroy
にdestroy.html.hamlファイルを作成しました。%h1 ログアウトしました。 = link_to "マイページに戻る", "/"コントローラーのカスタマイズ
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の理解がないと感じました。
冷静になって考えたらすぐに解決できた問題でした。
そして、マークダウン の書き方にも苦戦してます。
記事書くのにすごい時間かかってしまっているので早くなれなければ…
もっとアウトプットしていきます。しょうま
- 投稿日:2020-11-16T00:03:52+09:00
Git ブランチ作成・削除・便利コマンド【Gitコマンド】
ブランチとは
ブランチ(branch)は、1つのプロジェクトから分岐させることにより、プロジェクト本体に影響を与えずに開発を行える機能のことを言います。つまり変更などの履歴を分岐してそれぞれ記録していくということですね!
分岐したブランチはほかのブランチの影響を受けないので、同じレポジトリの中で複数の変更(実装)を同時に進めていくことができます。
このように複数のブランチで開発を行い、それぞれのブランチで異なる機能の実装を行い実装が完了したタイミングなど(チームや作業者で異なります)で、統合(merge)して開発を進めていきます。
ブランチの作成関連
※ブランチを移動する前にはcommitが必要になります。commitせずに移動しようとするとcommitしてください的なerrorが出るのでその際は潔くcommitしてから移動しましょう。
※[ ]は省いて入力してください#現在いる自分のブランチを確認(出力された中の*がついているブランチが現在のブランチです) $ git branch #ローカル間でのブランチの移動 $ git checkout [移動したい先のブランチ名] #新規ローカルブランチの作成 $ git branch -b [作成したいブランチ名] #リモートにあるブランチを元に新しくローカルブランチを作成 $ git branch [新しいブランチ名] [元にするブランチ名]ブランチの削除
1⃣マージ済みのブランチの削除(マージしてないとエラーが出ます、してない場合は2⃣) $ git branch -d [ブランチ名] 2⃣ローカルブランチを問答無用で削除 $ git branch -D [ブランチ名]ブランチ名の変更
#ローカルのブランチ名の変更(hogeからfugaなら[hoge] [fuga]) $ git branch -m [現状のブランチ名] [新しいブランチ名] #現在使用しているブランチ名の変更(fugaにしたければ[fuga]) $ git branch -m [新しいブランチ名]統合ブランチへのプルリク&マージ後のローカルへの反映
#1⃣反映させたいブランチへ移動(統合ブランチがhogeなら[ブランチ名]はhoge) $ git checkout [ブランチ名] #2⃣リモートの最新版をローカルにプルする(1⃣に合わせると[ブランチ名]はhoge) $ git pull origin [ブランチ名] #3⃣自分の作業ブランチへの反映(1⃣2⃣に合わせると[ブランチ名]はhoge) $ git checkout [自分の作業ブランチ名] $ git merge origin/[ブランチ名] ※2⃣でプルした後に新しくブランチを作成したい場合 $ git checkout -b [作成したいブランチ名]空コミット(筆者は良く忘れるコマンドなのでメモ代わりに、、)
空コミットは新規ファイルや修正ファイルが皆無でもコミットすることができます。
ブランチを確認するときなどに使います。$ git add -A $ git commit --allow-empty -m "コミットメッセージ" #リモートレポジトリにプッシュ $ git push origin [ブランチ名]最後に
今回は初心者である筆者による筆者のための備忘録要素がハンパないですが、参考になれば嬉しいです。
他にもよく使うコマンドがあれば追加していきたいと思います。