graphiti_gql 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2a19fd68dc698b8a7284fe80686da9f50ee137365508241cd374e32aa6c505aa
4
- data.tar.gz: d509f137452b90e94986c46267f8728160021de305e599ba47512af9366fe109
3
+ metadata.gz: 5d9dda0537212e47943587af28d28226e8ee6a4ad0d91b65f5074003ab370f17
4
+ data.tar.gz: 222fb453e4f73c6e4d0de606bae88aea041b23c48ba28355f575025dff7e3c92
5
5
  SHA512:
6
- metadata.gz: 22f8643e779d272d806d53179e5f83e6ca6157fc8c261e03ea3232fdf685faa35ad50c70bd3f5f0f445b8bd4578e616b9a538c9205e330a301a30f370b2c612a
7
- data.tar.gz: ec6e08b56162dc2bb077bfd3cfc5fb5dcf8edcdc3680d940347eb33949f8530967e6d828e0b914f0920a406bad4807d83c643981f9ab0136dd7a3e9e422e068a
6
+ metadata.gz: 8e5a6a8d98b058fb8237750190e6f051712d2f90f5bf49be615f68a594598589afb1811aec0f0fbe79bbcefd457d27a6cf78e84e8e90d539168da9ffd13b9a64
7
+ data.tar.gz: 6515ba7accff3edb9490028bc3545f649d2a33877f5d6eb77a32a9183e365241be56b3ba6b99b1c27095390c110ee3c3f7d6743844049dab6576461b22a64c7c
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- graphiti_gql (0.1.0)
4
+ graphiti_gql (0.2.2)
5
5
  graphiti (~> 1.3.9)
6
6
  graphql (~> 2.0)
7
7
  graphql-batch (~> 0.5)
@@ -47,7 +47,7 @@ GEM
47
47
  jsonapi-serializable (~> 0.3.0)
48
48
  graphiti_errors (1.1.2)
49
49
  jsonapi-serializable (~> 0.1)
50
- graphql (2.0.7)
50
+ graphql (2.0.11)
51
51
  graphql-batch (0.5.1)
52
52
  graphql (>= 1.10, < 3)
53
53
  promise.rb (~> 0.7.2)
