プログラミング学習記録

主にRuby on Railsを使ったプログラミングを学んでいます。

i18nによる日本語化対応

i18nrails-i18n

Railsでは様々な言語に対応するために、デフォルトでi18nというgemが導入されている。
以下Railsガイド Rails 国際化 (i18n) APIより引用

 RubyI18n (国際化・多言語化を意味する internationalization を短縮したもの) gemはRuby on Rails 2.2以降からRailsに同梱されています。このgemは、アプリケーションの文言を英語以外の 別の1つの言語に翻訳 する機能や 多言語サポート 機能を簡単かつ拡張可能な方式で導入するためのフレームワークを提供します。

つまり、導入のためのフレームワークであり、これだけでは日本語化対応ができない。
翻訳するためのファイルを用意したり、読み込むようにパスを設定したりする必要があるということ。

rails-i18nというgemがあり、これには様々な言語の翻訳ファイルが入っている。
rails-i18nをインストールし、アプリケーションのデフォルト言語を日本語に設定することで必要最低限(バリデーションエラー時のメッセージや、月や曜日など)の日本語を使用できるようになる。
ただし、必要最低限の日本語対応となるので、個別のアプリケーションのモデルやビューなどで使用している用語の日本語を設定したい場合は、別途YAML形式の翻訳ファイルを作成する必要がある。(拡張子は.yml 日本語のファイルなのでファイル名はja.ymlにする)

日本語化

まず、Railsのデフォルトの言語を日本語に設定する必要がある。
config/application.rb

module AppName
  class Application < Rails::Application
    #このクラス内に書く
    config.i18n.default_locale = :ja
  end
end

次にGemfileに

gem 'rails-i18n'

を追加しbundle installする。
このgemを導入することによって、Railsを日本語で使う場合のデフォルトのロケールファイル

rails-i18n/ja.yml at master · svenfuchs/rails-i18n · GitHub

をダウンロードしてローカル(config/locales以下にja.ymlとして)に配置しなくても、gem内部にある日本語の国際化設定ファイルを参照するようになる。

ViewとModelの日本語化

config/application.rb

module AppName
  class Application < Rails::Application
    config.i18n.default_locale = :ja
    # 読み込む対象のファイルを増やす
    config.i18n.load_path += Dir[Rails.root.join('config/locales/**/*.{rb,yml}').to_s]
  end
end

と記載して、i18n対応ファイルをlocales以下に配置することで、多言語対応させたviewを表示させることができる。
必要最低限の日本語対応に加え、モデルに関連する内容のみを記載するファイル、viewの表示の内容のみを記載するファイルなどと、i18nの定義ファイルを複数に分けて設定できる。
※この設定の変更を反映させるためには、サーバーの再起動が必要。

開発の初期段階から、作成したモデル名やカラムの翻訳ファイルを作成しておくとよい。
ActiveRecordとViewで翻訳ファイルを分けて作成すると管理しやすい。

(例)
locales以下にviews,activerecordディレクトリを作り、そこに配置する。
config/locales/views/ja.yml
config/locales/activerecord/ja.yml

翻訳ファイルはアプリケーションが大きくなるほど単一のファイルでの管理が難しくなる。その場合はviewの翻訳ファイルさらに細かく分け、コントローラごとに分けるなどする。(可読性はもちろん、後述するlazy-lookup記法を使う為の配慮)

翻訳ファイルの書き方例

config/locales/views/ja.yml

ja:
  defaults:
    login: 'ログイン'
    logout: 'ログアウト'
  users:
    new:
      title: 'ユーザー登録'
  user_sessions:
    new:
      title: 'ログイン'

config/locales/activerecord/ja.yml

ja:
  activerecord:
    models:
      user: 'ユーザー'
      post: '投稿'
    attributes:
      user:
        email: 'メールアドレス'
        password: 'パスワード'

tメソッド

