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.
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Harmonia
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def copy_database_connector
11
+ copy_file "database_connector.rb", "app/services/database_connector.rb"
12
+ end
13
+
14
+ def copy_trophonius_extension
15
+ copy_file "trophonius_model_extension.rb", "config/initializers/trophonius_model_extension.rb"
16
+ end
17
+
18
+ def copy_application_record_extension
19
+ copy_file "application_record_extension.rb", "app/models/application_record.rb"
20
+ end
21
+
22
+ def copy_sync_model
23
+ copy_file "harmonia_sync.rb", "app/models/harmonia/sync.rb"
24
+ end
25
+
26
+ def generate_migration
27
+ migration_template "create_harmonia_syncs.rb", "db/migrate/create_harmonia_syncs.rb"
28
+ end
29
+
30
+ def show_readme
31
+ readme_content = <<~README
32
+
33
+ ========================================
34
+ Harmonia has been installed!
35
+ ========================================
36
+
37
+ Files created:
38
+ - app/services/database_connector.rb
39
+ - config/initializers/trophonius_model_extension.rb
40
+ - app/models/application_record.rb (with to_fm extension)
41
+ - app/models/harmonia/sync.rb
42
+ - db/migrate/..._create_harmonia_syncs.rb
43
+
44
+ Next steps:
45
+ 1. Run migrations: rails db:migrate
46
+ 2. Update database_connector.rb with your FileMaker database name
47
+ 3. Add FileMaker credentials to Rails credentials
48
+ 4. Replace all instances of YourTrophoniusModel with your actual model names
49
+ 5. Generate syncers:
50
+ - rails generate harmonia:sync ModelName (FileMaker -> ActiveRecord)
51
+ - rails generate harmonia:reverse_sync ModelName (ActiveRecord -> FileMaker)
52
+
53
+ README
54
+
55
+ say readme_content, :green if behavior == :invoke
56
+ end
57
+
58
+ # Required for migration_template to work
59
+ def self.next_migration_number(dirname)
60
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Harmonia
4
+ module Generators
5
+ class ReverseSyncGenerator < Rails::Generators::NamedBase
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :name, type: :string, required: true, banner: "ModelName"
11
+
12
+ def create_sync_file
13
+ template "activerecord_to_filemaker_syncer_template.rb", "app/syncers/#{file_name}_to_filemaker_syncer.rb"
14
+ end
15
+
16
+ def generate_migration
17
+ migration_template "add_filemaker_id_to_table.rb", "db/migrate/add_filemaker_id_to_#{table_name}.rb"
18
+ end
19
+
20
+ def show_readme
21
+ readme_content = <<~README
22
+
23
+ ========================================
24
+ #{class_name}ToFileMakerSyncer has been generated!
25
+ ========================================
26
+
27
+ Files created:
28
+ - app/syncers/#{file_name}_to_filemaker_syncer.rb
29
+ - db/migrate/..._add_filemaker_id_to_#{table_name}.rb
30
+
31
+ Next steps:
32
+ 1. Run migrations: rails db:migrate
33
+
34
+ 2. Implement the #{class_name}.to_fm method in your model:
35
+ class #{class_name} < ApplicationRecord
36
+ def self.to_fm(record)
37
+ {
38
+ 'FieldMakerFieldName' => record.attribute_name,
39
+ # ... map other fields
40
+ }
41
+ end
42
+ end
43
+
44
+ 3. Implement the records_to_create method
45
+ - Set @total_create_required to the total number of records that should be created
46
+ - Return an array of ActiveRecord records to create in FileMaker
47
+
48
+ 4. Implement the records_to_update method
49
+ - Set @total_update_required to the total number of records that should be updated
50
+ - Return an array of ActiveRecord records to update in FileMaker
51
+
52
+ 5. Implement the find_filemaker_record method
53
+ - Find corresponding FileMaker record for a given ActiveRecord record
54
+
55
+ 6. Implement the records_to_delete method (optional)
56
+ - Return an array of FileMaker record IDs to delete
57
+
58
+ Note: The total_required count used for sync tracking is automatically calculated
59
+ from @total_create_required + @total_update_required
60
+
61
+ README
62
+
63
+ say readme_content, :green if behavior == :invoke
64
+ end
65
+
66
+ # Required for migration_template to work
67
+ def self.next_migration_number(dirname)
68
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
69
+ end
70
+
71
+ private
72
+
73
+ def file_name
74
+ name.underscore
75
+ end
76
+
77
+ def class_name
78
+ name.camelize
79
+ end
80
+
81
+ def table_name
82
+ name.underscore.pluralize
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Harmonia
4
+ module Generators
5
+ class SyncGenerator < Rails::Generators::NamedBase
6
+ include Rails::Generators::Migration
7
+
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ argument :name, type: :string, required: true, banner: "ModelName"
11
+
12
+ def create_sync_file
13
+ template "filemaker_to_activerecord_syncer_template.rb", "app/syncers/#{file_name}_syncer.rb"
14
+ end
15
+
16
+ def generate_migration
17
+ migration_template "add_filemaker_id_to_table.rb", "db/migrate/add_filemaker_id_to_#{table_name}.rb"
18
+ end
19
+
20
+ def show_readme
21
+ readme_content = <<~README
22
+
23
+ ========================================
24
+ #{class_name}Syncer has been generated!
25
+ ========================================
26
+
27
+ Files created:
28
+ - app/syncers/#{file_name}_syncer.rb
29
+ - db/migrate/..._add_filemaker_id_to_#{table_name}.rb
30
+
31
+ Next steps:
32
+ 1. Run migrations: rails db:migrate
33
+
34
+ 2. Implement the records_to_create method
35
+ - Set @total_create_required to the total number of records that should be created
36
+ - Return an array of Trophonius records to create
37
+
38
+ 3. Implement the records_to_update method
39
+ - Set @total_update_required to the total number of records that should be updated
40
+ - Return an array of Trophonius records to update
41
+
42
+ 4. Implement the records_to_delete method (optional)
43
+ - Return an array of record identifiers to delete
44
+
45
+ Note: The total_required count used for sync tracking is automatically calculated
46
+ from @total_create_required + @total_update_required
47
+
48
+ README
49
+
50
+ say readme_content, :green if behavior == :invoke
51
+ end
52
+
53
+ # Required for migration_template to work
54
+ def self.next_migration_number(dirname)
55
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
56
+ end
57
+
58
+ private
59
+
60
+ def file_name
61
+ name.underscore
62
+ end
63
+
64
+ def class_name
65
+ name.camelize
66
+ end
67
+
68
+ def table_name
69
+ name.underscore.pluralize
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %>ToFileMakerSyncer
4
+ attr_accessor :database_connector
5
+
6
+ def initialize(database_connector)
7
+ @database_connector = database_connector
8
+ @last_synced_on = Harmonia::Sync.last_sync_for('<%= table_name %>', 'ActiveRecord to FileMaker')&.ran_on || (Time.now - 15.years)
9
+ end
10
+
11
+ # Main sync method
12
+ # Executes the sync process for <%= class_name %> records to FileMaker
13
+ def run
14
+ raise StandardError, 'No database connector set' if @database_connector.blank?
15
+
16
+ sync_record = create_sync_record
17
+
18
+ @database_connector.open_database do
19
+ sync_record.start!
20
+ sync_records(sync_record)
21
+ end
22
+ rescue StandardError => e
23
+ sync_record&.fail!(e.message)
24
+ raise
25
+ end
26
+
27
+ private
28
+
29
+ def sync_records(sync_record)
30
+ updated_count = update_records
31
+ created_count = create_records
32
+ delete_records
33
+
34
+ total_synced = created_count + updated_count
35
+ total_required = (@total_create_required || 0) + (@total_update_required || 0)
36
+
37
+ sync_record.finish!(
38
+ records_synced: total_synced,
39
+ records_required: total_required
40
+ )
41
+ end
42
+
43
+ # Returns an array of ActiveRecord records that need to be created in FileMaker
44
+ # Use <%= class_name %>.to_fm(record) to convert to FileMaker attributes
45
+ # Set @total_create_required to the total number of records that should exist after creation
46
+ # @return [Array<<%= class_name %>>] Array of ActiveRecord records
47
+ def records_to_create
48
+ # TODO: Implement logic to fetch records from PostgreSQL that need to be created in FileMaker
49
+ # Example:
50
+ # pg_records = <%= class_name %>.all
51
+ # @total_create_required = pg_records.length
52
+ # existing_ids = YourTrophoniusModel.all.map { |r| r.field_data['PostgreSQLID'] }
53
+ # pg_records.reject { |record| existing_ids.include?(record.id.to_s) }
54
+ @total_create_required = 0
55
+ []
56
+ end
57
+
58
+ # Returns an array of ActiveRecord records that need to be updated in FileMaker
59
+ # Use <%= class_name %>.to_fm(record) to convert to FileMaker attributes
60
+ # Set @total_update_required to the total number of records that should be updated
61
+ # @return [Array<<%= class_name %>>] Array of ActiveRecord records
62
+ def records_to_update
63
+ # TODO: Implement logic to fetch records from PostgreSQL that need to be updated in FileMaker
64
+ # Example:
65
+ # pg_records = <%= class_name %>.where('updated_at > ?', 1.hour.ago)
66
+ # records_needing_update = pg_records.select { |pg_record|
67
+ # fm_record = find_filemaker_record(pg_record)
68
+ # fm_record && needs_update?(pg_record, fm_record)
69
+ # }
70
+ # @total_update_required = records_needing_update.length
71
+ # records_needing_update
72
+ @total_update_required = 0
73
+ []
74
+ end
75
+
76
+ # Returns an array of FileMaker record IDs that need to be deleted
77
+ # @return [Array] Array of FileMaker record IDs
78
+ def records_to_delete
79
+ # TODO: Implement logic to determine which FileMaker records should be deleted
80
+ # Example:
81
+ # pg_ids = <%= class_name %>.pluck(:id).map(&:to_s)
82
+ # YourTrophoniusModel.all.select { |fm_record|
83
+ # !pg_ids.include?(fm_record.field_data['PostgreSQLID'])
84
+ # }.map(&:record_id)
85
+ []
86
+ end
87
+
88
+ def create_records
89
+ records = records_to_create
90
+ return 0 if records.empty?
91
+
92
+ records.each do |pg_record|
93
+ fm_attributes = pg_record.to_fm
94
+ YourTrophoniusModel.create(fm_attributes)
95
+ end
96
+
97
+ records.size
98
+ end
99
+
100
+ def update_records
101
+ records = records_to_update
102
+ return 0 if records.empty?
103
+
104
+ records.each do |pg_record|
105
+ fm_attributes = pg_record.to_fm
106
+
107
+ # Find the FileMaker record by PostgreSQL ID or other unique identifier
108
+ fm_record = find_filemaker_record(pg_record)
109
+ next unless fm_record
110
+
111
+ fm_record.update(fm_attributes)
112
+ end
113
+
114
+ records.size
115
+ end
116
+
117
+ def delete_records
118
+ record_ids = records_to_delete
119
+ return if record_ids.empty?
120
+
121
+ record_ids.each do |record_id|
122
+ fm_record = YourTrophoniusModel.find(record_id)
123
+ fm_record.destroy
124
+ rescue Trophonius::RecordNotFoundError
125
+ # Record already deleted, skip
126
+ next
127
+ end
128
+ end
129
+
130
+ # Helper method to find a FileMaker record based on a PostgreSQL record
131
+ # @param pg_record [ActiveRecord::Base] The PostgreSQL record
132
+ # @return [Trophonius::Model, nil] The corresponding FileMaker record or nil
133
+ def find_filemaker_record(pg_record)
134
+ # TODO: Implement logic to find the corresponding FileMaker record
135
+ # Example:
136
+ # YourTrophoniusModel.find_by_field('PostgreSQLID', pg_record.id.to_s)
137
+ nil
138
+ end
139
+
140
+ # Determine if a FileMaker record needs to be updated based on PostgreSQL record
141
+ # @param pg_record [ActiveRecord::Base] The PostgreSQL record
142
+ # @param fm_record [Trophonius::Model] The FileMaker record
143
+ # @return [Boolean] true if update is needed
144
+ def needs_update?(pg_record, fm_record)
145
+ # TODO: Implement your comparison logic
146
+ # Example:
147
+ # fm_attributes = pg_record.to_fm
148
+ # fm_attributes.any? { |key, value| fm_record.field_data[key.to_s] != value }
149
+ true
150
+ end
151
+
152
+ def create_sync_record
153
+ Harmonia::Sync.create!(
154
+ table: '<%= table_name %>',
155
+ ran_on: Time.now,
156
+ status: 'pending',
157
+ direction: 'ActiveRecord to FileMaker'
158
+ )
159
+ end
160
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddFilemakerIdTo<%= table_name.camelize %> < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>.<%= Rails::VERSION::MINOR %>]
4
+ def change
5
+ unless column_exists?(:<%= table_name %>, :filemaker_id)
6
+ add_column :<%= table_name %>, :filemaker_id, :string
7
+ add_index :<%= table_name %>, :filemaker_id, unique: true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ primary_abstract_class
5
+
6
+ # Converts an ActiveRecord record to FileMaker-compatible attributes
7
+ # This method should be overridden in each model that syncs to FileMaker
8
+ # @param record [ActiveRecord::Base] The ActiveRecord record instance to convert
9
+ # @return [Hash] Hash of FileMaker field names and values
10
+ def to_fm(record)
11
+ raise NotImplementedError, "#{self.class.name}#to_fm must be implemented to convert ActiveRecord records to FileMaker attributes"
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateHarmoniaSyncs < ActiveRecord::Migration[<%= Rails::VERSION::MAJOR %>.<%= Rails::VERSION::MINOR %>]
4
+ def change
5
+ create_table :harmonia_syncs do |t|
6
+ t.datetime :ran_on
7
+ t.string :table
8
+ t.integer :records_synced, default: 0
9
+ t.integer :records_required, default: 0
10
+ t.string :status, default: 'pending'
11
+ t.string :direction
12
+ t.text :error_message
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ add_index :harmonia_syncs, [:table, :ran_on]
18
+ add_index :harmonia_syncs, :status
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DatabaseConnector
4
+ attr_accessor :hostname
5
+
6
+ def open_database(&block)
7
+ raise ArgumentError, 'No hostname set' if @hostname.blank?
8
+ raise ArgumentError, 'No block given' if block.blank?
9
+
10
+ connect
11
+ yield block
12
+ ensure
13
+ disconnect
14
+ end
15
+
16
+ private
17
+
18
+ def connect
19
+ Trophonius.configure do |config|
20
+ config.host = @hostname
21
+ config.database = 'DatabaseName'
22
+ config.username = Rails.application.credentials.dig(:filemaker, :username)
23
+ config.password = Rails.application.credentials.dig(:filemaker, :password)
24
+ config.ssl = true # or false depending on whether https or http should be used
25
+ config.debug = true # will output more information when true
26
+ config.pool_size = ENV.fetch('trophonius_pool', 5) # use multiple data api connections with a loadbalancer to improve performance
27
+ end
28
+ @connection_manager = Trophonius.connection_manager
29
+ end
30
+
31
+ def disconnect
32
+ @connection_manager.disconnect_all
33
+ end
34
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= class_name %>Syncer
4
+ attr_accessor :database_connector
5
+
6
+ def initialize(database_connector)
7
+ @database_connector = database_connector
8
+ @last_synced_on = Harmonia::Sync.last_sync_for('<%= table_name %>', 'FileMaker to ActiveRecord')&.ran_on || (Time.now - 15.year)
9
+ end
10
+
11
+ # Main sync method
12
+ # Executes the sync process for <%= class_name %> records
13
+ def run
14
+ raise StandardError, 'No database connector set' if @database_connector.blank?
15
+
16
+ sync_record = create_sync_record
17
+
18
+ @database_connector.open_database do
19
+ sync_record.start!
20
+ sync_records(sync_record)
21
+ end
22
+ rescue StandardError => e
23
+ sync_record&.fail!(e.message)
24
+ raise
25
+ end
26
+
27
+ private
28
+
29
+ def sync_records(sync_record)
30
+ updated_count = update_records
31
+ created_count = create_records
32
+ delete_records
33
+
34
+ total_synced = created_count + updated_count
35
+ total_required = (@total_create_required || 0) + (@total_update_required || 0)
36
+
37
+ sync_record.finish!(
38
+ records_synced: total_synced,
39
+ records_required: total_required
40
+ )
41
+ end
42
+
43
+ # Returns an array of Trophonius records that need to be created
44
+ # Use YourTrophoniusModel.to_pg(record) to convert to PostgreSQL attributes
45
+ # Set @total_create_required to the total number of records that should exist after creation
46
+ # @return [Array<Trophonius::Record>] Array of Trophonius records
47
+ def records_to_create
48
+ # TODO: Implement logic to fetch records from FileMaker that need to be created in PostgreSQL
49
+ # Example:
50
+ # filemaker_records = YourTrophoniusModel.all
51
+ # @total_create_required = filemaker_records.length
52
+ # existing_ids = <%= class_name %>.pluck(:filemaker_id)
53
+ # filemaker_records.reject { |record| existing_ids.include?(record.record_id) }
54
+ @total_create_required = 0
55
+ []
56
+ end
57
+
58
+ # Returns an array of Trophonius records that need to be updated
59
+ # Use YourTrophoniusModel.to_pg(record) to convert to PostgreSQL attributes
60
+ # Set @total_update_required to the total number of records that should be updated
61
+ # @return [Array<Trophonius::Record>] Array of Trophonius records
62
+ def records_to_update
63
+ # TODO: Implement logic to fetch records from FileMaker that need to be updated in PostgreSQL
64
+ # Example:
65
+ # filemaker_records = YourTrophoniusModel.all
66
+ # records_needing_update = filemaker_records.select { |fm_record|
67
+ # pg_record = <%= class_name %>.find_by(filemaker_id: fm_record.record_id)
68
+ # pg_record && needs_update?(fm_record, pg_record)
69
+ # }
70
+ # @total_update_required = records_needing_update.length
71
+ # records_needing_update
72
+ @total_update_required = 0
73
+ []
74
+ end
75
+
76
+ # Returns an array of record identifiers that need to be deleted
77
+ # @return [Array] Array of record identifiers
78
+ def records_to_delete
79
+ # TODO: Implement logic to determine which PostgreSQL records should be deleted
80
+ # Example:
81
+ # filemaker_ids = YourTrophoniusModel.all.map(&:record_id)
82
+ # <%= class_name %>.where.not(filemaker_id: filemaker_ids).pluck(:id)
83
+ []
84
+ end
85
+
86
+ def needs_update?(fm_record, pg_record)
87
+ pg_attributes = YourTrophoniusModel.to_pg(fm_record)
88
+
89
+ pg_attributes.any? { |key, value| pg_record.send(key) != value }
90
+ end
91
+
92
+ def create_records
93
+ records = records_to_create
94
+ return 0 if records.empty?
95
+
96
+ attributes_array = records.map do |trophonius_record|
97
+ YourTrophoniusModel.to_pg(trophonius_record).merge(
98
+ created_at: Time.current,
99
+ updated_at: Time.current
100
+ )
101
+ end
102
+
103
+ <%= class_name %>.insert_all(attributes_array)
104
+ records.size
105
+ end
106
+
107
+ def update_records
108
+ records = records_to_update
109
+ return 0 if records.empty?
110
+
111
+ records.each do |trophonius_record|
112
+ pg_attributes = YourTrophoniusModel.to_pg(trophonius_record)
113
+
114
+ <%= class_name %>.where(filemaker_id: trophonius_record.record_id).update_all(
115
+ pg_attributes.merge(updated_at: Time.current)
116
+ )
117
+ end
118
+
119
+ records.size
120
+ end
121
+
122
+ def delete_records
123
+ ids = records_to_delete
124
+ return if ids.empty?
125
+
126
+ <%= class_name %>.where(id: ids).destroy_all
127
+ end
128
+
129
+ def create_sync_record
130
+ Harmonia::Sync.create!(
131
+ table: '<%= table_name %>',
132
+ ran_on: Time.now,
133
+ status: 'pending',
134
+ direction: 'FileMaker to ActiveRecord'
135
+ )
136
+ end
137
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Harmonia
4
+ class Sync < ApplicationRecord
5
+ self.table_name = 'harmonia_syncs'
6
+
7
+ validates :table, presence: true
8
+ validates :ran_on, presence: true
9
+ validates :status, presence: true, inclusion: { in: %w[pending in_progress completed failed] }
10
+
11
+ # Scope to get syncs for a specific table
12
+ scope :for_table, ->(table_name) { where(table: table_name) }
13
+ scope :for_direction, ->(direction) {where(direction:)}
14
+
15
+ # Scope to get recent syncs
16
+ scope :recent, -> { order(ran_on: :desc) }
17
+
18
+ # Scope to get syncs by date
19
+ scope :on_date, ->(date) { where(ran_on: date) }
20
+
21
+ # Scope by status
22
+ scope :pending, -> { where(status: 'pending') }
23
+ scope :in_progress, -> { where(status: 'in_progress') }
24
+ scope :completed, -> { where(status: 'completed') }
25
+ scope :failed, -> { where(status: 'failed') }
26
+
27
+ # Get the most recent sync for a table in a given direction
28
+ def self.last_sync_for(table_name, direction)
29
+ for_direction(direction).for_table(table_name).recent.first
30
+ end
31
+
32
+ # Calculate sync completion percentage
33
+ def completion_percentage
34
+ return 0 if records_required.to_i.zero?
35
+ ((records_synced.to_f / records_required.to_f) * 100).round(2)
36
+ end
37
+
38
+ # Check if sync was complete
39
+ def complete?
40
+ status == 'completed' && records_synced == records_required
41
+ end
42
+
43
+ # Mark sync as started
44
+ def start!
45
+ update!(status: 'in_progress')
46
+ end
47
+
48
+ # Mark sync as completed
49
+ def finish!(records_synced:, records_required:)
50
+ update!(
51
+ status: 'completed',
52
+ records_synced: records_synced,
53
+ records_required: records_required
54
+ )
55
+ end
56
+
57
+ # Mark sync as failed
58
+ def fail!(error_message)
59
+ update!(
60
+ status: 'failed',
61
+ error_message: error_message
62
+ )
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Trophonius
4
+ class Model
5
+ # Converts a Trophonius record to PostgreSQL-compatible attributes
6
+ # @param record [Trophonius::Record] The Trophonius record instance to convert
7
+ def self.to_pg(record)
8
+ raise StandardError, 'Implement to_pg'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Harmonia
4
+ class Railtie < Rails::Railtie
5
+ generators do
6
+ require 'generators/harmonia/install_generator'
7
+ require 'generators/harmonia/sync_generator'
8
+ end
9
+ end
10
+ end