I want to assign_attribute to various rows in a table and then save them together in a loop. So here is how I am trying to do,
player_arr = []
params[:player_types].each do |index,p_type_params|
if p_type_params[:id]
player_arr << #player_types.find(p_type_params[:id]).assign_attributes(p_type_params)
end
end
Later I wish to do a save atomically on all the updates as a transaction,
ActiveRecord::Base.transaction do
player_arr.each do |p_type|
p_type.save
end
end
But this does not seem to work as p_type seems to be NilClass. I dont understand why because when I do player_arr.length I get a positive number.
Also the goal here is to capture all the assignment errors in the first loop and then do an atomic save. Ofcourse I can save in the first loop itself but it will capture only the first encountered error. Any pointers will be really helpful
The problem seems to be that you are doing too much in one line.
player_arr << #player_types.find(p_type_params[:id]).assign_attributes(p_type_params)
Here you are adding the return value of assign_attributes (nil) to the player_arr array.
Do this instead:
player_arr = []
params[:player_types].each do |index,p_type_params|
if p_type_params[:id]
player = #player_types.find(p_type_params[:id])
player.assign_attributes(p_type_params)
player_arr << player
end
end
Related
I have a model Product that has attribute description and code which is an index.
I would like to alter the product in code based on a CSV file.
What is faster?
#p = Product.find_by_code(row[:code])
if #p.description != row[:desc]
#p.update_attribute(:description, row[:desc])
or
#p = Product.find_by_code(row[:code])
#p.update_attribute(:description, row[:desc])
Let's consider all cases, such as descriptions are equal and not equal at all.
How is = comparison implemented for strings and texts?
You should use the ruby Benchmark module and directly measure that !
require 'benchmark'
Benchmark.bm do |x|
x.report('sort!') do
#p = Product.find_by(code: row[:code])
if #p.description != row[:desc]
#p.description = row[:desc]
p.save
end
end
x.report('sort') do
#p = Product.find_by(code: row[:code])
#p.description = row[:desc]
p.save
end
end
Ruby on Rails is clever enough to know whether an attribute has actually changed, and so won't roundtrip to the database to update a field when it hasn't changed. You can see this on the Rails console (rails c) if you run your update_attribute code with the same value, and then with a changed value - you'll only see the SQL log output when it's changed.
If you use update_attributes instead (which takes a hash of attributes to change) and there is nothing to update, you'll see it does begin and end a transaction with the database, albeit with no commands within it.
Hope that helps!
i was writing code to append new email to an empty array in my Rails App. like this:
#users_email = Array.new
#users_email << User.find_by_id(#comment.user_id).email
#users_email << User.find_by_id(Comment.find_by_id(#parent_id).user_id).email if !#parent_id.nil?
Note: Here #comment is just a hash of id, user_id, parent_id and #parent_id is a the id of a Parent of any Comment. as in Hierarchy of Parent-child.
But instead of having two items in array ex: ["ak#xyz.com", "aks#xyz.com"] I am getting only one item ex:["aks#xyz.com"] after appending second item.
My confusion starts when I try to save above expression in an Instance variable and tries to append the same into an empty array, It behaves as expected.
Ex:
#users_email = Array.new
#first = User.find_by_id(#comment.user_id).email
#second = User.find_by_id(Comment.find_by_id(#parent_id).user_id).email if !#parent_id.nil?
#users_email << #first # ["ak#xyz.com"]
#users_email << #second # ["ak#xyz.com", "aks#xyz.com"]
where as this wasn't the case in previous one.
Can someone please explain whats happening here.
Update
By mistake I have place "=" instead of "<<" while appending first element to the array for the first case and after appending second one I got the result something like "ak#xyz.comaks#xyz.com" but for some reason in my Rails Application I was getting only the first email id in return.ex: ["ak#xyz.com"]
So, probably this question is now not relevant to the issue raised. Sorry for bugging all of you.
That's because you have this check !#parent_id.nil? at the end of the following statement:
#users_email << User.find_by_id(Comment.find_by_id(#parent_id).user_id).email if !#parent_id.nil?
In the first case, #parent_id apparently is nil, so the statement never executes: hence only one value is appended to the array.
Aside: prefer using the array literal syntax (ary = [] over ary = Array.new)
Given this model:
class User < ActiveRecord::Base
has_many :things
end
Then we can do this::
#user = User.find(123)
#user.things.find_each{ |t| print t.name }
#user.thing_ids.each{ |id| print id }
There are a large number of #user.things and I want to iterate through only their ids in batches, like with find_each. Is there a handy way to do this?
The goal is to:
not load the entire thing_ids array into memory at once
still only load arrays of thing_ids, and not instantiate a Thing for each id
Rails 5 introduced in_batches method, which yields a relation and uses pluck(primary_key) internally. And we can make use of the where_values_hash method of the relation in order to retrieve already-plucked ids:
#user.things.in_batches { |batch_rel| p batch_rel.where_values_hash['id'] }
Note that in_batches has order and limit restrictions similar to find_each.
This approach is a bit hacky since it depends on the internal implementation of in_batches and will fail if in_batches stops plucking ids in the future. A non-hacky method would be batch_rel.pluck(:id), but this runs the same pluck query twice.
You can try something like below, the each slice will take 4 elements at a time and them you can loop around the 4
#user.thing_ids.each_slice(4) do |batch|
batch.each do |id|
puts id
end
end
It is, unfortunately, not a one-liner or helper that will allow you to do this, so instead:
limit = 1000
offset = 0
loop do
batch = #user.things.limit(limit).offset(offset).pluck(:id)
batch.each { |id| puts id }
break if batch.count < limit
offset += limit
end
UPDATE Final EDIT:
I have updated my answer after reviewing your updated question (not sure why you would downvote after I backed up my answer with source code to prove it...but I don't hold grudges :)
Here is my solution, tested and working, so you can accept this as the answer if it pleases you.
Below, I have extended ActiveRecord::Relation, overriding the find_in_batches method to accept one additional option, :relation. When set to true, it will return the activerecord relation to your block, so you can then use your desired method 'pluck' to get only the ids of the target query.
#put this file in your lib directory:
#active_record_extension.rb
module ARAExtension
extend ActiveSupport::Concern
def find_in_batches(options = {})
options.assert_valid_keys(:start, :batch_size, :relation)
relation = self
start = options[:start]
batch_size = options[:batch_size] || 1000
unless block_given?
return to_enum(:find_in_batches, options) do
total = start ? where(table[primary_key].gteq(start)).size : size
(total - 1).div(batch_size) + 1
end
end
if logger && (arel.orders.present? || arel.taken.present?)
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
relation = relation.reorder(batch_order).limit(batch_size)
records = start ? relation.where(table[primary_key].gteq(start)) : relation
records = records.to_a unless options[:relation]
while records.any?
records_size = records.size
primary_key_offset = records.last.id
raise "Primary key not included in the custom select clause" unless primary_key_offset
yield records
break if records_size < batch_size
records = relation.where(table[primary_key].gt(primary_key_offset))
records = records.to_a unless options[:relation]
end
end
end
ActiveRecord::Relation.send(:include, ARAExtension)
here is the initializer
#put this file in config/initializers directory:
#extensions.rb
require "active_record_extension"
Originally, this method forced a conversion of the relation to an array of activrecord objects and returned it to you. Now, I optionally allow you to return the query before the conversion to the array happens. Here is an example of how to use it:
#user.things.find_in_batches(:batch_size=>10, :relation=>true).each do |batch_query|
# do any kind of further querying/filtering/mapping that you want
# show that this is actually an activerecord relation, not an array of AR objects
puts batch_query.to_sql
# add more conditions to this query, this is just an example
batch_query = batch_query.where(:color=>"blue")
# pluck just the ids
puts batch_query.pluck(:id)
end
Ultimately, if you don't like any of the answers given on an SO post, you can roll-your-own solution. Consider only downvoting when an answer is either way off topic or not helpful in any way. We are all just trying to help. Downvoting an answer that has source code to prove it will only deter others from trying to help you.
Previous EDIT
In response to your comment (because my comment would not fit):
calling
thing_ids
internally uses
pluck
pluck internally uses
select_all
...which instantiates an activerecord Result
Previous 2nd EDIT:
This line of code within pluck returns an activerecord Result:
....
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
...
I just stepped through the source code for you. Using select_all will save you some memory, but in the end, an activerecord Result was still created and mapped over even when you are using the pluck method.
I would use something like this:
User.things.find_each(batch_size: 1000).map(&:id)
This will give you an array of the ids.
Is there a more succinct way of expressing the following:
if Model.all
array = Model.all
array.each do |a|
a.info
end
end
In my case, Model.all is a helper method (get_all_of_those()).
In the view, I am displaying data in tables based on the results. a.info might be
"<div class='row'>#{a.name}</div>"
Model.all is always truthy and is always an array-like object (Strictly speaking it's ActiveRecord::Relation object in rails 4; an Array in rails 3). You can just do:
Model.all.each do |a|
a.info
end
If there are no models, the loop will not be executed even once.
(Note however, that this code doesn't do anything interesting with models, so you need to update your question with: What do you want the final result to be? There is a chance that you are looking for Model.pluck(:info))
If info is a field in the database, you could do this more efficiently with
array = Model.pluck(:info)
Try this out:
Model.all.find_each do |a|
a.info
end
Read more about find_each in the documentation.
I have active record object of array.
#obj = User.post
Now I want to loop through #obj from third element to last. I want something like below
#obj.third-to-last.each do
#output here
end
I can do this using a counter and if else condition. I want know if there is any better and simple way to do this.
Use the Array#drop method to drop the first two elements of #obj:
#obj.drop(2).each do |obj|
# whatever...
end
Just do using method ary[range] :
#obj[2..-1].each do |el|
# your code here
end