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 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
+ [![Gem Version](https://badge.fury.io/rb/harmonia.svg)](https://badge.fury.io/rb/harmonia)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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.