trophonius 1.4.5.5 → 2.1.5

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.
data/lib/model.rb ADDED
@@ -0,0 +1,394 @@
1
+ require 'json'
2
+ require 'config'
3
+ require 'record'
4
+ require 'recordset'
5
+ require 'translator'
6
+ require 'query'
7
+ require 'error'
8
+ # require 'time'
9
+ # require 'date_time'
10
+ # require 'date'
11
+
12
+ require 'active_support/inflector'
13
+
14
+ module Trophonius
15
+ # This class will retrieve the records from the FileMaker database and build a RecordSet filled with Record objects.
16
+ # One Record object represents a record in FileMaker.
17
+ class Model
18
+ attr_reader :configuration
19
+ attr_accessor :current_query
20
+
21
+ def initialize(config:)
22
+ @configuration = config
23
+ @offset = ''
24
+ @limit = ''
25
+ end
26
+
27
+ ##
28
+ # Sets up the configuration for the model.
29
+ #
30
+ # @param [Hash] configuration: the hash containing the config to setup the model correctly.
31
+ # configuration = {layout_name: "theFileMakerLayoutForThisModel",
32
+ # non_modifiable_fields: ["an", "array", "containing", "calculation_fields", "etc."]}
33
+ def self.config(configuration)
34
+ @configuration ||= Configuration.new
35
+ @configuration.layout_name = configuration[:layout_name]
36
+ @configuration.non_modifiable_fields = configuration[:non_modifiable_fields] || []
37
+ @configuration.translations = {}
38
+ @configuration.has_many_relations = {}
39
+ @configuration.belongs_to_relations = {}
40
+ @offset = ''
41
+ @limit = ''
42
+ end
43
+
44
+ ##
45
+ # Add a belongs to relationship.
46
+ #
47
+ # @param [Symbol] model_name: the name of the model to build a relation with
48
+ # @param [String] primary_key: the name of the field containing the primary to build the relation over
49
+ # @param [String] foreign_key: the name of the field containing the primary to build the relation over
50
+ def self.belongs_to(model_name, primary_key:, foreign_key:)
51
+ @configuration.belongs_to_relations.merge!({ model_name => { primary_key: primary_key, foreign_key: foreign_key } })
52
+ end
53
+
54
+ ##
55
+ # Add a has many relationship.
56
+ #
57
+ # @param [Symbol] model_name: the name of the model to build a relation with
58
+ # @param [String] primary_key: the name of the field containing the primary to build the relation over
59
+ # @param [String] foreign_key: the name of the field containing the primary to build the relation over
60
+ def self.has_many(model_name, primary_key:, foreign_key:)
61
+ @configuration.has_many_relations.merge!({ model_name => { primary_key: primary_key, foreign_key: foreign_key } })
62
+ end
63
+
64
+ ##
65
+ # Limits the found record set.
66
+ #
67
+ # @param [Integer] page: number of current page
68
+ # @param [Integer] limit: number of records retreived
69
+ #
70
+ # @return [Trophonius::Model] Self
71
+ def self.paginate(page, limit)
72
+ @offset = (((page * limit) - limit) + 1).to_s
73
+ @limit = limit.to_s
74
+ self
75
+ end
76
+
77
+ ##
78
+ # Returns the FileMaker layout this Model corresponds to
79
+ #
80
+ # @return [String] layout name of the model
81
+ def self.layout_name
82
+ @configuration.layout_name
83
+ end
84
+
85
+ ##
86
+ # Returns the Hash containing the related parent models
87
+ #
88
+ # @return [Hash] child models
89
+ def self.has_many_relations
90
+ @configuration.has_many_relations
91
+ end
92
+
93
+ ##
94
+ # Returns the Hash containing the related parent models
95
+ #
96
+ # @return [Hash] parent models
97
+ def self.belongs_to_relations
98
+ @configuration.belongs_to_relations
99
+ end
100
+
101
+ ##
102
+ # Returns the fields that FileMaker won't allow us to modify
103
+ #
104
+ # @return [[Array]] fields that FileMaker won't allow us to modify
105
+ def self.non_modifiable_fields
106
+ @configuration.non_modifiable_fields
107
+ end
108
+
109
+ ##
110
+ # Returns the translations of the fields
111
+ #
112
+ # @return [Hash] translations of the fields Rails -> FileMaker
113
+ def self.translations
114
+ @configuration.translations
115
+ end
116
+
117
+ ##
118
+ # creates Rails -> FileMaker field translations by requesting the first record
119
+ #
120
+ # @return [Hash] translations of the fields Rails -> FileMaker
121
+ def self.create_translations
122
+ extend Trophonius::Translator
123
+ field_names = if Trophonius.config.fm_18
124
+ Trophonius::DatabaseRequest.get_layout_field_names(layout_name)
125
+ else
126
+ DatabaseRequest.retrieve_first(layout_name).dig(
127
+ 'response', 'data', 0, 'fieldData'
128
+ ).keys
129
+ end
130
+ field_names.each do |field|
131
+ new_name = methodize_field(field.to_s).to_s
132
+ @configuration.translations.merge!(
133
+ { new_name => field.to_s }
134
+ )
135
+ end
136
+ @configuration.translations
137
+ end
138
+
139
+ def self.method_missing(method, *args)
140
+ new_instance = Trophonius::Model.new(config: @configuration)
141
+ new_instance.current_query = Trophonius::Query.new(trophonius_model: self, limit: @limit, offset: @offset)
142
+ args << new_instance
143
+ new_instance.current_query.send(method, args) if new_instance.current_query.respond_to?(method)
144
+ end
145
+
146
+ def method_missing(method, *args, &block)
147
+ if @current_query.respond_to?(method)
148
+ args << self
149
+ @current_query.send(method, args)
150
+ elsif @current_query.response.respond_to?(method)
151
+ ret_val = @current_query.run_query(method, *args, &block)
152
+ @limit = ''
153
+ @offset = ''
154
+ ret_val
155
+ end
156
+ end
157
+
158
+ ##
159
+ # Finds all records in FileMaker corresponding to the requested query
160
+ # @param [Hash] fieldData: the data to find
161
+ #
162
+ # @return [Trophonius::Model] new instance of the model
163
+ def self.where(field_data)
164
+ create_translations if @configuration.translations.keys.empty?
165
+
166
+ new_instance = Trophonius::Model.new(config: @configuration)
167
+ new_instance.current_query = Trophonius::Query.new(trophonius_model: self, limit: @limit, offset: @offset)
168
+ new_instance.current_query.build_query[0].merge!(field_data)
169
+ new_instance
170
+ end
171
+
172
+ ##
173
+ # Finds all records in FileMaker corresponding to the requested query
174
+ # This method is created to enable where chaining
175
+ #
176
+ # @param [Hash] fieldData: the data to find
177
+ #
178
+ # @return [Trophonius::Model] new instance of the model
179
+ def where(field_data)
180
+ @current_query.build_query[0].merge!(field_data)
181
+ self
182
+ end
183
+
184
+ ##
185
+ # Creates and saves a record in FileMaker
186
+ #
187
+ # @param [Hash] fieldData: the fields to fill with the data
188
+ #
189
+ # @return [Record] the created record
190
+ # Model.create(fieldOne: "Data")
191
+ def self.create(field_data, portal_data: {})
192
+ create_translations if @configuration.translations.keys.empty?
193
+
194
+ field_data.transform_keys! { |k| (@configuration.translations[k.to_s] || k).to_s }
195
+
196
+ portal_data.each do |portal_name, values|
197
+ values.map { |record| record.transform_keys! { |k| "#{portal_name}::#{k}" } }
198
+ end
199
+
200
+ body = { fieldData: field_data }
201
+ body.merge!({ portalData: portal_data }) if portal_data.present?
202
+
203
+ response = DatabaseRequest.make_request("/layouts/#{layout_name}/records", 'post', body.to_json)
204
+
205
+ return throw_field_missing(field_data) if response['messages'][0]['code'] == '102'
206
+
207
+ return Error.throw_error(response['messages'][0]['code']) if response['messages'][0]['code'] != '0'
208
+
209
+ new_record = DatabaseRequest.make_request("/layouts/#{layout_name}/records/#{response['response']['recordId']}", 'get', '{}')
210
+ record = build_result(new_record['response']['data'][0])
211
+ record.send(:define_singleton_method, 'result_count') { 1 }
212
+ record
213
+ end
214
+
215
+ ##
216
+ # Finds and returns the first Record containing fitting the find request
217
+ #
218
+ # @param [Hash] fieldData: the data to find
219
+ #
220
+ # @return [Record] a Record object that correspond to FileMaker record fitting the find request
221
+ # Model.find_by(fieldOne: "Data")
222
+ def self.find_by(field_data)
223
+ url = "layouts/#{layout_name}/_find?_limit=1"
224
+ create_translations if @configuration.translations.keys.empty?
225
+
226
+ field_data.transform_keys! { |k| (@configuration.translations[k.to_s] || k).to_s }
227
+
228
+ body = { query: [field_data], limit: '1' }.to_json
229
+ response = DatabaseRequest.make_request(url, 'post', body)
230
+ code = response['messages'][0]['code']
231
+
232
+ return nil if %w[101 401].include?(code)
233
+
234
+ Error.throw_error(code) if code != '0'
235
+
236
+ r_results = response['response']['data']
237
+ build_result(r_results.first) if r_results.first.present?
238
+ end
239
+
240
+ ##
241
+ # Finds and returns a Record corresponding to the record_id
242
+ #
243
+ # @param [Integer] record_id: the record id to retrieve from FileMaker
244
+ #
245
+ # @return [Record] the record
246
+ def self.find(record_id)
247
+ create_translations if @configuration.translations.keys.empty?
248
+
249
+ url = "layouts/#{layout_name}/records/#{record_id}"
250
+ response = DatabaseRequest.make_request(url, 'get', '{}')
251
+ if response['messages'][0]['code'] == '0'
252
+ ret_val = build_result(response['response']['data'][0])
253
+ ret_val.send(:define_singleton_method, 'result_count') { 1 }
254
+ ret_val
255
+ else
256
+ Error.throw_error(response['messages'][0]['code'], record_id)
257
+ end
258
+ end
259
+
260
+ ##
261
+ # Deletes a record from FileMaker
262
+ #
263
+ # @param [Integer] record_id: the record id to retrieve from FileMaker
264
+ #
265
+ # @return [Boolean] True if the delete was successful
266
+ def self.delete(record_id)
267
+ create_translations if @configuration.translations.keys.empty?
268
+
269
+ url = "layouts/#{layout_name}/records/#{record_id}"
270
+ response = DatabaseRequest.make_request(url, 'delete', '{}')
271
+ if response['messages'][0]['code'] == '0'
272
+ true
273
+ else
274
+ Error.throw_error(response['messages'][0]['code'])
275
+ end
276
+ end
277
+
278
+ ##
279
+ # Edits a record in FileMaker
280
+ #
281
+ # @param [Integer] record_id: the record id to edit in FileMaker
282
+ #
283
+ # @param [Hash] fieldData: A hash containing the fields to edit and the new data to fill them with
284
+ #
285
+ # @return [Boolean] True if the delete was successful
286
+ def self.edit(record_id, field_data)
287
+ url = "layouts/#{layout_name}/records/#{record_id}"
288
+ new_field_data = {}
289
+ create_translations if @configuration.translations.keys.empty?
290
+
291
+ field_data.each_key do |k|
292
+ field_name = (@configuration.translations[k.to_s] || k).to_s
293
+ new_field_data.merge!({ field_name => field_data[k] })
294
+ end
295
+
296
+ body = "{\"fieldData\": #{new_field_data.to_json}}"
297
+ response = DatabaseRequest.make_request(url, 'patch', body)
298
+ response['messages'][0]['code'] == '0' ? true : Error.throw_error(response['messages'][0]['code'])
299
+ end
300
+
301
+ ##
302
+ # Builds the resulting Record
303
+ #
304
+ # @param [JSON] result: the HTTP result from FileMaker
305
+ #
306
+ # @return [Record] A Record with singleton_methods for the fields where possible
307
+ def self.build_result(result)
308
+ record = Trophonius::Record.new(result, name)
309
+ record.layout_name = layout_name
310
+ record
311
+ end
312
+
313
+ ##
314
+ # Retrieve the first record from FileMaker from the context of the Model.
315
+ #
316
+ # @return [Record]: a Record corresponding to the FileMaker record.
317
+ def self.first
318
+ create_translations if @configuration.translations.keys.empty?
319
+ results = DatabaseRequest.retrieve_first(layout_name)
320
+ if results['messages'][0]['code'] == '0'
321
+ r_results = results['response']['data']
322
+ ret_val = r_results.empty? ? Trophonius::Record.new({}, name) : build_result(r_results[0])
323
+ ret_val.send(:define_singleton_method, 'result_count') { r_results.empty? ? 0 : 1 }
324
+ ret_val
325
+ else
326
+ Error.throw_error(results['messages'][0]['code'])
327
+ end
328
+ end
329
+
330
+ ##
331
+ # Runs a FileMaker script from the context of the Model.
332
+ #
333
+ # @param [String] script: the FileMaker script to run
334
+ #
335
+ # @param [String] scriptparameter: the parameter required by the FileMaker script
336
+ #
337
+ # @return [String]: string representing the script result returned by FileMaker
338
+ def self.run_script(script: '', scriptparameter: '')
339
+ create_translations if @configuration.translations.keys.empty?
340
+ result = DatabaseRequest.run_script(script, scriptparameter, layout_name)
341
+ if result['messages'][0]['code'] != '0'
342
+ Error.throw_error(result['messages'][0]['code'])
343
+ elsif result['response']['scriptResult'] == '403'
344
+ Error.throw_error(403)
345
+ else
346
+ result['response']['scriptResult']
347
+ end
348
+ end
349
+
350
+ ##
351
+ # Retrieve the first 10000000 records from FileMaker from the context of the Model.
352
+ #
353
+ # @param [Hash] sort: a hash containing the fields to sort by and the direction to sort in (optional)
354
+ #
355
+ # @return [RecordSet]: a RecordSet containing all the Record objects that correspond to the FileMaker records.
356
+ def self.all(sort: {})
357
+ create_translations if @configuration.translations.keys.empty?
358
+ path = "/layouts/#{layout_name}/records?"
359
+ path += @limit.present? ? "_limit=#{@limit}" : '_limit=10000000'
360
+ path += "&_offset=#{@offset}" if @offset.present?
361
+ sort = sort.map { |k, v| { fieldName: k, sortOrder: v } }
362
+ path += "&_sort=#{sort.to_json}" unless sort.blank?
363
+
364
+ @limit = ''
365
+ @offset = ''
366
+ results = DatabaseRequest.make_request(path, 'get', '{}')
367
+ if results['messages'][0]['code'] == '0'
368
+ r_results = results['response']['data']
369
+ ret_val = RecordSet.new(layout_name, non_modifiable_fields)
370
+ r_results.each do |r|
371
+ hash = build_result(r)
372
+ ret_val << hash
373
+ end
374
+ ret_val.result_count = count
375
+ ret_val
376
+ else
377
+ Error.throw_error(results['messages'][0]['code'])
378
+ end
379
+ end
380
+
381
+ private
382
+
383
+ def throw_field_missing(field_data)
384
+ results = DatabaseRequest.retrieve_first(layout_name)
385
+ if results['messages'][0]['code'] == '0' && !results['response']['data'].empty?
386
+ r_results = results['response']['data']
387
+ ret_val = r_results[0]['fieldData']
388
+ Error.throw_error('102', (field_data.keys.map(&:downcase) - ret_val.keys.map(&:downcase)).flatten.join(', '), layout_name)
389
+ else
390
+ Error.throw_error('102')
391
+ end
392
+ end
393
+ end
394
+ end
@@ -1,11 +1,11 @@
1
1
  require 'json'
