Class: SlowYourRoles::Serialize

Inherits:
Object
  • Object
show all
Defined in:
lib/methods/serialize.rb

Overview

Serialize support

Instance Method Summary collapse

Constructor Details

#initialize(base, column_name, _options) ⇒ Serialize

Returns a new instance of Serialize.



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# File 'lib/methods/serialize.rb', line 6

def initialize(base, column_name, _options)
  base.serialize column_name.to_sym, Array
  base.before_validation(:make_default_roles, on: :create)
  base.send :define_method, :has_role? do |role|
    self[column_name.to_sym].include?(role)
  end

  base.send :define_method, :add_role do |*roles|
    clear_roles if self[column_name.to_sym].blank?

    roles.each do |role|
      return false if !roles_marker.empty? && role.include?(roles_marker)
    end

    roles.each do |role|
      next if has_role?(role)

      self[column_name.to_sym] << role
    end

    self[column_name.to_sym]
  end

  base.send :define_method, :add_role! do |role|
    return false unless add_role(role)

    save!
  end

  base.send :define_method, :remove_role do |role|
    self[column_name.to_sym].delete(role)
  end

  base.send :define_method, :remove_role! do |role|
    remove_role(role)
    save!
  end

  base.send :define_method, :clear_roles do
    self[column_name.to_sym] = []
  end

  base.send :define_method, :make_default_roles do
    clear_roles if self[column_name.to_sym].blank?
  end

  base.send :private, :make_default_roles

  # Scopes:
  # ---------
  # For security, wrapping markers must be included in the LIKE search,
  # otherwise a user with role 'administrator' would erroneously be included
  # in `User.with_scope('admin')`.
  #
  # Rails uses YAML for serialization, so the markers are newlines.
  # Unfortunately, sqlite can't match newlines reliably, and it doesn't
  # natively support REGEXP. Therefore, hooks are currently being used to
  # wrap roles in '!' markers when talking to the database. This is hacky,
  # but unavoidable. The implication is that, for security, it must be
  # actively enforced that role names cannot include the '!' character.
  #
  # An alternative would be to use JSON instead of YAML to serialize the
  # data, but I've wrestled countless SerializationTypeMismatch errors
  # trying to accomplish this, in vain. The real problem, of course, is even
  # trying to query serialized data. I'm unsure how well this would work in
  # different ruby versions or implementations, which may handle object
  # dumping differently. Bitmasking seems to be a more reliable strategy.

  base.class_eval do
    alias_method :add_roles, :add_role
    alias_method :add_roles!, :add_role

    cattr_accessor :roles_marker
    cattr_accessor :column

    self.roles_marker = '!'
    self.column = "#{table_name}.#{column_name}"

    scope :with_role, (proc { |r|
      where("#{column} LIKE '%#{roles_marker}#{r}#{roles_marker}%'")
    })

    scope :without_role, (proc { |r|
      where("#{column} NOT LIKE '%#{roles_marker}#{r}#{roles_marker}%' OR #{column} IS NULL")
    })

    define_method :add_role_markers do
      self[column_name.to_sym].map! { |r| [roles_marker, r, roles_marker].join }
    end

    define_method :strip_role_markers do
      self[column_name.to_sym].map! { |r| r.gsub(roles_marker, '') }
    end

    private :add_role_markers, :strip_role_markers
    before_save :add_role_markers
    after_save :strip_role_markers
    after_rollback :strip_role_markers
    after_find :strip_role_markers
  end
end