20200807のRailsに関する記事は16件です。

[Rails][Gem] mini_racer_extension.so: undefined symbol: _ZTTNSt7* 解決法

起こったこと

Vagrant CentOS7 環境に Rails プロジェクトに gem react_on_rails を導入して
React を動かそうとしたところ rails generate react_on_rails:install

追加されていた依存する gem mini_racer
が以下のエラーを出してRails 起動しなくなった。

/home/vagrant/dev/rails-proj/vendor/bundle/ruby/2.6.0/gems/bootsnap-1.4.7/lib/bootsnap/load_path_cache/core_ext/
kernel_require.rb:23:in `require': 
/home/vagrant/dev/rails-proj/vendor/bundle/ruby/2.6.0/gems/mini_racer-0.3.1/lib/
mini_racer_extension.so: 
undefined symbol: _ZTTNSt7__cxx1119basic_istringstreamIcSt11char_traitsIcESaIcEEE - /home/vagrant/dev/rails-proj/vendor/bundle/ruby/2.6.0/gems/mini_racer-0.3.1/lib/mini_racer_extension.so (LoadError)

原因

最近、gem mini_racer が version 0.3.x に上がったようで
不安定のよう。

解決法

とりあえず、gem mini_racer (最近0.3.xに上がったようなので) 、修正対応のある、最新のversion を取り入れる。

Gemfile
gem 'mini_racer', git: 'https://github.com/rubyjs/mini_racer', platforms: :ruby

該当部分

https://github.com/rubyjs/mini_racer/commit/94cdb03211044f2e0620c6275525d09f84f26c78

補足

最初は gem mini_racerを 安定版の0.2.15 に戻したりしたが、
libv8 などで特定のGCC version 依存があるようで、
OSの GCC version調整がいるようで、
とりあえずこれで。

$ gcc --version
gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-39)

https://www.cyberciti.biz/faq/centos-rhel-7-redhat-linux-install-gcc-compiler-development-tools/

https://stackoverflow.com/questions/36327805/how-to-install-gcc-5-3-with-yum-on-centos-7-2

参考

https://stackoverflow.com/questions/36327805/how-to-install-gcc-5-3-with-yum-on-centos-7-2

https://github.com/rubyjs/mini_racer/issues/169

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

any?メソッドを使用してエラーメッセージの取得

エラーメッセージの表示の際に使用したメソッドをまとめたいと思います。

any?メソッドとは

any?メソッドはすべての要素が偽である場合に false を返します。真である要素があれば、ただちに true を返します。

書き方はこのような感じ

p [false, nil].any? # => false

例えばエラーメッセージの記述を部分テンプレートに記載してオブジェクトにエラー情報がある場合のみ表示するように設定するとします。

app/controllers/items_controller.rb
def create
    @item = Item.new(item_params)
    if @item.save
      redirect_to root_path
    else
      render :new
    end
  end

この記述でバリデーション等で保存に失敗した際に
newアクションへ戻るように設定します。

app/views/items/new.html.erb
  <%= form_with model: @item, local: true do |f| %>
      <%= render 'shared/error_messages', model: @item %> 

レンダー先にエラーの情報を持ったモデルオブジェクトを持っていきます。

app/views/shared/_error_messages.html.erb
<% if model.errors.any? %>
<div class="error-alert">
  <ul>
    <% model.errors.full_messages.each do |message| %>
    <li class='error-message'><%= message %></li>
    <% end %>
  </ul>
</div>
<% end %>

any?メソッドでerrorsの中身を確認して存在する場合はtrueとなりエラーの繰り返し処理が働きます。

またpresent?メソッドとかなり似ていますがany?メソッドは

上記例では繰り返し処理でエラーメッセージ を表示していますが

labelの場所ごとにエラーメッセージを表示したい場合はinclude?メソッドを使ってもいいなと思います。

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

【Rails】form_withを完全に理解した

この記事の内容

  • form_withメソッドの使い分け
  • それに伴うストロングパラメータの取り扱い
  • フォーム内容をHTTP通信させる方法

環境

$ rails -v
Rails 6.0.3.1
$ ruby -v
ruby 2.7.0p0 (2019-12-25 revision 647ee6f091) [x86_64-darwin19]

form_withとは

フォーム送信するためのUI部品をビルドするメソッドで、自動でサーバー側のコントローラアクションを切り替えてくれる特徴を持つ。

切り替える要因となるのは、オプションのmodelやurlに対する値によって変わり、以下のようになる。

オプション @userの中身 呼び出されるアクション 用途
model: @user User.new create ユーザー作成
model: @user User.find() update ユーザー編集
url: sessions_path create ユーザーログイン

また、オプションの違いを比較すると、以下のようになる。

オプション 入力エリアのname属性 ストロングパラメータ
model: @user name="user[email]" params.require(:user).permit(:email)
url: users_path name="email" params.permit(:email)

modelオプションを使った例

<%= form_with model: @user do |f| %>
  <%= f.label :name, "名前" %>
  <%= f.text_field :name, placeholder: "山田" %>

  <%= f.submit "登録する" %>
<% end %>

urlオプションを使った例

<%= form_with url: sessions_path do |f| %>
  <%= f.label :name, "名前" %>
  <%= f.text_field :name, placeholder: "山田" %>

  <%= f.submit "ログインする" %>
<% end %>

localオプション

実は、デフォルトでremote: trueオプションが付与されており、いわゆるAjax通信をするようにあらかじめ設定されている。
そのため、特に何も設定しないと、renderメソッドが行われないことによるflashが表示されなかったりする。

通常のHTTP通信を行うために、以下のような設定を行うことで、上記の問題を解決することができる。

<%= form_with model: @user, local: true do |form| %>

まとめ

form_withは、自動でHTTPリクエストの種類を判別してくれるので、便利。

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

Secretsantasができるまで

事前準備

  1. secretsantasという名のフォルダを作成し、VS codeで開く
  2. rails 5.0.7.2 new secretsantas -d mysql
  3. cd secretsantas
  4. rails db:create
  5. Githubに連携
  6. public/uploads/*を.gitignorenに追加
  7. http://localhost:3000/にアクセスしデフォルト画面を表示
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Secretsantasができるまで コーディング

事前準備

  1. secretsantasという名のフォルダを作成し、VS codeで開く
  2. rails 5.0.7.2 new secretsantas -d mysql
  3. cd secretsantas
  4. rails db:create
  5. Githubに連携
  6. public/uploads/*を.gitignorenに追加
  7. http://localhost:3000/にアクセスしデフォルト画面を表示

user登録(ウィザード方式)

1.

Gemfile
gem 'devise'

2.

$ rails g devise:install

3.

config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
end

4.

$ rails g controller home

5.

app/views/home/index.html.erb
<h1>トップページ</h1>

6.

rails g devise user
migrationfile
# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[5.2]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :nickname,           null: false
      t.string :email,              null: false, default: "", unique: true
      t.string :encrypted_password, null: false, default: ""
      t.string :first_name,         null: false  
      t.string :last_name,          null: false  
      t.string :first_name_kana,    null: false  
      t.string :last_name_kana,     null: false  
      t.date   :birthday,           null: false  
      t.boolean :is_deleted,        null: false, default:false
      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end

    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

8.$ rails db:migrate

9.

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname,:first_name,:last_name, :first_name_kana, :last_name_kana,:birthday,:image])
  end
end

10.

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :nickname,:first_name,:last_name, :first_name_kana, :last_name_kana,:birthday,:image ,presence: true
end

11.$ rails g devise:views

12.

app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :nickname %><br />
    <%= f.text_field :nickname %>
  </div>
  <div class="field">
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name %>
  </div>
  <div class="field">
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </div>
  <div class="field">
    <%= f.label :first_name_kana %><br />
    <%= f.text_field :first_name_kana %>
  </div>
  <div class="field">
    <%= f.label :last_name_kana %><br />
    <%= f.text_field :last_name_kana %>
  </div>
  <div class="field">
    <%= f.label :birthday %><br />
    <%= f.date_field :birthday %>
  </div>
    <div class="field">
    <%= f.label :image %><br />
    <%= f.text_field :image %>
  </div>


  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>


app/views/home/index.html.erb
<h1>トップページ</h1>
<% if user_signed_in?%>
  <h2>ログインしています</h2>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
<% else %>
  <h2>ログインしていません</h2>
  <%= link_to "新規登録", new_user_registration_path %>
  <%= link_to "ログイン", new_user_session_path %>
<% end %>

13.挙動を確認
ログインできるか

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

Secretsantasができるまで  事前準備〜ユーザ新規登録(ウィザード方式)

事前準備

  1. secretsantasという名のフォルダを作成し、VS codeで開く
  2. rails 5.0.7.2 new secretsantas -d mysql
  3. cd secretsantas
  4. rails db:create
  5. Githubに連携
  6. public/uploads/*を.gitignorenに追加
  7. http://localhost:3000/にアクセスしデフォルト画面を表示

user登録(ウィザード方式)

deviseを準備

Gemfileにdeviseを記述し、bundle install

Gemfile
gem 'devise'

アプリケーション内でdeviseのヘルパーメソッドなど使用するために以下のコマンドを叩く

$ rails g devise:install

トップページを作成

config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
end

homeコントローラを作成

$ rails g controller home

******下

app/views/home/index.html.erb
<h1>トップページが表示される</h1>

ログインできるか確認

rails g devise user
db/migrate/20XXXXXX.rb
 # frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :nickname, null: false
      t.string :first_name,         null: false  
      t.string :last_name,          null: false  
      t.string :first_name_kana,    null: false  
      t.string :last_name_kana,     null: false  
      t.date   :birthday,           null: false  
      t.text :image,     null: false  

      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end
    add_index :users, :nickname,             unique: true
    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

$ rails db:migrate

Sequel Proでテーブルが作成されているか確認

次に、Appilication Controllerを修正します。nameやageなどのカラムを追加したためです。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname,:first_name,:last_name, :first_name_kana, :last_name_kana,:birthday,:image])
  end
end

カラムのバリデーションを設定

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :nickname,:first_name,:last_name, :first_name_kana, :last_name_kana,:birthday,:image ,presence: true
end

ビューをカスタマイズ

$ rails g devise:views
app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :nickname %><br />
    <%= f.text_field :nickname %>
  </div>
  <div class="field">
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name %>
  </div>
  <div class="field">
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </div>
  <div class="field">
    <%= f.label :first_name_kana %><br />
    <%= f.text_field :first_name_kana %>
  </div>
  <div class="field">
    <%= f.label :last_name_kana %><br />
    <%= f.text_field :last_name_kana %>
  </div>
  <div class="field">
    <%= f.label :birthday %><br />
    <%= f.date_field :birthday %>
  </div>
    <div class="field">
    <%= f.label :image %><br />
    <%= f.text_field :image %>
  </div>


  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign Up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
app/views/home/index.html.erb
<h1>トップページ</h1>
<% if user_signed_in?%>
  <h2>ログインしています</h2>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
<% else %>
  <h2>ログインしていません</h2>
  <%= link_to "新規登録", new_user_registration_path %>
  <%= link_to "ログイン", new_user_session_path %>
<% end %>

新規登録できるか確認

deviseをカスタマイズしてウィザード形式の新規登録にする

$ rails g model address
db/migrate/20XXXXX.rb
class CreateAddresses < ActiveRecord::Migration[5.0]
  def change
    create_table :addresses do |t|
      t.integer :zipcode,     null: false
      t.string :prefecture,     null: false
      t.string :city,     null: false
      t.string :district,     null: false
      t.string :building
      t.string :room
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

$ rails db:migrate
app/models/address.rb
class Address < ApplicationRecord
  belongs_to :user, optional: true
  validates :zipcode, :prefecture, :city,:district,presence: true
end

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :nickname,:first_name,:last_name, :first_name_kana, :last_name_kana,:birthday,:image ,presence: true
  has_one :address

end

$ rails g devise:controllers users
config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  root to: "home#index"
end
app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  def new
    @user = User.new
  end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end

end

app/views/devise/registrations/new.html.erb
<h2>ユーザー情報登録</h2>

<%= form_for(@user, url: user_registration_path) do |f| %>
  <%= render "devise/shared/error_messages", resource: @user %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :nickname %><br />
    <%= f.text_field :nickname %>
  </div>
  <div class="field">
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name %>
  </div>
  <div class="field">
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </div>
  <div class="field">
    <%= f.label :first_name_kana %><br />
    <%= f.text_field :first_name_kana %>
  </div>
  <div class="field">
    <%= f.label :last_name_kana %><br />
    <%= f.text_field :last_name_kana %>
  </div>
  <div class="field">
    <%= f.label :birthday %><br />
    <%= f.date_field :birthday %>
  </div>
    <div class="field">
    <%= f.label :image %><br />
    <%= f.text_field :image %>
  </div>


  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

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

<%= render "devise/shared/links" %>
app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]


  def new
    @user = User.new
  end

  def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.build_address
    render :new_address
  end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end

end

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  devise_scope :user do
    get 'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
  end
  root to: "home#index"
end
app/views/devise/registrations/new_address.html.erb
<h2>住所情報登録</h2>

<%= form_for @address do |f| %>
  <%= render "devise/shared/error_messages", resource: @address %>

  <div class="field">
    <%= f.label :zipcode %><br />
    <%= f.text_field :zipcode %>
  </div>

  <div class="field">
    <%= f.label :prefecture %><br />
    <%= f.text_field :prefecture %>
  </div>

  <div class="field">
    <%= f.label :city %><br />
    <%= f.text_field :city %>
  </div>

  <div class="field">
    <%= f.label :district %><br />
    <%= f.text_field :district %>
  </div>

  <div class="field">
    <%= f.label :building %><br />
    <%= f.text_field :building %>
  </div>

  <div class="field">
    <%= f.label :room %><br />
    <%= f.text_field :room %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]


  def new
    @user = User.new
  end

  def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.build_address
    render :new_address
  end

  def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    unless @address.valid?
      flash.now[:alert] = @address.errors.full_messages
      render :new_address and return
    end
    @user.build_address(@address.attributes)
    @user.save
    session["devise.regist_data"]["user"].clear
    sign_in(:user, @user)
  end
  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
  protected

  def address_params
    params.require(:address).permit(:zipcode, :prefecture, :city,:district, :building, :room)
  end
end
app/views/devise/registrations/create_address.html.erb
<h2>登録が完了しました</h2>
<%= link_to "トップへ戻る", root_path%>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Secretsantasができるまで①  事前準備〜ユーザ新規登録(ウィザード方式)

事前準備

  1. secretsantasという名のフォルダを作成し、VS codeで開く
  2. rails 5.0.7.2 new secretsantas -d mysql
  3. cd secretsantas
  4. rails db:create
  5. Githubに連携
  6. public/uploads/*を.gitignorenに追加
  7. http://localhost:3000/にアクセスしデフォルト画面を表示

user登録(ウィザード方式)

deviseを準備

Gemfileにdeviseを記述し、bundle install

Gemfile
gem 'devise'

アプリケーション内でdeviseのヘルパーメソッドなど使用するために以下のコマンドを叩く

$ rails g devise:install

トップページを作成

config/routes.rb
Rails.application.routes.draw do
  root to: "home#index"
end

homeコントローラを作成

$ rails g controller home

******下

app/views/home/index.html.erb
<h1>トップページが表示される</h1>

ログインできるか確認

rails g devise user
db/migrate/20XXXXXX.rb
 # frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[5.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :nickname, null: false
      t.string :first_name,         null: false  
      t.string :last_name,          null: false  
      t.string :first_name_kana,    null: false  
      t.string :last_name_kana,     null: false  
      t.date   :birthday,           null: false  
      t.text :image,     null: false  

      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

      ## Trackable
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## Confirmable
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # Only if using reconfirmable

      ## Lockable
      # t.integer  :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
      # t.string   :unlock_token # Only if unlock strategy is :email or :both
      # t.datetime :locked_at


      t.timestamps null: false
    end
    add_index :users, :nickname,             unique: true
    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true
    # add_index :users, :confirmation_token,   unique: true
    # add_index :users, :unlock_token,         unique: true
  end
end

$ rails db:migrate

Sequel Proでテーブルが作成されているか確認

次に、Appilication Controllerを修正します。nameやageなどのカラムを追加したためです。

app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up, keys: [:nickname,:first_name,:last_name, :first_name_kana, :last_name_kana,:birthday,:image])
  end
end

カラムのバリデーションを設定

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :nickname,:first_name,:last_name, :first_name_kana, :last_name_kana,:birthday,:image ,presence: true
end

ビューをカスタマイズ

$ rails g devise:views
app/views/devise/registrations/new.html.erb
<h2>Sign up</h2>

<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
  <%= render "devise/shared/error_messages", resource: resource %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :nickname %><br />
    <%= f.text_field :nickname %>
  </div>
  <div class="field">
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name %>
  </div>
  <div class="field">
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </div>
  <div class="field">
    <%= f.label :first_name_kana %><br />
    <%= f.text_field :first_name_kana %>
  </div>
  <div class="field">
    <%= f.label :last_name_kana %><br />
    <%= f.text_field :last_name_kana %>
  </div>
  <div class="field">
    <%= f.label :birthday %><br />
    <%= f.date_field :birthday %>
  </div>
    <div class="field">
    <%= f.label :image %><br />
    <%= f.text_field :image %>
  </div>


  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

  <div class="actions">
    <%= f.submit "Sign Up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
app/views/home/index.html.erb
<h1>トップページ</h1>
<% if user_signed_in?%>
  <h2>ログインしています</h2>
  <%= link_to "ログアウト", destroy_user_session_path, method: :delete %>
<% else %>
  <h2>ログインしていません</h2>
  <%= link_to "新規登録", new_user_registration_path %>
  <%= link_to "ログイン", new_user_session_path %>
<% end %>

新規登録できるか確認

deviseをカスタマイズしてウィザード形式の新規登録にする

$ rails g model address
db/migrate/20XXXXX.rb
class CreateAddresses < ActiveRecord::Migration[5.0]
  def change
    create_table :addresses do |t|
      t.integer :zipcode,     null: false
      t.string :prefecture,     null: false
      t.string :city,     null: false
      t.string :district,     null: false
      t.string :building
      t.string :room
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end

$ rails db:migrate
app/models/address.rb
class Address < ApplicationRecord
  belongs_to :user, optional: true
  validates :zipcode, :prefecture, :city,:district,presence: true
end

app/models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  validates :nickname,:first_name,:last_name, :first_name_kana, :last_name_kana,:birthday,:image ,presence: true
  has_one :address

end

$ rails g devise:controllers users
config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations',
  }
  root to: "home#index"
end
app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]

  def new
    @user = User.new
  end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end

end

app/views/devise/registrations/new.html.erb
<h2>ユーザー情報登録</h2>

<%= form_for(@user, url: user_registration_path) do |f| %>
  <%= render "devise/shared/error_messages", resource: @user %>

  <div class="field">
    <%= f.label :email %><br />
    <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
  </div>

  <div class="field">
    <%= f.label :nickname %><br />
    <%= f.text_field :nickname %>
  </div>
  <div class="field">
    <%= f.label :first_name %><br />
    <%= f.text_field :first_name %>
  </div>
  <div class="field">
    <%= f.label :last_name %><br />
    <%= f.text_field :last_name %>
  </div>
  <div class="field">
    <%= f.label :first_name_kana %><br />
    <%= f.text_field :first_name_kana %>
  </div>
  <div class="field">
    <%= f.label :last_name_kana %><br />
    <%= f.text_field :last_name_kana %>
  </div>
  <div class="field">
    <%= f.label :birthday %><br />
    <%= f.date_field :birthday %>
  </div>
    <div class="field">
    <%= f.label :image %><br />
    <%= f.text_field :image %>
  </div>


  <div class="field">
    <%= f.label :password %>
    <% if @minimum_password_length %>
    <em>(<%= @minimum_password_length %> characters minimum)</em>
    <% end %><br />
    <%= f.password_field :password, autocomplete: "new-password" %>
  </div>

  <div class="field">
    <%= f.label :password_confirmation %><br />
    <%= f.password_field :password_confirmation, autocomplete: "new-password" %>
  </div>

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

<%= render "devise/shared/links" %>
app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]


  def new
    @user = User.new
  end

  def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.build_address
    render :new_address
  end

  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end

end

config/routes.rb
Rails.application.routes.draw do
  devise_for :users, controllers: {
    registrations: 'users/registrations'
  }
  devise_scope :user do
    get 'addresses', to: 'users/registrations#new_address'
    post 'addresses', to: 'users/registrations#create_address'
  end
  root to: "home#index"
end
app/views/devise/registrations/new_address.html.erb
<h2>住所情報登録</h2>

<%= form_for @address do |f| %>
  <%= render "devise/shared/error_messages", resource: @address %>

  <div class="field">
    <%= f.label :zipcode %><br />
    <%= f.text_field :zipcode %>
  </div>

  <div class="field">
    <%= f.label :prefecture %><br />
    <%= f.text_field :prefecture %>
  </div>

  <div class="field">
    <%= f.label :city %><br />
    <%= f.text_field :city %>
  </div>

  <div class="field">
    <%= f.label :district %><br />
    <%= f.text_field :district %>
  </div>

  <div class="field">
    <%= f.label :building %><br />
    <%= f.text_field :building %>
  </div>

  <div class="field">
    <%= f.label :room %><br />
    <%= f.text_field :room %>
  </div>

  <div class="actions">
    <%= f.submit "Sign up" %>
  </div>
<% end %>

<%= render "devise/shared/links" %>
app/controllers/users/registrations_controller.rb
# frozen_string_literal: true

class Users::RegistrationsController < Devise::RegistrationsController
  # before_action :configure_sign_up_params, only: [:create]
  # before_action :configure_account_update_params, only: [:update]


  def new
    @user = User.new
  end

  def create
    @user = User.new(sign_up_params)
    unless @user.valid?
      flash.now[:alert] = @user.errors.full_messages
      render :new and return
    end
    session["devise.regist_data"] = {user: @user.attributes}
    session["devise.regist_data"][:user]["password"] = params[:user][:password]
    @address = @user.build_address
    render :new_address
  end

  def create_address
    @user = User.new(session["devise.regist_data"]["user"])
    @address = Address.new(address_params)
    unless @address.valid?
      flash.now[:alert] = @address.errors.full_messages
      render :new_address and return
    end
    @user.build_address(@address.attributes)
    @user.save
    session["devise.regist_data"]["user"].clear
    sign_in(:user, @user)
  end
  # GET /resource/edit
  # def edit
  #   super
  # end

  # PUT /resource
  # def update
  #   super
  # end

  # DELETE /resource
  # def destroy
  #   super
  # end

  # GET /resource/cancel
  # Forces the session data which is usually expired after sign
  # in to be expired now. This is useful if the user wants to
  # cancel oauth signing in/up in the middle of the process,
  # removing all OAuth session data.
  # def cancel
  #   super
  # end

  # protected

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_sign_up_params
  #   devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute])
  # end

  # If you have extra params to permit, append them to the sanitizer.
  # def configure_account_update_params
  #   devise_parameter_sanitizer.permit(:account_update, keys: [:attribute])
  # end

  # The path used after sign up.
  # def after_sign_up_path_for(resource)
  #   super(resource)
  # end

  # The path used after sign up for inactive accounts.
  # def after_inactive_sign_up_path_for(resource)
  #   super(resource)
  # end
  protected

  def address_params
    params.require(:address).permit(:zipcode, :prefecture, :city,:district, :building, :room)
  end
end
app/views/devise/registrations/create_address.html.erb
<h2>登録が完了しました</h2>
<%= link_to "トップへ戻る", root_path%>

ユーザー登録のデザイン

  • JQueryを使えるようにする
  • fileをアップデートしてプレビューできるようにする
  • 住所を自動入力できるようにする
  • fontawesomeを使えるようにする
  • reset.scssを追加する
  • application.cssをscssに変更する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでRailsチュートリアルのローカル開発環境構築 - WebpackでBootstrapとFont Awesomeを導入 -

はじめに

Dockerでローカル開発環境構築を行い、Railsチュートリアルを再走しております

  • Railsチュートリアル最新版(2020.8.6現在)に対応のRails 6
  • Dockerを使用し、開発環境の再現が可能
  • なるべくローカル環境にインストールしない

今回はRailsチュートリアルの5章に相当する部分です

3章, 4章の内容は特に問題になりませんが、

5章 5.1.2 BootstrapとカスタムCSS の部分でいよいよWebpackの沼に突入して行きます

具体的にはgemを利用せず, YarnとWebpackでBootstrapやFont Awesomeを導入、管理します

加えてテストを書くことが増えるのでその辺りをRSpecで置き換えて進めていきます

5章終了時のブランチはfilling-in-layoutです
https://github.com/dev-naokit/sample_app_on_docker/tree/filling-in-layout

第一回
DockerでRailsチュートリアルのローカル開発環境構築(Rails 6 + PostgreSQL + Webpack) - Qiita

第二回
DockerでRailsチュートリアルのローカル開発環境構築 - RSpec導入 & CircleCIでHerokuデプロイ- - Qiita

個人開発アプリ
mdClip <オンラインmarkdownエディタ>

Dockerのコンテナ上で操作する場合はターミナルのコマンドを適宜

$ docker-compose run app ...

もしくは

$ docker-compose exec app ...

で置き換えてください。

BootstrapとFontawesomeの導入

Rails 6ではJavaScriptのモジュールバンドラとしてWebpackが導入されていますが、
画像やCSSに関しては従来のSprocketがアセットパイプラインとして使用され、
JavaScriptのみをWebpackでコンパイルするようになっています

application.html.erb

<head>の内容を書き換えます

app/views/layouts/application.html.erb

<head>
  <title><%= full_title(yield(:title)) %></title>
  <%= csrf_meta_tags %>
  <%= csp_meta_tag %>
  <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  # (下の行を追記)webpackがCSSを扱えるようにする
  <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  # JavaScriptはデフォルトでwebpacerが pack フォルダをコンパイルして出力している
  <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  <!--[if lt IE 9]>
      <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/r29/html5.min.js">
      </script>
    <![endif]-->
</head>

YarnでBootstrapをインストール

jquerypopperはBootstrapに必要なパッケージ
Font Awesomeもあとで必要になるのでインストールします
('@fortawesome'は誤字ではありません)

yarn add bootstrap jquery popper.js @fortawesome/fontawesome-free

インストールされたモジュールは
package.jsonで確認できます

application.js

app/javascript/packs/application.js

以下を追記します

require("bootstrap");
require("@fortawesome/fontawesome-free");

require('jquery')も併記するような情報もありましたが、
Bootstrapが自動的にrequire('jquery')してくれるようでココには記述不要です

私も色々不具合を検証している過程で
require('jquery')がなくてもjqueryがロードされることに気づいたのですが
jqueryの多重起動はJavaScriptの動作不具合につながることもあるようで、
現状はこのまま進めます

(下に参考記事を貼っておきます)

environment.js

jQueryをどこからでも呼び出せるようにする

config/webpack/environment.js

const { environment } = require('@rails/webpacker')
var webpack = require('webpack');

environment.plugins.append(
    'Provide',
    new webpack.ProvidePlugin({
        $: 'jquery/src/jquery',
        jQuery: 'jquery/src/jquery',
        Popper: ['popper.js', 'default']
    })
)

module.exports = environment

こうすることで毎回import $ from 'jquery';と書かなくて良くなるとのことです

CSS

app/javascript/stylesheets/application.scss(新規作成)

以下追記

@import '@fortawesome/fontawesome-free/scss/fontawesome';
@import 'bootstrap/scss/bootstrap';

application.js

app/javascript/packs/application.js

以下を追記します

import '@fortawesome/fontawesome-free/js/all';
import "../stylesheets/application.scss";

参考

Rails 6: Webpacker+Yarn+Sprocketsを十分理解してJavaScriptを書く: 前編(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

Rails 6+Webpacker開発環境をJS強者ががっつりセットアップしてみた(翻訳)|TechRacho(テックラッチョ)〜エンジニアの「?」を「!」に〜|BPS株式会社

CSSの扱いについて

JavaScriptだけでなく画像やCSS、全てをWebpackでコンパイルすることも可能なようです

現状はSprocketによるアセットコンパイルと、Webpackerが共存している状況に変わりはなく

app/javascript/stylesheets/...をコンパイルした内容が<head>内の

<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

で出力され

app/assets/stylesheets/...をコンパイルした内容が同じく<head>内の

  <%= stylesheet_pack_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

に出力されている状況です

つまりチュートリアル通りassets内のCSS, SCSSを編集しても、
packs内のCSS, SCSSを編集してもビューに反映される状況が出来上がっています

BootstrapやFont Awesomeのstyleは後者によってimportされ、コンパイル、ビューに反映されています

今後チュートリアルをすすめる上でどちらのStylesheetを編集しても問題ないと思いますが
<head>内での呼び出しの順序くらいは気に留めて置いたほうがいいかもしれません

ちなみに、packs内でCSSを編集する場合、webpack-dev-serverのHot reloadの恩恵を受けられます
具体的には、変更保存した場合に自動的にブラウザがリロードされ、即座に(やや遅延あり...)変更内容を確認できます

Railsチュートリアルの流れでcustom.scssを作成する場合
app/javascript/packs/application.jsに以下を記述すれば大丈夫です

import "../stylesheets/custom.scss";

(私はこの仕様で進めてみます)

Troubleshoot

Bootstrapのスタイルがおかしい

おそらくヘッダー周りが上手く表示されないと思います
Bootstrapのバージョンの違いによるものでこちらの記事の通りインストールすると
Bootstrapの最新版ver. 4.5(2020.8.7現在)が導入されます
(Railsチュートリアルでは3.4.1)

navbar-inverseというタグがBootstrap 4では使用できなくなっていますので

  <header class="navbar navbar-expand-md bg-dark navbar-dark bg-dark">

で置き換えるなど細かな修正が必要です
修正を加えたこの章終了時のBranchを公開する予定ですが

yarn addの際にバージョンを指定するといった対策も可能です

テストRSpec関連

assert_...をどう書き換えるか?

そのまま使えます

「RSpecでも assert xxx って書いてテストしたい」=> すぐできます! - Qiita

ApplicationHelperをテストでも使えるように...

include ApplicationHelperを個別の_spec.rbファイルに記述

もしくは、spec/rails_helper.rbに記述
_spec.rbでrequireされているので、個々に記述する必要はないはずです

おわりに

この部分はRails 6環境で個人アプリ開発にあたり、最も苦労しました

身の丈に合わない内容で誤りを含むかもしれませんが

Railsチュートリアルの環境構築だけでなく、Rails 6環境でアプリ開発を行う方の一助になれば幸いです

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

必須項目ではないのにdeviseバリデーションが掛かってしまうときの対処法

こんな人におすすめ

・deviseを使ってログイン機能を持たせている
・任意の項目に意図せずバリデーションが掛かってしまう

環境

ruby 2.5.1
Rails 5.0.7.2

電話番号は任意にしたいが半角数字入力で制御したいのでバリデーションを掛ける

app/models/user.rb
# Eメール・パスワード・パスワード確認は必須項目
validates :email, :password, :password_confirmation,presence: true

# 電話番号は半角数字の入力でよい
validates :tellphone_number,
format: {with: /\A[0-9]+\z/, message:"半角数字でご入力ください。"}

これだと、電話番号も必須項目扱いにされてしまいました。

解決策

1)電話番号のバリデーションに追記

追記前

app/models/user.rb
validates :tellphone_number,
format: {with: /\A[0-9]+\z/, message:"半角数字でご入力ください。"}

追記後

app/models/user.rb
validates :tellphone_number, uniqueness: true,
format: {with: /\A[0-9]+\z/, message:"半角数字でご入力ください。"},on: :phone_number_validates # phone_number_validatesという名称は任意(後述のコントローラーで使用する時と同じであれば良い)

2)合わせてコントローラーも追記

app/controllers/users_controller.rb
def update
  if current_user.update(user_params)
    redirect_to user_path(current_user.id)
    @phone_number.save(context: :phone_number_validates) # ここを追記
  else
    render :edit
  end
end

これで制御できるようになりました。
おわり。

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

アプリ作成の流れ④【パスワード認証機能】

前回は

検索機能を実装しました。
https://qiita.com/ksyantaro/private/af201b653cf55ad6f31c

目次

  1. パスワード認証機能

パスワード認証機能

  • メールアドレスとパスワードの2つを入力してデータベースに保存されているユーザーと一致したらログインさせるといった機能。
  • SQLにおけるWHEREを指定してSELECT。
  • 入力されたメールアドレスに一致するユーザーを取得して、入力されたパスワードが合っているかを判定する。
  • パスワードはハッシュ化してデータベースに保存する。

gem 'devise'を使わずに実装していくこともできる

ただし、以下の記事からわかるように
- 自分で作ると脆弱性の可能性
- 車輪の再発明になる可能性
- 時間がかかる
といった問題点が出てきます。

【参考記事】
問題点
https://teratail.com/questions/66458
作り方
https://qiita.com/kodaii/items/13b73cc687ee3b7db051

deviseを使い、その仕組みを理解していくほうがいいのではないかという結論に至りました。

今回は'devise'で実装していく

'devise'によって、認証系の機能を簡単に実装することができる。

【参考記事】
deviseの使い方(rails6版)
https://qiita.com/cigalecigales/items/16ce0a9a7e79b9c3974e

deviseのインストール

Gemfile
  gem 'devise'

ターミナル

% bundle install

% rails g devise:install
とすると以下のような記述が出てくる。

Running via Spring preloader in process 48598
      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 *

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

デフォルトURLの指定

config/environments_development.rb
Rails.application.configure do

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

end

rootパスの指定

自分の場合はすでに指定しているので大丈夫。

指定していない場合はconfig/routesにて
rootを指定しよう。

flashメッセージの追加

layouts/application.html.haml
  %body
    %p.notice
      = notice
    %p.alert
      = alert
    = yield

deviseのviewsを追加する

ターミナル
% rails g devise:views

Userモデルを作成・編集する

Userモデルの作成

ターミナル
% rails g devise user

nameカラムの追加

migrate/2020XXXXXXXXXX_devise_create_users.rb
class DeviseCreateUsers < ActiveRecord::Migration[6.0]
  def change
    create_table :users do |t|
      ## Database authenticatable
      t.string :name,              null: false, default: ""
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## Recoverable
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## Rememberable
      t.datetime :remember_created_at

   ~~~~~~~~~~~省略しています~~~~~~~~~~~

      t.timestamps null: false
    end

    add_index :users, :name,                unique: true
    add_index :users, :email,                unique: true
    add_index :users, :reset_password_token, unique: true

  end
end

models/user.rb
class User < ApplicationRecord
  # Include default devise modules. Others available are:
  # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable

  以下を追記しています!
  validates :name, presence: true, uniqueness: true

end

忘れずにrails db:migrateします!

controllers/application_controller.rb
class ApplicationController < ActionController::Base
  before_action :authenticate_user!
  before_action :configure_permitted_parameters, if: :devise_controller?

  protected

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

ビューの編集

deviseのビューがerbなので、hamlに変更。

ターミナル
% rails haml:erb2haml

nameの新規登録と編集ができるように

ログインはメールアドレスとパスワードでいけるようにします。

registrations/new.html.haml
  .field
    = f.label :name
    %br/
    = f.text_field :name, autofocus: true, autocomplete: "name"

これを追加します。
emailのものをコピーして少し編集するのがおすすめ。
email_fieldはtext_fieldにしましょう。

registrations/edit.html.haml
  .field
    = f.label :name
    %br/
    = f.text_field :name, autofocus: true, autocomplete: "name"
  - if devise_mapping.confirmable? && resource.pending_reconfirmation?
    %div
      Currently waiting confirmation for: #{resource.unconfirmed_name}

これを追加します。
emailのものをコピーして少し編集するのがおすすめ。
email_fieldはtext_fieldにしましょう。

ログイン時とログアウト時の表示の変更

layouts/application.html.haml
  %body
    %header
      %nav
        - if user_signed_in?
          %strong
            = link_to current_user.name, blog_path(current_user.id)
          = link_to 'プロフィール変更', edit_user_registration_path
          = link_to 'ログアウト', destroy_user_session_path, method: :delete
        - else
          = link_to 'サインアップ', new_user_registration_path
          = link_to 'ログイン', new_user_session_path
    %p.notice
      = notice
    %p.alert
      = alert
    = yield

まとめ

以上でdeviseを用いた
簡単なパスワード認証機能は実装できました。

次回はSNS認証の実装をしていきたいと思います。

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

編集、削除の権限を投稿者だけにしたい

バージョン

・ruby 2.5.7
・Rails 5.2.4.3

編集、削除の権限を投稿者だけにしたい

CRUD処理は出来た!
けど、このままだと全ての投稿を編集や削除が出来てしまう状態。
編集、削除の権限を投稿者だけの機能にしたい。
スクリーンショット 2020-08-07 11 24 11

ユーザーの投稿を守る為に下記のメソッドを使う

unlessはもし〜でなかったらと言う意味。下記でいうと、
もし受け取ったユーザーのIDが、ログインしているユーザー(current_user)のIDと一致しなければ、処理を実行せずリダイレクトで戻しますよっていう意味です。
スクリーンショット 2020-08-07 11 33 46

before_actionでメソッドを呼び出して、完成!!!

before_actionはコントローラーの全てのアクションが実行される前に実行されるものです。
今回は編集と削除のみの場合で行いたいので、editとupdateとdestroyのみにしています。
スクリーンショット 2020-08-07 11 33 58

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

rails5中級チュートリアルのテストがうまく通らなかった時

前提・実現したいこと

rails5中級チュートリアルを行なっています。
チュートリアルの3-3でのテストの部分で
昨日から以下のエラーが発生し、テストをパスすることができず、困っていた。

発生している問題・エラーメッセージ

ターミナルでのrspec spec/features/user/login_spec.rb実行時

  1) Login ユーザーがloginページにリダイレクトされ、ログインに成功する
     Failure/Error: expect(page).to have_selector('#user-settings')
       expected to find css "#user-settings" but there were no matches
     # ./spec/features/user/login_spec.rb:13:in `block (2 levels) in <top (required)>'

