Class: ProjectUnification::Service
- Inherits:
-
Object
- Object
- ProjectUnification::Service
- Defined in:
- lib/project_unification.rb
Constant Summary collapse
- UNIFY_CUTOFF =
1000
Instance Attribute Summary collapse
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#results ⇒ Object
readonly
Returns the value of attribute results.
-
#source_project ⇒ Object
readonly
Returns the value of attribute source_project.
-
#target_project ⇒ Object
readonly
Returns the value of attribute target_project.
Instance Method Summary collapse
-
#initialize(source_project:, target_project:, options: {}) ⇒ Service
constructor
A new instance of Service.
-
#run_cleanup(merge_registry) ⇒ Object
private
Iterate the merge registry produced by Phase 1 and collapse each sentinel record into its canonical target.
- #run_migration ⇒ Object private
-
#unify ⇒ Hash
Execute the unification process.
- #validate_prerequisites! ⇒ Object private
Constructor Details
#initialize(source_project:, target_project:, options: {}) ⇒ Service
Returns a new instance of Service.
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/project_unification.rb', line 30 def initialize(source_project:, target_project:, options: {}) @source_project = source_project @target_project = target_project @options = { preview: true, skip_cached_rebuild: false }.merge() @results = { unified: false, preview_mode: @options[:preview], source_project_id: source_project.id, target_project_id: target_project.id, started_at: nil, completed_at: nil, duration_seconds: 0, statistics: {}, details_by_model: {}, conflicts: [], errors: [], rollback_performed: false } end |
Instance Attribute Details
#options ⇒ Object (readonly)
Returns the value of attribute options.
15 16 17 |
# File 'lib/project_unification.rb', line 15 def @options end |
#results ⇒ Object (readonly)
Returns the value of attribute results.
15 16 17 |
# File 'lib/project_unification.rb', line 15 def results @results end |
#source_project ⇒ Object (readonly)
Returns the value of attribute source_project.
15 16 17 |
# File 'lib/project_unification.rb', line 15 def source_project @source_project end |
#target_project ⇒ Object (readonly)
Returns the value of attribute target_project.
15 16 17 |
# File 'lib/project_unification.rb', line 15 def target_project @target_project end |
Instance Method Details
#run_cleanup(merge_registry) ⇒ Object (private)
Iterate the merge registry produced by Phase 1 and collapse each sentinel record into its canonical target. Failures are collected as errors (not raised) so one bad pair does not abort the remaining cleanup.
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/project_unification.rb', line 160 def run_cleanup(merge_registry) merge_registry.each do |entry| klass = entry[:model].constantize target = klass.find(entry[:target_id]) renamed = klass.find(entry[:renamed_id]) # TODO: this pre-step should live in Image#unify (or a before_unify hook) # so that any image merge handles it, not just project unification. # Image has dependent: :restrict_with_error on depictions, so unify # rolls back if any Depiction (on community data like Person) can't be # rerouted due to a uniqueness conflict. if entry[:model] == 'Image' renamed.depictions.find_each do |sentinel_dep| target_dep = Depiction.find_by( image_id: target.id, depiction_object_type: sentinel_dep.depiction_object_type, depiction_object_id: sentinel_dep.depiction_object_id ) target_dep.unify(sentinel_dep, cutoff: UNIFY_CUTOFF) if target_dep end end result = target.unify(renamed, cutoff: UNIFY_CUTOFF) unless result[:result][:unified] @results[:errors] << { model: entry[:model], error: "Post-migration unify failed for #{entry[:model]} ID #{entry[:renamed_id]}: #{result[:result][:message]}" } end rescue => e @results[:errors] << { model: entry[:model], error: e. } end end |
#run_migration ⇒ Object (private)
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/project_unification.rb', line 131 def run_migration migrator = ProjectUnification::Migrator.new( source_project_id: source_project.id, target_project_id: target_project.id, options: @options ) migration_results = migrator.migrate_all @results[:statistics] = migration_results[:statistics] @results[:details_by_model] = migration_results[:details_by_model] @results[:conflicts].concat(migration_results[:conflicts]) @results[:errors].concat(migration_results[:errors]) # Run unify on records that need it - both sides are now in the target # project, so Shared::Unify works without cross-project restrictions. run_cleanup(migration_results[:merge_registry] || []) # Rebuild cached fields unless skipped unless @options[:skip_cached_rebuild] || @options[:preview] rebuilder = ProjectUnification::CachedRebuilder.new(target_project.id) rebuild_results = rebuilder.rebuild_all @results[:cached_rebuild] = rebuild_results end end |
#unify ⇒ Hash
Execute the unification process
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/project_unification.rb', line 56 def unify @results[:started_at] = Time.now validate_prerequisites! # Capture ambient Current state so we can restore it after (important if # called from a request context; from a rake task they'd both be nil). saved_user_id = Current.user_id saved_project_id = Current.project_id # Migration must run under the provided user so that updated_by_id is set # correctly on every record. project_id is cleared so that # find_or_create_by calls in callbacks cannot accidentally scope to the # caller's ambient project. Current.user_id = @options[:user_id] Current.project_id = nil # Allows for cross-project saves Utilities::ThreadStore[:tw_project_unification] = true Project.transaction do run_migration @results[:unified] = @results[:errors].empty? && @results[:conflicts].empty? if @options[:preview] || @results[:errors].any? || @results[:conflicts].any? raise ActiveRecord::Rollback end rescue ActiveRecord::Rollback @results[:rollback_performed] = true raise rescue StandardError => e @results[:errors] << { model: 'Transaction', error: e., backtrace: e.backtrace.first(5) } @results[:rollback_performed] = true raise ActiveRecord::Rollback rescue Exception => e @results[:rollback_performed] = true raise end @results[:completed_at] = Time.now @results[:duration_seconds] = (@results[:completed_at] - @results[:started_at]).round(2) @results ensure Utilities::ThreadStore[:tw_project_unification] = nil Current.user_id = saved_user_id Current.project_id = saved_project_id end |
#validate_prerequisites! ⇒ Object (private)
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/project_unification.rb', line 112 def validate_prerequisites! if source_project.id == target_project.id raise ArgumentError, 'Cannot unify a project with itself' end if @options[:root_taxon_name_id] target_taxon = TaxonName.find_by(id: @options[:root_taxon_name_id]) unless target_taxon && target_taxon.project_id == target_project.id raise ArgumentError, 'root_taxon_name_id must belong to target project' end end if @options[:user_id] unless User.find_by(id: @options[:user_id]) raise ArgumentError, 'user_id must be a valid user' end end end |