sort a hash by a property of the value - ruby

I have a hash that has "Song" objects as values. These song objects have a property 'updated_at' that I want to sort them by. I tried something like:
#data.values.updated_at.sort
but no luck. Any ideas?

You want to check the documentation of Enumerable#sort_by
e.g.
[{name: 'ryan', number: 3},
{name: 'john', number: 1},
{name: 'june', number: 2}].sort_by{|item| item[:number]}
Hopefully that is enough to get you going.

It's simply:
#data.values.sort_by(&:updated_at)
For example:
class A
attr_reader :updated_at
def initialize(updated_at)
#updated_at = updated_at
end
end
#data = { "Bob"=>A.new(3), "Trixi"=>A.new(1), "Bubba"=>A.new(2) }
#=> {"Bob"=>#<A:0x007ffa191935a0 #updated_at=3>,
# "Trixi"=>#<A:0x007ffa19193550 #updated_at=1>,
# "Bubba"=>#<A:0x007ffa19193528 #updated_at=2>}
#data.values.sort_by(&:updated_at)
#=> [#<A:0x007ffa19193550 #updated_at=1>,
# #<A:0x007ffa19193528 #updated_at=2>,
# #<A:0x007ffa191935a0 #updated_at=3>]

Related

Ruby function to get specific item in array and dynamically get nested data

If I have an array of hashes that looks like this
array = [{
name: 'Stan',
surname: 'Smith',
address: {
street: 'Some street',
postcode: '98877',
#...
}
}, {
#...
}]
can you write a function to get a specific item in an array, iterate over it and dynamically retrieve subsequently nested data?
This example doesn't work, but hopefully better explains my question:
def getDataFromFirstItem(someVal)
array(0).each{ |k, v| v["#{ someVal }"] }
end
puts getDataFromFirstItem('name')
# Expected output: 'Stan'
For context, I'm trying to create a Middleman helper so that I don't have to loop through a specific array that only has one item each time I use it in my template. The item (a hash) contains a load of global site variables. The data is coming from Contentful, within which everything is an array of entries.
Starting in ruby 2.3 and greater, you can use Array#dig and Hash#dig which both
Extracts the nested value specified by the sequence of idx objects by calling dig at each step, returning nil if any intermediate step is nil.
array = [{
name: 'Stan',
surname: 'Smith',
address: {
street: 'Some Street',
postcode: '98877'
}
}, {
}]
array.dig(0, :name) # => "Stan"
array.dig(0, :address, :postcode) # => "98877"
array.dig(0, :address, :city) # => nil
array.dig(1, :address, :postcode) # => nil
array.dig(2, :address, :postcode) # => nil
Please try this
array = [{
name: 'Stan',
surname: 'Smith',
address: {
street: 'Some street',
postcode: '98877',
#...
}
},
{
name: 'Nimish',
surname: 'Gupta',
address: {
street: 'Some street',
postcode: '98877',
#...
}
}
]
def getDataFromFirstItem(array, someVal)
array[0][someVal]
end
#Run this command
getDataFromFirstItem(array, :name) # => 'Stan'
#Please notice I send name as a symbol instead of string because the hash you declared consists of symbol keys
#Also if you want to make a dynamic program that works on all indexes of an array and not on a specific index then you can try this
def getDataFromItem(array, index, someVal)
if array[index]
array[index][someVal]
end
end
getDataFromItem(array, 0, :name) # => Stan
getDataFromItem(array, 1, :name) # => Nimish
getDataFromItem(array, 2, :name) # => nil
Hope this works, Please let me know if you still faces any issues

Passing sort_by an array of sort fields

If I have a array of hashes
collection = [
{ first_name: 'john', last_name: 'smith', middle: 'c'},
{ first_name: 'john', last_name: 'foo', middle: 'a'}
]
And an array of keys I want to sort by:
sort_keys = ['first_name', 'last_name']
How can I pass these keys to sort_by given that the keys will always match the keys in the collection?
I've tried
collection.sort_by { |v| sort_keys.map(&:v) }
but this doesn't work. I believe I'll need to use a proc but I'm not sure how to implement it. Would appreciate any help!
Using Ruby 2.2.1
If you change your sort_keys to contain symbols:
sort_keys = [:first_name, :last_name]
You can use values_at to retrieve the values:
collection.sort_by { |h| h.values_at(*sort_keys) }
#=> [{:first_name=>"john", :last_name=>"foo", :middle=>"a"}, {:first_name=>"john", :last_name=>"smith", :middle=>"c"}]
The array that is used to sort the hashes looks like this:
collection.map { |h| h.values_at(*sort_keys) }
#=> [["john", "smith"], ["john", "foo"]]

