`
xitong
  • 浏览: 6190725 次
文章分类
社区版块
存档分类
最新评论

cancan 一用户多角色解决方案

 
阅读更多
在这个应用的注册页面上有几个复选框(checkbox),它用来为我们的用户指定一个或多个角色。
The signup page showing the roles checkboxes.

在这个应用中有一个模型Role,它和模型User通过一个关联表进行了多对多关联。如下图,在数据库中有三条roles的数据。

>> Role.all 
 +----+-----------+-------------------------+-------------------------+ 
 | id | name | created_at | updated_at | 
 +----+-----------+-------------------------+-------------------------+ 
 | 1 | Admin | 2009-11-16 21:22:59 UTC | 2009-11-16 21:22:59 UTC | 
 | 2 | Moderator | 2009-11-16 21:23:06 UTC | 2009-11-16 21:23:06 UTC | 
 | 3 | Author | 2009-11-16 21:23:16 UTC | 2009-11-16 21:23:16 UTC | 
 +----+-----------+-------------------------+-------------------------+ 
3 rows in set 

现在的主要问题是数据库中的角色数据和文件authorization_rules中为角色定义的权限代码产生了紧密耦合

  1. role:authordo
  2. includes:guest
  3. has_permission_on:articles,:to=>[:new,:create]
  4. has_permission_on:articles,:to=>[:edit,:update]do
  5. if_attribute:user=>is{user}
  6. end
  7. end

如果这样定义角色和权限,我们就不能在不修改ruby代码的情况下去给角色表做修改,否则将角色存储在数据库中的优势就没有了。下面将演示如何去除耦合,将角色只定义在代码中,和数据库脱离关系。

我们不再需要模型Role,在模型User中,也将把下面两行代码删掉。

  1. has_many:assignments
  2. has_many:roles,:through=>:assignments

做完这些我们需要创建角色,因为角色和用户是关联的,所以我们在模型User中定义一个ROLE的常量

  1. classUser<ActiveRecord::Base
  2. acts_as_authentic
  3. has_many:articles
  4. has_many:comments
  5. ROLES=%w[adminmoderatorauthor]
  6. defrole_symbols
  7. roles.mapdo|role|
  8. role.name.underscore.to_sym
  9. end
  10. end
  11. end

现在,当我们想要修改角色的时候只需要修改这个常量就可以了。但问题仍然没有全部解决:如何将角色和用户进行关联?由于在数据库中没有了角色表,我们不得不在其他地方将角色和模型User进行关联。

下面我们将演示两种关联类型不同的做法。

嵌入一对多关联

目前在我们的应用中,角色和用户是多对多的关联关系。现在我们将其修改为一对多关系,使用户只能是角色中的一种。角色将存储在数据库表users当中,因为现在的角色是一个单一的值,所以我们只需要一个字符型的字段来存储它。现在让我们生成数据库迁移文件

script/generate migration add_role_to_users role:string 

执行文件为表添加一个新的字段

rake db:migrate 

下一步是在注册页面的表单中使用下拉菜单(select menu)代替先前使用的复选框(checkbox),像下面代码一样修改/app/views/users/new.html.erb

/app/views/users/new.html.erb

  1. <%=form.label:roles%>
  2. <%forroleinRole.all%>
  3. <%=check_box_tag"user[role_ids][]",role.id,@user.roles.include?(role)%>
  4. <%=hrole.name%><br/>
  5. <%end%>

修改为

/app/views/users/new.html.erb

  1. <%=form.label:role%>
  2. <%=form.collection_select:role,User::ROLES,:to_s,:humanize%>

我们使用collection_select来生成一个下拉菜单(select menu),这个方法通常需要一个模型做参数,但在这这样做也没有问题。当我们将参数设置为模型的时候,通常我们是传递一串模型对象数组和这些对象的属性来设置下拉菜单的值(options的value)和显示字符(options的innerText),现在我们传递了在模型User中定义的角色数组,我们为下拉菜单的值设置为角色.to_s方法和为显示字符设置调用:humanize方法。最终显示如下,我们只能选择一个角色。

The checkboxes have now been replaced by a select menu.

像我们在前面声明的权限验证一样,这里我们需要对方法role_symbols稍作修改,让它返回一个symbol类型

/app/models/user.rb

  1. defrole_symbols
  2. [role.to_sym]
  3. end

现在我们创建一个用户,并且把角色存储到数据库表Usersrole字段中

>> User.last.role  
=> "moderator"  

多对多关联

现在我们在一个模型中实现了一对多关系,但是在不恢复模型Role的情况下,我们如何实现一个用户指定多个角色的功能呢?这稍微有点复杂,我们可以把多个值序列化到表users的一个字段中去

一个解决方案是我们为表users建立一个text类型的字段roles,然后使用Rails的serialize方法对要存储的用户角色进行封装。这个方法在数据进入数据库之前会对字段调用to_yaml方法。这个方法确实能够达到存储多个角色的目的,但它在遇到根据指定角色进行查询的时候就行不通了。所以我们还得找一个其他的方法

取而代之的是我们使用一种叫位掩码(bitmask)的方法,它能够实现在一个integer类型的字段中存储多个值,并且能够根据角色查询出指定的用户来。

首先我们要为表users添加一个新字段--roles_mask,integer类型

script/generate migration add_roles_mask_to_users roles_mask:integer 

执行迁移文件

rake db:migrate 

为了方便位掩码(bitmask)和角色数组的转换,在模型User中我们需要为roles_mask写一个getter方法和一个setter方法

/app/models/user.rb

  1. defroles=(roles)
  2. self.roles_mask=(roles&ROLES).map{|r|2**ROLES.index(r)}.sum
  3. end
  4. defroles
  5. ROLES.reject{|r|((roles_mask||0)&2**ROLES.index(r)).zero?}
  6. end

这个setter方法把一个角色数组转换为位掩码(bitmask)然后赋值给roles_mask。getter方法通过遍历返回在位掩码中存储的角色。有一些插件可以完成这个工作,但因为在这我们只有一个字段需要处理并且也不需要写太多代码,所以我们就没有引入进来。

我们需要修改role_symbols方法来实现修改角色。如下代码,我们可以这样将角色数组转换为symbol类型。

/app/models/user.rb

  1. defrole_symbols
  2. roles.map(&:to_sym)
  3. end

最后一步,我们还得修改视图代码,我们仍需要使用checkbox这样在用户注册的时候可以选择多个角色

  1. <%=form.label:roles%>
  2. <%forroleinUser::ROLES%>
  3. <%=check_box_tag"user[roles][]",role,@user.roles.include?(role)%>
  4. <%=hrole.humanize%>
  5. <%end%>
  6. <%=hidden_field_tag"user[roles][]"%>

这里创建多对多关系使用的checkbox和第17集[视频 文章]有类似之处。user[roles][]最后的这个空的方括号的作用是:当我们对表单进行提交时系统会把选中的checkbox当做数组传递给模型User,User再对它们进行转换操作。check_box_tag方法的最后一个参数是当用户已经是某个角色的时候,这个checkbox默认会选中。隐藏表单确保即使没有给改用户分配角色系统也会传递一个空数组到后台。

让我们回到注册页面,现在我们注册一个新用户并给他分配两个角色。

Signing up a new user and assigning their roles.

我们可以在控制台查看这个用户的角色以及为它分配的位掩码(roles_mask)

>> User.last.roles  
=> ["admin", "author"]  
>> User.last.roles_mask  
=> 5  

每一个角色的值都会有两种情况(Each role has a value twice that of the role next to it),最小值是1.所以5是通过公式(1*1) + (2*0) + (4*1)计算出来的。

下面的问题是,如何才能找出某种角色的所有用户呢?假设我们想要找出所有角色是moderator的用户。我们可以为我们的模型User添加如下代码

/app/models/user.rb

  1. named_scope:with_role,lambda{|role|{:conditions=>"roles_mask&#{2**ROLES.index(role.to_s)}>0"}}

这个named scope需要一个角色作为参数,根据这个参数按位操作去检查某一个用户是否具有这个角色。我们可以在控制台测试一下,如下图,我们查找所有角色为admin的用户。

>> User.with_role("admin") 
+----+----------+-------------+-------------+-------------+-------------+------------+
| id | username | email       | crypted_... | password... | persiste... | roles_mask |
+----+----------+-------------+-------------+-------------+-------------+------------+
| 6  | paul     | paul@tes... | cffada11... | FDGoNtM1... | 35a7d8c8... | 5          |
+----+----------+-------------+-------------+-------------+-------------+------------+
 1 row in set 

结果返回了自从我们创建字段roles_mask后添加的匹配用户。

当你需要再添加新角色的时候就要小心了,因为前面我们设置的位掩码(bitmask)是基于角色数组ROLES位置的,如果你在这个数组的开始或中间添加一个新的角色,那么已经存在的角色也将会受到影响。

  1. ROLES=%w[adminmoderatorauthoreditor]

这一集到此结束。位掩码(bitmasking)在应用于多对多关系时非常好用,它仅需要一个integer类型字段。你可能会说,这只有在记录不属于数据库的时候才有用。我们的角色列表是绑定到代码当中的,在我们无需修改角色的情况下把角色列表定义到别处是没有意义的。如果你想在不改变代码的情况下添加或修改角色,你可以将它们存储到多对多关系数据库也是个不错的主意。


分享到:
评论

相关推荐

    秦灿灿21011132.pages

    秦灿灿21011132.pages

    1灿灿懒得中华文化.ppt

    1灿灿懒得中华文化.ppt

    气动机械手PLC控制部分设计_李灿灿

    气动机械手PLC控制部分设计_李灿灿

    科学工作流管理及调度研究_刘灿灿

    科学工作流管理及调度研究_刘灿灿

    基于低阈值单元的高性能低功耗设计方法.pdf

    一种高性能的低功耗设计方法介绍,有兴趣的可以下载看一下

    互联网金融与大数据.doc

    信用看不见,摸不着,但大数据的方式可以帮助复原一个人,甚至一群人的信用轮 廓,让个人或者群体的信用变得金光灿灿,触手可及。这将是根本性的改变,并产生巨 大的影响。大数据的应用例子中,对于天气预报的实践...

    cancanart.github.io

    #计算机科学与入门编程第二次作业##2010205317 孙灿灿##1.作业一作业一.1——Worldcloud_三国演义人物词频词云图通过字体大小正比于出现频次React人物出现频率。作业一.2——line_三国演义人物词频折线图曲线的形式...

    奇安信天眼新一代威胁感知系统的初步认识及使用~

    天眼安全设备的初步认识及使用,其中也讲了Web类攻击思路,蠕虫类告警分析,爆破(ftp爆破和ssh)和踩点分析,着重讲了天眼针对Web类的告警分析, (信息泄露,弱口令,XS攻击,XXE,SQL注入,...向前看长路漫漫亦灿灿#

    課题人教版小学语文一年级上册《小小的船》教学设计与反思

    课 题 人教版小学语文一年级上册小小的船教学设计与反思 作者及工作单位 张灿灿 瑞昌市码头亚东希望小学 教 材 分 析 小小的船是九年义务教育课程标准实验教科书小学语文一年级上册的一篇课文课文以优美

    聚丙烯基磷酸酯的制备及其阻燃黄麻织物的研究

    聚丙烯基磷酸酯的制备及其阻燃黄麻织物的研究,王灿灿,魏丽乔,以三氯氧磷、丙烯醇和甲醇为原料合成了一种聚丙烯基磷酸酯阻燃剂,并得出了其合成的较适宜条件:丙烯基二甲基磷酸酯单体浓度75%,

    汉诺塔问题

    用c语言解决输入数字n以解决n阶汉诺塔问题

    社会媒体研究中的数据搜集方法及趋势研究

    社会媒体研究中的数据搜集方法及趋势研究,杨灿灿,张爱华,随着信息技术的高速发展,社会媒体研究中的数据搜集方法及趋势研究web2.0新兴产物琳琅满目,信息传播方式更加多样化,社会媒体概念

    利用人工湿地处理污水并修复沙漠化的可行性

    利用人工湿地处理污水并修复沙漠化的可行性,陈灿灿,任勇翔,由于沙漠化地区经济与技术力量薄弱,城镇污水常未经工业化工艺处理而排放造成环境污染。沙漠化程度的加剧导致土地资源不断丧失,

    沙漠化地区污水处理用人工湿地植物的筛选

    沙漠化地区污水处理用人工湿地植物的筛选,陈灿灿,任勇翔,为提高用于冬季寒冷沙漠化地区的极浅床潜流人工湿的污水处理效率和稳定性,进行了适宜栽培植物的筛选。高羊茅、黑麦草、狗牙根、

    论文研究-五级供应链网络的建模及订货策略仿真研究 .pdf

    五级供应链网络的建模及订货策略仿真研究,雷少娟,赵灿灿,为描述供应链网络运作的复杂性和动态性,本文针对五级复杂供应链网络建立了供应链仿真模型,并在仿真模型的基础上分析比较了经济

    2019喜庆中国年春节联合晚会PPT模板

    这是一款2019喜庆中国年春节联合晚会PPT模板,犬吠鸡鸣春灿灿 ,莺歌燕舞日瞳瞳,本PPT模板适用于春节联合晚会、除夕辞岁晚会、新春迎福晚会、春节活动策划等,欢迎大家下载参考使用!该文档为2019喜庆中国年春节...

    干扰素可诱导静息期CD4 T细胞中的APOBEC3G磷酸化

    干扰素可诱导静息期CD4 T细胞中的APOBEC3G磷酸化,罗海华,陈灿灿,目的:揭示干扰素对静息期CD4 T细胞中APOBEC3G (A3G) 磷酸化的诱导作用。方法:从人外周血中分离原代CD4 T细胞,构建pcDNA3.1-A3G-HA真核表达�

    Nba2K13下载链接

    《NBA 2K》是一款由Take-Two Interactive旗下的Rockstar Games、2K Games制作的NBA篮球类游戏,初版发行于1999年11月10日。 此系列作品有着全方位的NBA体验、精致的球员人工智能和招牌动作等特色,用户可以通过细致...

    ILI9320DS_V0.44.pdf

    ILI9320DS_V0.44.pdf CSDN另外一个可下的是V0.42版本的。我的这个是V0.44版,我已经加入的pdf标签,更方便阅读,欢迎下载

Global site tag (gtag.js) - Google Analytics