Convert array of hashes to array of structs? - ruby-on-rails

Let's say I have two objects: User and Race.
class User
attr_accessor :first_name
attr_accessor :last_name
end
class Race
attr_accessor :course
attr_accessor :start_time
attr_accessor :end_time
end
Now let's say I create an array of hashes like this:
user_races = races.map{ |race| {:user => race.user, :race => race} }
How do I then convert user_races into an array of structs, keeping in mind that I want to be able to access the attributes of both user and race from the struct element? (The key thing is I want to create a new object via Struct so that I can access the combined attributes of User and Race. For example, UserRace.name, UserRace.start_time.)

Try this:
class User
attr_accessor :first_name
attr_accessor :last_name
end
class Race
attr_accessor :course
attr_accessor :start_time
attr_accessor :end_time
end
UserRace = Struct.new(:first_name, :last_name, :course, :start_time, :end_time)
def get_user_race_info
user_races = races.map do |r|
UserRace.new(r.user.first_name, r.user.last_name,
r.course, r.start_time, r.end_time)
end
end
Now let's test the result:
user_races = get_user_race_info
user_races[0].first_name
user_races[0].end_time

Create a definition for the UserRace object (as a Struct), then just make an array of said objects.
UserRace = Struct.new(:user, :race)
user_races = races.map { |race| UserRace.new(race.user, race) }
# ...
user_races.each do |user_race|
puts user_race.user
puts user_race.race
end

if your hash has so many attributes such that listing them all:
user_races = races.map{ |race| {:user => race.user, :race => race, :best_lap_time => 552.33, :total_race_time => 1586.11, :ambient_temperature => 26.3, :winning_position => 2, :number_of_competitors => 8, :price_of_tea_in_china => 0.38 } } # garbage to show a user_race hash with many attributes
becomes cumbersome (or if you may be adding more attributes later), you can use the * ("splat") operator.
the splat operator converts an array into an argument list.
so you can populate Struct.new's argument list with the list of keys in your hash by doing:
UserRace = Struct.new(*races.first.keys)
of course, this assumes all hashes in your array have the same keys (in the same order).
once you have your struct defined, you can use inject to build the final array of objects. (inject greatly simplifies converting many objects from one data type to another.)
user_races.inject([]) { |result, user_race| result << UserRace.new(*user_race.values) }

You have this :::
user_races = races.map{ |race| {:user => race.user, :race => race} }
Now create a Struct as shown below :
UserRace = Struct.new(:user, :race)
And then ::
user_races.each do |user_race|
new_array << UserRace.new(user_race[:user],user_race[:race])
end
Haven't tested the code... should be fine... what say?
EDIT: Here I am adding the objects of UserRace to a new_array.

Related

Ruby on Rails search with multiple parameters

For example in my Car model i have such fields:
color, price, year
and in form partial i generate form with all this fields. But how to code such logic:
user could enter color and year and i must find with this conditions, user could enter just year or all fields in same time...
And how to write where condition? I could write something like:
if params[:color].present?
car = Car.where(color: params[:color])
end
if params[:color].present? && params[:year].present?
car = Car.where(color: params[:color], year: params[:year])
end
and so over....
But this is very ugly solution, i'm new to rails, and want to know: how is better to solve my problem?
Check out the has_scope gem: https://github.com/plataformatec/has_scope
It really simplifies a lot of this:
class Graduation < ActiveRecord::Base
scope :featured, -> { where(:featured => true) }
scope :by_degree, -> degree { where(:degree => degree) }
scope :by_period, -> started_at, ended_at { where("started_at = ? AND ended_at = ?", started_at, ended_at) }
end
class GraduationsController < ApplicationController
has_scope :featured, :type => :boolean
has_scope :by_degree
has_scope :by_period, :using => [:started_at, :ended_at], :type => :hash
def index
#graduations = apply_scopes(Graduation).all
end
end
Thats it from the controller side
I would turn those into scopes on your Car model:
scope :by_color, lambda { |color| where(:color => color)}
scope :by_year, lambda { |year| where(:year => year)}
and in your controller you would just conditionally chain them like this:
def index
#cars = Car.all
#cars = #cars.by_color(params[:color]) if params[:color].present?
#cars = #cars.by_year(params[:year]) if params[:year].present?
end
user_params = [:color, :year, :price]
cars = self
user_params.each do |p|
cars = cars.where(p: params[p]) if params[p].present?
end
The typical (naive, but simple) way I would do this is with a generic search method in my model, eg.
class Car < ActiveRecord::Base
# Just pass params directly in
def self.search(params)
# By default we return all cars
cars = all
if params[:color].present?
cars = cars.where(color: params[:color])
end
if params[:price1].present? && params[:price2].present?
cars = cars.where('price between ? and ?', params[:price1], params[:price2])
end
# insert more fields here
cars
end
end
You can easily keep chaining wheres onto the query like this, and Rails will just AND them all together in the SQL. Then you can just call it with Car.search(params).
I think you could use params.permit
my_where_params = params.permit(:color, :price, :year).select {|k,v| v.present?}
car = Car.where(my_where_params)
EDIT: I think this only works in rails 4, not sure what version you're using.
EDIT #2 excerpt from site I linked to:
Using permit won't mind if the permitted attribute is missing
params = ActionController::Parameters.new(username: "john", password: "secret")
params.permit(:username, :password, :foobar)
# => { "username"=>"john", "password"=>"secret"}
as you can see, foobar isn't inside the new hash.
EDIT #3 added select block to where_params as it was pointed out in the comments that empty form fields would trigger an empty element to be created in the params hash.

