Using an Enumerator instead of loops - ruby

I usually use loop as below to request data form an external API or DB (redis pop):
records = []
loop do
record = MyHandler.new(token).fetch
break unless record
records.push(record)
end
It works, but to make it look better, I wonder whether there is any way to use an Enumerator. Does anyone know one?

Wrapping your code in an Enumerator is quite easy:
record_enumerator = Enumerator.new do |y|
loop do
record = MyHandler.new(token).fetch
break unless record
y << record
end
end
You can now iterate over the records using a block:
record_enumerator.each { |record|
# do something with record
}
Or fetch all records with:
records = record_enumerator.to_a
If MyHandler is your class, you could implement MyHandler#each instead and include Enumerable.

How about this?
while(record = MyHandler.new(token).fetch)
records.push(record)
end
That gets rid of the ugly loop/break logic.
Alternatively, you could create an ennumerable object instead of using the records array:
class RecordList
include Enumerable
attr :token
def initialize(token)
#token = token
end
def each
MyHandler.new(token).fetch
end
end
records = RecordList.new token

You can use while loops in ruby like so:
records = []
record = MyHandler.new(token).fetch
while record do
records << record
record = MyHandler.new(token).fetch
end
There might be an even more elegent way, but from the information provided, that's the best I can come up with.

If you are looking for the really short syntax, here you go:
records << record while record = MyHandler.new(token).fetch
It will work like a charm, the condition check is done before executing the loop body, hence record is always initialized.

Related

Hash not being added to array

I give up, I have no idea why the hashes I'm creating are not being added to the end of the array. When I pp the hash it is correct, but for some reason the first hash is getting duplicated, while the second hash isn't being added..
The result I'm getting is this:
[{:id=>"36757153479", :quantity=>1, :status=>"new"},
{:id=>"36757153479", :quantity=>1, :status=>"new"}]
#notice that the id is the same
While what I want is this:
[{:id=>"36767751239", :quantity=>1, :status=>"new"},
{:id=>"36757153479", :quantity=>1, :status=>"new"}]
The incoming array looks like this:
me = [{"id"=>36767751239, "quantity"=>1,"vendor"=>"Martha
Stewart", "product_id"=>9707911431, "gift_card"=>false}, {"id"=>36757153479,
"quantity"=>1, "vendor"=>"Naturalizer", "product_id"=>9707504007,
"gift_card"=>false}]
And my code that steps thru it is this:
incoming_cart_array = []
incoming_cart_hash = {}
unless me.nil?
me.each do |product|
incoming_cart_hash[:id] = product['variant_id'].to_s
incoming_cart_hash[:quantity] = product['quantity']
incoming_cart_hash[:status] = "new"
incoming_cart_array << incoming_cart_hash
end
end
I've done this sort of thing 100's of times, but somehow this isn't working. Its probably something right in front of me, I just can't see it.
Thanks
I seemed to be able to solve it as
incoming_cart_array = []
unless me.nil?
me.each do |product|
incoming_cart_hash = {}
incoming_cart_hash[:id] = product['id'].to_s
incoming_cart_hash[:quantity] = product['quantity']
incoming_cart_hash[:status] = "new"
incoming_cart_array << incoming_cart_hash
end
end
However, I cannot seem to find the reason, that it cannot overwrite incoming_cart_hash[:id] when it isn't defined in the same scope.
I'll dig into it, and update my answer if I figure it out!
Edit: My first initial though after a little debugging is, that when the hash isn't a local variable, it's defined (In the Ruby Source, which is C-based), as a pointer to the hash-type. Therefore in the array << hash line, you're inserting a pointer to the hash in the array. When you're running me.each n-times (2 in this case), the hash is updated, thus you'll have n-pointers in the array, all pointing to the same element. The hash which you're updating. It's seen as the same ruby object.
If you're outputting incoming_cart_hash.object_id inside the loop, each time, you'll see that the object_id is the same every time, when the hash-definition is outside the loop. However, when it's inside - defined as a new local variable every time, it'll differ, as it is a new and redefined object every time.
I found a bit of notes about it here: Ruby - Parameters by reference or by value?
Your code creates only one {} ever, so you get an array with n times the same hash.
You must create a new {} for each iteration.
incoming_cart_array = []
unless me.nil?
me.each do |product|
incoming_cart_hash = {}
...
end
end
Pro tipp — best practice in Ruby is to use map to create and array from an array. And you can use literal syntax to create a new hash, and use && to check for the nil case.
incoming_cart_array = me && me.map do | product |
{
id: product['id'].to_s,
quantity: product['quantity'],
status: "new",
}
end

