Class: GraphQL::Node

Inherits:
Object
  • Object
show all
Defined in:
lib/graphql/node.rb

Overview

Node is the base class for your GraphQL nodes. It’s essentially a delegator that only delegates methods you whitelist with Node.field. To use it:

Examples:

Expose a class in your app

class PostNode < GraphQL::Node
  exposes('Post')

  cursor(:id)

  field.number(:id)
  field.string(:title)
  field.string(:content)
  field.connection(:comments)
end

Expose a data type

class DateType < GraphQL::Node
  exposes "Date"
  type :date
  call :minus_days, -> (prev_value, minus_days) { prev_value - minus_days.to_i }
  field.number(:year)
  field.number(:month)
end

# now you could use it
class PostNode
  field.date(:published_at)
end

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target = nil, fields:, query:, calls: []) ⇒ Node

Returns a new instance of Node.



45
46
47
48
49
50
51
# File 'lib/graphql/node.rb', line 45

def initialize(target=nil, fields:, query:, calls: [])
  @query = query
  @calls = calls
  @syntax_fields = fields
  @original_target = target
  @target = apply_calls(target)
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object

If the target responds to ‘method_name`, send it to target.



54
55
56
57
58
59
60
# File 'lib/graphql/node.rb', line 54

def method_missing(method_name, *args, &block)
  if target.respond_to?(method_name)
    target.public_send(method_name, *args, &block)
  else
    super
  end
end

Instance Attribute Details

#original_targetObject (readonly)

The object wrapped by this ‘Node`, before calls are applied



37
38
39
# File 'lib/graphql/node.rb', line 37

def original_target
  @original_target
end

#queryObject (readonly)

The query to which this ‘Node` belongs. Used for accessing its Query#context.



43
44
45
# File 'lib/graphql/node.rb', line 43

def query
  @query
end

#syntax_fieldsObject (readonly)

Fields parsed from the query string



41
42
43
# File 'lib/graphql/node.rb', line 41

def syntax_fields
  @syntax_fields
end

#targetObject (readonly)

The object wrapped by this ‘Node`, after calls are applied



39
40
41
# File 'lib/graphql/node.rb', line 39

def target
  @target
end

Class Method Details

.all_fieldsObject

All accessible fields on this node (including those defined in parent classes)



165
166
167
168
169
# File 'lib/graphql/node.rb', line 165

def all_fields
  superclass.all_fields.merge(own_fields)
rescue NoMethodError
  own_fields
end

.call(name, lambda) ⇒ Object

Define a call that can be made on nodes of this type. The ‘lambda` receives arguments:

  • 1: ‘previous_value` – the value of this node

  • *: arguments passed in the query (as strings)

Examples:

# upcase a string field:
call :upcase, -> (prev_value) { prev_value.upcase }
# tests a number field:
call :greater_than, -> (prev_value, test_value) { prev_value > test_value.to_f }
# (`test_value` is passed in as a string)

Parameters:

  • name (String)

    the identifier for this call

  • operation (lambda)

    the transformation this call makes



223
224
225
# File 'lib/graphql/node.rb', line 223

def call(name, lambda)
  own_calls[name.to_s] = GraphQL::Call.new(name: name.to_s, lambda: lambda)
end

.callsObject



203
204
205
206
207
# File 'lib/graphql/node.rb', line 203

def calls
  superclass.calls.merge(own_calls)
rescue NoMethodError
  {}
end

.cursor(field_name) ⇒ Object

Declares what field will be used as the cursor for this node.

Parameters:

  • field_name (String)

    name of the field to be used as the cursor



157
158
159
160
161
162
# File 'lib/graphql/node.rb', line 157

def cursor(field_name)
  define_method "cursor" do
    field_mapping = self.class.all_fields[field_name.to_s]
    public_send(field_mapping.name).to_s
  end
end

.default_schema_nameObject



151
152
153
# File 'lib/graphql/node.rb', line 151

def default_schema_name
  name.split("::").last.sub(/(Node|Type)$/, '').underscore
end

.desc(describe) ⇒ Object

Provide a description for this node which will be accessible from SCHEMA

Parameters:

  • describe (String)


130
131
132
# File 'lib/graphql/node.rb', line 130

def desc(describe)
  @description = describe
end

.descriptionObject

The description of this node



135
136
137
# File 'lib/graphql/node.rb', line 135

