trophonius 1.2.4.7 → 1.2.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/trophonius_config.rb +2 -0
- data/lib/trophonius_connection.rb +37 -0
- data/lib/trophonius_error.rb +23 -8
- data/lib/trophonius_model.rb +40 -0
- data/lib/trophonius_record.rb +162 -30
- metadata +1 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41574e377e8690e833092a9dedd6e7929ae57bdc10d34aa61b76ff89369f8051
|
4
|
+
data.tar.gz: '0096a1ae118fc10514f68005ad6408710ebc37269e7f2a1ba6fab7395e0f48da'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ef4fb3e20cb91540f88e391ffd83acc7f54165b2974d7a9ad62a18ebf35ca867829f9d6d09e7e1bcccbabdf1690f6bb0bfb8e083b4c387f598b6e4b31865a38
|
7
|
+
data.tar.gz: e3f3eab5629b894e7e7a7aebc6dbe1252ed56d2c9a902d0a929f9a513bdc9edad66def8a20dcca697002f107f3016fa6c5151f0d48b838e74e836d2647e2d4f2
|
data/lib/trophonius_config.rb
CHANGED
@@ -18,6 +18,8 @@ module Trophonius
|
|
18
18
|
config_accessor(:non_modifiable_fields) { [] }
|
19
19
|
config_accessor(:all_fields) { {} }
|
20
20
|
config_accessor(:translations) { {} }
|
21
|
+
config_accessor(:has_many_relations) { {} }
|
22
|
+
config_accessor(:belongs_to_relations) { {} }
|
21
23
|
config_accessor(:local_network) { false }
|
22
24
|
config_accessor(:redis_connection) { false }
|
23
25
|
end
|
@@ -77,6 +77,43 @@ module Trophonius
|
|
77
77
|
return parsed['response']['token']
|
78
78
|
end
|
79
79
|
|
80
|
+
##
|
81
|
+
# Disconnects from the FileMaker server
|
82
|
+
#
|
83
|
+
def self.disconnect
|
84
|
+
url =
|
85
|
+
URI(
|
86
|
+
"http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/sessions/#{
|
87
|
+
Trophonius.config.redis_connection ? Trophonius::RedisManager.get_key(key: 'token') : @token
|
88
|
+
}"
|
89
|
+
)
|
90
|
+
puts url
|
91
|
+
ssl_verifyhost = Trophonius.config.local_network ? 0 : 2
|
92
|
+
ssl_verifypeer = !Trophonius.config.local_network
|
93
|
+
|
94
|
+
request =
|
95
|
+
Typhoeus::Request.new(
|
96
|
+
url,
|
97
|
+
method: :delete,
|
98
|
+
params: {},
|
99
|
+
ssl_verifyhost: ssl_verifyhost,
|
100
|
+
ssl_verifypeer: ssl_verifypeer,
|
101
|
+
headers: { 'Content-Type' => 'application/json' }
|
102
|
+
)
|
103
|
+
temp = request.run
|
104
|
+
|
105
|
+
begin
|
106
|
+
parsed = JSON.parse(temp.response_body)
|
107
|
+
rescue Exception => e
|
108
|
+
Error.throw_error('1631')
|
109
|
+
end
|
110
|
+
Error.throw_error(parsed['messages'][0]['code']) if parsed['messages'][0]['code'] != '0'
|
111
|
+
Trophonius::RedisManager.disconnect! if Trophonius.config.redis_connection
|
112
|
+
@token = nil
|
113
|
+
@last_connection = nil
|
114
|
+
return true
|
115
|
+
end
|
116
|
+
|
80
117
|
##
|
81
118
|
# Returns the last received token
|
82
119
|
# @return [String] the last valid *token* used to connect with the FileMaker data api
|
data/lib/trophonius_error.rb
CHANGED
@@ -19,6 +19,14 @@ module Trophonius
|
|
19
19
|
class ConnectionError < StandardError; end # :nodoc:
|
20
20
|
class EmptyFindError < StandardError; end # :nodoc:
|
21
21
|
class ValidationError < StandardError; end # :nodoc:
|
22
|
+
class DateValueError < ValidationError; end # :nodoc:
|
23
|
+
class TimeValueError < ValidationError; end # :nodoc:
|
24
|
+
class NumberValueError < ValidationError; end # :nodoc:
|
25
|
+
class ValueOutOfRangeError < ValidationError; end # :nodoc:
|
26
|
+
class ValueNotUniqueError < ValidationError; end # :nodoc:
|
27
|
+
class ValueNotExistingError < ValidationError; end # :nodoc:
|
28
|
+
class ValueNotInValuelistError < ValidationError; end # :nodoc:
|
29
|
+
class ValueFailedCalculationError < ValidationError; end # :nodoc:
|
22
30
|
|
23
31
|
##
|
24
32
|
# Throws an error corresponding to the error number
|
@@ -140,14 +148,21 @@ module Trophonius
|
|
140
148
|
# when "417"
|
141
149
|
# when "418"
|
142
150
|
when '500'
|
143
|
-
raise
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
+
raise DateValueError.new, 'Date value does not meet validation entry options (hint make sure your date values are formatted as MM/DD/YYYY)'
|
152
|
+
when '501'
|
153
|
+
raise TimeValueError.new, 'Time value does not meet validation entry options'
|
154
|
+
when '502'
|
155
|
+
raise NumberValueError.new, 'Number value does not meet validation entry options'
|
156
|
+
when '503'
|
157
|
+
raise ValueOutOfRangeError.new, 'Value in field is not within the range specified in validation entry options'
|
158
|
+
when '504'
|
159
|
+
raise ValueNotUniqueError.new, 'Value in field is not unique, as required in validation entry options'
|
160
|
+
when '505'
|
161
|
+
raise ValueNotExistingError.new, 'Value in field is not an existing value in the file, as required in validation entry options'
|
162
|
+
when '506'
|
163
|
+
raise ValueNotInValuelistError.new, 'Value in field is not listed in the value list specified in validation entry options'
|
164
|
+
when '507'
|
165
|
+
raise ValueFailedCalculationError.new, 'Value in field failed calculation test of validation entry options'
|
151
166
|
# when "508"
|
152
167
|
# when "509"
|
153
168
|
# when "510"
|
data/lib/trophonius_model.rb
CHANGED
@@ -28,10 +28,32 @@ module Trophonius
|
|
28
28
|
@configuration.non_modifiable_fields = configuration[:non_modifiable_fields]
|
29
29
|
@configuration.all_fields = {}
|
30
30
|
@configuration.translations = {}
|
31
|
+
@configuration.has_many_relations = {}
|
32
|
+
@configuration.belongs_to_relations = {}
|
31
33
|
@offset = ''
|
32
34
|
@limit = ''
|
33
35
|
end
|
34
36
|
|
37
|
+
##
|
38
|
+
# Add a belongs to relationship.
|
39
|
+
#
|
40
|
+
# @param [Symbol] model_name: the name of the model to build a relation with
|
41
|
+
# @param [String] primary_key: the name of the field containing the primary to build the relation over
|
42
|
+
# @param [String] foreign_key: the name of the field containing the primary to build the relation over
|
43
|
+
def self.belongs_to(model_name, primary_key:, foreign_key:)
|
44
|
+
@configuration.belongs_to_relations.merge!({ model_name => { primary_key: primary_key, foreign_key: foreign_key } })
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# Add a has many relationship.
|
49
|
+
#
|
50
|
+
# @param [Symbol] model_name: the name of the model to build a relation with
|
51
|
+
# @param [String] primary_key: the name of the field containing the primary to build the relation over
|
52
|
+
# @param [String] foreign_key: the name of the field containing the primary to build the relation over
|
53
|
+
def self.has_many(model_name, primary_key:, foreign_key:)
|
54
|
+
@configuration.has_many_relations.merge!({ model_name => { primary_key: primary_key, foreign_key: foreign_key } })
|
55
|
+
end
|
56
|
+
|
35
57
|
##
|
36
58
|
# Limits the found record set.
|
37
59
|
#
|
@@ -53,6 +75,22 @@ module Trophonius
|
|
53
75
|
@configuration.layout_name
|
54
76
|
end
|
55
77
|
|
78
|
+
##
|
79
|
+
# Returns the Hash containing the related parent models
|
80
|
+
#
|
81
|
+
# @return [Hash] child models
|
82
|
+
def self.has_many_relations
|
83
|
+
@configuration.has_many_relations
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Returns the Hash containing the related parent models
|
88
|
+
#
|
89
|
+
# @return [Hash] parent models
|
90
|
+
def self.belongs_to_relations
|
91
|
+
@configuration.belongs_to_relations
|
92
|
+
end
|
93
|
+
|
56
94
|
##
|
57
95
|
# Returns the fields that FileMaker won't allow us to modify
|
58
96
|
#
|
@@ -296,6 +334,8 @@ module Trophonius
|
|
296
334
|
hash = Trophonius::Record.new
|
297
335
|
hash.record_id = result['recordId']
|
298
336
|
hash.layout_name = layout_name
|
337
|
+
hash.model_name = name
|
338
|
+
|
299
339
|
result['fieldData'].keys.each do |key|
|
300
340
|
# unless key[/\s/] || key[/\W/]
|
301
341
|
@configuration.translations.merge!(
|
data/lib/trophonius_record.rb
CHANGED
@@ -8,7 +8,7 @@ module Trophonius
|
|
8
8
|
#
|
9
9
|
# A Record is contained in a RecordSet and has methods to retrieve data from the fields inside the Record-hash
|
10
10
|
class Trophonius::Record < Hash
|
11
|
-
attr_accessor :record_id, :layout_name, :modifiable_fields, :modified_fields
|
11
|
+
attr_accessor :record_id, :model_name, :layout_name, :modifiable_fields, :modified_fields
|
12
12
|
|
13
13
|
##
|
14
14
|
# Initializes a new Record
|
@@ -22,20 +22,147 @@ module Trophonius
|
|
22
22
|
super
|
23
23
|
end
|
24
24
|
|
25
|
+
def method_missing(method, *args, &block)
|
26
|
+
if ActiveSupport::Inflector.pluralize(method).to_s == method.to_s
|
27
|
+
model = ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.classify(ActiveSupport::Inflector.singularize(method)))
|
28
|
+
pk_model = ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.classify(ActiveSupport::Inflector.singularize(model_name)))
|
29
|
+
|
30
|
+
if model.belongs_to_relations[ActiveSupport::Inflector.parameterize(model_name).to_sym]
|
31
|
+
relation = model.belongs_to_relations[ActiveSupport::Inflector.parameterize(model_name).to_sym]
|
32
|
+
layout = model.layout_name
|
33
|
+
model.create_translations if model.translations.keys.empty?
|
34
|
+
|
35
|
+
url =
|
36
|
+
URI(
|
37
|
+
"http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{
|
38
|
+
Trophonius.config.database
|
39
|
+
}/layouts/#{layout}/_find"
|
40
|
+
)
|
41
|
+
|
42
|
+
if model.translations.key?(relation[:foreign_key])
|
43
|
+
foreign_key_field = model.translations[relation[:foreign_key]].to_s
|
44
|
+
else
|
45
|
+
foreign_key_field = relation[:foreign_key].to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
if pk_model.translations.key?(relation[:primary_key])
|
49
|
+
primary_key_field = pk_model.translations[relation[:primary_key]].to_s
|
50
|
+
else
|
51
|
+
primary_key_field = relation[:primary_key].to_s
|
52
|
+
end
|
53
|
+
|
54
|
+
body = { query: [{ foreign_key_field => self[primary_key_field].to_s }], limit: 100_000 }.to_json
|
55
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", 'post', body)
|
56
|
+
|
57
|
+
if response['messages'][0]['code'] != '0'
|
58
|
+
if response['messages'][0]['code'] == '101' || response['messages'][0]['code'] == '401'
|
59
|
+
resp = RecordSet.new(layout, model.non_modifiable_fields)
|
60
|
+
return resp
|
61
|
+
else
|
62
|
+
if response['messages'][0]['code'] == '102'
|
63
|
+
results = Request.retrieve_first(layout)
|
64
|
+
if results['messages'][0]['code'] != '0'
|
65
|
+
Error.throw_error('102')
|
66
|
+
else
|
67
|
+
r_results = results['response']['data']
|
68
|
+
ret_val = r_results.empty? ? Error.throw_error('102') : r_results[0]['fieldData']
|
69
|
+
query_keys = [foreign_key_field]
|
70
|
+
Error.throw_error('102', (query_keys - ret_val.keys.map(&:downcase)).flatten.join(', '), layout)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
Error.throw_error(response['messages'][0]['code'])
|
74
|
+
end
|
75
|
+
else
|
76
|
+
r_results = response['response']['data']
|
77
|
+
ret_val = RecordSet.new(layout, model.non_modifiable_fields)
|
78
|
+
r_results.each do |r|
|
79
|
+
hash = model.build_result(r)
|
80
|
+
ret_val << hash
|
81
|
+
end
|
82
|
+
@response = ret_val
|
83
|
+
return @response
|
84
|
+
end
|
85
|
+
end
|
86
|
+
elsif ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.classify(ActiveSupport::Inflector.singularize(method))).respond_to?('first')
|
87
|
+
fk_model = ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.classify(ActiveSupport::Inflector.singularize(model_name)))
|
88
|
+
pk_model = ActiveSupport::Inflector.constantize(ActiveSupport::Inflector.classify(ActiveSupport::Inflector.singularize(method)))
|
89
|
+
|
90
|
+
if pk_model.has_many_relations[ActiveSupport::Inflector.parameterize(ActiveSupport::Inflector.pluralize(model_name)).to_sym]
|
91
|
+
relation = pk_model.has_many_relations[ActiveSupport::Inflector.parameterize(ActiveSupport::Inflector.pluralize(model_name)).to_sym]
|
92
|
+
layout = pk_model.layout_name
|
93
|
+
pk_model.create_translations if pk_model.translations.keys.empty?
|
94
|
+
|
95
|
+
url =
|
96
|
+
URI(
|
97
|
+
"http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{
|
98
|
+
Trophonius.config.database
|
99
|
+
}/layouts/#{layout}/_find"
|
100
|
+
)
|
101
|
+
|
102
|
+
if fk_model.translations.key?(relation[:foreign_key])
|
103
|
+
foreign_key_field = fk_model.translations[relation[:foreign_key]].to_s
|
104
|
+
else
|
105
|
+
foreign_key_field = relation[:foreign_key].to_s
|
106
|
+
end
|
107
|
+
|
108
|
+
if pk_model.translations.key?(relation[:primary_key])
|
109
|
+
primary_key_field = pk_model.translations[relation[:primary_key]].to_s
|
110
|
+
else
|
111
|
+
primary_key_field = relation[:primary_key].to_s
|
112
|
+
end
|
113
|
+
|
114
|
+
body = { query: [{ primary_key_field => self[foreign_key_field].to_s }], limit: 1 }.to_json
|
115
|
+
|
116
|
+
response = Request.make_request(url, "Bearer #{Request.get_token}", 'post', body)
|
117
|
+
if response['messages'][0]['code'] != '0'
|
118
|
+
if response['messages'][0]['code'] == '101' || response['messages'][0]['code'] == '401'
|
119
|
+
resp = RecordSet.new(layout, pk_model.non_modifiable_fields)
|
120
|
+
return resp
|
121
|
+
else
|
122
|
+
if response['messages'][0]['code'] == '102'
|
123
|
+
results = Request.retrieve_first(layout)
|
124
|
+
if results['messages'][0]['code'] != '0'
|
125
|
+
Error.throw_error('102')
|
126
|
+
else
|
127
|
+
r_results = results['response']['data']
|
128
|
+
ret_val = r_results.empty? ? Error.throw_error('102') : r_results[0]['fieldData']
|
129
|
+
query_keys = [primary_key_field]
|
130
|
+
Error.throw_error('102', (query_keys - ret_val.keys.map(&:downcase)).flatten.join(', '), layout)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
Error.throw_error(response['messages'][0]['code'])
|
134
|
+
end
|
135
|
+
else
|
136
|
+
r_results = response['response']['data']
|
137
|
+
ret_val = RecordSet.new(layout, pk_model.non_modifiable_fields)
|
138
|
+
r_results.each do |r|
|
139
|
+
hash = pk_model.build_result(r)
|
140
|
+
ret_val << hash
|
141
|
+
end
|
142
|
+
@response = ret_val
|
143
|
+
return @response.first
|
144
|
+
end
|
145
|
+
end
|
146
|
+
else
|
147
|
+
super
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
25
151
|
##
|
26
152
|
# Saves the last changes made to the Record to FileMaker.
|
27
153
|
# Throws a FileMaker error if save failed
|
28
154
|
#
|
29
155
|
# @return [True] if successful
|
30
156
|
def save
|
31
|
-
url =
|
157
|
+
url =
|
158
|
+
URI(
|
159
|
+
"http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{
|
160
|
+
layout_name
|
161
|
+
}/records/#{record_id}"
|
162
|
+
)
|
32
163
|
body = "{\"fieldData\": #{modified_fields.to_json}}"
|
33
164
|
response = Request.make_request(url, "Bearer #{Request.get_token}", 'patch', body)
|
34
|
-
|
35
|
-
Error.throw_error(response['messages'][0]['code'])
|
36
|
-
else
|
37
|
-
true
|
38
|
-
end
|
165
|
+
response['messages'][0]['code'] != '0' ? Error.throw_error(response['messages'][0]['code']) : true
|
39
166
|
end
|
40
167
|
|
41
168
|
##
|
@@ -44,13 +171,14 @@ module Trophonius
|
|
44
171
|
#
|
45
172
|
# @return [True] if successful
|
46
173
|
def delete
|
47
|
-
url =
|
174
|
+
url =
|
175
|
+
URI(
|
176
|
+
"http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{
|
177
|
+
layout_name
|
178
|
+
}/records/#{record_id}"
|
179
|
+
)
|
48
180
|
response = Request.make_request(url, "Bearer #{Request.get_token}", 'delete', '{}')
|
49
|
-
|
50
|
-
Error.throw_error(response['messages'][0]['code'])
|
51
|
-
else
|
52
|
-
true
|
53
|
-
end
|
181
|
+
response['messages'][0]['code'] != '0' ? Error.throw_error(response['messages'][0]['code']) : true
|
54
182
|
end
|
55
183
|
|
56
184
|
##
|
@@ -61,24 +189,27 @@ module Trophonius
|
|
61
189
|
#
|
62
190
|
# @return [True] if successful
|
63
191
|
def update(fieldData)
|
64
|
-
url =
|
65
|
-
|
66
|
-
|
67
|
-
|
192
|
+
url =
|
193
|
+
URI(
|
194
|
+
"http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{
|
195
|
+
layout_name
|
196
|
+
}/records/#{record_id}"
|
197
|
+
)
|
198
|
+
fieldData.keys.each { |field| modifiable_fields[field] = fieldData[field] }
|
68
199
|
body = "{\"fieldData\": #{fieldData.to_json}}"
|
69
200
|
response = Request.make_request(url, "Bearer #{Request.get_token}", 'patch', body)
|
70
201
|
if response['messages'][0]['code'] != '0'
|
71
|
-
if response[
|
202
|
+
if response['messages'][0]['code'] == '102'
|
72
203
|
results = Request.retrieve_first(layout_name)
|
73
|
-
if results[
|
74
|
-
Error.throw_error(
|
204
|
+
if results['messages'][0]['code'] != '0'
|
205
|
+
Error.throw_error('102')
|
75
206
|
else
|
76
|
-
r_results = results[
|
77
|
-
ret_val = r_results.empty? ? Error.throw_error(
|
78
|
-
Error.throw_error(
|
207
|
+
r_results = results['response']['data']
|
208
|
+
ret_val = r_results.empty? ? Error.throw_error('102') : r_results[0]['fieldData']
|
209
|
+
Error.throw_error('102', (fieldData.keys.map(&:downcase) - ret_val.keys.map(&:downcase)).flatten.join(', '), layout_name)
|
79
210
|
end
|
80
211
|
end
|
81
|
-
Error.throw_error(response[
|
212
|
+
Error.throw_error(response['messages'][0]['code'])
|
82
213
|
else
|
83
214
|
true
|
84
215
|
end
|
@@ -94,14 +225,15 @@ module Trophonius
|
|
94
225
|
#
|
95
226
|
# @return [True] if successful
|
96
227
|
def upload(container_name:, container_repetition: 1, file:)
|
97
|
-
url =
|
228
|
+
url =
|
229
|
+
URI(
|
230
|
+
"http#{Trophonius.config.ssl == true ? 's' : ''}://#{Trophonius.config.host}/fmi/data/v1/databases/#{Trophonius.config.database}/layouts/#{
|
231
|
+
layout_name
|
232
|
+
}/records/#{record_id}/containers/#{container_name}/#{container_repetition}"
|
233
|
+
)
|
98
234
|
|
99
235
|
response = Request.upload_file_request(url, "Bearer #{Request.get_token}", file)
|
100
|
-
|
101
|
-
Error.throw_error(response['messages'][0]['code'])
|
102
|
-
else
|
103
|
-
true
|
104
|
-
end
|
236
|
+
response['messages'][0]['code'] != '0' ? Error.throw_error(response['messages'][0]['code']) : true
|
105
237
|
end
|
106
238
|
end
|
107
239
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trophonius
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kempen Automatisering
|
@@ -43,9 +43,6 @@ dependencies:
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '5.2'
|
48
|
-
- - "<"
|
49
46
|
- !ruby/object:Gem::Version
|
50
47
|
version: 6.0.3.1
|
51
48
|
type: :runtime
|
@@ -53,9 +50,6 @@ dependencies:
|
|
53
50
|
version_requirements: !ruby/object:Gem::Requirement
|
54
51
|
requirements:
|
55
52
|
- - ">="
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
version: '5.2'
|
58
|
-
- - "<"
|
59
53
|
- !ruby/object:Gem::Version
|
60
54
|
version: 6.0.3.1
|
61
55
|
description: An easy to use link between Ruby (on Rails) and FileMaker using the FileMaker
|