Class: Scim2::Filter::ArelHandler

Inherits:
Object
  • Object
show all
Defined in:
lib/scim2/filter/arel_handler.rb

Overview

Implementation of parser handler which translates SCIM 2.0 filters into AREL. In order to do this, instances of this class will need to be passed the mapping of attribute names to columns/AREL.

Examples:

# userName sw "J"

mapping = {
  userName: User.arel_table[:name],
}

# "users"."name" LIKE 'J%'
# urn:ietf:params:scim:schemas:core:2.0:User:userType ne "Employee" and not (name.familyName.value co "ab" or name.familyName.value co "xy")

mapping = {
  userType: User.arel_table[:type],
  name: {
    familyName: User.arel_table[:family_name],
  },
}

# "users"."type" != 'Employee' AND NOT ("users"."family_name" LIKE '%ab%' OR "users"."family_name" LIKE '%xy%')
# emails[type eq "work" and value ew "example.com"]

mapping = {
  emails: ->(path, op, value) {
    case path
    when [:type]
      User.arel_table[:email_type]
    when [:value]
      User.arel_table[:email]
    end
  },
}

# "users"."email_type" = 'work' AND "users"."email" LIKE '%example.com'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(arel_mapping) ⇒ ArelHandler

Returns a new instance of ArelHandler.


49
50
51
# File 'lib/scim2/filter/arel_handler.rb', line 49

def initialize(arel_mapping)
  @arel_mapping = arel_mapping
end

Instance Attribute Details

#arel_mappingObject (readonly)

Returns the value of attribute arel_mapping.


47
48
49
# File 'lib/scim2/filter/arel_handler.rb', line 47

def arel_mapping
  @arel_mapping
end

Instance Method Details

#apply_arel_operation(arel, op, value) ⇒ Object (protected)


124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/scim2/filter/arel_handler.rb', line 124

def apply_arel_operation(arel, op, value)
  case op
  when :eq
    arel.eq(value)
  when :ne
    arel.not_eq(value)
  when :co
    arel.matches("%#{value}%")
  when :sw
    arel.matches("#{value}%")
  when :ew
    arel.matches("%#{value}")
  when :gt
    arel.gt(value)
  when :ge
    arel.gteq(value)
  when :lt
    arel.lt(value)
  when :le
    arel.lteq(value)
  when :pr
    arel.not_eq(nil)
  end
end

#before_nested_filter(*_ignored) ⇒ Object

Begins capturing nested filter conditions inside a SimpleHandler

Returns:

  • nil


90
91
92
93
# File 'lib/scim2/filter/arel_handler.rb', line 90

def before_nested_filter(*_ignored)
  @nested_filter_handler = SimpleHandler.new
  nil
end

#lookup_arel(attribute_path) ⇒ Object (protected)

Looks up the arel object from the mapping according to the given attribute path

Parameters:

  • attribute_path (Array<Symbol>)

    the attribute name(s) being filtered on, split by .

Returns:

  • (Object)

    the object returned by the mapping


152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/scim2/filter/arel_handler.rb', line 152

def lookup_arel(attribute_path)
  arel = arel_mapping.dig(*attribute_path)

  case arel
  when NilClass
    raise ArgumentError, "Attribute #{attribute_path.join(',').inspect} not found in mapping"
  when Arel::Predications, Proc
    arel
  else
    raise ArgumentError, "Mapping for attribute #{attribute_path.join(',').inspect} is not a valid arel object"
  end
end

#on_attribute_filter(attribute_path, value, context:, op:, schema: nil) ⇒ Hash<Symbol, Object>

Handle basic attribute comparison filters (e.g. preference.color eq "red")

Parameters:

  • attribute_path (Array<Symbol>)

    the attribute name(s) being filtered on, split by .

  • value (Object)

    the value being compared against

  • op (Object)

    the comparison operator (e.g. :eq)

  • schema (String) (defaults to: nil)

    schema namespace of the attribute

Returns:

  • (Hash<Symbol, Object>)

66
67
68
69
70
# File 'lib/scim2/filter/arel_handler.rb', line 66

def on_attribute_filter(attribute_path, value, context:, op:, schema: nil)
  arel = lookup_arel(attribute_path)
  arel = arel.call(attribute_path, op, value) if arel.is_a?(Proc)
  apply_arel_operation(arel, op, value) or raise Racc::ParseError, "invalid attribute operand #{op.inspect} with argument #{value.inspect}"
end

#on_logical_filter(filter1, filter2, context:, op:) ⇒ Hash<Symbol, Object>

Handle logical filters (e.g. name.givenName sw "D" AND title co "VP")

Parameters:

  • filter1 (Hash<Symbol, Object>)

    the left-hand side filter

  • filter2 (Hash<Symbol, Object>)

    the right-hand side filter

  • op (Object)

    the logical operator (e.g. AND)

Returns:

  • (Hash<Symbol, Object>)

77
78
79
80
81
82
83
84
85
86
# File 'lib/scim2/filter/arel_handler.rb', line 77

def on_logical_filter(filter1, filter2, context:, op:)
  case op
  when :and
    filter1.and(filter2)
  when :or
    filter1.or(filter2)
  else
    raise Racc::ParseError, "invalid logical operand #{op.inspect}"
  end
end

#on_nested_filter(attribute_path, filter, context:, schema: nil) ⇒ Hash<Symbol, Object>

Handle nested filters (e.g. emails[type eq "work"])

Parameters:

  • attribute_path (Array<Symbol>)

    the attribute name(s) being filtered on, split by .

  • filter (Hash<Symbol, Object>)

    the nested filter inside the backets

  • schema (String) (defaults to: nil)

    schema namespace of the attribute

Returns:

  • (Hash<Symbol, Object>)

100
101
102
103
104
# File 'lib/scim2/filter/arel_handler.rb', line 100

def on_nested_filter(attribute_path, filter, context:, schema: nil)
  @nested_filter_handler = nil
  arel = lookup_arel(attribute_path)
  recursively_handle_nested_filter(arel, *filter.first)
end

#on_not_filter(filter, context:) ⇒ Hash<Symbol, Object>

Handle NOT filters (e.g. not (color eq "red"))

Parameters:

  • filter (Hash<Symbol, Object>)

    the internal filter being NOT'ed

Returns:

  • (Hash<Symbol, Object>)

56
57
58
# File 'lib/scim2/filter/arel_handler.rb', line 56

def on_not_filter(filter, context:)
  filter.not
end

#recursively_handle_nested_filter(arel, op, condition) ⇒ Object (protected)


165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/scim2/filter/arel_handler.rb', line 165

def recursively_handle_nested_filter(arel, op, condition)
  case op
  when :not
    recursively_handle_nested_filter(arel, *condition.first).not
  when :and, :or
    condition.map do |c|
      recursively_handle_nested_filter(arel, *c.first)
    end.reduce(op)
  else
    path, value = condition.values_at(:path, :value)
    arel = arel.call(path, op, value) if arel.is_a?(Proc)
    arel = apply_arel_operation(arel, op, value) if arel && !arel.is_a?(Arel::Nodes::Node)
    arel || Arel::Nodes::False.new
  end
end