Module: Graphiform::Helpers

Defined in:
lib/graphiform/helpers.rb

Constant Summary collapse

NAME_NORMALIZE_CACHE =

— Name normalization & per-class registries ————————-

Replaces the O(n) ‘arguments.keys.any? { equal_graphql_names?(…) }` scan (and its repeated string allocations) with an O(1) Set lookup.

{}
NAME_NORMALIZE_MUTEX =
Mutex.new

Class Method Summary collapse

Class Method Details

.add_unless_exists(klass, name) ⇒ Object

Guard helper: yield (which should add the field/argument) only if the name isn’t already present. Returns true when the block ran.



59
60
61
62
63
64
65
66
67
# File 'lib/graphiform/helpers.rb', line 59

def self.add_unless_exists(klass, name)
  normalized = normalize_graphql_name(name)
  set = tracked_names(klass)
  return false if set.include?(normalized)

  yield
  set << normalized
  true
end

.association_arguments_valid?(association_def, method) ⇒ Boolean

Returns:

  • (Boolean)


103
104
105
106
107
108
109
110
111
112
# File 'lib/graphiform/helpers.rb', line 103

def self.association_arguments_valid?(association_def, method)
  return false unless association_def.present?
  return false unless association_def.klass.respond_to?(method)

  target = association_def.klass.send(method)
  return false unless target.respond_to?(:arguments)

  own_args = target.instance_variable_get(:@own_arguments) || {}
  !own_args.empty?
end

.dataloader_support?(dataloader, association_def) ⇒ Boolean

Returns:

  • (Boolean)


114
115
116
117
118
119
# File 'lib/graphiform/helpers.rb', line 114

def self.dataloader_support?(dataloader, association_def)
  association_def.present? &&
    !association_def.polymorphic? &&
    !association_def.inverse_of&.polymorphic? &&
    !dataloader.is_a?(GraphQL::Dataloader::NullDataloader)
end

.full_const_name(name) ⇒ Object



96
97
98
99
100
101
# File 'lib/graphiform/helpers.rb', line 96

def self.full_const_name(name)
  name = "Object#{name}" if name.starts_with?('::')
  name = "Object::#{name}" unless name.starts_with?('Object::')

  name
end

.get_const_or_create(const, mod = Object) ⇒ Object



88
89
90
91
92
93
94
# File 'lib/graphiform/helpers.rb', line 88

def self.get_const_or_create(const, mod = Object)
  return mod.const_get(const) if mod.const_defined?(const, false)
  
  val = yield
  mod.const_set(const, val)
  val
end

.graphql_type(active_record_type) ⇒ Object




70
71
72
73
74
75
# File 'lib/graphiform/helpers.rb', line 70

def self.graphql_type(active_record_type)
  is_array = active_record_type.is_a? Array
  active_record_type = is_array ? active_record_type[0] : active_record_type
  graphql_type = graphql_type_single(active_record_type)
  is_array ? [graphql_type] : graphql_type
end

.graphql_type_single(active_record_type) ⇒ Object



77
78
79
80
81
# File 'lib/graphiform/helpers.rb', line 77

def self.graphql_type_single(active_record_type)
  return active_record_type unless active_record_type.respond_to?(:to_sym)

  Graphiform.configuration[:scalar_mappings][active_record_type.to_sym] || active_record_type
end

.loggerObject



7
8
9
10
11
12
# File 'lib/graphiform/helpers.rb', line 7

def self.logger
  return Rails.logger if Rails.logger.present?

  @logger ||= Logger.new($stdout)
  @logger
end

.normalize_graphql_name(name) ⇒ Object

Canonicalize a name the same way graphql-ruby presents it externally, so ‘:my_field`, `“my_field”`, `“myField”`, `“MyField”` all collide.



24
25
26
27
28
29
30
31
32
# File 'lib/graphiform/helpers.rb', line 24

def self.normalize_graphql_name(name)
  key = name.is_a?(Symbol) ? name : name.to_s
  cached = NAME_NORMALIZE_CACHE[key]
  return cached if cached

  NAME_NORMALIZE_MUTEX.synchronize do
    NAME_NORMALIZE_CACHE[key] ||= key.to_s.camelize(:lower).freeze
  end
end

.resolver?(val) ⇒ Boolean

Returns:

  • (Boolean)


83
84
85
86
# File 'lib/graphiform/helpers.rb', line 83

def self.resolver?(val)
  val.respond_to?(:ancestors) &&
    val.ancestors.include?(GraphQL::Schema::Resolver)
end

.tracked_names(klass) ⇒ Object

Fetch (and lazily seed) the registered-names Set for a generated GraphQL class. Seeding from existing ‘arguments` / `fields` makes this safe even when classes are pre-populated (e.g. `OR`/`AND` on filters, or manual user-defined args).



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/graphiform/helpers.rb', line 38

def self.tracked_names(klass)
  set = klass.instance_variable_get(:@graphiform_names)
  return set if set

  set = Set.new

  if klass.respond_to?(:own_arguments)
    own_args = klass.instance_variable_get(:@own_arguments) || {}
    set.merge(own_args.each_key.map { |k| normalize_graphql_name(k) })
  end

  if klass.respond_to?(:fields)
    own_fields = klass.instance_variable_get(:@own_fields) || {}
    set.merge(own_fields.each_key.map { |k| normalize_graphql_name(k) })
  end

  klass.instance_variable_set(:@graphiform_names, set)
end