2
- require 'trophonius_config'
3
- require 'trophonius_record'
4
- require 'trophonius_recordset'
5
- require 'trophonius_error'
2
+ require 'config'
3
+ require 'record'
4
+ require 'recordset'
5
+ require 'error'
6
6
 
7
7
  module Trophonius
8
- class Trophonius::Query
8
+ class Query
9
9
  attr_reader :response
10
10
  attr_accessor :presort_script, :presort_scriptparam, :prerequest_script, :prerequest_scriptparam, :post_request_script, :post_request_scriptparam
11
11
 
@@ -53,7 +53,7 @@ module Trophonius
53
53
  # Returns the current portal limits
54
54
  #
55
55
  # @return [Hash] Hash representing the portal limits
56
- def build_portal_limits()
56
+ def build_portal_limits
57
57
  @portal_limits ||= {}
58
58
  end
59
59
 
@@ -153,7 +153,7 @@ module Trophonius
153
153
  # @param [args] arguments containing the limit and offset
154
154
  # @return [Trophonius::Model] updated base model
155
155
  def paginate(args)
156
- @offset = ((args[0] * args[1] - args[1]) + 1)
156
+ @offset = (((args[0] * args[1]) - args[1]) + 1)
157
157
  @limit = args[1]
158
158
  args[2]
