Ruby on Rails 4: Pluck results to hash - ruby

How can I turn:
Person.all.pluck(:id, :name)
to
[{id: 1, name: 'joe'}, {id: 2, name: 'martin'}]
without having to .map every value (since when I add or remove from the .pluck I have to do he same with the .map)

You can map the result:
Person.all.pluck(:id, :name).map { |id, name| {id: id, name: name}}
As mentioned by #alebian:
This is more efficient than
Person.all.as_json(only: [:id, :name])
Reasons:
pluck only returns the used columns (:id, :name) whereas the other solution returns all columns. Depending on the width of the table (number of columns) this makes quite a difference
The pluck solution does not instantiate Person objects, does not need to assign attributes to the models and so on. Instead it just returns an array with one integer and one string.
as_json again has more overhead than the simple map as it is a generic implementation to convert a model to a hash

You could simply do this
Person.select(:id,:name).as_json
You could try this as well
Person.all.as_json(only: [:id, :name])

I see three options:
1) pluck plus map:
Person.pluck(:id, :name).map { |p| { id: p[0], name: p[1] } }
2) pluck plus map plus zip and a variable to make it DRY-er:
attrs = %w(id name)
Person.pluck(*attrs).map { |p| attrs.zip(p).to_h }
3) or you might not use pluck at all although this is much less performant:
Person.all.map { |p| p.slice(:id, :name) }

If you use postgresql, you can use json_build_object function in pluck method:
https://www.postgresql.org/docs/9.5/functions-json.html
That way, you can let db create hashes.
Person.pluck("json_build_object('id', id, 'name', name)")
#=> [{id: 1, name: 'joe'}, {id: 2, name: 'martin'}]

Could go for a hash after the pluck with the ID being the key and the Name being the value:
Person.all.pluck(:id, :name).to_h
{ 1 => 'joe', 2 => 'martin' }
Not sure if this fits your needs, but presenting as an option.

You can use the aptly-named pluck_to_hash gem for this:
https://github.com/girishso/pluck_to_hash
It will extend AR with pluck_to_hash method that works like this:
Post.limit(2).pluck_to_hash(:id, :title)
#
# [{:id=>213, :title=>"foo"}, {:id=>214, :title=>"bar"}]
#
Post.limit(2).pluck_to_hash(:id)
#
# [{:id=>213}, {:id=>214}]
It claims to be several times faster than using AR select and as_json

There is pluck_all gem that do almost the same thing as pluck_to_hash do. And it claims that it's 30% faster. (see the benchmark here).
Usage:
Person.pluck_all(:id, :name)

If you have multiple attributes, you may do this for cleanliness:
Item.pluck(:id, :name, :description, :cost, :images).map do |item|
{
id: item[0],
name: item[1],
description: item[2],
cost: item[3],
images: item[4]
}
end

The easiest way is to use the pluck method combined with the zip method.
attrs_array = %w(id name)
Person.all.pluck(attrs_array).map { |ele| attrs_array.zip(ele).to_h }
You can also create a helper method if you are using this method through out your application.
def pluck_to_hash(object, *attrs)
object.pluck(*attrs).map { |ele| attrs.zip(ele).to_h }
end
Consider modifying by declaring self as the default receiver rather than passing Person.all as the object variable.
Read more about zip.

Here is a method that has worked well for me:
def pluck_to_hash(enumerable, *field_names)
enumerable.pluck(*field_names).map do |field_values|
field_names.zip(field_values).each_with_object({}) do |(key, value), result_hash|
result_hash[key] = value
end
end
end

I know it's an old thread but in case someone is looking for simpler version of this
Hash[Person.all(:id, :name)]
Tested in Rails 5.

Related

Parsing recursive hashes into data records in Ruby

