如何RESTful更新has_and_belongs_to_many集合?

时间:2009-07-06 20:46:25

标签: ruby-on-rails rest

我有两个脚手架生成的模型,学生。他们与has_and_belongs_to_many实现了多对多的关系 我希望能够改变哪个班级一个学生以及哪个学生正在攻击每个班级即可。也就是说,我想修改学生变量(添加和删除项目),反之亦然。
我如何RESTful地执行此操作? 如果我从学生列表中删除,那么我似乎想要调用 update 在我的students_controller上。如果是这种情况,那么我应该作为参数传入什么来修改 classes 变量?另一个 es的集合(删除了正确的)?
我的另一个想法就是在students_controller中调用一些动作(比如remove_class)并传入要删除的的ID。这似乎很敏感,但不是RESTful。

最好的方法是什么?

5 个答案:

答案 0 :(得分:8)

解决此问题的关键是正确识别您正在修改的资源。在这种情况下,您正在修改的资源是班级与学生之间的关系,我将其称为Enrollment

在Rails中习惯使用has_many :through优先使用has_and_belongs_to_many。您可能希望更改域逻辑以适应自定义,但如果您确实不需要存储关于关系的元数据,您也可以逆转趋势。

REST的一个关键思想是RESTful资源不需要映射到模型。您应该创建一个EnrollmentsController并在config / routes.rb中添加一行:

map.resources :enrollments

然后你就可以创建和删除你的关系:

class EnrollmentsController < ApplicationController
    def create
       @student = Student.find(params[:student_id])
       @course = Course.find(params[:course_id])
       @student.courses << @course
       if @student.save
         #do happy path stuff
       else
         #show errors
       end
    end

    def destroy
       @student = Student.find(params[:student_id])
       @course = @student.courses.find(params[:course_id])
       @student.courses.delete( @course )
    end
end

你可以为这些动作制作按钮:

<%= button_to "Enroll", enrollments_path(:student_id => current_student.id, :course_id => @course.id ), :method => :post %>
<%= button_to "Withdraw", enrollment_path(1, :student_id => current_student.id, :course_id => @course.id ), :method => :delete %>

上面一行中的1充当:enrollment_id应该去的占位符,并且是一小段语法醋,提醒您正在克服Rails框架的意愿。

答案 1 :(得分:0)

如果这确实是REST,那么你只需要同时更改班级和学生,要么将关系添加到两者,要么从两者中删除它。在检查上传的模型是否自洽之后,您可以将其与之前的模型进行比较,并进行必要的更改。无论这种变化是由班级驱动还是由学生驱动,都会成为实施细节。

还有两个选择:

您可以通过让班级或学生拥有关系来减少REST状态信息的冗余,但这可能不符合您的需求。

你也可以保持双方的关系信息,但决定一个是规范的。因此,例如,如果学生拥有的是学生,而不是学生,则忽略对学生的belongs_to的任何更改。

我更喜欢第一种选择,但我觉得至少应该考虑这些。

答案 2 :(得分:0)

我不确定你是想要RESTful地做这两件事。通过在两者之间创建级联删除关系,您可能会获得更多里程。

这是关于Cascade Deleting on Rails的SO讨论。

答案 3 :(得分:0)

你可以拥有两个嵌套资源吗?

取决于你是否有params[:class_id](请不要说你叫你的模特类!)或者你是否有一个params[:student_id]你可以告诉哪个操作是哪个对象被召唤(参见附件一侧的创作样本):

class SchoolClassController < ApplicationController

before_filter :get_school_class, :except => [:new, :create, :index]
before_filter :get_student

def create
  get_school_class(false)
  get_student(false)
  if @school_class
    if @student
      (@school_class.students << @student)
    else
      redirect_to "/" and return
    end
  else
    @school_class = SchoolClass.new(params[:school_class])
  end
  if @school_class.save
    redirect_loc = @student ? student_school_class_path(@student, @school_class) : school_class_path(@student)
    redirect_to redirect_loc and return
  else
    redirect_to "/"
  end

end

private

def get_school_class(will_redirect=true)
  begin
    @school_class = SchoolClass.find(params[:id])
  rescue AR::RNF
    (redirect_to "/" and return false) if will_redirect
  end
end

def get_student(will_redirect=true)
  if params[:student_id]
    begin
      @student = Student.find(params[:student_id])
    rescue AR:RNF
      (redirect_to "/" and return false) if will_redirect
    end
  end
end

end

原谅任何错误!应该真的用作理论范例

答案 4 :(得分:0)

在根据 rails 6.1.0 指示多对多关系 (17 to be exact) 时添加了多种方法

根据您从前端接收到的内容,无论是整个相关对象还是它们的 ID,您都需要调用不同的方法:

使用整个相关对象的数组:collection_plural=

即:@student.classes = new_student_classes_array

接收仅包含相关对象主键(通常为 id)的数组:collection_singular_ids=

即:@student.class_ids = new_student_classes_array