trophonius 1.4.5.5 → 2.1

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