sk

開発で得たこと

ユーザープロフィールの作成

この記事で行うこと

ユーザーが現在のパスワード(current_password)なしでプロフィールを登録・編集できるようにする。 f:id:sksksksksk:20180729031314p:plain

使うもの

前提

予約機能の作成 - さがえもん まで完了していること。

現場では?

ユーザープロフィールには登録される項目が多いため、別テーブルで切り分けて保存します。

実装手順

プロフィールテーブルの作成

userテーブルとは別にprofileテーブルを作成します。 userとprofileテーブルは以下のような関連性を持ちます。 f:id:sksksksksk:20180729023437p:plain

  • profileの作成
$ rails g model Users::Profile

app/models/users/profile.rb

class Users::Profile < ApplicationRecord
  # -------------------------------------------------------------------------------
  # Relations
  # -------------------------------------------------------------------------------
  belongs_to :user
end

app/models/user.rb

  # -------------------------------------------------------------------------------
  # Relations
  # -------------------------------------------------------------------------------
  has_many :deliveries, dependent: :destroy
  has_one :profile, class_name: 'Users::Profile', dependent: :destroy
  accepts_nested_attributes_for :profile

profileはmodel/users/profile.rbのようにmodel直下にはないので class_name で指定します。 accepts_nested_attributes_for でuserを編集する時に一緒にprofile(子レコード)を保存・更新することができるようになります。

db/migrate/20180728140656_create_users_profiles.rb

class CreateUsersProfiles < ActiveRecord::Migration[5.1]
  def change
    create_table :users_profiles do |t|
      t.belongs_to :user, foreign_key: true
      t.integer :gender
      t.timestamps
    end
  end
end

データを作成しましょう。

$ rails db:migrate

プロフィール入力フォームの作成

プロフィール変更ページにgender(性別)用のフォームを作成します。

性別は男性・女性どちらかをgenderカラムに持ちます。 この際genderはbooleanでもいいのですが、enumという便利な機能があるのでこちらを使っていきます。

enumの詳細は enumまとめ - さがえもん を読んでみてください。

app/models/users/profile.rb

  belongs_to :user
  # -------------------------------------------------------------------------------
  # Enumerables
  # -------------------------------------------------------------------------------
  enum gender: {
    woman:  1000,
    man:    2000
  }

プロフィール画面へ遷移する前にprofileをbuildさせてあげます。 buildしてあげないと field_for のフォームが表示されません。

app/controllers/users/registrations_controller.rb

  def edit
    resource.build_profile if resource.profile.nil?
    super
  end

フォームは以下のように書くことでシンプルに表現することができます。 app/views/users/registrations/edit.html.haml

    ・・・・・
    .form-group
      = f.label :password_confirmation, class: 'col-sm-2 control-label'
      .col-sm-10= f.password_field :password_confirmation, class: 'form-control', autocomplete: 'off'
    // プロフィール
    = f.fields_for :profile do |profile_f|
      .form-group
        = profile_f.label :gender, class: 'col-sm-2 control-label'
        .col-sm-9.col-xs-12
          - Users::Profile.genders_i18n.invert.each do |key_i18n, value|
            %label.radio-inline
              = profile_f.radio_button :gender, value
              = key_i18n
    .form-group
      = f.label :current_password, class: 'col-sm-2 control-label'
   ・・・・・

f:id:sksksksksk:20180729024719p:plain

  • current_passwordなしで登録できるように変更する

こちらは railsのdeviseまとめ - 現場の開発 で説明しています。ご覧ください。

app/views/users/registrations/edit.html.haml

  .form-group # 削除
    = f.label :current_password, class: 'col-sm-2 control-label' # 削除
    .col-sm-10= f.password_field :current_password, class: 'form-control', autocomplete: 'off' #削除

参考

github.com

ソースコード

github.com

質疑応答

コメントしてください。 こんなコード見たいというような要望あれば記事にしますのでそちらもコメントかメッセージください。

railsのdeviseまとめ

アカウント登録時に許可するパラメータの設定

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

リソースの更新時のストロングパラメータを変更する

app/controllers/users/registrations_controller.rb

  # protected
  ・・・・
  def account_update_params
    params.require(:user).permit(
      :email,
      :password,
      :password_confirmation,
      :current_password,
      profile_attributes: %i(id gender)
    )
  end
  ・・・

パスワードなしでリソースを更新する

app/controllers/users/registrations_controller.rb

  # protected
  ・・・・
  #
  # アカウント情報の更新時にはパスワードは不要
  #
  def update_resource(resource, params)
    resource.update_without_password(params)
  end
  ・・・

日本語化

config/locales/devise.ja.yml

