Sylar
Sylar

Reputation: 12072

Update Each Array-Object Value in Rails

Basically I want to update each table column for a Model in Rails 5.

str = "abc---def"

str.split('---').map do |a|
 Foo.where(product_id:1).update_all(bar: a)
end

Old object would be like:

[
 [0] { product_id: 1,
       ...,
       bar: "xxx",
       ...
     },
 [1] { product_id: 1,
       ...,
       bar: "xxx",
       ...
     }

]

New should be like:

[
 [0] { product_id: 1,
       ...,
       bar: "abc",
       ...
     },
 [1] { product_id: 1,
       ...,
       bar: "def",
       ...
     }

]

But what I got is bar: "def" for each. Is there a clean method in rails to achieve what I want? update_attributes gives an error.

Is the title name correct?

Upvotes: 1

Views: 5467

Answers (2)

Qaisar Nadeem
Qaisar Nadeem

Reputation: 2434

First of all let's get started from some basics.

You want to update multiple rows and want to set different value for each row. So it cannot be done in single query like you are doing. So you need to loop through the Foo objects and set each one separately.

So let's assume

str = "abc---def---ghi---jkl"
tokens = str.split('---') 
foos_to_update = Foo.where(product_id: 1) #Let's assume it will return 4 or lesser records. (otherwise you need to tell what do you wanna do if it returns more then `tokens`)
foos_to_update.each_with_index {|foo,i| foo.update(bar: tokens[i])}

The last line is looping through returned objects and setting the bar value for each object.

Upvotes: 4

max pleaner
max pleaner

Reputation: 26758

First of all, using Foo.where(id:1).update_all to update a single record may work, but is non-idiomatic. It's better to use Foo.find_by(id: 1).update. For getting single records, I prefer to use find_by instead of find because it returns nil instead of raising NotFound errors, but that's a personal preference.

Second, the way you're using update_all(bar: a) is giving you unexpected results. In a map block, the returned value becomes part of the resulting array. update_all doesn't return the record which were changed. It returns an integer showing the count of records which were changed. Similarly, update doesn't return the record. It returns true or false` depending on if the validations passed.

Tying together these concepts, the following code can be written:

str = "abc---def"    
str.split('---').map do |a|
 foo = Foo.find_by(id:1)
 foo&.update(bar: a)
 foo
end

# note that you could instead write `foo.update(bar: a)` if you
# don't want to use the safe navigation operator

Or another way to write it which does the same thing:

str = "abc---def"
str.split('---').map do |a|
 Foo.find_by(id:1)&.tap { |foo| foo.update(bar: a) }
end

Note that in these examples I'm using the safe navigation operator which is in Ruby versions newer than 2.3. It helps prevent NoMethodError on nil objects, but isn't really necessary.

Upvotes: 2

Related Questions