Finished in 4.17 seconds (files took 2.75 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/features/user/login_spec.rb:6 # Login ユーザーがloginページにリダイレクトされ、ログインに成功する

該当のソースコード

login_spec.rb
require "rails_helper"

RSpec.feature "Login", :type => :feature do
  let(:user) { create(:user) }

  scenario 'ユーザーがloginページにリダイレクトされ、ログインに成功する', js: true do
    user
    visit root_path
    find('nav a', text: 'ログイン').click
    fill_in 'user[email]', with: user.email
    fill_in 'user[password]', with: user.password
    find('.login-button').click
    expect(page).to have_selector('#user-settings')
  end

end

試したこと

①エラー文で検索をかけるも、手がかりを見つけることができなかった。

②エラー文にcssの"#user-setting"とhave_selectorの"#user-setting"が一致しないとあるため、cssの"#user-setting"に問題があると仮説するが、そもそもcssに"#user-setting"というものがなくどうすれば良いのかすらわからない状態であった。

③vscodeの文字列検索で[user-setting]と検索した際に、[_signed_in_links.html.erb]ファイル内でuser-settingをidとして定義してある箇所があったためそちらの記述を"#user-setting"に変更してみたが、解決することができなかった。

_signed_in_links_html.erb
<li class="dropdown pc-menu">
# この下の行の"user-settingを変更を試した"
  <a id="user-setting"  class="dropdown-toggle" data-toggle="dropdown" href="#">
   <span id="user-name"><%= current_user.name %></span>
   <span class="caret"></span>
  </a>

  <ul class="dropdown-menu" role="menu">
   <li><%= link_to 'プロフィールを編集', edit_user_registration_path %></li>
   <li><%= link_to 'ログアウト', destroy_user_session_path, method: :delete%></li>
  </ul>
</li>

<li class="mobile-menu">
  <%= link_to 'プロフィールを編集', edit_user_registration_path %>
</li>
<li class="mobile-menu">
  <%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
</li>

解決方法

一日以上時間を費やしては埒が明かないと考え,teratailで質問を投げかけたところ解決。
上記にある試したこと③にもある_signed_in_links_html.erbでのid指定を#user-settingsではなく
user-settingsに修正したところテストを通過することができた。

学び

cssセレクタでは、#はid属性、.の場合はclss属性ということを完全に忘れてしまっていた。

補足情報(FW/ツールのバージョンなど)

vscode最新バージョン使用
railsバージョン 5.1.7
bootstrap

テストツール
factory_bot_rails
capybara 2.15
rspec-rails
selenium-webdriver
rails-controller-testing
headless
poktergeist

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

リダイレクトはしたい。だがQueryParameterは失いたくない。

そんな時はこう。

get '/users/new', to: redirect(path: "users/sign_up")

Parameterは据置でpathだけ変更してくれるのさ。

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

Ruby on Railsのシード機能についてのメモ

はじめに

初心者です。
RubyとRuby on Railsを使ってアプリケーションを作っています。
備忘録も兼ねておりますので、間違いなどあればご指摘ください。

シード機能とは

データベース作成後に、初期データを簡単に流し込むことができる機能。

どのファイルを使うのか

db/seeds.rbに初期データとして流し込みたいコードを記述して、
ターミナルでrails db:seedすればOK。
実行してもターミナルに何か表示されるわけではないが、データに問題なければ流し込まれているはず。

ファイルにはどう記述するのか

例)productsテーブルのnameカラムとdescriptionカラムにデータを5つ流し込む

