fmrest 0.10.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +2 -0
- data/CHANGELOG.md +38 -0
- data/README.md +194 -763
- metadata +70 -97
- data/.gitignore +0 -26
- data/.rspec +0 -3
- data/.travis.yml +0 -5
- data/Gemfile +0 -3
- data/Rakefile +0 -6
- data/fmrest.gemspec +0 -38
- data/lib/fmrest.rb +0 -29
- data/lib/fmrest/errors.rb +0 -28
- data/lib/fmrest/spyke.rb +0 -21
- data/lib/fmrest/spyke/base.rb +0 -23
- data/lib/fmrest/spyke/container_field.rb +0 -59
- data/lib/fmrest/spyke/model.rb +0 -36
- data/lib/fmrest/spyke/model/associations.rb +0 -82
- data/lib/fmrest/spyke/model/attributes.rb +0 -171
- data/lib/fmrest/spyke/model/auth.rb +0 -35
- data/lib/fmrest/spyke/model/connection.rb +0 -74
- data/lib/fmrest/spyke/model/container_fields.rb +0 -25
- data/lib/fmrest/spyke/model/global_fields.rb +0 -40
- data/lib/fmrest/spyke/model/http.rb +0 -37
- data/lib/fmrest/spyke/model/orm.rb +0 -212
- data/lib/fmrest/spyke/model/serialization.rb +0 -91
- data/lib/fmrest/spyke/model/uri.rb +0 -30
- data/lib/fmrest/spyke/portal.rb +0 -55
- data/lib/fmrest/spyke/relation.rb +0 -359
- data/lib/fmrest/spyke/spyke_formatter.rb +0 -273
- data/lib/fmrest/spyke/validation_error.rb +0 -25
- data/lib/fmrest/string_date.rb +0 -220
- data/lib/fmrest/token_store.rb +0 -6
- data/lib/fmrest/token_store/active_record.rb +0 -74
- data/lib/fmrest/token_store/base.rb +0 -25
- data/lib/fmrest/token_store/memory.rb +0 -26
- data/lib/fmrest/token_store/moneta.rb +0 -41
- data/lib/fmrest/token_store/redis.rb +0 -45
- data/lib/fmrest/v1.rb +0 -21
- data/lib/fmrest/v1/connection.rb +0 -89
- data/lib/fmrest/v1/container_fields.rb +0 -114
- data/lib/fmrest/v1/dates.rb +0 -81
- data/lib/fmrest/v1/paths.rb +0 -47
- data/lib/fmrest/v1/raise_errors.rb +0 -57
- data/lib/fmrest/v1/token_session.rb +0 -142
- data/lib/fmrest/v1/token_store/active_record.rb +0 -13
- data/lib/fmrest/v1/token_store/memory.rb +0 -13
- data/lib/fmrest/v1/type_coercer.rb +0 -192
- data/lib/fmrest/v1/utils.rb +0 -95
- data/lib/fmrest/version.rb +0 -5
@@ -1,359 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module FmRest
|
4
|
-
module Spyke
|
5
|
-
class Relation < ::Spyke::Relation
|
6
|
-
SORT_PARAM_MATCHER = /(.*?)(!|__desc(?:end)?)?\Z/.freeze
|
7
|
-
|
8
|
-
# NOTE: We need to keep limit, offset, sort, query and portal accessors
|
9
|
-
# separate from regular params because FM Data API uses either "limit" or
|
10
|
-
# "_limit" (or "_offset", etc.) as param keys depending on the type of
|
11
|
-
# request, so we can't set the params until the last moment
|
12
|
-
|
13
|
-
|
14
|
-
attr_accessor :limit_value, :offset_value, :sort_params, :query_params,
|
15
|
-
:included_portals, :portal_params, :script_params
|
16
|
-
|
17
|
-
def initialize(*_args)
|
18
|
-
super
|
19
|
-
|
20
|
-
@limit_value = klass.default_limit
|
21
|
-
|
22
|
-
if klass.default_sort.present?
|
23
|
-
@sort_params = Array.wrap(klass.default_sort).map { |s| normalize_sort_param(s) }
|
24
|
-
end
|
25
|
-
|
26
|
-
@query_params = []
|
27
|
-
|
28
|
-
@included_portals = nil
|
29
|
-
@portal_params = {}
|
30
|
-
@script_params = {}
|
31
|
-
end
|
32
|
-
|
33
|
-
# @param options [String, Array, Hash, nil, false] sets script params to
|
34
|
-
# execute in the next get or find request
|
35
|
-
#
|
36
|
-
# @example
|
37
|
-
# # Find records and run the script named "My script"
|
38
|
-
# Person.script("My script").find_some
|
39
|
-
#
|
40
|
-
# # Find records and run the script named "My script" with param "the param"
|
41
|
-
# Person.script(["My script", "the param"]).find_some
|
42
|
-
#
|
43
|
-
# # Find records and run a prerequest, presort and after (normal) script
|
44
|
-
# Person.script(after: "Script", prerequest: "Prereq script", presort: "Presort script").find_some
|
45
|
-
#
|
46
|
-
# # Same as above, but passing parameters too
|
47
|
-
# Person.script(
|
48
|
-
# after: ["After script", "the param"],
|
49
|
-
# prerequest: ["Prereq script", "the param"],
|
50
|
-
# presort: o ["Presort script", "the param"]
|
51
|
-
# ).find_some
|
52
|
-
#
|
53
|
-
# Person.script(nil).find_some # Disable script execution
|
54
|
-
# Person.script(false).find_some # Disable script execution
|
55
|
-
#
|
56
|
-
# @return [FmRest::Spyke::Relation] a new relation with the script
|
57
|
-
# options applied
|
58
|
-
def script(options)
|
59
|
-
with_clone do |r|
|
60
|
-
if options.eql?(false) || options.eql?(nil)
|
61
|
-
r.script_params = {}
|
62
|
-
else
|
63
|
-
r.script_params = script_params.merge(FmRest::V1.convert_script_params(options))
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
# @param value_or_hash [Integer, Hash] the limit value for this layout,
|
69
|
-
# or a hash with limits for the layout's portals
|
70
|
-
# @example
|
71
|
-
# Person.limit(10) # Set layout limit
|
72
|
-
# Person.limit(children: 10) # Set portal limit
|
73
|
-
# @return [FmRest::Spyke::Relation] a new relation with the limits
|
74
|
-
# applied
|
75
|
-
def limit(value_or_hash)
|
76
|
-
with_clone do |r|
|
77
|
-
if value_or_hash.respond_to?(:each)
|
78
|
-
r.set_portal_params(value_or_hash, :limit)
|
79
|
-
else
|
80
|
-
r.limit_value = value_or_hash
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# @param value_or_hash [Integer, Hash] the offset value for this layout,
|
86
|
-
# or a hash with offsets for the layout's portals
|
87
|
-
# @example
|
88
|
-
# Person.offset(10) # Set layout offset
|
89
|
-
# Person.offset(children: 10) # Set portal offset
|
90
|
-
# @return [FmRest::Spyke::Relation] a new relation with the offsets
|
91
|
-
# applied
|
92
|
-
def offset(value_or_hash)
|
93
|
-
with_clone do |r|
|
94
|
-
if value_or_hash.respond_to?(:each)
|
95
|
-
r.set_portal_params(value_or_hash, :offset)
|
96
|
-
else
|
97
|
-
r.offset_value = value_or_hash
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
# Allows sort params given in either hash format (using FM Data API's
|
103
|
-
# format), or as a symbol, in which case the of the attribute must match
|
104
|
-
# a known mapped attribute, optionally suffixed with `!` or `__desc` to
|
105
|
-
# signify it should use descending order.
|
106
|
-
#
|
107
|
-
# @param args [Array<Symbol, Hash>] the names of attributes to sort by with
|
108
|
-
# optional `!` or `__desc` suffix, or a hash of options as expected by
|
109
|
-
# the FM Data API
|
110
|
-
# @example
|
111
|
-
# Person.sort(:first_name, :age!)
|
112
|
-
# Person.sort(:first_name, :age__desc)
|
113
|
-
# Person.sort(:first_name, :age__descend)
|
114
|
-
# Person.sort({ fieldName: "FirstName" }, { fieldName: "Age", sortOrder: "descend" })
|
115
|
-
# @return [FmRest::Spyke::Relation] a new relation with the sort options
|
116
|
-
# applied
|
117
|
-
def sort(*args)
|
118
|
-
with_clone do |r|
|
119
|
-
r.sort_params = args.flatten.map { |s| normalize_sort_param(s) }
|
120
|
-
end
|
121
|
-
end
|
122
|
-
alias order sort
|
123
|
-
|
124
|
-
# Sets the portals to include with each record in the response.
|
125
|
-
#
|
126
|
-
# @param args [Array<Symbol, String>, true, false] the names of portals to
|
127
|
-
# include, or `false` to request no portals
|
128
|
-
# @example
|
129
|
-
# Person.portal(:relatives, :pets)
|
130
|
-
# Person.portal(false) # Disables portals
|
131
|
-
# Person.portal(true) # Enables portals (includes all)
|
132
|
-
# @return [FmRest::Spyke::Relation] a new relation with the portal
|
133
|
-
# options applied
|
134
|
-
def portal(*args)
|
135
|
-
raise ArgumentError, "Call `portal' with at least one argument" if args.empty?
|
136
|
-
|
137
|
-
with_clone do |r|
|
138
|
-
if args.length == 1 && args.first.eql?(true) || args.first.eql?(false)
|
139
|
-
r.included_portals = args.first ? nil : []
|
140
|
-
else
|
141
|
-
r.included_portals ||= []
|
142
|
-
r.included_portals += args.flatten.map { |p| normalize_portal_param(p) }
|
143
|
-
r.included_portals.uniq!
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
alias includes portal
|
148
|
-
alias portals portal
|
149
|
-
|
150
|
-
# Same as calling `portal(true)`
|
151
|
-
#
|
152
|
-
# @return (see #portal)
|
153
|
-
def with_all_portals
|
154
|
-
portal(true)
|
155
|
-
end
|
156
|
-
|
157
|
-
# Same as calling `portal(false)`
|
158
|
-
#
|
159
|
-
# @return (see #portal)
|
160
|
-
def without_portals
|
161
|
-
portal(false)
|
162
|
-
end
|
163
|
-
|
164
|
-
def query(*params)
|
165
|
-
with_clone do |r|
|
166
|
-
r.query_params += params.flatten.map { |p| normalize_query_params(p) }
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
def omit(params)
|
171
|
-
query(params.merge(omit: true))
|
172
|
-
end
|
173
|
-
|
174
|
-
# @return [Boolean] whether a query was set on this relation
|
175
|
-
def has_query?
|
176
|
-
query_params.present?
|
177
|
-
end
|
178
|
-
|
179
|
-
# Finds a single instance of the model by forcing limit = 1, or simply
|
180
|
-
# fetching the record by id if the primary key was set
|
181
|
-
#
|
182
|
-
# @return [FmRest::Spyke::Base]
|
183
|
-
def find_one
|
184
|
-
@find_one ||=
|
185
|
-
if primary_key_set?
|
186
|
-
without_collection_params { super }
|
187
|
-
else
|
188
|
-
klass.new_collection_from_result(limit(1).fetch).first
|
189
|
-
end
|
190
|
-
rescue ::Spyke::ConnectionError => error
|
191
|
-
fallback_or_reraise(error, default: nil)
|
192
|
-
end
|
193
|
-
alias_method :first, :find_one
|
194
|
-
alias_method :any, :find_one
|
195
|
-
|
196
|
-
# Yields each batch of records that was found by the find options.
|
197
|
-
#
|
198
|
-
# NOTE: By its nature, batch processing is subject to race conditions if
|
199
|
-
# other processes are modifying the database
|
200
|
-
#
|
201
|
-
# @param batch_size [Integer] Specifies the size of the batch.
|
202
|
-
# @return [Enumerator] if called without a block.
|
203
|
-
def find_in_batches(batch_size: 1000)
|
204
|
-
unless block_given?
|
205
|
-
return to_enum(:find_in_batches, batch_size: batch_size) do
|
206
|
-
total = limit(1).find_some.metadata.data_info.found_count
|
207
|
-
(total - 1).div(batch_size) + 1
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
offset = 1 # DAPI offset is 1-based
|
212
|
-
|
213
|
-
loop do
|
214
|
-
relation = offset(offset).limit(batch_size)
|
215
|
-
|
216
|
-
records = relation.find_some
|
217
|
-
|
218
|
-
yield records if records.length > 0
|
219
|
-
|
220
|
-
break if records.length < batch_size
|
221
|
-
|
222
|
-
# Save one iteration if the total is a multiple of batch_size
|
223
|
-
if found_count = records.metadata.data_info && records.metadata.data_info.found_count
|
224
|
-
break if found_count == (offset - 1) + batch_size
|
225
|
-
end
|
226
|
-
|
227
|
-
offset += batch_size
|
228
|
-
end
|
229
|
-
end
|
230
|
-
|
231
|
-
# Looping through a collection of records from the database (using the
|
232
|
-
# #all method, for example) is very inefficient since it will fetch and
|
233
|
-
# instantiate all the objects at once.
|
234
|
-
#
|
235
|
-
# In that case, batch processing methods allow you to work with the
|
236
|
-
# records in batches, thereby greatly reducing memory consumption and be
|
237
|
-
# lighter on the Data API server.
|
238
|
-
#
|
239
|
-
# The find_each method uses #find_in_batches with a batch size of 1000
|
240
|
-
# (or as specified by the :batch_size option).
|
241
|
-
#
|
242
|
-
# NOTE: By its nature, batch processing is subject to race conditions if
|
243
|
-
# other processes are modifying the database
|
244
|
-
#
|
245
|
-
# @param (see #find_in_batches)
|
246
|
-
# @example
|
247
|
-
# Person.find_each do |person|
|
248
|
-
# person.greet
|
249
|
-
# end
|
250
|
-
#
|
251
|
-
# Person.query(name: "==Mitch").find_each do |person|
|
252
|
-
# person.say_hi
|
253
|
-
# end
|
254
|
-
# @return (see #find_in_batches)
|
255
|
-
def find_each(batch_size: 1000)
|
256
|
-
unless block_given?
|
257
|
-
return to_enum(:find_each, batch_size: batch_size) do
|
258
|
-
limit(1).find_some.metadata.data_info.found_count
|
259
|
-
end
|
260
|
-
end
|
261
|
-
|
262
|
-
find_in_batches(batch_size: batch_size) do |records|
|
263
|
-
records.each { |r| yield r }
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
protected
|
268
|
-
|
269
|
-
def set_portal_params(params_hash, param)
|
270
|
-
# Copy portal_params so we're not modifying the same hash as the parent
|
271
|
-
# scope
|
272
|
-
self.portal_params = portal_params.dup
|
273
|
-
|
274
|
-
params_hash.each do |portal_name, value|
|
275
|
-
# TODO: Use a hash like { portal_name: { param: value } } instead so
|
276
|
-
# we can intelligently avoid including portal params for excluded
|
277
|
-
# portals
|
278
|
-
key = "#{param}.#{normalize_portal_param(portal_name)}"
|
279
|
-
|
280
|
-
# Delete key if value is falsy
|
281
|
-
if !value && portal_params.has_key?(key)
|
282
|
-
portal_params.delete(key)
|
283
|
-
else
|
284
|
-
self.portal_params[key] = value
|
285
|
-
end
|
286
|
-
end
|
287
|
-
end
|
288
|
-
|
289
|
-
private
|
290
|
-
|
291
|
-
def normalize_sort_param(param)
|
292
|
-
if param.kind_of?(Symbol) || param.kind_of?(String)
|
293
|
-
_, attr, descend = param.to_s.match(SORT_PARAM_MATCHER).to_a
|
294
|
-
|
295
|
-
unless field_name = klass.mapped_attributes[attr]
|
296
|
-
raise ArgumentError, "Unknown attribute `#{attr}' given to sort as #{param.inspect}. If you want to use a custom sort pass a hash in the Data API format"
|
297
|
-
end
|
298
|
-
|
299
|
-
hash = { fieldName: field_name }
|
300
|
-
hash[:sortOrder] = "descend" if descend
|
301
|
-
return hash
|
302
|
-
end
|
303
|
-
|
304
|
-
# TODO: Sanitize sort hash param for FM Data API conformity?
|
305
|
-
param
|
306
|
-
end
|
307
|
-
|
308
|
-
def normalize_portal_param(param)
|
309
|
-
if param.kind_of?(Symbol)
|
310
|
-
portal_key, = klass.portal_options.find { |_, opts| opts[:name].to_s == param.to_s }
|
311
|
-
|
312
|
-
unless portal_key
|
313
|
-
raise ArgumentError, "Unknown portal #{param.inspect}. If you want to include a portal not defined in the model pass it as a string instead"
|
314
|
-
end
|
315
|
-
|
316
|
-
return portal_key
|
317
|
-
end
|
318
|
-
|
319
|
-
param
|
320
|
-
end
|
321
|
-
|
322
|
-
def normalize_query_params(params)
|
323
|
-
params.each_with_object({}) do |(k, v), normalized|
|
324
|
-
if k == :omit || k == "omit"
|
325
|
-
# FM Data API wants omit values as strings, e.g. "true" or "false"
|
326
|
-
# rather than true/false
|
327
|
-
normalized["omit"] = v.to_s
|
328
|
-
next
|
329
|
-
end
|
330
|
-
|
331
|
-
# TODO: Raise ArgumentError if an attribute given as symbol isn't defiend
|
332
|
-
if k.kind_of?(Symbol) && klass.mapped_attributes.has_key?(k)
|
333
|
-
normalized[klass.mapped_attributes[k].to_s] = v
|
334
|
-
else
|
335
|
-
normalized[k.to_s] = v
|
336
|
-
end
|
337
|
-
end
|
338
|
-
end
|
339
|
-
|
340
|
-
def primary_key_set?
|
341
|
-
params[klass.primary_key].present?
|
342
|
-
end
|
343
|
-
|
344
|
-
def without_collection_params
|
345
|
-
orig_values = limit_value, offset_value, sort_params, query_params
|
346
|
-
self.limit_value = self.offset_value = self.sort_params = self.query_params = nil
|
347
|
-
yield
|
348
|
-
ensure
|
349
|
-
self.limit_value, self.offset_value, self.sort_params, self.query_params = orig_values
|
350
|
-
end
|
351
|
-
|
352
|
-
def with_clone
|
353
|
-
clone.tap do |relation|
|
354
|
-
yield relation
|
355
|
-
end
|
356
|
-
end
|
357
|
-
end
|
358
|
-
end
|
359
|
-
end
|
@@ -1,273 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "json"
|
4
|
-
require "ostruct"
|
5
|
-
|
6
|
-
module FmRest
|
7
|
-
module Spyke
|
8
|
-
# Metadata class to be passed to Spyke::Collection#metadata
|
9
|
-
class Metadata < Struct.new(:messages, :script, :data_info)
|
10
|
-
alias_method :scripts, :script
|
11
|
-
end
|
12
|
-
|
13
|
-
class DataInfo < OpenStruct
|
14
|
-
def total_record_count; totalRecordCount; end
|
15
|
-
def found_count; foundCount; end
|
16
|
-
def returned_count; returnedCount; end
|
17
|
-
end
|
18
|
-
|
19
|
-
# Response Faraday middleware for converting FM API's response JSON into
|
20
|
-
# Spyke's expected format
|
21
|
-
class SpykeFormatter < ::Faraday::Response::Middleware
|
22
|
-
SINGLE_RECORD_RE = %r(/records/\d+\z).freeze
|
23
|
-
MULTIPLE_RECORDS_RE = %r(/records\z).freeze
|
24
|
-
CONTAINER_RE = %r(/records/\d+/containers/[^/]+/\d+\z).freeze
|
25
|
-
FIND_RECORDS_RE = %r(/_find\b).freeze
|
26
|
-
SCRIPT_REQUEST_RE = %r(/script/[^/]+\z).freeze
|
27
|
-
|
28
|
-
VALIDATION_ERROR_RANGE = 500..599
|
29
|
-
|
30
|
-
# @param app [#call]
|
31
|
-
# @param model [Class<FmRest::Spyke::Base>]
|
32
|
-
def initialize(app, model)
|
33
|
-
super(app)
|
34
|
-
@model = model
|
35
|
-
end
|
36
|
-
|
37
|
-
# @param env [Faraday::Env]
|
38
|
-
def on_complete(env)
|
39
|
-
return unless env.body.is_a?(Hash)
|
40
|
-
|
41
|
-
json = env.body
|
42
|
-
|
43
|
-
case
|
44
|
-
when single_record_request?(env)
|
45
|
-
env.body = prepare_single_record(json)
|
46
|
-
when multiple_records_request?(env), find_request?(env)
|
47
|
-
env.body = prepare_collection(json)
|
48
|
-
when create_request?(env), update_request?(env), delete_request?(env), container_upload_request?(env)
|
49
|
-
env.body = prepare_save_response(json)
|
50
|
-
when execute_script_request?(env)
|
51
|
-
env.body = build_base_hash(json)
|
52
|
-
else
|
53
|
-
# Attempt to parse unknown requests too
|
54
|
-
env.body = build_base_hash(json)
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
private
|
59
|
-
|
60
|
-
# @param json [Hash]
|
61
|
-
# @return [Hash] the response in Spyke format
|
62
|
-
def prepare_save_response(json)
|
63
|
-
response = json[:response]
|
64
|
-
|
65
|
-
data = {}
|
66
|
-
data[:mod_id] = response[:modId] if response[:modId]
|
67
|
-
data[:id] = response[:recordId].to_i if response[:recordId]
|
68
|
-
|
69
|
-
build_base_hash(json, true).merge!(data: data)
|
70
|
-
end
|
71
|
-
|
72
|
-
# (see #prepare_save_response)
|
73
|
-
def prepare_single_record(json)
|
74
|
-
data =
|
75
|
-
json[:response][:data] &&
|
76
|
-
prepare_record_data(json[:response][:data].first)
|
77
|
-
|
78
|
-
build_base_hash(json).merge!(data: data)
|
79
|
-
end
|
80
|
-
|
81
|
-
# (see #prepare_save_response)
|
82
|
-
def prepare_collection(json)
|
83
|
-
data =
|
84
|
-
json[:response][:data] &&
|
85
|
-
json[:response][:data].map { |record_data| prepare_record_data(record_data) }
|
86
|
-
|
87
|
-
build_base_hash(json).merge!(data: data)
|
88
|
-
end
|
89
|
-
|
90
|
-
# @param json [Hash]
|
91
|
-
# @param include_errors [Boolean]
|
92
|
-
# @return [FmRest::Spyke::Metadata] the skeleton structure for a
|
93
|
-
# Spyke-formatted response
|
94
|
-
def build_base_hash(json, include_errors = false)
|
95
|
-
{
|
96
|
-
metadata: Metadata.new(
|
97
|
-
prepare_messages(json),
|
98
|
-
prepare_script_results(json),
|
99
|
-
prepare_data_info(json)
|
100
|
-
).freeze,
|
101
|
-
errors: include_errors ? prepare_errors(json) : {}
|
102
|
-
}
|
103
|
-
end
|
104
|
-
|
105
|
-
# @param json [Hash]
|
106
|
-
# @return [Array<OpenStruct>] the skeleton structure for a
|
107
|
-
# Spyke-formatted response
|
108
|
-
def prepare_messages(json)
|
109
|
-
return [] unless json[:messages]
|
110
|
-
json[:messages].map { |m| OpenStruct.new(m).freeze }.freeze
|
111
|
-
end
|
112
|
-
|
113
|
-
# @param json [Hash]
|
114
|
-
# @return [OpenStruct] the script(s) execution results for Spyke metadata
|
115
|
-
# format
|
116
|
-
def prepare_script_results(json)
|
117
|
-
results = {}
|
118
|
-
|
119
|
-
[:prerequest, :presort].each do |s|
|
120
|
-
if json[:response][:"scriptError.#{s}"]
|
121
|
-
results[s] = OpenStruct.new(
|
122
|
-
result: json[:response][:"scriptResult.#{s}"],
|
123
|
-
error: json[:response][:"scriptError.#{s}"]
|
124
|
-
).freeze
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
if json[:response][:scriptError]
|
129
|
-
results[:after] = OpenStruct.new(
|
130
|
-
result: json[:response][:scriptResult],
|
131
|
-
error: json[:response][:scriptError]
|
132
|
-
).freeze
|
133
|
-
end
|
134
|
-
|
135
|
-
results.present? ? OpenStruct.new(results).freeze : nil
|
136
|
-
end
|
137
|
-
|
138
|
-
# @param json [Hash]
|
139
|
-
# @return [OpenStruct] the script(s) execution results for
|
140
|
-
# Spyke metadata format
|
141
|
-
def prepare_data_info(json)
|
142
|
-
data_info = json[:response] && json[:response][:dataInfo]
|
143
|
-
|
144
|
-
return nil unless data_info.present?
|
145
|
-
|
146
|
-
DataInfo.new(data_info).freeze
|
147
|
-
end
|
148
|
-
|
149
|
-
# @param json [Hash]
|
150
|
-
# @return [Hash] the errors hash in Spyke format
|
151
|
-
def prepare_errors(json)
|
152
|
-
# Code 0 means "No Error"
|
153
|
-
# https://fmhelp.filemaker.com/help/17/fmp/en/index.html#page/FMP_Help/error-codes.html
|
154
|
-
return {} if json[:messages][0][:code].to_i == 0
|
155
|
-
|
156
|
-
json[:messages].each_with_object(base: []) do |message, hash|
|
157
|
-
# Only include validation errors
|
158
|
-
next unless VALIDATION_ERROR_RANGE.include?(message[:code].to_i)
|
159
|
-
|
160
|
-
hash[:base] << "#{message[:message]} (#{message[:code]})"
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
# `json_data` is expected in this format:
|
165
|
-
#
|
166
|
-
# {
|
167
|
-
# "fieldData": {
|
168
|
-
# "fieldName1" : "fieldValue1",
|
169
|
-
# "fieldName2" : "fieldValue2",
|
170
|
-
# ...
|
171
|
-
# },
|
172
|
-
# "portalData": {
|
173
|
-
# "portal1" : [
|
174
|
-
# { <portalRecord1> },
|
175
|
-
# { <portalRecord2> },
|
176
|
-
# ...
|
177
|
-
# ],
|
178
|
-
# "portal2" : [
|
179
|
-
# { <portalRecord1> },
|
180
|
-
# { <portalRecord2> },
|
181
|
-
# ...
|
182
|
-
# ]
|
183
|
-
# },
|
184
|
-
# "modId": <Id_for_last_modification>,
|
185
|
-
# "recordId": <Unique_internal_ID_for_this_record>
|
186
|
-
# }
|
187
|
-
#
|
188
|
-
# @param json_data [Hash]
|
189
|
-
# @return [Hash] the record data in Spyke format
|
190
|
-
def prepare_record_data(json_data)
|
191
|
-
out = { id: json_data[:recordId].to_i, mod_id: json_data[:modId] }
|
192
|
-
out.merge!(json_data[:fieldData])
|
193
|
-
out.merge!(prepare_portal_data(json_data[:portalData])) if json_data[:portalData]
|
194
|
-
out
|
195
|
-
end
|
196
|
-
|
197
|
-
# Extracts `recordId` and strips the `"PortalName::"` field prefix for each
|
198
|
-
# portal
|
199
|
-
#
|
200
|
-
# Sample `json_portal_data`:
|
201
|
-
#
|
202
|
-
# "portalData": {
|
203
|
-
# "Orders":[
|
204
|
-
# { "Orders::DeliveryDate": "3/7/2017", "recordId": "23" }
|
205
|
-
# ]
|
206
|
-
# }
|
207
|
-
#
|
208
|
-
# @param json_portal_data [Hash]
|
209
|
-
# @return [Hash] the portal data in Spyke format
|
210
|
-
def prepare_portal_data(json_portal_data)
|
211
|
-
json_portal_data.each_with_object({}) do |(portal_name, portal_records), out|
|
212
|
-
portal_options = @model.portal_options[portal_name.to_s] || {}
|
213
|
-
|
214
|
-
out[portal_name] =
|
215
|
-
portal_records.map do |portal_fields|
|
216
|
-
attributes = { id: portal_fields[:recordId].to_i }
|
217
|
-
attributes[:mod_id] = portal_fields[:modId] if portal_fields[:modId]
|
218
|
-
|
219
|
-
prefix = portal_options[:attribute_prefix] || portal_name
|
220
|
-
prefix_matcher = /\A#{prefix}::/
|
221
|
-
|
222
|
-
portal_fields.each do |k, v|
|
223
|
-
next if :recordId == k || :modId == k
|
224
|
-
attributes[k.to_s.gsub(prefix_matcher, "").to_sym] = v
|
225
|
-
end
|
226
|
-
|
227
|
-
attributes
|
228
|
-
end
|
229
|
-
end
|
230
|
-
end
|
231
|
-
|
232
|
-
# @param env [Faraday::Env]
|
233
|
-
# @return [Boolean]
|
234
|
-
def single_record_request?(env)
|
235
|
-
env.method == :get && env.url.path.match(SINGLE_RECORD_RE)
|
236
|
-
end
|
237
|
-
|
238
|
-
# (see #single_record_request?)
|
239
|
-
def multiple_records_request?(env)
|
240
|
-
env.method == :get && env.url.path.match(MULTIPLE_RECORDS_RE)
|
241
|
-
end
|
242
|
-
|
243
|
-
# (see #single_record_request?)
|
244
|
-
def find_request?(env)
|
245
|
-
env.method == :post && env.url.path.match(FIND_RECORDS_RE)
|
246
|
-
end
|
247
|
-
|
248
|
-
# (see #single_record_request?)
|
249
|
-
def update_request?(env)
|
250
|
-
env.method == :patch && env.url.path.match(SINGLE_RECORD_RE)
|
251
|
-
end
|
252
|
-
|
253
|
-
# (see #single_record_request?)
|
254
|
-
def create_request?(env)
|
255
|
-
env.method == :post && env.url.path.match(MULTIPLE_RECORDS_RE)
|
256
|
-
end
|
257
|
-
|
258
|
-
# (see #single_record_request?)
|
259
|
-
def container_upload_request?(env)
|
260
|
-
env.method == :post && env.url.path.match(CONTAINER_RE)
|
261
|
-
end
|
262
|
-
|
263
|
-
# (see #single_record_request?)
|
264
|
-
def delete_request?(env)
|
265
|
-
env.method == :delete && env.url.path.match(SINGLE_RECORD_RE)
|
266
|
-
end
|
267
|
-
|
268
|
-
def execute_script_request?(env)
|
269
|
-
env.method == :get && env.url.path.match(SCRIPT_REQUEST_RE)
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|