Class: Graphiti::Query

Inherits:
Object show all
Defined in:
lib/graphiti/query.rb

Defined Under Namespace

Classes: RemoteSideloadResource

Constant Summary collapse

LINKLESS_FORMATS =
[:json, :xml, "json", "xml"].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(resource, params, *positional, association_name: nil, nested_include: nil, parents: [], action: nil) ⇒ Query

Returns a new instance of Query.



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/graphiti/query.rb', line 9

def initialize(resource, params, *positional, association_name: nil, nested_include: nil, parents: [], action: nil)
  if positional.any?
    Graphiti::DEPRECATOR.warn("Passing Query.new trailing arguments positionally is deprecated. Use association_name:/nested_include:/parents:/action: keywords.")
    association_name ||= positional[0]
    nested_include ||= positional[1]
    parents = positional[2] if positional.length > 2
    action ||= positional[3]
  end

  @resource = resource
  @association_name = association_name
  @params = params
  @params = @params.permit! if @params.respond_to?(:permit!)
  @params = @params.to_h if @params.respond_to?(:to_h)
  @params = @params.deep_symbolize_keys
  @include_param = nested_include || @params[:include]
  @parents = parents
  @action = parse_action(action)
  @entity_map = Concurrent::Map.new if parents.empty?
  @association_owners = Concurrent::Map.new if parents.empty?
  @public_id_maps = Concurrent::Map.new if parents.empty?
end

Instance Attribute Details

#action ⇒ Object (readonly)

Returns the value of attribute action.



7
8
9
# File 'lib/graphiti/query.rb', line 7

def action
  @action
end

#association_name ⇒ Object (readonly)

Returns the value of attribute association_name.



7
8
9
# File 'lib/graphiti/query.rb', line 7

def association_name
  @association_name
end

#params ⇒ Object (readonly)

Returns the value of attribute params.



7
8
9
# File 'lib/graphiti/query.rb', line 7

def params
  @params
end

#resource ⇒ Object (readonly)

Returns the value of attribute resource.



7
8
9
# File 'lib/graphiti/query.rb', line 7

def resource
  @resource
end

Instance Method Details

#association? ⇒ Boolean

Returns:

  • (Boolean)


32
33
34
# File 'lib/graphiti/query.rb', line 32

def association?
  !!@association_name
end

#association_owners ⇒ Object



43
44
45
46
47
# File 'lib/graphiti/query.rb', line 43

def association_owners
  return root.association_owners unless root == self

  @association_owners
end

#cache_key ⇒ Object



318
319
320
# File 'lib/graphiti/query.rb', line 318

def cache_key
  "args-#{query_cache_key}"
end

#debug_requested? ⇒ Boolean

Returns:

  • (Boolean)


119
120
121
# File 'lib/graphiti/query.rb', line 119

def debug_requested?
  Types.flag(@params[:debug])
end

#entity_map ⇒ Object

Concurrent::Map because sideload scopes resolve on pool threads



37
38
39
40
41
# File 'lib/graphiti/query.rb', line 37

def entity_map
  return root.entity_map unless root == self

  @entity_map
end

#extra_fields ⇒ Object



214
215
216
# File 'lib/graphiti/query.rb', line 214

def extra_fields
  @extra_fields ||= parse_fieldset(@params[:extra_fields] || {})
end

#fields ⇒ Object



204
205
206
207
208
209
210
211
212
# File 'lib/graphiti/query.rb', line 204

def fields
  @fields ||= begin
    hash = parse_fieldset(@params[:fields] || {})
    hash.each_pair do |type, fields|
      hash[type] += extra_fields[type] if extra_fields[type]
    end
    hash
  end
end

#filters ⇒ Object



218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/graphiti/query.rb', line 218

def filters
  @filters ||= {}.tap do |hash|
    (@params[:filter] || {}).each_pair do |name, value|
      name = name.to_sym

      if legacy_nested?(name)
        value.keys.each do |key|
          filter_name = key.to_sym
          filter_value = value[key]

          if @resource.get_attr!(filter_name, :filterable, request: true)
            hash[filter_name] = filter_value
          end
        end
      elsif nested?(name)
        name = name.to_s.split(".").last.to_sym
        validate!(name, :filterable)
        hash[name] = value
      elsif top_level? && validate!(name, :filterable)
        hash[name] = value
      end
    end
  end
