Ruby condition for inserting unique items into an array - ruby-on-rails

I know that if you have an array and reference it as array.uniq it will return without any of the duplicates.
However in this case it is an array of objects (is that proper ruby speak?). I want each call to go into the #calls array unless the call.from is the same as a call_formatted object already present in the array.
How can I conditionally place these objects in the array if no other objects in the array have the same call.from value?
calls_raw.each do |call|
call_formatted = {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
#calls << call_formatted
end

array.uniq { |item| item[:from] }

Use #map to build your array for you and call #uniq on it...
calls_raw.map do |call|
{
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.uniq{|call| call[:from]}
The above approach will first build an array of calls larger than it may ultimately need to be, and the final call to #uniq will make the list unique.
Or, to avoid adding all the duplicates in the array, you could build it with a Hash as such:
calls_raw.each_with_object do |call, h|
h[call.from] ||= {
:date => date,
:time => time,
:from => call.from,
:duration => call.duration,
:recording => recording,
}
end.values
The Hash approach will use the first occurrence of call.from as it is being set with ||=. To use the last occurrence of call.from then use a straightforward assignment with =.
It's also been suggested to just use a Set instead of an Array.
To take that approach you're going to have to implement #eql? and #hash on the class we're populating the set with.
class CallRaw
attr_accessor :from
def initialize(from)
self.from = from
end
def eql?(o)
# Base equality on 'from'
o.from == self.from
end
def hash
# Use the hash of 'from' for our hash
self.from.hash
end
end
require 'set'
s = Set.new
=> <Set: {}>
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 #from="Chewbaca">}>
# We expect now, that adding another will not grow our set any larger
s << CallRaw.new("Chewbaca")
=> <Set: {<CallRaw:0x00000002211888 #from="Chewbaca">}>
# Great, it's not getting any bigger
s << CallRaw.new("Chewbaca")
s << CallRaw.new("Chewbaca")
=> <Set: {#<CallRaw:0x00000002211888 #from="Chewbaca">}>
Awesome - the Set works!!!
Now, it is interesting to note that having implemented #eql? and #hash, we can now use Array#uniq without having to pass in a block.
a = Array.new
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">]
a << CallRaw.new("Chewbaca")
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">, <CallRaw:0x000000021c2bc0 #from="Chewbaca">]
a.uniq
=> [<CallRaw:0x000000021e2128 #from="Chewbaca">]
Now, I'm just wondering if there is a badge that StackOverflow awards for having too much coffee before setting out to answer a question?

Unless there's some reason it has to be an array, I'd store the data in a Hash, keyed by the from value.
Then it's easy and fast to look up an entry by the from value. You can choose to insert a new value only if there's no value already with the same key, or insert the new value and let it replace the old entry with that key.
Example:
calls = Hash.new
def add(call)
if not calls[call.from]
calls[call.from] = call
end
end

Related

How to understand this code?

def event_calendar_options
{
:year => #year,
:month => #month,
:event_strips => #event_strips,
:month_name_text => I18n.localize(#shown_month, :format => "%B %Y"),
:previous_month_text => "<< " + month_link(#shown_month.prev_month),
:next_month_text => month_link(#shown_month.next_month) + " >>"
}
end
def event_calendar
calendar event_calendar_options do |args|
event = args[:event]
%(#{h(event.name)})
end
end
Here the whole event_calendar_options is enclosed by {}, so does it simply return the hash table?
Secondly, why event_calendar_options, a module method, can be passed as a parameter to calendar?
Assuming they're part of the same class (e.g. controller or model), event_calendar_options can be called by event_calendar. This is common practice within classes. Even if they're not, they can be declared as public and called by other classes as well (which is less common).
And, yes, event_calendar_options returns the hash table. In ruby, the final statement will be returned unless a return statement is supplied. In this case, it will return a hash table that will be iterated over by the event_calendar method.

Getting ruby hash values by an array of keys

What I'm aiming to do is to create an object which is initialized with a hash and then query this object in order to get values from that hash.
To make things clearer here's a rough example of what I mean:
class HashHolder
def initialize(hash)
#hash = hash
end
def get_value(*args)
# What are my possibilities here?
end
end
holder = HashHolder.new({:a => { :b => { :c => "value" } } } )
holder.get_value(:a, :b, :c) # should return "value"
I know I can perform iteration on the arguments list as in:
def get_value(*args)
value = #hash
args.each do |k|
value = value[k]
end
return value
end
But if I plan to use this method a lot this is going to degrade my performance dramatically when all I want to do is to access a hash value.
Any suggestions on that?
To update the answer since it's been a while since it was asked.
(tested in ruby 2.3.1)
You have a hash like this:
my_hash = {:a => { :b => { :c => "value" } } }
The question asked:
my_hash.get_value(:a, :b, :c) # should return "value"
Answer: Use 'dig' instead of get_value, like so:
my_hash.dig(:a,:b,:c) # returns "value"
Since the title of the question is misleading (it should be something like: how to get a value inside a nested hash with an array of keys), here is an answer to the question actually asked:
Getting ruby hash values by an array of keys
Preparation:
my_hash = {:a => 1, :b => 3, :d => 6}
my_array = [:a,:d]
Answer:
my_hash.values_at(*my_array) #returns [1,6]
def get_value(*args)
args.inject(#hash, &:fetch)
end
In case you want to avoid iteration at lookup (which I do not feel necessary), then you need to flatten the hash to be stored:
class HashHolder
def initialize(hash)
while hash.values.any?{|v| v.kind_of?(Hash)}
hash.to_a.each{|k, v| if v.kind_of?(Hash); hash.delete(k).each{|kk, vv| hash[[*k, kk]] = vv} end}
end
#hash = hash
end
def get_value(*args)
#hash[args]
end
end
If you know the structure of the hash is always in that format you could just do:
holder[:a][:b][:c]
... returns "value".

Trying to master Ruby. How can I optimize this method?

I'm learning new tricks all the time and I'm always on the lookout for better ideas.
I have this rather ugly method. How would you clean it up?
def self.likesit(user_id, params)
game_id = params[:game_id]
videolink_id = params[:videolink_id]
like_type = params[:like_type]
return false if like_type.nil?
if like_type == "videolink"
liked = Like.where(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink").first unless videolink_id.nil?
elsif like_type == "game"
liked = Like.where(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game").first unless game_id.nil?
end
if liked.present?
liked.amount = 1
liked.save
return true
else # not voted on before...create Like record
if like_type == "videolink"
Like.create(:user_id => user_id, :likeable_id => videolink_id, :likeable_type => "Videolink", :amount => 1)
elsif like_type == "game"
Like.create(:user_id => user_id, :likeable_id => game_id, :likeable_type => "Game", :amount => 1)
end
return true
end
return false
end
I would do something like:
class User < ActiveRecord::Base
has_many :likes, :dependent => :destroy
def likes_the(obj)
like = likes.find_or_initialize_by_likeable_type_and_likeable_id(obj.class.name, obj.id)
like.amount += 1
like.save
end
end
User.first.likes_the(VideoLink.first)
First, I think its wrong to deal with the "params" hash on the model level. To me its a red flag when you pass the entire params hash to a model. Thats in the scope of your controllers, your models should have no knowledge of the structure of your params hash, imo.
Second, I think its always cleaner to use objects when possible instead of class methods. What you are doing deals with an object, no reason to perform this on the class level. And finding the objects should be trivial in your controllers. After all this is the purpose of the controllers. To glue everything together.
Finally, eliminate all of the "return false" and "return true" madness. The save method takes care of that. The last "return false" in your method will never be called, because the if else clause above prevents it. In my opinion you should rarely be calling "return" in ruby, since ruby always returns the last evaluated line. In only use return if its at the very top of the method to handle an exception.
Hope this helps.
I'm not sure what the rest of your code looks like but you might consider this as a replacement:
def self.likesit(user_id, params)
return false unless params[:like_type]
query = {:user_id => user_id,
:likeable_id => eval("params[:#{params[:like_type]}_id]"),
:likeable_type => params[:like_type].capitalize}
if (liked = Like.where(query).first).present?
liked.amount = 1
liked.save
else # not voted on before...create Like record
Like.create(query.merge({:amount => 1}))
end
end
I assume liked.save and Like.create return true if they are succesful, otherwise nil is returned. And what about the unless game_id.nil? ? Do you really need that? If it's nil, it's nil and saved as nil. But you might as well check in your data model for nil's. (validations or something)

FasterCSV Parsing issue?

G'day guys, I'm currently using fastercsv to construct ActiveRecord elements and I can't for the life of me see this bug (tired), but for some reason when it creates, if in the rake file i output the column I want to save as the element value, it puts out correctly, as either a Trade or a Quote
but when I try to save it into the activerecord, it won't work.
FasterCSV.foreach("input.csv", :headers => true) do |row|
d = DateTime.parse(row[1]+" "+row[2])
offset = Rational(row[3].to_i,24)
o = d.new_offset(offset)
t = Trade.create(
:name => row[0],
:type => row[4],
:time => o,
:price => row[6].to_f,
:volume => row[7].to_i,
:bidprice => row[10].to_f,
:bidsize => row[11].to_i,
:askprice => row[14].to_f,
:asksize => row[15].to_i
)
end
Ideas?
Name and Type are both strings, every other value works except for type. Have I missed something really simple?
Ruby's Object class has a type method. You need to t[:type] = row[4] to avoid that method.
-Tim

ruby object array... or hash

I have an object now:
class Items
attr_accessor :item_id, :name, :description, :rating
def initialize(options = {})
options.each {
|k,v|
self.send( "#{k.to_s}=".intern, v)
}
end
end
I have it being assigned as individual objects into an array...
#result = []
some loop>>
#result << Items.new(options[:name] => 'name', options[:description] => 'blah')
end loop>>
But instead of assigning my singular object to an array... how could I make the object itself a collection?
Basically want to have the object in such a way so that I can define methods such as
def self.names
#items.each do |item|
item.name
end
end
I hope that makes sense, possibly I am overlooking some grand scheme that would make my life infinitely easier in 2 lines.
A few observations before I post an example of how to rework that.
Giving a class a plural name can lead to a lot of semantic issues when declaring new objects, as in this case you'd call Items.new, implying you're creating several items when in fact actually making one. Use the singular form for individual entities.
Be careful when calling arbitrary methods, as you'll throw an exception on any misses. Either check you can call them first, or rescue from the inevitable disaster where applicable.
One way to approach your problem is to make a custom collection class specifically for Item objects where it can give you the information you need on names and such. For example:
class Item
attr_accessor :item_id, :name, :description, :rating
def initialize(options = { })
options.each do |k,v|
method = :"#{k}="
# Check that the method call is valid before making it
if (respond_to?(method))
self.send(method, v)
else
# If not, produce a meaningful error
raise "Unknown attribute #{k}"
end
end
end
end
class ItemsCollection < Array
# This collection does everything an Array does, plus
# you can add utility methods like names.
def names
collect do |i|
i.name
end
end
end
# Example
# Create a custom collection
items = ItemsCollection.new
# Build a few basic examples
[
{
:item_id => 1,
:name => 'Fastball',
:description => 'Faster than a slowball',
:rating => 2
},
{
:item_id => 2,
:name => 'Jack of Nines',
:description => 'Hypothetical playing card',
:rating => 3
},
{
:item_id => 3,
:name => 'Ruby Book',
:description => 'A book made entirely of precious gems',
:rating => 1
}
].each do |example|
items << Item.new(example)
end
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
Do you know the Ruby key word yield?
I'm not quite sure what exactly you want to do. I have two interpretations of your intentions, so I give an example that makes two completely different things, one of them hopefully answering your question:
class Items
#items = []
class << self
attr_accessor :items
end
attr_accessor :name, :description
def self.each(&args)
#items.each(&args)
end
def initialize(name, description)
#name, #description = name, description
Items.items << self
end
def each(&block)
yield name
yield description
end
end
a = Items.new('mug', 'a big cup')
b = Items.new('cup', 'a small mug')
Items.each {|x| puts x.name}
puts
a.each {|x| puts x}
This outputs
mug
cup
mug
a big cup
Did you ask for something like Items.each or a.each or for something completely different?
Answering just the additional question you asked in your comment to tadman's solution: If you replace in tadman's code the definition of the method names in the class ItemsCollection by
def method_missing(symbol_s, *arguments)
symbol, s = symbol_s.to_s[0..-2], symbol_s.to_s[-1..-1]
if s == 's' and arguments.empty?
select do |i|
i.respond_to?(symbol) && i.instance_variables.include?("##{symbol}")
end.map {|i| i.send(symbol)}
else
super
end
end
For his example data you will get following outputs:
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
puts items.descriptions.join(', ')
# => Faster than a slowball, Hypothetical playing card, A book made entirely of precious gems
As I don't know about any way to check if a method name comes from an attribute or from another method (except you redefine attr_accessor, attr, etc in the class Module) I added some sanity checks: I test if the corresponding method and an instance variable of this name exist. As the class ItemsCollection does not enforce that only objects of class Item are added, I select only the elements fulfilling both checks. You can also remove the select and put the test into the map and return nil if the checks fail.
The key is the return value. If not 'return' statement is given, the result of the last statement is returned. You last statement returns a Hash.
Add 'return self' as the last line of initialize and you're golden.
Class Item
def initialize(options = {})
## Do all kinds of stuff.
return self
end
end

Resources