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
CONST_LOCKS =
{}
CONST_LOCKS_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.



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/graphiform/helpers.rb', line 60

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

  mutex = (klass.instance_variable_get(:@graphiform_names_mutex) ||
    klass.instance_variable_set(:@graphiform_names_mutex, Monitor.new))
  mutex.synchronize do
    return false if set.include?(normalized)

    yield
    set << normalized
  end
  true
end

.association_arguments_valid?(association_def, method) ⇒ Boolean

Returns:

  • (Boolean)


120
121
122
123
124
125
126
127
128
129
# File 'lib/graphiform/helpers.rb', line 120

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)


131
132
133
134
135
136
# File 'lib/graphiform/helpers.rb', line 131

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



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

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



98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/graphiform/helpers.rb', line 98

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

  key = [mod, const.to_s]
  lock = CONST_LOCKS_MUTEX.synchronize { CONST_LOCKS[key] ||= Monitor.new }

  lock.synchronize do
    return mod.const_get(const) if mod.const_defined?(const, false)

    val = yield
    mod.const_set(const, val)
    val
  end
end

.graphql_type(active_record_type) ⇒ Object




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

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



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

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



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

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.



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

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)


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

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).



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

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