harmonia 0.1.7 → 0.1.9
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 +4 -4
- data/lib/generators/harmonia/reverse_sync_generator.rb +56 -0
- data/lib/generators/harmonia/sync_generator.rb +56 -0
- data/lib/generators/harmonia/templates/create_harmonia_syncs.rb +2 -0
- data/lib/generators/harmonia/templates/filemaker_to_activerecord_syncer_template.rb +77 -39
- data/lib/generators/harmonia/templates/harmonia_sync.rb +10 -6
- data/lib/generators/harmonia/templates/sync_data.rake +6 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 60bf90e5016a52779a8cd3400aa7a92ebb510c8b4304a38d77b6b4891d255a41
|
|
4
|
+
data.tar.gz: 4a7a354befe4e2d0164e0c1cdd0a2d83769a5429f205da68816faa54c2bb34df
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9012312d892912d09d3bb214197ffbb388d0ab11940fd0df940c8f3b9beea4a54fa66b7dd51c0fa7b759e414b90c41cde8244d9cc716f9c25da123ff2256867
|
|
7
|
+
data.tar.gz: 70fb50a16c8fc7a998818d89522d88432b00ec23df2630781b1752ee2f67b294a5349008a981b9c99d4de6a9c7503d64651cc1c244def6c06f9f97b7ed18f781
|
|
@@ -17,6 +17,57 @@ module Harmonia
|
|
|
17
17
|
migration_template "add_filemaker_id_to_table.rb", "db/migrate/add_filemaker_id_to_#{table_name}.rb"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def create_or_update_rake_task
|
|
21
|
+
rake_file = "lib/tasks/sync_data.rake"
|
|
22
|
+
task_name = "sync_#{file_name}_to_filemaker"
|
|
23
|
+
|
|
24
|
+
if File.exist?(rake_file)
|
|
25
|
+
# Read existing file
|
|
26
|
+
content = File.read(rake_file)
|
|
27
|
+
|
|
28
|
+
# Add new task before the final 'end' if it doesn't exist
|
|
29
|
+
unless content.include?("task #{task_name}:")
|
|
30
|
+
# Add the new task before the final 'end'
|
|
31
|
+
new_task = <<~TASK
|
|
32
|
+
|
|
33
|
+
desc 'sync #{table_name} from ActiveRecord to FileMaker'
|
|
34
|
+
task #{task_name}: :environment do
|
|
35
|
+
#{class_name}ToFileMakerSyncer.new.sync
|
|
36
|
+
end
|
|
37
|
+
TASK
|
|
38
|
+
|
|
39
|
+
# Insert before the final 'end'
|
|
40
|
+
content = content.sub(/^end\s*$/, "#{new_task}end")
|
|
41
|
+
|
|
42
|
+
# Add task to the 'all' array
|
|
43
|
+
content = content.sub(/task all: %i\[(.*?)\]/) do
|
|
44
|
+
tasks = $1.split.map(&:to_sym)
|
|
45
|
+
tasks << task_name.to_sym unless tasks.include?(task_name.to_sym)
|
|
46
|
+
"task all: %i[#{tasks.join(' ')}]"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
File.write(rake_file, content)
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
# Create new rake file
|
|
53
|
+
template "sync_data.rake", rake_file
|
|
54
|
+
|
|
55
|
+
# Add the new task
|
|
56
|
+
content = File.read(rake_file)
|
|
57
|
+
new_task = <<~TASK
|
|
58
|
+
|
|
59
|
+
desc 'sync #{table_name} from ActiveRecord to FileMaker'
|
|
60
|
+
task #{task_name}: :environment do
|
|
61
|
+
#{class_name}ToFileMakerSyncer.new.sync
|
|
62
|
+
end
|
|
63
|
+
TASK
|
|
64
|
+
|
|
65
|
+
content = content.sub(/^end\s*$/, "#{new_task}end")
|
|
66
|
+
content = content.sub(/task all: %i\[\]/, "task all: %i[#{task_name}]")
|
|
67
|
+
File.write(rake_file, content)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
20
71
|
def show_readme
|
|
21
72
|
readme_content = <<~README
|
|
22
73
|
|
|
@@ -27,6 +78,7 @@ module Harmonia
|
|
|
27
78
|
Files created:
|
|
28
79
|
- app/syncers/#{file_name}_to_filemaker_syncer.rb
|
|
29
80
|
- db/migrate/..._add_filemaker_id_to_#{table_name}.rb
|
|
81
|
+
- lib/tasks/sync_data.rake (updated with sync_#{file_name}_to_filemaker task)
|
|
30
82
|
|
|
31
83
|
Next steps:
|
|
32
84
|
1. Run migrations: rails db:migrate
|
|
@@ -58,6 +110,10 @@ module Harmonia
|
|
|
58
110
|
Note: The total_required count used for sync tracking is automatically calculated
|
|
59
111
|
from @total_create_required + @total_update_required
|
|
60
112
|
|
|
113
|
+
7. Run the sync task:
|
|
114
|
+
- Individual sync: rake sync:sync_#{file_name}_to_filemaker
|
|
115
|
+
- All syncs: rake sync:all
|
|
116
|
+
|
|
61
117
|
README
|
|
62
118
|
|
|
63
119
|
say readme_content, :green if behavior == :invoke
|
|
@@ -17,6 +17,57 @@ module Harmonia
|
|
|
17
17
|
migration_template "add_filemaker_id_to_table.rb", "db/migrate/add_filemaker_id_to_#{table_name}.rb"
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
+
def create_or_update_rake_task
|
|
21
|
+
rake_file = "lib/tasks/sync_data.rake"
|
|
22
|
+
task_name = "sync_#{file_name}"
|
|
23
|
+
|
|
24
|
+
if File.exist?(rake_file)
|
|
25
|
+
# Read existing file
|
|
26
|
+
content = File.read(rake_file)
|
|
27
|
+
|
|
28
|
+
# Add new task before the final 'end' if it doesn't exist
|
|
29
|
+
unless content.include?("task #{task_name}:")
|
|
30
|
+
# Add the new task before the final 'end'
|
|
31
|
+
new_task = <<~TASK
|
|
32
|
+
|
|
33
|
+
desc 'sync #{table_name} from FileMaker to ActiveRecord'
|
|
34
|
+
task #{task_name}: :environment do
|
|
35
|
+
#{class_name}Syncer.new.sync
|
|
36
|
+
end
|
|
37
|
+
TASK
|
|
38
|
+
|
|
39
|
+
# Insert before the final 'end'
|
|
40
|
+
content = content.sub(/^end\s*$/, "#{new_task}end")
|
|
41
|
+
|
|
42
|
+
# Add task to the 'all' array
|
|
43
|
+
content = content.sub(/task all: %i\[(.*?)\]/) do
|
|
44
|
+
tasks = $1.split.map(&:to_sym)
|
|
45
|
+
tasks << task_name.to_sym unless tasks.include?(task_name.to_sym)
|
|
46
|
+
"task all: %i[#{tasks.join(' ')}]"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
File.write(rake_file, content)
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
# Create new rake file
|
|
53
|
+
template "sync_data.rake", rake_file
|
|
54
|
+
|
|
55
|
+
# Add the new task
|
|
56
|
+
content = File.read(rake_file)
|
|
57
|
+
new_task = <<~TASK
|
|
58
|
+
|
|
59
|
+
desc 'sync #{table_name} from FileMaker to ActiveRecord'
|
|
60
|
+
task #{task_name}: :environment do
|
|
61
|
+
#{class_name}Syncer.new.sync
|
|
62
|
+
end
|
|
63
|
+
TASK
|
|
64
|
+
|
|
65
|
+
content = content.sub(/^end\s*$/, "#{new_task}end")
|
|
66
|
+
content = content.sub(/task all: %i\[\]/, "task all: %i[#{task_name}]")
|
|
67
|
+
File.write(rake_file, content)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
20
71
|
def show_readme
|
|
21
72
|
readme_content = <<~README
|
|
22
73
|
|
|
@@ -27,6 +78,7 @@ module Harmonia
|
|
|
27
78
|
Files created:
|
|
28
79
|
- app/syncers/#{file_name}_syncer.rb
|
|
29
80
|
- db/migrate/..._add_filemaker_id_to_#{table_name}.rb
|
|
81
|
+
- lib/tasks/sync_data.rake (updated with sync_#{file_name} task)
|
|
30
82
|
|
|
31
83
|
Next steps:
|
|
32
84
|
1. Run migrations: rails db:migrate
|
|
@@ -45,6 +97,10 @@ module Harmonia
|
|
|
45
97
|
Note: The total_required count used for sync tracking is automatically calculated
|
|
46
98
|
from @total_create_required + @total_update_required
|
|
47
99
|
|
|
100
|
+
5. Run the sync task:
|
|
101
|
+
- Individual sync: rake sync:sync_#{file_name}
|
|
102
|
+
- All syncs: rake sync:all
|
|
103
|
+
|
|
48
104
|
README
|
|
49
105
|
|
|
50
106
|
say readme_content, :green if behavior == :invoke
|
|
@@ -10,6 +10,8 @@ class CreateHarmoniaSyncs < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>
|
|
|
10
10
|
t.string :status, default: 'pending'
|
|
11
11
|
t.string :direction
|
|
12
12
|
t.text :error_message
|
|
13
|
+
t.string :failed_fm_ids, array: true
|
|
14
|
+
t.integer :failed_pg_ids, array: true
|
|
13
15
|
|
|
14
16
|
t.timestamps
|
|
15
17
|
end
|
|
@@ -6,6 +6,8 @@ class <%= class_name %>Syncer
|
|
|
6
6
|
def initialize(database_connector)
|
|
7
7
|
@database_connector = database_connector
|
|
8
8
|
@last_synced_on = Harmonia::Sync.last_sync_for('<%= table_name %>', 'FileMaker to ActiveRecord')&.ran_on || (Time.now - 15.year)
|
|
9
|
+
@failed_fm_ids = []
|
|
10
|
+
@failed_pg_ids = []
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
# Main sync method
|
|
@@ -20,7 +22,7 @@ class <%= class_name %>Syncer
|
|
|
20
22
|
sync_records(sync_record)
|
|
21
23
|
end
|
|
22
24
|
rescue StandardError => e
|
|
23
|
-
sync_record&.fail!(e.message)
|
|
25
|
+
sync_record&.fail!(e.message, failed_fm_ids: @failed_fm_ids, failed_pg_ids: @failed_pg_ids)
|
|
24
26
|
raise
|
|
25
27
|
end
|
|
26
28
|
|
|
@@ -36,55 +38,68 @@ class <%= class_name %>Syncer
|
|
|
36
38
|
|
|
37
39
|
sync_record.finish!(
|
|
38
40
|
records_synced: total_synced,
|
|
39
|
-
records_required: total_required
|
|
41
|
+
records_required: total_required,
|
|
42
|
+
failed_fm_ids: @failed_fm_ids,
|
|
43
|
+
failed_pg_ids: @failed_pg_ids
|
|
40
44
|
)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
# Returns an array of Trophonius records that need to be created
|
|
44
|
-
# Use
|
|
48
|
+
# Use FileMaker::<%= class_name %>.to_pg(record) to convert to PostgreSQL attributes
|
|
45
49
|
# Set @total_create_required to the total number of records that should exist after creation
|
|
46
50
|
# @return [Array<Trophonius::Record>] Array of Trophonius records
|
|
47
51
|
def records_to_create
|
|
48
52
|
# TODO: Implement logic to fetch records from FileMaker that need to be created in PostgreSQL
|
|
49
53
|
# Example:
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@total_create_required = 0
|
|
55
|
-
[]
|
|
54
|
+
filemaker_records = FileMaker::<%= class_name %>.where(creation_timestamp: ">= #{@last_synced_on.to_fm}").not
|
|
55
|
+
@total_create_required = filemaker_records.length
|
|
56
|
+
existing_ids = <%= class_name %>.pluck(:filemaker_id)
|
|
57
|
+
filemaker_records.reject { |record| existing_ids.include?(record.id) }
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
# Returns an array of Trophonius records that need to be updated
|
|
59
|
-
# Use
|
|
61
|
+
# Use FileMaker::<%= class_name %>.to_pg(record) to convert to PostgreSQL attributes
|
|
60
62
|
# Set @total_update_required to the total number of records that should be updated
|
|
61
63
|
# @return [Array<Trophonius::Record>] Array of Trophonius records
|
|
62
64
|
def records_to_update
|
|
63
65
|
# TODO: Implement logic to fetch records from FileMaker that need to be updated in PostgreSQL
|
|
64
66
|
# Example:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
@total_update_required = 0
|
|
73
|
-
[]
|
|
67
|
+
filemaker_records = FileMaker::<%= class_name %>.where(modification_timestamp: ">= #{@last_synced_on.to_fm}")
|
|
68
|
+
records_needing_update = filemaker_records.select { |fm_record|
|
|
69
|
+
pg_record = <%= class_name %>.find_by(filemaker_id: fm_record.record_id)
|
|
70
|
+
pg_record && needs_update?(fm_record, pg_record)
|
|
71
|
+
}
|
|
72
|
+
@total_update_required = records_needing_update.length
|
|
73
|
+
records_needing_update
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
# Returns an array of record identifiers that need to be deleted
|
|
77
77
|
# @return [Array] Array of record identifiers
|
|
78
78
|
def records_to_delete
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
# Get all modified FileMaker record IDs
|
|
80
|
+
filemaker_records = FileMaker::<%= class_name %>.where(modification_timestamp: ">= #{@last_synced_on.to_fm}")
|
|
81
|
+
fm_ids = filemaker_records.map(&:record_id)
|
|
82
|
+
|
|
83
|
+
# Find PostgreSQL records whose FileMaker IDs aren't in the modified set
|
|
84
|
+
# These might have been deleted in FileMaker
|
|
85
|
+
fm_ids_no_update_needed = <%= class_name %>.where.not(filemaker_id: fm_ids).pluck(:filemaker_id)
|
|
86
|
+
return [] if fm_ids_no_update_needed.empty?
|
|
87
|
+
|
|
88
|
+
# Query FileMaker to check if these records still exist
|
|
89
|
+
possibly_deleted_query = FileMaker::<%= class_name %>.where(record_id: fm_ids_no_update_needed.first)
|
|
90
|
+
fm_ids_no_update_needed.count > 1 && fm_ids_no_update_needed[1..].each do |fm_id|
|
|
91
|
+
possibly_deleted_query.or(record_id: fm_id)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Find IDs that exist in PostgreSQL but not in FileMaker (truly deleted)
|
|
95
|
+
deleted_fm_ids = fm_ids_no_update_needed - possibly_deleted_query.map(&:record_id)
|
|
96
|
+
|
|
97
|
+
# Return PostgreSQL IDs for records with these FileMaker IDs
|
|
98
|
+
<%= class_name %>.where(filemaker_id: deleted_fm_ids).pluck(:id)
|
|
84
99
|
end
|
|
85
100
|
|
|
86
101
|
def needs_update?(fm_record, pg_record)
|
|
87
|
-
pg_attributes =
|
|
102
|
+
pg_attributes = FileMaker::<%= class_name %>.to_pg(fm_record)
|
|
88
103
|
|
|
89
104
|
pg_attributes.any? { |key, value| pg_record.send(key) != value }
|
|
90
105
|
end
|
|
@@ -93,37 +108,60 @@ class <%= class_name %>Syncer
|
|
|
93
108
|
records = records_to_create
|
|
94
109
|
return 0 if records.empty?
|
|
95
110
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
success_count = 0
|
|
112
|
+
|
|
113
|
+
records.each do |trophonius_record|
|
|
114
|
+
begin
|
|
115
|
+
attributes = FileMaker::<%= class_name %>.to_pg(trophonius_record).merge(
|
|
116
|
+
created_at: Time.current,
|
|
117
|
+
updated_at: Time.current
|
|
118
|
+
)
|
|
119
|
+
<%= class_name %>.create!(attributes)
|
|
120
|
+
success_count += 1
|
|
121
|
+
rescue StandardError => e
|
|
122
|
+
@failed_fm_ids << trophonius_record.record_id
|
|
123
|
+
Rails.logger.error("Failed to create record from FileMaker ID #{trophonius_record.record_id}: #{e.message}")
|
|
124
|
+
end
|
|
101
125
|
end
|
|
102
126
|
|
|
103
|
-
|
|
104
|
-
records.size
|
|
127
|
+
success_count
|
|
105
128
|
end
|
|
106
129
|
|
|
107
130
|
def update_records
|
|
108
131
|
records = records_to_update
|
|
109
132
|
return 0 if records.empty?
|
|
110
133
|
|
|
111
|
-
|
|
112
|
-
pg_attributes = YourTrophoniusModel.to_pg(trophonius_record)
|
|
134
|
+
success_count = 0
|
|
113
135
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
136
|
+
records.each do |trophonius_record|
|
|
137
|
+
begin
|
|
138
|
+
pg_attributes = FileMaker::<%= class_name %>.to_pg(trophonius_record)
|
|
139
|
+
|
|
140
|
+
<%= class_name %>.where(filemaker_id: trophonius_record.record_id).update_all(
|
|
141
|
+
pg_attributes.merge(updated_at: Time.current)
|
|
142
|
+
)
|
|
143
|
+
success_count += 1
|
|
144
|
+
rescue StandardError => e
|
|
145
|
+
@failed_fm_ids << trophonius_record.record_id
|
|
146
|
+
Rails.logger.error("Failed to update record from FileMaker ID #{trophonius_record.record_id}: #{e.message}")
|
|
147
|
+
end
|
|
117
148
|
end
|
|
118
149
|
|
|
119
|
-
|
|
150
|
+
success_count
|
|
120
151
|
end
|
|
121
152
|
|
|
122
153
|
def delete_records
|
|
123
154
|
ids = records_to_delete
|
|
124
155
|
return if ids.empty?
|
|
125
156
|
|
|
126
|
-
|
|
157
|
+
ids.each do |pg_id|
|
|
158
|
+
begin
|
|
159
|
+
<%= class_name %>.where(id: pg_id).destroy_all
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
@failed_pg_ids << pg_id
|
|
162
|
+
Rails.logger.error("Failed to delete record with PostgreSQL ID #{pg_id}: #{e.message}")
|
|
163
|
+
end
|
|
164
|
+
end
|
|
127
165
|
end
|
|
128
166
|
|
|
129
167
|
def create_sync_record
|
|
@@ -24,9 +24,9 @@ module Harmonia
|
|
|
24
24
|
scope :completed, -> { where(status: 'completed') }
|
|
25
25
|
scope :failed, -> { where(status: 'failed') }
|
|
26
26
|
|
|
27
|
-
# Get the most recent sync for a table in a given direction
|
|
27
|
+
# Get the most recent successful sync for a table in a given direction
|
|
28
28
|
def self.last_sync_for(table_name, direction)
|
|
29
|
-
for_direction(direction).for_table(table_name).recent.first
|
|
29
|
+
completed.for_direction(direction).for_table(table_name).recent.first
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
# Calculate sync completion percentage
|
|
@@ -46,19 +46,23 @@ module Harmonia
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Mark sync as completed
|
|
49
|
-
def finish!(records_synced:, records_required:)
|
|
49
|
+
def finish!(records_synced:, records_required:, failed_fm_ids: [], failed_pg_ids: [])
|
|
50
50
|
update!(
|
|
51
51
|
status: 'completed',
|
|
52
52
|
records_synced: records_synced,
|
|
53
|
-
records_required: records_required
|
|
53
|
+
records_required: records_required,
|
|
54
|
+
failed_fm_ids: failed_fm_ids,
|
|
55
|
+
failed_pg_ids: failed_pg_ids
|
|
54
56
|
)
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
# Mark sync as failed
|
|
58
|
-
def fail!(error_message)
|
|
60
|
+
def fail!(error_message, failed_fm_ids: [], failed_pg_ids: [])
|
|
59
61
|
update!(
|
|
60
62
|
status: 'failed',
|
|
61
|
-
error_message: error_message
|
|
63
|
+
error_message: error_message,
|
|
64
|
+
failed_fm_ids: failed_fm_ids,
|
|
65
|
+
failed_pg_ids: failed_pg_ids
|
|
62
66
|
)
|
|
63
67
|
end
|
|
64
68
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: harmonia
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.9
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kempen Automatisering
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: trophonius
|
|
@@ -43,6 +43,7 @@ files:
|
|
|
43
43
|
- lib/generators/harmonia/templates/database_connector.rb
|
|
44
44
|
- lib/generators/harmonia/templates/filemaker_to_activerecord_syncer_template.rb
|
|
45
45
|
- lib/generators/harmonia/templates/harmonia_sync.rb
|
|
46
|
+
- lib/generators/harmonia/templates/sync_data.rake
|
|
46
47
|
- lib/generators/harmonia/templates/trophonius_model_extension.rb
|
|
47
48
|
- lib/harmonia.rb
|
|
48
49
|
- lib/harmonia/railtie.rb
|