I've struggled with this problem for a while, and I'm finally going to ask here for help.
Take a very straightforward hash that represents some event:
{
:eventkey=>"someeventkey",
:web_id=>"77d5f434-5a40-4582-88e8-9667b7774c7d",
:apikey=>"eaf3b6e1-b020-41b6-b67f-98f1cc0a9590",
:details=> {
:phone=>"1-936-774-6886",
:email=>"dasia_schuster#wisokytrantow.com",
:pageUrl=>"http://ortiz.info/joe"
}
}
My goal is to create a 'master record' for the entire hash, with the fields in the record being all the keys that do not contain values that are also hashes. When I run into a value that is a hash (in this case 'details'), I need to create a separate record for each k/v pair in that hash bearing the same record id as the parent master record.
I'm not getting the recursion right somehow. Ideally I would get back a single primary record:
{
:recordid=>"some-generated-record-id",
:web_id=>"77d5f434-5a40-4582-88e8-9667b7774c7d",
:apikey=>"eaf3b6e1-b020-41b6-b67f-98f1cc0a9590",
:details=>nil
}
And a distinct entry for each key in the nested hash:
{
:recordid=>"some-generated-detail-record-id",
:parentid=>"the-parent-id-from-the-master-record",
:phone=>"1-936-774-6886"
}
{
:recordid=>"another-generated-detail-record-id",
:parentid=>"the-same-parent-id-from-the-master-record",
:email=>"dasia_schuster#wisokytrantow.com"
}
And so on. I'm trying to get this set of records back as an array of hashes.
I've gotten as far as being able to generate the master record, as well as a detail record, but the detail record contains all the keys in the detail.
def eventToBreakout(eventhash,sequenceid = -1, parentrecordid = nil, records = [])
recordid = SecureRandom.uuid
sequenceid += 1
recordstruc = {:record_id => recordid, :parent_record_id => parentrecordid, :record_processed_ts => Time.now, :sequence_id => sequenceid}
eventhash.each_pair do |k,v|
if recurse?(v)
eventToBreakout(v,sequenceid,recordid,records)
else
if !recordstruc.keys.include?(k)
recordstruc[k]=v
end
end
end
records << recordstruc
records
end
I've included my code and here is the output I'm currently getting from it.
[{:record_id=>"ed98be89-4c1f-496e-beb4-ede5f38dd549",
:parent_record_id=>"fa77299b-95b0-429d-ad8a-f5d365f2f357",
:record_processed_ts=>2016-04-25 16:46:10 -0500,
:sequence_id=>1,
:phone=>"1-756-608-8114",
:email=>"hipolito_wilderman#morar.co",
:pageUrl=>"http://haag.net/alexie.marvin"},
{:record_id=>"fa77299b-95b0-429d-ad8a-f5d365f2f357",
:parent_record_id=>nil,
:record_processed_ts=>2016-04-25 16:46:10 -0500,
:sequence_id=>0,
:eventts=>2016-04-25 22:10:32 -0500,
:web_id=>"a61c57ae-3a01-4994-8803-8d8292df3338",
:apikey=>"9adbc7a4-03ff-4fcc-ac81-ae8d0ee01ef0"}]
Maybe you want something along these lines?
input = { id: 'parent', value: 'parent value', child: { child_value: 1}}
record = {}
input.each do |k,v|
if v.is_a? Hash
v[:parent_id] = input[:id]
(record[:children] ||= []) << v
else
record[k] = v
end
end
puts record
# {:id=>"parent", :value=>"parent value", :children=>[{:child_value=>1, :parent_id=>"parent"}]}
By the way this is a good example to get started with "spec" or "test" frameworks like minitest or rspec (both can be used for both). You have defined input and expected output already and "just" need to code until all test/spec-runs are green.

build a hash from iterating over a hash with nested arrays

