使用虚拟属性构建表单
需求
有这么一个需求,
一个教学网站,上架的课程需要分成不同的类别,对不同的会员进行权限设置。
- 免费课
- VIP 课
- 员工课
这是一个非常常规的需求,一般做法是对课程的 Model 增加一个 :category
的字段,再判定就可以了。
但是,这个项目有一些「历史包袱」。「员工课」是新需求,本来网站的设计只考虑了「免费课」和「VIP课」,只用了一个 :vip_required
的布尔值字段作为判断。
对于一个已经运营的网站来说,为了尽可能少地修改原数据库,又能满足新需求,我采取的办法是新增一个 :staff
的布尔值字段来判定。
于是问题就来了。
在编辑或者新建课程 course
的时候,表单设计上非常别扭。因为 :vip_requied
和 :staff
都是布尔值,如果使用 simple_form
的时候,会自动采用 check_box
的组件。于是表单就会变成
- 是否VIP课
- 是否员工课
这里有两个问题。
- 这个两个选项实际上用互斥的,而
check_box
在使用习惯上代表着多选; - 选项不全,还有一个「免费课」的选项无法呈现。
最符合使用习惯的体验应该是这样:
问题就变成了,如何用两个布尔值的字段来构建这么一个 radio_buttons
表单?
解决方法
跟同事商量之后,采取的办法是构建虚拟属性。
新建一个虚拟属性
在 models/course.rb
里建一个 virtual_role
的虚拟属性,包括读、写方法。
def virtual_role
end
def virtual_role=
end
这个虚拟属性不需要写入数据库,仅仅在新建和编辑的时候作为中间变量来使用,需要满足的功能是
- 读取
:virtual_role
的时候,返回正确的:vip_requied
和:staff
值; - 写入
:virtual_role
的时候,写入正确的:vip_requied
和:staff
值;
可以这么来设计 :virtual_role
:virtual_role | :vip_requied | :staff | 课程类别 |
---|---|---|---|
1 | false | false | 免费课 |
2 | false | true | VIP 课 |
3 | true | false | 员工课 |
4 | true | true | 无此种情况 |
于是,对于读取方法,可以这么写
def virtual_role
if !staff && !vip_required
return 1
elsif !staff && vip_required
return 2
elsif staff && !vip_required
return 3
elsif staff && vip_required
return 4
end
end
对于写入方法,可以这么写
def virtual_role=(_new_vbirual_role)
_new_vbirual_role = _new_vbirual_role.to_i
if _new_vbirual_role == 1
self[:vip_required] = false
self[:staff] = false
elsif _new_vbirual_role == 2
self[:vip_required] = true
self[:staff] = false
elsif _new_vbirual_role == 3
self[:vip_required] = false
self[:staff] = true
elsif _new_vbirual_role == 4
self[:vip_required] = true
self[:staff] = true
end
end
再建一个表单使用的选项
def self.virtual_roles
[
[3, '员工培训课'],
[1, '免费公开课'],
[2, '对外售卖课']
]
end
# 因为第4种情况不是我们想要的,表单不应该可选。
表单设计
这样一来,就可以在表单里直接使用 :virtual_role
来做 radio_bottons
了。根据 simple_form
的文档,可以这么来写。
<%= f.collection_radio_buttons :virtual_role, Course.virtual_roles, :first, :last %>
这样就得到想要的效果了。
强参数
还有一件事不能忘了,就是修改 Controller 里的 permit_params ,把 :vip_required
和 :staff
删掉,把 :virtual_role
加上。
总结
基本思路是这样的
- 构建虚拟属性作为选项
- 穷举所有选项
- 读取虚拟属性时,返回相应字段的值
- 写入虚拟属性时,写入相应字段的值
- 构建表单
- 强参数检查