viewで翻訳ファイルに記載した用語を使用する場合は、tメソッドを使う。(tはtranslateのt)
上の翻訳ファイルの書き方例の場合

<%= t('defaults.login') %>

ログインと表示できる。

<%= t('users.new.title') %>

ユーザー登録と表示できる。

lazy-lookup記法

<%= t('users.new.title') %>

と書く場合、app/views/users/new.htmlのようにビューテンプレートの階層と翻訳ファイルの階層構造が同じ場合は

<%= t('.title') %>

のように省略して書くことができる。この書き方がlazy-lookup記法。

参考サイト

Rails 国際化 (i18n) API - Railsガイド

[初学者]Railsのi18nによる日本語化対応 - Qiita

RailsでI18nを使ってViewに表示する文字列を一元管理する - Qiita

ruby - Railsのファイルの変更の自動読み込みについて - スタック・オーバーフロー

sorceryを使用して、ユーザー登録・ログイン機能を作成

sorceryとは

Railsに認証機能を実装するためのライブラリ。

インストール

Gemfileに

gem 'sorcery'

と書きターミナルでbundle installを実行。

初期設定

$ bundle exec rails g sorcery:install

これで必要最低限なファイルが作成される。

  • config/initialize/sorcery.rb
  • db/migrate/20xxxxxxxx_sorcery_core.rb
  • app/models/users.rb

など

マイグレーションファイル(db/migrate/20xxxxxxxx_sorcery_core.rb)には、必要最低限のカラム(eメール、パスワード等)がすでに定義されている。ここに適宜必要なカラム(例えば氏名など)を追加しデータベースに反映させる。

bundle exec rails db:migrate

または

rake db:migrate

を実行する。
(ちなみに、bundle execはBundlerが管理するGemを利用できる状態でコマンドを実行するということ)

コントローラを設定

$ bundle exec rails g sorcery:installを実行した事で、app/models/users.rbは作成されているが、usersコントローラは自分で作成する。

$ rails g controller users

を実行。
app/controllers/users_controller.rb

class UsersController < ApplicationController
  # ...
  private

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

また

$ rails g controller UserSessions new create destroy

でuser_sessions.controller.rbを作る。
app/controllers/user_sessions.controller.rb

class UserSessionsController < ApplicationController
  def create
    @user = login(params[:email], params[:password])

    if @user
      redirect_back_or_to(:users, notice: 'Login successful')
    else
      flash.now[:alert] = 'Login failed'
      render :new
    end
  end

  def destroy
    logout
    redirect_to(:users, notice: 'Logged out!')
  end
end

リダイレクト先やフラッシュなどは仕様に合わせて変更すれば良い。
usersコントローラでユーザー情報、user_sessionsコントローラでログイン/ログアウトに関する情報を扱う。

ルーティングを設定

config/routes.rb

root 'users#index'
resources :users

get 'login', to: 'user_sessions#new'
post 'login', to: 'user_sessions#create'
delete 'logout', to: 'user_sessions#destroy'

例えば、users_contoroller.rbでnewアクションとcreateアクションしか設定しないのであれば、
resources :users, only %i[new create]という書き方もできる。

※リソースベースのルーティングを設定すると、URL用のヘルパーを使用することができるようになる。(login_pathのようなやつ)

モデル

app/models/users.rb

class User < ActiveRecord::Base
  authenticates_with_sorcery!

  validates :password, length: { minimum: 3 }, if: -> { new_record? || changes[:crypted_password] }
  validates :password, confirmation: true, if: -> { new_record? || changes[:crypted_password] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes[:crypted_password] }

  validates :email, uniqueness: true
end

ログインフォーム

入力用のフォーム(パスワードがデータベースで暗号化される前にパスワードを保持しておくためのもの)
app/views/users/_form.html.erb

<div class="field">
   <%= form.label :password %><br />
   <%= form.password_field :password %>
</div>
<div class="field">
   <%= form.label :password_confirmation %><br />
   <%= form.password_field :password_confirmation %>
</div>