159
159
  end
@@ -166,11 +166,11 @@ module Trophonius
166
166
  def sort(args)
167
167
  @trophonius_model.create_translations if @trophonius_model.translations.keys.empty?
168
168
  args[0].each do |key, value|
169
- if @trophonius_model.translations.key?(key.to_s)
170
- args[1].current_query.build_sort << { fieldName: "#{@trophonius_model.translations[key.to_s]}", sortOrder: "#{value}" }
171
- else
172
- args[1].current_query.build_sort << { fieldName: "#{key}", sortOrder: "#{value}" }
173
- end
169
+ args[1].current_query.build_sort << if @trophonius_model.translations.key?(key.to_s)
170
+ { fieldName: @trophonius_model.translations[key.to_s], sortOrder: value }
171
+ else
172
+ { fieldName: key, sortOrder: value }
173
+ end
174
174
  end
175
175
  args[1]
176
176
  end
@@ -184,20 +184,12 @@ module Trophonius
184
184
  #
185
185
  # @return Response of the called method
186
186
  def run_query(method, *args, &block)
187
- uri = URI::RFC2396_Parser.new
188
- url =
189
- URI(
190
- uri.escape(
191
- "http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{
192
- Trophonius.config.database
193
- }/layouts/#{@trophonius_model.layout_name}/_find"
194
- )
195
- )
187
+ url = "layouts/#{@trophonius_model.layout_name}/_find"
196
188
  new_field_data = @current_query.map { |_q| {} }