@@ -0,0 +1,210 @@
1
+ module GraphitiGql
2
+ module ActiveResource
3
+ extend ActiveSupport::Concern
4
+
5
+ class Node < OpenStruct
6
+ def initialize(resource, hash)
7
+ @resource = resource
8
+ hash.each_pair do |key, value|
9
+ if value.is_a?(Hash)
10
+ if (sideload = resource.sideload(key))
11
+ if value.key?(:edges)
12
+ hash[key] = value[:edges].map { |v| Node.new(sideload.resource.class, v[:node]) }
13
+ else
14
+ hash[key] = Node.new(sideload.resource.class, value)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ super(hash)
20
+ end
21
+
22
+ def decoded_id
23
+ Base64.decode64(self.id)
24
+ end
25
+
26
+ def int_id
27
+ decoded_id.to_i
28
+ end
29
+ end
30
+
31
+ class Proxy
32
+ def initialize(resource, params, ctx)
33
+ @resource = resource
34
+ @ctx = ctx
35
+ @params = params.deep_transform_keys { |key| key.to_s.camelize(:lower).to_sym }
36
+ (@params[:sort] || []).each do |sort|
37
+ sort[:att] = sort[:att].to_s.camelize(:lower)
38
+ sort[:dir] = sort[:dir].to_s
39
+ end
40
+ end
41
+
42
+ def to_h(symbolize_keys: true)
43
+ result = GraphitiGql.run(query, @params, @ctx)
44
+ result = result.deep_symbolize_keys if symbolize_keys
45
+ @response = result
46
+ result
47
+ end
48
+
49
+ def nodes
50
+ return [] unless data
51
+ nodes = edges.map { |e| underscore(e[:node]) }
52
+ nodes.map { |n| Node.new(@resource, n) }
53
+ end
54
+ alias :to_a :nodes
55
+
56
+ def response
57
+ @response ||= to_h
58
+ end
59
+
60
+ def data
61
+ if response.key?(:data)
62
+ response[:data]
63
+ else
64
+ raise "Tried to access 'data', but these errors were returned instead: #{error_messages.join(". ")}."
65
+ end
66
+ end
67
+
68
+ def errors
69
+ response[:errors]
70
+ end
71
+
72
+ def error_messages
73
+ response[:errors].map { |e| e[:message] }
74
+ end
75
+
76
+ def edges
77
+ data[data.keys.first][:edges]
78
+ end
79
+
80
+ def stats
81
+ underscore(data[data.keys.first][:stats])
82
+ end
83
+
84
+ def page_info
85
+ underscore(data[data.keys.first][:pageInfo])
86
+ end
87
+
88
+ def query
89
+ name = Schema.registry.key_for(@resource)
90
+ filter_bang = "!" if @resource.filters.values.any? { |f| f[:required] }
91
+ sortvar = "$sort: [#{name}Sort!]," if @resource.sorts.any?
92
+
93
+ if !(fields = @params[:fields])
94
+ fields = []
95
+ @resource.attributes.each_pair do |name, config|
96
+ (fields << name) if config[:readable]
97
+ end
98
+ end
99
+
100
+ q = %|
101
+ query #{name} (
102
+ $filter: #{name}Filter#{filter_bang},
103
+ #{sortvar}
104
+ $first: Int,
105
+ $last: Int,
106
+ $before: String,
107
+ $after: String,
108
+ ) {
109
+ #{@resource.graphql_entrypoint} (
110
+ filter: $filter,
111
+ #{ 'sort: $sort,' if sortvar }
112
+ first: $first,
113
+ last: $last,
114
+ before: $before,
115
+ after: $after,
116
+ ) {
117
+ edges {
118
+ node {|
119
+
120
+ fields.each do |name|
121
+ q << %|
122
+ #{name.to_s.camelize(:lower)}|
123
+ end
124
+
125
+ if @params[:include]
126
+ includes = Array(@params[:include])
127
+ # NB HASH (?)
128
+ includes.each do |inc|
129
+ sideload = @resource.sideload(inc.to_sym)
130
+ to_one = [:belongs_to, :has_one, :polymorphic_belongs_to].include?(sideload.type)
131
+ indent = " " if !to_one
132
+ q << %|
133
+ #{inc.to_s.camelize(:lower)} {|
134
+ if !to_one
135
+ q << %|
136
+ edges {
137
+ node {|
138
+ end
139
+
140
+ r = @resource.sideload(inc.to_sym).resource
141
+ r.attributes.each_pair do |name, config|
142
+ next unless config[:readable]
143
+ q << %|
144
+ #{indent}#{name.to_s.camelize(:lower)}|
145
+ end
146
+
147
+ if to_one
148
+ q << %|
149
+ }|
150
+ else
151
+ q << %|
152
+ }
153
+ }
154
+ }|
155
+ end
156
+ end
157
+ end
158
+
159
+ q << %|
160
+ }
161
+ }
162
+ pageInfo {
163
+ startCursor
164
+ endCursor
165
+ hasNextPage
166
+ hasPreviousPage
167
+ }|
168
+
169
+ if @params[:stats]
170
+ q << %|
171
+ stats {|
172
+ @params[:stats].each_pair do |name, calculations|
173
+ q << %|
174
+ #{name.to_s.camelize(:lower)} {|
175
+ Array(calculations).each do |calc|
176
+ q << %|
177
+ #{calc.to_s.camelize(:lower)}|
178
+ end
179
+
180
+ q << %|
181
+ }|
182
+ end
183
+ q << %|
184
+ }|
185
+ end
186
+
187
+ q << %|
188
+ }
189
+ }
190
+ |
191
+
192
+ q
193
+ end
194
+
195
+ private
196
+
197
+ def underscore(hash)
198
+ hash.deep_transform_keys { |k| k.to_s.underscore.to_sym }
199
+ end
200
+ end
201
+
202
+ class_methods do
203
+ def gql(params = {}, ctx = {})
204
+ Proxy.new(self, params, ctx)
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ Graphiti::Resource.send(:include, GraphitiGql::ActiveResource)
@@ -9,9 +9,18 @@ module GraphitiGql
9
9
  GraphitiGql.schema!
10
10
  end
11
11
 
12
+ module ControllerContext
13
+ def graphql_context
14
+ ctx = { controller: self }
15
+ ctx[:current_user] = current_user if respond_to?(:current_user)
16
+ ctx
17
+ end
18
+ end
19
+
12
20
  initializer "graphiti_gql.define_controller" do
13
21
  require "#{Rails.root}/app/controllers/application_controller"
14
22
  app_controller = GraphitiGql.config.application_controller || ::ApplicationController
23
+ app_controller.send(:include, ControllerContext)
15
24
 
16
25
  # rubocop:disable Lint/ConstantDefinitionInBlock(Standard)
17
26
  class GraphitiGql::ExecutionController < app_controller
@@ -20,9 +29,15 @@ module GraphitiGql
20
29
  variables = params[:variables] || {}
21
30
  result = GraphitiGql.run params[:query],
22
31
  params[:variables],
23
- { current_user: current_user }
32
+ graphql_context
24
33
  render json: result
25
34
  end
