trophonius 1.0.0
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.
- checksums.yaml +7 -0
- data/lib/trophonius.rb +19 -0
- data/lib/trophonius_config.rb +18 -0
- data/lib/trophonius_connection.rb +90 -0
- data/lib/trophonius_error.rb +297 -0
- data/lib/trophonius_model.rb +251 -0
- data/lib/trophonius_record.rb +74 -0
- data/lib/trophonius_recordset.rb +58 -0
- data/lib/trophonius_request.rb +84 -0
- metadata +114 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5c744efd7c138bb2666a5fbe75aca5098a8503e06c235ed19014e175e331efbb
|
4
|
+
data.tar.gz: e58679a2dd7b909e5349b3493a58262c74f3a251de1ff7245d7491379606cae3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8fcf452c57e99c7bd282712c46d564d21801f383a9a4af39cfbb49c81077bd03133c738f7ce343afaed05efa1b13394f51a8f6afacf5c5880896b2c6735fad8b
|
7
|
+
data.tar.gz: 9dfe62d1a27018d9d2cf9f706af9333f84ff92680a68d8afd891d3c572ee1b0ea475679d73a8f85a6a3aab170ffdd6850acf8f06e0ac98246b57ce950aaaeaf9
|
data/lib/trophonius.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'trophonius_request'
|
2
|
+
require 'trophonius_model'
|
3
|
+
require 'trophonius_config'
|
4
|
+
|
5
|
+
module Trophonius # :nodoc:
|
6
|
+
def self.configuration
|
7
|
+
@configuration ||= Configuration.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.configure
|
11
|
+
yield configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.config
|
15
|
+
@configuration
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'active_support/configurable'
|
2
|
+
|
3
|
+
module Trophonius
|
4
|
+
class Trophonius::Configuration # :nodoc:
|
5
|
+
include ActiveSupport::Configurable
|
6
|
+
|
7
|
+
config_accessor(:host) { '127.0.0.1' }
|
8
|
+
config_accessor(:port) { 0 }
|
9
|
+
config_accessor(:database) { '' }
|
10
|
+
config_accessor(:username) { 'Admin' }
|
11
|
+
config_accessor(:password) { '' }
|
12
|
+
config_accessor(:ssl) { true }
|
13
|
+
config_accessor(:count_result_script) { '' }
|
14
|
+
config_accessor(:layout_name) { '' }
|
15
|
+
config_accessor(:non_modifiable_fields) { [] }
|
16
|
+
config_accessor(:local_network) { false }
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'typhoeus'
|
3
|
+
|
4
|
+
module Trophonius
|
5
|
+
module Trophonius::Connection
|
6
|
+
##
|
7
|
+
# Creates a new connection to FileMaker
|
8
|
+
#
|
9
|
+
# @return [String] the *token* used to connect with the FileMaker data api
|
10
|
+
|
11
|
+
def self.connect
|
12
|
+
@token = setup_connection
|
13
|
+
@last_connection = Time.now
|
14
|
+
@token
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Creates and runs a HTTP request to create a new data api connection
|
19
|
+
# This method throws an error when the request returns with a HTTP error or a FileMaker error
|
20
|
+
# @return [String] the *token* used to connect with the FileMaker data api if successful
|
21
|
+
|
22
|
+
def self.setup_connection
|
23
|
+
@token = ''
|
24
|
+
ssl_verifyhost = Trophonius.config.local_network ? 0 : 2
|
25
|
+
ssl_verifypeer = !Trophonius.config.local_network
|
26
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/sessions")
|
27
|
+
request = Typhoeus::Request.new(
|
28
|
+
url,
|
29
|
+
method: :post,
|
30
|
+
body: {},
|
31
|
+
params: {},
|
32
|
+
ssl_verifyhost: ssl_verifyhost,
|
33
|
+
ssl_verifypeer: ssl_verifypeer,
|
34
|
+
headers: { 'Content-Type' => 'application/json', Authorization: "Basic #{Base64.strict_encode64("#{Trophonius.config.username}:#{Trophonius.config.password}")}" }
|
35
|
+
)
|
36
|
+
temp = request.run
|
37
|
+
|
38
|
+
begin
|
39
|
+
parsed = JSON.parse(temp.response_body)
|
40
|
+
if parsed['messages'][0]['code'] != '0'
|
41
|
+
Error.throw_error(response['messages'][0]['code'])
|
42
|
+
end
|
43
|
+
return parsed['response']['token']
|
44
|
+
rescue Exception
|
45
|
+
Error.throw_error('1631')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
##
|
50
|
+
# Returns the last received token
|
51
|
+
# @return [String] the last valid *token* used to connect with the FileMaker data api
|
52
|
+
def self.token
|
53
|
+
@token
|
54
|
+
end
|
55
|
+
|
56
|
+
##
|
57
|
+
# Returns the receive time of the last received token
|
58
|
+
# @return [Time] Returns the receive time of the last received token
|
59
|
+
def self.last_connection
|
60
|
+
@last_connection
|
61
|
+
end
|
62
|
+
|
63
|
+
##
|
64
|
+
# Tests whether the FileMaker token is still valid
|
65
|
+
# @return [Boolean] True if the token is valid False if invalid
|
66
|
+
def self.test_connection
|
67
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{Trophonius.config.layout_name}/records?_limit=1")
|
68
|
+
begin
|
69
|
+
request = Typhoeus::Request.new(
|
70
|
+
url,
|
71
|
+
method: :get,
|
72
|
+
body: {},
|
73
|
+
params: {},
|
74
|
+
headers: { 'Content-Type' => 'application/json', Authorization: "Bearer #{@token}" }
|
75
|
+
)
|
76
|
+
temp = request.run
|
77
|
+
JSON.parse(temp.response_body)['messages'][0]['code'] == '0'
|
78
|
+
rescue StandardError
|
79
|
+
return false
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# Returns whether the current connection is still valid
|
85
|
+
# @return [Boolean] True if the connection is valid False if invalid
|
86
|
+
def self.valid_connection?
|
87
|
+
@last_connection.nil? ? false : (((Time.now - last_connection) / 60).round <= 15 || test_connection)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,297 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'trophonius_config'
|
3
|
+
|
4
|
+
module Trophonius
|
5
|
+
module Trophonius::Error
|
6
|
+
class RecordNotFoundError < StandardError; end # :nodoc:
|
7
|
+
class FieldUnexistingError < NoMethodError; end # :nodoc:
|
8
|
+
class ScriptUnexistingError < NoMethodError; end # :nodoc:
|
9
|
+
class LayoutUnexistingError < NoMethodError; end # :nodoc:
|
10
|
+
class InvalidTokenError < StandardError; end # :nodoc:
|
11
|
+
class UnauthenticatedError < StandardError; end # :nodoc:
|
12
|
+
class FieldNotModifiableError < StandardError; end # :nodoc:
|
13
|
+
class ResponseNotYetImplementedError < StandardError; end # :nodoc:
|
14
|
+
class UnknownFileMakerError < StandardError; end # :nodoc:
|
15
|
+
class UserCanceledError < StandardError; end # :nodoc:
|
16
|
+
class MemoryError < StandardError; end # :nodoc:
|
17
|
+
class FileError < StandardError; end # :nodoc:
|
18
|
+
class CommandError < StandardError; end # :nodoc:
|
19
|
+
class ConnectionError < StandardError; end # :nodoc:
|
20
|
+
|
21
|
+
##
|
22
|
+
# Throws an error corresponding to the error number
|
23
|
+
# :args: error_id, more_info
|
24
|
+
def self.throw_error(error_id, more_info = 0)
|
25
|
+
case error_id
|
26
|
+
when '-1'
|
27
|
+
raise UnknownFileMakerError.new, 'Unknown Error Ocurred'
|
28
|
+
when '0'
|
29
|
+
raise UnknownFileMakerError.new, 'Unknown Error Ocurred'
|
30
|
+
when '1'
|
31
|
+
raise UserCanceledError.new, 'An outside source canceled the action'
|
32
|
+
when '2'
|
33
|
+
raise MemoryError.new, 'FileMaker encountered a memory error'
|
34
|
+
when '3'
|
35
|
+
raise CommandError.new, 'Command is unavailable (for example, wrong operating system or mode)'
|
36
|
+
when '4'
|
37
|
+
raise CommandError.new, 'Command is unknown'
|
38
|
+
when '5'
|
39
|
+
raise CommandError.new, 'Command is invalid, check your FileMaker script/calculation'
|
40
|
+
when '6'
|
41
|
+
raise FileError.new, 'File is read-only'
|
42
|
+
when '7'
|
43
|
+
raise MemoryError.new, 'FileMaker is running out of memory'
|
44
|
+
when '8'
|
45
|
+
raise RecordNotFoundError.new, 'Empty result'
|
46
|
+
when '9'
|
47
|
+
raise UnauthenticatedError.new, 'User has insufficient privileges'
|
48
|
+
# when "10"
|
49
|
+
# when "11"
|
50
|
+
# when "12"
|
51
|
+
# when "13"
|
52
|
+
# when "14"
|
53
|
+
# when "15"
|
54
|
+
# when "16"
|
55
|
+
# when "17"
|
56
|
+
# when "16"
|
57
|
+
# when "18"
|
58
|
+
# when "19"
|
59
|
+
# when "09"
|
60
|
+
# when "20"
|
61
|
+
# when "21"
|
62
|
+
# when "100"
|
63
|
+
when '101'
|
64
|
+
raise RecordNotFoundError.new, "Record #{more_info} was not found"
|
65
|
+
when '102'
|
66
|
+
raise FieldUnexistingError.new, 'Field does not exist'
|
67
|
+
# when "103"
|
68
|
+
when '104'
|
69
|
+
raise ScriptUnexistingError.new, 'Script does not exist'
|
70
|
+
when '105'
|
71
|
+
raise LayoutUnexistingError.new, 'Layout does not exist'
|
72
|
+
# when "106"
|
73
|
+
# when "107"
|
74
|
+
# when "108"
|
75
|
+
# when "109"
|
76
|
+
# when "110"
|
77
|
+
# when "111"
|
78
|
+
# when "112"
|
79
|
+
# when "113"
|
80
|
+
# when "114"
|
81
|
+
# when "115"
|
82
|
+
# when "116"
|
83
|
+
# when "117"
|
84
|
+
# when "118"
|
85
|
+
# when "130"
|
86
|
+
# when "131"
|
87
|
+
# when "200"
|
88
|
+
when '201'
|
89
|
+
raise FieldNotModifiableError.new, 'Trying to write to a read-only field'
|
90
|
+
# when "202"
|
91
|
+
# when "203"
|
92
|
+
# when "204"
|
93
|
+
# when "205"
|
94
|
+
# when "206"
|
95
|
+
# when "207"
|
96
|
+
# when "208"
|
97
|
+
# when "209"
|
98
|
+
# when "210"
|
99
|
+
# when "211"
|
100
|
+
# when "212"
|
101
|
+
# when "213"
|
102
|
+
# when "214"
|
103
|
+
# when "215"
|
104
|
+
# when "216"
|
105
|
+
# when "217"
|
106
|
+
# when "218"
|
107
|
+
# when "300"
|
108
|
+
# when "301"
|
109
|
+
# when "302"
|
110
|
+
# when "303"
|
111
|
+
# when "304"
|
112
|
+
# when "306"
|
113
|
+
# when "307"
|
114
|
+
# when "308"
|
115
|
+
# when "400"
|
116
|
+
when '401'
|
117
|
+
raise RecordNotFoundError.new, "Record #{more_info} was not found"
|
118
|
+
# when "402"
|
119
|
+
when '403'
|
120
|
+
raise UnauthenticatedError.new, 'You are unauthenticated to perform this request'
|
121
|
+
# when "404"
|
122
|
+
# when "405"
|
123
|
+
# when "406"
|
124
|
+
# when "407"
|
125
|
+
# when "408"
|
126
|
+
# when "409"
|
127
|
+
# when "410"
|
128
|
+
# when "412"
|
129
|
+
# when "413"
|
130
|
+
# when "414"
|
131
|
+
# when "415"
|
132
|
+
# when "416"
|
133
|
+
# when "417"
|
134
|
+
# when "418"
|
135
|
+
# when "500"
|
136
|
+
# when "501"
|
137
|
+
# when "502"
|
138
|
+
# when "503"
|
139
|
+
# when "504"
|
140
|
+
# when "505"
|
141
|
+
# when "506"
|
142
|
+
# when "507"
|
143
|
+
# when "508"
|
144
|
+
# when "509"
|
145
|
+
# when "510"
|
146
|
+
# when "511"
|
147
|
+
# when "512"
|
148
|
+
# when "513"
|
149
|
+
# when "600"
|
150
|
+
# when "601"
|
151
|
+
# when "602"
|
152
|
+
# when "603"
|
153
|
+
# when "700"
|
154
|
+
# when "706"
|
155
|
+
# when "707"
|
156
|
+
# when "708"
|
157
|
+
# when "711"
|
158
|
+
# when "714"
|
159
|
+
# when "715"
|
160
|
+
# when "716"
|
161
|
+
# when "717"
|
162
|
+
# when "718"
|
163
|
+
# when "719"
|
164
|
+
# when "720"
|
165
|
+
# when "721"
|
166
|
+
# when "722"
|
167
|
+
# when "723"
|
168
|
+
# when "724"
|
169
|
+
# when "725"
|
170
|
+
# when "726"
|
171
|
+
# when "727"
|
172
|
+
# when "729"
|
173
|
+
# when "730"
|
174
|
+
# when "731"
|
175
|
+
# when "732"
|
176
|
+
# when "733"
|
177
|
+
# when "734"
|
178
|
+
# when "735"
|
179
|
+
# when "736"
|
180
|
+
# when "738"
|
181
|
+
# when "800"
|
182
|
+
# when "801"
|
183
|
+
# when "802"
|
184
|
+
# when "803"
|
185
|
+
# when "804"
|
186
|
+
# when "805"
|
187
|
+
# when "806"
|
188
|
+
# when "807"
|
189
|
+
# when "808"
|
190
|
+
# when "809"
|
191
|
+
# when "810"
|
192
|
+
# when "811"
|
193
|
+
# when "812"
|
194
|
+
# when "813"
|
195
|
+
# when "814"
|
196
|
+
# when "815"
|
197
|
+
# when "816"
|
198
|
+
# when "817"
|
199
|
+
# when "819"
|
200
|
+
# when "820"
|
201
|
+
# when "821"
|
202
|
+
# when "822"
|
203
|
+
# when "823"
|
204
|
+
# when "824"
|
205
|
+
# when "825"
|
206
|
+
# when "826"
|
207
|
+
# when "827"
|
208
|
+
# when "850"
|
209
|
+
# when "851"
|
210
|
+
# when "852"
|
211
|
+
# when "853"
|
212
|
+
# when "900"
|
213
|
+
# when "901"
|
214
|
+
# when "902"
|
215
|
+
# when "903"
|
216
|
+
# when "905"
|
217
|
+
# when "906"
|
218
|
+
# when "920"
|
219
|
+
# when "921"
|
220
|
+
# when "922"
|
221
|
+
# when "923"
|
222
|
+
# when "951"
|
223
|
+
when '952'
|
224
|
+
raise InvalidTokenError.new, 'Could not retrieve a valid token from FileMaker, check your FileMaker server'
|
225
|
+
# when "954"
|
226
|
+
# when "955"
|
227
|
+
# when "956"
|
228
|
+
# when "957"
|
229
|
+
# when "958"
|
230
|
+
# when "959"
|
231
|
+
# when "960"
|
232
|
+
# when "1200"
|
233
|
+
# when "1201"
|
234
|
+
# when "1202"
|
235
|
+
# when "1203"
|
236
|
+
# when "1204"
|
237
|
+
# when "1205"
|
238
|
+
# when "1206"
|
239
|
+
# when "1207"
|
240
|
+
# when "1208"
|
241
|
+
# when "1209"
|
242
|
+
# when "1210"
|
243
|
+
# when "1211"
|
244
|
+
# when "1212"
|
245
|
+
# when "1213"
|
246
|
+
# when "1214"
|
247
|
+
# when "1215"
|
248
|
+
# when "1216"
|
249
|
+
# when "1217"
|
250
|
+
# when "1218"
|
251
|
+
# when "1219"
|
252
|
+
# when "1220"
|
253
|
+
# when "1221"
|
254
|
+
# when "1222"
|
255
|
+
# when "1223"
|
256
|
+
# when "1224"
|
257
|
+
# when "1225"
|
258
|
+
# when "1300"
|
259
|
+
# when "1301"
|
260
|
+
# when "1400"
|
261
|
+
# when "1401"
|
262
|
+
# when "1402"
|
263
|
+
# when "1403"
|
264
|
+
# when "1404"
|
265
|
+
# when "1405"
|
266
|
+
# when "1406"
|
267
|
+
# when "1407"
|
268
|
+
# when "1408"
|
269
|
+
# when "1409"
|
270
|
+
# when "1413"
|
271
|
+
# when "1414"
|
272
|
+
# when "1450"
|
273
|
+
# when "1451"
|
274
|
+
# when "1501"
|
275
|
+
# when "1502"
|
276
|
+
# when "1503"
|
277
|
+
# when "1504"
|
278
|
+
# when "1505"
|
279
|
+
# when "1506"
|
280
|
+
# when "1507"
|
281
|
+
# when "1550"
|
282
|
+
# when "1551"
|
283
|
+
# when "1626"
|
284
|
+
# when "1627"
|
285
|
+
# when "1628"
|
286
|
+
# when "1629"
|
287
|
+
# when "1630"
|
288
|
+
when '1631'
|
289
|
+
raise ConnectionError.new, 'An error occurred while attempting to connect to the FileMaker server'
|
290
|
+
# when "1632"
|
291
|
+
# when "1633"
|
292
|
+
else
|
293
|
+
raise ResponseNotYetImplementedError.new, "An unknown error has been encountered: err_no was #{error_id}"
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
@@ -0,0 +1,251 @@
|
|
1
|
+
require "json"
|
2
|
+
require "trophonius_config"
|
3
|
+
require "trophonius_record"
|
4
|
+
require "trophonius_recordset"
|
5
|
+
require "trophonius_error"
|
6
|
+
|
7
|
+
module Trophonius
|
8
|
+
# This class will retrieve the records from the FileMaker database and build a RecordSet filled with Record objects. One Record object represents a record in FileMaker.
|
9
|
+
class Trophonius::Model
|
10
|
+
# Contains all the fields on the model (modifiable and non_modifiable)
|
11
|
+
attr_reader :all_fields
|
12
|
+
|
13
|
+
##
|
14
|
+
# Sets up the configuration for the model.
|
15
|
+
#
|
16
|
+
# @param [Hash] configuration: the hash containing the config to setup the model correctly.
|
17
|
+
# configuration = {layout_name: "theFileMakerLayoutForThisModel", non_modifiable_fields: ["an", "array", "containing", "calculation_fields", "etc."]}
|
18
|
+
def self.config(configuration)
|
19
|
+
@configuration ||= Configuration.new
|
20
|
+
@configuration.layout_name = configuration[:layout_name]
|
21
|
+
@configuration.non_modifiable_fields = configuration[:non_modifiable_fields]
|
22
|
+
@all_fields = {}
|
23
|
+
end
|
24
|
+
|
25
|
+
##
|
26
|
+
# Returns the FileMaker layout this Model corresponds to
|
27
|
+
def self.layout_name
|
28
|
+
@configuration.layout_name
|
29
|
+
end
|
30
|
+
|
31
|
+
##
|
32
|
+
# Returns the fields that FileMaker won't allow us to modify
|
33
|
+
def self.non_modifiable_fields
|
34
|
+
@configuration.non_modifiable_fields
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Creates and saves a record in FileMaker
|
39
|
+
#
|
40
|
+
# @param [Hash] fieldData: the fields to fill with the data
|
41
|
+
#
|
42
|
+
# @return [Record] the created record
|
43
|
+
# Model.create(fieldOne: "Data")
|
44
|
+
def self.create(fieldData)
|
45
|
+
url = URI("http#{Trophonius.config.ssl == true ? "s" : ""}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records")
|
46
|
+
body = "{\"fieldData\": #{fieldData.to_json}}"
|
47
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", "post", body)
|
48
|
+
if response["messages"][0]["code"] != "0"
|
49
|
+
Error.throw_error(response["messages"][0]["code"])
|
50
|
+
else
|
51
|
+
url = URI("http#{Trophonius.config.ssl == true ? "s" : ""}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records/#{response["response"]["recordId"]}")
|
52
|
+
ret_val = build_result(Request.make_request(url, "Bearer #{Request.get_token}", "get", "{}")["response"]["data"][0])
|
53
|
+
ret_val.send(:define_singleton_method, "result_count") do
|
54
|
+
1
|
55
|
+
end
|
56
|
+
return ret_val
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
##
|
61
|
+
# Finds and returns a RecordSet containing the records fitting the find request
|
62
|
+
#
|
63
|
+
# @param [Hash] fieldData: the data to find
|
64
|
+
#
|
65
|
+
# @return [RecordSet] a RecordSet containing all the Record objects that correspond to FileMaker records fitting the find request
|
66
|
+
# Model.where(fieldOne: "Data")
|
67
|
+
def self.where(fieldData)
|
68
|
+
url = URI("http#{Trophonius.config.ssl == true ? "s" : ""}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{self.layout_name}/_find")
|
69
|
+
body = "{\"query\": [#{fieldData.to_json}]}"
|
70
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", "post", body)
|
71
|
+
if response["messages"][0]["code"] != "0"
|
72
|
+
return RecordSet.new(self.layout_name, self.non_modifiable_fields) if response["messages"][0]["code"] == "101" || response["messages"][0]["code"] == "401"
|
73
|
+
Error.throw_error(response["messages"][0]["code"])
|
74
|
+
else
|
75
|
+
r_results = response["response"]["data"]
|
76
|
+
ret_val = RecordSet.new(self.layout_name, self.non_modifiable_fields)
|
77
|
+
r_results.each do |r|
|
78
|
+
hash = build_result(r)
|
79
|
+
ret_val << hash
|
80
|
+
end
|
81
|
+
return ret_val
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
##
|
86
|
+
# Finds and returns a Record corresponding to the record_id
|
87
|
+
#
|
88
|
+
# @param [Integer] record_id: the record id to retrieve from FileMaker
|
89
|
+
#
|
90
|
+
# @return [Record] the record
|
91
|
+
def self.find(record_id)
|
92
|
+
url = URI("http#{Trophonius.config.ssl == true ? "s" : ""}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records/#{record_id}")
|
93
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", "get", "{}")
|
94
|
+
if response["messages"][0]["code"] != "0"
|
95
|
+
Error.throw_error(response["messages"][0]["code"], record_id)
|
96
|
+
else
|
97
|
+
ret_val = build_result(response["response"]["data"][0])
|
98
|
+
ret_val.send(:define_singleton_method, "result_count") do
|
99
|
+
1
|
100
|
+
end
|
101
|
+
return ret_val
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Deletes a record from FileMaker
|
107
|
+
#
|
108
|
+
# @param [Integer] record_id: the record id to retrieve from FileMaker
|
109
|
+
#
|
110
|
+
# @return [Boolean] True if the delete was successful
|
111
|
+
def self.delete(record_id)
|
112
|
+
url = URI("http#{Trophonius.config.ssl == true ? "s" : ""}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records/#{record_id}")
|
113
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", "delete", "{}")
|
114
|
+
if response["messages"][0]["code"] != "0"
|
115
|
+
Error.throw_error(response["messages"][0]["code"])
|
116
|
+
else
|
117
|
+
return true
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
##
|
122
|
+
# Edits a record in FileMaker
|
123
|
+
#
|
124
|
+
# @param [Integer] record_id: the record id to edit in FileMaker
|
125
|
+
#
|
126
|
+
# @param [Hash] fieldData: A hash containing the fields to edit and the new data to fill them with
|
127
|
+
#
|
128
|
+
# @return [Boolean] True if the delete was successful
|
129
|
+
def self.edit(record_id, fieldData)
|
130
|
+
url = URI("http#{Trophonius.config.ssl == true ? "s" : ""}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records/#{record_id}")
|
131
|
+
body = "{\"fieldData\": #{fieldData.to_json}}"
|
132
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", "patch", body)
|
133
|
+
if response["messages"][0]["code"] != "0"
|
134
|
+
Error.throw_error(response["messages"][0]["code"])
|
135
|
+
else
|
136
|
+
true
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
##
|
141
|
+
# Builds the resulting Record
|
142
|
+
#
|
143
|
+
# @param [JSON] result: the HTTP result from FileMaker
|
144
|
+
#
|
145
|
+
# @return [Record] A Record with singleton_methods for the fields where possible
|
146
|
+
def self.build_result(result)
|
147
|
+
hash = Trophonius::Record.new()
|
148
|
+
hash.id = result["recordId"]
|
149
|
+
hash.layout_name = layout_name
|
150
|
+
result["fieldData"].keys.each do |key|
|
151
|
+
unless key[/\s/] || key[/\W/]
|
152
|
+
hash.send(:define_singleton_method, key.to_s) do
|
153
|
+
hash[key]
|
154
|
+
end
|
155
|
+
unless non_modifiable_fields&.include?(key)
|
156
|
+
@all_fields.merge!(key.to_s.downcase => key.to_s)
|
157
|
+
hash.send(:define_singleton_method, "#{key.to_s}=") do |new_val|
|
158
|
+
hash[key] = new_val
|
159
|
+
hash.modifiable_fields[key] = new_val
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
hash.merge!({key => result["fieldData"][key]})
|
164
|
+
unless non_modifiable_fields&.include?(key)
|
165
|
+
hash.modifiable_fields.merge!({key => result["fieldData"][key]})
|
166
|
+
end
|
167
|
+
end
|
168
|
+
result["portalData"].keys.each do |key|
|
169
|
+
unless key[/\s/] || key[/\W/]
|
170
|
+
hash.send(:define_singleton_method, key.to_s) do
|
171
|
+
hash[key]
|
172
|
+
end
|
173
|
+
end
|
174
|
+
result["portalData"][key].each_with_index do |inner_hash|
|
175
|
+
inner_hash.keys.each do |inner_key|
|
176
|
+
inner_method = inner_key.gsub(/\w+::/, "")
|
177
|
+
unless inner_method[/\s/] || inner_method[/\W/]
|
178
|
+
inner_hash.send(:define_singleton_method, inner_method.to_s) { inner_hash[inner_key] }
|
179
|
+
inner_hash.send(:define_singleton_method, "id") { inner_hash["recordId"] }
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
hash.merge!({key => result["portalData"][key]})
|
184
|
+
end
|
185
|
+
return hash
|
186
|
+
end
|
187
|
+
|
188
|
+
##
|
189
|
+
# Retrieve the first record from FileMaker from the context of the Model.
|
190
|
+
#
|
191
|
+
# @return [Record]: a Record corresponding to the FileMaker record.
|
192
|
+
def self.first
|
193
|
+
results = Request.retrieve_first(layout_name)
|
194
|
+
if results["messages"][0]["code"] != "0"
|
195
|
+
Error.throw_error(results["messages"][0]["code"])
|
196
|
+
else
|
197
|
+
r_results = results["response"]["data"]
|
198
|
+
ret_val = r_results.empty? ? Trophonius::Record.new({}) : build_result(r_results[0])
|
199
|
+
ret_val.send(:define_singleton_method, "result_count") do
|
200
|
+
r_results.empty? ? 0 : 1
|
201
|
+
end
|
202
|
+
return ret_val
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
##
|
207
|
+
# Runs a FileMaker script from the context of the Model.
|
208
|
+
#
|
209
|
+
# @param [String] script: the FileMaker script to run
|
210
|
+
#
|
211
|
+
# @param [String] scriptparameter: the parameter required by the FileMaker script
|
212
|
+
#
|
213
|
+
# @return [String]: string representing the script result returned by FileMaker
|
214
|
+
def self.run_script(script: "", scriptparameter: "")
|
215
|
+
result = Request.run_script(script, scriptparameter, layout_name)
|
216
|
+
if result["messages"][0]["code"] != "0"
|
217
|
+
Error.throw_error(result["messages"][0]["code"])
|
218
|
+
elsif result["response"]["scriptResult"] == "403"
|
219
|
+
Error.throw_error(403)
|
220
|
+
else
|
221
|
+
ret_val = result["response"]["scriptResult"]
|
222
|
+
return ret_val
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
##
|
227
|
+
# Retrieve the first 10000000 records from FileMaker from the context of the Model.
|
228
|
+
#
|
229
|
+
# @param [Hash] sort: a hash containing the fields to sort by and the direction to sort in (optional)
|
230
|
+
#
|
231
|
+
# @return [RecordSet]: a RecordSet containing all the Record objects that correspond to the FileMaker records.
|
232
|
+
def self.all(sort: {})
|
233
|
+
results = Request.retrieve_all(layout_name, sort)
|
234
|
+
count = results["response"]["scriptResult"].to_i
|
235
|
+
url = URI("http#{Trophonius.config.ssl == true ? "s" : ""}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records?_limit=#{count == 0 ? 1000000 : count}")
|
236
|
+
results = Request.make_request(url, "Bearer #{Request.get_token}", "get", "{}")
|
237
|
+
if results["messages"][0]["code"] != "0"
|
238
|
+
Error.throw_error(results["messages"][0]["code"])
|
239
|
+
else
|
240
|
+
r_results = results["response"]["data"]
|
241
|
+
ret_val = RecordSet.new(self.layout_name, self.non_modifiable_fields)
|
242
|
+
r_results.each do |r|
|
243
|
+
hash = build_result(r)
|
244
|
+
ret_val << hash
|
245
|
+
end
|
246
|
+
ret_val.result_count = count
|
247
|
+
return ret_val
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'trophonius_config'
|
3
|
+
|
4
|
+
module Trophonius
|
5
|
+
# This class will hold a singular record
|
6
|
+
#
|
7
|
+
# A Record is contained in a RecordSet and has methods to retrieve data from the fields inside the Record-hash
|
8
|
+
class Trophonius::Record < Hash
|
9
|
+
attr_accessor :id, :layout_name, :modifiable_fields
|
10
|
+
|
11
|
+
##
|
12
|
+
# Initializes a new Record
|
13
|
+
def initialize
|
14
|
+
@modifiable_fields = {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(field, new_val)
|
18
|
+
modifiable_fields[field] = new_val
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
##
|
23
|
+
# Saves the last changes made to the Record to FileMaker.
|
24
|
+
# Throws a FileMaker error if save failed
|
25
|
+
#
|
26
|
+
# @return [True] if successful
|
27
|
+
def save
|
28
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records/#{id}")
|
29
|
+
body = "{\"fieldData\": #{modifiable_fields.to_json}}"
|
30
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", 'patch', body)
|
31
|
+
if response['messages'][0]['code'] != '0'
|
32
|
+
Error.throw_error(response['messages'][0]['code'])
|
33
|
+
else
|
34
|
+
return true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Deletes the corresponding record from FileMaker
|
40
|
+
# Throws a FileMaker error if save failed
|
41
|
+
#
|
42
|
+
# @return [True] if successful
|
43
|
+
def delete
|
44
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records/#{id}")
|
45
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", 'delete', '{}')
|
46
|
+
if response['messages'][0]['code'] != '0'
|
47
|
+
Error.throw_error(response['messages'][0]['code'])
|
48
|
+
else
|
49
|
+
return true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
##
|
54
|
+
# Changes and saves the corresponding record in FileMaker
|
55
|
+
# Throws a FileMaker error if save failed
|
56
|
+
#
|
57
|
+
# @param [Hash] fieldData: Fields to be changed and data to fill the fields with
|
58
|
+
#
|
59
|
+
# @return [True] if successful
|
60
|
+
def update(fieldData)
|
61
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records/#{id}")
|
62
|
+
fieldData.keys.each do |field|
|
63
|
+
modifiable_fields[field] = fieldData[field]
|
64
|
+
end
|
65
|
+
body = "{\"fieldData\": #{fieldData.to_json}}"
|
66
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", 'patch', body)
|
67
|
+
if response['messages'][0]['code'] != '0'
|
68
|
+
Error.throw_error(response['messages'][0]['code'])
|
69
|
+
else
|
70
|
+
return true
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'trophonius_config'
|
3
|
+
require 'trophonius_model'
|
4
|
+
require 'trophonius_connection'
|
5
|
+
|
6
|
+
module Trophonius
|
7
|
+
# A RecordSet contains all records, as Record, retrieved from the FileMaker database
|
8
|
+
class Trophonius::RecordSet < Array
|
9
|
+
attr_accessor :result_count, :layout_name, :non_modifiable_fields, :records
|
10
|
+
|
11
|
+
class EmptyParameterError < ArgumentError; end # :nodoc:
|
12
|
+
|
13
|
+
##
|
14
|
+
# Initializes a new RecordSet
|
15
|
+
#
|
16
|
+
# @param [String] l_name: name of the FileMaker layout
|
17
|
+
#
|
18
|
+
# @param [Array] nmf: names of the fields that cannot be modified (calculation fields etc.)
|
19
|
+
def initialize(l_name, nmf)
|
20
|
+
self.layout_name = l_name
|
21
|
+
self.non_modifiable_fields = nmf
|
22
|
+
self.records = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def <<(data)
|
26
|
+
records << data
|
27
|
+
super
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# This method allows to chain where statements
|
32
|
+
#
|
33
|
+
# @param [Hash] fielddata: hash containing the query
|
34
|
+
#
|
35
|
+
# @return [RecordSet] the records where the statement holds
|
36
|
+
def where(fielddata)
|
37
|
+
raise EmptyParameterError.new, 'No requested data to find' if fielddata.nil? || fielddata.empty?
|
38
|
+
|
39
|
+
temp = Trophonius::Model
|
40
|
+
temp.config layout_name: layout_name, non_modifiable_fields: non_modifiable_fields
|
41
|
+
retval = temp.where(fielddata)
|
42
|
+
retval
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# This method chops the RecordSet up in parts.
|
47
|
+
#
|
48
|
+
# @param [Integer] page: the current page
|
49
|
+
#
|
50
|
+
# @param [Integer] records_per_page: the amount of records on the page
|
51
|
+
#
|
52
|
+
# @return [RecordSet] the records in the range ((page * records_per_page) - records_per_page) + 1 until ((page * records_per_page) - records_per_page) + 1 + records_per_page
|
53
|
+
def paginate(page, records_per_page)
|
54
|
+
offset = ((page * records_per_page) - records_per_page) + 1
|
55
|
+
records[offset...offset + records_per_page]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'trophonius_connection'
|
3
|
+
|
4
|
+
module Trophonius
|
5
|
+
module Trophonius::Request
|
6
|
+
##
|
7
|
+
# Crafts and runs a HTTP request of any type
|
8
|
+
#
|
9
|
+
# @param [URI] urlparam: the url to make the request to
|
10
|
+
#
|
11
|
+
# @param [String] auth: the authentication required for the request
|
12
|
+
#
|
13
|
+
# @param [String] method: the type of HTTP request to make (i.e. get)
|
14
|
+
#
|
15
|
+
# @param [JSONString] body: the body of the HTTP request
|
16
|
+
#
|
17
|
+
# @param [String] params: optional parameters added to the request
|
18
|
+
#
|
19
|
+
# @return [JSON] parsed json of the response
|
20
|
+
def self.make_request(url_param, auth, method, body, params = '')
|
21
|
+
ssl_verifyhost = Trophonius.config.local_network ? 0 : 2
|
22
|
+
ssl_verifypeer = !Trophonius.config.local_network
|
23
|
+
request = Typhoeus::Request.new(
|
24
|
+
url_param,
|
25
|
+
method: method.to_sym,
|
26
|
+
body: body,
|
27
|
+
params: params,
|
28
|
+
ssl_verifyhost: ssl_verifyhost,
|
29
|
+
ssl_verifypeer: ssl_verifypeer,
|
30
|
+
headers: { 'Content-Type' => 'application/json', Authorization: auth.to_s }
|
31
|
+
)
|
32
|
+
temp = request.run
|
33
|
+
begin
|
34
|
+
JSON.parse(temp.response_body)
|
35
|
+
rescue Exception
|
36
|
+
Error.throw_error('1631')
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
##
|
41
|
+
# Gets the current FileMaker token
|
42
|
+
#
|
43
|
+
# @return [String] a valid FileMaker token
|
44
|
+
def self.get_token
|
45
|
+
if Connection.valid_connection?
|
46
|
+
Connection.token
|
47
|
+
else
|
48
|
+
Connection.connect
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
##
|
53
|
+
# Retrieves the first record from FileMaker
|
54
|
+
#
|
55
|
+
# @return [JSON] The first record from FileMaker
|
56
|
+
def self.retrieve_first(layout_name)
|
57
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records?_limit=1")
|
58
|
+
make_request(url, "Bearer #{get_token}", 'get', '{}')
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Runs a FileMaker script
|
63
|
+
#
|
64
|
+
# @return [JSON] The script result from FileMaker
|
65
|
+
def self.run_script(script, scriptparameter, layout_name)
|
66
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records?_limit=1&script=#{script}&script.param=#{scriptparameter}")
|
67
|
+
make_request(url, "Bearer #{get_token}", 'get', '{}')
|
68
|
+
end
|
69
|
+
|
70
|
+
##
|
71
|
+
# Retrieves the 10000000 records from FileMaker
|
72
|
+
#
|
73
|
+
# @return [JSON] The first 10000000 records from FileMaker
|
74
|
+
def self.retrieve_all(layout_name, sort)
|
75
|
+
if !sort.empty?
|
76
|
+
sort_order = sort.to_json.to_s
|
77
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records?_limit=10000000_sort=#{sort_order}#{Trophonius.config.count_result_script == '' ? '' : "&script=#{Trophonius.config.count_result_script}"}")
|
78
|
+
else
|
79
|
+
url = URI("http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{layout_name}/records?_limit=10000000#{Trophonius.config.count_result_script == '' ? '' : "&script=#{Trophonius.config.count_result_script}"}")
|
80
|
+
end
|
81
|
+
make_request(url, "Bearer #{get_token}", 'get', '{}')
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
metadata
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trophonius
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kempen Automatisering
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: typhoeus
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: redis
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activesupport
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: solargraph
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.32.0
|
62
|
+
- - "~>"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0.32'
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: 0.32.0
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0.32'
|
75
|
+
description: An easy to use link between Ruby (on Rails) and FileMaker using the FileMaker
|
76
|
+
Data-API.
|
77
|
+
email:
|
78
|
+
executables: []
|
79
|
+
extensions: []
|
80
|
+
extra_rdoc_files: []
|
81
|
+
files:
|
82
|
+
- lib/trophonius.rb
|
83
|
+
- lib/trophonius_config.rb
|
84
|
+
- lib/trophonius_connection.rb
|
85
|
+
- lib/trophonius_error.rb
|
86
|
+
- lib/trophonius_model.rb
|
87
|
+
- lib/trophonius_record.rb
|
88
|
+
- lib/trophonius_recordset.rb
|
89
|
+
- lib/trophonius_request.rb
|
90
|
+
homepage:
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata: {}
|
94
|
+
post_install_message:
|
95
|
+
rdoc_options: []
|
96
|
+
require_paths:
|
97
|
+
- lib
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
version: '0'
|
108
|
+
requirements: []
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 2.7.9
|
111
|
+
signing_key:
|
112
|
+
specification_version: 4
|
113
|
+
summary: Link between Ruby (on Rails) and FileMaker.
|
114
|
+
test_files: []
|