为了简单起见,假设我有一个简单的具有多个贯穿关系的
class User < ActiveRecord::Base
has_many :courses, :through => :registrations
end
class Registration < ActiveRecord::Base
belongs_to :user
belongs_to :course
end
class Course < ActiveRecord::Base
has_many :users, :through => :registrations
end
我想保护我的应用程序的安全,所以我使用attr_accessible
将我的属性列入白名单。
我的问题有两个:
我该如何设置我的白名单属性,以便通过表单创建一个新的Registration对象(传入
:user
和:course
,但不冒稍后恶意更新这些外键的风险?我该如何设置我的验证,使两个
belongs_to
关联都是必需的,但也允许在嵌套表单中创建注册对象?
第一个问题的答案
一种解决方案是可以将:user
和:course
标记为只读。
attr_readonly :user_id, :course_id
通过这种方式,可以在创建时设置它们,但不能在更新时设置。如果您希望它们在未来可更新,您可以按照@registration.update_dangerous_stuff(params)
在注册模型上创建一个特殊的方法,并且只在具有更高权限的用户可以访问的控制器操作中使用此方法。该方法将使用类似update_column
的内容来更改这些字段。
另一种方法可以是创建用于设置用户和课程的虚拟属性,这些属性具有决定现在是否可以设置用户或课程的逻辑。下面是一个例子。
attr_accessor :safe_user, :safe_course
attr_accessible :safe_user, :safe_course
before_save :set_user, if: 'user.blank?'
before_save :set_course, if: 'course.blank?'
def set_user
self.user = safe_user
end
def set_course
self.course = safe_course
end
因此,在表单中,您将使用safe_user
和safe_course
字段,而不是user
和course
。在这种情况下,它只会设置实际的user
和course
,如果它们是空的,并且它们实际上从未公开为可访问的。您可以通过使用回调条件来绕过此规则。
第二个问题的答案
当你以复杂的形式提交一堆数据时,你应该想出一种方法来判断这些数据中哪些是有意义的,哪些只是占位符。如果没有提交有意义的数据,就不要创建注册。你必须决定哪些表单字段"足够",才能看到有人真的试图填写它们并犯了错误,而不是有人从未接触过嵌套表单。如果没有简单的方法来确定这一点,一种方法是在嵌套表单中添加一个"添加此注册"复选框。如果选中该复选框,将尝试创建并运行验证。之后,您可以添加一些javascript来隐藏此复选框,并在有人激活嵌套表单中的任何字段时自动选中它
为了促进后端的这种行为,您有accepts_nested_attributes_for
rails方法。例如,你可以在课程模型中说以下内容。
accepts_nested_attributes_for :registrations,
reject_if: -> attrs { attrs[:name].blank? }
# Don't forget this too
attr_accessible :registrations_attributes
Rails还提供了一个快捷方式。
accepts_nested_attributes_for :registrations, reject_if: :all_blank
在复选框的情况下,您可以说reject_if: -> attrs { attrs[:my_check_box] == '1' }
等
有了reject_if
,它会告诉rails,如果给定proc中的条件不满足,它将忽略注册,而不是试图创建和验证它
希望这能给你一些想法。