5.times do |i|
  Product.create(name: "Product ##{i}", description: "A product.")
end

timesメソッドを使っているが、1行ずつ書いていっても問題なし。

まとめ

  • シード機能はデータベース作成後に初期データを流し込むことができる機能
  • db/seeds.rbに流し込みたいデータを記述
  • ターミナルでrails db:seedすれば流し込める

参考

Railsガイドv6.0
https://railsguides.jp/active_record_migrations.html

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

DockerでRailsチュートリアルのローカル開発環境構築 - RSpec導入 & CircleCIでHerokuデプロイ-

はじめに

前回の記事
DockerでRailsチュートリアルのローカル開発環境構築(Rails 6 + PostgreSQL + Webpack) - Qiita

個人開発アプリ
mdClip <オンラインmarkdownエディタ>

前回の記事に続いて、Railsチュートリアルのローカル開発環境構築を行っていきます。

  • Railsチュートリアル最新版(2020.8.6現在)に対応のRails 6

  • Dockerを使用し、開発環境の再現が可能

  • なるべくローカル環境にインストールしない

Docker環境で操作する場合はターミナルのコマンドを適宜

$ docker-compose run app ...

もしくは

$ docker-compose exec app ...

で置き換えてください。

Rspec導入

minitestでも問題ないです
後述のCircleCIでもminitestを走らせる事もできました。

