Module: Quail::Resource::MutationBuilder

Defined in:
lib/quail/resource/mutation_builder.rb,
lib/quail/resource/mutation_builder/context.rb,
lib/quail/resource/mutation_builder/resolvers.rb

Overview

Builds create, update, and delete GraphQL mutations for a resource.

Defined Under Namespace

Modules: Resolvers Classes: MutationContext

Class Method Summary collapse

Class Method Details

.add_result_fields(klass, underscore_name, type_class) ⇒ Object



71
72
73
74
# File 'lib/quail/resource/mutation_builder.rb', line 71

def self.add_result_fields(klass, underscore_name, type_class)
  klass.field underscore_name.to_sym, type_class, null: true
  klass.field :errors, [GraphQL::Types::String], null: false
end

.add_writable_arguments(klass, model, writable, required:) ⇒ Object



62
63
64
65
66
67
68
69
# File 'lib/quail/resource/mutation_builder.rb', line 62

def self.add_writable_arguments(klass, model, writable, required:)
  writable.each do |attr|
    col = model.columns_hash[attr.to_s]
    next unless col

    klass.argument attr, TypeMap.graphql_types(col), required: required ? !TypeMap.nullable?(col) : false
  end
end

.build_create(ctx) ⇒ Object



96
97
98
99
100
101
102
103
104
105
# File 'lib/quail/resource/mutation_builder.rb', line 96

def self.build_create(ctx)
  model = ctx.model
  name = ctx.underscore_name
  subs = ctx.subscriptions
  klass = new_mutation_class(ctx, "Create", model)
  add_writable_arguments(klass, model, ctx.writable, required: true)
  add_result_fields(klass, name, ctx.type_class)
  klass.define_method(:resolve) { |**attrs| Resolvers.create(model, name, subs, context, attrs) }
  klass
end

.build_delete(ctx) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
# File 'lib/quail/resource/mutation_builder.rb', line 119

def self.build_delete(ctx)
  model = ctx.model
  name = ctx.underscore_name
  subs = ctx.subscriptions
  klass = new_mutation_class(ctx, "Delete", model)
  klass.argument :id, GraphQL::Types::ID, required: true
  klass.field :success, GraphQL::Types::Boolean, null: false
  klass.field :errors, [GraphQL::Types::String], null: false
  klass.define_method(:resolve) { |id:| Resolvers.delete(model, name, subs, context, id) }
  klass
end

.build_mutation(action, ctx) ⇒ Object



24
25
26
27
28
29
30
# File 'lib/quail/resource/mutation_builder.rb', line 24

def self.build_mutation(action, ctx)
  case action
  when :create then build_create(ctx)
  when :update then build_update(ctx)
  when :delete then build_delete(ctx)
  end
end

.build_update(ctx) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/quail/resource/mutation_builder.rb', line 107

def self.build_update(ctx)
  model, name, subs = ctx.model, ctx.underscore_name, ctx.subscriptions
  klass = new_mutation_class(ctx, "Update", model)
  klass.argument :id, GraphQL::Types::ID, required: true
  add_writable_arguments(klass, model, ctx.writable, required: false)
  add_result_fields(klass, name, ctx.type_class)
  klass.define_method(:resolve) do |id:, **attrs|
    Resolvers.update(model, name, subs, context, { id: id, attrs: attrs })
  end
  klass
end

.call(resource_class) ⇒ Object

rubocop:disable Metrics/ModuleLength



10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/quail/resource/mutation_builder.rb', line 10

def self.call(resource_class)
  skipped = resource_class.skipped_mutations
  overrides = resource_class.resolved_mutation_overrides
  ctx = MutationContext.new(resource_class)
  mutations = {}

  %i[create update delete].each do |action|
    next if skipped.include?(action)

    mutations[action] = overrides[action] || build_mutation(action, ctx)
  end
  mutations
end

.capture_delete_snapshot(subs, record) ⇒ Object



84
85
86
87
88
# File 'lib/quail/resource/mutation_builder.rb', line 84

def self.capture_delete_snapshot(subs, record)
  return nil unless subs[:delete]

  { attributes: record.attributes, scope_args: resolve_scope(subs[:delete][:scope], record) }
end

.default_writable(model, resource_class = nil) ⇒ Object



32
33
34
35
36
# File 'lib/quail/resource/mutation_builder.rb', line 32

def self.default_writable(model, resource_class = nil)
  excluded = %i[id created_at updated_at]
  excluded += polymorphic_columns(resource_class) if resource_class
  model.column_names.map(&:to_sym).reject { |c| excluded.include?(c) }
end

.new_mutation_class(ctx, prefix, model) ⇒ Object



131
132
133
134
135
136
# File 'lib/quail/resource/mutation_builder.rb', line 131

def self.new_mutation_class(ctx, prefix, model)
  Class.new(ctx.base) do
    graphql_name "#{prefix}#{model.name}"
    description "#{prefix}s a #{model.name}"
  end
end

.polymorphic_columns(resource_class) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/quail/resource/mutation_builder.rb', line 38

def self.polymorphic_columns(resource_class)
  return [] unless resource_class

  resource_class.association_definitions
                .select { |_, config| config[:polymorphic] }
                .flat_map do |name, _|
    [
      :"#{name}_type", :"#{name}_id"
    ]
  end
end

.resolve_scope(scope_config, record) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/quail/resource/mutation_builder.rb', line 50

def self.resolve_scope(scope_config, record)
  return {} unless scope_config

  case scope_config
  when Symbol then { scope_config => record.public_send(scope_config) }
  when Hash
    key, value_proc = scope_config.first
    { key.to_sym => value_proc.call(record) }
  else {}
  end
end

.trigger_delete_event(gql_context, name, snapshot) ⇒ Object



90
91
92
93
94
# File 'lib/quail/resource/mutation_builder.rb', line 90

def self.trigger_delete_event(gql_context, name, snapshot)
  return unless snapshot

  gql_context.schema.subscriptions&.trigger(:"#{name}_deleted", snapshot[:scope_args], snapshot[:attributes])
end

.trigger_subscription(gql_context, subs, event, underscore_name, record) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/quail/resource/mutation_builder.rb', line 76

def self.trigger_subscription(gql_context, subs, event, underscore_name, record)
  sub_config = subs[event]
  return unless sub_config

  scope_args = resolve_scope(sub_config[:scope], record)
  gql_context.schema.subscriptions&.trigger(:"#{underscore_name}_#{event}d", scope_args, record)
end