Class: Graphiti::ResourceProxy

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/graphiti/resource_proxy.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(resource, scope, query, payload: nil, single: false, raise_on_missing: false, assign_action: nil, cache: nil, cache_expires_in: nil, cache_tag: nil) ⇒ ResourceProxy

Returns a new instance of ResourceProxy.



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/graphiti/resource_proxy.rb', line 7

def initialize(
  resource,
  scope,
  query,
  payload: nil,
  single: false,
  raise_on_missing: false,
  assign_action: nil,
  cache: nil,
  cache_expires_in: nil,
  cache_tag: nil
)
  @resource = resource
  @scope = scope
  @query = query
  @payload = payload
  @single = single
  @raise_on_missing = raise_on_missing
  @assign_action = assign_action
  @cache = cache
  @cache_expires_in = cache_expires_in
  @cache_tag = cache_tag
end

Instance Attribute Details

#cache ⇒ Object (readonly)

Returns the value of attribute cache.



5
6
7
# File 'lib/graphiti/resource_proxy.rb', line 5

def cache
  @cache
end

#cache_expires_in ⇒ Object (readonly)

Returns the value of attribute cache_expires_in.



5
6
7
# File 'lib/graphiti/resource_proxy.rb', line 5

def cache_expires_in
  @cache_expires_in
end

#cache_tag ⇒ Object (readonly)

Returns the value of attribute cache_tag.



5
6
7
# File 'lib/graphiti/resource_proxy.rb', line 5

def cache_tag
  @cache_tag
end

#payload ⇒ Object (readonly)

Returns the value of attribute payload.



5
6
7
# File 'lib/graphiti/resource_proxy.rb', line 5

def payload
  @payload
end

#query ⇒ Object (readonly)

Returns the value of attribute query.



5
6
7
# File 'lib/graphiti/resource_proxy.rb', line 5

def query
  @query
end

#resource ⇒ Object (readonly)

Returns the value of attribute resource.



5
6
7
# File 'lib/graphiti/resource_proxy.rb', line 5

def resource
  @resource
end

#scope ⇒ Object (readonly)

Returns the value of attribute scope.



5
6
7
# File 'lib/graphiti/resource_proxy.rb', line 5

def scope
  @scope
end

Instance Method Details

#[](val) ⇒ Object



49
50
51
# File 'lib/graphiti/resource_proxy.rb', line 49

def [](val)
  data[val]
end

#as_graphql(options = {}) ⇒ Object



80
81
82
# File 'lib/graphiti/resource_proxy.rb', line 80

def as_graphql(options = {})
  Renderer.new(self, options).as_graphql
end

#as_json(options = {}) ⇒ Object



68
69
70
# File 'lib/graphiti/resource_proxy.rb', line 68

def as_json(options = {})
  Renderer.new(self, options).as_json
end

#assign_attributes(params) ⇒ Object

Apply request params to the underlying model without saving it, Rails-style: the params are always passed explicitly, in the same request-params shape find/build accept. They are validated, deserialized, and become the payload #save will persist.

Idempotent per params - calling again with params that normalize to the same payload is a no-op, so the attributes callbacks fire once. Different params re-assign onto the same model instance.

Note the attributes callbacks fire here, outside any transaction opened during #save - the persistence hooks wrap only the save phase, receiving this assigned model.



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/graphiti/resource_proxy.rb', line 161

def assign_attributes(params)
  action = @assign_action || :update
  params = normalized_params_copy(params)
  add_endpoint_filter(params, action)
  validator = ::Graphiti::RequestValidator.new(@resource, params, action)
  with_context_action(action) { validator.validate! }

  if @assigned_model && same_write_payload?(validator.deserialized_payload)
    return @assigned_model
  end

  @payload = validator.deserialized_payload
  @assigned_model = @data = @resource.assign_with_relationships(
    @payload.meta(action: action),
    @payload.attributes,
    @payload.relationships,
    model_instance: @assigned_model || (data if action == :update)
  )
end

#cache? ⇒ Boolean Also known as: cached?

Returns:

  • (Boolean)


31
32
33
# File 'lib/graphiti/resource_proxy.rb', line 31

def cache?
  !!@cache
end

#cache_key ⇒ Object



