Ruby transform hash based on mapping - ruby

Let's say I have a mapping of how I want a hash to turn out, along with new key names like this:
JSON_MAP = {
image: {
id: :id,
media_url: :url,
time: :duration,
timestamp: :time_posted,
text_caption: :caption,
metadata: {
camera: :camera_type,
flash: :camera_flash
}
},
viewers: {
views: :view_count,
likes: :likes_count
}
}
and I have a hash like this:
{
image: {
id: 1,
media_url: 'http://placekitten.com',
nsfw: false,
time: 4,
timestamp: 14149292,
text_caption: "I'm a kitten",
metadata: {
camera: 'iPhone',
flash: true
}
},
viewers: {
views: 50,
likes: 15
},
extras: {
features: {
enabled: true
}
}
}
I only want it to transform the data so it ends up like:
{
image: {
id: 1,
url: 'http://placekitten.com',
duration: 4,
time_posted: 14149292,
caption: "I'm a kitten",
metadata: {
camera: 'iPhone',
flash: true
}
},
viewers: {
view_count: 50,
likes_count: 15
}
}
Basically, renaming all the keys based on the source map, and deleting any keys that don't match the source map...

You can obtain your desired result using recursion.
def convert(mapper, hsh)
mapper.each_with_object({}) do |(k,o),h|
next unless hsh.key?(k)
if o.is_a? Hash
h[k] = convert(o, hsh[k])
else
h[o] = hsh[k]
end
end
end
Assuming h equals your second hash,
convert(JSON_MAP, h)
#=> { :image=>{
# :id=>1,
# :url=>"http://placekitten.com",
# :duration=>4,
# :time_posted=>14149292,
# :caption=>"I'm a kitten",
# :metadata=>{
# :camera_type=>"iPhone",
# :camera_flash=>true
# }
# },
# :viewers=>{
# :view_count=>50,
# :likes_count=>15
# }
# }

Related

Dynamically set parameters in define_method