35
+
36
+ private
37
+
38
+ def default_context
39
+ defined?(:current_user)
40
+ end
26
41
  end
27
42
  end
28
43
  end
@@ -11,6 +11,27 @@ module GraphitiGql
11
11
  end
12
12
  end
13
13
 
14
+ def selections
15
+ return @selections if @selections
16
+ lookahead = context[:current_arguments]
17
+ .keyword_arguments[:lookahead]
18
+ nodes = lookahead.selection(:nodes)
19
+ if !nodes.selected?
20
+ nodes = lookahead
21
+ .selection(:edges)
22
+ .selection(:node)
23
+ end
24
+
25
+ if !nodes.selected?
26
+ nodes = lookahead
27
+ end
28
+
29
+ @selections = nodes
30
+ .selections
31
+ .map(&:name).map { |name| name.to_s.underscore.to_sym }
32
+ @selections
33
+ end
34
+
14
35
  class_methods do
15
36
  def attribute(*args)
16
37
  super(*args).tap do
@@ -121,6 +142,24 @@ module GraphitiGql
121
142
  end
122
143
  Graphiti::Scoping::Paginate.send(:prepend, PaginateExtras)
123
144
 
145
+ module ManyToManyExtras
146
+ extend ActiveSupport::Concern
147
+
148
+ class_methods do
149
+ attr_accessor :edge_resource
150
+
151
+ def attribute(*args, &blk)
152
+ @edge_resource = Class.new(Graphiti::Resource) do
153
+ def self.abstract_class?
154
+ true
155
+ end
156
+ end
157
+ @edge_resource.attribute(*args, &blk)
158
+ end
159
+ end
160
+ end
161
+ Graphiti::Sideload::ManyToMany.send(:include, ManyToManyExtras)
162
+
124
163
  module StatsExtras
125
164
  def calculate_stat(name, function)
126
165
  config = @resource.all_attributes[name] || {}
@@ -141,7 +180,11 @@ module GraphitiGql
141
180
  end
142
181
 
143
182
  _in = definition.constructor do |input|
144
- Time.zone.parse(input)
183
+ if input.is_a?(ActiveSupport::TimeWithZone)
184
+ input = input.utc.round(10).iso8601(6)
185
+ else
186
+ Time.zone.parse(input)
187
+ end
145
188
  end
146
189
 
147
190
  # Register it with Graphiti
@@ -166,7 +209,9 @@ module GraphitiGql
166
209
  alias_method :filter_precise_datetime_not_eq, :filter_not_eq
167
210
  end
168
211
  end
169
- Graphiti::Adapters::ActiveRecord.send(:include, ActiveRecordAdapterExtras)
212
+ if defined?(Graphiti::Adapters::ActiveRecord)
213
+ Graphiti::Adapters::ActiveRecord.send(:include, ActiveRecordAdapterExtras)
214
+ end
170
215
 
171
216
  Graphiti::Adapters::Abstract.class_eval do
172
217
  class << self
@@ -215,4 +260,17 @@ module GraphitiGql
215
260
  end
216
261
  end
217
262
  Graphiti::Scope.send(:prepend, ScopeExtras)
263
+
264
+ module ActiveRecordManyToManyExtras
265
+ # flipping .includes to .joins
266
+ def belongs_to_many_filter(scope, value)
267
+ scope
268
+ .joins(through_relationship_name)
269
+ .where(belongs_to_many_clause(value, type))
270
+ end
271
+ end
272
+ if defined?(ActiveRecord)
273
+ ::Graphiti::Adapters::ActiveRecord::ManyToManySideload
274
+ .send(:prepend, ActiveRecordManyToManyExtras)
275
+ end
218
276
  end
@@ -53,7 +53,8 @@ module GraphitiGql
53
53
  end
54
54
  else
55
55
  params = {filter: {id: {eq: ids.join(",")}}}
56
- records = @sideload.resource.class.all(params).data
56
+ resource = Schema.registry.get(@sideload.resource.class)[:resource]
57
+ records = resource.all(params).data
57
58
  map = records.index_by { |record| record.id }
58
59
  ids.each { |id| fulfill(id, map[id]) }
59
60
  end
@@ -1,12 +1,12 @@
1
1
  module GraphitiGql
2
2
  module Loaders
3
3
  class HasMany < Many
4
- def assign(ids, proxy)
4
+ def assign(parent_records, proxy)
5
5
  records = proxy.data
6
6
  map = records.group_by { |record| record.send(@sideload.foreign_key) }
7
- ids.each do |id|
8
- data = [map[id] || [], proxy]
9
- fulfill(id, data)
7
+ parent_records.each do |pr|
8
+ data = [map[pr.send(@sideload.primary_key)] || [], proxy]
9
+ fulfill(pr, data)
10
10
  end
