Redis: Expand key references in values to their values - lua

I am fairly new to redis. I have a CMS that outputs JSON to redis in a structure like this:
partial:1 => {id: 1, title: 'Foo1', ...}
partial:2 => {id: 2, title: 'Foo2', ...}
partial:3 => {id: 3, title: 'Foo3', ...}
page:home => {
id: 'Homepage',
teaser1: 'partial:1',
teaser2: 'partial:2',
teaser3: {type: Slider, content: 'partial:3'}
}
So the JSON can contain other redis-keys that conform to a certain naming scheme in its structure. What I would like to have is a way to query redis so that when I get the page:home-key the references to other keys in the json get 'expanded' to their respective values, like this:
{
id: 'Homepage',
teaser1: {id: 1, title: 'Foo1', ...},
teaser2: {id: 2, title: 'Foo2', ...},
teaser3: {type: Slider, content: {id: 3, title: 'Foo3', ...}
}
Is this possible and how could it be achieved?

Yes. it is quite possible. Here's a working approach:
Create a function to convert your redis hashes (e.g: partial:*,page:home) to lua tables: hgetall
Create a function to check if the given redis hashname conforms with your naming scheme, if so use hgetall to convert it to a lua table; otherwise return the same value: evaluate_hash
Create a function to evaluate and convert all properties of a given hash name and return it as a json: expand
Here's a simple implementation:
--Script: redis_expand_as_json.lua
--Function to Convert a given hash name (e.g: partial:1..n, page:home) to a lua table
local function hgetall(a)local b=redis.call('HGETALL',a)local c={}local d;for e,f in ipairs(b)do if e%2==1 then d=f else c[d]=f end end;return c end
--Function to check if the given value conforms with a naming scheme,
-- if so convert it to a lua table; otherwise return the values as is.
local function evaluate_hash(value)
local pattern = "partial:%d+"
if string.match(value, pattern) then
return hgetall(value)
else
return value
end
end
--Function to convert a given hash_name to a lua table,
-- iterate all elements and convert them to lua table if necessary
-- returns the table as a json object
local function expand(hash_name)
local obj_table = hgetall(hash_name)
for k, val in pairs(obj_table) do
obj_table[k] = evaluate_hash(val)
end
return cjson.encode(obj_table)
end
local page = KEYS[1]
local json_result = expand(page) or {}
redis.log(redis.LOG_NOTICE, tostring(json_result))
return json_result
Testing in console:
redis-cli -c hmset "partial:1" id 1 title foo1
OK
redis-cli -c hmset "partial:2" id 2 title foo2
OK
redis-cli -c hmset 'page:home' id 'Homepage' teaser1 'partial:1' teaser2 'partial:2'
OK
redis-cli EVAL "$(cat redis_expand_as_json.lua)" 1 'page:home'
{"teaser1":{"id":"1","title":"foo1"},"teaser2":{"id":"2","title":"foo2"},"id":"Homepage"}

I don't know Redis but perhaps this works for you:
local T=[[
partial:1 => {id: 1, title: 'Foo1', ...}
partial:2 => {id: 2, title: 'Foo2', ...}
partial:3 => {id: 3, title: 'Foo3', ...}
page:home => {
id: 'Homepage',
teaser1: 'partial:1',
teaser2: 'partial:2',
teaser3: {type: Slider, content: 'partial:3'}
}
]]
local D={}
for k,v in T:gmatch("(%w+:%w+)%s*=>%s*(%b{})") do
D[k]=v
end
print((T:gsub("'(.-)'",D)))

Related

Rail convert array into group

I am trying to convert one of my array into some format where it can convert itself into table format.
I have an array which is:
[
{
id: 1,
Revenue_Account: "Revenue Receipt",
Amount: 59567,
Year: "2012-13",
created_at: "2018-08-21T06:30:17.000Z",
updated_at: "2018-08-21T06:30:17.000Z"
},
{
id: 2,
Revenue_Account: "Revenue Expenditure ",
Amount: 54466,
Year: "2012-13",
created_at: "2018-08-21T06:30:17.000Z",
updated_at: "2018-08-21T06:30:17.000Z"
},
...
]
Full code of my array link to my actual array
I want this data to be converted into this format:
data: [
{
id: 1,
Sector: "Revenue Receipt",
2012-13: 59567,
2013-14: 68919,
2014-15: 72570,
2015-16: 96123,
2016-17: 105585,
2017-18_BE: 137158,
},
{
id: 2,
Sector: "Revenue Expenditure",
2012-13: 59567,
2013-14: 68919,
2014-15: 72570,
2015-16: 96123,
2016-17: 105585,
2017-18_BE: 137158,
},
....
]
I am using this code to group my array:
group = b.group_by{|data| data[:Revenue_Account]}
this is grouping my data as I am expecting in order to achieve my goal I am trying this code.
group = b.group_by{|data| data[:Revenue_Account]}
du = []
group.each do |i|
du.push({Sector:i[0]})
end
This is giving me Sector wise result how can I add year in my code.
You can't have a single id in there because you're grouping up many entries with different ids, but this is how you'd get the array in the format you're asking for:
grouped = {}
b.each do |x|
grouped[x[:Revenue_Account]] ||= {}
grouped[x[:Revenue_Account]][:Sector] = x[:Revenue_Account]
grouped[x[:Revenue_Account]][x[:Year]] = x[:Amount]
end
return {data: grouped.values}
Which gets you:
{
:data=>[
{
:Sector=>"Revenue Receipt",
"2012-13"=>59567,
"2013-14"=>68919,
"2014-15"=>78417,
"2015-16"=>96123,
"2016-17"=>105585,
"2017-18_BE"=>137158
},
{
:Sector=>"Revenue Expenditure ",
"2012-13"=>54466,
"2013-14"=>62477,
"2014-15"=>72570,
"2015-16"=>83616,
"2016-17"=>94765,
"2017-18_BE"=>122603
},
]
}
We build a new hash by looping through the original hash and creating hash keys if they don't exist. Then we start assigning values as you want them to be in the output. On each iteration, we're creating a new key in this hash for the Revenue_Account value if its the first time we've seen it. Then we assign that particular Revenue_Account's Date and Amount to the output. So for value 'Revenue Receipt' it looks like this:
Grouped hash starts off as empty
On first iteration, we see that group["Revenue Receipt"] is nil, so we initialize it with an empty hash via ||= (assign if nil)
We then assign :Sector => "Revenue Receipt" and this entry's Year and Amount, "2012-13" => 59567
Our grouped hash looks like: {"Revenue Receipt" => {:Sector => "Revenue Receipt", "2012-13" => 59567}
On the next iteration we see that group["Revenue Receipt"] is not nil, so ||= does not override it with an empty hash
We then assign :Sector => "Revenue Receipt" and this entry's Year and Amount, "2012-14" => 68919, which adds a new key/value to the existing hash
Our grouped hash now looks like: {"Revenue Receipt" => {:Sector => "Revenue Receipt", "2012-13" => 59567, "2012-14" => 68919}
After we parse the entire array, we now have a hash that has a key of the Revenue_Account, and values which look like the hash output you're expecting.
We discard the key and return only the hash values, which gets you the final output.
Another option, directly manipulating the array.
array_of_data = array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
.each { |h| h[h[:Year]] = h[:Amount]}
.each { |h| h.delete_if{ |k, _| k == :created_at || k == :updated_at || k == :id || k == :Year || k == :Amount} }
.group_by { |h| h[:Sector] }
.values.map { |a| a.inject(:merge) }
Then just:
h = {}
h[:data] = array_of_data
To understand what happens along the code, just ad line by line outputting the result, like:
p array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
Then:
p array
.each { |h| h[:Sector] = h.delete(:Revenue_Account) }
.each { |h| h[h[:Year]] = h[:Amount]}
Etcetera...
To understand .inject(:merge), see here Rails mapping array of hashes onto single hash

rails array of hashes calculate one column

I have an array and it has many columns and I want to change one value of my one column.
My array is:
[
{
id: 1,
Districts: "Lakhisarai",
Area: 15.87,
Production: 67.77,
Productivity: 4271,
Year: 2015,
Area_Colour: "Red",
Production_Colour: "Orange",
Productivity_Colour: "Dark_Green",
created_at: "2018-07-24T11:24:13.000Z",
updated_at: "2018-07-24T11:24:13.000Z"
},
{
id: 29,
Districts: "Begusarai",
Area: 18.53,
Production: 29.35,
Productivity: 1584,
Year: 2015,
Area_Colour: "Red",
Production_Colour: "Red",
Productivity_Colour: "Orange",
created_at: "2018-07-24T11:24:13.000Z",
updated_at: "2018-07-24T11:24:13.000Z"
},
...
]
This is my sample array and I want my Productivity to be divided by 100 for that I am using one empty array and pushing these hashes to my array like:
j = []
b.map do |k|
if k.Productivity
u = k.Productivity/100
j.push({id: k.id, Productivity: u })
else
j.push({id: k.id, Productivity: k.Productivity })
end
Is there any simple way where I can generate this kind of array and reflect my changes to to one column. Is there any way where I don't need to push name of column one by one in push method.
I want to generate exact same array with one modification in productivity
let's say your array is e, then:
e.each { |item| item[:Productivity] = item[:Productivity]/100}
Example:
e = [{p: 12, d: 13}, {p:14, d:70}]
e.each { |item| item[:p] = item[:p]/10}
output: [{:p=>1, :d=>13}, {:p=>1, :d=>70}]
You could take help of map method here to create a new array from your original array, but with the mentioned changes.
ary.map do |elem|
h = elem.slice(:id)
h[:productivity] = elem[:Productivity] / 100 if elem[:Productivity]
h
end
=> [{:id=>1, :productivity=>42}, {:id=>29, :productivity=>15}]
Note, Hash#slice returns a new hash with only the key-value pairs for the keys passed in argument e.g. here, it returns { id: 1 } for first element.
Also, we are assigning the calculated productivity to the output only when it is set on original hash. Hence, the if condition there.

String interpolation with subhashes

In my code I want to use string interpolation for an email subject I am generating.
output = "this is my %{title}" % {title: "Text here"}
This works as expected, but is there a way to use hashes inside of hashes and still be able to use string interpolation?
It would be awesome if I could do something like:
output = "this is my %{title.text}" % {title: {text: "text here"}}
In Ruby 2.3, sprintf checks the hash's default value, so you could provide a default_proc to dig up the nested value:
hash = {title: {text: "text here"}}
hash.default_proc = proc { |h, k| h.dig(*k.to_s.split('.').map(&:to_sym)) }
"this is my %{title.text}" % hash
#=> "this is my text here"
Kind of hacky, but it seems to work.
I don't think this is possible with % method. You'd have to use regular Ruby interpolation with "#{}".
I'd also point out that you can use OpenStruct.
title = OpenStruct.new(text: 'text here')
output = "this is my #{title.text}"
It's actually not hard to make this work if you write a simple utility method to "squash" a nested Hash's keys, e.g.:
def squash_hash(hsh, stack=[])
hsh.reduce({}) do |res, (key, val)|
next_stack = [ *stack, key ]
if val.is_a?(Hash)
next res.merge(squash_hash(val, next_stack))
end
res.merge(next_stack.join(".").to_sym => val)
end
end
hsh = { foo: { bar: 1, baz: { qux: 2 } }, quux: 3 }
p squash_hash(hsh)
# => { :"foo.bar" => 1, :"foo.baz.qux" => 2, :quux => 3 }
puts <<END % squash_hash(hsh)
foo.bar: %{foo.bar}
foo.baz.qux: %{foo.baz.qux}
quux: %{quux}
END
# => foo.bar: 1
# foo.baz.qux: 2
# quux: 3

Parsing JSON file in Rails: extracting and mapping values to DB model

I have a JSON file the has a structure something like this:
[
{
"Name" : {
"Attribute" : " Value",
"Attribute2" : " Value2",
"Attribute3" : " Value3",
}
, "Name2" : {
...
}
]
I'm trying to seed this file into a database table. I don't need all attribute:value pairs, so I need to map the ones I need to the create command in the loop. This is what I've attempted in the seeds.rb file:
json = ActiveSupport::JSON.decode(File.read("db/exercises.json"))
json.each_with_index do |e, index|
Model.create!(
name: e[0]
)
end
What I essentially need to do is something like this:
Model.create!(
name: e[0],
attribute1: e[0][attribute1],
attribute3: e[0][attribute3]
)
Any help ?
Most of your confusion is around how to access your objects. ActiveSupport::JSON.decode parses your data into an array containing a Hash with keys "Name", "Name2", etc. Hash#each yields pairs of |key, value| that you can use to populate your database. Your keys become Model#name, and then you can map the data's attributes to your model's.
data = ActiveSupport::JSON.decode(File.read("db/exercises.json"))
data = data[0] # Unwrap the Array
data.each do |name, attributes|
Model.create!(
name: name,
column_one: attributes['one'],
column_two: attributes['another one']
)
end
If your file shares key names with your columns, Rails provides Hash#slice to easily pull out a subset.
> {a: 1, b: 2, c: 3}.slice(:a, :c)
#=> {a: 1, c: 3}

Nested dynamic array rails

I have created an array
steps = [{'title' =>'abc','content' =>'click this', 'target' => 'bca'}]
tours = ['id'=>'tour', 'steps:' => "#{steps}"]
puts tours
Getting following output :
{"id"=>"tour", "steps:"=>"[{\"title\"=>\"abc\", \"content\"=>\"click this\", \"target\"=>\"bca\"}]"}
The structure of the output is right but i don't want these \ in the output.
What should i do to remove these \.
Thanks!
In ruby "#{}" invoke the to_s method on the object. You can check it run the following code: steps.to_s.
Just use:
tours = ['id'=>'tour', 'steps:' => steps]
Because this:
"[{\"title\"=>\"abc\", \"content\"=>\"click this\", \"target\"=>\"bca\"}]"
is a string representation of:
[{'title' =>'abc','content' =>'click this', 'target' => 'bca'}]
Зелёный has the direct answer for you, however, there's a more pressing issue I would point out -- I think you're getting confused between {hashes} and [arrays]
--
An array is a set of unordered data:
array = [3, 4, 5, 6, 0, 5, 3, "cat", "dog"]
Arrays are mainly used for non-sequential collections of data, a good example being product_ids in a shopping cart.
Arrays can only be identified by using the location of the data inside the array:
array[1] # -> 4
array[2] # -> 5
--
A hash is a collection of key:value pairs:
hash = {name: "Greg", type: "cat"}
Hashes are used when you wish to assign multiple values to a single piece of data, and can be called by referencing the "key" of the hash:
hash["name"] #-> Greg
hash["type"] #-> cat
Whilst you can create an array of hashes:
hash_array = [{name: "Greg", type: "cat"}, {name: "Sulla", type: "Dog"}]
... the problem with this is that you cannot call the hashes directly - they have to be through the array:
hash_array["name"] # -> error
hash_array[0]["name"] #-> "Greg"
Thus, I'd use the following in your example:
steps = {'title' =>'abc','content' =>'click this', 'target' => 'bca'}
tours = {id: 'tour', steps: steps}
tours.inspect #-> { id: "tour", steps: { "title" => "abc", "content" => "click this", "target" => "bca" }

Resources