Dynamically populate an object's attributes in Ruby, whose attribute names are the same as hash keys - ruby

I have the following hash:
row = {:id => 1, :name => "Altus Raizen", :email => "altus#blarg.com"}
Now I have a Person Struct with the same attributes as the keys in row:
Person = Struct.new(:id, :name, :email)
I want to dynamically populate a Person object using the values in the row hash as follows:
person = Person.new
person.id = row[:id]
person.name = row[:name]
person.email = row[:email]
The code above works, but there must be a more elegant way of doing this, i.e. populating the attributes dynamically. How do I do this? (I have 9 attributes actually, so the code above become much longer and "uglier" by considering to set values to the other attributes such as phone, address, etc.).

person = Person.new
row.each_pair { |key, value| person.send("#{key}=", value) }

In ruby >= 1.9. you can do:
row = {:id => 1, :name => "Altus Raizen", :email => "altus#blarg.com"}
Person = Struct.new(:id, :name, :email)
p person = Person.new(*row.values)
# => <struct Person id=1, name="Altus Raizen", email="altus#blarg.com">
Which happens to work because everything is in the right order. More control gives values_at, which also works on older Rubies:
row = {:id => 1, :name => "Altus Raizen", :email => "altus#blarg.com"}
Person = Struct.new(:id, :name, :email)
p person = Person.new(*row.values_at(:id, :name, :email))
Another option is OpenStruct:
require 'ostruct'
row = {:id => 1, :name => "Altus Raizen", :email => "altus#blarg.com"}
person = OpenStruct.new(row)
p person #=><OpenStruct id=1, name="Altus Raizen", email="altus#blarg.com">
puts person.name #=> Altus Raizen

Related

New hash from array of hashes

The objective of the code below is to produce a hash with the keys being the :id field of
the hashes in original_array, and the values being all elements in original_array which have that :id.
original_array = [
{:id => '123', :name => 'test'},
{:id => '123', :name => 'another test'},
{:id => '456', :name => 'yet another test'}
]
new_hash = {}
original_array.each do |a|
new_hash[a[:id]] = original_array.select {|x| x[:id] == a[:id]}
end
My code does that, but there must be some better way to do it, ideally where the hash can be created in one step. If anyone can suggest and explain one (in the hope that I might improve my understanding of this sort of thing), then it would be appreciated.
This should do it
new_hash = original_array.group_by{|h| h[:id]}
Documentation: Enumerable#group_by.

How would I construct a Hash from this scenario in Ruby?

Given I have the following code:
ENDPOINT = 'http://api.eventful.com'
API_KEY = 'PbFVZfjTXJQWrnJp'
def get_xml(url, options={})
compiled_url = "#{ENDPOINT}/rest#{url}" << "?app_key=#{API_KEY}&sort_order=popularity"
options.each { |k, v| compiled_url << "&#{k.to_s}=#{v.to_s}" }
REXML::Document.new((Net::HTTP.get(URI.parse(URI.escape(compiled_url)))))
end
def event_search(location, date)
get_xml('/events/search',
:location => "#{location}, United Kingdom",
:date => date
)
end
And we access the XML data formatted by REXML::Document like this:
events = event_search('London', 'Today').elements
And we can access these elements like this (this prints all the titles in the events):
events.each('search/events/event/title') do |title|
puts title.text
end
The XML I'm using can be found here. I would like this construct a Hash like so:
{"Title1" => {:title => 'Title1', :date => 'Date1', :post_code => 'PostCode1'},
"Title2" => {:title => 'Title2', :date => 'Date2', :post_code => 'PostCode2'}}
When using events.each('search/events/event/title'), events.each('search/events/event/date'), and events.each('search/events/event/post_code').
So I want to create a Hash from the XML provided by the URL I have included above. Thanks!
You should loop over the events themselves, not the titles. Something like this
events_by_title = {}
elements.each('search/events/event') do |event|
title = event.get_elements('title').first.text
events_by_title[title] = {
:title => title,
:date => event.get_elements('start_time').first.text
:post_code => event.get_elements('postal_code').first.text,
}
end
Get the root element using root() on the REXML:Document object then use each_element("search/events/event") to iterate over "event" node. You can then extract the different values out of it using the different methods on element: http://ruby-doc.org/stdlib-1.9.3/libdoc/rexml/rdoc/REXML/Element.html