11
11
  end
12
12
  end
@@ -0,0 +1,15 @@
1
+ module GraphitiGql
2
+ module Loaders
3
+ class HasOne < Many
4
+ def assign(parent_records, proxy)
5
+ records = proxy.data
6
+ parent_records.each do |pr|
7
+ corresponding = records.find do |r|
8
+ r.send(@sideload.foreign_key) == pr.send(@sideload.primary_key)
9
+ end
10
+ fulfill(pr, corresponding)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -6,6 +6,8 @@ module GraphitiGql
6
6
  PolymorphicHasMany.for(sideload, params)
7
7
  elsif sideload.type == :many_to_many
8
8
  ManyToMany.for(sideload, params)
9
+ elsif sideload.type == :has_one
10
+ HasOne.for(sideload, params)
9
11
  else
10
12
  HasMany.for(sideload, params)
11
13
  end
@@ -16,13 +18,26 @@ module GraphitiGql
16
18
  @params = params
17
19
  end
18
20
 
19
- def perform(ids)
20
- raise ::Graphiti::Errors::UnsupportedPagination if paginating? && ids.length > 1
21
- raise Errors::UnsupportedStats if requesting_stats? && ids.length > 1 && !can_group?
21
+ def perform(parent_records)
22
+ raise ::Graphiti::Errors::UnsupportedPagination if paginating? && parent_records.length > 1
23
+ raise Errors::UnsupportedStats if requesting_stats? && parent_records.length > 1 && !can_group?
22
24
 
23
- build_params(ids)
24
- proxy = @sideload.resource.class.all(@params)
25
- assign(ids, proxy)
25
+ ids = parent_records.map do |pr|
26
+ pk = pr.send(@sideload.primary_key)
27
+ if @sideload.polymorphic_as
28
+ hash = {}
29
+ hash[@sideload.foreign_key] = pk
30
+ hash[:"#{@sideload.polymorphic_as}_type"] = pr.class.name
31
+ hash
32
+ else
33
+ pk
34
+ end
35
+ end
36
+
37
+ build_params(ids, parent_records)
38
+ resource = Schema.registry.get(@sideload.resource.class)[:resource]
39
+ proxy = resource.all(@params)
40
+ assign(parent_records, proxy)
26
41
  end
27
42
 
28
43
  def assign(ids, proxy)
@@ -31,7 +46,7 @@ module GraphitiGql
31
46
 
32
47
  private
33
48
 
34
- def build_params(ids)
49
+ def build_params(ids, parent_records)
35
50
  @params[:filter] ||= {}
36
51
 
37
52
  if @sideload.polymorphic_as
@@ -59,6 +74,10 @@ module GraphitiGql
59
74
  @params[:page] ||= {}
60
75
  @params[:page][:size] = 999
61
76
  end
77
+
78
+ if @sideload.params_proc
79
+ @sideload.params_proc.call(@params, parent_records)
80
+ end
62
81
  end
63
82
 
64
83
  def paginating?
@@ -1,14 +1,31 @@
1
1
  module GraphitiGql
2
2
  module Loaders
3
3
  class ManyToMany < Many
4
- def assign(ids, proxy)
5
- records = proxy.data
4
+ def assign(parent_records, proxy)
6
5
  thru = @sideload.foreign_key.keys.first
7
6
  fk = @sideload.foreign_key[thru]
8
- ids.each do |id|
9
- match = ->(thru) { thru.send(fk) == id }
10
- corresponding = records.select { |record| record.send(thru).any?(&match) }
11
- fulfill(id, [corresponding, proxy])
7
+ add_join_table_magic(proxy)
8
+ records = proxy.data
9
+ parent_records.each do |pr|
10
+ corresponding = records.select do |record|
11
+ record.send(:"_edge_#{fk}") == pr.send(@sideload.primary_key)
12
+ end
13
+ fulfill(pr, [corresponding, proxy])
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def add_join_table_magic(proxy)
20
+ if defined?(ActiveRecord) && proxy.resource.model.ancestors.include?(ActiveRecord::Base)
21
+ thru = @sideload.foreign_key.keys.first
22
+ thru_model = proxy.resource.model.reflect_on_association(thru).klass
23
+ names = thru_model.column_names.map do |n|
24
+ "#{thru_model.table_name}.#{n} as _edge_#{n}"
25
+ end
26
+ scope = proxy.scope.object
27
+ scope = scope.select(["#{proxy.resource.model.table_name}.*"] + names)
28
+ proxy.scope.object = scope
12
29
  end
13
30
  end
14
31
  end
@@ -1,15 +1,18 @@
1
1
  module GraphitiGql