ja:
  devise:
    confirmations:
      confirmed: "アカウントが確認されました。ログインしています。"
      send_instructions: "アカウントの確認方法を数分以内にメールでご連絡します。"
      send_paranoid_instructions: "ご登録のメールアドレスが保存されている場合、アカウントの確認方法をメールでご連絡します。"
    failure:
      already_authenticated: "既にログインしています。"
      inactive: "Your account is not activated yet."
      invalid: "Invalid email or password."
      locked: "アカウントがロックされています。"
      last_attempt: "あなたのアカウントがロックされる前に、もう1つの試みを持っています。"
      not_found_in_database: "メールアドレスまたはパスワードが無効です。"
      timeout: "一定時間が経過したため、再度ログインが必要です"
      unauthenticated: "ログインまたは登録が必要です。"
      unconfirmed: "本登録を行ってください。"
    mailer:
      confirmation_instructions:
        subject: "アカウントの登録方法"
      reset_password_instructions:
        subject: "パスワードの再設定"
      unlock_instructions:
        subject: "アカウントのロック解除"
    omniauth_callbacks:
      failure: "%{kind} から承認されませんでした。理由:%{reason}"
      success: "%{kind} から承認されました。"
    passwords:
      no_token: "このページにアクセスする事が出来ません。正しいURLでアクセスしている事を確認して下さい。"
      send_instructions: "パスワードのリセット方法を数分以内にメールでご連絡します。"
      send_paranoid_instructions: ""
      updated: "パスワードを変更しました。"
      updated_not_active: "パスワードを変更しました。"
    registrations:
      destroyed: "アカウントを削除しました。またのご利用をお待ちしております。"
      signed_up: "アカウント登録を受け付けました。"
      signed_up_but_inactive: "アカウントは登録されていますが、アクティブになっていないため利用できません。"
      signed_up_but_locked: "アカウントは登録されていますが、ロックされているため利用できません。"
      signed_up_but_unconfirmed: "確認メールを登録したメールアドレス宛に送信しました。リンクを開いてアカウントを有効にして下さい。"
      update_needs_confirmation: "アカウント情報が更新されました。更新の確認メールを新しいメールアドレス宛に送信しましたので、リンクを開いて更新を有効にして下さい。"
      updated: "アカウントが更新されました。"
    sessions:
      signed_in: "ログインしました。"
      signed_out: "ログアウトしました。"
    unlocks:
      send_instructions: "アカウントのロックを解除する方法を数分以内にメールでご連絡します。"
      send_paranoid_instructions: "アカウントが存在する場合、ロックを解除する方法をメールでご連絡します。"
      unlocked: "アカウントのロックが解除されました。ログインしています。"
  errors:
    messages:
      already_confirmed: "は既に登録済みです。ログインしてください"
      confirmation_period_expired: "%{period}以内に確認する必要がありますので、新しくリクエストしてください。"
      expired: "有効期限切れです。新規登録してください。"
      not_found: "は見つかりませんでした。"
      not_locked: "ロックされていません。"
      not_saved:
        one: "エラーにより、この %{resource} を保存できません:"
        other: "%{count} 個のエラーにより、この %{resource} を保存できません:"

プロフィールとは別でパスワードのみ編集する場合

  # PUT /resource
  def update
    if params[:user][:password]
      if resource.update_with_password(user_password_params)
        sign_in(resource, bypass: true)
        redirect_to users_settings_password_url, notice: t('devise.passwords.updated')
      else
        render 'users/settings/password'
      end
    else
      super
    end
  end
  ・・・・・
  #
  # パスワード更新時のパラメータ
  #
  def user_password_params
    params.require(:user).permit(
      :password,
      :password_confirmation,
      :current_password
    )
  end

参考

github.com

ローカライズ(i18n)まとめ

localeを日本語(:ja)に変更する

config/application.rb

config.i18n.default_locale = :ja
config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}')]

翻訳ファイルの作成と翻訳の記入

以下のファイルを作成しましょう。 config/application.rbで日本語設定を施した後にja.ymlファイルが存在しないと I18n::InvalidLocale: :ja is not a valid locale エラーが発生しますので気をつけてください。

config/locales/ja.yml

