save an active records array - ruby-on-rails

I have an array like this
a = []
a << B.new(:name => "c")
a << B.new(:name => "s")
a << B.new(:name => "e")
a << B.new(:name => "t")
How i can save it at once?

B.transaction do
a.each(&:save!)
end
This will create a transaction that loops through each element of the array and calls element.save on it.
You can read about ActiveRecord Transactions and the each method in the Rails and Ruby APIs.

a.each(&:save)
This will call B#save on each item in the array.

So I think we need a middle ground to Alexey's raising exceptions and aborting the transaction and Jordan's one-liner solution. May I propose:
B.transaction do
success = a.map(&:save)
unless success.all?
errored = a.select{|b| !b.errors.blank?}
# do something with the errored values
raise ActiveRecord::Rollback
end
end
This will give you a bit of both worlds: a transaction with rollback, knowledge of which records failed and even gives you access to the validation errors therein.

Wrapping save in transaction will not be enough: if a validation is not passed, there will be no exception raised and no rollback triggered.
I can suggest this:
B.transaction do
a.each do |o|
raise ActiveRecord::Rollback unless o.save
end
end
Just doing B.transaction do a.each(&:save!) end is not an option either, because the transaction block will not rescue any exception other than ActiveRecord::Rollback, and the application would crash on failed validation.
I do not know how to check afterwards if the records have been saved.
Update. As someone has downrated my answer, i assume that the person was looking for a cut-and-paste solution :), so here is some (ugly :)) way to process fail/success value:
save_failed = nil
B.transaction do
a.each do |o|
unless o.save
save_failed = true
raise ActiveRecord::Rollback
end
end
end
if save_failed
# ...
else
# ...
end

I know this is an old question but I'm suprised no one thought of this:
B.transaction do
broken = a.reject { |o| o.save }
raise ActiveRecord::Rollback if broken.present?
end
if broken.present?
# error message
end

In case you're looking for more efficient solution than save each row in the loop please look my answer here Ruby on Rails - Import Data from a CSV file
I'm suggesting to use gem activerecord-import there.

Related

How do I add default error handling to a function which produces an iterable?

I have a model, Transaction, and a method, external_evaluation. external_evaluation works its way down the stack and eventually calls out to an out to an AWS lambda. When the response is bad, a BadResponse exception is raised.
There is a pattern in the codebase that gets used frequently that goes something like
def get_some_transactions()
Transaction.where(some_column: some_expression)
end
def do_some_stuff()
get_some_transactions.each do |transaction|
do_something(transaction.external_evaluation)
rescue BadResponse => e
log(e)
next
end
end
def do_some_other_stuff()
get_some_transactions.each_with_object({}) do |transaction, transaction_hash|
transaction_hash[transaction] = do_something_else(transaction.external_evaluation)
rescue BadResponse => e
log(e)
next
end
end
I really dislike the duplication of the error handling code in this pattern, and would like to be able to add default error handling into get_some_transactions which will apply regardless of which iteration function is called (each, each_with_object, each_with_index, ...). Is there an idiomatic way to do this in Ruby?
def with_error_handing(&block)
begin
yield
rescue BadResponse => e
log(e)
end
end
def do_some_stuff()
get_some_transactions.each do |transaction|
with_error_handing do
do_something(transaction.external_evaluation)
end
end
end
def do_some_other_stuff()
get_some_transactions.each_with_object({}) do |transaction, transaction_hash|
with_error_handing do
transaction_hash[transaction] = do_something_else(transaction.external_evaluation)
end
end
end
You can move the rescue to external_evaluation method.

How to DRY a list of functions in ruby that are differ only by a single line of code?

