この記事はプログラミング学習者がアプリ開発中に躓いた内容を備忘録として記事におこしたものです。内容に不備などあればご指摘頂けると助かります。
0.前提条件
X(旧Twitter)のクローンサイトの制作過程でつぶやきの投稿機能を実装している際に躓いた内容をご紹介したいと思います。
1. 実装内容
以下のコードは実装初期の時点でのコードです。
躓きながらコードを修正していきました。
namespace :api do
namespace :v1 do
mount_devise_token_auth_for 'User', at: 'users', controllers: {
registrations: 'api/v1/registrations'
}
resources :tweets, only: %i[create]
end
end
class Api::V1::TweetsController < ApplicationController
before_action :set_user, only: %i[create]
before_action :authenticate_user!, only: %i[create]
def create
@tweet = @user.tweets.new(tweet_params)
if @tweet.save
render json: { status: 'SUCCESS', message: 'Saved tweet', data: @tweet }
else
render json: { status: 'ERROR', message: 'Tweet not saved', data: @tweet.errors }, status: :unprocessable_entity
end
end
private
def tweet_params
params.permit(:user_id, :content, images: [])
end
def set_user
@user = User.find(current_user.id)
end
end
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :confirmable
has_many :tweets, dependent: :destroy
include DeviseTokenAuth::Concerns::User
end
class Tweet < ApplicationRecord
belongs_to :user
has_many_attached :images
end
class CreateTweets < ActiveRecord::Migration[7.0]
def change
create_table :tweets do |t|
t.string :content, limit: 140, null: false
t.timestamps
end
end
end
2. 躓き
今回は上記の通りにつぶやきの投稿機能を実装したので、試しにcurlコマンドを使って投稿できるかどうかを実際にやってみました。
※記憶を遡りながら途中経過を書いたので、途中の内容に若干のズレがあるかもしれまんせ。ご容赦下さい。
-
curl -X POST -v http://localhost:3000/api/v1/tweets -d 'content=お試し投稿'
最初のコマンドではTweetテーブルのcontentカラムだけをデータとして付与する送り方でやってみた結果です。
undefiend method method 'id'とあるので、下の部分でidを取得できていないのかな?と推測していました。
def set_user
@user = User.find(current_user.id)
end
- 色々といじっていると新しいエラーに遭遇しました。修正した内容は不明(申し訳ありません。)
current_userが正しく動作していないことが分かりました。
devise_token_authのメソッドであるcurrent_userはroutesのnamespaceを使った階層に影響を受けることが分かったので下記のように内容を修正しました。
def set_user
@user = User.find(current_api_v1_user.id)
end
また、資料を色々と閲覧している中で認証情報(access-token, uid, client)を一緒に送る必要があることに気付きました。
よって、curlコマンドを下記のように書き換えました。
- curl -X POST -v http://localhost:3000/api/v1/tweets -d 'content=お試し投稿' -d 'access-token=xxxxxxx' -d 'client=yyyyy' -d 'uid=zzzzzz'
※認証情報の詳細は割愛しました。
今度はauthenticate_user!が未定義だと指摘されました。
これは先述のcurrent_userのエラーと同じ内容だったので下記のように修正しました。
before_action :authenticate_api_v1_user!, only: %i[create]
authenticate_user!を修正した後に下記のエラーが発生しました。
400 bad requestなので、こちらから送っているリクエストに問題がありそうです。
これはcontrollerのストロングパラメーターでrequire(:tweet)
としているにも関わらず、tweetをキーとして渡していないのが原因でした。
def tweet_params
params.require(:tweet).permit(:user_id, :content, images: [])
end
最初はtweetキーのことに気付かずに一時的にrequire(:tweet)
の記述を外して同じcurlコマンドを実行したところ、エラーの内容が変わりました。
user_idが不明だという指摘に変わっています。
モデル同士のアソシエーションは設定しているので問題無いなどと高を括っていたのですが、その認識が誤っていました。ふと、設定をしていた時のことを思い出していたのですが、マイグレーションのところで外部キーを設定した記憶が無かったのです。。。
class CreateTweets < ActiveRecord::Migration[7.0]
def change
create_table :tweets do |t|
t.string :content, limit: 140, null: false
t.timestamps
end
end
end
はい、実際にやっていませんでした。カッコワル...
直ぐにロールバックして、外部キーを設定してマイグレーションをやり直しました。
class CreateTweets < ActiveRecord::Migration[7.0]
def change
create_table :tweets do |t|
t.string :content, limit: 140, null: false
t.references :user, null: false, foreign_key: true #追記
t.timestamps
end
end
end
ここでrequire(:tweet)
を外した状態でcurlコマンドを実行したところ、コマンドは成功してしまいました。
require(:tweet)
の記述を元に戻して、curlコマンドのcontent
を渡す部分の記述を書き換えたところ、投稿に成功しました。
curl -X POST -v http://localhost:3000/api/v1/tweets -d 'tweet[content]=お試し投稿 2回目' -d 'access-token=xxxxxx' -d 'client=yyyyyy' -d 'uid=zzzzzzz
'
これでやっと正しく投稿することに成功しました。
色々な躓きがありましたが、勉強になりました。
ここまで拙い躓きを見て頂きありがとうございました。
読まれた方のエラー解決の一助になれば幸いです。
3. 参考にさせて頂いたサイト
curlコマンド完全ガイド:基礎から現場での実践的な使い方まで
[Rails] undefined method authenticate_user!の対処方法
paramsで値を取得する際にrequireを必ずしも使わなくもいいものなのでしょうか?