ログイン用のフォーム
app/views/user_sessions/new.html.erb

<h1>Login</h1>

<%= render 'form' %>

<%= link_to 'Back', users_path %>

app/views/user_sessions/_form.html.erb

<%= form_with url: login_path, method: :post do |f| %>
  <div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password %>
  </div>
  <div class="actions">
    <%= f.submit "Login" %>
  </div>
<% end %>

あとは、app/views/layouts/application.html.erbにナビゲーションリンクとフラッシュメッセージを表示するためのビューを用意する(もちろんフラッシュ用のパーシャルを作っても良い)。そこにログアウト用のリンクを作る。
注意:logoutのリンクでmethod: :deleteを指定し忘れないようにする。
そして、ユーザー登録用のビューをapp/views/users/new/html.erbとして作成する。

ログインしていないユーザーの権限を制限

sorceryには便利なメソッドが用意されている。
require_login(sorceryが用意しているので自分で定義しなくて良い)
ログインしていない状態だとログインページへリダイレクトさせる

# app/controllers/application_controller.rb
# 全てのアクションが呼び出される前にrequire_loginメソッドを呼び出す
before_action :require_login

# app/controllers/users_controller.rb
# users_controllerのindex,new,createアクションが呼び出されるときは、require_loginを呼び出さない
skip_before_action :require_login, only: [:index, :new, :create]

# app/controllers/user_sessions_controller.rb
skip_before_action :require_login, only: [:new, :create]

参考サイト

Simple Password Authentication · Sorcery/sorcery Wiki · GitHub

Gitコマンドのまとめ

Gitとは

簡単にいうと、ソースコードのバージョン管理をするためのツール。

Gitコマンド

$ git init

これでGitリポジトリが作成され、カレントディレクトリに.gitディレクトリが作られる。

  • Gitの状態を確認する
$ git status

  • git add
    ファイルを追加する場合。ファイルを複数追加する場合は、スペースで区切る。
$ git add ファイル名

特定のディレクトリ以下のファイルを全て追加