Gemfile

必要に応じて以下のgemを追加
(不要ならminitest関連のgemを削除する)

Gemfile

  # Test enviroment: Rspec
  gem 'rspec-rails'
  gem 'spring-commands-rspec'
  gem 'guard'
  gem 'guard-rspec', require: false

  # Test enviroment: Fake date generator
  gem 'factory_bot_rails'
  gem 'faker'
  gem 'forgery_ja'

bundle install

Gemfileを編集したのでdocker-compose buildが必要です

$ docker-compose build

初期ファイル生成

$ rails g rspec:install

レポートフォーマット

.rspec

--require spec_helper
--format documentation

Guard初期化

$ bundle exec guard init

spring boot対応

$ bundle exec spring binstub --all

Guardの自動テスト時にもspringが有効になるように
Guardfile

$ guard :rspec, cmd: "bundle exec spring rspec" do

CircleCI設定

事前準備

ここでは説明しませんが事前に以下の手順が必要だと思います

  • GitHub & CircleCI & Herokuのアカウント登録
  • SSH接続設定(GitHub - CircleCI)
  • CircleCIへのproject登録および環境変数定義(Heroku APIキー, app名)

設定概要

  • git push -> 自動test (RSpec) -> 自動デプロイ(Heroku)
  • Orbを使うとconfig.ymlの内容を簡略化できる(キャッシュ利用のための記述が不要)
  • Orbを使うためにversion: 2.1指定
  • Docker imageのRuby versionはRailsチュートリアルに合わせて2.6.3
  • Node.jsが必要なので-nodeのついたimageを指定

