Rails指导建议以避免深层嵌套路由,因此我想知道如何在不嵌套多个路由的情况下使这种关系工作:
用户有很多课程
课程有很多课程
课程有很多问题
一个问题有很多答案
约束:用户只能看到自己的课程,课程,问题和答案(应用程序需要这样)
作为第一次尝试,我们可以做到这一点(这违反了上面提到的建议):
resources :users
resources :courses do
resources :lessons do
resources :questions do
resources :answers
end
end
end
不幸的是,这导致了长嵌套路由,但我发现我们可以像这样传递shallow: true
resources :courses, shallow: true do
resources :lessons do
resources :questions do
resources :answers
end
end
end
简而言之,浅选项不会为这些操作生成嵌套URL(:show,:edit,:update,:destroy),例如,课程的show动作将是lessons/2
而不是{{1但是这样做的不便之处在于:我无法通过current_user确定范围。
为了确保课程仅针对他/她的主人,我应该做以下事情:
courses/1/lessons/2
这意味着课程ID应该作为url中的参数传递,这不适用于浅层路由。 (对于其他路线也是如此:即:显示一个问题,我们应该通过course_id,lesson_id和question_id作为参数)
知道如何解决这个问题吗?
更新:
让我的问题更简单:
如果我们要遵循Rails建议并且不要将资源嵌套到多个级别,那么我们如何在不通过{def show
current_user.courses.find(params[:id].lessons.find(params[:lesson_id])
end
的情况下获得属于current_user
的答案网址中有{1}}和course_id
以及lesson_id
?
答案 0 :(得分:4)
你所抓到的是一个常见的错误,你将上下文与授权混淆。嵌套在REST中的作用并不是决定谁得到了什么 - 嵌套告诉你资源之间的关联。
例如:
/users/:user_id/comments
创建一个有意义的restful URI,告诉我们在请求该端点时会发生什么 - 该用户的评论。
当前用户确定范围时,您希望从会话或令牌中获取用户,而不是使用参数:
class ReviewsController
# POST /books/:book_id/reviews
def create
@comment = Book.find(params[:books_id]).comments.new(comment_params) do |c|
c.user = current_user # from the session
end
# ...
end
# ...
end
使用param指定当前用户非常糟糕,因为它会让孩子们玩恶搞。恶意用户可以通过更改参数或使用firebug来改变表单上的隐藏输入,从而像其他用户一样执行操作。
您的情况很奇怪,但如果我理解正确,问题的核心实际上是提供间接关联 - 而不是路由。
class User
has_many :courses
has_many :lessons, through: :courses
has_many :questions, through: :courses
has_many :answers, through: :courses
end
class Course
belongs_to :user
has_many :lessons
has_many :questions, through: :lessons
has_many :answers, through: :lessons
end
class Lesson
belongs_to :course
has_many :questions
has_many :answers, through: :questions
has_one :user, through: :course
end
class Question
belongs_to :lesson
has_many :answers
has_one :course, through: :lesson
has_one :user, through: :course
end
class Answer
belongs_to :question
has_one :user, through: :question
has_one :lesson, through: :question
has_one :course, through: :question
end
# routes.rb
resources :courses, shallow: true do
resources :lessons
end
resources :lessons, only: [], shallow: true do
resources :questions
end
resources :questions, only: [], shallow: true do
resources :answers
end
class AnswersController < ApplicationController
# We only need question for the nested routes
before_action :set_question, only: [:new, :create, :index]
before_action :set_answer, only: [:show, :edit, :update, :destroy]
# /lessons/:lesson_id/answers
def index
@lessons = @course.lessons
end
# /lessons/:id
def show
@lesson = current_user.questions.find(:id)
end
private
def set_question
@course = current_user.questions.includes(:answers)
.find(:question_id)
end
def set_answer
@answer = current_user.answers.find(:id)
end
end