Whats a succinct way to express 'If an array exists then do' - ruby-on-rails

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.

Related

Equivalent of find_each for foo_ids?

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.

Nice array from pluck

I have a model and I love the pluck method I can use. If I do this:
#x = AwesomeModel.all.pluck(:column_one, :column_two)
then I get a multidimensional array: #x[][]. With my sad skills, I work with them using the numbers:
#x[0][1]
how can I can use pluck or a similar method to access the array something like this:
#x[0][:column_two]
If you are concerned about the structure of what you get back from the db, you should simply do:
#x = AwesomeModel.all.select(:column_one, :column_two)
Then you'd keep the fast db query advantage + have AwesomeModel instances, but with only column_one and column_two filled
Or if you desire to do it manually:
#x = AwesomeModel.all.pluck(:column_one, :column_two).map do |array|
OpenStruct.new({column_one: array[0], column_two: array[1] }) }
end
Then you can use it like a regular model:
#x[0].column_one
# or even
#x[0][:column_two]
You could do
class ActiveRecord::Base
def self.pluck_hash(*args)
plucked = pluck(*args)
plucked.map {|ary| Hash[args.zip ary]}
end
end
AwesomeModel.all.pluck_hash(:column_one, :column_two)
#=> [{:column_one => 'value', :column_two => 'value}, {...}, ... ]
First of all, don't use .all.pluck, because it returns an array of values, and that makes you loose all the advantages of ActiveRecord::Relation.
Instead use AwsomeModel.method directly, it would create the query but not run it until you need it, AwsomeModel.select(:column_1, :column_2) would create a
select (awesome_models.column_1, awsome_models.column_2)
query, and the result would be an array of ActiveRecord::Relation objects, which are still chainable, and values are still under keys of the column name eg:
AwsomeModel.select(:column_1, :column_2).first.column_1
Instead of
AwesomeModel.all.pluck(:column_1, :column_2).first[0] # or .first.first

Rails: Applying function to every record in table?

I have a table in Rails and I would like to find all records of a table where a certain function returns true.
What is the best way to do this? I could of course iterate over all the records in the table and use conditional statements to test whether the function, given the individual record, returns true and add it to a list.
Is there any easier way of doing this something along the lines of Model.find(:all, :conditions => {...}) maybe?
Thanks
Class MyModel < ActiveRecord
def self.targetted
find_each.select(&:predicate_method?)
end
def predicate_method?
#something that returns either true or false
end
end
this is a bit more Rails idiomatic :
find_each will fetch your record by batches of 1000. it is better than all for your memory
&:predicate_method : transforming a symbol into a Proc (with the # operator) will actually make your code call the method on each of the passed objects
def record_that_returns_true_for_xfunction
Model.all.select {|record| xfunction(record.some_column) == true}
end
This is seems like what you are looking for. This method will return an array of all the records where xfunction(record.some_column) == true.

Sending a code block to a find_all dynamic method

I am working with some complex queries using the dynamic find_all method and reached to a point where sending a block to that find_all method would really simplify my code.
Is there any plugin or work in-progress dealing with this?
In simple terms, I'd like to do something like:
#products = Product.find_all_by_ids(ids, .....) do |p|
# do something to each product like
p.stock += 10
end
Any other guide or better way of doing this would be greatly appreciated.
Rails 2.3 introduced the find_in_batches and find_each methods (see here) for batch processing of many records.
You can thus do stuff like:
Person.find_each(:conditions => "age > 21") do |person|
person.party_all_night!
end
I use the .each method which Enumerable provides like
#products = Product.find_all_by_ids(ids, .....)
#products.each { |p| p.stock += 10 }
There are even some extensions to Enumerable that Rails provides that might help you a bit if you're doing some common stuff.
Also, don't forget to save your objects with something like p.save if you want the changes to actually persist.
What's wrong with this:
#products = Product.find_all_by_ids(ids).each do |p|
p.stock+=10
end
In case you didn't know, each returns the array passed to it.

How do you iterate over active record objects in Ruby On Rails?

This question is quite simple but I have run into the problem a few times.
Let's say you do something like:
cars = Vehicle.find_by_num_wheels(4)
cars.each do |c|
puts "#{c.inspect}"
end
This works fine if cars is an array but fails if there is only one car in the database. Obviously I could do something like "if !cars.length.nil?" or check some other way if the cars object is an array before calling .each, but that is a bit annoying to do every time.
Is there something similar to .each that handles this check for you? Or is there an easy way to force the query result into an array regardless of the size?
You might be looking for
cars = Vehicle.find_all_by_num_wheels(4)
The dynamic find_by_ methods only return one element and you have to use find_all_by_ to return multiple.
If you always want all of the cars, you should use find_all instead:
cars = Vehicle.find_all_by_num_wheels(4)
You could also turn a single Vehicle into an array with:
cars = [cars] unless cars.respond_to?(:each)
Named scoped version for your problem
Vehicle.scoped(:conditions => { :num_wheels => 4 } ).each { |car| car.inspect }
You can do this to get arrays everytimes :
cars = Vehicle.find(:all, :conditions => {num_wheels => 4})
I don't think that you have a loop that will check if the object is an array.
Another solution could be:
for i in (1..cars.lenght)
puts cars[i].inspect
end
(haven't tested, it might break to test the lenght on a string. Let me know if it does)

Resources