2
2
  module Loaders
3
3
  class PolymorphicHasMany < Many
4
- def assign(ids, proxy)
4
+ def assign(parent_records, proxy)
5
5
  records = proxy.data
6
- ids.each do |id|
6
+ parent_records.each do |pr|
7
7
  corresponding = records.select do |record|
8
- record.send("#{@sideload.polymorphic_as}_type") == id[:"#{@sideload.polymorphic_as}_type"] &&
9
- record.send(@sideload.foreign_key) == id[@sideload.foreign_key]
8
+ child_ft = record.send("#{@sideload.polymorphic_as}_type")
9
+ child_fk = record.send(@sideload.foreign_key)
10
+ parent_ft = pr.class.name
11
+ parent_fk = pr.send(@sideload.primary_key)
12
+ child_ft == parent_ft && child_fk == parent_fk
10
13
  end
11
14
  data = [corresponding || [], proxy]
12
- fulfill(id, data)
15
+ fulfill(pr, data)
13
16
  end
14
17
  end
15
18
  end
@@ -2,10 +2,12 @@ module GraphitiGql
2
2
  class Schema
3
3
  module Fields
4
4
  class Attribute
5
- def initialize(name, config)
5
+ # If sideload is present, we're applying m2m metadata to an edge
6
+ def initialize(name, config, sideload = nil)
6
7
  @config = config
7
8
  @name = name
8
9
  @alias = config[:alias]
10
+ @sideload = sideload # is_edge: true
9
11
  end
10
12
 
11
13
  def apply(type)
@@ -13,20 +15,39 @@ module GraphitiGql
13
15
  _config = @config
14
16
  _name = @name
15
17
  _alias = @alias
18
+ _sideload = @sideload
16
19
  opts = @config.slice(:null, :deprecation_reason)
17
20
  type.field(_name, field_type, **opts)
18
21
  type.define_method _name do
19
22
  if (readable = _config[:readable]).is_a?(Symbol)
20
- resource = object.instance_variable_get(:@__graphiti_resource)
23
+ obj = object
24
+ obj = object.node if _sideload
25
+ resource = obj.instance_variable_get(:@__graphiti_resource)
21
26
  unless resource.send(readable)
22
27
  path = Graphiti.context[:object][:current_path].join(".")
23
28
  raise Errors::UnauthorizedField.new(path)
24
29
  end
25
30
  end
31
+
32
+ edge_attrs = nil
33
+ if _sideload
34
+ edge_attrs = object.node.attributes
35
+ .select { |k, v| k.to_s.starts_with?("_edge_") }
36
+ edge_attrs.transform_keys! { |k| k.to_s.gsub("_edge_", "").to_sym }
37
+ end
38
+
26
39
  value = if _config[:proc]
27
- instance_eval(&_config[:proc])
40
+ if _sideload
41
+ instance_exec(edge_attrs, &_config[:proc])
42
+ else
43
+ instance_eval(&_config[:proc])
44
+ end
28
45
  else
29
- object.send(_alias || _name)
46
+ if _sideload
47
+ edge_attrs[_alias || _name]
48
+ else
49
+ object.send(_alias || _name)
50
+ end
30
51
  end
31
52
  return if value.nil?
32
53
  Graphiti::Types[_config[:type]][:read].call(value)
@@ -36,9 +57,12 @@ module GraphitiGql
36
57
  private
37
58
 
38
59
  def field_type
39
- canonical_graphiti_type = Graphiti::Types.name_for(@config[:type])
40
- field_type = GQL_TYPE_MAP[canonical_graphiti_type.to_sym]
41
- field_type = String if @name == :id
60
+ field_type = Graphiti::Types[@config[:type]][:graphql_type]
61
+ if !field_type
62
+ canonical_graphiti_type = Graphiti::Types.name_for(@config[:type])
63
+ field_type = GQL_TYPE_MAP[canonical_graphiti_type.to_sym]
64
+ field_type = String if @name == :id
65
+ end
42
66
  field_type = [field_type] if @config[:type].to_s.starts_with?("array_of")
43
67
  field_type
44
68
  end
@@ -29,7 +29,7 @@ module GraphitiGql
29
29
  name = Registry.instance.key_for(@resource)
30
30
  stat_graphql_name = "#{name}Stats"
31
31
  return Registry.instance[stat_graphql_name][:type] if Registry.instance[stat_graphql_name]
32
- klass = Class.new(GraphQL::Schema::Object)
32
+ klass = Class.new(Schema.base_object)
33
33
  klass.graphql_name(stat_graphql_name)
34
34
  @resource.stats.each_pair do |name, config|
35
35
  calc_class = build_calc_class(stat_graphql_name, name, config.calculations.keys)
