module ActiveGraph module Migrations module Helpers module IdProperty extend ActiveSupport::Concern def populate_id_property(label) model = label.to_s.constantize max_per_batch = (ENV['MAX_PER_BATCH'] || default_max_per_batch).to_i last_time_taken = nil until (nodes_left = idless_count(label, model.primary_key)) == 0 print_status(last_time_taken, max_per_batch, nodes_left) count = [nodes_left, max_per_batch].min last_time_taken = Benchmark.realtime do max_per_batch = id_batch_set(label, model.primary_key, Array.new(count) { new_id_for(model) }, count) end end end protected def idless_count(label, id_property) query.match(n: label).where("n.#{id_property} IS NULL").pluck('COUNT(n) AS ids').first end def id_batch_set(label, id_property, new_ids, count) ActiveGraph::Base.transaction do execute("MATCH (n:`#{label}`) WHERE n.#{id_property} IS NULL with COLLECT(n) as nodes, #{new_ids} as ids FOREACH(i in range(0,#{count - 1})| FOREACH(node in [nodes[i]]| SET node.#{id_property} = ids[i])) RETURN distinct(true) LIMIT #{count}") count end rescue ActiveGraph::Server::CypherResponse::ResponseError new_max_per_batch = (max_per_batch * 0.8).round output "Error querying #{max_per_batch} nodes. Trying #{new_max_per_batch}" new_max_per_batch end def print_status(last_time_taken, max_per_batch, nodes_left) time_per_node = last_time_taken / max_per_batch if last_time_taken message = if time_per_node eta_seconds = (nodes_left * time_per_node).round "#{nodes_left} nodes left. Last batch: #{(time_per_node * 1000.0).round(1)}ms / node (ETA: #{eta_seconds / 60} minutes)" else 'Running first batch...' end output message end def default_max_per_batch 900 end def new_id_for(model) if model.id_property_info[:type][:auto] SecureRandom.uuid else model.new.send(model.id_property_info[:type][:on]) end end end end end end