1

Here is a hash that I save to a file to later be read.

my_hash = {-1 => 20, -2 => 30, -3 => 40}
File.open("my_file.txt", "w") { |f| f.write my_hash }
#how it looks opening the text file
{-1 => 20, -2 => 30, -3 => 40}

When I go to read it, is where my problem is. (following code is separate from top)

my_hash = File.foreach("my_file.txt") { |f| print f }
p my_hash
#=> {-1 => 20, -2 => 30, -3 => 40}nil

that nil messes up the rest of my code..not sure how to get rid of if. Just for clarity the rest of the code...

back_up_hash = {-1 => 20}
if my_hash.nil?
  my_hash = back_up_hash
end

That little nil always makes my_hash equal to back_up_hash. I need that .nil? just in case the file is doesn't have the hash, otherwise the problem just gets pushed further down.

I also tried to read (slurp?..it's a small file) the file like this....

my_hash = File.read("my_file.txt") { |f| print f }
p my_hash
=> "{-1 => 20, -2 => 30, -3 => 40}"
# not sure how to get it out of string form...and I have searched for it.
Phrogz
  • 296,393
  • 112
  • 651
  • 745
melee
  • 13
  • 1
  • 3
  • 1
    I'm not to familiar with Ruby, but I'm pretty sure you'd have to serialize the object to a file, rather than just writing the data structures representation. If I had to guess, I'd say that's part of your problem. – Christian Dean Jul 07 '17 at 23:03
  • You may be interested in using the [`Marshal` module](https://ruby-doc.org/core-2.2.2/Marshal.html) do dump native Ruby objects to disk and quickly load them later. – Phrogz Jul 07 '17 at 23:33
  • @ user. Yes, that thread does have a working answer for me....and I saw it before. Unfortunately, I'm a little overwhelmed with eveything and if the answer isnt near perfect for my situation, it passes me by. Thanks for pointing out that link. Marshal looks like a good solution. – melee Jul 08 '17 at 02:48

4 Answers4

6

You could use the eval method on the string (source)

eval("{-1 => 20, -2 => 30, -3 => 40}")
=> {-1 => 20, -2 => 30, -3 => 40}
2

If you want to take a file on disk whose contents are {-1 => 20, -2 => 30, -3 => 40} and make a hash from it, you want:

hash_str = File.read('my_file.txt')
my_hash  = eval(hash_str) # Treat a string like Ruby code and evaluate it

# or, as a one-liner
my_hash  = eval(File.read('my_file.txt'))

What you were doing is reading in the file and printing it to the screen, one line at a time. The 'print' command does not transform the data, and the foreach method does not map the data it yields to your block into any result. This is why you get nil for your my_hash.

As I recommended in a comment, if you have a Ruby object (like a Hash) and you need to save it to disk and load it later, you may want to use the Marshal module (built into Ruby):

$ irb
irb(main):001:0> h = {-1 => 20, -2 => 30, -3 => 40}
#=> {-1=>20, -2=>30, -3=>40}
irb(main):002:0> File.open('test.marshal','wb'){ |f| Marshal.dump(h, f) }
#=> #<File:test.marshal (closed)>

$ irb     # later, a new irb session with no knowledge of h
irb(main):001:0> h = File.open('test.marshal'){ |f| Marshal.load(f) }
#=> {-1=>20, -2=>30, -3=>40}
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • I never tried eval because I keep reading that it is dangerous. The one liner worked perfect, though. my_hash will have user input, but will always be integers. Knowing that, would you steer me toward the Marshall solution or is eval ok? – melee Jul 08 '17 at 02:20
  • Do I have to manually close eval? – melee Jul 08 '17 at 02:22
  • @melee: No, you don't need to close eval, whatever that would mean. The file itself is closed automatically after being read. Please don't use the `eval` solution though, it is dangerous, especially with user input. – Eric Duminil Jul 08 '17 at 09:42
  • @Phrogz: I really think you should use json or yaml. `eval` is wrong and dangerous `Marshal` is version dependent, and might leave you with unreadable binary files. – Eric Duminil Jul 08 '17 at 09:45
  • @melee As long as you understand why `eval` is dangerous (it runs arbitrary Ruby code; if anyone other than you has the ability to write to that file, then they can do whatever they want on your computer) it's fine to use it. I'd suggest `eval` when you want to hand-edit the file on disk. I'd suggest `Marshal` when the data is huge and needs to be loaded quickly. I'd suggest JSON when you need the data to be safe and portable. I'd suggest YAML if you need to let non-programmers edit the file. I'd suggest XML if you need to query and process complex data. I'd suggest ProtoBufs if you need… – Phrogz Jul 08 '17 at 15:12
1

The proper way to save simple data structures to a file is to serialize them. In this particular case, using JSON is probably a good choice:

# save hash to file:
f.write MultiJson.dump(my_hash)

# load it back:
p MultiJson.load(file_contents)

Keep in mind that JSON is only able to serialize simple, built-in data types (strings, numbers, arrays, hashes and the like). You will not be able to serialize and deserialize custom objects this way without some additional work.

If you don't have MultiJson, try it with JSON instead.

Mate Solymosi
  • 5,699
  • 23
  • 30
0

I have had success with these 2 simple methods:

def create_json_copy
  File.open("db/json_records/stuff.json","w") do |f|
    f.write("#{@existing_data.to_json}")
  end
end

def read_json_copy
  @json = JSON.parse(File.read("db/json_records/stuff.json")).as_json.with_indifferent_access
  @json.each do |identifier,record|
    existing_record = Something.find_by(some_column: identifier)
    if !existing_record
      Something.create!(record.except(:id).except(:created_at).except(:updated_at))
    end
  end
end

note: @existing_data is a Ruby Hash organised as { some_identifier: record_objet, ... } . I call .to_json on it before writing it to file and then when reading I JSON.parse it followed by .as_json, with_indifferent_access isn't really needed here so you can take it off as long as you substitute the symbols inside the excepts.

m3characters
  • 2,240
  • 2
  • 14
  • 18