197
189
 
198
190
  @trophonius_model.create_translations if @trophonius_model.translations.keys.empty?
199
191
  @current_query.each_with_index do |query, index|
200
- query.keys.each do |k|
192
+ query.each_key do |k|
201
193
  if @trophonius_model.translations.key?(k.to_s)
202
194
  new_field_data[index].merge!(@trophonius_model.translations[k.to_s].to_s => query[k].to_s)
203
195
  else
@@ -205,16 +197,13 @@ module Trophonius
205
197
  end
206
198
  end
207
199
  end
208
- if @offset.nil? || @limit.nil? || @offset == '' || @limit == '' || @offset == 0 || @limit == 0
209
- body = @current_sort.nil? ? { query: new_field_data, limit: '100000' } : { query: new_field_data, sort: @current_sort, limit: '100000' }
210
- else
211
- body =
212
- if @current_sort.nil?
213
- { query: new_field_data, limit: @limit.to_s, offset: @offset.to_s }
214
- else
215
- { query: new_field_data, sort: @current_sort, limit: @limit.to_s, offset: @offset.to_s }
216
- end
217
- end
200
+ body = if @offset.nil? || @limit.nil? || @offset == '' || @limit == '' || @offset == 0 || @limit == 0
201
+ @current_sort.nil? ? { query: new_field_data, limit: '100000' } : { query: new_field_data, sort: @current_sort, limit: '100000' }
202
+ elsif @current_sort.nil?
203
+ { query: new_field_data, limit: @limit.to_s, offset: @offset.to_s }
204
+ else
205
+ { query: new_field_data, sort: @current_sort, limit: @limit.to_s, offset: @offset.to_s }
206
+ end
218
207
 
