Search code examples
ruby-on-railsbatch-processingacts-as-audited

Rails audited create in batch


I work with rails 5 / audited 4.6.5 I have batch actions on more than 2000 items on once. To make it usable, I need to use the updatre_all function.

Then, I would like to create the needed audited in one time

What I would like to do is something like that :

Followup.where(id: ids).in_batches(of: 500) do |group|
  #quick update for user responsiveness
  group.update_all(step_id: 1)
  group.delay.newAudits(step_id: 1)
end

But the audited gem looks to be to basic for that. I'm sure a lot of poeple faced issue like that before


Solution

  • after a lot of iteration, I manage to create an optimized query. I put it in an AditBatch class and also add delay

    class AuditBatch
    
    
    ############################################
    ## Init a batch update from a list of ids
    ## the aim is to avoid instantiations in controllers
    ## @param string class name (eq: 'Followup')
    ## @param int[] ids of object to update
    ## @param changet hash of changes {key1: newval, key2: newval}
    ############################################
    
    def self.init_batch_creation(auditable_type, auditable_ids, changes)
        obj = Object.const_get(auditable_type)
        group = obj.where(id: auditable_ids)
        AuditBatch.delay.batch_creation(group, changes, false)
    end
    
    ############################################
    ## insert a long list of audits in one time
    ## @param group array array of auditable objects
    ## @param changet hash of changes {key1: newval, key2: newval}
    ############################################
     def self.batch_creation(group, changes, delayed = true)
    
      sql = 'INSERT INTO audits ("action", "audited_changes", "auditable_id", "auditable_type", "created_at", "version", "request_uuid") 
      VALUES '
    
      total = group.size
      group.each_with_index  do |g, index|
    
        parameters = 'json_build_object('
        length = changes.size
        i=1
        changes.each do |key, val|
          parameters += "'#{key}',"
          parameters += "json_build_array("
          parameters += "(SELECT ((audited_changes -> '#{key}')::json->>1) FROM audits WHERE auditable_id = #{g.id} order by id desc limit 1),"
          parameters += val.is_a?(String) ? "'#{val.to_s}'" : val.to_s 
          parameters += ')'
          parameters += ',' if i < length
          i +=1
        end
        parameters += ')'
    
        sql += "('update', #{parameters}, #{g.id}, '#{g.class.name}', '#{Time.now}', (SELECT max(version) FROM audits where auditable_id= #{g.id})+1, '#{SecureRandom.uuid}')"
        sql += ", " if (index+1) < total
      end
      if delayed==true
        AuditBatch.delay.execute_delayed_sql(sql)
      else
        ActiveRecord::Base.connection.execute(sql)
     end
    
     end
    
    def self.execute_delayed_sql(sql)
      ActiveRecord::Base.connection.execute(sql)
    end
    end