end

#hash ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/graphiti/query.rb', line 123

def hash
  @hash ||= {}.tap do |h|
    h[:filter] = filters
    h[:sort] = sorts
    h[:page] = pagination
    if association?
      resource_type = @resource.class.type
      h[:extra_fields] = {resource_type => extra_fields[resource_type]} if extra_fields.key?(resource_type)
    else
      h[:fields] = fields
      h[:extra_fields] = extra_fields
    end
    h[:stats] = stats
    h[:include] = sideload_hash
  end.reject { |_, value| value.empty? }
end

#include_hash ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# File 'lib/graphiti/query.rb', line 282

def include_hash
  @include_hash ||= begin
    requested = include_directive.to_hash

    allowlist = nil
    if @resource.context&.respond_to?(:sideload_allowlist)
      allowlist = @resource.context.sideload_allowlist
      allowlist = allowlist[@resource.current_action] if allowlist
    end

    scrubbed = allowlist ? Util::IncludeParams.scrub(requested, allowlist) : requested

    scrubbed.filter do |key, value|
      sideload = @resource.class.sideload(key)
      sideload.nil? || sideload.readable?
    end
  end
end

#includes_requested? ⇒ Boolean

Returns:

  • (Boolean)


82
83
84
# File 'lib/graphiti/query.rb', line 82

def includes_requested?
  @include_param.present?
end

Returns:

  • (Boolean)


86
87
88
89
90
# File 'lib/graphiti/query.rb', line 86

def links_requested?
  return @links_requested if defined?(@links_requested)

  @links_requested = Types.flag(@params[:links])
end

#page_links? ⇒ Boolean Also known as: pagination_links?

Returns:

  • (Boolean)


104
105
106
107
108
109
110
111
112
# File 'lib/graphiti/query.rb', line 104

def page_links?
  if action == :find
    false
  elsif @resource.page_links == :on_demand
    page_links_requested?
  else
    @resource.page_links
  end
end

Returns:

  • (Boolean)


114
115
116
117
# File 'lib/graphiti/query.rb', line 114

def page_links_requested?
  [@params[:page_links], @params[:pagination_links]]
    .any? { |value| Types.flag(value) }
end

#paginate? ⇒ Boolean

Returns:

  • (Boolean)


314
315
316
# File 'lib/graphiti/query.rb', line 314

def paginate?
  ![false, "false"].include?(@params[:paginate])
end

#pagination ⇒ Object



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/graphiti/query.rb', line 265

def pagination
  @pagination ||= {}.tap do |hash|
    (@params[:page] || {}).each_pair do |name, value|
      if legacy_nested?(name)
        value.each_pair do |k, v|
          hash[k.to_sym] = cast_page_param(k.to_sym, v)
        end
      elsif nested?(name)
        param_name = name.to_s.split(".").last.to_sym
        hash[param_name] = cast_page_param(param_name, value)
      elsif top_level? && Scoping::Paginate::PARAMS.include?(name.to_sym)
        hash[name.to_sym] = cast_page_param(name.to_sym, value)
      end
    end
  end
end

#parents ⇒ Object



196
197
198
# File 'lib/graphiti/query.rb', line 196

def parents
  @parents ||= []
end

#public_id_maps ⇒ Object



49
50
51
52
53
# File 'lib/graphiti/query.rb', line 49

def public_id_maps
  return root.public_id_maps unless root == self

  @public_id_maps
end

#render_link?(mode) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
# File 'lib/graphiti/query.rb', line 98

def render_link?(mode)
  return false if suppress_links?

  (mode == :on_demand) ? links_requested? : mode == true
end

#repeated_resource_classes ⇒ Object

Deduplication exists for a row two include paths both resolve, so a resource class sitting at one node has nothing to collide with.



57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/graphiti/query.rb', line 57