219
208
  if @post_request_script.present?
220
209
  body.merge!(script: @post_request_script)
@@ -222,43 +211,25 @@ module Trophonius
222
211
  end
223
212
 
224
213
  if @prerequest_script.present?
225
- body.merge!("script.prerequest" => @prerequest_script)
214
+ body.merge!('script.prerequest' => @prerequest_script)
226
215
  body.merge!('script.prerequest.param' => @prerequest_scriptparam) if @prerequest_scriptparam.present?
227
216
  end
228
217
 
229
218
  if @presort_script.present?
230
- body.merge!("script.presort" => @presort_script)
219
+ body.merge!('script.presort' => @presort_script)
231
220
  body.merge!('script.presort.param' => @presort_scriptparam) if @presort_scriptparam.present?
232
221
  end
233
222
 
234
223
  if @portal_limits
235
- portal_hash = { portal: @portal_limits.map { |portal_name, limit| "#{portal_name}" } }
224
+ portal_hash = { portal: @portal_limits.map { |portal_name, _limit| portal_name } }
236
225
  body.merge!(portal_hash)
237
226
  @portal_limits.each { |portal_name, limit| body.merge!({ "limit.#{portal_name}" => limit.to_s }) }
238
227
  end
239
228
 
240
229
  body = body.to_json
