Reputation: 12072
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
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
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