summaryrefslogtreecommitdiff
path: root/app/models/scim/visitor.rb
blob: 85898f30cafb715631c391e58497fe35fd22bd86 (plain)
1
2
3
4
5
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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# frozen_string_literal: true

module Scim
  class Visitor
    include Varkon
    VISITORS = {
      and: :visit_and,
      co: :visit_contains,
      eq: :visit_equals,
      ew: :visit_ends_with,
      ge: :visit_greater_than_equals,
      gt: :visit_greater_than,
      le: :visit_less_than_equals,
      lt: :visit_less_than,
      ne: :visit_not_equals,
      or: :visit_or,
      pr: :visit_presence,
      sw: :visit_starts_with,
    }.freeze

    def initialize(clazz, mapper = {})
      @clazz = clazz
      @mapper = mapper
    end

    def visit(node)
      visitor_for(node).call(node)
    end

    private

    def visitor_for(node)
      method(VISITORS.fetch(node.operator, :visit_unknown))
    end

    def visit_and(node)
      visit(node.left).merge(visit(node.right))
    end

    def visit_or(node)
      visit(node.left).or(visit(node.right))
    end

    def visit_equals(node)
      query_for(node, attr_for(node) => node.value)
    end

    def visit_not_equals(node)
      if node.not?
        @clazz.where(attr_for(node) => node.value)
      else
        @clazz.where.not(attr_for(node) => node.value)
      end
    end

    def visit_contains(node)
      query_for(
        node,
        "#{attr_for(node)} LIKE ?",
        "%#{escape_sql_wildcards(node.value)}%"
      )
    end

    def visit_starts_with(node)
      query_for(
        node,
        "#{attr_for(node)} LIKE ?",
        "#{escape_sql_wildcards(node.value)}%"
      )
    end

    def visit_ends_with(node)
      query_for(
        node,
        "#{attr_for(node)} LIKE ?",
        "%#{escape_sql_wildcards(node.value)}"
      )
    end

    def visit_greater_than(node)
      query_for(node, "#{attr_for(node)} > ?", cast_value_from(node))
    end

    def visit_greater_than_equals(node)
      query_for(node, "#{attr_for(node)} >= ?", cast_value_from(node))
    end

    def visit_less_than(node)
      query_for(node, "#{attr_for(node)} < ?", cast_value_from(node))
    end

    def visit_less_than_equals(node)
      query_for(node, "#{attr_for(node)} <= ?", cast_value_from(node))
    end

    def visit_presence(node)
      if node.not?
        @clazz.where(attr_for(node) => nil)
      else
        @clazz.where.not(attr_for(node) => nil)
      end
    end

    def visit_unknown(_node)
      @clazz.none
    end

    def cast_value_from(node)
      case @clazz.columns_hash[attr_for(node).to_s].type
      when :datetime
        DateTime.parse(node.value).utc
      else
        node.value.to_s
      end
    end

    def attr_for(node)
      @mapper.fetch(node.attribute, node.attribute)
    end

    def query_for(node, *conditions)
      node.not? ? @clazz.where.not(*conditions) : @clazz.where(*conditions)
    end
  end
end