I'd like to structure data I get pack from an Instagram API call:
{"attribution"=>nil,
"tags"=>["loudmouth"],
"location"=>{"latitude"=>40.7181015, "name"=>"Fontanas Bar", "longitude"=>-73.9922791, "id"=>31443955},
"comments"=>{"count"=>0, "data"=>[]},
"filter"=>"Normal",
"created_time"=>"1444181565",
"link"=>"https://instagram.com/p/8hJ-UwIDyC/",
"likes"=>{"count"=>0, "data"=>[]},
"images"=>
{"low_resolution"=>{"url"=>"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/s320x320/e35/12145134_169501263391761_636095824_n.jpg", "width"=>320, "height"=>320},
"thumbnail"=>
{"url"=>"https://scontent.cdninstagram.com/hphotos-xfa1/t51.2885-15/s150x150/e35/c135.0.810.810/12093266_813307028768465_178038954_n.jpg", "width"=>150, "height"=>150},
"standard_resolution"=>
{"url"=>"https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/s640x640/sh0.08/e35/12145134_169501263391761_636095824_n.jpg", "width"=>640, "height"=>640}},
"users_in_photo"=>
[{"position"=>{"y"=>0.636888889, "x"=>0.398666667},
"user"=>
{"username"=>"ambersmelson",
"profile_picture"=>"http://photos-h.ak.instagram.com/hphotos-ak-xfa1/t51.2885-19/11909108_1492226137759631_1159527917_a.jpg",
"id"=>"194780705",
"full_name"=>""}}],
"caption"=>
{"created_time"=>"1444181565",
"text"=>"the INCOMPARABLE Amber Nelson closing us out! #loudmouth",
"from"=>
{"username"=>"alex3nglish",
"profile_picture"=>"http://photos-f.ak.instagram.com/hphotos-ak-xaf1/t51.2885-19/s150x150/11906214_483262888501413_294704768_a.jpg",
"id"=>"30822062",
"full_name"=>"Alex English"}}
I'd like to structure it in this way:
hash ={}
hash {"item1"=>
:location => {"latitude"=>40.7181015, "name"=>"Fontanas Bar", "longitude"=>-73.9922791, "id"=>31443955},
:created_time => "1444181565",
:images =>https://scontent.cdninstagram.com/hphotos-xaf1/t51.2885-15/s320x320/e35/12145134_169501263391761_636095824_n.jpg"
:user =>"Alex English"}
I'm iterating over 20 objects, each with their location, images, etc... how can I get a hash structure like the one above ?
This is what I've tried:
array_images = Array.new
# iterate through response object to extract what is needed
response.each do |item|
array_images << { :image => item.images.low_resolution.url,
:location => item.location,:created_time => Time.at(item.created_time.to_i), :user => item.user.full_name}
end
Which works fine. So what is the better way, the fastest one?
The hash that you gave is one item in the array stored at the key "data" in a larger hash right? At least that's how it is for the tags/ endpoint so I'll assume it's the same here. (I'm referring to that array of hashes as data)
hash = {}
data.each_with_index do |h, idx|
hash["item#{idx + 1}"] = {
location: h["location"], #This grabs the entire hash at "location" because you are wanting all of that data
created_time: h["created_time"],
image: h["images"]["low_resolution"]["url"], # You can replace this with whichever resolution.
caption: h["caption"]["from"]["full_name"]
}
end
I feel like you want a more simple solution, but I'm not sure how that's going to happen as you want things nested at different levels and you are pulling things from diverse levels of nesting.

Rail's strong_parameters not marking Array's Hashes as Permitted

I've got a bit of a puzzler on for strong_parameters.
I'm posting a large array of JSON to get processed and added as relational models to a central model. It looks something like this:
{
"buncha_data": {
"foo_data" [
{ "bar": 1, "baz": 3 },
...
]
},
...
}
And I've got a require/permit flow that looks like it should work:
class TheController < ApplicationController
def create
mymodel = MyModel.create import_params
end
def import_params
params.require(:different_property)
params.require(:buncha_data).permit(foo_data: [:bar, :baz])
params
end
end
Yet in the create method when I iterate through this data to create the related model:
self.relatables = posted_data['buncha_data']['foo_data'].map do |raw|
RelatedModel.new raw
end
I get a ActiveModel::ForbiddenAttributesError. What I've ended up having to do is iterate through the array on my own and call permit on each hash in the array, like so:
params.required(:buncha_data).each do |_, list|
list.each{ |row| row.permit [:bar, :baz] }
end
What gives?
As MikeJ pointed out - require and permit do not update the object.
I rewrote my controller to be:
def import_params
params[:different_property] = params.require(:different_property)
params[:buncha_data] = params.require(:buncha_data).permit(foo_data: [:bar, :baz])
params
end
And everything worked great. This is somewhat apparent if you read the source code.

Getting typed results from ActiveRecord raw SQL