def description
  @description || raise("#{name}.description isn't defined")
end

.exposes(*exposes_class_names) ⇒ Object

Parameters:

  • class_name (String)

    name of the class this node will wrap.



118
119
120
121
# File 'lib/graphql/node.rb', line 118

def exposes(*exposes_class_names)
  @exposes_class_names = exposes_class_names
  GraphQL::SCHEMA.add_type(self)
end

.exposes_class_namesObject

The names of the classes wrapped by this node



124
125
126
# File 'lib/graphql/node.rb', line 124

def exposes_class_names
  @exposes_class_names || []
end

.fieldGraphQL::FieldDefiner

Returns definer.

Returns:



177
178
179
# File 'lib/graphql/node.rb', line 177

def field
  @field_definer ||= GraphQL::FieldDefiner.new(self)
end

.own_callsObject



227
228
229
# File 'lib/graphql/node.rb', line 227

def own_calls
  @own_calls ||= {}
end

.own_fieldsObject

Fields defined by this class, but not its parents



172
173
174
# File 'lib/graphql/node.rb', line 172

def own_fields
  @own_fields ||= {}
end

.remove_field(field_name) ⇒ Object

Un-define field with name ‘field_name`

Parameters:

  • field_name (String)


183
184
185
# File 'lib/graphql/node.rb', line 183

def remove_field(field_name)
  own_fields.delete(field_name.to_s)
end

.respond_to_field?(field_name) ⇒ Boolean

Can the node handle a field with this name?

Returns:

  • (Boolean)


188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/graphql/node.rb', line 188

def respond_to_field?(field_name)
  if all_fields[field_name.to_s].blank?
    false
  elsif method_defined?(field_name)
    true
  elsif exposes_class_names.any? do |exposes_class_name|
      exposes_class = Object.const_get(exposes_class_name)
      exposes_class.method_defined?(field_name) || exposes_class.respond_to?(field_name)
    end
    true
  else
    false
  end
end

.schema_nameObject

Returns the name of this node used by SCHEMA



147
148
149
# File 'lib/graphql/node.rb', line 147

def schema_name
  @type_name || default_schema_name
end

.type(type_name) ⇒ Object

Declares an alternative name to use in SCHEMA

Parameters:

  • type_name (String)


141
142
143
144
# File 'lib/graphql/node.rb', line 141

def type(type_name)
  @type_name = type_name.to_s
  GraphQL::SCHEMA.add_type(self)
end

Instance Method Details

#__type__Object



94
95
96
# File 'lib/graphql/node.rb', line 94

def __type__
  self.class
end

#apply_calls(value) ⇒ Object



98
99
100
# File 'lib/graphql/node.rb', line 98

def apply_calls(value)
  finished_value(value)
end

#as_resultObject

Looks up #syntax_fields against this node and returns the results



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
# File 'lib/graphql/node.rb', line 63

def as_result
  @as_result ||= begin
    json = {}
    syntax_fields.each do |syntax_field|
      key_name = syntax_field.alias_name || syntax_field.identifier
      if key_name == 'node'
        clone_node = self.class.new(target, fields: syntax_field.fields, query: query, calls: syntax_field.calls)
        json[key_name] = clone_node.as_result
      elsif key_name == 'cursor'
        json[key_name] = cursor
      elsif key_name[0] == "$"
        fragment = query.fragments[key_name]
        # execute the fragment and merge it into this result
        clone_node = self.class.new(target, fields: fragment.fields, query: query, calls: @calls)
        json.merge!(clone_node.as_result)
      else
        field = get_field(syntax_field)
        new_target = public_send(field.name)
        new_node = field.type_class.new(new_target, fields: syntax_field.fields, query: query, calls: syntax_field.calls)
        json[key_name] = new_node.as_result
      end
    end
    json
  end
end

#contextObject

The object passed to Query#initialize as ‘context`.



90
91
92
# File 'lib/graphql/node.rb', line 90

def context
  query.context
end

#finished_value(raw_value) ⇒ Object



102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/graphql/node.rb', line 102

def finished_value(raw_value)
  @finished_value ||= begin
    val = raw_value
    @calls.each do |call|
      registered_call = self.class.calls[call.identifier]
      if registered_call.nil?
        raise "Call not found: #{self.class.name}##{call.identifier}"
      end
      val = registered_call.lambda.call(val, *call.arguments)
    end
    val
  end
end