@@ -41,7 +41,7 @@ module GraphitiGql
41
41
 
42
42
  def build_calc_class(stat_graphql_name, stat_name, calculations)
43
43
  name = "#{stat_graphql_name}#{stat_name}Calculations"
44
- klass = Class.new(GraphQL::Schema::Object)
44
+ klass = Class.new(Schema.base_object)
45
45
  klass.graphql_name(name)
46
46
  calculations.each do |calc|
47
47
  klass.field calc, Float, null: false
@@ -4,32 +4,62 @@ module GraphitiGql
4
4
  class ToMany
5
5
  def initialize(sideload, sideload_type)
6
6
  @sideload = sideload
7
- @sideload_type = sideload_type
7
+ @sideload_type = if customized_edge?
8
+ build_customized_edge_type(sideload_type)
9
+ else
10
+ sideload_type
11
+ end
8
12
  end
9
13
 
10
14
  def apply(type)
11
- field = type.field @sideload.name,
12
- @sideload_type.connection_type,
13
- null: false,
15
+ opts = {
16
+ null: has_one?,
14
17
  connection: false,
15
- extensions: [RelayConnectionExtension],
16
18
  extras: [:lookahead]
19
+ }
20
+ opts[:extensions] = [RelayConnectionExtension] unless has_one?
21
+ field_type = has_one? ? @sideload_type : @sideload_type.connection_type
22
+ field = type.field @sideload.name,
23
+ field_type,
24
+ **opts
17
25
  ListArguments.new(@sideload.resource.class, @sideload).apply(field)
18
26
  _sideload = @sideload
19
27
  type.define_method(@sideload.name) do |**arguments|
20
28
  Util.is_readable_sideload!(_sideload)
21
29
  params = Util.params_from_args(arguments)
22
- pk = object.send(_sideload.primary_key)
23
- id = if _sideload.polymorphic_as
24
- hash = {}
25
- hash[_sideload.foreign_key] = pk
26
- hash[:"#{_sideload.polymorphic_as}_type"] = object.class.name
27
- id = hash
28
- else
29
- id = pk
30
- end
31
- Loaders::Many.factory(_sideload, params).load(id)
30
+ Loaders::Many.factory(_sideload, params).load(object)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def has_one?
37
+ @sideload.type == :has_one
38
+ end
39
+
40
+ def customized_edge?
41
+ @sideload.type == :many_to_many && @sideload.class.edge_resource
42
+ end
43
+
44
+ def build_customized_edge_type(sideload_type)
45
+ # build the edge class
46
+ prior_edge_class = sideload_type.edge_type_class
47
+ edge_class = Class.new(prior_edge_class)
48
+ edge_resource = @sideload.class.edge_resource
49
+ edge_resource.attributes.each_pair do |name, config|
50
+ next if name == :id
51
+ Schema::Fields::Attribute.new(name, config, @sideload).apply(edge_class)
32
52
  end
53
+ registered_parent = Schema.registry.get(@sideload.parent_resource.class)
54
+ parent_name = registered_parent[:type].graphql_name
55
+ edge_class.define_method :graphql_name do
56
+ "#{parent_name}To#{sideload_type.graphql_name}Edge"
57
+ end
58
+
59
+ # build the sideload type with new edge class applied
60
+ klass = Class.new(sideload_type)
61
+ klass.edge_type_class(edge_class)
62
+ klass
33
63
  end
34
64
  end
35
65
  end
@@ -19,7 +19,9 @@ module GraphitiGql
19
19
  if _sideload.type == :has_one
20
20
  id = object.send(_sideload.primary_key)
21
21
  params = { filter: { _sideload.foreign_key => { eq: id } } }
22
- return _sideload.resource.class.all(params).data[0]
22
+
23
+ resource = Schema.registry.get(@sideload.resource.class)[:resource]
24
+ return resource.all(params).data[0]
23
25
  end
24
26
 
25
27
  lookahead = arguments[:lookahead]
@@ -3,9 +3,8 @@ module GraphitiGql
3
3
  class Query
4
4
  def initialize(resources, existing_query: nil)
5
5
  @resources = resources
6
- @query_class = Class.new(existing_query || ::GraphQL::Schema::Object)
6
+ @query_class = Class.new(existing_query || Schema.base_object)
7
7
  @query_class.graphql_name "Query"
8
- @query_class.field_class ::GraphQL::Schema::Field
9
8
  end
10
9
 
11
10
  def build
@@ -32,7 +31,7 @@ module GraphitiGql
32
31
 
33
32
  def add_relationships
34
33
  each_relationship do |type, sideload_type, sideload|