In Sequel, I can do:
irb(main):003:0> DB["select false"].get
=> false
Which returns a false boolean. I'd like to be able to do something similar in ActiveRecord:
irb(main):007:0> ActiveRecord::Base.connection.select_value "select false"
=> "f"
As you can see, it returns the string "f". Is there a way to get a false boolean with ActiveRecord? (Similarly, I might be calling a function that returns a timestamptz, an array, etc -- I'd want the returned value to have the correct type)
My use case: I'm calling a database function, want to get back a typed result instead of a string.
While I have no doubt that Björn Nilsson's answer worked when he posted it, it is failing for me with Postgres 9.4 and PG gem version 0.18.2. I have found the following to work after looking through the PG gem documentation:
pg = ActiveRecord::Base.connection
#type_map ||= PG::BasicTypeMapForResults.new(pg.raw_connection)
res = pg.execute("SELECT 'abc'::TEXT AS a, 123::INTEGER AS b, 1.23::FLOAT;")
res.type_map = #type_map
res[0]
# => {"a"=>"abc", "b"=>123, "float8"=>1.23}
Pretty ugly but does what you are asking for:
res = ActiveRecord::Base.connection.
select_all("select 1 as aa, false as aas, 123::varchar, Array[1,2] as xx")
# Breaks unless returned values have unique column names
res.map{|row|row.map{|col,val|res.column_types[col].type_cast val}}
# Don't care about no column names
res.map{|row|
row.values.map.with_index{|val,idx|
res.column_types.values[idx].type_cast val
}
}
gives:
[[1, false, "123", [1, 2]]]
How it works:
res.column_types
returns a hash of columns names and Postgresql column types
Here is a pointer to how it works:
https://github.com/rails/docrails/blob/fb8ac4f7b8487e4bb5c241dc0ba74da30f21ce9f/activerecord/lib/active_record/connection_adapters/postgresql/oid/float.rb
Don't have enough reputation points to respond, but Bjorn's answer and associated replies are broken in Rails 5. This works:
res = ActiveRecord::Base.connection.select_all(sql)
res.to_a.map{|o| o.each{|k, v| o[k] = res.column_types[k].cast v}}
I don't know if it is the way, but you can create activerecord model without table with sort of fake column:
class FunctionValue < ActiveRecord::Base
def self.columns
#columns ||= [];
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
default,
sql_type.to_s,
null
)
end
column :value, :boolean
end
And then you can run this:
function_value = FunctionValue.find_by_sql('select false as value').first
function_value.value
This works for me in rails 5
results = ActiveRecord::Base.connection.select_all(sql)
results.rows.map{ |row| Hash[results.columns.zip(row)] }
Gives nice results
[{"person1"=>563, "person2"=>564, "count"=>1},
{"person1"=>563, "person2"=>566, "count"=>5},
{"person1"=>565, "person2"=>566, "count"=>1}]
In Rails 6, Person.connection.select_all(sql_query).to_a
...will return an array of hashes whose values are type-casted. Example:
[{"id"=>12, "name"=>"John Doe", "vip_client"=>false, "foo"=> nil, "created_at"=>2018-01-24 23:55:58 UTC}]
If you prefer an OpenStruct, use Mike's suggestion:
Person.connection.select_all(sql_query).to_a.map {|r| OpenStruct.new(r) }
If you prefer symbols as keys, call map(&:symbolize_keys) after to_a.

How to construct the 2d structure in a dynamic fashion

I iterate through all cars and its supported attributes (many attributes per car) to create a structure like this, how do I do this in a dynamic fashion.
cars = {
"honda" => {'color' => 'blue', 'type' => 'sedan'}.
"nissan" => {'color' => 'yellow', 'type' => 'sports'}.
...
}
cars.each do |car|
car_attrs = ...
car_attrs.each do |attr|
??? How to construct the above structure
end
end
Your question is not very clear... But i guess this is what you want:
cars = {}
options = {}
options['color'] = 'blue'
...
cars['honda'] = options
Is that what you were looking for?
It sounds like you may be asking for a way to create a 2-dimensional hash without having to explicitly create each child hash. One way to accomplish that is by specifying the default object created for a hash key.
# When we create the cars hash, we tell it to create a new Hash
# for undefined keys
cars = Hash.new { |hash, key| hash[key] = Hash.new }
# We can then assign values two-levels deep as follows
cars["honda"]["color"] = "blue"
cars["honda"]["type"] = "sedan"
cars["nissan"]["color"] = "yellow"
cars["nissan"]["type"] = "sports"
# But be careful not to check for nil using the [] operator
# because a default hash is now created when using it
puts "Found a Toyota" if cars["toyota"]
# The correct way to check would be
puts "Really found a Toyota" if cars.has_key? "toyota"
Many client libraries assume that the [] operator returns a nil default, so make sure other code doesn't depend on that behavior before using this solution. Good luck!
Assuming you are using something similar to ActiveRecord (but easy to modify if you are not):
cars_info = Hash[cars.map { |car| [car.name, car.attributes] }

Resources