メモ: Node.js バリアントの Docker イメージ (-node で終わるタグ) に対しては、Node.js の LTS リリースがプリインストールされています。 独自に特定のバージョンの Node.js/NPM を使用する場合は、.circleci/config.yml 内の run ステップで設定できます。 Ruby イメージと共に特定のバージョンの Node.js をインストールする例については、以下を参照してください。

公式サンプルを踏襲しています

公式do - Ruby
Language Guide: Ruby - CircleCI

公式doc - Heroku Deploy
デプロイの構成 - CircleCI

現時点で最新のHeroku-orbは1.2.0ですがバグがあるようで
pull requestされています

deploy-via-git bash script error · Issue #13 · CircleCI-Public/heroku-orb

config.yml

.circleci/config.yml

version: 2.1

orbs:
  ruby: circleci/ruby@1.0
  node: circleci/node@2
  heroku: circleci/heroku@1.1.0

# setupでまとめる
commands:
  setup:
    steps:
      - checkout
      - ruby/install-deps
      - node/install-packages:
          pkg-manager: yarn
          cache-key: "yarn.lock"

jobs:
  build:
    docker:
      - image: circleci/ruby:2.6.3-node
    steps:
      - setup
  test:
    docker:
      - image: circleci/ruby:2.6.3-node
      - image: circleci/postgres:11.6-alpine
        environment:
          POSTGRES_USER: postgres
          POSTGRES_DB: myapp_test
          POSTGRES_PASSWORD: ""
    environment:
      BUNDLE_JOBS: "4"
      BUNDLE_RETRY: "3"
      PGHOST: 127.0.0.1
      PGUSER: postgres
      PGPASSWORD: ""
      RAILS_ENV: test
    steps:
      - setup
      - run:
          name: Wait for DB
          command: dockerize -wait tcp://localhost:5432 -timeout 1m
      - run:
          name: Database setup
          command: bundle exec rails db:schema:load --trace
      - run:
          name: Rspec
          command: bundle exec rspec

  deploy:
    executor: heroku/default
    steps:
      - checkout
      - heroku/install
      - heroku/deploy-via-git:
          maintenance-mode: true
      - run:
          name: DB migrate
          command: heroku run rails db:migrate --app $HEROKU_APP_NAME