35
- if [:has_many, :many_to_many].include?(sideload.type)
34
+ if [:has_many, :many_to_many, :has_one].include?(sideload.type)
36
35
  Fields::ToMany.new(sideload, sideload_type).apply(type)
37
36
  else
38
37
  Fields::ToOne.new(sideload, sideload_type).apply(type)
@@ -68,7 +68,7 @@ module GraphitiGql
68
68
  Registry.instance[registry_name][:type]
69
69
  end
70
70
  else
71
- klass = Class.new(GraphQL::Schema::Object)
71
+ klass = Class.new(Schema.base_object)
72
72
  end
73
73
 
74
74
  klass
@@ -32,6 +32,21 @@ module GraphitiGql
32
32
  end
33
33
  end
34
34
 
35
+ def self.base_object
36
+ klass = Class.new(GraphQL::Schema::Object)
37
+ # TODO make this config maybe
38
+ if defined?(ActionView)
39
+ klass.send(:include, ActionView::Helpers::TranslationHelper)
40
+ klass.class_eval do
41
+ def initialize(*)
42
+ super
43
+ @virtual_path = "."
44
+ end
45
+ end
46
+ end
47
+ klass
48
+ end
49
+
35
50
  def self.registry
36
51
  Registry.instance
37
52
  end
@@ -2,149 +2,44 @@ module GraphitiGql
2
2
  module SpecHelper
3
3
  extend ActiveSupport::Concern
4
4
 
5
- class Node < OpenStruct
6
- def decoded_id
7
- Base64.decode64(self.id)
8
- end
9
-
10
- def int_id
11
- decoded_id.to_i
12
- end
13
- end
5
+ included do
6
+ extend Forwardable
7
+ def_delegators :result,
8
+ :page_info,
9
+ :errors,
10
+ :error_messages,
11
+ :nodes,
12
+ :stats
14
13
 
15
- class Util
16
- def self.underscore(hash)
17
- hash.deep_transform_keys { |k| k.to_s.underscore.to_sym }
14
+ if defined?(RSpec)
15
+ let(:params) { {} }
16
+ let(:resource) { described_class }
17
+ let(:ctx) { {} }
18
18
  end
19
19
  end
20
20
 
21
- def query
22
- name = Schema.registry.key_for(resource)
23
- q = %|
24
- query #{name} (
25
- $filter: #{name}Filter,
26
- $sort: [#{name}Sort!],
27
- $first: Int,
28
- $last: Int,
29
- $before: String,
30
- $after: String,
31
- ) {
32
- #{resource.graphql_entrypoint} (
33
- filter: $filter,
34
- sort: $sort,
35
- first: $first,
36
- last: $last,
37
- before: $before,
38
- after: $after,
39
- ) {
40
- edges {
41
- node {|
42
-
43
- fields.each do |name|
44
- q << %|
45
- #{name.to_s.camelize(:lower)}|
46
- end
47
-
48
- q << %|
49
- }
50
- }
51
- pageInfo {
52
- startCursor
53
- endCursor
54
- hasNextPage
55
- hasPreviousPage
56
- }|
57
-
58
- if params[:stats]
59
- q << %|
60
- stats {|
61
- params[:stats].each_pair do |name, calculations|
62
- q << %|
63
- #{name.to_s.camelize(:lower)} {|
64
- Array(calculations).each do |calc|
65
- q << %|
66
- #{calc.to_s.camelize(:lower)}|
67
- end
68
-
69
- q << %|
70
- }|
71
- end
72
- q << %|
73
- }|
21
+ def gql_datetime(timestamp, precise = false)
22
+ if precise
23
+ timestamp.utc.round(10).iso8601(6)
24
+ else
25
+ DateTime.parse(timestamp.to_s).iso8601
74
26
  end
75
-
76
- q << %|
77
- }
78
- }
79
- |
80
-
81
- q
82
27
  end
83
28
 
84
29
  def run
85
30
  lambda do
86
- gql_params = params.deep_transform_keys { |key| key.to_s.camelize(:lower).to_sym }
87
- (gql_params[:sort] || []).each do |sort|
88
- sort[:att] = sort[:att].to_s.camelize(:lower)
89
- sort[:dir] = sort[:dir].to_s
90
- end
91
- GraphitiGql.run(query, gql_params, ctx).deep_symbolize_keys
31
+ proxy = resource.gql(params, ctx)
32
+ proxy.to_h
33
+ proxy
92
34
  end
93
35
  end
94
36
 
95
37
  def run!
96
- @response = run.call
97
- end
98
-
99
- def response
100
- @response ||= run!
101
- end
102
-
103
- def errors
104
- response[:errors]
105
- end
106
-
107
- def error_messages
108
- response[:errors].map { |e| e[:message] }
38
+ @result = run.call
109
39
  end
