- 投稿日:2020-08-14T23:28:19+09:00
ransack でカラム同士を比較する
まとめ
下記の ransacker とパラメータの組み合わせで
(model.column_A <= model.column_B) = TRUE
が条件に追加されます。同じテーブルのカラム同士の比較を条件に加えることができます。モデルに書くransackerransacker "compare", args: %i[parent ransacker_args] do |parent, args| columns = args.values Arel::Nodes::Grouping.new( Arel::Nodes::InfixOperation.new( '<=', parent.table[columns[0].to_sym], parent.table[columns[1].to_sym] ) ) endパラメータ'q' => { 'c' => { '0' => { 'a' => {'0' => {'name' => 'compare', 'ransacker_args' => { '0' => 'column_A', '1' => 'column_B' } } }, 'p' => 'true', 'v' => {'0' => {'value' => 1} } } } }解説
ransacker
ransacker "compare", args: %i[parent ransacker_args] do |parent, args| columns = args.values Arel::Nodes::Grouping.new( Arel::Nodes::InfixOperation.new( '<=', parent.table[columns[0].to_sym], parent.table[columns[1].to_sym] ) ) endransacker で追加の ransack 用のメソッドを用意してあげます。
args: %i[parent ransacker_args]
でransacker_args
を使えるようにして、ブロック引数にもargs
を書いて、ブロックの中で使えるようにします。
Arel::Nodes::Grouping
は()
に引数の結果を収めてくれます。これを書かないと SQL の式が成り立たないです。
Arel::Nodes::InfixOperation
は第一引数に演算子をとって、第二引数と第三引数を演算する式を返します。
この ransacker は(model.column_A <= model.column_B)
という式を作ります。パラメータ
'q' => { 'c' => { '0' => { 'a' => {'0' => {'name' => 'compare', 'ransacker_args' => { '0' => 'column_A', '1' => 'column_B' } } }, 'p' => 'true', 'v' => {'0' => {'value' => 1} } } } }Advanced Mode を使います。
比較するだけなので p や v は不要に思えますが、a p v の3つが揃わないと ransack が条件として追加してくれないようなので p や v も書きます。
a の name には素直に追加した ransacker のメソッド名を書きます。
ransacker_args には比較したいカラムを書きます。
p は true、value は 1 にします。でも、value は何でもいいです。考察
ransacker_args を使わないと、比較するカラムの組み合わせの数だけ ransacker を書かないといけなくなりますが、このように ransacker_args を使えばコンパクトに書けます。
便利ですね。
- 投稿日:2020-08-14T23:19:24+09:00
foreign_keyを設定しているカラムのカラム名を変更したときにはまったこと。
困っていたこと
先日、foreign_keyを設定していたカラムのカラム名を変更したときに、エラーにはまってしまったので、備忘も兼ね記事に残しておこうと思います。
なお、実行環境は下記のとおりです。
- Rails 5.2.4.2
- PostgreSQL 12.2
前提条件
従業員の活動を記録するデータベースにおいて、従業員(employees) は 活動(activities)と多対多の関係にあります。下記のようなUIで一気に活動に参加した従業員を記録したく、
以下のコードを書きました。
作成したコード
Model
activitiesとemployeesで多対多の関係を定義し、中間テーブルをactivity_employees
としています。models/employee.rbclass Employee < ApplicationRecord has_many :activity_employees, dependent: :destroy has_many :activities, through: :activity_employees endmodels/activity.rbclass Activity < ApplicationRecord has_many :activity_employees, dependent: :destroy has_many :employees, through: :activity_employees endclass ActivityEmployee < ApplicationRecord belongs_to :activity belongs_to :employee endController
一気にデータを受け取れるよう、employee_ids: []
をstrong paramaterに入れています。class ActivitiesController < ApplicationController # 略 def create @activity = @facility.activities.build(activity_params) if @activity.save # 処理 else # 処理 end end private def activity_params params.require(:activity).permit( :activity_type, # 略, employee_ids: [], # 略 ) end endView
= form_with model: [@facility, @activity], class: "p-input-form", local: true do |f| = f.collection_check_boxes :employee_ids, @employees, :id, :name, include_hidden: false do |t| = t.label(class: "c-check-box__label mr-4") { t.check_box(class: "c-check-box") + t.text }起こっていた問題
上記のコードでデータを保存しようとすると、下記のエラーが出ていました。
え。。。。staffs
テーブルにデータを保存しようとなんでしていない。。。。なぜ、staffs
テーブルの参照整合性を崩すというエラーになるの???調べたこと
activity_params
として送っているデータの中身を調べました。
→ こちらに間違いはなかったので、略
db:schema:dump
をしてschema
ファイルを最新化したのち、値を調べました。db/schema.rbcreate_table "activity_employees", force: :cascade do |t| t.bigint "activity_id" t.bigint "employee_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["activity_id"], name: "index_activity_employees_on_activity_id" t.index ["employee_id"], name: "index_activity_employees_on_employee_id" end…カラム名は正しい。。。おかしくなさそう。。。。
いったい何が。。。?と思っていたら、
annotate
というgemによってモデルに自動生成されている記述の中に、見つけました!models/activity_employee.rb# Foreign Keys # # fk_rails_... (activity_id => staffs.id) # fk_rails_... (employee_id => employees.id)カラム名が
activity_id
なのに、参照先がstaffs
テーブルだ!!問題の原因
原因を探るためmigrationファイルをたどっていくと。。。。
▼こちらのマイグレーションの後に、migration1class CreateActivityEmployees < ActiveRecord::Migration[5.2] def change create_table :activity_employees do |t| t.references :staff, foreign_key: true t.references :facility_employee, foreign_key: true t.timestamps end endカラム名間違えたわ!!!…と
▼こんなマイグレーションを作っていましたmigration2class RenameStaffIdColumnToActivityEmployees < ActiveRecord::Migration[5.2] def change rename_column :activity_employees, :staff_id, :activity_id end endその結果、カラム名は変更できたのですが、
foreign_key
は変更できていなかったようです。解決策
上記を踏まえて、以下のようなマイグレーションを作成し、実行することで解決しました。
migration3class ChangeReferencesOfActivityEmployeesTable < ActiveRecord::Migration[5.2] def change remove_foreign_key :activity_employees, :staffs add_foreign_key :activity_employees, :activities end endpostgresにまだ慣れていなかったので、gemの
annotate
を入れてDBの中身が見えるようにしておかないと、気づけないエラーでした。*_id
というカラムでは、カラム名を変更してもforeign_key
を変更したことにはならないのね^^;マイグレーションファイルを作るときのカラム名には、今後はより一層の注意を払おうと思います
- 投稿日:2020-08-14T22:16:08+09:00
【Rails】ハッシュや配列に特定要素が存在するか判別 [ActiveRecord] [has_attribute?]
ハッシュや配列の中に特定の要素が存在するか確認する方法です。
emailが登録されていなかったら登録、されていたらメッセージ、みたいなイメージです。任意のハッシュを判別
このようなハッシュを例にします。
nil
empty
に注目です。@user = { "id" => 1, "name" => "Taro", "age" => 26, "from" => nil, "sex" => "", "created_at" => Sun, 21 Aug 2020 09:55:05, "updated_at" => Sun, 21 Aug 2020 09:55:05 }has_attribute?で判別
特定の要素自体があるかどうか判別します。
has_attribute?
でこのような結果が得られます。@user.has_attribute?(:name) # => true @user.has_attribute?(:from) # => true @user.has_attribute?(:sex) # => true @user.has_attribute?(:userid) # => false少しややこしいのが、中身が
nil
empty
だとしても結果はtrue
というところです。
要素自体があるかないかの判別ということです。attribute_present?で判別
特定の要素の中身が存在するか判別します。
attribute_present?
でこのような結果が得られます。@user.attribute_present?(:name) # => true @user.attribute_present?(:from) # => false @user.attribute_present?(:sex) # => false @user.attribute_present?(:userid) # => エラー要素は存在する前提で、中身が空かどうか判別します。
参考
ActiveRecordやハッシュ・配列などで使えるメソッドは他にもたくさんありますよー
https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods.html
- 投稿日:2020-08-14T20:55:36+09:00
Railsで日本語をurlに使いたい時の書き方備忘録
この記事で書くこと
/animals/隣の猫さん
/animals/山奥の熊さん
のようなurlを実現させるために何をする必要があるのか。何も対処しないと、以下のような
animals#show
にリクエストするとURI::InvalidURIError
が発生してしまう。
animals_controller.rbclass AnimalsController < ApplicationController def show @animal = Animal.find_by(name: params[:name]) end end結論
Controller
以下のようにutf-8にエンコードしてあげれば良い.
animals_controller.rbclass AnimalsController < ApplicationController before_action :encode_with_utf_8, only: :show def show @animal = Animal.find_by(name: params[:name]) end private def encode_with_utf_8 request.url.force_encoding("utf-8") end endテストはどう書くか
Ruby 2.7.0 リファレンスマニュアル | URI.encode_www_form_component を使用する。
spec/requests/animals_spec.rbRSpec.describe 'Animals', type: :request do let(:animal) { create(:animal, name: '隣町の鳥さん') } describe 'GET /animals/:name' do context 'when animal exist' do it 'successes.' do url_encoded = URI.encode_www_form_component animal.name get "/animals/#{url_encoded}" expect(response).to be_success end end end end
- 投稿日:2020-08-14T19:36:05+09:00
assign_attributes で中間テーブルが保存されてしまう
- 入力フォームから値を受け取り、バリデーションを行ってから保存したい、みたいな時に一旦インスタンスの変数を更新するために
assign_attributes
を利用したら思わぬ結果になった- rails の中間テーブルにおいては、
assign_attributes
実行時にDBインサートが走り、コミットまでされてしまう
- そのため、modelのバリデーションエラーで保存に失敗した際に、中間テーブルが保存されっぱなしの状態になってしまった
- 以下のように理解していたが、これは正確ではなさそう
update_attributes
はインスタンス変数の更新を行い、DBの更新まで行うassign_attributes
はインスタンス変数の更新を行うが、DBの更新は行わない- つまり、
assign_attributes
してからバリデーションにかかったり、何らかの理由で保存されたくない時は明示的にロールバックするか、先にインサートしないという工夫が必要になるユースケース
- 以下のようなユースケースを想定
User
とGroup
が多対多で、中間テーブルのモデルとしてGroupUsers
が存在User
を保存する際に、同時にGroupUsers
も保存したい- view から受け取ったパラメータをセットし保存を行うが、バリデーションエラーの時は保存せずエラーを返す
groups.rbclass Group < ApplicationRecord has_many :group_users has_many :users, through: :group_users endusers.rbclass User < ApplicationRecord has_many :group_users has_many :groups, through: :group_users validates :name, presence: true endgroup_users.rbclass GroupUsers < ApplicationRecord belongs_to :group belongs_to :user end
- User に以下のフィールドが生えるが、そのフィールドに値をセットするとインサートが走ってしまう
- groups
- group_ids
- group_users
- group_user_ids
案1 transaction を張る
assign_attributes
からsave
までを transaction で囲んで、保存できない時はロールバックするようにするusers_controller.rbclass UsersController < ApplicationController def update @user = Users.new begin ActiveRecord::Base.transaction do @user.assign_attributes(user_params) #ここで一旦中間テーブルに保存されるが、save!でエラーならロールバックされる @user.save! end p "更新成功" rescue p "更新失敗" end end end案2 after_save を利用する
after_save
は、データベースへの COMMIT の直前に実行される
- つまり、model のバリデーションなどを実行後に、そのメソッドを呼び出してくれる
- 自動で生えるリレーション用のフィールドとは別の attribute 名で view からパラメータを送り、model のバリデーション後にそのパラメータの値を保存したい中間テーブルの attribute にセットして保存することで、バリデーション通過後に中間テーブルが保存されるようにする
assign_attributes
実行時にはリレーションを意味するフィールドには何もセットせず、after_save
で実行するメソッドの中で、値をセットするようにするusers.rbclass User < ApplicationRecord has_many :group_users has_many :groups, through: :group_users validates :name, presence: true after_save :save_group_user attr_accessor :g_ids # User のバリデーション実行後、コミットする直前に呼ばれる def save_group_user self.group_ids = @g_ids end end
- model には
after_save
を定義し、実行するメソッドの中で中間テーブル用のgroup_ids
をセットするようにしておく- また view からきたパラメータをセットしておく用の attribute として
attr_accessor :g_ids
を定義しておく- view からは
g_ids
のように適当なフィールド名でパラメータを送り、after_save
のメソッドでgroup_ids
にセットすることで中間テーブルを保存するusers_controller.rbclass UsersController < ApplicationController def update @user = Users.new @user.assign_attributes(user_params) @user.save ? (redirect_to root_path notice: '更新成功') : (render :edit) end end
- コントローラーでは普通に save を実行して保存すれば良い
ちなみに
- 新規作成時など、User が id フィールドが nil の状態であれば、中間テーブル用のフィールドに値をセットしても自動でインサートが走ることはないので、あくまでも更新時に気をつければいいっぽい
- 投稿日:2020-08-14T18:18:02+09:00
Invalid route name, already in use: 'new_user_session'のエラー解決方法
環境
Mac OS Catalina
Ruby 2.7.1
Rails 6.0.3.2現象
ログイン機能用にdeviseを導入しrouteを設定。
その後、Rails routesを実行すると下記のエラーが出た。Invalid route name, already in use: 'new_user_session' (ArgumentError) You may have defined two routes with the same name using the `:as` option, or you may be overriding a route already defined by a resource with the same naming. For the latter, you can restrict the routes created with `resources` as explained here: https://guides.rubyonrails.org/routing.html#restricting-the-routes-createdどうやら、'new_user_session'という名前のルートが既に使われているぞ!と言ってる様子。
現象発生時のルートは下記の通り。
routes.rbRails.application.routes.draw do devise_for :users # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html root to: 'toppages#index' resources :users, only: [:index, :show, :edit, :update, :destroy] do member do get :followings get :followers get :likes end end devise_for :users, path: '', path_names: { sign_up: '', sign_in: 'login', sign_out: 'logout', registration: 'signup' }, controllers: { registrations: 'users/registrations', sessions: 'users/sessions', passwords: 'users/passwords' } devise_scope :user do get 'signup', to: 'users/registrations#new' get 'login', to: 'users/sessions#new' get 'logout', to: 'users/sessions#destroy' end resources :posts, only: [:create, :destroy] do collection do get :search end end resources :relationships, only: [:create, :destroy] resources :favorites, only: [:create, :destroy] end解決方法
「2箇所定義とかしてないのにな〜」とか考えていましたが、ふと一番上に行が追加されていることに気づきましたw
devise_for :users の行を削除することで解決。routes.rbRails.application.routes.draw do root to: 'toppages#index' resources :users, only: [:index, :show, :edit, :update, :destroy] do member do get :followings get :followers get :likes end end devise_for :users, path: '', path_names: { sign_up: '', sign_in: 'login', sign_out: 'logout', registration: 'signup' }, controllers: { registrations: 'users/registrations', sessions: 'users/sessions', passwords: 'users/passwords' } devise_scope :user do get 'signup', to: 'users/registrations#new' get 'login', to: 'users/sessions#new' get 'logout', to: 'users/sessions#destroy' end resources :posts, only: [:create, :destroy] do collection do get :search end end resources :relationships, only: [:create, :destroy] resources :favorites, only: [:create, :destroy] end無事、起動することができました!
最後に
already in use系のエラーは、今回と同様に同じルーティングが既に設定されていると考えて良さそうですね。
(そのままですが)
今後同じエラーが出た際は、サクッと解決できそう。
- 投稿日:2020-08-14T17:26:03+09:00
Railsのテンプレート機能(ERB)をController(Action View)以外で使う
概要
Railsは、テンプレートをアクションで紐付けてくれるので便利ですよね。
Action ViewとERB (Embedded Ruby)ってやつです。
たまに、そのテンプレートを、他の箇所で使いたいことがあります。
調べましたがあまり情報が出てこなかったり、見つけたと思ったら動かなかったりしたので解決した方法を備忘として投稿します。動作環境
ruby 2.6.5
rails 5.2.3サンプル
例えばJobとかで利用。
app/views下(任意の場所可)にtemplateを作成。
sample.html.erb<%= user_name %>さんへ これはサンプルです。次にJob。
send_template_job.rbclass SendTemplateJob < ApplicationJob queue_as :default def perform(*args) # テンプレートを使う content = ActionView::Base.new('app/views').render(file: 'sample', locals: { user_name: current_user.name }, layout: false) # テンプレートを利用して生成した文字列をどこかに送る処理を記載 # 例えばslackとか end endこんな感じで書くと、erb内のuser_nameて定義してある変数が文字列として展開され取得できます。
- 投稿日:2020-08-14T16:31:12+09:00
localhostで(google_oauth2) Authentication failure! csrf_detected: OmniAuth::Strategies::OAuth2::CallbackError, csrf_detected | CSRF detectedって言われた時の対応
結論
今回はlocalhostで試していたので、config/devise/session_store.rbを下記のように変更すればOK。
if Rails.env.production? Rails.application.config.session_store :cookie_store, key: '_my_session', expire_after: 1.weeks, domain: 'mydomain.com' else Rails.application.config.session_store :cookie_store, key: '_my_session', domain: 'localhost' end原因など
Ruby On Railsのdeviseは本当に優秀であっという間にFacebookのログインは出来るようになったのですが、どうもlocalhostで試した時に「Google OAuthの挙動がおかしいような?」となり調べました。
どうもエラーでググると「provider_ignores_state: true を設定して解決!」みたいな記事がちらほら見つかりましたが、普通に考えてそれって必要だからチェックをしてエラーを吐いているわけですよね?
なので、もう少し調べてみると、ドンピシャの記事を発見!
ドンピシャの記事によると、「/config/initializer/session_store.rb」に定義されているドメインとGoogle APIの「origin/redirect_uri」に定義されているドメインが違うと発生するとのこと。
というわけで、冒頭の対応方法で解決できました。
- 投稿日:2020-08-14T15:33:54+09:00
Ruby on Railsのflashの使い方をまとめてみた
プログラミングの勉強日記
2020年8月14日 Progate Lv.226
flashとは
ページ上に一度だけ表示されるメッセージのこと。ユーザに対してページを移動したときに簡単なメッセージを表示させることができる。ユーザの登録完了などのサクセスメッセージや、投稿などの操作が失敗したときのエラーメッセージなどを表示するときに使う。
flashの書き方
flashはハッシュのような形で記述する。
コントローラflash[:キー名]="表示したいメッセージ"キー名は好きな名前を付けることができる。
フラッシュメッセージを表示したい箇所には以下のように記述する。ビューファイル<%= flash[:キー名] %>scaffoldで自動的に作成した場合
posts_controller.rbdef update respond_to do |format| if @post.update(post_params) format.html { redirect_to ("/posts"), notice: 'Post was successfully updated.' } format.json { render :show, status: :ok, location: @post } else format.html { render :edit } format.json { render json: @post.errors, status: :unprocessable_entity } end end endviews/posts/index.html.erb<p id="notice"><%= notice %></p>自分で作成した場合
users_controller.rbdef login @user = User.find_by(email: params[:email]) if @user && @user.authenticate(params[:password]) session[:user_id] = @user.id redirect_to("/users/index") else flash[:alert] = "メールアドレスまたはパスワードが間違っています" @email = params[:email] @password = params[:password] render("users/login_new") end endviews/users/new.html.erb<p id="alert"><%= alert %></p>noticeとalertオプション
キーには好きな名前を付けることができるが、
notice
とalert
はオプションがある。これらは<%= flash[:キー名] %>
と書く必要がなく、以下のようにflashを省略して書くことができる。ビューファイル<%= notice %> <%= alert %>noticeは通知に、alertは警告に使うことが望ましい。
redirect_toと合わせて使うときの書き方
redirect_to
でnoticeとalertを使うときはまとめて書くことができる。scaffold時のように以下のように書ける。コントローラredirect_to ("パス"), notice: '表示するメッセージ'他のキー名でもまとめて書くことができ、その場合は以下のようになる。
コントローラredirect_to ("パス"), flash{キー名: '表示するメッセージ' }redirect_toはアクションが実行されてからビューが表示される。
- 投稿日:2020-08-14T15:33:54+09:00
Railsのflashの使い方をまとめてみた
プログラミングの勉強日記
2020年8月14日 Progate Lv.226
flashとは
ページ上に一度だけ表示されるメッセージのこと。ユーザに対してページを移動したときに簡単なメッセージを表示させることができる。ユーザの登録完了などのサクセスメッセージや、投稿などの操作が失敗したときのエラーメッセージなどを表示するときに使う。
flashの書き方
flashはハッシュのような形で記述する。
コントローラflash[:キー名]="表示したいメッセージ"キー名は好きな名前を付けることができる。
フラッシュメッセージを表示したい箇所には以下のように記述する。ビューファイル<%= flash[:キー名] %>scaffoldで自動的に作成した場合
posts_controller.rbdef update respond_to do |format| if @post.update(post_params) format.html { redirect_to ("/posts"), notice: 'Post was successfully updated.' } format.json { render :show, status: :ok, location: @post } else format.html { render :edit } format.json { render json: @post.errors, status: :unprocessable_entity } end end endviews/posts/index.html.erb<p id="notice"><%= notice %></p>自分で作成した場合
users_controller.rbdef login @user = User.find_by(email: params[:email]) if @user && @user.authenticate(params[:password]) session[:user_id] = @user.id redirect_to("/users/index") else flash[:alert] = "メールアドレスまたはパスワードが間違っています" @email = params[:email] @password = params[:password] render("users/login_new") end endviews/users/new.html.erb<p id="alert"><%= alert %></p>noticeとalertオプション
キーには好きな名前を付けることができるが、
notice
とalert
はオプションがある。これらは<%= flash[:キー名] %>
と書く必要がなく、以下のようにflashを省略して書くことができる。ビューファイル<%= notice %> <%= alert %>noticeは通知に、alertは警告に使うことが望ましい。
redirect_toと合わせて使うときの書き方
redirect_to
でnoticeとalertを使うときはまとめて書くことができる。scaffold時のように以下のように書ける。コントローラredirect_to ("パス"), notice: '表示するメッセージ'他のキー名でもまとめて書くことができ、その場合は以下のようになる。
コントローラredirect_to ("パス"), flash{キー名: '表示するメッセージ' }redirect_toはアクションが実行されてからビューが表示される。
- 投稿日:2020-08-14T11:47:28+09:00
リクエストパラメーターとその型について
最近のAPIの開発に着手し始めて、クライアントから送られてくるリクエストパラメーターの型について意識し始めた。クエリストリングやボディーのフォーマットに応じてどのような型がくるのか整理したく記録に残す。
クエリストリングで送られてくる場合
localhost:3000/shops?pageno=1&pagesize=60キーに値が指定していればその値の型は
文字列
となる。*ちなみに
key=value
の記述がない場合はnull
となり、key=
のみ記述した場合はその値は空文字列
となる。
*(Railsの場合)paramsで文字列として値がセットされるので、API側で文字列以外の値を使いたいときはその型へキャストする必要がある。ボディーで送られてくる場合
ボディーのフォーマットが
Content-Type: application/x-www-form-urlencoded
の場合WEBブラウザのフォームのリクエストでよく使われる。
キーに指定した値はRFC1866が定める変換フォーマットにしたがって文字列
に変換される。*(Railsの場合)paramsで文字列として値がセットされるので、API側で文字列以外の値を使いたいときはその型へキャストする必要がある。
ボディーのフォーマットが
Content-Type: application/json
の場合アプリのリクエストやAjaxのリクエストでよく使われる。
jsonは型を持つことができるので、クライアント指定した型
を持った状態でリクエストを投げることができる。Chromeを使った確認方法
「右クリック>検証>Networkタブ」でリクエストとレスポンスの情報を確認することができる。
以下は、
Content-Type: application/x-www-form-urlencoded
でリクエストを投げた時の例。
- 投稿日:2020-08-14T11:04:50+09:00
ページネーションの実装
「kaminari」というGemをインストール
Gemfilegem 'kaminari'ターミナル(実装したいアプリのディレクトリ)でbundle installを実行し、rails sで再起動
kaminariを導入すると、モデルクラスにpageメソッドとperメソッドが定義されます。
2つのメソッドは、よく以下のように使われます。
【例】変数名 = クラスを利用して取得したレコードのインスタンス.page(params[:page]).per(1ページで表示したい件数)コントローラーを編集
tweets_controller.rbdef index @tweets = Tweet.includes(:user).order("created_atDESC").page(params[:page]).per(5) endビューを編集
以下の1行を任意の場所に記述すれば実装が完了です。
index.html.erb<%= paginate(@tweets) %>
- 投稿日:2020-08-14T11:01:49+09:00
CarrierWaveを使い、複数の画像を一斉に投入する2つの方法/①seedの初期データで投入/②CSVでインポートする
他のtextカラムは簡単に複数投入できるのですが、image(画像)カラムだけはいずれもnilになってしまい苦労したので、ここに複数の画像カラムを一斉に投入する2つの方法を記しておきます。
①複数の画像カラムをseedの初期データで投入する方法
db/migrate/seed.rb
に、seed.rbPost.create!(image: /public/uploads/post/image/1/yatoguti.jpg, park: 谷戸口公園, outline: 程久保の知る人ぞ知る, location: 東京都日野市程久保1丁目20−14, access: モノレール程久保駅から徒歩5分)のように記載し、
rails db:reset
してから、rails db:seed
するが、seed.rb=> [#<Post:0x00007fa6c66bc8e8 id: 27, image: nil, park: "谷戸口公園", outline: "程久保の知る人ぞ知る。", location: "京王線高幡不動駅より百草園駅方面へ徒歩10分", access: "日野市高幡1024番地", created_at: Sun, 02 Aug 2020 14:38:30 JST +09:00, updated_at: Sun, 02 Aug 2020 14:38:30 JST +09:00, likes_count: nil, tag_list: ["#水遊び", "#アスレチック"]>, #<Post:0x00007fa6c7b48f28 id: 28, image: nil, park: "雨乞公園", outline: "明るさと落ち着いた雰囲気の両面を持つ公園です。", location: "日野市百草881番地の8", access: "三沢台小学校バス停より東へ徒歩3分", created_at: Sun, 02 Aug 2020 14:38:30 JST +09:00, updated_at: Sun, 02 Aug 2020 14:38:30 JST +09:00, likes_count: nil, :...skipping...このように、画像カラムは直接URLだけをを記載するとnilになってしまう。
解決方法
imageカラムの画像URLの記載を下記のように書き換える。
seed.rbPost.create!(image: File.open("#{Rails.root}/public/uploads/post/image/1/yatoguti.jpg"), park: 谷戸口公園, outline: 程久保の知る人ぞ知る, location: 東京都日野市程久保1丁目20−14, access: モノレール程久保駅から徒歩5分)imageカラムの記載を、
Post.create!(image: /public/uploads/post/image/1/yatoguti.jpg,ではなく、
Post.create!(image: File.open("#{Rails.root}/public/uploads/post/image/1/yatoguti.jpg"),に変更する。そして、
rails db:reset
してから、rails db:seed
する。※
/image/1/yatoguti.jpg
には、実際に画像が入っているかどうか、予めファイルを確認しておいて下さい。これで、seed初期データの一斉投入が出来ました!
②複数の画像カラムをCSVでインポートする方法
seed.rbで初期データ投入では成功したので、今度は
db/csv_dara/csv_data.csv
とlib/tasks/import_csv.rake
で、同じように記載して、CSVインポートでも試してみる。csv_data.csvimage,park,outline,location,access,tag_list File.open("#{Rails.root}/public/uploads/post/image/6/ajisai.jpg"),芙蓉公園,日野市の「高幡不動」のすぐそばにあるこぢんまりとした公園です。団地の中の高台にあり、地域の子供達の憩いの場となっています。ブランコ、すべり台、鉄棒、砂場があり、自由に遊ぶことができるスペースも確保されています。,東京都日野市高幡714-21,高幡不動駅から徒歩5分,#滑り台 #鉄棒 #ブランコ #砂場 File.open("#{Rails.root}/public/uploads/post/image/8/hohoemi.jpg"),ほほえみ公園,コンクリートの小山が人気の公園です。小山の中には縦横に走るトンネル、外には留め金のついた登山ルート、そして幅広いすべり台と砂場とが一体化していて、ちょっとした冒険気分が楽しめます。,日野市南平2丁目31番地の6,北野街道口バス停より東へ5分,#滑り台 #鉄棒 #ブランコ File.open("#{Rails.root}/public/uploads/post/image/7/hodokubo.jpg"),ほどくぼ地区広場,少し傾斜のある草原と雑木林。遊具はありませんが、木登り、草すべり、どんぐり拾いなどが楽しめます。原っぱなので、はだしでかけまわっても大丈夫。,日野市程久保3丁目22番地の2,京王線多摩動物公園駅より東へ徒歩5分,#芝生lib/tasks/import_csv.rakerequire 'csv' namespace :import_csv do desc "postテーブルのCSVデータをインポートするタスク" task posts: :environment do path = File.join Rails.root, "db/csv_data/csv_data.csv" list = [] CSV.foreach(path, headers: true) do |row| list << { image: row ["image"], park: row["park"], outline: row["outline"], location: row["location"], access: row["access"], tag_list: row["tag_list"] } end puts "インポート処理を開始" Post.create!(list) puts "インポート完了!!" end endseedの初期投入ができた記載だから、CSVインポートも出来ると思っていました。すると…
rake aborted! CSV::MalformedCSVError: Illegal quoting in line 2. /Users/sekishinya/Desktop/park_app/lib/tasks/import_csv.rake:11:in `block (2 levels) in <main>' Tasks: TOP => import_csv:posts (See full trace by running task with --trace)エラーでインポートできません。
元々imageカラム抜きでインポートしていたので、imageカラムが問題なのは間違いないはずですが。解決方法
CSVは、seedの時とは異なり、全て文字列扱いとなるので記載が異なる。
そこで、
lib/tasks/import_csv.rake
と、db/csv_dara/csv_data.csv
を、以下のように書き直す。import_csv.rakerequire 'csv' namespace :import_csv do desc "postテーブルのCSVデータをインポートするタスク" task posts: :environment do path = File.join Rails.root, "db/csv_data/csv_data.csv" list = [] CSV.foreach(path, headers: true) do |row| list << { image: File.open("#{Rails.root}/#{row["image"]}"), park: row["park"], outline: row["outline"], location: row["location"], access: row["access"], tag_list: row["tag_list"] } end puts "インポート処理を開始" Post.create!(list) puts "インポート完了!!" end end
import_csv.rake
のimageカラムの記載を、image: row ["image"],ではなく、
image: File.open("#{Rails.root}/#{row["image"]}"),に変更する。
そして、
csv_data.csv
のimageカラムを以下のように変更する。csv_data.csvimage,park,outline,location,access,tag_list "public/uploads/post/image/8/fuyou.jpg",芙蓉公園,日野市の「高幡不動」のすぐそばにあるこぢんまりとした公園です。団地の中の高台にあり、地域の子供達の憩いの場となっています。ブランコ、すべり台、鉄棒、砂場があり、自由に遊ぶことができるスペースも確保されています。,東京都日野市高幡714-21,高幡不動駅から徒歩5分,#滑り台 #鉄棒 #ブランコ #砂場 "public/uploads/post/image/8/hohoemi.jpg",ほほえみ公園,コンクリートの小山が人気の公園です。小山の中には縦横に走るトンネル、外には留め金のついた登山ルート、そして幅広いすべり台と砂場とが一体化していて、ちょっとした冒険気分が楽しめます。,日野市南平2丁目31番地の6,北野街道口バス停より東へ5分,#滑り台 #鉄棒 #ブランコ "public/uploads/post/image/8/hodokubo.jpg",ほどくぼ地区広場,少し傾斜のある草原と雑木林。遊具はありませんが、木登り、草すべり、どんぐり拾いなどが楽しめます。原っぱなので、はだしでかけまわっても大丈夫。,日野市程久保3丁目22番地の2,京王線多摩動物公園駅より東へ徒歩5分,#芝生seedの時は、URLのみの記載で失敗しましたが、逆にCSVデータはURLのみにした方が良いですね。
これで見事に、複数の画像の一斉インポートに成功しました!
- 投稿日:2020-08-14T11:01:49+09:00
【Rails6】CarrierWaveを使い、複数の画像を一斉に投入する2つの方法/①seedの初期データで投入/②CSVでインポートする
他のtextカラムは簡単に複数投入できるのですが、image(画像)カラムだけはいずれもnilになってしまい苦労したので、ここに複数の画像カラムを一斉に投入する2つの方法を記しておきます。
環境
- Ruby 2.6.5
- Rails 6.0.3.2
①複数の画像カラムをseedの初期データで投入する方法
db/migrate/seed.rb
に、seed.rbPost.create!(image: /public/uploads/post/image/1/yatoguti.jpg, park: 谷戸口公園, outline: 程久保の知る人ぞ知る, location: 東京都日野市程久保1丁目20−14, access: モノレール程久保駅から徒歩5分)のように記載し、
rails db:reset
してから、rails db:seed
するが、seed.rb=> [#<Post:0x00007fa6c66bc8e8 id: 27, image: nil, park: "谷戸口公園", outline: "程久保の知る人ぞ知る。", location: "京王線高幡不動駅より百草園駅方面へ徒歩10分", access: "日野市高幡1024番地", created_at: Sun, 02 Aug 2020 14:38:30 JST +09:00, updated_at: Sun, 02 Aug 2020 14:38:30 JST +09:00, likes_count: nil, tag_list: ["#水遊び", "#アスレチック"]>, #<Post:0x00007fa6c7b48f28 id: 28, image: nil, park: "雨乞公園", outline: "明るさと落ち着いた雰囲気の両面を持つ公園です。", location: "日野市百草881番地の8", access: "三沢台小学校バス停より東へ徒歩3分", created_at: Sun, 02 Aug 2020 14:38:30 JST +09:00, updated_at: Sun, 02 Aug 2020 14:38:30 JST +09:00, likes_count: nil, :...skipping...このように、画像カラムは直接URLだけをを記載するとnilになってしまう。
解決方法
imageカラムの画像URLの記載を下記のように書き換える。
seed.rbPost.create!(image: File.open("#{Rails.root}/public/uploads/post/image/1/yatoguti.jpg"), park: 谷戸口公園, outline: 程久保の知る人ぞ知る, location: 東京都日野市程久保1丁目20−14, access: モノレール程久保駅から徒歩5分)imageカラムの記載を、
Post.create!(image: /public/uploads/post/image/1/yatoguti.jpg,ではなく、
Post.create!(image: File.open("#{Rails.root}/public/uploads/post/image/1/yatoguti.jpg"),に変更する。そして、
rails db:reset
してから、rails db:seed
する。※
/image/1/yatoguti.jpg
には、実際に画像が入っているかどうか、予めファイルを確認しておいて下さい。これで、seed初期データの一斉投入が出来ました!
②複数の画像カラムをCSVでインポートする方法
seed.rbで初期データ投入では成功したので、今度は
db/csv_dara/csv_data.csv
とlib/tasks/import_csv.rake
で、同じように記載して、CSVインポートでも試してみる。csv_data.csvimage,park,outline,location,access,tag_list File.open("#{Rails.root}/public/uploads/post/image/6/ajisai.jpg"),芙蓉公園,日野市の「高幡不動」のすぐそばにあるこぢんまりとした公園です。団地の中の高台にあり、地域の子供達の憩いの場となっています。ブランコ、すべり台、鉄棒、砂場があり、自由に遊ぶことができるスペースも確保されています。,東京都日野市高幡714-21,高幡不動駅から徒歩5分,#滑り台 #鉄棒 #ブランコ #砂場 File.open("#{Rails.root}/public/uploads/post/image/8/hohoemi.jpg"),ほほえみ公園,コンクリートの小山が人気の公園です。小山の中には縦横に走るトンネル、外には留め金のついた登山ルート、そして幅広いすべり台と砂場とが一体化していて、ちょっとした冒険気分が楽しめます。,日野市南平2丁目31番地の6,北野街道口バス停より東へ5分,#滑り台 #鉄棒 #ブランコ File.open("#{Rails.root}/public/uploads/post/image/7/hodokubo.jpg"),ほどくぼ地区広場,少し傾斜のある草原と雑木林。遊具はありませんが、木登り、草すべり、どんぐり拾いなどが楽しめます。原っぱなので、はだしでかけまわっても大丈夫。,日野市程久保3丁目22番地の2,京王線多摩動物公園駅より東へ徒歩5分,#芝生lib/tasks/import_csv.rakerequire 'csv' namespace :import_csv do desc "postテーブルのCSVデータをインポートするタスク" task posts: :environment do path = File.join Rails.root, "db/csv_data/csv_data.csv" list = [] CSV.foreach(path, headers: true) do |row| list << { image: row ["image"], park: row["park"], outline: row["outline"], location: row["location"], access: row["access"], tag_list: row["tag_list"] } end puts "インポート処理を開始" Post.create!(list) puts "インポート完了!!" end endseedの初期投入ができた記載だから、CSVインポートも出来ると思っていました。すると…
rake aborted! CSV::MalformedCSVError: Illegal quoting in line 2. /Users/sekishinya/Desktop/park_app/lib/tasks/import_csv.rake:11:in `block (2 levels) in <main>' Tasks: TOP => import_csv:posts (See full trace by running task with --trace)エラーでインポートできません。
元々imageカラム抜きでインポートしていたので、imageカラムが問題なのは間違いないはずですが。解決方法
CSVは、seedの時とは異なり、全て文字列扱いとなるので記載が異なる。
そこで、
lib/tasks/import_csv.rake
と、db/csv_dara/csv_data.csv
を、以下のように書き直す。import_csv.rakerequire 'csv' namespace :import_csv do desc "postテーブルのCSVデータをインポートするタスク" task posts: :environment do path = File.join Rails.root, "db/csv_data/csv_data.csv" list = [] CSV.foreach(path, headers: true) do |row| list << { image: File.open("#{Rails.root}/#{row["image"]}"), park: row["park"], outline: row["outline"], location: row["location"], access: row["access"], tag_list: row["tag_list"] } end puts "インポート処理を開始" Post.create!(list) puts "インポート完了!!" end end
import_csv.rake
のimageカラムの記載を、image: row ["image"],ではなく、
image: File.open("#{Rails.root}/#{row["image"]}"),に変更する。
そして、
csv_data.csv
のimageカラムを以下のように変更する。csv_data.csvimage,park,outline,location,access,tag_list "public/uploads/post/image/8/fuyou.jpg",芙蓉公園,日野市の「高幡不動」のすぐそばにあるこぢんまりとした公園です。団地の中の高台にあり、地域の子供達の憩いの場となっています。ブランコ、すべり台、鉄棒、砂場があり、自由に遊ぶことができるスペースも確保されています。,東京都日野市高幡714-21,高幡不動駅から徒歩5分,#滑り台 #鉄棒 #ブランコ #砂場 "public/uploads/post/image/8/hohoemi.jpg",ほほえみ公園,コンクリートの小山が人気の公園です。小山の中には縦横に走るトンネル、外には留め金のついた登山ルート、そして幅広いすべり台と砂場とが一体化していて、ちょっとした冒険気分が楽しめます。,日野市南平2丁目31番地の6,北野街道口バス停より東へ5分,#滑り台 #鉄棒 #ブランコ "public/uploads/post/image/8/hodokubo.jpg",ほどくぼ地区広場,少し傾斜のある草原と雑木林。遊具はありませんが、木登り、草すべり、どんぐり拾いなどが楽しめます。原っぱなので、はだしでかけまわっても大丈夫。,日野市程久保3丁目22番地の2,京王線多摩動物公園駅より東へ徒歩5分,#芝生seedの時は、URLのみの記載で失敗しましたが、逆にCSVデータはURLのみにした方が良いですね。
これで見事に、CSVでも複数の画像の一斉インポートに成功しました!
- 投稿日:2020-08-14T03:36:57+09:00
RailsでAjaxでいいね機能を実装する方法
「Railsでいいね機能を実装する方法」でいいね機能の実装方法をご紹介しましたが、今回はそのいいね機能をAjax(非同期通信)実装する方法をご紹介いたします。
完成系は以下のような感じです。
環境
- Ruby 2.5.7
- Rails 5.2.4
前提
- この記事によって、いいね機能が実装済みであること
index.html.erbを編集
2つのlink_to(method: :delete と method: post)に remote: trueを追記します。
remote: trueを記載することで、Ajaxでの処理を実行することができます。index.html.erb<div class="container"> <h1>記事一覧</h1> <table class="table"> <% @posts.each do |post| %> <tr> <td><%= post.title %></td> <td> <% if post.liked_by?(current_user) %> <% like = Like.find_by(user_id: current_user.id, post_id: post.id) %> <%= link_to like_path(like), method: :delete, remote: true do %> <span class="glyphicon glyphicon-heart" aria-hidden="true" style="color: red;"> <span><%= post.likes.count %></span> <% end %> <% else %> <%= link_to post_likes_path(post), method: :post, remote: true do %> <span class="glyphicon glyphicon-heart" aria-hidden="true" style="color: gray;"> <span><%= post.likes.count %></span> <% end %> <% end %> </td> </tr> <% end %> </table> </div>いいね機能の部分をテンプレート化する
index.html.erbと同じディレクトリに以下のファイルを作成し、いいね機能の部分をコピーし、貼り付けます。
_like.html.erb<% if post.liked_by?(current_user) %> <% like = Like.find_by(user_id: current_user.id, post_id: post.id) %> <%= link_to like_path(like), method: :delete, remote: true do %> <span class="glyphicon glyphicon-heart" aria-hidden="true" style="color: red;"> <span><%= post.likes.count %></span> <% end %> <% else %> <%= link_to post_likes_path(post), method: :post, remote: true do %> <span class="glyphicon glyphicon-heart" aria-hidden="true" style="color: gray;"> <span><%= post.likes.count %></span> <% end %> <% end %>部分テンプレート(_like.html.erb)を呼び出すため、いいね機能の部分があったところにrenderを記述します。
また、Ajaxの処理がされる部分を識別できるように id を記述します。index.html.erb<div class="container"> <h1>記事一覧</h1> <table class="table"> <% @posts.each do |post| %> <tr> <td><%= post.title %></td> <td id="like-<%= post.id %>"> <!--idで識別できるようにする--> <%= render "like", post: post %> <!--renderで部分テンプレートを呼び出す--> </td> </tr> <% end %> </table> </div>controllerの編集
各アクションの最後にredirect_backをしていましたが、redirect_backをすると再読み込みをしていまい、Ajaxが機能しません。
そのため、redirect_backを削除します。likes_controller.rbdef create like = Like.new(user_id: current_user.id, post_id: params[:post_id]) @post = like.post like.save end def destroy like = Like.find(params[:id]) @post = like.post like.destroy endjsファイルの作成
remote: trueによってjs形式のリクエストを送信しているため、実行するアクション名(createやdestroy)のjsファイルを最終的に探しに行きます。
そのため、app/views/配下にlikesフォルダを作成し、そのフォルダの中に create.js.erb と destory.js.erb を作成します。create.js.erb$("#like-<%= @post.id %>").html("<%= j(render 'posts/like', post: @post) %>");destory.js.erb$("#like-<%= @post.id %>").html("<%= j(render 'posts/like', post: @post) %>");idで識別し部分的にhtmlを書き換えます。
これで完成です。
- 投稿日:2020-08-14T03:23:59+09:00
Railsでいいね機能を実装する方法
Railsでいいね機能を実装する方法をご紹介いたします。
完成系は以下のような感じです。
環境
- Ruby 2.5.7
- Rails 5.2.4
前提
- deviseによるログイン機能が実装できていること
- ユーザーのテーブルは"users"、記事のテーブルは"posts"、いいねの中間テーブルは"likes"とする。
- postsのカラムはtitleを追加する
Likeモデルを追加
$ rails g model like user_id:integer post_id:integer $ rails db:migrateアソシエーションを設定
それぞれ以下を記述し、アソシエーションを設定します。
like.rbbelongs_to :user belongs_to :postuser.rbhas_many :likes, dependent: :destroypost.rbhas_many :likes, dependent: :destroyルーティングを設定する
routes.rbresources :posts, shallow: true do resources :likes, only: [:create, :destroy] endliked_by?メソッドを作成
post.rbdef liked_by?(user) likes.where(user_id: user.id).exists? endcontrollerの記述
likes_controller.rbdef create like = Like.new(user_id: current_user.id, post_id: params[:post_id]) @post = like.post like.save redirect_back(fallback_location: posts_path) end def destroy like = Like.find(params[:id]) @post = like.post like.destroy redirect_back(fallback_location: posts_path) endBootstrapを導入
Gemfilegem 'bootstrap-sass', '~> 3.3.6' gem 'jquery-rails'$ bundle installapplication.cssのファイル名をapplication.scssに変更する
$ mv app/assets/stylesheets/application.css app/assets/stylesheets/application.scssBootstrapをscssに読み込ませる
application.scssの一番下に以下を記述
application.scss@import "bootstrap-sprockets"; @import "bootstrap";application.jsを編集する
application.jsの以下の部分を
application.js//= require rails-ujs //= require turbolinks //= require_tree .から
application.js//= require rails-ujs //= require jquery //= require bootstrap-sprockets //= require_tree .に書き換えます。
index.html.erbを編集
index.html.erb<div class="container"> <h1>記事一覧</h1> <table class="table"> <% @posts.each do |post| %> <tr> <td><%= post.title %></td> <td> <% if post.liked_by?(current_user) %> <% like = Like.find_by(user_id: current_user.id, post_id: post.id) %> <%= link_to like_path(like), method: :delete do %> <span class="glyphicon glyphicon-heart" aria-hidden="true" style="color: red;"> <span><%= post.likes.count %></span> <% end %> <% else %> <%= link_to post_likes_path(post), method: :post do %> <span class="glyphicon glyphicon-heart" aria-hidden="true" style="color: gray;"> <span><%= post.likes.count %></span> <% end %> <% end %> </td> </tr> <% end %> </table> </div>これで完成です。
参考記事
- 投稿日:2020-08-14T02:26:03+09:00
初心者がDeviseのコードを見て仕組みをふんわり理解する【registrations new】
はじめに
Railsチュートリアルがやっと1周終わった者です。
gemのDeviseを入れてみたら、Railsチュートリアルでの5~6章分の実装が10分ほどで終わり、驚愕しています。なんでできたのか分からない...
そして、テストも書きたいけどどうやって書いたらいいのか分からない。
そこで「公式やソースコードを読みましょう」ということで、読んでみたけどこれまたさっぱり分からない...ながらも少しずつ理解したいので、読み込んでいきます。読みながら自分が調べたことをまとめていきます。
・初心者だけどDeviseの仕組みを知りたい
そんな方の役に立てたら幸いです。
DeviseのGitHub
https://github.com/heartcombo/deviseDeviseの概要
GitHubのREADMEの一番最初に、概要の説明があります。
- Deviseは、Wardenに基づくRails向けの柔軟な認証ソリューションです。
- ラックベースです。
- Railsエンジンに基づく完全なMVCソリューションです。
- 複数のモデルに同時にサインインさせることができます。
- モジュール性の概念に基づいています。本当に必要なものだけを使用してください。
ここで出てくるWardenというのは認証のためのgemで、devise内でこれを引っ張ってきているようです。
また、10個のモジュールで構成されており、必要なものはコメントアウトを外したりしながら使ってね、ということのようです。
モジュールというかもう機能ですね。
Qiita内で表にしてくださっている方がいらっしゃったので、下記にて引用します。
機能 概要 database_authenticatable サインイン時にユーザーの正当性を検証するためにパスワードをハッシュ化してDBに登録します。認証方法としてはPOSTリクエストかHTTP Basic認証が使えます。 registerable 登録処理を通してユーザーをサインアップします。また、ユーザーに自身のアカウントを編集したり削除できるようにします。 recoverable パスワードをリセットし、それを通知します。 rememberable 保存されたcookieから、ユーザーを記憶するためのトークンを生成・削除します。 trackable サインイン回数や、サインイン時間、IPアドレスを記録します。 validatable Emailやパスワードのバリデーションを提供します。独自に定義したバリデーションを追加することもできます。 confirmable メールに記載されているURLをクリックして本登録を完了する、といったよくある登録方式を提供します。また、サインイン中にアカウントが認証済みかどうかを検証します。 lockable 一定回数サインインを失敗するとアカウントをロックします。ロック解除にはメールによる解除か、一定時間経つと解除するといった方法があります。 timeoutable 一定時間活動していないアカウントのセッションを破棄します。 omniauthable intridea/omniauthをサポートします。TwitterやFacebookなどの認証を追加したい場合はこれを使用します。 引用元:[Rails] deviseの使い方(rails6版)
https://qiita.com/cigalecigales/items/16ce0a9a7e79b9c3974eまた、Deviseは、コントローラーとビュー内で使用するヘルパーを作成します。
よく使用するコマンドを予め設定したものです。デバイスモデルが 'User'であると想定してヘルパー名は例示していますが、
デバイスモデルがユーザー以外の場合は、「_ user」を「_yourmodel(任意のモデル名)」に置き換えると、同じロジックが適用されます。こちらもQiita内で表にしてくださっている方がいらっしゃったので引用します。
メソッド 用途 before_action :authenticate_user! コントローラーに設定して、ログイン済ユーザーのみにアクセスを許可する user_signed_in? ユーザーがサインイン済かどうかを判定する current_user サインインしているユーザーを取得する user_session ユーザーのセッション情報にアクセスする 引用元:Rails deviseで使えるようになるヘルパーメソッド一覧
https://qiita.com/tobita0000/items/866de191635e6d74e392registrations -- サインアップ・アカウント編集・削除
最も基本的なアカウントのCRUDはこのregistrationが担っているので、ここが分からないと応用の機能部分のコードリーディングは難しそうです。
コード全体を上から調べた範囲で記入してきますが、ちまちま区切っていくので、分かりくいかもしれません。
横にソースコードを置きながら見てもらえれば、ちょっとは分かりやすいかも...frozen_string_literal
devise/app/controllers/devise/registrations_controller.rb# frozen_string_literal: true
コメントアウトされていますが、Rubyのバージョンアップに備えた1文のようです。
参考:frozen_string_literalが入って気づいた、メソッド設計の原則
https://qiita.com/jkr_2255/items/300b5db8c1f04e1e2815prepend_before_action
devise/app/controllers/devise/registrations_controller.rbclass Devise::RegistrationsController < DeviseController prepend_before_action :require_no_authentication, only: [:new, :create, :cancel] prepend_before_action :authenticate_scope!, only: [:edit, :update, :destroy] prepend_before_action :set_minimum_password_length, only: [:new, :edit]
DeviseController
を継承しています。 ソースコードのファイルをみると、devise_controller.rbはこのモジュールだけでなく、すべてのモジュールへ引き継いでいます。prepend_before_action
はbefore_action
より前に実行するメソッドです。アクセスできるアクションがユーザーのログイン状態で制限されるようにしています。
- 参考:Railsドキュメント https://railsdoc.com/page/prepend_before_action
newアクション
devise/app/controllers/devise/registrations_controller.rb# GET /resource/sign_up def new build_resource yield resource if block_given? respond_with resource end
- ログインするための最初の部分です。
resource
はすでにdevise_controllerで定義されています。devise/app/controllers/devise_controller.rbdef resource instance_variable_get(:"@#{resource_name}") end # Proxy to devise map name def resource_name devise_mapping.name end alias :scope_name :resource_name
instance_variable_get
メソッドはインスタンス変数の値を取得して返します。@user =
からの定義と同じもののようです。
- 参考:Ruby 2.7.0 リファレンスマニュアル https://docs.ruby-lang.org/ja/latest/method/Object/i/instance_variable_get.html
resource
内に#{resource_name}
という変数がありますが、その下部分で定義されています。devise_mapping.name
こちらもすでにdevise_controllerで定義されてますが、引用記事をみると、別部分にヒントがありそうです。devise/app/controllers/devise_controller.rbdef devise_mapping @devise_mapping ||= request.env["devise.mapping"] endnameに注目してコードを繋げてみると、認証モデルがUserである場合は@singular = :users.to_s.tr('/', '').singularize.to_symとみれます。singularizeは複数形を単数形に変換するメソッドで、最終的に@singular = :userとなりsingularのエイリアスがnameとなっているのでmapping.nameで:userが取得できます。
するとdefinemethodsの引数に:userが渡されauthenticate_user!が出来上がるという流れになっています。
引用元:DeviseのコードリーティングでRailsを学ぶ
https://qiita.com/irisAsh/items/513b8b58f54421b9a1a0端的に言うと、
mapping.name
で:user
が取得できるのでそれをresource_name
にしているということでしょうか。
registrations_controller
下部にbuild_resource
があります。セッションを新しく作るという意味のようです。devise/app/controllers/devise/registrations_controller.rbdef build_resource(hash = {}) #build_resource(hash = {})の定義 self.resource = resource_class.new_with_session(hash, session) end
- 元の
registrations_controller
上部へ戻りましょう。build
はほぼnew
と近い役割をしています。build_resource
という1文は、データベースから取り出したユーザーのインスタンス変数とセッションを作るということになります。block_given?
はメソッドを実行する時にブロックが渡されていればtrueを返し、渡されていない時はfalseを返します。
- ブロックとは
do ... end または { ... } で囲まれたコードの断片 (ブロックと呼ばれる)を後ろに付けてメソッドを呼び出すと、そのメソッドの内部からブロックを評価できます。ブロック付きメソッドを自分で定義するには yield 式を使います。
引用元:Ruby 2.7.0 リファレンスマニュアル
https://docs.ruby-lang.org/ja/latest/doc/spec=2fcall.html#block
この場合、resourceブロックを
yield
として定義し、block_given?
で真偽を確認している。yieldについて詳しく知る必要がありそうです。
- 参考:yield @variable if block_given? ってなに? https://kossy-web-engineer.hatenablog.com/entry/2020/01/19/094958-
- 参考:【Ruby入門】yieldの使い方まとめ https://www.sejuku.net/blog/20478
respond_with resource
はこの辺が関係して呼び出されるのかなと思いますが、devise/app/controllers/devise_controller.rbdef respond_with_navigational(*args, &block) respond_with(*args) do |format| format.any(*navigational_formats, &block) end enddevise/app/controllers/devise_controller.rbdef navigational_formats @navigational_formats ||= Devise.navigational_formats.select { |format|Mime::EXTENSION_LOOKUP[format.to_s] } endピンときていないので勉強して加筆・修正したいと思います。
ここまで複雑なんだなGem...たった数行のコードにいろいろなものが凝縮されていてとても勉強になりました。
newアクションしか書けなかったけど、最終的にはregistrations_controllerの各アクションのだけでも読んだ記録を残したい...!お付き合いいただきありがとうございました。