graphiti_gql 0.2.0 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
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: []