Bitmask在Rails中的应用

Bitmask

bitmask(位掩码),是利用二进制位,表示多种状态的组合,例如:4个状态的数据,有16种组合,那么就可以利用4位的二进制数,去表示这个16种组合,然后在通过按位的逻辑运算(OR,AND, XOR),来达到使用极少的空间存储和表示数据。

解决的问题

在大部分的应用程序中,我们都会遇见模型的多对多关联,比如用户和用户所在的分组,这就存在了多对多关联,如果按照正常的思路的话,我们会使用,中间表关联两张数据表,已到达多对多。但是使用关联表,会增加查询的复杂度,需要使用Join语句关联。
其实实现这个关联的办法还有一个,那就是使用bitmask,当然了,这个方法也仅限于,N:M中,M的数量不多的情况下,大概在M < 10,具体的实现方式就是:

class User < ActiveRecord::Base
  5.times do |i| 
    const_set("GROUP#{i + 1}", 1 << i)  
  end  
  # GROUP1 = 1 
  # GROUP2 = 2 
  # GROUP3 = 4 
  # GROUP4 = 8 
  # GROUP5 = 16 
end

那如果一个用户属于分组2和分组3 那么它的掩码就是:6 查询就是:

User.where(‘groups & 6 = 6’)

不在分组2和3的查询就是:

User.where(‘groups & 6 = 0’)

优点:查询语句简单,不需要二外的两张表,和join查询
缺点:使用 where groups & mask = mask 的方式是无法使用索引的,这样性能就会下降

第三种方案,升级版的bitmask
要让bitmask的查询方式使用到索引,我们可以将用 按位与的查询语句替换成待遇 in 语句的查询语句这样就可以使用到索引了。
还是原先的例子查询属于分组1和2的用户:
User.where(groups: (1...(1 << 5)).select{|x| x & 3 == 3})

这个原理就是,先生成出所有可能出现的掩码,然后在对掩码进行筛选,找出包含1和2的掩码也就是,按位与 3 得 3 的掩码,这样求出的集合就可以用于 in 语句查询了。

关于这里使用索引,根据索引的原理,被索引数据的选择性,是很注意的指标,在 bitmask中如果状态的组合数低的话,索引的效果是不明显的。

优点:不需要二外的两张表,和join查询,并且可以使用索引性能也不错。
缺点:查询的代码稍微复杂一点。不过我们可以是用Ruby的元编程,改进它

class User < ActiveRecord::Base

  GROUP_SIZE = 5  

  GROUP_SIZE.times do |i| 
    const_set("GROUP#{i + 1}", 1 << i)  
  end

  # GROUP1 = 1 
  # GROUP2 = 2 
  # GROUP3 = 4 
  # GROUP4 = 8 
  # GROUP5 = 16 

  BITMASKS = (1...(1 << GROUP_SIZE)).to_a 

  # 
  # Find User with groups
  #
  # @example 
  #   User.with_groups(User::GROUP1, User::GROUP3)
  #
  # @param [Integer] group mask
  # @return [ActiveRecord::Relation<User>]
  #
  def self.with_groups(*groups) 
    mask = groups.sum
    self.where(groups: BITMASKS.select {|x| x & mask == mask})  
  end  

end

其实我们上面做的事情,已经有gem帮我们实现了bitmask-attributes
使用 bitmask_attributes

class User < ActiveRecord::Base
  bitmask :groups, :as => [:group1, :group2, :group3, :group4]
end

然后我们的查询就可以使用它提供的 named scope

User.with_groups(:group1, :group2)

cancan

在 cancan 中有一个 bitmask 实际应用的例子,就是在基于角色的权限验证中,cancan 提供了,用户多角色的功能,它的默认实现就是,通过为 users 表添加一个名为 roles_mask 的 bitmask 字段,然后在 User模型中添加下面的方法,来操作角色。

class User < ActiveRecord::Base

  def roles=(roles)
    self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
  end

  def roles
    ROLES.reject do |r|
      ((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
    end
  end
end 

这个通过 bitmask 实现的角色控制,还被抽取成了单独的Gem叫role_model

总结

利用 bitmask,存储状态可以为我们节省很多的存储空间,同样它也会增加代码的复杂度,还好Ruby生态圈有很多很好的工具帮我们解决复杂度的问题,但是想利用 bitmask还是要看的实际应用场景,比如上面利用 bitmask去代替表关联的场景要谨慎使用,因为虽然减少了数据模型,但是它只能表示数量少的关联并且这样的模型设计是不符合数据库设计范式的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容