241
- response = Request.make_request(url, "Bearer #{Request.get_token}", 'post', body)
242
-
243
- if response['messages'][0]['code'] != '0'
244
- if response['messages'][0]['code'] == '101' || response['messages'][0]['code'] == '401'
245
- resp = RecordSet.new(@trophonius_model.layout_name, @trophonius_model.non_modifiable_fields).send(method, *args, &block)
246
- return resp
247
- else
248
- if response['messages'][0]['code'] == '102'
249
- results = Request.retrieve_first(@trophonius_model.layout_name)
250
- if results['messages'][0]['code'] != '0'
251
- Error.throw_error('102')
252
- else
253
- r_results = results['response']['data']
254
- ret_val = r_results.empty? ? Error.throw_error('102') : r_results[0]['fieldData']
255
- query_keys = new_field_data.map { |q| q.keys.map(&:downcase) }.uniq
256
- Error.throw_error('102', (query_keys - ret_val.keys.map(&:downcase)).flatten.join(', '), @trophonius_model.layout_name)
257
- end
258
- end
259
- Error.throw_error(response['messages'][0]['code'])
260
- end
261
- else
230
+ response = DatabaseRequest.make_request(url, 'post', body)
231
+
232
+ if response['messages'][0]['code'] == '0'
262
233
  r_results = response['response']['data']
263
234
  ret_val = RecordSet.new(@trophonius_model.layout_name, @trophonius_model.non_modifiable_fields)
264
235
 
@@ -285,7 +256,23 @@ module Trophonius
285
256
  ret_val << hash
286
257
  end
287
258
  @response = ret_val
288
- return @response.send(method, *args, &block)
259
+ @response.send(method, *args, &block)
260
+ elsif response['messages'][0]['code'] == '101' || response['messages'][0]['code'] == '401'
261
+ RecordSet.new(@trophonius_model.layout_name, @trophonius_model.non_modifiable_fields).send(method, *args, &block)
262
+
263
+ else
264
+ if response['messages'][0]['code'] == '102'
265
+ results = DatabaseRequest.retrieve_first(@trophonius_model.layout_name)
266
+ if results['messages'][0]['code'] == '0'
267
+ r_results = results['response']['data']
268
+ ret_val = r_results.empty? ? Error.throw_error('102') : r_results[0]['fieldData']
269
+ query_keys = new_field_data.map { |q| q.keys.map(&:downcase) }.uniq
270
+ Error.throw_error('102', (query_keys - ret_val.keys.map(&:downcase)).flatten.join(', '), @trophonius_model.layout_name)
271
+ else
272
+ Error.throw_error('102')
273
+ end
274
+ end
275
+ Error.throw_error(response['messages'][0]['code'])
289
276
  end
290
277
  end
291
278