Ruby: Loop through hash and check if a key exists to determine markup and data to be displayed

This is my first time working with Ruby, so I may be approaching this incorrectly.
I am trying to go through a hash to display it's contents. As I'm going through the hash I'll need to test if a key exists, like city. If city doesn't exist then it shouldn't display the address. This is where I've started with building my hash:
# app.rb
set :haml, :format => :html5
get "/" do
#users = Hash[
[["name", "bill"], ["city", "nyc"], ["address", "street"]],
[["name", "ted"], ["city", "denver"]],
[["name", "sam"], ["address", "road"]]
]
haml :index
end
And this is how I am looping through the hash:
# layout.haml
- #users.each do |user|
- user.each do |u|
- u.each do |b|
= b
Once I get to b it will display all of the content like so:
["name", "bill"]
["city", "nyc"]
["address", "street"]
["name", "ted"]
["city", "denver"]
In the loop, how can I display the name as well as check to see if the address exists for each user to determine if the city should be displayed as well as any markup that may need to be added? It would ideally display something like:
<p>bill, <span class="address">nyc, street</span></p>
<p>ted</p>
<p>sam, <span class="address">road</span></p>
Am I creating the Hash properly to do it this way?
Instead of what you are trying to do with nested arrays inside a hash, it would be better to have an array that contains user hashes:
#users = [
{ :name => 'bill', :city => 'city', :address => 'street' },
{ :name => 'ted', :city => 'denver' },
{ :name => 'sam', :address => 'road' }
]
With that, you can do something like this:
- #users.each do |user|
= user[:name]
- if user.has_key?(:address) && user.has_key?(:city)
= "#{user[:address]}, #{user[:city]}"
- elsif user.has_key?(:address)
= "#{user[:address]}"

Deleting EmbeddedDocuments with Mongo Mapper

I have mongo_mapper set up like so:
class Person
include MongoMapper::Document
many :pets
end
class Pet
include MongoMapper::EmbeddedDocument
key :animal, String
key :name, String
key :colour, String
end
# Create a person
me = Person.new
# Add pets to the person
me.pets << Pet.new(:animal => 'dog',:name => 'Mr. Woofs', :colour => 'golden')
me.pets << Pet.new(:animal => 'cat', :name => 'Kitty', :colour => 'black')
me.pets << Pet.new(:animal => 'cat', :name => 'Freckles', :colour => 'black')
me.pets << Pet.new(:animal => 'cat', :name => 'Fluffy', :colour => 'tabby')
I know I can delete all pets very simply (me.pets works as an array but also calls back)
# Delete all pets
me.pets.clear
I also know that I could delete all black cats by doing this:
# Delete black cats
me.pets.delete_if {|pet| pet.animal == 'cat' and pet.colour = 'black'}
But that seems like it'll take a very long time if there are a large number of pets to iterate through.
I feel like there should be a way to select only the black cats and then clear that array instead. Is there such a way?
try something like this, no idea if this'll work but worth a shot.
me.pets.all(:animal => "cat", :colour => "black").clear
To be honest though I think you are worrying about this for nothing. Usually array manipulation are plenty fast.

Comparing lists of field-hashes with equivalent AR-objects