ja:
  activerecord:
    errors:
      messages:
        record_invalid: "バリデーションに失敗しました: %{errors}"
        restrict_dependent_destroy:
          has_one: "%{record}が存在しているので削除できません"
          has_many: "%{record}が存在しているので削除できません"
  date:
    abbr_day_names:
    - - - - - - - abbr_month_names:
    -
    - 1月
    - 2月
    - 3月
    - 4月
    - 5月
    - 6月
    - 7月
    - 8月
    - 9月
    - 10月
    - 11月
    - 12月
    day_names:
    - 日曜日
    - 月曜日
    - 火曜日
    - 水曜日
    - 木曜日
    - 金曜日
    - 土曜日
    formats:
      default: "%Y/%m/%d"
      long: "%Y年%m月%d日(%a)"
      short: "%m/%d"
    month_names:
    -
    - 1月
    - 2月
    - 3月
    - 4月
    - 5月
    - 6月
    - 7月
    - 8月
    - 9月
    - 10月
    - 11月
    - 12月
    order:
    - :year
    - :month
    - :day
  datetime:
    distance_in_words:
      about_x_hours:
        one: 約1時間
        other: 約%{count}時間
      about_x_months:
        one: 約1ヶ月
        other: 約%{count}ヶ月
      about_x_years:
        one: 約1年
        other: 約%{count}年
      almost_x_years:
        one: 1年弱
        other: "%{count}年弱"
      half_a_minute: 30秒前後
      less_than_x_minutes:
        one: 1分以内
        other: "%{count}分未満"
      less_than_x_seconds:
        one: 1秒以内
        other: "%{count}秒未満"
      over_x_years:
        one: 1年以上
        other: "%{count}年以上"
      x_days:
        one: 1日
        other: "%{count}日"
      x_minutes:
        one: 1分
        other: "%{count}分"
      x_months:
        one: 1ヶ月
        other: "%{count}ヶ月"
      x_years:
        one: 1年
        other: "%{count}年"
      x_seconds:
        one: 1秒
        other: "%{count}秒"
    prompts:
      day:hour:minute:month:second:year:errors:
    format: "%{attribute}%{message}"
    messages:
      accepted: を受諾してください
      blank: を入力してください
      present: は入力しないでください
      confirmation: と%{attribute}の入力が一致しません
      empty: を入力してください
      equal_to: は%{count}にしてください
      even: は偶数にしてください
      exclusion: は予約されています
      greater_than: は%{count}より大きい値にしてください
      greater_than_or_equal_to: は%{count}以上の値にしてください
      inclusion: は一覧にありません
      invalid: は不正な値です
      less_than: は%{count}より小さい値にしてください
      less_than_or_equal_to: は%{count}以下の値にしてください
      model_invalid: "バリデーションに失敗しました: %{errors}"
      not_a_number: は数値で入力してください
      not_an_integer: は整数で入力してください
      odd: は奇数にしてください
      required: を入力してください
      taken: はすでに存在します
      too_long: は%{count}文字以内で入力してください
      too_short: は%{count}文字以上で入力してください
      wrong_length: は%{count}文字で入力してください
      other_than: は%{count}以外の値にしてください
    template:
      body: 次の項目を確認してください
      header:
        one: "%{model}にエラーが発生しました"
        other: "%{model}に%{count}個のエラーが発生しました"
  helpers:
    select:
      prompt: 選択してください
    submit:
      create: 登録する
      submit: 保存する
      update: 更新する
  number:
    currency:
      format:
        delimiter: ","
        format: "%n%u"
        precision: 0
        separator: "."
        significant: false
        strip_insignificant_zeros: false
        unit:format:
      delimiter: ","
      precision: 3
      separator: "."
      significant: false
      strip_insignificant_zeros: false
    human:
      decimal_units:
        format: "%n %u"
        units:
          billion: 十億
          million: 百万
          quadrillion: 千兆
          thousand:trillion:unit: ''
      format:
        delimiter: ''
        precision: 3
        significant: true
        strip_insignificant_zeros: true
      storage_units:
        format: "%n%u"
        units:
          byte: バイト
          gb: GB
          kb: KB
          mb: MB
          tb: TB
    percentage:
      format:
        delimiter: ''
        format: "%n%"
    precision:
      format:
        delimiter: ''
  support:
    array:
      last_word_connector:two_words_connector:words_connector:time:
    am: 午前
    formats:
      default: "%Y年%m月%d日(%a) %H時%M分%S秒 %z"
      long: "%Y/%m/%d %H:%M"
      short: "%m/%d %H:%M"
    pm: 午後

以下のサイトから入手できます。 github.com

config/locales/resource/users.ja.yml

ja:
  activerecord:
    models:
      user: ユーザー
      user_profile: プロフィール
    attributes:
      user:
        id: ユーザーID
        email: メールアドレス
        password: パスワード
        password_confirmation: パスワード(確認用)
        sign_in_count: ログイン回数
        created_at: 登録日時
        last_sign_in_at: 前回ログイン時刻

      users/profile:
        user: ユーザー
        gender: 性別

Railsサーバの再起動をしてください。

動作確認

> I18n.t('activerecord.models.user') => "ユーザー"

enumまとめ

enumとは?

enumは列挙型は列挙型と呼ばれています。 列挙型は整数が割り当てられた文字を順番に出力していくことができます。

属性名、属性の値(ハッシュ)で指定することでenumを定義することができます。

enumのすごいところ