check if a list of object is blank in rails

lets say I have this
#objects = SampleObject.all
then I want to check if #objects is blank, I could the ff:
unless #objects.blank?
#objects.each do |object|
end
else
..
end
however, doing so will trigger rails to execute a SELECT count(*) query
so instead, I could do something like
unless #objects.length > 0
is there a way to override the .blank? given a particular class?
say
def self.empty?
self.length > 0 ? false : true <br>
end
You should use ActiveRecord::Relation#any? method:
if #objects.any?
# ...
end
which is (in this case) negation of ActiveRecord::Relation#empty? method:
unless #objects.empty?
# ...
end
blank? uses empty?, since blank? source code:
# File activesupport/lib/active_support/core_ext/object/blank.rb, line 13
def blank?
respond_to?(:empty?) ? empty? : !self
end
Now the docs about empty? says:
Returns true if the collection is empty.
If the collection has been
loaded or the :counter_sql option is provided, it is equivalent to
collection.size.zero?.
If the collection has not been loaded, it is
equivalent to collection.exists?.
If the collection has not already
been loaded and you are going to fetch the records anyway it is better
to check collection.length.zero?
So, it really depends weather the collection is loaded or not?
Both empty? & any? use SELEC COUNT(*) if the collection isn't loaded (reference), i think in your case SampleObject.all will be lazy loaded as #Marek said, thus the COUNT calls.
For your case i don't think you can avoid the COUNT call since you want to fetch all records and eager loading all records just to avoid a second call to db just feels pointless (solving one performance issue by causing bigger one), however if its a subset collection, i believe there will be no second COUNT call.
SampleObject.all gives ActiveRecord::Relation object, you can use blank? also.
blank? - Returns true if relation is blank
Thus you can write as
unless #objects.blank?
# ...
end
You can call ActiveRecord::Relation#to_a to execute the query immediately:
#objects = SampleObject.all.to_a # runs the query, returns an array
if #objects.any? # no query, this is Enumerable#any?
# ...
end

Ruby - Invoking a class from a CONSTANT that contains the class name

I have a class that calls different suppliers to find if an item is available. How do I execute the class that each constant returns?
class ItemProvider
ADAPTER_ONE = Providers::ItemFromSupplierOne
ADAPTER_TWO = Providers::ItemFromSupplierTwo
def get_item(item)
id = ItemDetail.new(item)
%w(ADAPTER_ONE ADAPTER_TWO).each do |provider|
item_detail = provider.new(id)
break if item_detail.valid?
end
item_detail
end
Your problem is that you aren't making an array that contains the constants' values; you're making an array with the strings "ADAPTER_ONE" and "ADAPTER_TWO". The %w() syntax always makes an array of strings — it doesn't resolve variable names.
What you want is to change your get_item code to something like this:
def get_item(item)
id = ItemDetail.new(item)
[ADAPTER_ONE, ADAPTER_TWO].each do |provider|
item_detail = provider.new(id)
break item_detail if item_detail.valid?
end or nil # break automatically makes the block return the value you break with
end
As an aside, personally, I think I'd rewrite it like this:
def get_item(item)
id = ItemDetail.new(item)
[ADAPTER_ONE, ADAPTER_TWO].map {|provider| provider.new(id) }.find &:valid?
end
Yup you have an array of strings not constants but if you want to go down that road in using classes from strings well it will be nice if you look at http://blog.sidu.in/2008/02/loading-classes-from-strings-in-ruby.html#.UuGdmGQ1i2w .Maybe it is not directly related to your problem but it is a good read.

Find Value in Array of ActiveRecord Objects vs. Find Value from Repeated DB Calls

