QA@IT

Railsのresourcesネスト時のURIについて

3706 PV

Railsを勉強中の身です。
質問させていただきます。
よろしくお願いいたします。


以下の2つのモデルがあり、

Userモデル → has_many :articles
Articleモデル → belongs_to :user

各モデルは、以下のカラムを持っているとします。

User:id
Article:id / user_id

これについて、resources :articlesを書くと、以下のURLでアクセスできると思います。
(各articleのuser_idは右に示すものとします)

/articles/1 # user_id:1
/articles/2 # user_id:1
/articles/3 # user_id:2
/articles/4 # user_id:1
/articles/5 # user_id:3

これを、以下のように、各ユーザに紐付けてアクセスできるようにしたいと思っています。

/users/1/articles/3 # Article.find(id: 4)
/users/2/articles/1 # Article.find(id: 3)
/users/3/articles/1 # Article.find(id: 5)

単純にresourecesをネストさせてもうまくいかないのは確認できているのですが、どうすればスマートにできるでしょうか。
あるいは、上記のような方法は行なわない方がよいのでしょうか。


質問の意図として、Articleモデルは各ユーザは自分の投稿したものにしかアクセスできないようにしたいと思っております。
(/users/:idをログイン後のルートにする、など)
なので、/articles/:idでアクセスできてしまうと、他のユーザの記事にアクセスでき、アクセス制限すればよいとはいれ潜在的な脆弱性が入ってしまうのではないか、と思っています。

アドバイス等、何卒よろしくお願いいたします。

回答

アクセスコントロール目的なら、routes.rb は ironsand さんの挙げた例のようにし、ArticlesController では、

@article = Article.find(params(:id))

の代わりに、

@article = @user.articles.where(user_id: params[:user_id], id: params[:id]).first

とすることで、URL に含まれる user_id に無関係な Article を参照することを避けられます。

質問にある例なら、/users/1/articles/4 だと @article == Article.find(4) になりますが、/users/2/articles/4 だと @article == nil になります。

どうしてもユーザごとに記事の URL を 1 から始める必要があるなら、(ironsand さんのコメントにあるように) id とは別のカラムに値を持つのがよいと思います。composite_primary_keys を使って複合主キーを使うという方法もありますが、経験上、コードが複雑になりやすいのでおすすめはしません。

編集 履歴 (0)
  • なるほど、ありがとうございます!ご指摘のとおり構造が複雑になりそうなので、一応方法だけ理解する、というところでとどめておこうと思います。。。 -

下記のように resources のネストでできるはずですが、どのように書かれてますか?

resources :users do
  resources :articles
end

あと、URLを手で入力するだけで他のユーザーのページも見えますので
/users/:idをログイン後のルートにするのはセキュリティ対策としては意味がありません。

編集 履歴 (0)
  • ご回答ありがとうございます。もちろんその方法は試しておりますが、それだと例えば`/users/2/articles/1 # Article.find(id: 3)`が成り立たないと思います。(`/users/2/articles/3`になってしまう) -
  • 申し訳ありません、質問の意図を読み取れていませんでした。自分のわかる範囲で答えさせてもらいますと、Article モデルに別に sub_id などのカラムを用意して `Article.find_by(sub_id: 1, user_id: 2)` などのように参照すればどうにかなると思うのですが、他にも必要な処理があるかもしれません。 -
ウォッチ

この質問への回答やコメントをメールでお知らせします。