0

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

Jaycreation
  • 2,029
  • 1
  • 15
  • 30

2 Answers2

2

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
Jaycreation
  • 2,029
  • 1
  • 15
  • 30
0

With group.update_all your callbacks are skipped and it doesn't end up recording changes in new audit.

You cannot manually create audits for those records, and even if you can create that audit changes manually, you will need the reference of "what changed?" (goes in audited_changes). But those changes are already lost when you did update_all on group earlier.

(`action`, `audited_changes`, `auditable_id`, `auditable_type`, `created_at`, `version`, `request_uuid`)

It is also documented in this audited issue - https://github.com/collectiveidea/audited/issues/352

paper_trail, another such gem which retains change_logs, also has this issue: https://github.com/airblade/paper_trail/issues/337

kiddorails
  • 12,961
  • 2
  • 32
  • 41
  • thank you for you help. What i'm looking for is a way to passe arguments on a creat method (maybe the instance and the chages) – Jaycreation Oct 26 '17 at 11:04