I am learning Ruby & Perl has this very convenient module called Data::Dumper, which allows you to recursively analyze a data structure (like hash) & allow you to print it. This is very useful while debugging. Is there some thing similar for Ruby?
Look into pp
example:
require 'pp'
x = { :a => [1,2,3, {:foo => bar}]}
pp x
there is also the inspect method which also works quite nicely
x = { :a => [1,2,3, {:foo => bar}]}
puts x.inspect
I normally use a YAML dump if I need to quickly check something.
In irb the syntax is simply y obj_to_inspect. In a normal Ruby app, you may need to add a require 'YAML' to the file, not sure.
Here is an example in irb:
>> my_hash = {:array => [0,2,5,6], :sub_hash => {:a => 1, :b => 2}, :visible => true}
=> {:sub_hash=>{:b=>2, :a=>1}, :visible=>true, :array=>[0, 2, 5, 6]}
>> y my_hash # <----- THE IMPORTANT LINE
---
:sub_hash:
:b: 2
:a: 1
:visible: true
:array:
- 0
- 2
- 5
- 6
=> nil
>>
The final => nil just means the method didn't return anything. It has nothing to do with your data structure.
you can use Marshal, amarshal, YAML
Related
I am learning Ruby & Perl has this very convenient module called Data::Dumper, which allows you to recursively analyze a data structure (like hash) & allow you to print it. This is very useful while debugging. Is there some thing similar for Ruby?
Look into pp
example:
require 'pp'
x = { :a => [1,2,3, {:foo => bar}]}
pp x
there is also the inspect method which also works quite nicely
x = { :a => [1,2,3, {:foo => bar}]}
puts x.inspect
This question already has answers here:
Saving hashes to file on Ruby
(3 answers)
Closed 5 years ago.
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.
You could use the eval method on the string (source)
eval("{-1 => 20, -2 => 30, -3 => 40}")
=> {-1 => 20, -2 => 30, -3 => 40}
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}
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.
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.
The Savon gem I am using is giving me back a single object or an array, and I have no way to know which it will be until the SOAP response comes back.
For convenience I would like to have a nil response converted to [], a single response converted to [obj] and an array stay as an array. This can easily be done with Kernel#Array, thus:
> Array nil
=> []
> Array 1
=> [1]
> Array [1,2,3]
=> [1, 2, 3]
However, because Kernel#Array calls to_a, it fails for Hash which overrides to_a:
> Array({a: 1})
=> [[:a, 1]]
> Array([{a: 1}, {b: 2}])
=> [{:a=>1}, {:b=>2}]
On line 2 above I would like to see [{:a=>1}].
If you are using ActiveSupport, you can do the following:
> Array.wrap({a: 1})
=> [{:a, 1}]
> Array.wrap([{a: 1}, {b: 2}])
=> [{:a=>1}, {:b=>2}]
>> [nil].compact.flatten(1)
=> []
>> [1].compact.flatten(1)
=> [1]
>> [{a: 1, b: 2}].compact.flatten(1)
=> [{:a=>1, :b=>2}]
Currently I am able to bypass Hash#to_a with my own straight_to_a method:
def straight_to_a(o)
o.kind_of?(Array) ? o : [o].compact
end
Thus:
> straight_to_a nil
=> []
> straight_to_a 1
=> [1]
> straight_to_a( {a: 1} )
=> [{:a=>1}]
I'm hoping there's an easier way?
Another poster suggested the use of Active Support. If you don't want to add an extra dependency to your project for just one method, here is the source code for Active Support's Array.wrap:
class Array
def self.wrap(object)
if object.nil?
[]
elsif object.respond_to?(:to_ary)
object.to_ary || [object]
else
[object]
end
end
end
You could easily add this code to your own utilities.rb or core_extensions.rb file and include that in your project.
Your solution seems ok, perhaps you can try something based on flatten, like
def straight_to_a *ary
ary.flatten(1)
end
I'm trying to implement the rails-settings gem (https://github.com/100hz/rails-settings) into my Rails 3 project using Ruby 1.8.7
Setting and retrieving the settings works perfectly, but I get an error if I try getting all settings of a specific user.
So, in the 'rails console' the following works:
user = User.find(123)
user.settings.color = :red
user.settings.color
But if I try to get all settings:
user.settings.all
I get:
NoMethodError: undefined method `merge' for []:Array
from /[...]/.rvm/gems/ruby-1.8.7-p334/bundler/gems/rails-settings-883114dfd933/lib/rails-settings/settings.rb:55:in `all'
from (irb):5
line 55 in the settings.rb:
#retrieve all settings as a hash (optionally starting with a given namespace)
def self.all(starting_with=nil)
options = starting_with ? { :conditions => "var LIKE '#{starting_with}%'"} : {}
vars = thing_scoped.find(:all, {:select => 'var, value'}.merge(options))
result = {}
vars.each do |record|
result[record.var] = record.value
end
# line 55 is below this one...
##defaults.select{ |k| k =~ /^#{starting_with}/ }.merge(result).with_indifferent_access
end
Whats the problem here? Or is this a ruby 1.8.7 vs. 1.9.2 thing?
That's a Ruby 1.8.7 vs. 1.9.2 thing
The Hash select method under ruby 1.8.7 will return an Array of Arrays.
Example:
{:a => 'a', :b => 'b', :c => 'c'}.select {|k, v| v > 'a'} #=> [[:b,'b'],[:c,'c']]
While the same thing running Ruby 1.9.2 will return:
{:a => 'a', :b => 'b', :c => 'c'}.select {|k, v| v > 'a'} #=> {:b => 'b',:c => 'c'}
You will need to post process the result and turn it into a hsah again or use something like inject.
Edit:
Here is a quick/ugly example of the inject
{:a => 'a', :b => 'b', :c => 'c'}.inject({}) {|r, e| e[1] > 'a' ? r.merge({e[0] => e[1]}) : r }
Semantically speaking:
collection.inject(container) { |container, element| select_condition ? container + element : container }
Edit 2: (Based on #CaleyWoods post)
Hash[*##defaults.select{ |k,v| k =~ /^#{starting_with}/ }.flatten].merge(result)
The |k, v| will prevent unnecessary warnings.
This looks like it's trying to do a merge on an Array which is not a method the Array class in Ruby. Merge is supported for Hash, it looks like your returned object is not the correct type. The author was definitely relying on a hash, in the next to last line 'with_indifferent_access' is called which is trying to allow you to select items from the hash with strings or symbols.
I can't examine the gem further right now and I wish I could provide a more helpful answer. If this hasn't been answered later i'll come back and help you out.
Not sure why the author is using double and single quotes in options and vars. He's also trying to populate the hash by hand instead of with inject. It's not awful by any means but I think there's room for improvement in the small bit of code you posted from the file.
Example for array
arr = ["a", "b", "c"]
# TODO create an alias for arr[1] as x
x = "X"
# arr should be ["a", "X", "c"] here
Example for hash
hash = { :a => "aaa", :b => "bbb" , :c => "ccc" }
# TODO create an alias for hash[:b] as y
y = "YYY"
# hash should be { :a => "aaa", :b => "YYY" , :c => "ccc" } here
And also an alias for a variable?
var = 5
# TODO create an alias for var as z
z = 7
# var should be 7 here
Motivation: I have a big large deep construct of data, and you can imagine the rest. I want to use it in a read-only manner, but due to performance reasons copy is not permissible.
Metaphor: I want to choose context from a larger data structure and I want to access it with a short and simple name.
UPDATE: Problem solved as sepp2k advised. I just want to draw a summarizing picture here about the solution.
irb(main):001:0> arr = [ { "a" => 1, "b" => 2}, { "x" => 7, "y" => 8 } ]
=> [{"a"=>1, "b"=>2}, {"x"=>7, "y"=>8}]
irb(main):002:0> i = arr[0]
=> {"a"=>1, "b"=>2}
irb(main):004:0> j = arr[1]
=> {"x"=>7, "y"=>8}
irb(main):007:0> j["z"] = 9
=> 9
irb(main):008:0> j
=> {"x"=>7, "y"=>8, "z"=>9}
irb(main):009:0> arr
=> [{"a"=>1, "b"=>2}, {"x"=>7, "y"=>8, "z"=>9}]
What you want is not possible. There is no feature in ruby that you could use to make your examples work like you want.
However since you're saying you want to only use it read-only, there is no need for that. You can just do x = myNestedStructure[foo][bar][baz]. There will be no copying involved when you do that. Assignment does not copy the assigned object in ruby.
You would have to create a method that is your alias, which would update the data.
def y=(value)
arr[:b]=value
end
Then call it.
self.y="foo"
Edit: updated second code snippet.