$ git add ディレクトリのパス/*

全ての変更をまとめて追加

$ git add -A

もしくは

$ git add .

  • git commit
    インデックスに追加された変更内容をコミットする
$ git commit -m 'コミットメッセージ'

チーム開発の場合、変更内容がわかりやすいコミットメッセージを書く必要がある。


  • 過去のコミット一覧を表示
$ git log

コミットしたユーザー名・メールアドレス、コミット日時、コミットIDが表示される。
終了する場合はターミナルにqと打つ。


  • 過去のコミット内容・差分を表示
$ git show

複数名で手分けして開発を進める際ブランチを作り、コミットしたいものはマージすることで反映される。
- ブランチを作成

$ git branch ブランチ名

ブランチを切り替えるには

$ git branch ブランチ名

ブランチの作成とそのブランチへの切り替えを同時に実行できるコマンドが以下

$ git checkout-b ブランチ名

  • マージ
    現在いるブランチに別のブランチをマージ
$ git merge ブランチ名

  • ブランチの削除
    マージされていないブランチは削除されないコマンド
$ git branch -d ブランチ名

マージされていないブランチも削除できるコマンド

$ git branch -D ブランチ名

  • git resetコマンド
    主に3つのオプションがあり、それぞれ取り消しの範囲が違う
    コミットのみ取り消す場合
$ git reset --soft コミットID

コミットとインデックスの変更を取り消す。作業ツリーにはコードに変更が残された状態になる。

$ git reset --mixed コミットID

変更を全て取り消す。一度取り消すと元に戻せない(リモートリポジトリにコミットがある場合を除く)

$ git reset --hard コミットID

※作業中の変更の取り消し
git reset --hard コミットIDを使えば良い。
ただし、Untrackedな状態のファイルはGitの操作の対象とならないため消せない。git reset時に変更を戻したい場合は、一度git add ファイル名 -NでGit管理下におく必要がある。

参考サイト

Git コマンドリファレンス(日本語版)

Gitコマンド早見表 - Qiita

基本的なGitコマンドまとめ - Qiita

[git] 戻したい時よく使っているコマンドまとめ - Qiita

誰にとってもわかりやすいGitのコミットメッセージを考える | Tips Note by TAM

rails consoleとテーブル Rails入門②

8月2日はほぼ復習で終わり、8月3日はRUNTEQのイベント「Vimハラ」に参加した。
8月4日、今日はRails入門②「rails consoleを制する者はRailsを制する」と入門フェーズ総復習をした。rails consoleを使って複数のモデルを作り、紐付ける事を学んだ。 学んだことをまとめてみる。

課題ではusersテーブルとtasksテーブル、categoriesテーブル、tasksテーブルとcategoriesテーブルを繋ぐ中間テーブルのtask_categoriesテーブルを作った。そこから、rails consoleを起動させて、user情報のレコード、そのuserのtask、categoryを作り、taskとcategoryを紐付ける事をした。


モデル作成時に外部キーを持たせるには
rails g model モデル名B(単数形) キー1:データ型 キー2:データ型 ... 紐付けたいモデル名(単数形)A:references
のようにターミナルに打ち込む。
こうして作ったモデルBのマイグレーションファイルに、外部キー制約を自動で作ってくれて、Rails6系だとNOT NULL制約を自動でつけてくれる。


Bに紐付いたAを作る
まずアソシエーションを定義する。
app/models/モデル名A.rbに、has_many :Bs(複数形), dependent: :destroy
を追加。 rails consoleを使い、Aのテーブルからidが例えば1のAを探し、aに代入(インスタンスを作る)
a.Bs.create(〇〇: □□)
で作れる。B.create(〇〇: □□, A_id: 1)としても作れるが、上の方法の方がわかりやすい。


中間テーブル
モデル同士を一対一で紐付けたい場合、例えばAとBだとBにA_idを持たせれば良いが、モデルに多対多の関係がある場合は、中間テーブルを作れば良い。(課題ではtaskとcategoryに中間テーブルを持たせた。userとtaskは一対多。)


has_many :〇〇, through: :中間テーブル名
これを中間テーブルで繋がったそれぞれのテーブルのapp/models/テーブル名.rbに入れる。〇〇には他方のモデルの複数形を入れる。
こうすると、rails consoleで情報を取得した時、関係するテーブルの情報も取得できる。これをしないと外部キーのidしか取得できず、同時に扱いたい場合に手数が多くなる。(例えば、タスクとそのカテゴリを同時に表示させたい場合など。task.categoriesで一発でカテゴリ名を表示できる。カテゴリに関しても同様。category.tasksでそのカテゴリのタスクを表示できる。)

バグ修正 Rails入門②

8月1日は、Rails入門②の課題「バグと友達になろう」とHTTP演習をした。 復習のため学習内容をまとめてみる。
まずエラー修正課題だが、解決できていなかったエラーを書き出してみると、
1 NoNameError undefined local variable or method 'about_url'
2 NoMethodError undefined method 'log_in'
3 Failure パスワードが空欄でログインできないという旨の内容

解決の為に試みた事

3はソースコードを確認するとparams.require(:user).permit(:name, :email, :password_confirmation)となっており、ストロングパラメーターに:passwordが抜けていることが原因だとわかった。慣れていない為、関係していそうなコントローラーやビューのコードとしばらくにらめっこしてようやく発見。


1はまずroutes.rbを見てみるとget 'static_pages/about'はあるが、アクションを呼び出すto: 'static_pages#about'がない。 これを記載しなくてもページは表示できるんだっけ?いやできなかったはず……つまり、ここは間違いだな。しかし、get 'static_pages/about'なわけだから、このままだと/static_pages/aboutというURLになるはず。about_urlというヘルパーメソッドで指定できない。routes.rbを書き換えるべきか、テストコードをabout_static_pages_urlにするべきなのか……。
また、about.rbはあるがこれではページの表示はできないのでは?(後から振り返ればここを修正しようとしてファイルの名前をabout.erbに変更していたが、これは間違いで正しくはabout.html.erbだった。確認大事。)
さらにstatic_pages.controller.rbにaboutアクションがないぞ? →追加する
よし、これで動くはず →動かず(前述の事が原因)


2は、log_inなんてメソッドどこにも定義されてないぞ?よしlog_inメソッドをわざわざ定義しなくてもsession[:id]をuser.idに入れればいいのかな? →テスト通らず(実は見つけられなかっただけでhelpersフォルダ内のsession_helper.rbに定義されている。また、session[:user_id]とするべきだった。)


以上こんな方法でバグ修正していては全く効率的ではなく無駄に時間がかかる上、見落としも起きてしまうだろう。
解答の動画があるのだが、エラーを特定していく過程がとてもスマートで効率的で感動した!目からウロコが落ちた。ので、その方法を自分なりに言語化して復習しようというのが今回の本題!

エラー修正の心得

  • テスト実行時のエラーメッセージとソースコードだけ見て進めない
  • ブラウザの検証ツールを使う
  • binding.irb
  • grep -r 検索したいワード ディレクトリ とターミナルでコマンドを打つと、指定したディレクトリ以下にあるファイルで検索したいワードを全体検索

例えば、3のログインできないケース。
まず実際にブラウザでログインを試す。→より詳しいエラーメッセージが表示される。 (実際にブラウザで試すメリット!)
それによるとパスワードが空欄、パスワードが規定文字数以下 とのこと。 パスワードが正しく送信できているかを確認する必要がある。ここでブラウザの検証ツール(ディベロッパツール)の出番だ。
ネットワークタブでフォームから送信された内容を確認できる。(その他にも様々なことを確認できるので使わない手はない)
viewのフォームからの送信は問題ないとわかる。つまり送信された情報がsaveできていないということ。
saveを実行する手前で一旦処理を止め検証する。ソースコードをエディタなどで開き、saveをする行の直前にbinding.irbと加える。それからまたブラウザでログインしてみると、saveする直前で処理が止まり、その時点での@userを出力させたりできる。
こうして実際にアプリケーションの挙動を探っていく事で、どうすればいいかわからない場合でもエラーの原因を探ることができる。

メモ

  • command + option + I でディベロッパツールを開ける
  • .valid?インスタンスがバリデーションを通るか確認
  • pryなどのバグ修正に役立つGemをインストールしてもよいが、binding.irbは元々Railsに入っているので便利
  • VSCodeで全体検索をかけるときは、command + shift + F
  • 空のメソッドを定義するときは、def メソッド名; endと一行で書くことで空であると明示でき、他の人がソースコードを読んだ時に誤解が生じにくい

2021-7-31

現在RUNTEQに通い始めて19日目。
カリキュラムのRails入門②の「バグと友達になろう」に取り組んでいる最中。バグの修正課題。
昨日は課題となるコードをGithubリポジトリをクローンし、環境構築でrbenvでRubyのバージョン、nodenvでNode.jsのバージョンをそれぞれ指定し、Yarnをインストールをしたりした。

参考にしたサイト

docs.github.com

github.com

blog-tomdom.site

obel.hatenablog.jp

肝心のバグ修正だが、rails test:systemを実行し見つけたErrorとFailureが計4つ。
1つ目は比較的簡単に見つかった。
NoMethodError: undefined method `authenticate' for nil:NilClass
とエラーが出た箇所の変数にぼっち演算子がついておらず、nilの場合エラーになるということ。
その他のエラーがなかなか手強く色々試すもバグ修正できなかった。
エラーが出ている箇所はわかるものの、どう変更を加えればいいかわからない状態。
pryなどのデバッグツールを使ってみようと思うが……理解が浅く使いこなせるだろうか?
明日は講師に質問してみようと思う。