How to handle mongoid custom type dirty tracking?

I've got the following custom field type that allows me to save a GeoJSON LineString while treating the field like an array of points:
class GeoLineString
attr_reader :coordinates
def initialize(array)
#coordinates = array
end
# Converts an object of this instance into a database friendly value.
def mongoize
{
"type" => "LineString",
"coordinates" => #coordinates
}
end
def as_json(options={})
mongoize
end
class << self
# Get the object as it was stored in the database, and instantiate
# this custom class from it.
def demongoize(object)
return self.new(object["coordinates"]) if object.is_a?(Hash)
end
# Takes any possible object and converts it to how it would be
# stored in the database.
def mongoize(object)
case object
when GeoLineString then object.mongoize
when Array then GeoLineString.new(object).mongoize
else object
end
end
# Converts the object that was supplied to a criteria and converts it
# into a database friendly form.
def evolve(object)
case object
when GeoLineString then object.mongoize
else object
end
end
end
end
Which i use as follows in my model:
class Track
include Mongoid::Document
include Mongoid::Timestamps
field :name, type: String
field :coordinates, type: ::GeoLineString
index({ coordinates: "2dsphere" }, { min: -200, max: 200 })
end
This works as expected but when i want to change the coordinates inside of the GeoLineString field mongoid does not recognize the field "coordinates" as dirty an thus does not update it in the database. Example:
t=Track.last
=> #<Track _id: 568a70e6859c862ee1000000, created_at: 2016-01-04 13:17:40 UTC, updated_at: 2016-01-04 13:17:40 UTC, name: nil, coordinates: {"type"=>"LineString", "coordinates"=>[[1, 2], [2, 2]]}>
t.coordinates.coordinates.push([3,3])
t
=> #<Track _id: 568a70e6859c862ee1000000, created_at: 2016-01-04 13:17:40 UTC, updated_at: 2016-01-04 13:17:40 UTC, name: nil, coordinates: {"type"=>"LineString", "coordinates"=>[[1, 2], [2, 2], [3, 3]]}>
t.changed?
=> false
How can i get mongoid to recognize that the value has changed?

Removing hashes that have identical values for particular keys

I have an Array of Hashes with the same keys, storing people's data.
I want to remove the hashes that have the same values for the keys :name and :surname. The rest of the values can differ, so calling uniq! on array won't work.
Is there a simple solution for this?
You can pass a block to uniq or uniq!, the value returned by the block is used to compare two entries for equality:
irb> people = [{name: 'foo', surname: 'bar', age: 10},
{name: 'foo', surname: 'bar' age: 11}]
irb> people.uniq { |p| [p[:name], p[:surname]] }
=> [{:name=>"foo", :surname=>"bar", :age=>10}]
arr=[{name: 'john', surname: 'smith', phone:123456789},
{name: 'thomas', surname: 'hardy', phone: 671234992},
{name: 'john', surname: 'smith', phone: 666777888}]
# [{:name=>"john", :surname=>"smith", :phone=>123456789},
# {:name=>"thomas", :surname=>"hardy", :phone=>671234992},
# {:name=>"john", :surname=>"smith", :phone=>666777888}]
arr.uniq {|h| [h[:name], h[:surname]]}
# [{:name=>"john", :surname=>"smith", :phone=>123456789},
# {:name=>"thomas", :surname=>"hardy", :phone=>671234992}]
unique_people = {}
person_array.each do |person|
unique_people["#{person[:name]} #{person[:surname]}"] = person
end
array_of_unique_people = unique_people.values
This should do the trick.
a.delete_if do |h|
a.select{|i| i[:name] == h[:name] and i[:surname] == h[:surname] }.count > 1
end

Convert Array of objects to Hash with a field as the key

I have an Array of objects:
[
#<User id: 1, name: "Kostas">,
#<User id: 2, name: "Moufa">,
...
]
And I want to convert this into an Hash with the id as the keys and the objects as the values. Right now I do it like so but I know there is a better way:
users = User.all.reduce({}) do |hash, user|
hash[user.id] = user
hash
end
The expected output:
{
1 => #<User id: 1, name: "Kostas">,
2 => #<User id: 2, name: "Moufa">,
...
}
users_by_id = User.all.map { |user| [user.id, user] }.to_h
If you are using Rails, ActiveSupport provides Enumerable#index_by:
users_by_id = User.all.index_by(&:id)
You'll get a slightly better code by using each_with_object instead of reduce.
users = User.all.each_with_object({}) do |user, hash|
hash[user.id] = user
end
You can simply do (using the Ruby 3 syntax of _1)
users_by_id = User.all.to_h { [_1.id, _1] }

Resources