workflows:
  version: 2
  test_and_deploy:
    jobs:
      - build
      - test:
          requires:
            - build
      - deploy:
          requires:
            - build
            - test
          filters:
            branches:
              only: master

動作確認

HTMLを書き換える

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  def hello
    render html: "Bye, world!"
  end
end

変更をgit pushして、CircleCIのpipelineがsuccessに変われば
ブラウザから見た内容が上記を反映するはずです

簡単なテスト

仮のrequest_specを作成

rails g rspec:request test

spec/requests/tests_spec.rb

require 'rails_helper'

RSpec.describe "Tests", type: :request do
  describe "GET /" do
    it "Bye, world!が表示されること" do
      get root_path
      expect(response.body).to include "Bye, world!"
    end
  end
end

CircleCI上でも上記テストがpassするはずです

Troubleshoot

CircleCIのtestが以下のエラーで失敗する

rails aborted!
PG::ConnectionBad: could not translate host name "db" to address: Name or service not known

database.ymlの記述が原因です

test環境ではdefaultを引き継いでhost: dbが適応されているはずです

config/database.yml
以下のように書き換えて環境変数がある場合はそちらを優先するようにしました
(ymlのなかでもerbが使えるという学びでした)

test:
  <<: *default
  host: <%= ENV['PGHOST'] || 'db' %>
  database: myapp_test

