Victor Ronin
Victor Ronin

Reputation: 23288

Iterate through parameters array and ActiveRecord:Relation

I have a controller which needs to implement bulk update. (However, it needs to set specific values to each object vs the same value for all objects).

Here is the array which the controller will get

[
  {
    "task_id": 1,
    "some_property": "value1"
  },
  {
    "task_id": 2,
    "some_property": "value2"
  },
]

I need to find all tasks and for each task update the property to a provided value.

The obvious solution is

task_ids = params[::_json].map { |task| task[:task_id] }
tasks = Task.where(id: task_ids)

tasks.each do |task| 
  params[::_json].each do |task_from_params| do
    if task.id == task_form_params[:task_id] 
       task.some_property = task_form_params[:some_property]
       task.save!
    end
  end
end

The thing which I don't like (big time) is the code where we do N^2 comparisons.

I am pretty sure there should be a better way to do in Rails. I am looking for something more concise which doesn't require N^2 comparisons.

Upvotes: 0

Views: 1554

Answers (1)

Greg Navis
Greg Navis

Reputation: 2934

Option 1: ActiveRecord::Relation#update_all

If you don't care about validations and callbacks then you can simply use:

params.each do |param|
  Task.where(id: param.fetch('task_id')).
    update_all(some_property: param.fetch('some_property')
end

This will iterate over N items in params and issue N UPDATEs and no SELECTs.

Option 2: Convert to a hash mapping ID to property

If you do care about validations or callbacks then you can convert your input array to a hash first:

# Convert params to a hash. Iterate over N items.
id_to_property = params.map do |param|
  [param.fetch('task_id'), param.fetch('some_property')]
end.to_h

# Load corresponding tasks. Iterate over N keys.
Task.where(id: id_to_property.keys).find_each do |task|
  # Look up the property value - O(1).
  task.update!(some_property: id_to_property[task.id])
end

Upvotes: 1

Related Questions