harmonia 0.1.7
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/License.txt +7 -0
- data/README.md +666 -0
- data/lib/generators/harmonia/install_generator.rb +64 -0
- data/lib/generators/harmonia/reverse_sync_generator.rb +86 -0
- data/lib/generators/harmonia/sync_generator.rb +73 -0
- data/lib/generators/harmonia/templates/activerecord_to_filemaker_syncer_template.rb +160 -0
- data/lib/generators/harmonia/templates/add_filemaker_id_to_table.rb +10 -0
- data/lib/generators/harmonia/templates/application_record_extension.rb +13 -0
- data/lib/generators/harmonia/templates/create_harmonia_syncs.rb +20 -0
- data/lib/generators/harmonia/templates/database_connector.rb +34 -0
- data/lib/generators/harmonia/templates/filemaker_to_activerecord_syncer_template.rb +137 -0
- data/lib/generators/harmonia/templates/harmonia_sync.rb +65 -0
- data/lib/generators/harmonia/templates/trophonius_model_extension.rb +11 -0
- data/lib/harmonia/railtie.rb +10 -0
- data/lib/harmonia.rb +9 -0
- metadata +73 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fe5daefb15ce5ef2a2cc7a90a66123967ade8300f08b8f017220d5919c9aebe4
|
|
4
|
+
data.tar.gz: f826c905f2192dd472ab849108afebda80282b1af0050b400ddcd15402dc2ae8
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 40ec19a556c1f7fe3251f4f8d2885a6f5f4480098df2bac9654473b55870457029133abc013434cbdf99859873b51317266037c06432bbbd343a82a6afeec92a
|
|
7
|
+
data.tar.gz: 5e2d1d49a203f0c5b415413a577665f0ab1f2fe90bec8b22a70cbdf1d5b96f4f1259f3817baeff760505e16c4474f61f5e5d6850d9402429ec991862f97a7344
|
data/License.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2019 Kempen Automatisering
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
# Harmonia
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/harmonia)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Harmonia is a Rails generator gem that creates synchronization logic between FileMaker databases and Ruby on Rails ActiveRecord models. It leverages the [Trophonius gem](https://github.com/KA-HQ/trophonius) for FileMaker Data API communication.
|
|
7
|
+
|
|
8
|
+
## Origin of the Name
|
|
9
|
+
|
|
10
|
+
Harmonia is named after the Greek goddess of harmony and concord. In Greek mythology, [Harmonia](https://en.wikipedia.org/wiki/Harmonia) was the immortal goddess who reconciled opposing forces and brought them into balance. Just as the goddess brought harmony between different elements, this gem achieves harmony and concord between FileMaker databases and ActiveRecord databases, bridging two different data systems into a synchronized whole.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Bidirectional Sync**: Synchronize data from FileMaker to ActiveRecord and vice versa
|
|
15
|
+
- **Automated Sync Generation**: Generate syncer classes for your models with a single command
|
|
16
|
+
- **Sync Tracking**: Built-in model to track synchronization status, completion, and errors
|
|
17
|
+
- **Flexible Architecture**: Customize creation, update, and deletion logic for your specific needs
|
|
18
|
+
- **Connection Management**: Automatic FileMaker connection handling with proper cleanup
|
|
19
|
+
- **Extensible**: Override methods to implement custom sync logic
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
Add this line to your application's Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'harmonia'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
And then execute:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bundle install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or install it yourself as:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gem install harmonia
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Getting Started
|
|
42
|
+
|
|
43
|
+
### 1. Install Harmonia
|
|
44
|
+
|
|
45
|
+
Run the install generator to set up the necessary files and database tables:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
rails generate harmonia:install
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This will create:
|
|
52
|
+
|
|
53
|
+
- `app/services/database_connector.rb` - Manages FileMaker connections
|
|
54
|
+
- `config/initializers/trophonius_model_extension.rb` - Extends Trophonius models with `to_pg` method
|
|
55
|
+
- `app/models/application_record.rb` - Extends ApplicationRecord with `to_fm` method
|
|
56
|
+
- `app/models/harmonia/sync.rb` - Tracks sync operations
|
|
57
|
+
- `db/migrate/[timestamp]_create_harmonia_syncs.rb` - Migration for sync tracking table
|
|
58
|
+
|
|
59
|
+
After generation, remember to:
|
|
60
|
+
|
|
61
|
+
1. Replace all instances of `YourTrophoniusModel` with your actual Trophonius model names
|
|
62
|
+
2. Update the `database` configuration in `database_connector.rb`
|
|
63
|
+
3. Run the migration: `rails db:migrate`
|
|
64
|
+
|
|
65
|
+
### 2. Configure FileMaker Credentials
|
|
66
|
+
|
|
67
|
+
Add your FileMaker credentials to your Rails credentials file:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
rails credentials:edit
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Add the following:
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
filemaker:
|
|
77
|
+
username: your_username
|
|
78
|
+
password: your_password
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 3. Create a Trophonius Model
|
|
82
|
+
|
|
83
|
+
Define your FileMaker model using Trophonius. For example:
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
# app/models/trophonius/product.rb
|
|
87
|
+
module Trophonius
|
|
88
|
+
class Product < Trophonius::Model
|
|
89
|
+
config layout_name: 'ProductsLayout'
|
|
90
|
+
|
|
91
|
+
# Convert FileMaker record to PostgreSQL attributes
|
|
92
|
+
def self.to_pg(record)
|
|
93
|
+
{
|
|
94
|
+
filemaker_id: record.id,
|
|
95
|
+
name: record.product_name,
|
|
96
|
+
price: record.price,
|
|
97
|
+
sku: record.sku
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 4. Generate a Syncer
|
|
105
|
+
|
|
106
|
+
Harmonia supports two types of synchronization:
|
|
107
|
+
|
|
108
|
+
#### FileMaker to ActiveRecord Sync
|
|
109
|
+
|
|
110
|
+
Generate a syncer that pulls data from FileMaker into your Rails database:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
rails generate harmonia:sync Product
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This creates:
|
|
117
|
+
- `app/syncers/product_syncer.rb` - The syncer class
|
|
118
|
+
- `db/migrate/[timestamp]_add_filemaker_id_to_products.rb` - Migration to add `filemaker_id` column
|
|
119
|
+
|
|
120
|
+
**Important**: Run `rails db:migrate` after generation to add the `filemaker_id` column to your table. This column is used to track the FileMaker record ID and is automatically indexed for performance.
|
|
121
|
+
|
|
122
|
+
#### ActiveRecord to FileMaker Sync
|
|
123
|
+
|
|
124
|
+
Generate a reverse syncer that pushes data from Rails to FileMaker:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
rails generate harmonia:reverse_sync Product
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
This creates:
|
|
131
|
+
- `app/syncers/product_to_filemaker_syncer.rb` - The reverse syncer class
|
|
132
|
+
- `db/migrate/[timestamp]_add_filemaker_id_to_products.rb` - Migration to add `filemaker_id` column (if not already present)
|
|
133
|
+
|
|
134
|
+
**Important**: Run `rails db:migrate` after generation. The `filemaker_id` column is used to maintain the relationship between ActiveRecord and FileMaker records.
|
|
135
|
+
|
|
136
|
+
### 5. Implement Sync Logic
|
|
137
|
+
|
|
138
|
+
#### FileMaker to ActiveRecord Implementation
|
|
139
|
+
|
|
140
|
+
Edit the generated syncer to implement your synchronization logic:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
# app/syncers/product_syncer.rb
|
|
144
|
+
class ProductSyncer
|
|
145
|
+
attr_accessor :database_connector
|
|
146
|
+
|
|
147
|
+
def initialize(database_connector)
|
|
148
|
+
@database_connector = database_connector
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def run
|
|
152
|
+
raise StandardError, 'No database connector set' if @database_connector.blank?
|
|
153
|
+
|
|
154
|
+
sync_record = create_sync_record
|
|
155
|
+
|
|
156
|
+
@database_connector.open_database do
|
|
157
|
+
sync_record.start!
|
|
158
|
+
sync_records(sync_record)
|
|
159
|
+
end
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
sync_record&.fail!(e.message)
|
|
162
|
+
raise
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
def records_to_create
|
|
168
|
+
filemaker_records = Trophonius::Product.all
|
|
169
|
+
@total_create_required = filemaker_records.length
|
|
170
|
+
|
|
171
|
+
existing_ids = Product.pluck(:filemaker_id)
|
|
172
|
+
filemaker_records.reject { |record| existing_ids.include?(record.record_id) }
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def records_to_update
|
|
176
|
+
filemaker_records = Trophonius::Product.all
|
|
177
|
+
|
|
178
|
+
records_needing_update = filemaker_records.select { |fm_record|
|
|
179
|
+
pg_record = Product.find_by(filemaker_id: fm_record.record_id)
|
|
180
|
+
pg_record && needs_update?(fm_record, pg_record)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
@total_update_required = records_needing_update.length
|
|
184
|
+
records_needing_update
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def records_to_delete
|
|
188
|
+
filemaker_ids = Trophonius::Product.all.map(&:record_id)
|
|
189
|
+
Product.where.not(filemaker_id: filemaker_ids).pluck(:id)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def needs_update?(fm_record, pg_record)
|
|
193
|
+
# Implement your comparison logic
|
|
194
|
+
fm_data = Trophonius::Product.to_pg(fm_record)
|
|
195
|
+
pg_record.name != fm_data[:name] || pg_record.price != fm_data[:price]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# ... other methods (create_records, update_records, etc.)
|
|
199
|
+
end
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
#### ActiveRecord to FileMaker Implementation
|
|
203
|
+
|
|
204
|
+
For reverse sync, implement the `to_fm` method in your model and the syncer logic:
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
# app/models/product.rb
|
|
208
|
+
class Product < ApplicationRecord
|
|
209
|
+
# Convert ActiveRecord record to FileMaker attributes
|
|
210
|
+
def self.to_fm(record)
|
|
211
|
+
{
|
|
212
|
+
'PostgreSQLID' => record.id.to_s,
|
|
213
|
+
'ProductName' => record.name,
|
|
214
|
+
'Price' => record.price.to_s,
|
|
215
|
+
'SKU' => record.sku
|
|
216
|
+
}
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
# app/syncers/product_to_filemaker_syncer.rb
|
|
223
|
+
class ProductToFileMakerSyncer
|
|
224
|
+
attr_accessor :database_connector
|
|
225
|
+
|
|
226
|
+
def initialize(database_connector)
|
|
227
|
+
@database_connector = database_connector
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def run
|
|
231
|
+
raise StandardError, 'No database connector set' if @database_connector.blank?
|
|
232
|
+
|
|
233
|
+
sync_record = create_sync_record
|
|
234
|
+
|
|
235
|
+
@database_connector.open_database do
|
|
236
|
+
sync_record.start!
|
|
237
|
+
sync_records(sync_record)
|
|
238
|
+
end
|
|
239
|
+
rescue StandardError => e
|
|
240
|
+
sync_record&.fail!(e.message)
|
|
241
|
+
raise
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
private
|
|
245
|
+
|
|
246
|
+
def records_to_create
|
|
247
|
+
pg_records = Product.all
|
|
248
|
+
@total_create_required = pg_records.length
|
|
249
|
+
|
|
250
|
+
existing_ids = Trophonius::Product.all.map { |r| r.field_data['PostgreSQLID'] }
|
|
251
|
+
pg_records.reject { |record| existing_ids.include?(record.id.to_s) }
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def records_to_update
|
|
255
|
+
pg_records = Product.where('updated_at > ?', 1.hour.ago)
|
|
256
|
+
|
|
257
|
+
records_needing_update = pg_records.select { |pg_record|
|
|
258
|
+
fm_record = find_filemaker_record(pg_record)
|
|
259
|
+
fm_record && needs_update?(pg_record, fm_record)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@total_update_required = records_needing_update.length
|
|
263
|
+
records_needing_update
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def find_filemaker_record(pg_record)
|
|
267
|
+
Trophonius::Product.find_by_field('PostgreSQLID', pg_record.id.to_s)
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def needs_update?(pg_record, fm_record)
|
|
271
|
+
fm_attributes = Product.to_fm(pg_record)
|
|
272
|
+
fm_attributes.any? { |key, value| fm_record.field_data[key.to_s] != value }
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# ... other methods (create_records, update_records, etc.)
|
|
276
|
+
end
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### 6. Run Your Sync
|
|
280
|
+
|
|
281
|
+
#### FileMaker to ActiveRecord
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
# Create a database connector
|
|
285
|
+
connector = DatabaseConnector.new
|
|
286
|
+
connector.hostname = 'your-filemaker-server.com'
|
|
287
|
+
|
|
288
|
+
# Initialize and run the syncer
|
|
289
|
+
syncer = ProductSyncer.new(connector)
|
|
290
|
+
syncer.run
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
#### ActiveRecord to FileMaker
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
# Create a database connector
|
|
297
|
+
connector = DatabaseConnector.new
|
|
298
|
+
connector.hostname = 'your-filemaker-server.com'
|
|
299
|
+
|
|
300
|
+
# Initialize and run the reverse syncer
|
|
301
|
+
syncer = ProductToFileMakerSyncer.new(connector)
|
|
302
|
+
syncer.run
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Architecture
|
|
306
|
+
|
|
307
|
+
### Key Components
|
|
308
|
+
|
|
309
|
+
#### 1. DatabaseConnector
|
|
310
|
+
|
|
311
|
+
Manages connections to the FileMaker server using Trophonius:
|
|
312
|
+
|
|
313
|
+
```ruby
|
|
314
|
+
connector = DatabaseConnector.new
|
|
315
|
+
connector.hostname = 'filemaker.example.com'
|
|
316
|
+
|
|
317
|
+
connector.open_database do
|
|
318
|
+
# Your FileMaker operations here
|
|
319
|
+
end
|
|
320
|
+
# Connection automatically closed
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
#### 2. Syncer Classes
|
|
324
|
+
|
|
325
|
+
Each syncer handles the synchronization logic for a specific model. Harmonia supports two directions:
|
|
326
|
+
|
|
327
|
+
**FileMaker to ActiveRecord Syncers** (`ModelNameSyncer`):
|
|
328
|
+
- **`records_to_create`**: Returns Trophonius records that need to be created in PostgreSQL
|
|
329
|
+
- Must set `@total_create_required` to track total records
|
|
330
|
+
- **`records_to_update`**: Returns Trophonius records that need to be updated
|
|
331
|
+
- Must set `@total_update_required` to track total records
|
|
332
|
+
- **`records_to_delete`**: Returns PostgreSQL record IDs that should be deleted
|
|
333
|
+
- **`create_records`**: Bulk creates new records in PostgreSQL
|
|
334
|
+
- **`update_records`**: Updates existing PostgreSQL records
|
|
335
|
+
- **`delete_records`**: Removes obsolete PostgreSQL records
|
|
336
|
+
|
|
337
|
+
**ActiveRecord to FileMaker Syncers** (`ModelNameToFileMakerSyncer`):
|
|
338
|
+
- **`records_to_create`**: Returns ActiveRecord records that need to be created in FileMaker
|
|
339
|
+
- Must set `@total_create_required` to track total records
|
|
340
|
+
- **`records_to_update`**: Returns ActiveRecord records that need to be updated in FileMaker
|
|
341
|
+
- Must set `@total_update_required` to track total records
|
|
342
|
+
- **`records_to_delete`**: Returns FileMaker record IDs that should be deleted
|
|
343
|
+
- **`find_filemaker_record`**: Finds corresponding FileMaker record for an ActiveRecord record
|
|
344
|
+
- **`create_records`**: Creates new records in FileMaker
|
|
345
|
+
- **`update_records`**: Updates existing FileMaker records
|
|
346
|
+
- **`delete_records`**: Removes obsolete FileMaker records
|
|
347
|
+
|
|
348
|
+
#### 3. Harmonia::Sync Model
|
|
349
|
+
|
|
350
|
+
Tracks sync operations with the following attributes:
|
|
351
|
+
|
|
352
|
+
- `table` - Name of the table being synced
|
|
353
|
+
- `ran_on` - Date the sync was run
|
|
354
|
+
- `status` - One of: `pending`, `in_progress`, `completed`, `failed`
|
|
355
|
+
- `records_synced` - Number of records successfully synced
|
|
356
|
+
- `records_required` - Total number of records that should exist
|
|
357
|
+
- `error_message` - Error details if sync failed
|
|
358
|
+
|
|
359
|
+
**Important**: The `records_required` value is automatically calculated as:
|
|
360
|
+
|
|
361
|
+
```ruby
|
|
362
|
+
total_required = (@total_create_required || 0) + (@total_update_required || 0)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
You must set `@total_create_required` in `records_to_create` and `@total_update_required` in `records_to_update`.
|
|
366
|
+
|
|
367
|
+
#### Useful Methods
|
|
368
|
+
|
|
369
|
+
```ruby
|
|
370
|
+
# Get the last sync for a table
|
|
371
|
+
Harmonia::Sync.last_sync_for('products')
|
|
372
|
+
|
|
373
|
+
# Check completion percentage
|
|
374
|
+
sync = Harmonia::Sync.last
|
|
375
|
+
sync.completion_percentage # => 98.5
|
|
376
|
+
|
|
377
|
+
# Check if sync was complete
|
|
378
|
+
sync.complete? # => true if all records synced
|
|
379
|
+
|
|
380
|
+
# Query by status
|
|
381
|
+
Harmonia::Sync.completed
|
|
382
|
+
Harmonia::Sync.failed
|
|
383
|
+
Harmonia::Sync.in_progress
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
#### 4. Model Extensions
|
|
387
|
+
|
|
388
|
+
**Trophonius Model Extension** - The `to_pg` class method is required for FileMaker to ActiveRecord sync:
|
|
389
|
+
|
|
390
|
+
```ruby
|
|
391
|
+
module Trophonius
|
|
392
|
+
class Product < Trophonius::Model
|
|
393
|
+
# Converts FileMaker record to PostgreSQL attributes
|
|
394
|
+
def self.to_pg(record)
|
|
395
|
+
{
|
|
396
|
+
filemaker_id: record.record_id,
|
|
397
|
+
name: record.field_data['ProductName'],
|
|
398
|
+
price: record.field_data['Price'].to_f,
|
|
399
|
+
# ... map other fields
|
|
400
|
+
}
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
**ApplicationRecord Extension** - The `to_fm` class method is required for ActiveRecord to FileMaker sync:
|
|
407
|
+
|
|
408
|
+
```ruby
|
|
409
|
+
class Product < ApplicationRecord
|
|
410
|
+
# Converts ActiveRecord record to FileMaker attributes
|
|
411
|
+
def self.to_fm(record)
|
|
412
|
+
{
|
|
413
|
+
'PostgreSQLID' => record.id.to_s,
|
|
414
|
+
'ProductName' => record.name,
|
|
415
|
+
'Price' => record.price.to_s,
|
|
416
|
+
# ... map other fields
|
|
417
|
+
}
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Database Schema
|
|
423
|
+
|
|
424
|
+
### The `filemaker_id` Column
|
|
425
|
+
|
|
426
|
+
When you generate a syncer (either direction), Harmonia automatically creates a migration that adds a `filemaker_id` column to your table:
|
|
427
|
+
|
|
428
|
+
```ruby
|
|
429
|
+
add_column :products, :filemaker_id, :string
|
|
430
|
+
add_index :products, :filemaker_id, unique: true
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
This column serves as the bridge between your ActiveRecord records and FileMaker records:
|
|
434
|
+
|
|
435
|
+
- **FileMaker to ActiveRecord**: Stores the FileMaker `record_id` to identify which FileMaker record corresponds to each Rails record
|
|
436
|
+
- **ActiveRecord to FileMaker**: Can store the FileMaker `record_id` after creation, or you can use a separate field in FileMaker (like `PostgreSQLID`) to maintain the relationship
|
|
437
|
+
|
|
438
|
+
**Important Notes**:
|
|
439
|
+
- The column is indexed with a unique constraint for performance and data integrity
|
|
440
|
+
- The migration uses `column_exists?` to avoid errors if the column already exists
|
|
441
|
+
- If you generate both sync directions for the same model, the migration will only add the column once
|
|
442
|
+
|
|
443
|
+
## Advanced Usage
|
|
444
|
+
|
|
445
|
+
### Custom Comparison Logic
|
|
446
|
+
|
|
447
|
+
Implement `needs_update?` to define when a record should be updated:
|
|
448
|
+
|
|
449
|
+
```ruby
|
|
450
|
+
def needs_update?(fm_record, pg_record)
|
|
451
|
+
fm_data = Trophonius::Product.to_pg(fm_record)
|
|
452
|
+
|
|
453
|
+
# Compare specific fields
|
|
454
|
+
pg_record.name != fm_data[:name] ||
|
|
455
|
+
pg_record.price != fm_data[:price] ||
|
|
456
|
+
pg_record.updated_at < 1.day.ago
|
|
457
|
+
end
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Batch Processing
|
|
461
|
+
|
|
462
|
+
For large datasets, consider processing in batches:
|
|
463
|
+
|
|
464
|
+
```ruby
|
|
465
|
+
def create_records
|
|
466
|
+
records = records_to_create
|
|
467
|
+
return 0 if records.empty?
|
|
468
|
+
|
|
469
|
+
records.each_slice(1000) do |batch|
|
|
470
|
+
attributes_array = batch.map do |trophonius_record|
|
|
471
|
+
Trophonius::Product.to_pg(trophonius_record).merge(
|
|
472
|
+
created_at: Time.current,
|
|
473
|
+
updated_at: Time.current
|
|
474
|
+
)
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
Product.insert_all(attributes_array)
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
records.size
|
|
481
|
+
end
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Scheduled Syncs
|
|
485
|
+
|
|
486
|
+
Use a background job processor like Sidekiq:
|
|
487
|
+
|
|
488
|
+
```ruby
|
|
489
|
+
class ProductSyncJob < ApplicationJob
|
|
490
|
+
queue_as :default
|
|
491
|
+
|
|
492
|
+
def perform
|
|
493
|
+
connector = DatabaseConnector.new
|
|
494
|
+
connector.hostname = ENV['FILEMAKER_HOST']
|
|
495
|
+
|
|
496
|
+
syncer = ProductSyncer.new(connector)
|
|
497
|
+
syncer.run
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# Schedule with cron or similar
|
|
502
|
+
# 0 2 * * * # Every day at 2 AM
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Multiple FileMaker Databases
|
|
506
|
+
|
|
507
|
+
Configure different connectors for different databases:
|
|
508
|
+
|
|
509
|
+
```ruby
|
|
510
|
+
class DatabaseConnector
|
|
511
|
+
attr_accessor :hostname, :database_name
|
|
512
|
+
|
|
513
|
+
def connect
|
|
514
|
+
Trophonius.configure do |config|
|
|
515
|
+
config.host = @hostname
|
|
516
|
+
config.database = @database_name
|
|
517
|
+
# ... other config
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Usage
|
|
523
|
+
connector = DatabaseConnector.new
|
|
524
|
+
connector.hostname = 'filemaker.example.com'
|
|
525
|
+
connector.database_name = 'Products'
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
## Error Handling
|
|
529
|
+
|
|
530
|
+
Syncers automatically handle errors and mark syncs as failed:
|
|
531
|
+
|
|
532
|
+
```ruby
|
|
533
|
+
def run
|
|
534
|
+
sync_record = create_sync_record
|
|
535
|
+
|
|
536
|
+
@database_connector.open_database do
|
|
537
|
+
sync_record.start!
|
|
538
|
+
sync_records(sync_record)
|
|
539
|
+
end
|
|
540
|
+
rescue StandardError => e
|
|
541
|
+
sync_record&.fail!(e.message)
|
|
542
|
+
raise
|
|
543
|
+
end
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Check failed syncs:
|
|
547
|
+
|
|
548
|
+
```ruby
|
|
549
|
+
failed_syncs = Harmonia::Sync.failed.recent
|
|
550
|
+
failed_syncs.each do |sync|
|
|
551
|
+
puts "#{sync.table}: #{sync.error_message}"
|
|
552
|
+
end
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
## Configuration Options
|
|
556
|
+
|
|
557
|
+
### Trophonius Pool Size
|
|
558
|
+
|
|
559
|
+
Adjust connection pool size for better performance:
|
|
560
|
+
|
|
561
|
+
```ruby
|
|
562
|
+
# In database_connector.rb
|
|
563
|
+
config.pool_size = ENV.fetch('TROPHONIUS_POOL', 5)
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
Set in your environment:
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
TROPHONIUS_POOL=10
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### SSL Configuration
|
|
573
|
+
|
|
574
|
+
Enable or disable SSL for FileMaker connections:
|
|
575
|
+
|
|
576
|
+
```ruby
|
|
577
|
+
config.ssl = true # Use HTTPS
|
|
578
|
+
config.ssl = false # Use HTTP
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Debug Mode
|
|
582
|
+
|
|
583
|
+
Enable detailed logging:
|
|
584
|
+
|
|
585
|
+
```ruby
|
|
586
|
+
config.debug = true
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
## Testing
|
|
590
|
+
|
|
591
|
+
### RSpec Example
|
|
592
|
+
|
|
593
|
+
```ruby
|
|
594
|
+
require 'rails_helper'
|
|
595
|
+
|
|
596
|
+
RSpec.describe ProductSyncer do
|
|
597
|
+
let(:connector) { instance_double(DatabaseConnector) }
|
|
598
|
+
let(:syncer) { described_class.new(connector) }
|
|
599
|
+
|
|
600
|
+
describe '#records_to_create' do
|
|
601
|
+
it 'returns records not in PostgreSQL' do
|
|
602
|
+
# Mock FileMaker records
|
|
603
|
+
allow(Trophonius::Product).to receive(:all).and_return([
|
|
604
|
+
double(record_id: 1),
|
|
605
|
+
double(record_id: 2)
|
|
606
|
+
])
|
|
607
|
+
|
|
608
|
+
# Mock existing PostgreSQL records
|
|
609
|
+
allow(Product).to receive(:pluck).with(:filemaker_id).and_return([1])
|
|
610
|
+
|
|
611
|
+
result = syncer.send(:records_to_create)
|
|
612
|
+
expect(result.map(&:record_id)).to eq([2])
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
## Troubleshooting
|
|
619
|
+
|
|
620
|
+
### Connection Issues
|
|
621
|
+
|
|
622
|
+
**Problem**: Cannot connect to FileMaker server
|
|
623
|
+
|
|
624
|
+
**Solutions**:
|
|
625
|
+
|
|
626
|
+
- Verify hostname and credentials
|
|
627
|
+
- Check if FileMaker Data API is enabled
|
|
628
|
+
- Ensure SSL settings match your server configuration
|
|
629
|
+
- Check firewall rules allow connections to port 443 (HTTPS) or 80 (HTTP)
|
|
630
|
+
|
|
631
|
+
### Missing Records
|
|
632
|
+
|
|
633
|
+
**Problem**: Not all records are syncing
|
|
634
|
+
|
|
635
|
+
**Solutions**:
|
|
636
|
+
|
|
637
|
+
- Verify `records_to_create` and `records_to_update` logic
|
|
638
|
+
- Check FileMaker layouts include all necessary fields
|
|
639
|
+
- Ensure `to_pg` mapping is correct
|
|
640
|
+
- Review sync completion percentage
|
|
641
|
+
|
|
642
|
+
### Performance Issues
|
|
643
|
+
|
|
644
|
+
**Problem**: Syncs are slow
|
|
645
|
+
|
|
646
|
+
**Solutions**:
|
|
647
|
+
|
|
648
|
+
- Increase `pool_size` in Trophonius configuration
|
|
649
|
+
- Implement batch processing
|
|
650
|
+
- Use `insert_all` instead of individual inserts
|
|
651
|
+
- Add database indexes on `filemaker_id` columns
|
|
652
|
+
- Consider partial syncs for large datasets
|
|
653
|
+
|
|
654
|
+
## Contributing
|
|
655
|
+
|
|
656
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/KA-HQ/Harmonia.
|
|
657
|
+
|
|
658
|
+
## License
|
|
659
|
+
|
|
660
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
661
|
+
|
|
662
|
+
## Credits
|
|
663
|
+
|
|
664
|
+
Developed by [Kempen Automatisering](https://www.kempenautomatisering.nl)
|
|
665
|
+
|
|
666
|
+
Built on top of the excellent [Trophonius](https://github.com/KA-HQ/trophonius) gem for FileMaker Data API integration.
|