ERROR IN CONFIG FILE:

インデント、フォーマットの間違いが多いと思います

最後に

  • 意地でローカル環境をきれいに保つことに注力した結果、デプロイやdb:migrateを自動化することで、Heroku CLIすらインストールすることを回避しましたが、素直にHeroku CLIくらいは入れておいた方が今後のエラー解決に役立つ気もします。
  • 難易度は上がりますが、ローカル環境でDocker, CICDを取り入れてのRailsチュートリアル、学べることも多いと思いますので腰を据えて取り組むことのできる方はぜひトライしてみてください。
  • 今後も未知のエラーに遭遇することが予想されますが、おおよそココまでが大変なところかとおもっていますので、この記事がお役に立てれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Rails開発で役立つVSCodeの拡張機能

プログラミングの勉強日記

2020年8月7日 Progate Lv.226
VSCodeでRailsアプリケーション開発するうえで便利だなと思った拡張機能をまとめてみる。

VSCode拡張機能のインストール方法

0807.PNG

  1. 左端のバーから拡張機能を選択する
  2. 検索ボックスにインストールしたい拡張機能の名前を入力
  3. インストールしたい拡張機能を選択し、インストールをクリック

Rails開発で便利な拡張機能

Ruby

 Ruby言語のサポートをする。Rubyのベーシックなシンタックスハイライト(テキストエディタなどの文字表示に関する機能の1つで、特定のキーワードや記号を色を変えたりフォントを変えたりして見やすいように表示する)や入力候補を提供する。

Ruby Language Colorization

 Rubyのシンタックスハイライトを提供する。

endwise

 Rubyを書くときに忘れやすいendを自動で入力してくれる。

Rails

 Railsをサポートする。Railsのスニペット(コードの断片)やナビゲーションを提供する。

Ruby Solargraph

 Rubyの入力予測をより強化してくれる。(Rails拡張機能を入れるとインストールされる)

Rails Go to Spec

 Windowsの場合はCmd + Shift + yのショートカットで対応するspecファイルと行き来ができる。

ERB

 erbのシンタックスハイライトを提供する。

Slim

 Slim使ってる方はこちらで、、

Rainbow End

 endを色分けする拡張機能。

参考文献

VSCodeでRuby on Railsを開発するなら絶対に入れたいおすすめ拡張機能7選
VSCodeの拡張機能でRailsと仲良くなる

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