283
284
285
286
287
288
289
290
291
# File 'lib/graphiti/resource_proxy.rb', line 283

def cache_key
  ActiveSupport::Cache.expand_cache_key(
    [
      @scope.cache_key,
      @query.cache_key,
      resource_cache_tag
    ].compact_blank
  )
end

#cache_key_with_version ⇒ Object



293
294
295
296
297
298
299
300
301
# File 'lib/graphiti/resource_proxy.rb', line 293

def cache_key_with_version
  ActiveSupport::Cache.expand_cache_key(
    [
      @scope.cache_key_with_version,
      @query.cache_key,
      resource_cache_tag
    ].compact_blank
  )
end

#data ⇒ Object Also known as: to_a, resolve_data



90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/graphiti/resource_proxy.rb', line 90

def data
  return @data unless @data.nil?
  return assign_attributes(@payload.params) if @assign_action

  @data = begin
    records = @scope.resolve
    raise Graphiti::Errors::RecordNotFound if records.empty? && raise_on_missing?

    records = records[0] if single?
    records
  end
end

#data=(models) ⇒ Object

Records supplied directly, no scope resolution



85
86
87
88
# File 'lib/graphiti/resource_proxy.rb', line 85

def data=(models)
  @data = models
  [@data].flatten.compact.each { |record| @resource.decorate_record(record) }
end

#debug_requested? ⇒ Boolean

Returns:

  • (Boolean)


265
266
267
# File 'lib/graphiti/resource_proxy.rb', line 265

def debug_requested?
  query.debug_requested?
end

#destroy ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
# File 'lib/graphiti/resource_proxy.rb', line 221

def destroy
  resolve_data
  transaction_response = @resource.transaction do
     = {method: :destroy}
    model = @resource.destroy(@query.filters[:id], )
    model.instance_variable_set(:@__serializer_klass, @resource.serializer)
    @resource.after_graph_persist(model, )
    validator = ::Graphiti::Util::ValidationResponse.new \
      model, @payload
    validator.validate!
    @resource.before_commit(model, )

    {result: validator}
  end
  @data, success = transaction_response[:result].to_a
  success
end

#each(&blk) ⇒ Object



119
120
121
# File 'lib/graphiti/resource_proxy.rb', line 119

def each(&blk)
  to_a.each(&blk)
end

#errors ⇒ Object



45
46
47
# File 'lib/graphiti/resource_proxy.rb', line 45

def errors
  data.errors
end

#etag ⇒ Object



273
274
275
# File 'lib/graphiti/resource_proxy.rb', line 273

def etag
  "W/#{ActiveSupport::Digest.hexdigest(cache_key_with_version.to_s)}"
end

#extra_fields ⇒ Object



261
262
263
# File 'lib/graphiti/resource_proxy.rb', line 261

def extra_fields
  query.extra_fields
end

#fields ⇒ Object



257
258
259
# File 'lib/graphiti/resource_proxy.rb', line 257

def fields
  query.fields
end

#future_resolve_data ⇒ Object



106
107
108
109
110
111
112
113
# File 'lib/graphiti/resource_proxy.rb', line 106

def future_resolve_data
  @scope.future_resolve.then do |records|
    raise Graphiti::Errors::RecordNotFound if records.empty? && raise_on_missing?

    records = records[0] if single?
    @data = records
  end
end

#include_hash ⇒ Object



250
251
252
253
254
255
# File 'lib/graphiti/resource_proxy.rb', line 250

def include_hash
  @include_hash ||= begin
    base = @payload ? @payload.include_hash : {}
    base.deep_merge(@query.include_hash)
  end
end

#jsonapi_render_options(opts = {}) ⇒ Object



53
54
55
56
57
# File 'lib/graphiti/resource_proxy.rb', line 53

def jsonapi_render_options(opts = {})
  opts[:expose] ||= {}
  opts[:expose][:context] = Graphiti.context[:object]
  opts
end

#meta ⇒ Object



115
116
117
# File 'lib/graphiti/resource_proxy.rb', line 115

def meta
  @meta ||= data.respond_to?(:meta) ? data.meta : {}
end

#pagination ⇒ Object



145
146
147
# File 'lib/graphiti/resource_proxy.rb', line 145

