1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

X(旧Twitter)クローン 投稿機能の実装と躓き

Posted at

この記事はプログラミング学習者がアプリ開発中に躓いた内容を備忘録として記事におこしたものです。内容に不備などあればご指摘頂けると助かります。

0.前提条件

X(旧Twitter)のクローンサイトの制作過程でつぶやきの投稿機能を実装している際に躓いた内容をご紹介したいと思います。

1. 実装内容

以下のコードは実装初期の時点でのコードです。
躓きながらコードを修正していきました。

routes.rb
  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
tweets_controller.rb
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
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, :confirmable
  has_many :tweets, dependent: :destroy
  include DeviseTokenAuth::Concerns::User
end
tweet.rb
class Tweet < ApplicationRecord
  belongs_to :user
  has_many_attached :images
end
20250603124429_create_tweets.rb
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コマンドを使って投稿できるかどうかを実際にやってみました。
※記憶を遡りながら途中経過を書いたので、途中の内容に若干のズレがあるかもしれまんせ。ご容赦下さい。

  • ルートの確認
    tweet create route.png

  • curl -X POST -v http://localhost:3000/api/v1/tweets -d 'content=お試し投稿'
    contentのみのcurlコマンド結果.png
    最初のコマンドではTweetテーブルのcontentカラムだけをデータとして付与する送り方でやってみた結果です。
    undefiend method method 'id'とあるので、下の部分でidを取得できていないのかな?と推測していました。

tweetes_controll.rb
  def set_user
    @user = User.find(current_user.id)
  end
  • 色々といじっていると新しいエラーに遭遇しました。修正した内容は不明(申し訳ありません。)
    current_uerが無効な場合の結果.png
    current_userが正しく動作していないことが分かりました。
    devise_token_authのメソッドであるcurrent_userはroutesのnamespaceを使った階層に影響を受けることが分かったので下記のように内容を修正しました。
tweets_controller.rb
  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'
    ※認証情報の詳細は割愛しました。

認証情報を入れた後の最初のcurlコマンド結果.png
今度はauthenticate_user!が未定義だと指摘されました。
これは先述のcurrent_userのエラーと同じ内容だったので下記のように修正しました。

tweets_controller.rb
before_action :authenticate_api_v1_user!, only: %i[create]

authenticate_user!を修正した後に下記のエラーが発生しました。
authenticate_use!を書き換えた後のエラー文.png
400 bad requestなので、こちらから送っているリクエストに問題がありそうです。
これはcontrollerのストロングパラメーターでrequire(:tweet)としているにも関わらず、tweetをキーとして渡していないのが原因でした。

tweets_controller.rb
def tweet_params
    params.require(:tweet).permit(:user_id, :content, images: [])
end

最初はtweetキーのことに気付かずに一時的にrequire(:tweet)の記述を外して同じcurlコマンドを実行したところ、エラーの内容が変わりました。
user_idが見つからないエラー.png
user_idが不明だという指摘に変わっています。

モデル同士のアソシエーションは設定しているので問題無いなどと高を括っていたのですが、その認識が誤っていました。ふと、設定をしていた時のことを思い出していたのですが、マイグレーションのところで外部キーを設定した記憶が無かったのです。。。

20250603124429_create_tweets.rb
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

はい、実際にやっていませんでした。カッコワル...
直ぐにロールバックして、外部キーを設定してマイグレーションをやり直しました。

20250603124429_create_tweets.rb(修正後)
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コマンドを実行したところ、コマンドは成功してしまいました。
ストロングパラメーター修正して仮の成功.png

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'

curlコマンドを書き換えて最終的に正しく投稿できた.png

これでやっと正しく投稿することに成功しました。

色々な躓きがありましたが、勉強になりました。

ここまで拙い躓きを見て頂きありがとうございました。
読まれた方のエラー解決の一助になれば幸いです。

3. 参考にさせて頂いたサイト

curlコマンド完全ガイド:基礎から現場での実践的な使い方まで
[Rails] undefined method authenticate_user!の対処方法
paramsで値を取得する際にrequireを必ずしも使わなくもいいものなのでしょうか?

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?