def repeated_resource_classes
  return root.repeated_resource_classes unless root == self

  @repeated_resource_classes ||= begin
    counts = Hash.new(0)
    walk = [self]
    while (query = walk.pop)
      counts[query.resource.class] += 1
      query.sideloads.each_pair do |name, sideload_query|
        walk << sideload_query
        sideload = query.resource.class.sideload(name)
        next unless sideload.respond_to?(:children)

        # A polymorphic child resolves under a class no node names.
        sideload.children.each_value { |child| counts[child.resource.class] += 2 }
      end
    end
    counts.select { |_, count| count > 1 }.keys.to_set
  end
end

#resource_for_sideload(sideload) ⇒ Object



159
160
161
162
163
164
165
# File 'lib/graphiti/query.rb', line 159

def resource_for_sideload(sideload)
  if @resource.remote?
    RemoteSideloadResource.new
  else
    sideload.resource
  end
end

#root ⇒ Object



200
201
202
# File 'lib/graphiti/query.rb', line 200

def root
  parents.first || self
end

#sideload_hash ⇒ Object



146
147
148
149
150
151
152
# File 'lib/graphiti/query.rb', line 146

def sideload_hash
  @sideload_hash = {}.tap do |hash|
    sideloads.each_pair do |key, value|
      hash[key] = sideloads[key].hash
    end
  end
end

#sideloads ⇒ Object



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/graphiti/query.rb', line 167

def sideloads
  @sideloads ||= {}.tap do |hash|
    include_hash.each_pair do |key, sub_hash|
      sideload = @resource.class.sideload(key)

      if sideload || @resource.remote?
        sl_resource = resource_for_sideload(sideload)
        query_parents = parents + [self]
        sub_hash = sub_hash[:include] if sub_hash.key?(:include)

        # NB: To handle on__<type>--<name>
        # A) relationship_name == :positions
        # B) key == on__employees.positions
        # This way A) ensures sideloads are resolved
        # And B) ensures nested filters, sorts etc still work
        relationship_name = sideload ? sideload.name : key
        hash[relationship_name] = Query.new sl_resource,
          @params,
          association_name: key,
          nested_include: sub_hash,
          parents: query_parents,
          action: :all
      else
        handle_missing_sideload(key)
      end
    end
  end
end

#sorts ⇒ Object



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/graphiti/query.rb', line 243

def sorts
  @sorts ||= begin
    return @params[:sort] if @params[:sort].is_a?(Array)
    return [] if @params[:sort].nil?

    [].tap do |arr|
      sort_hashes do |key, value, type|
        if legacy_nested?(type)
          unless @resource.remote?
            @resource.get_attr!(key, :sortable, request: true)
          end
          arr << {key => value}
        elsif !type && top_level? && validate!(key, :sortable)
          arr << {key => value}
        elsif nested?("#{type}.#{key}")
          arr << {key => value}
        end
      end
    end
  end
end

#stats ⇒ Object



301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/graphiti/query.rb', line 301

def stats
  @stats ||= {}.tap do |hash|
    (@params[:stats] || {}).each_pair do |k, v|
      if legacy_nested?(k)
        raise NotImplementedError.new("Association statistics are not currently supported")
      elsif top_level?
        v = v.split(",") if v.is_a?(String)
        hash[k.to_sym] = Array(v).flatten.map(&:to_sym)
      end
    end
  end
end

#suppress_links? ⇒ Boolean

Returns:

  • (Boolean)


92
93
94
95
96
# File 'lib/graphiti/query.rb', line 92

def suppress_links?
  return @suppress_links if defined?(@suppress_links)

  @suppress_links = LINKLESS_FORMATS.include?(params[:format])
end

#top_level? ⇒ Boolean

Returns:

  • (Boolean)


78
79
80
# File 'lib/graphiti/query.rb', line 78

def top_level?
  !association?
end

#zero_results? ⇒ Boolean

Returns:

  • (Boolean)


140
141
142
143
144
# File 'lib/graphiti/query.rb', line 140

def zero_results?
  !@params[:page].nil? &&
    !@params[:page][:size].nil? &&
    @params[:page][:size].to_i == 0
end