I'm storing configuration data in hashes written in flat files. I want to import the hashes into my Class so that I can invoke corresponding methods.
example.rb
{
:test1 => { :url => 'http://www.google.com' },
:test2 => {
{ :title => 'This' } => {:failure => 'sendemal'}
}
}
simpleclass.rb
class Simple
def initialize(file_name)
# Parse the hash
file = File.open(file_name, "r")
#data = file.read
file.close
end
def print
#data
end
a = Simple.new("simpleexample.rb")
b = a.print
puts b.class # => String
How do I convert any "Hashified" String into an actual Hash?
You can use eval(#data), but really it would be better to use a safer and simpler data format like JSON.
You can try YAML.load method
Example:
YAML.load("{test: 't_value'}")
This will return following hash.
{"test"=>"t_value"}
You can also use eval method
Example:
eval("{test: 't_value'}")
This will also return same hash
{"test"=>"t_value"}
Hope this will help.
I would to this using the json gem.
In your Gemfile you use
gem 'json'
and then run bundle install.
In your program you require the gem.
require 'json'
And then you may create your "Hashfield" string by doing:
hash_as_string = hash_object.to_json
and write this to your flat file.
Finally, you may read it easily by doing:
my_hash = JSON.load(File.read('your_flat_file_name'))
This is simple and very easy to do.
Should it not be clear, it is only the hash that must be contained in a JSON file. Suppose that file is "simpleexample.json":
puts File.read("simpleexample.json")
# #{"test1":{"url":"http://www.google.com"},"test2":{"{:title=>\"This\"}":{"failure":"sendemal"}}}
The code can be in a normal Ruby source file, "simpleclass.rb":
puts File.read("simpleclass.rb")
# class Simple
# def initialize(example_file_name)
# #data = JSON.parse(File.read(example_file_name))
# end
# def print
# #data
# end
# end
Then we can write:
require 'json'
require_relative "simpleclass"
a = Simple.new("simpleexample.json")
#=> #<Simple:0x007ffd2189bab8 #data={"test1"=>{"url"=>"http://www.google.com"},
# "test2"=>{"{:title=>\"This\"}"=>{"failure"=>"sendemal"}}}>
a.print
#=> {"test1"=>{"url"=>"http://www.google.com"},
# "test2"=>{"{:title=>\"This\"}"=>{"failure"=>"sendemal"}}}
a.class
#=> Simple
To construct the JSON file from the hash:
h = { :test1=>{ :url=>'http://www.google.com' },
:test2=>{ { :title=>'This' }=>{:failure=>'sendemal' } } }
we write:
File.write("simpleexample.json", JSON.generate(h))
#=> 95
Related
I have a Ruby script that generates a SQLite3 database.
I want to be able to generate an "output.csv" file containing one of the database tables.
Is there a way to handle that in Ruby?
It is easy with Sequel and to_csv:
require 'sequel'
DB = Sequel.sqlite
# since Sequel 3.48.0 to_csv is deprecated,
# we must load the to_csv feature via a extension
DB.extension(:sequel_3_dataset_methods) #define to_csv
DB.create_table(:test){
Fixnum :one
Fixnum :two
Fixnum :three
}
#Prepare some test data
5.times{|i|
DB[:test].insert(i,i*2,i*3)
}
File.open('test.csv', 'w'){|f|
f << DB[:test].to_csv
}
The result is:
one, two, three
0, 0, 0
1, 2, 3
2, 4, 6
3, 6, 9
4, 8, 12
In my test I had problems with line ends, so I needed an additional gsub:
File.open('test.csv', 'w'){|f|
f << DB[:test].to_csv.gsub("\r\n","\n")
}
If you want the export without the header line, use to_csv(false)
Remarks:
.to_csv is deprecated since Sequel 3.48.0 (2013-06-01).
You may use an old version with gem 'sequel', '< 3.48.0' or load the extension sequel_3_dataset_methods).
To get support for other seperators and other CSV-features you may use a combination of Sequel and CSV:
require 'sequel'
require 'csv'
#Build test data
DB = Sequel.sqlite
DB.create_table(:test){
Fixnum :one
Fixnum :two
Fixnum :three
String :four
}
#Prepare some test data
5.times{|i|
DB[:test].insert(i,i*2,i*3, 'test, no %i' % i)
}
#Build csv-file
File.open('test.csv', 'w'){|f|
DB[:test].each{|data|
f << data.values.to_csv(:col_sep=>';')
}
}
Result:
0;0;0;"test, no 0"
1;2;3;"test, no 1"
2;4;6;"test, no 2"
3;6;9;"test, no 3"
4;8;12;"test, no 4"
As an alternative you may patch Sequel::Dataset (modified code from a post of marcalc at Github):
class Sequel::Dataset
require 'csv'
#
#Options:
#* include_column_titles: true/false. default true
#* Other options are forwarded to CSV.generate
def to_csv(options={})
include_column_titles = options.delete(:include_column_titles){true} #default: true
n = naked
cols = n.columns
csv_string = CSV.generate(options) do |csv|
csv << cols if include_column_titles
n.each{|r| csv << cols.collect{|c| r[c] } }
end
csv_string
end
end
# Assume that model is an activerecord model
#secrets = Model.all
#csv = CSV.generate do |csv|
#secrets.each { |secret|
csv << ["#{secret.attr1.to_s}", "#{secret.attr2.to_s"] # and so on till your row is finished
}
end
render :text => #csv, :content_type => 'application/csv'
If you have further problems, leave a comment.
Adding an update for 2020. Since Sequel v5, sequel_3_dataset_methods has been completely removed and is unavailable. As such, generating a CSV as a Database extension has also been completely removed.
It appears the current "best practice" is to add the csv_serializer plugin to a Sequel::Model class. There is a catch here though, that the Sequel::Model class you define must be defined after the call to Sequel.connect. The act of subclassing Sequel::Model invokes a read from the database.
This prevents a typical workflow of pre-defining your classes as part of any generic Gem.
According to the Sequel author, the preferred way to do this is through MyClass = Class.new(Sequel::Model(:tablename)) in-line, or otherwise only calling require within your method definitions.
Making no promises about efficiency, here is a code sample that defines 'best practice'
require 'sequel'
require 'csv'
module SequelTsv
class One
def self.main
db = Sequel.connect('sqlite://blog.db') # requires sqlite3
db.create_table :items do
primary_key :id
String :name
Float :price
end
items = db[:items] # Create a dataset
items.insert(:name => 'abc', :price => rand * 100)
items.insert(:name => 'def', :price => rand * 100)
items.insert(:name => 'ghi', :price => rand * 100)
item_class = Class.new(Sequel::Model(:items))
item_class.class_eval do
plugin :csv_serializer
end
tsv = item_class.to_csv(write_headers: true, col_sep:"\t")
CSV.open('output.tsv', 'w') do |csv|
CSV.parse(tsv) do | c |
csv << c
end
end
end
end
end
SequelTsv::One.main
output:
id name price
1 abc 39.307899453608364
2 def 99.28471503410731
3 ghi 58.0295131255661
I've scrapped the part of the data from the pages with Nokogiri .
require 'net/http'
require 'nokogiri'
require 'open-uri'
require 'json'
sources = {
cb: "http://www.cbbankmm.com/fxratesho.php",
}
puts "Currencies from CB Bank are"
if #page = Nokogiri::HTML(open(sources[:cb]))
(1..3).each do |i|
puts #page.css("tr")[i].text.gsub(/\s+/,'')
end
end
The result is
Currencies from CB Bank are
USD873883
SGD706715
EURO11241135
I would like to format the output to the below JSON format
{
"bank":"CB",
"rates": {
"USD":"[873,883]",
"SGD":"[706,715]",
"EURO":"[1124,1135]"
}
}
Which gems, method do I have to use to get the above Hash or JSON format?
Some abstraction might be an idea. So, perhaps a class to help you with the job:
class Currencies
def initialize(page, bank)
#page = page
#bank = bank
end
def parsed
#parsed ||= #page.css("tr").collect{ |el| el.text.gsub(/\s+/,'') }
end
def to_hash
{
bank: #bank,
rates: {
USD: usd,
SGD: sgd,
....
}
}
end
def usd
parsed[0].gsub(/^USD/, '')
end
def sgd
parsed[1].gsub(/^SGD/, '')
end
...
end
Use it like this
Currencies.new(Nokogiri::HTML(open(sources[:cb])), "CB").to_hash.to_json
Just make an equivalent hash structure in Ruby, and do e.g.
hash = {
"bank" => "CB",
"rates" => {
"USD" => "[873,883]",
"SGD" => "[706,715]",
"EURO" => "[1124,1135]"
}
}
hash.to_json
You are already including the json gem. Obviously you build the Ruby hash up in places where you currently have puts statements.
Edit: If the layout is important to you, you may prefer:
JSON.pretty_generate( hash )
I'm trying to dump duration objects (from the ruby-duration gem) to yaml with a custom type, so they are represented in the form hh:mm:ss. I've tried to modify the answer from this question, but when parsing the yaml with YAML.load, a Fixnum is returned instead of a Duration. Interestingly, the Fixnum is the total number of seconds in the duration, so the parsing seems to work, but convert to Fixnum after that.
My code so far:
class Duration
def to_yaml_type
"!example.com,2012-06-28/duration"
end
def to_yaml(opts = {})
YAML.quick_emit( nil, opts ) { |out|
out.scalar( to_yaml_type, to_string_representation, :plain )
}
end
def to_string_representation
format("%h:%m:%s")
end
def Duration.from_string_representation(string_representation)
split = string_representation.split(":")
Duration.new(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
end
YAML::add_domain_type("example.com,2012-06-28", "duration") do |type, val|
Duration.from_string_representation(val)
end
To clarify, what results I get:
irb> Duration.new(27500).to_yaml
=> "--- !example.com,2012-06-28/duration 7:38:20\n...\n"
irb> YAML.load(Duration.new(27500).to_yaml)
=> 27500
# should be <Duration:0xxxxxxx #seconds=20, #total=27500, #weeks=0, #days=0, #hours=7, #minutes=38>
It look like you’re using the older Syck interface, rather that the newer Psych. Rather than using to_yaml and YAML.quick_emit, you can use encode_with, and instead of add_domain_type use add_tag and init_with. (The documentation for this is pretty poor, the best I can offer is a link to the source).
class Duration
def to_yaml_type
"tag:example.com,2012-06-28/duration"
end
def encode_with coder
coder.represent_scalar to_yaml_type, to_string_representation
end
def init_with coder
split = coder.scalar.split ":"
initialize(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
def to_string_representation
format("%h:%m:%s")
end
def Duration.from_string_representation(string_representation)
split = string_representation.split(":")
Duration.new(:hours => split[0], :minutes => split[1], :seconds => split[2])
end
end
YAML.add_tag "tag:example.com,2012-06-28/duration", Duration
p s = YAML.dump(Duration.new(27500))
p YAML.load s
The output from this is:
"--- !<tag:example.com,2012-06-28/duration> 7:38:20\n...\n"
#<Duration:0x00000100e0e0d8 #seconds=20, #total=27500, #weeks=0, #days=0, #hours=7, #minutes=38>
(The reason the result you’re seeing is the total number of seconds in the Duration is because it is being parsed as sexagesimal integer.)
I can go one way using
require 'json'
def saveUserLib(user_lib)
File.open("/Users/name/Documents/user_lib.json","w") do |f|
f.write($user_lib.to_json)
end
end
uname = gets.chomp
$user_lib["_uname"] = uname
saveUserLib($user_lib)
but how do i get it back again as my user_lib?
You want JSON.parse or JSON.load:
def load_user_lib( filename )
JSON.parse( IO.read(filename) )
end
The key here is to use IO.read as a simple way to load the JSON string from disk, so that it can be parsed. Or, if you have UTF-8 data in your file:
my_object = JSON.parse( IO.read(filename, encoding:'utf-8') )
I've linked to the JSON documentation above, so you should go read that for more details. But in summary:
json = my_object.to_json — method on the specific object to create a JSON string.
json = JSON.generate(my_object) — create JSON string from object.
JSON.dump(my_object, someIO) — create a JSON string and write to a file.
my_object = JSON.parse(json) — create a Ruby object from a JSON string.
my_object = JSON.load(someIO) — create a Ruby object from a file.
Alternatively:
def load_user_lib( filename )
File.open( filename, "r" ) do |f|
JSON.load( f )
end
end
Note: I have used a "snake_case" name for the method corresponding to your "camelCase" saveUserLib as this is the Ruby convention.
JSON.load will do the trick. Here's an example that goes both ways:
>> require 'json'
=> true
>> a = {"1" => "2"}
=> {"1"=>"2"}
>> b = JSON.dump(a)
=> "{\"1\":\"2\"}"
>> c = JSON.load(b)
=> {"1"=>"2"}
here is some example:
require 'json'
source_hash = {s: 12, f: 43}
json_string = JSON.generate source_hash
back_to_hash = JSON.parse json_string
What's the simplest method to convert YAML to dot-separated strings in Ruby?
So this:
root:
child_a: Hello
child_b:
nested_child_a: Nesting
nested_child_b: Nesting Again
child_c: K
To this:
{
"ROOT.CHILD_A" => "Hello",
"ROOT.CHILD_B.NESTED_CHILD_A" => "Nesting",
"ROOT.CHILD_B.NESTED_CHILD_B" => "Nesting Again",
"ROOT.CHILD_C" => "K"
}
It's not a one-liner, but perhaps it will fit your needs
def to_dotted_hash(source, target = {}, namespace = nil)
prefix = "#{namespace}." if namespace
case source
when Hash
source.each do |key, value|
to_dotted_hash(value, target, "#{prefix}#{key}")
end
when Array
source.each_with_index do |value, index|
to_dotted_hash(value, target, "#{prefix}#{index}")
end
else
target[namespace] = source
end
target
end
require 'pp'
require 'yaml'
data = YAML.load(DATA)
pp data
pp to_dotted_hash(data)
__END__
root:
child_a: Hello
child_b:
nested_child_a: Nesting
nested_child_b: Nesting Again
child_c: K
prints
{"root"=>
{"child_a"=>"Hello",
"child_b"=>{"nested_child_a"=>"Nesting", "nested_child_b"=>"Nesting Again"},
"child_c"=>"K"}}
{"root.child_c"=>"K",
"root.child_b.nested_child_a"=>"Nesting",
"root.child_b.nested_child_b"=>"Nesting Again",
"root.child_a"=>"Hello"}