Related
Is there a better way to write this? markdown is a StringIO
coverage_hash_arr = [
{
"Module": "Mobile",
"name": "Sheila Chapman",
"age": 21
},
{
"Module": "Web",
"name": "Hendricks Walton",
"age": 40
},
{
"Module": "Misc",
"name": "Torres Mcdonald",
"age": 39
}
]
coverage_hash_arr.each do |the_hash|
markdown << "------- Status on #{the_hash[:Module]} -------\n"
the_hash.delete(:Module)
the_hash.each {|key, value| markdown << "- #{key}: #{value} \n"}
markdown << "----------------------------------------------\n"
end
I tried this and it seems to work but I wonder if there's a better way (recursion)?
coverage_hash_arr.collect do |the_hash|
the_hash.each do |key,value|
key == :Module ? markdown << "--------- Status for #{value} ----------\n" : markdown << " - #{key}: #{value} \n"
end
markdown << "------------------------------------\n\n"
end
You could:
use puts instead of << to avoid explicit newlines
use center to center the caption horizontally
use map to generate the attribute strings and utilize puts' behavior of printing array elements on separate lines
use without to get a hash without the :Module key
use * to repeat a string
Applied to your code:
markdown = StringIO.new
coverage_hash_arr.each do |hash|
markdown.puts " Status on #{hash[:Module]} ".center(46, '-')
markdown.puts hash.without(:Module).map { |k, v| "- #{k}: #{v}" }
markdown.puts '-' * 46
markdown.puts
end
Output via puts markdown.string:
-------------- Status on Mobile --------------
- name: Sheila Chapman
- age: 21
----------------------------------------------
--------------- Status on Web ----------------
- name: Hendricks Walton
- age: 40
----------------------------------------------
--------------- Status on Misc ---------------
- name: Torres Mcdonald
- age: 39
----------------------------------------------
Note that the above isn't proper Markdown syntax. You might want to change your output to something like this:
### Status on Mobile
- name: Sheila Chapman
- age: 21
### Status on Web
- name: Hendricks Walton
- age: 40
### Status on Misc
- name: Torres Mcdonald
- age: 39
Here's a more streamlined version which has been adapted to be more idiomatic Ruby:
# Define your hashes with keys having consistent case, and omit extraneous
# surrounding quotes unless defining keys like "this-name" which are not
# valid without escaping.
coverage = [
{
module: "Mobile",
name: "Sheila Chapman",
age: 21
},
{
module: "Web",
name: "Hendricks Walton",
age: 40
},
{
module: "Misc",
name: "Torres Mcdonald",
age: 39
}
]
# Iterate over each of these elements...
coverage.each do |entry|
markdown << "------- Status on #{entry[:module]} -------\n"
entry.each do |key, value|
# Skip a particular key
next if (key == :module)
markdown << "- #{key}: #{value} \n"
end
markdown << "----------------------------------------------\n"
end
This can be adapted to have a list of keys to exclude, or the inverse, of having a list of keys to actually print.
There's really nothing wrong with your approach specifically. The major faux-pas committed is in using delete on the data, which mangles it, rendering it useless if you needed to print this again.
It's generally best to try and avoid tampering with the data you're iterating over unless the purpose of that code is clear in its intent to alter it.
It looks like your input data has always the same key/value pairs and I would be more explicit to make it easier to read and to understand what the actual output is:
coverage_hash_arr.each do |hash|
markdown << <<~STRING
------- Status on #{hash[:Module]} -------
- name: #{hash[:name]}
- age: #{hash[:age]}
----------------------------------------------
STRING
end
I am creating a regex matcher using:
Regexp.new(Regexp.union(some_hash.keys))
is it possible to add a boundaries filter to each element of the array so I have:
/\bkey1\b|\bkey2\b|,....../
For regexp keys:
Regexp.union(some_hash.keys.map { |k| /\b#{k}\b/ })
or for literal keys:
Regexp.union(some_hash.keys.map { |k| /\b#{Regexp.escape(k)}\b/ })
The result of Regexp.union is already a Regexp, no need for Regexp.new. In fact, we can also use plain strings inside Regexp.union, the difference being we don't initialise the flags in each subexpression:
Regexp.union(some_hash.keys.map { |k| "\\b#{k}\\b" })
Regexp.union(some_hash.keys.map { |k| "\\b#{Regexp.escape(k)}\\b" })
You can generate regex in simplest way without using Regexp as
hash = {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4}
/\b#{hash.keys.join('\b|\b')}\b/
=>/\ba\b|\bb\b|\bc\b|\bd\b/
Not exactly but you can use the scan method ...
for example:-
a = "cruel world"
a.scan(/\w+/) #=> ["cruel", "world"]
a.scan(/.../) #=> ["cru", "el ", "wor"]
a.scan(/(...)/) #=> [["cru"], ["el "], ["wor"]]
a.scan(/(..)(..)/) #=> [["cr", "ue"], ["l ", "wo"]]
In demo.txt file:
'{
"cars": {
"Nissan": [
{"model":"Sentra", "doors":4},
{"model":"Maxima", "doors":4}
]
}
}'
Expected result:
{
"cars" => {
"Nissan" => [
{
"model"=>"Sentra",
"doors"=>4
},
{
"model" => "Maxima",
"doors"=>4
}
]
}
}
I want to read whole content of the file and parse it to JSON format like JSON.parse(file_contents).Help me out.
I have tried like:
file_contents = File.readlines 'demo.txt'
arr = []
file_contents.each do |d|
puts arr << d.gsub("\n","")
end
puts arr # ["'{", "\"cars\": {", "\"Nissan\": [", "{\"model\":\"Sentra\", \"doors\":4},", "{\"model\":\"Maxima\", \"doors\":4}", "]", "}", "}'"]
puts arr * '' #"'{\"cars\": {\"Nissan\": [{\"model\":\"Sentra\", \"doors\":4},{\"model\":\"Maxima\", \"doors\":4}]}}'"
If you can strip out the leading and trailing ' with gsub or something else, it's as simple as
require 'JSON'
JSON.parse(File.open('demo.txt').read.chop!.reverse.chop!.reverse)
The file contains this string.
str =
"'{
\"cars\": {
\"Nissan\": [
{\"model\":\"Sentra\", \"doors\":4},
{\"model\":\"Maxima\", \"doors\":4}
]
}
}'"
#=> "'{\n \"cars\": {\n \"Nissan\": [\n {\"model\":\"Sentra\", \"doors\":4},\n {\"model\":\"Maxima\", \"doors\":4}\n ]\n }\n }'"
Let's create the file.
FName = 'temp'
File.write(FName, str)
#=> 152
We can JSON-parse the file after removing the single quotes.
require 'json'
JSON.parse(File.read(FName)[1..-2])
#=> {"cars"=>{"Nissan"=>[{"model"=>"Sentra", "doors"=>4},
# {"model"=>"Maxima", "doors"=>4}]}}
If the last line of the file ends with a newline, the file would be
str1 = str + "\n"
#=> "'{\n \"cars\": {\n \"Nissan\": [\n {\"model\":\"Sentra\", \"doors\":4},\n {\"model\":\"Maxima\", \"doors\":4}\n ]\n }\n }'\n"
File.write(FName, str1)
#=> 153
In this case we need to chomp before [1..-2].
JSON.parse(File.read(FName).chomp[1..-2])
#=> {"cars"=>{"Nissan"=>[{"model"=>"Sentra", "doors"=>4}, {"model"=>"Maxima", "doors"=>4}]}}
We could have instead written
JSON.parse(File.read(FName)[1..-3])
but the former has the advantage that it works regardless of whether the file ends with a newline.
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
I have str1 and str2. str1 may or not be an empty string, and I want to construct an array like:
str1 = ""
str2 = "bar"
["bar"]
or
str1 = "foo"
str2 = "bar"
["foo", "bar"]
I can only figure out a way to do this on two lines right now but I know there must be a way to do it one.
In ruby 1.9
[*(str1 unless str1.empty?), str2]
In ruby 1.8
[(str1 unless str1.empty?), str2].compact
[str1, str2].reject {|x| x==''}
# ...or...
[str1, str2].reject &:empty?
Object#tap
[:starting_element].tap do |a|
a << true if true
a << false if false
a << :for_sure
end
# => [:starting_element, true, :for_sure]
So in one line
[].tap { |a| [foo, bar].each { |thing| a << thing unless thing.blank? } }
[bar].tap { |a| a << bar unless foo.blank? }
You can use delete_if:
['', 'hola'].delete_if(&:empty?)
If you're using Rails, you can replace empty? by blank?
['', 'hola'].delete_if(&:blank?)
or use a block:
['', 'hola'].delete_if{ |x| x == '' }
Note that sawa's proposed answer for Ruby 1.9 (currently the answer with the most votes) has issues when used with a hash - as follows;
> [*({foo: 1} if true), {foo: 2}]
[
[0] [
[0] :foo,
[1] 1
],
[1] {
:foo => 2
}
]
Note that the compact example works as you would expect;
[({foo: 1} if true), {foo: 2}].compact
[
[0] {
:foo => 1
},
[1] {
:foo => 2
}
]
You can use a ternary statement:
ary = (str1.empty?) ? [ str2 ] : [ str1, str2 ]
str1 = ''; str2 = 'bar'
(str1.empty?) ? [ str2 ] : [ str1, str2 ] #=> ["bar"]
str1 = 'foo'; str2 = 'bar'
(str1.empty?) ? [ str2 ] : [ str1, str2 ] #=> ["foo", "bar"]
Another way,
(str1.present? ? str1 : []) + [str2]
Perhaps a more concise version of Cyril's answer:
Array.new.tap do |array|
if condition
array << "foo"
end
end
You could monkey-patch Aray pr Enumerable and provide a conditional adding method.
module Array
def add_if(object, condition=true)
self << object if condition
return self
end
end
That way, it would be chainable, preserving most space in line.
array = [].add(:class, is_given?).add(object, false) #etc
A more compact way to do this is using presence, which returns the object if present, or nil if not.
nil values can then be stripped with .compact:
[str1.presence, str2].compact
or even better, the magical * splat operator:
[*str1.presence, str2]
(And yes, pun was intended)
my_array = [str1, str2].find_all{|item| item != ""}
In a way, I feel like there are 2 different questions being asked:
Is there a way to conditionally add to an array in one line?
How do I achieve this specific task.
Regarding question #1:
Other people have already offered a lot of good options. One that I didn't see specifically mentioned though is that you can technically use a semicolon (;) as an inline statement separator and basically write whatever you want on a single line of code. For example:
array = []; if x; array << a; elsif y; array << b; else; array << c; end; array
Regarding question #2:
Here's a fun option to to add to the list. Use the chomp(separator), split, and reverse methods.
str1 = ""
str2 = "bar"
"#{str2},#{str1}".chomp(",").split(",").reverse
#=> ["bar"]
str1 = "foo"
str2 = "bar"
"#{str2},#{str1}".chomp(",").split(",").reverse
#=> ["foo", "bar"]
string.chomp(substring) with and without reverse can be super handy ways to format string results that would otherwise need some conditional statements to handle the same operation somewhere else in the chain.