110
40
 
111
- def nodes
112
- return [] unless data
113
- nodes = edges.map { |e| Util.underscore(e[:node]) }
114
- nodes.map { |n| ::GraphitiGql::SpecHelper::Node.new(n) }
115
- end
116
-
117
- def data
118
- if response.key?(:data)
119
- response[:data]
120
- else
121
- raise "Tried to access 'data', but these errors were returned instead: #{error_messages.join(". ")}."
122
- end
123
- end
124
-
125
- def edges
126
- data[data.keys.first][:edges]
127
- end
128
-
129
- def stats
130
- Util.underscore(data[data.keys.first][:stats])
131
- end
132
-
133
- def page_info
134
- Util.underscore(data[data.keys.first][:pageInfo])
135
- end
136
-
137
- included do
138
- let(:params) { {} }
139
- let(:resource) { described_class }
140
- let(:ctx) { {} }
141
- let(:fields) do
142
- fields = []
143
- resource.attributes.each_pair do |name, config|
144
- (fields << name) if config[:readable]
145
- end
146
- fields
147
- end
41
+ def result
42
+ @result ||= run!
148
43
  end
149
44
  end
150
45
  end
@@ -1,3 +1,3 @@
1
1
  module GraphitiGql
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.3"
3
3
  end
data/lib/graphiti_gql.rb CHANGED
@@ -9,6 +9,7 @@ require "graphiti_gql/loaders/has_many"
9
9
  require "graphiti_gql/loaders/many_to_many"
10
10
  require "graphiti_gql/loaders/polymorphic_has_many"
11
11
  require "graphiti_gql/loaders/belongs_to"
12
+ require "graphiti_gql/loaders/has_one"
12
13
  require "graphiti_gql/response_shim"
13
14
  require "graphiti_gql/schema"
14
15
  require "graphiti_gql/schema/connection"
@@ -24,6 +25,7 @@ require "graphiti_gql/schema/fields/to_many"
24
25
  require "graphiti_gql/schema/fields/to_one"
25
26
  require "graphiti_gql/schema/fields/attribute"
26
27
  require "graphiti_gql/schema/fields/stats"
28
+ require "graphiti_gql/active_resource"
27
29
  require "graphiti_gql/engine" if defined?(Rails)
28
30
 
29
31
  module GraphitiGql
@@ -64,6 +66,9 @@ module GraphitiGql
64
66
  end
65
67
 
66
68
  def self.run(query_string, variables = {}, context = {})
69
+ if context.empty? && Graphiti.context[:object]
70
+ context = Graphiti.context[:object]
71
+ end
67
72
  Graphiti.with_context(context) do
68
73
  result = schema.execute query_string,
69
74
  variables: variables,
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphiti_gql
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lee Richmond
8
- autorequire:
8
+ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-16 00:00:00.000000000 Z
11
+ date: 2022-06-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: graphql
@@ -108,7 +108,7 @@ dependencies:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
110
  version: '7.0'
111
- description:
111
+ description:
112
112
  email:
113
113
  - richmolj@gmail.com
114
114
  executables: []
@@ -137,11 +137,13 @@ files:
137
137
  - config/routes.rb
138
138
  - graphiti_gql.gemspec
139
139
  - lib/graphiti_gql.rb
140
+ - lib/graphiti_gql/active_resource.rb
140
141
  - lib/graphiti_gql/engine.rb
141
142
  - lib/graphiti_gql/errors.rb
142
143
  - lib/graphiti_gql/graphiti_hax.rb
143
144
  - lib/graphiti_gql/loaders/belongs_to.rb
144
145
  - lib/graphiti_gql/loaders/has_many.rb
146
+ - lib/graphiti_gql/loaders/has_one.rb
145
147
  - lib/graphiti_gql/loaders/many.rb
146
148
  - lib/graphiti_gql/loaders/many_to_many.rb
147
149
  - lib/graphiti_gql/loaders/polymorphic_has_many.rb
@@ -167,7 +169,7 @@ licenses:
167
169
  - MIT
168
170
  metadata:
169
171
  homepage_uri: https://www.graphiti.dev
170
- post_install_message:
172
+ post_install_message:
171
173
  rdoc_options: []
172
174
  require_paths:
173
175
  - lib
@@ -182,8 +184,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
182
184
  - !ruby/object:Gem::Version
183
185
  version: '0'
184
186
  requirements: []
185
- rubygems_version: 3.0.3.1
186
- signing_key:
187
+ rubygems_version: 3.3.7
188
+ signing_key:
187
189
  specification_version: 4
188
190
  summary: GraphQL support for Graphiti
189
191
  test_files: []