Sorting objects by field availability

I have a place object that has the following parameters: phone, category, street, zip, website.
I also have an array of place objects: [place1, place2, place3, place4, place5].
What's the best way to sort the array of places, based on the parameter availability? I.e., if place1 has the most available parameters, or the least number of parameters that are nil, it should be reordered to first and so on.
Edit: These objects are not ActiveRecord objects
I'd let each Place object know how complete it was:
class Place
attr_accessor :phone, :category, :street, :website, :zip
def completeness
attributes.count{|_,value| value.present?}
end
end
Then it is easy to sort your place objects by completeness:
places.sort_by(&:completeness)
Edit: Non-ActiveRecord solution:
I had assumed this was an ActiveRecord model because of the Ruby on Rails tag. Since this is a non-ActiveRecord model, you can use instance_variables instead of attributes. (By the way, congratulations for knowing that domain models in Rails don't have to inherit from ActiveRecord)
class Place
attr_accessor :phone, :category, :street, :website, :zip
def completeness
instance_variables.count{|v| instance_variable_get(v).present?}
end
end
Edit 2: Weighted attributes
You have a comment about calculating a weighted score. In this case, or when you want to choose specific attributes, you can put the following in your model:
ATTR_WEIGHTS = {phone:1, category:1, street:2, website:1, zip:2}
def completeness
ATTR_WEIGHTS.select{|k,v| instance_variable_get(k).present?}.sum(&:last)
end
Note that the sum(&:last) is equivalent to sum{|k,v| v} which in turn is a railsism for reduce(0){|sum, (k,v)| sum += v}.
I'm sure there's a better way to do it, but this is a start :
ruby fat one liner
values = {phone: 5, category: 3, street: 5, website: 3, zip: 5} #Edit these values to ponderate.
array = [place1, place2, place3, place4, place5]
sorted_array = array.sort_by{ |b| b.attributes.select{ |k, v| values.keys.include?(k.to_sym) && v.present? }.inject(0){ |sum, n| sum + values[n[0]] } }.reverse
So we're basically creating a sub-hash of the attributes of your ActiveRecord object by only picking the key-value pairs that are in the values hash and only if they have a present? value.
Then on this sub-hash, we're invoking inject that will sum the ponderated values we've put in the values hash. Finally, we reverse everything so you have the highest score first.
To make it clean, I suggest you implement a method that will compute the score of each object in an instance method in your model, like mark suggested
If you have a class Place:
class Place
attr_accessor :phone, :category, :street, :website, :zip
end
and you create an instance place1:
place1 = Place.new
place1.instance_variables # => []
place1.instance_variables.size # => 0
place1.phone = '555-1212' # => "555-1212"
place1.instance_variables # => [ :#phone ]
place1.instance_variables.size # => 1
And create the next instance:
place2 = Place.new
place2.phone = '555-1212'
place2.zip = '00000'
place2.instance_variables # => [ :#phone, :#zip ]
place2.instance_variables.size # => 2
You can sort by an ascending number of instance variables that have been set:
[place1, place2].sort_by{ |p| p.instance_variables.size }
# => [ #<Place:0x007fa8a32b51a8 #phone="555-1212">, #<Place:0x007fa8a31f5380 #phone="555-1212", #zip="00000"> ]
Or sort in descending order:
[place1, place2].sort_by{ |p| p.instance_variables.size }.reverse
# => [ #<Place:0x007fa8a31f5380 #phone="555-1212", #zip="00000">, #<Place:0x007fa8a32b51a8 #phone="555-1212"> ]
This uses basic Ruby objects, Rails is not needed, and it asks the object instances themselves what is set, so you don't have to maintain any external lists of attributes.
Note: this breaks if you set an instance variable to something, then set it back to nil.
This fixes it:
[place1,place2].sort_by{ |p|
p.instance_variables.reject{ |v|
p.instance_variable_get(v).nil?
}.size
}.reverse
and this shortens it by using Enumerable's count with a block:
[place1,place2].sort_by{ |p|
p.instance_variables.count{ |v|
!p.instance_variable_get(v).nil?
}
}.reverse

Initializing a variable RUBY

I have a class Sample
Sample.class returns
(id :integer, name :String, date :date)
and A hash has all the given attributes as its keys.
Then how can I initialize a variable of Sample without assigning each attribute independently.
Something like
Sample x = Sample.new
x.(attr) = Hash[attr]
How can I iterate through the attributes, the problem is Hash contains keys which are not part of the class attributes too
class Sample
attr_accessor :id, :name, :date
end
h = {:id => 1, :name => 'foo', :date => 'today', :extra1 => '', :extra2 => ''}
init_hash = h.select{|k,v| Sample.method_defined? "#{k}=" }
# This will work
s = Sample.new
init_hash.each{|k,v| s.send("#{k}=", v)}
# This may work if constructor takes a hash of attributes
s = Sample.new(init_hash)
Take a look at this article on Object initialization. You want an initialize method.
EDIT You might also take a look at this SO post on setting instance variables, which I think is exactly what you're trying to do.
Try this:
class A
attr_accessor :x, :y, :z
end
a = A.new
my_hash = {:x => 1, :y => 2, :z => 3, :nono => 5}
If you do not have the list of attributes that can be assigned from the hash, you can do this:
my_attributes = (a.methods & my_hash.keys)
Use a.instance_variable_set(:#x = 1) syntax to assign values:
my_attributes.each do |attr|
a.instance_variable_set("##{attr.to_s}".to_sym, my_hash[attr])
end
Note(Thanks to Abe): This assumes that either all attributes to be updated have getters and setters, or that any attribute which has getter only, does not have a key in my_hash.
Good luck!

2 level deep grouping in ruby

So I have a array of records and I would like to group by 2 levels
Essentially I would like to group_by{|x| x.field1 } and then each value in hash to be further grouped in by field2. Effectively leading to a tree that I can dump out.
def treemaker(array = [])
tree = ledgers.group_by{|x|x.master_group}
tree.each{|x,z| tree[x] = z.group_by{|y| y.account_group}}
tree
end
I would then render tree in a way that i can be put into a "tree" javascript plugin.
Is there a more efficient way?
Sample Input: An Array of ActiveRecord objects, where the model contains, fields master_group, account_group and name
Class MyModel < ActiveRecord::Base
validates :master_group, :account_group, :name, :presence => true
end
Sample Ouput:
{"master_group1" => {"account_group1" => ["name1","name2",...],
"account_groupx" => ["name3", "name4",...],
....},
"master_group2" => {"account_group2" => ["namex", "namey"]},
...
}
I'm not specifically looking for an "SQL grouping" solution (but that would be nice too). Just a solution using enumerables on a any given list of ruby objects.
#xaxxon sent me thinking in the right way basically with the "default value of hash" path.
I think i can now add a method to my model where i can use all sorts of scopes and tack on tree at the end to get my models in tree mode.
class MyModel < ActiveRecord::Base
validates :master_group, :account_group, :name, :presence => true
def self.tree(field1 = 'master_group', field2 = 'account_group')
tree = Hash.new{|hash,key| hash[key] = Hash.new{|h,k| h[k] = []}}
all.each do |item|
tree[item.send('field1')][item.send('field2')].push(item)
end
tree # bob's your uncle!
end
end
MyModel.recent.tree => Hash of Hash of arrays
set up some fake data
foo=[{:a=>1,:b=>2},{:a=>3,:b=>4}]
set up the output data structure
tree={}
populate the output data structure - this is weird looking because you have to populate the hashes that don't exist when they don't exist, hence the ||={} stuff.
foo.each{|thing| (tree[thing[:a]]||={})[thing[:b]]=thing}
looks good.. :a is your master group and :b is your account_group
pp tree
{1=>{2=>{:a=>1, :b=>2}}, 3=>{4=>{:a=>3, :b=>4}}}

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