I would just like to return true, if my Array of Contact(s) (model) contains a Contact with id equal to some value. For example:
#contacts = Contact.all
#someval = "alskjdf"
find_val(#contacts, #someval)
def find_val(contacts, val)
#contact.each do |c|
if c.id == val
return true
end
end
return false
end
I have to do this repeatedly in my app (at most about 100 times for this particular actions), in order to exclude some data from an external API that has a list of contacts. Is this going to be too expensive?
I thought I might be able to do something faster, similar to ActiveRecord find on the array after it's been pulled down from the db, but can't figure that out. Would it be too expensive to call ActiveRecord like this?
Contacts.find_by_id(#someval)
The above line would have to be called hundreds of times... I figure iterating through the array would be less expensive. Thanks!
The best approach would be to index the contacts in a hash, using the contact id as the key after you retrieve them all from the db:
contacts = Contact.all.inject({}) {|hash, contact| hash[contact.id] = contact; hash }
Then you can easily get a contact with contact[id] in a performant way.
One way to reduce the amount of code you have to write to search the array is to open the array class and make a custom instance method:
class Array
def haz_value?(someval)
if self.first.respond_to? :id
self.select { |contact| contact.id == someval }.length > 0
else
false
end
end
end
Then, you can call #contacts.haz_value? #someval. In terms of efficiency, I haven't done a comparison, but both ways use Array's built-in iterators. It would probably be faster to create a stored procedure in your database and call it through ActiveRecord, here is how you can do that.

Access Ruby accessors using block variables

I have an application that I am using Active Record to access a database. I'm trying to take the information and put it into a hash for later use.
Here is basically what I am doing.
require 'active_support'
#emailhash = Hash.new
emails = Email.find(:all)
emails.each do |email|
email.attributes.keys.each do |#column|
#emailhash[email.ticketno] || Hash.new
#emailhash[email.ticketno] = email.#column
end
end
The line that doesn't work is:
#emailhash[email.ticketno] = email.#column
Is there any way that I can do this properly? Basically my goal is to build a hash off of the values that are stored in the table columns, any input is appreciated.
Ruby programmers usually indent 2
Your code was squishing all of the emails into one hash entry, rather than an entry per email.
If you want to call a method dynamically, use Object.send.
#emailhash[email.ticketno] || Hash.new doesn't do anything.
Something like this might do it:
require 'active_support'
#emailhash = {}
Email.find(:all).each do |email|
#mailhash[email.ticketno] = {}
email.attributes.keys.each do |key|
#emailhash[email.ticketno][key] = email.send(key)
end
end
The key piece is "send", which calls the method identified by a string or symbol.
You cannot have an iterator variable starting with an #. Try something like this:
require 'active_support'
#emailhash = Hash.new
emails = Email.find(:all)
emails.each do |email|
#emailhash[email.ticketno] = email.attributes.keys.collect{|key| key.column}
end
In addition to blinry's comment, the line
#emailhash[email.ticketno] || Hash.new
looks suspicious. Are you sure you don't want
#emailhash[email.ticketno] ||= Hash.new
?
Besides the previous accurate observations, I would like to add the following:
Point 1.
Is important to state that #ivars may not work on formal function parameters... This said, I think it is invalid to have:
collection.each { |#not_valid| }
Is also a bad practice to have #ivars inside blocks, you won't know for sure in which context this block will be executed in (As you many know, the self reference inside that block may be different than the self reference outside it).
Point 2.
Another point that you should have in mind is that if you don't assign the result of a (||) operator this won't do any modification at all (just will be a time waster), however you could use:
mail_hash[email.ticketno] = mail_hash[email.ticketno] || Hash.new
That can be easily rewritten to:
mail_hash[email.ticketno] ||= Hash.new
Point 3.
Even if email.attributes.keys is a cheap instruction, is not free... I would suggest to have that outside the iteration block (given that the keys will always be the same for each record, given we are not using Document Databases).
Final Result
require 'active_support'
mails = Email.all
#mailshash = mails.inject(Hash.new) do |hsh, mail|
# mail.attributes is already a representation of the
# email record in a hash
hsh[mail.ticketno] = mail.attributes
hsh
end

Resources