可読性が上がります。 例えば、statusカラムがあるとして、そのstatusカラムは以下の属性と値を持つと表現できます。

pending: 1000 # 審査待ち
active:  2000 # 登録済み
quit:    3000 # 退会後

以上の値を属性(pending, active, quit)からでも値(1000, 2000, 3000)を取得・更新することができます。 viewやcontroller、model内で属性名で表現できれば見やすさは高まります。

実例

コンソールで試します。

booleanで検証できる

> User.create(email: 'hoge@example.com', password: 'hogehoge')
> profile = User.first.build_profile

> profile.gender = 'man'
> profile.man? => true
> profile.woman? => false

破壊的メソッドで更新できる。

> profile.man!
> profile.gender => "man"

ハッシュで取得できる

> Users::Profile.genders

属性名(キー)で設定できる。

> profile = Users::Profile.new(gender: :woman)
> profile.woman? => true

値(バリュー)で設定できる。

> profile = Users::Profile.new(gender: Users::Profile.genders[:woman])
> profile.woman? => true

検索ができる

> profile = Users::Profile.new(gender: Users::Profile.genders[:woman])
> profile.user = User.first
> profile.save

> Users::Profile.woman => #<ActiveRecord::Relation [#<Users::Profile id: 2, user_id: 1, gender: "woman", created_at: "2018-07-28 15:33:28", updated_at: "2018-07-28 15:33:28">]>

属性名を日本語で取得することができる

enum_helpというgemを使うことで属性名を日本語にすることができます。

Gemfile

gem 'enum_help'
bundle install

ここでi18nを使います。i18nローカライズ(i18n)まとめ - 現場の開発 でまとめています。

config/locales/resource/users.ja.yml

ja:
  activerecord:
    models:
      user: ユーザー
      user_profile: プロフィール
    attributes:
      user:
        id: ユーザーID
        email: メールアドレス
        password: パスワード
        password_confirmation: パスワード(確認用)
        sign_in_count: ログイン回数
        created_at: 登録日時
        last_sign_in_at: 前回ログイン時刻

      users/profile:
        user: ユーザー
        gender: 性別
  enums:
    users:
      profile:
        gender:
          woman: 女性
          man: 男性

コンソールで確認します。

> User.first.profile.gender_i18n => "男性"

参考

github.com github.com

Railsアプリにwebpackerの導入

この記事で行うこと

webpackerの導入

使うもの

Ruby 2.4.2

Rails 5.1.6

前提

(Tutorialをしている人は)予約機能の作成 - さがえもん まで完了していること。

webpackerとは

Webpackerとは、Webpackを用いてRails上でJavaScript開発をするために必要な一連の流れを提供してくれる、Rails organizationで開発されているgemです。

Rails開発プロセスに沿ったJSのパッケージ管理・エントリーポイントの制御ができることが導入のメリットになります。

現場では?

以下の記事がうまくまとまっています。というか、参考にさせていただきました。 techlife.cookpad.com

RailsにおいてJavaScriptのパッケージ管理方法はたくさんありますが、 現状もっともRails開発プロセスにのっとった管理方法を採用しているのがwebpackerになります。

実装手順

インストール

Gemfile

gem 'webpacker'

webpackerの設定ファイルをインストール

bundle exec rails webpacker:install

するとapp配下にjavascriptフォルダが作成されます。 今後、Javascriptは、app/javascript/packs配下に拡張子.jsでファイルを作成してコードを書いてください。

動作確認

webpackerが正常に動作するかどうかを確認します。

  • applicationが動作しないことを確認する
option(alt)+ command(⌘)+ i

デベロッパーコンソールを開きます。 「Console」タブに何も表示されないことを確認します。 f:id:sksksksksk:20180728210334p:plain

application.html.hamljavascript_pack_tag を書いてください。

app/views/layouts/application.html.haml

・・・・・
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
    = javascript_pack_tag 'application' # 追加
  %body
・・・・・

ページをリロードするとデベロッパツールのConsoleに「Hello World from Webpacker」が表示されます。 これで動作確認完了です。

f:id:sksksksksk:20180728210702p:plain

動作確認で記述した以下のコードは消しておいてください。このコードをapplication.html.hamlで書いてしまうと、全てのファイルでapplication.jsが呼び出されてしまいます。 必要なhamlファイルにのみ javascript_pack_tag でjsファイルを呼びましょう。

app/views/layouts/application.html.haml

・・・・・
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
    = javascript_pack_tag 'application' # 削除
  %body
・・・・・

参考

github.com techlife.cookpad.com github.com

質疑応答

コメントしてください。

ProgateやRailsチュートリアル、プログラミングスクールを通い終えたが現場のコードはかけない、

一体どうやって書くの?と思っているエンジニアのみなさんのためのチュートリアルを公開しています。

チュートリアル