I have a class method ::add_method(name, params = {}) that creates an instance method with define_method.
I need the parameters of the defined method to be keyword arguments depending on the params.
class Whatever
def self.add_method(name, params = {})
# do something with params
define_method name do |?|
# some business
end
end
end
The goal is that when the ::add_method is called with:
params = {
foo: { required: false, default: 0 },
bar: { required: true }
}
Whatever.add_method(:hello, params)
then it creates this method:
def hello(foo: 0, bar:)
# some business
end
Nota bene: this is not the real business, I've over simplified it so the question is easier to understand.
So as advised I went to class_eval.
class Whatever
class << self
def add_method(name, parameters = {})
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}(#{method_parameters(parameters)})
#{method_body(parameters)}
end
RUBY
end
# method_parameters({
# foo: { required: false, default: 0 },
# bar: { required: true }
# })
# => "foo: 0, bar:"
def method_parameters(parameters)
parameters.map do |key, options|
value = options[:required] ? '' : " #{options[:default] || 'nil'}"
"#{key}:#{value}"
end.join(', ')
end
# method_parameters({
# foo: { required: false, default: 0 },
# bar: { required: true }
# })
# => "[foo, bar]"
def method_body(parameters)
"[#{parameters.keys.map(&:to_s).join(', ')}]"
end
end
end
params = {
foo: { required: false, default: 0 },
bar: { required: true }
}
Whatever.add_method(:hello, params)
Whatever.new.hello(bar: true) # => [0, true]
Whatever.new.hello(foo: 42, bar: true) # => [42, true]
Whatever.new.hello # missing keyword: bar (ArgumentError)

Apply delta values on nested fields

Suppose I have record like this:
{
id: 1,
statistics: {
stat1: 1,
global: {
stat2: 3
},
stat111: 99
}
}
I want to make update on record with object:
{
statistics: {
stat1: 8,
global: {
stat2: 6
},
stat4: 3
}
}
And it should be added to current record as delta. So, the result record should looks like this:
{
id: 1,
statistics: {
stat1: 9,
global: {
stat2: 9
},
stat4: 3,
stat111: 99
}
}
Is it possible to make this with one query?
Do you want something generic or something specific?
Specific is easy, this is the generic case:
const updateValExpr = r.expr(updateVal);
const updateStats = (stats, val) => val
.keys()
.map(key => r.branch(
stats.hasFields(key),
[key, stats(key).add(val(key))],
[key, val(key)]
))
.coerceTo('object')
r.table(...)
.update(stats =>
updateStats(stats.without('global'), updateValExpr.without('global'))
.merge({ global: updateStats(stats('global'), updateValExpr('global'))
)
There might be some bugs here sincce it's untested but the solution key point is the updateStats function, the fact that you can get all the keys with .keys() and that coerceTo('object') transforms this array: [['a',1],['b',2]] to this object: { a: 1, b: 2 },
Edit:
You can do it recursively, although with limited stack (since you can't send recursive stacks directly, they resolve when the query is actually built:
function updateStats(stats, val, stack = 10) {
return stack === 0
? {}
: val
.keys()
.map(key => r.branch(
stats.hasFields(key).not(),
[key, val(key)],
stats(key).typeOf().eq('OBJECT'),
[key, updateStats(stats(key), val(key), stack - 1)],
[key, stats(key).add(val(key))]
)).coerceTo('object')
}
r.table(...).update(row => updateStats(row, r(updateVal)).run(conn)
// test in admin panel
updateStats(r({
id: 1,
statistics: {
stat1: 1,
global: {
stat2: 3
},
stat111: 99
}
}), r({
statistics: {
stat1: 8,
global: {
stat2: 6
},
stat4: 3
}
}))

delayed_paperclip not running in the background

I am using the latest versions of both Paperclip (v5.0.0) and Delayed_Paperclip (v3) on Rails 4.2
Everything is working fine except Delayed_Paperclip is not processing the files in the background:
Userfile.rb
class Userfile < ActiveRecord::Base
has_attached_file :userfile,
path: ':get_dir_path/:style_:normalized_file_name',
url: ':get_dir_path/:style_:normalized_file_name',
use_timestamp: false,
styles: lambda { |a| a.instance.check_file_type[:styles] },
only_process: lambda { |a| a.instance.check_file_type[:foreground] },
source_file_options: { all: '-auto-orient' }
validates_attachment_content_type :userfile, content_type: /.*/
process_in_background :userfile,
processing_image_url: lambda { |a|
f = a.instance
f.check_file_type[:processing_image_url].call(f)
},
only_process: lambda { |a| a.instance.check_file_type[:background] }
def check_file_type
if image?
{
styles: {
resized: '800x600',
resized_watermark: {
...
},
thumbnail: {
...
}
},
foreground: [:resized, :resized_watermark, :thumbnail],
background: [],
processing_image_url: lambda { |f| f.actual_path :resized }
}
elsif video?
{
styles: {
screenshot: ['300x300', :jpg],
thumbnail: {
...
},
preview: {
...
},
mp4: {
...
}
},
foreground: [:screenshot, :thumbnail],
background: [:preview, :mp4],
processing_image_url: lambda { |f| f.actual_path :screenshot }
}
elsif audio?
{
styles: {
preview: {
...
},
mp3: {
...
}
},
foreground: [],
background: [:preview, :mp3],
processing_image_url: lambda { |f| f.actual_path }
}
else
{}
end
end
end
Now this all works except for the fact that the ActiveJob that Delayed_Paperclip creates, runs immediately on the same request. Is there something I'm missing to have in process the files after the initial request?

Split an array of hashes in two based on the value of a key

How can split this array of hashes in two based on the value of the ate key?
array = [
{ name: "Gad", ate: true },
{ name: "Lad", ate: false },
{ name: "Bad", ate: true },
{ name: "Sad", ate: false }
]
Example output
array_1 = [
{ name: "Gad", ate: true },
{ name: "Bad", ate: true }
]
array_2 = [
{ name: "Lad", ate: false },
{ name: "Sad", ate: false }
]
Use the Enumerable#partition method:
array.partition { |x| x[:ate] }
# => [[{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}],
# [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]]
Or:
array_1, array_2 = array.partition { |x| x[:ate] }
array_1
# => [{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}]
array_2
# => [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]
array_one, array_two = *array.group_by { |x| x[:ate] }.map(&:last)
=> array_one
=> # [{:name=>"Gad", :ate=>true}, {:name=>"Bad", :ate=>true}]
=> array_two
=> # [{:name=>"Lad", :ate=>false}, {:name=>"Sad", :ate=>false}]
thx #CarySwoveland
I can't compete with partition, but here's another way:
trues = array.select { |h| h[:ate] }
falses = array - trues

How to sort an array by prioritizing specific attributes

So I'm getting records from ActiveRecord and I'd like to do something like:
VIP_LIST = ['John', 'Larry', 'Dan']
records = [
{ name: "Adam" },
{ name: "Larry" },
{ name: "John" },
{ name: "Eric" },
{ name: "Dan" }
]
# This is what I want to end up with:
sort_please(records, VIP_LIST)
=> [
{ name: "John" },
{ name: "Larry" },
{ name: "Dan" },
{ name: "Adam" },
{ name: "Eric" }
]
How can i achieve this?
P.S. There could be values in VIP_LIST that are not even in records
It's not a clever one liner, but it works:
VIP_LIST = ['John', 'Larry', 'Dan', 'Fred']
records = [
{ name: "Adam" },
{ name: "Larry" },
{ name: "John" },
{ name: "Eric" },
{ name: "Dan" }
]
sorted = records.sort_by do |record|
name = record[:name]
if VIP_LIST.include?(name)
VIP_LIST.index(name)
else
records.index(record) + VIP_LIST.length
end
end
p sorted # => [{:name=>"John"}, {:name=>"Larry"}, {:name=>"Dan"}, {:name=>"Adam"}, {:name=>"Eric"}]
try this:
records.sort_by do |x|
[VIP_LIST.index(x[:name]) || VIP_LIST.length, records.index(x)]
end
# => [{:name=>"John"}, {:name=>"Larry"}, {:name=>"Dan"}, {:name=>"Adam"}, {:name=>"Eric"}]
this one?
new_arr = []
VIP_LIST.each do |v|
new_arr << records.select {|r| r[:name] == v } unless nil?
end
new_arr.flatten!
new_arr = new_arr + (records - new_arr)
I think this is both clear and concise:
vip = records.select{ |hash| VIP_LIST.include? hash[:name] }
vip = vip.sort_by{|x| VIP_LIST.find_index(x[:name])} #reorder vip array
sorted_records = (vip + records).uniq

Resources