I have a User model in a ROR application that has multiple methods like this
#getClient() returns an object that knows how to find certain info for a date
#processHeaders() is a function that processes output and updates some values in the database
#refreshToken() is function that is called when an error occurs when requesting data from the object returned by getClient()
def transactions_on_date(date)
if blocked?
# do something
else
begin
output = getClient().transactions(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().transactions(date)
process_fitbit_rate_headers(output)
return output
end
end
end
def events_on_date(date)
if blocked?
# do something
else
begin
output = getClient().events(date)
processHeaders(output)
return output
rescue UnauthorizedError => ex
refresh_token()
output = getClient().events(date)
processHeaders(output)
return output
end
end
end
I have several functions in my User class that look exactly the same. The only difference among these functions is the line output = getClient().something(date). Is there a way that I can make this code look cleaner so that I do not have a repetitive list of functions.
The answer is usually passing in a block and doing it functional style:
def handle_blocking(date)
if blocked?
# do something
else
begin
output = yield(date)
processHeaders(output)
output
rescue UnauthorizedError => ex
refresh_token
output = yield(date)
process_fitbit_rate_headers(output)
output
end
end
end
Then you call it this way:
handle_blocking(date) do |date|
getClient.something(date)
end
That allows a lot of customization. The yield call executes the block of code you've supplied and passes in the date argument to it.
The process of DRYing up your code often involves looking for patterns and boiling them down to useful methods like this. Using a functional approach can keep things clean.
Yes, you can use Object#send: getClient().send(:method_name, date).
BTW, getClient is not a proper Ruby method name. It should be get_client.
How about a combination of both answers:
class User
def method_missing sym, *args
m_name = sym.to_s
if m_name.end_with? '_on_date'
prop = m_name.split('_').first.to_sym
handle_blocking(args.first) { getClient().send(prop, args.first) }
else
super(sym, *args)
end
end
def respond_to? sym, private=false
m_name.end_with?('_on_date') || super(sym, private)
end
def handle_blocking date
# see other answer
end
end
Then you can call "transaction_on_date", "events_on_date", "foo_on_date" and it would work.

Rollback in Rails?

In my Rails application, I have a method which copies many rows, and also goes on to copy down some of the parent-child relationships.
def merge
params[:merge_rows].each do |merge_row|
batch_detail = BatchDetail.find(merge_row)
batch_detail.duplicate
batch_detail.batch_id = batch.id
batch_detail.save
end
render nothing: true
end
# BatchDetail.duplicate
def duplicate
batch_detail = dup
batch_detail.primer3_parameter = primer3_parameter.dup if primer3_parameter.present?
primer3_outputs.each do |primer3_output|
batch_detail.primer3_outputs << primer3_output.duplicate
end
batch_detail
end
Ideally, I would like to only save if all rows are successfully duplicated, and rollback all changes if any are unsuccessful.
Then I would like to report 200 or 500 via the render if successful or error.
wrap your ActiveRecord changes in a transaction block, if the end of the block is bypassed by some exception, all changes are rolled back.
begin
ActiveRecord::Base.transaction do
...various transactions
if (some_error_condition)
raise
end
end
...stuff to do if all successful
rescue
...stuff to do on failure
end

Ruby on Rails: How to handle exceptions(rescue?)

I'm fetching videos' data from youtube, including general video info, and likes number of them.
The problem is that sometimes, there is no corresponding data. Maybe the video is private, or the likes number is 0.
In either case, there will report NoMethodError: undefined method 'likes' for nil:NilClass
How can I handle this? I just want to ignore them and continue to the next youtube video.
I think I can use something like rescue, but I don't find many tutorial of it.
Here is my code:
client = YouTubeIt::Client.new(:dev_key => my_key)
Video.all.each do |video|
video_info = client.video_by(video.url)
like_number = video_info.rating.likes.to_i
video.likes = like_number
if video.save
puts "#{video.title} likes: #{video.likes}"
end
end
Rather that using the exception handling, which is slower, in this case just check for the presence of the value before calling the method.
Assuming the error is generated on the line
like_number = video_info.rating.likes.to_i
simply use
client = YouTubeIt::Client.new(:dev_key => my_key)
Video.all.each do |video|
video_info = client.video_by(video.url)
# here check for the presence.
if video_info.rating
like_number = video_info.rating.likes.to_i
video.likes = like_number
if video.save
puts "#{video.title} likes: #{video.likes}"
end
end
end
You should check for all possible nil values with, eg, video.nil? and corresponding conditionals. Rescuing should always be your last resort (it's slow and dirty), but, if there is no way to predict where the script will fail, you can always rescue some piece of code:
begin
#your error-prone code
rescue
#action you want to take in case your error-prone code raises error
end
Here is a good article on exceptions: http://www.tutorialspoint.com/ruby/ruby_exceptions.htm.
In this example, the method body acts as default begin block so need of begin block
def any_method
client = YouTubeIt::Client.new(:dev_key => my_key)
Video.all.each do |video|
video_info = client.video_by(video.url)
# try tries to find the method, if not found returns nil
like_number = video_info.try(:rating).try(:likes).to_i
video.likes = like_number
if video.save
puts "#{video.title} likes: #{video.likes}"
end
end
# For any type of standard errors, use rescue block
rescue => error_object
p 'Sorry, some error occured'
p error_object
end

Why doesn't calling next within a rescue block within a transaction within a loop work?

I have a loop like this:
# Iterate a list of items
req_wf_list.each do |req_wf|
# Begin a transaction
ReqWf.transaction do # ReqWf is an ActiveRecord model class
# Do some things
# ...
# 1. I want to be able to continue processing with the
# next iteration of the loop if there is an error here
# 2. I also want to rollback the transaction associated with
# this particular iteration if I encounter an error
begin
# Do something that might return an error
rescue
# Do some error processing
puts "Caught such and such error"
# Don't complete transaction (rollback),
# don't "do some more things",
# proceed to next item in req_wf_list
next
end
# Do some more things
# Shouldn't make it here if there is an error but I do indeed make it here
# ...
# End transaction
end
# End loop
end
Now, I would expect that calling "next" within the rescue block would cause the transaction associated with that particular iteration of the loop to rollback and for execution to resume at the top of the next iteration of the loop. Instead, execution appears to resume at the "Do some more things" line. It is as if the "next" statement is completely ignored. What am I missing?
Most likely that in this case next applies to transaction so you are in a nested loop situation.
This is an example of what can be done to solve the issue
req_wf_list.each do |req_wf|
catch :go_here do #:missingyear acts as a label
ReqWf.transaction do
throw :go_here unless something #break out of two loops
end
end #You end up here if :go_here is thrown
end
But in general, it is not a good practice to use next. You should be able to put a global begin .. rescue and have all the conditions inside of it, so that nothing else gets executed once you catch an error.
Update
I did some a small test and the behavior is as you expect it.
loop = [1,2,3]
loop.each do |value|
puts "value => #{value}"
ActiveRecord::Base.transaction do
puts "Start transaction"
begin
raise
rescue
puts "ActiveRecord::StatementInvalid"
next
end
puts "Should not get here!"
end
end
The output is the following:
value => 1
Start transaction
ActiveRecord::StatementInvalid
value => 2
Start transaction
ActiveRecord::StatementInvalid
value => 3
Start transaction
ActiveRecord::StatementInvalid
Is it possible that you had another error in your code before the next was being called ?
In any case, using the next statement is not the best option as I said before.

Resources