一种Ruby on Rails5框架下的用户自定义视图实现方法
作者: 贾薇
摘要:设计一个Ruby on Rails 5框架下的视图管理方案,在不影响系统安全性的前提下,满足生产环境中用户在后台自定义视图,高自由度定制页面外观并即时动态载入呈现的需求。
关键词:Web;MVC;视图;Ruby on Rails 5
中图分类号:TP311 文献标识码:A
文章编号:1009-3044(2022)30-0037-04
开放科学(资源服务)标识码(OSID):
一个多站点系统通常由不同用户建立并管理,涵盖不同内容面向不同受众;用户对站点外观有个性化要求。除了采用预制模板之外,高级用户会希望系统提供自定义视图功能,能够高自由度地改变页面布局、外观及内容排版方式,并能无须重启即时生效;同时不能影响系统的安全性。
设计一个Ruby on Rails 5框架下的自定义视图管理方案,并在Linux系统中实现,使其具有上述功能。
1 系统环境
开发环境为Ubuntu16.04操作系统,Ruby on Rails 5.2.3(以下简称RoR),MySQL 5.7数据库。
2 视图
RoR遵循模型-视图-控制器设计模式,即MVC模式。视图是表达数据显示逻辑的地方, 通常完成或辅助完成GUI 功能[1]。
2.1 RoR 5的视图
RoR 5返回客户端的完整 HTML 由 ERB 视图文件和包装它的布局文件,以及视图可能引用的所有局部视图文件组成[2]。
2.1.1 视图模板
常用视图模板为ERB模板,由Ruby代码和HTML组成;<%%>标签内为Ruby代码,渲染后返回完整的HTML。ERB模板在RoR框架中以.erb文件的方式存储。erb模板包含Ruby代码,允许用户上传erb文件会带来安全性问题;攻击者上传了一个可执行的脚本文件,并通过此文件进行进一步的恶意操作,会造成系统崩溃、数据泄露等严重安全事件[3]。
2.1.2 局部视图
局部视图的作用是把渲染过程分成多个更容易管理的部分。局部视图从模板中提取代码片断并保存在独立的文件中,然后在模板中重用[2]。在视图中使用 render 方法来渲染局部视图:
<%= render "menu" %>
上述代码会在当前路径下载入_menu.html.erb 局部视图文件并渲染。局部视图的文件名总是以下划线开头,以便和普通视图文件区分开来,但在引用局部视图时不写下划线[2]。注意,此处支持相对路径,以加载其他目录中的局部视图文件。同一个局部视图文件可以多次加载,也可以在一个页面上加载多个局部视图文件[4]。
render方法通过locals关键字传递父视图的参数:
<%= render "menu",locals:{title: @now_title} %>
上述代码表示将父视图的@now_title传递到局部视图,并通过title变量读取。
2.1.3 局部布局
应用于局部视图的布局称为局部布局。局部布局和应用于控制器动作的全局布局不一样,但两者的工作方式类似[2]。即局部布局只作用于页面的局部区域。
2.2 视图的嵌套
局部视图中可以加载其他的局部视图,方法同前述。
3 定义自定义布局
通过提供布局、视图模板与局部视图选项让用户自由选择组合,配合默认/自定义样式文件,即可实现高自由度自定义页面的效果。构造一个模型与对应的编辑器、解析方法,使用户可以在后台在线编辑页面布局,并实现页面外观的动态加载。
3.1 模型
需要自定义视图的页面通常用于提供给普通用户浏览,在CMS中即首页、栏目页、内容页等[5]。创建模型FrontPage,用于关联控制器与自定义视图。FrontPage的act属性用于关联处理该页面的控制器/动作,如首页记为“site/index”,即site控制器的index动作;layout属性用于定义该页面使用的视图模板;layout_params属性用于定义视图的详细布局信息。
3.2 编辑
3.2.1 LAYOUT常量
为避免暴露后端文件名,FrontPage对象layout属性的值应使用代字,在后端进行转换。定义Hash类型常量LAYOUT用于存储模板数据,键名为代字,作为layout属性的值;值为Hash对象,用于存储视图模板的属性。LAYOUT常量结构如下:
LAYOUT= {
‘'index201801’ => {
:title => '首页模板201801',
:charset => 'UTF-8',
:view => 'index_201801',
:layout_file => 'index_201801.html',
:loop_list => true,
:img => 'data:image/gif;base64,(略……)',
:config => {:top => 5,:slide => 10,:affiche => 7,:topic => 3,},
:core_assets => ['/stylesheets/base.css','/stylesheets/index.css',(略……)],
:partial => {
'one_col_201801' => {
:title => '单行单列图片链接带标题广告',
:charset => 'UTF-8',
:view => 'one_col_201801',
:layout_file => 'one_col_201801.html',
:img => 'data:image/gif;base64,(略……)'
},
'two_cols_201801' => {
:title => '两列对分图片链接',
:charset => 'UTF-8',
:view => 'two_cols_201801',
:layout_file => 'two_cols_201801.html',
:img => 'data:image/gif;base64,(略……)'
},
(略……)
}
},
(略……)
}
LAYOUT常量主要键值对含义如表1:
partial的值为Hash结构,每个键值对定义一个父模板可使用的局部视图。键名为代字,用于识别;值为Hash类型,用于存储局部视图的属性。主要键值对如表2:
3.2.2 编辑模块
编辑模块为一个html文件,与erb视图文件一一对应,用于在后台可视化编辑器中直观展示页面布局和编辑配置。编辑模块用线框图展示视图的布局结构,并通过Dom属性记录不同区域可加载的信息类型。
图1中左侧为实际页面,右侧的编辑模块用线框图模拟了视图的实际布局。其中蓝色区域不可自定义,白色区域中方括号内的内容为当前配置到此区域的对象。channel表示对象为Channel类,其后表示具体选中的类实例。
<table width="100%" border="0" cellspacing="2" cellpadding="5">
<tr>
<td width="5%" class="full">学习</td>
<td width="95%" class="channel[5]|obj[{className}][4]" title="topic">专题</td>
</tr>
</table>
上述代码片段为某编辑模块的局部,使用table结构模拟一个左右布局的页面区域。左侧样式类名为full的TD标签,表示一个不可自定义的区域,右侧表示一个可自定义的区域。title属性的值是区域的唯一标识符,在同一个编辑模块中应具有唯一性;注意,此处应做好规划并与erb视图文件对应位置的标识符保持一致。class属性的值用于定义该区域可展示的内容,channel表示可选择Channel类对象,即频道;obj表示Channel类的子对象,即文章,其后的第一个方括号表示载入子对象时调用的局部视图名,{className}表示子对象类名,此处为News,意即此处调用_news.html.erb局部视图并通过News对象实例渲染;方括号中的数字表示数量上限。
3.2.3 视图编辑器
视图编辑器是读取、解析、生成、更新页面配置数据的人机接口。
视图编辑器根据LAYOUT常量加载可使用视图,并生成可视化UI。选择一个视图模板后会自动载入该模板的编辑模块,页面脚本逐个检查编辑模块的每个区域:如果该区域的className不为full,则该区域可自定义,根据前述规则解析className并添加脚本动作;当该区域触发点击事件时,打开类选择器,通过类选择器为该区域配置内容。类选择器的配置受该区域class属性的限值,以前述代码片段为例:channel[5]限制类选择器只能选择Channel实例,不可超过5个。
图3所示类选择器关联Channel类对象,列出所有可选择的对象实例;采用复选框进行勾选,深色底为已选项;此时已选择5个选项,达到该区域上限,其他选项的复选框已处于不可选状态。
一个视图模板除了固定的可编辑区域外,还可以根据LAYOUT常量中对应partial键值载入可选择的局部视图;这些局部视图可重复使用。点击可视化按钮可顺序插入新的局部视图;自动生成唯一字符串作为该局部视图的唯一标识符。配置局部视图内容的方法同上,并可对局部视图执行删除、排序等操作。
视图编辑器同步更新页面对象配置数据front_page_layout_params和可视化显示信息。
3.2.4 配置数据
配置数据front_page_layout_params在页面同时以JSON对象和表单元素的形式存在,并保持同步更新。
图4为一个经可视化处理的front_page_layout_params数据,蓝框中的键值对为该视图模板的固定区域;这些区域的键名由其编辑模块中的title属性定义;不同视图模板的键名及键值对数量均有差异。loop_list键值对中存储插入的局部视图配置数据。channel代表Channel类对象,ad代表Ad类对象,1780、308为实例ID;partial_id的值表示LAYOUT常量对应视图模板的partial值中由“one_col0_202001”键名指向的键值对,由此可定位到局部视图文件。