I have a list of hashes, as such:
incoming_links = [
{:title => 'blah1', :url => "http://blah.com/post/1"},
{:title => 'blah2', :url => "http://blah.com/post/2"},
{:title => 'blah3', :url => "http://blah.com/post/3"}]
And an ActiveRecord model which has fields in the database with some matching rows, say:
Link.all =>
[<Link#2 #title='blah2' #url='...post/2'>,
<Link#3 #title='blah3' #url='...post/3'>,
<Link#4 #title='blah4' #url='...post/4'>]
I'd like to do set operations on Link.all with incoming_links so that I can figure out that <Link#4 ...> is not in the set of incoming_links, and {:title => 'blah1', :url =>'http://blah.com/post/1'} is not in the Link.all set, like so:
#pseudocode
#incoming_links = as above
links = Link.all
expired_links = links - incoming_links
missing_links = incoming_links - links
expired_links.destroy
missing_links.each{|link| Link.create(link)}
Crappy solution a):
I'd rather not rewrite Array#- and such, and I'm okay with converting incoming_links to a set of unsaved Link objects; so I've tried overwriting hash eql? and so on in Link so that it ignored the id equality that AR::Base provides by default. But this is the only place this sort of equality should be considered in the application - in other places the Link#id default identity is required. Is there some way I could subclass Link and apply the hash, eql?, etc overwriting there?
Crappy solution b):
The other route I've tried is to pull out the attributes hash for each Link and doing a .slice('id',...etc) to prune the hashes down. But this requires writing seperate - methods for keeping track of the Link objects while doing set operations on the hashes, and writing seperate Proxy classes to wrap the incoming_links hashes and Links, which seems a bit overkill. Nonetheless, this is the current solution for me.
Can you think of a better way to design this interaction? Extra credit for cleanliness.
try this
incoming_links = [
{:title => 'blah1', :url => "http://blah.com/post/1"},
{:title => 'blah2', :url => "http://blah.com/post/2"},
{:title => 'blah3', :url => "http://blah.com/post/3"}]
ar_links = Link.all(:select => 'title, url').map(&:attributes)
# wich incoming links are not in ar_links
incoming_links - ar_links
# and vice versa
ar_links - incoming_links
upd
For your Link model:
def self.not_in_array(array)
keys = array.first.keys
all.reject do |item|
hash = {}
keys.each { |k| hash[k] = item.send(k) }
array.include? hash
end
end
def self.not_in_class(array)
keys = array.first.keys
class_array = []
all.each do |item|
hash = {}
keys.each { |k| hash[k] = item.send(k) }
class_array << hash
end
array - class_array
end
ar = [{:title => 'blah1', :url => 'http://blah.com/ddd'}]
Link.not_in_array ar
#=> all links from Link model which not in `ar`
Link.not_in_class ar
#=> all links from `ar` which not in your Link model
If you rewrite the equality method, will ActiveRecord complain still?
Can't you do something similar to this (as in a regular ruby class):
class Link
attr_reader :title, :url
def initialize(title, url)
#title = title
#url = url
end
def eql?(another_link)
self.title == another_link.title and self.url == another_link.url
end
def hash
title.hash * url.hash
end
end
aa = [Link.new('a', 'url1'), Link.new('b', 'url2')]
bb = [Link.new('a', 'url1'), Link.new('d', 'url4')]
(aa - bb).each{|x| puts x.title}
The requirements are:
# Keep track of original link objects when
# comparing against a set of incomplete `attributes` hashes.
# Don't alter the `hash` and `eql?` methods of Link permanently,
# or globally, throughout the application.
The current solution is in effect using Hash's eql? method, and annotating the hashes with the original objects:
class LinkComp < Hash
LINK_COLS = [:title, :url]
attr_accessor :link
def self.[](args)
if args.first.is_a?(Link) #not necessary for the algorithm,
#but nice for finding typos and logic errors
links = args.collect do |lnk|
lk = super(lnk.attributes.slice(*(LINK_COLS.collect(&:to_s)).to_a)
lk.link = lnk
lk
end
elsif args.blank?
[]
#else #raise error for finding typos
end
end
end
incoming_links = [
{:title => 'blah1', :url => "http://blah.com/post/1"},
{:title => 'blah2', :url => "http://blah.com/post/2"},
{:title => 'blah3', :url => "http://blah.com/post/3"}]
#Link.all =>
#[<Link#2 #title='blah2' #url='...post/2'>,
# <Link#3 #title='blah3' #url='...post/3'>,
# <Link#4 #title='blah4' #url='...post/4'>]
incoming_links= LinkComp[incoming_links.collect{|i| Link.new(i)}]
links = LinkComp[Link.all] #As per fl00r's suggestion
#this could be :select'd down somewhat, w.l.o.g.
missing_links = (incoming_links - links).collect(&:link)
expired_links = (links - incoming_links).collect(&:link)

Resources