def pagination
  @pagination ||= Delegates::Pagination.new(self)
end

#raise_on_missing? ⇒ Boolean

Returns:

  • (Boolean)


41
42
43
# File 'lib/graphiti/resource_proxy.rb', line 41

def raise_on_missing?
  !!@raise_on_missing
end

#resource_cache_tag ⇒ Object



277
278
279
280
281
# File 'lib/graphiti/resource_proxy.rb', line 277

def resource_cache_tag
  return unless @cache_tag.present? && @resource.respond_to?(@cache_tag)

  @resource.try(@cache_tag)
end

#save(action: :create) ⇒ Object



191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/graphiti/resource_proxy.rb', line 191

def save(action: :create)
  validator = with_context_action(action) do
    # An assigned model can only come from #assign_attributes, which
    # validated the payload it stored - re-validating here would run the
    # writable guards (and their guard_model lookups) a redundant time.
    unless @assigned_model
      ::Graphiti::RequestValidator.new(@resource, @payload.params, action).validate!
    end
    persist {
      @resource.persist_with_relationships \
        @payload.meta(action: action),
        @payload.attributes,
        @payload.relationships,
        assigned_model: @assigned_model
    }
  end
  @data, success = validator.to_a

  if success
    # If the context action is `update` or `create`, certain
    # adapters will cause N+1 validation calls, so lets explicitly
    # switch to a lookup context.
    Graphiti.with_context(Graphiti.context[:object], :show) do
      @scope.resolve_sideloads([@data])
    end
  end

  success
end

#single? ⇒ Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/graphiti/resource_proxy.rb', line 37

def single?
  !!@single
end

#stats ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/graphiti/resource_proxy.rb', line 123

def stats
  @stats ||= if @query.hash[:stats]
    scope = @scope.unpaginated_object
    if resource.adapter.can_group?
      if (group = @query.hash[:stats].delete(:group_by))
        # A foreign key whose values render as public ids exposes nothing, so it may group even when unreadable.
        flag = resource.class.public_id_source_for(group[0]) ? :filterable : :readable
        resource.get_attr!(group[0], flag, request: true)
        scope = resource.adapter.group(scope, resource.model_attribute_for(group[0]))
      end
    end
    payload = Stats::Payload.new @resource,
      @query,
      scope,
      data,
      group_by: group&.first
    payload.generate
  else
    {}
  end
end

#to_graphql(options = {}) ⇒ Object



76
77
78
# File 'lib/graphiti/resource_proxy.rb', line 76

def to_graphql(options = {})
  Renderer.new(self, options).to_graphql
end

#to_json(options = {}) ⇒ Object



64
65
66
# File 'lib/graphiti/resource_proxy.rb', line 64

def to_json(options = {})
  Renderer.new(self, options).to_json
end

#to_jsonapi(options = {}) ⇒ Object



59
60
61
62
# File 'lib/graphiti/resource_proxy.rb', line 59

def to_jsonapi(options = {})
  options = jsonapi_render_options(options)
  Renderer.new(self, options).to_jsonapi
end

#to_xml(options = {}) ⇒ Object



72
73
74
# File 'lib/graphiti/resource_proxy.rb', line 72

def to_xml(options = {})
  Renderer.new(self, options).to_xml
end

#update(params = nil) ⇒ Object Also known as: update_attributes

Rails-style: pass params to assign and save in one call, or call with no arguments to save a payload assigned earlier (via find or #assign_attributes).



242
243
244
245
246
# File 'lib/graphiti/resource_proxy.rb', line 242

def update(params = nil)
  assign_attributes(params) if params
  resolve_data
  save(action: :update)
end

#updated_at ⇒ Object



269
270
271
# File 'lib/graphiti/resource_proxy.rb', line 269

def updated_at
  @scope.updated_at
end

#with_context_action(action) ⇒ Object

TODO: remove this. Only used for persisting many-to-many with AR (see activerecord adapter)



183
184
185
186
187
188
189
# File 'lib/graphiti/resource_proxy.rb', line 183

def with_context_action(action)
  original = Graphiti.context[:action]
  Graphiti.context[:action] = action
  yield
ensure
  Graphiti.context[:action] = original
end