Ruby YAML serialization and unserialization - ruby

I have a problem with Object-Oriented Project Hangman - serialization part. I saved my code in serialize method, but when I try to unserialize it, I have a problem with it. I see all components of classes in the YAML file and when I try to reach them with code, I can't do it. I don't know where the problem is. Here is my serialize method and deserialize method.
def serialize
file = File.open('game.yml', 'w') do |f|
YAML.dump(self, f)
end
file.close
exit
end
def deserialize
File.open('game.yml', 'r') do |f|
p YAML.load(f)
end
# p file['word']
end
If you want to see my all codes, here is my GitHub and Repl
Repl : https://replit.com/#Burakkepuc/Hangman#main.rb
GitHub : https://github.com/Burakkepuc/Hangman

I think it's actually working fine.
YAML.load returns an different instance of Game. You can call methods on that instance, but you don't have any access to the properties.
def deserialize
File.open('game.yml', 'r') do |f|
loaded_game = YAML.load(f)
puts loaded_game.length # fails with method not found
end
# p file['word']
end
add a reader to you Game class and then the above will report the value of the length in the newly load instance of Game.
attr_reader :length
I am not sure what you want to do with the loaded game, but perhaps you simply want to call guess on it?
def deserialize
File.open('game.yml', 'r') do |f|
loaded_game = YAML.load(f)
loaded_game.guess
end
end

Related

Calling custom method from within a Ruby core class

TL;DR;
How can I call a method (written in the middle of nowhere) from within a Ruby core class?
I'm writing a script that manages text files. Here is my code:
File.open("file.txt", "w").each do |f|
f.puts "Text to be inserted"
f.puts text_generated
f.puts "Some other text" if some_condition?
f.puts ""
end
I want to clean up the code by introducing a method:
File.open("file.txt", "w").each do |f|
f.puts_content(text_generated, some_condition?
# -> Generates an error: private method called for <File> (NoMethodError)
end
def puts_content(text, cond)
puts "Text to be inserted"
puts text
puts "Some other text" if cond
puts ""
end
But in fact this method is not callable from within the File class because of private method access.
Can anyone explain that error, and how I can do that?
My workaround is to write those methods inside a custom MyFile class that inherits from File:
MyFile.open("file.txt", "w").each do |f|
f.puts_content # Seems to work
end
class MyFile < File
def puts_content(cond)
puts "Text to be inserted"
puts text_generated_elsewhere
puts "Some other text" if cond
puts ""
end
end
I could put that stuff directly in File, but I'm timid when it comes to touching a language core library.
I want to know if this is a good way to do it.
It is possible to call Ruby core methods from other core modules/classes. Does it mean that all the core modules/classes include or require one another? How does it work under the hood?
When you define a method on the top level, it is added an an instance method on Object and therefore accessible to descendent classes (most of the other core classes)
def foo
1
end
method(:foo)
# => #<Method: Object#foo>
However the access level of this method seems to be different in IRB/pry than when running a script.
In IRB:
puts [].foo
# => 1
In a script:
puts [].foo
# => NoMethodError (private method called...)
Of course, you can always just call the private method using send:
[].send(:foo)
# or, in your case, f.send(:puts_content, text_generated, some_condition?)
Also, in neither case will it overwrite the method on the descendant class if it was already defined:
def length
1
end
puts [].length
# => 0
Your second approach (patching the core class directly) will work and will overwrite the puts_content if it was already defined on File (which it wasn't). However if you want to avoid patching core classes, there are two approaches I recommend:
Use a static (class) method and pass the file object as an argument
class FileUtils
def self.puts_content(file, text, cond)
file.puts "Text to be inserted"
file.puts text
file.puts "Some other text" if cond
file.puts ""
end
end
File.open("file.txt", "w").each do |f|
FileUtils.puts_content(f, text_generated, some_condition?)
end
use a refinement:
module FileUtils
refine File do
def puts_content(text, cond)
puts "Text to be inserted"
puts text
puts "Some other text" if cond
puts ""
end
end
end
# elsewhere ...
using FileUtils
File.open("file.txt", "w").each do |f|
f.puts_content(f, text_generated, some_condition?)
end
You can read about refinements here, but essentially, they're a way to patch a core class in a certain file or class only. This gives you the benefit of that nice, terse monkey-patched syntax with less risk of changing the behavior defined elsewhere.
About your first question. You were getting the error because the method wasn't defined in the File class. So you were not able to call it like this f.puts_content.
You could define a method that receives the File as a parameter puts_content(file, ...).
About the second part of your question, I this is a good solution (thinking object-oriented).

How do I access class variables in ruby, in the context of making a gem?

I am trying to learn ruby right now, but I am struggling with the idea of scope. I have put together an incredibly basic git hub search gem, but I really dont know how to access, or just return a single variable.
Here is the main API handler class (/lib/basic_github_search/basic_github_search_api.rb):
require 'HTTParty'
module BasicGithubSearch
class BasicGithubSearchApi
attr_reader :urls
def initialize(k)
keyword = k
#url = "http://api.github.com/search/repositories?q=#{keyword}&sort=stars&order=desc"
end
def search
response = HTTParty.get(#url, headers: {'User-Agent' => 'BasicGithubSearch_Spider'}, :verify => false)
response.parsed_response
#SearchItems = response["items"]
#urls = []
#SearchItems.each do |i|
#urls += [i["html_url"]]
end
#puts #urls
end
end
BasicGithubSearchApi.new("tetris").search
puts #urls
end
Specifically, I am wondering how I would access the urls variable in the main module's class?
My main class file (/lib/basic_github_search.rb):
require "basic_github_search/version"
require "basic_github_search/basic_github_search_api"
module BasicGithubSearch
def self.gitSearch(keyword)
BasicGithubSearchApi.new(keyword).search
#from here, how would I access the urls variable?
end
end
Thanks for any help you can provide, especially if I am just missing something painfully obvious.
If anyone knows of any simple to follow gems that I can look at, that would be great too, I always find learning from examples help.
In ruby, the last result will be returned from the function:
def search
response = HTTParty.get(#url, ...)
response.parsed_response
# do not capitalize variables names
#search_items = response["items"]
#urls = #search_items.map do |i|
i["html_url"]
end
end
The last assigned value (#urls in this particular case) will be returned.
To use it, just assign a call to method to a variable:
module BasicGithubSearch
def self.gitSearch(keyword)
result = BasicGithubSearchApi.new(keyword).search
puts result.inspect # prints the result out
result # returns the result variable from this method
end
end

Ruby - Send hash data created in one class's method to another class's method for further processing this hash data

I have one class, that in its methods produce an output of a hash.
=> {"A"=>"1", "B"=>"2"}
My question is how can I send this produced hash to another class, with methods that will further process this hash data?
Ive read the ruby doc and searched on StackOverflow, but can't seem to figure out how to get this new class to pick up this data (from the original class).
I am getting "undefined local variable or method" error when attempting to call on the methods of the first class, while in the second class.
Thanks for your help on this.
Hopefully I supplied enough background on my issue that someone can provide some guidance.
EDIT--
Here is the code that I have, which produces a this above mentioned hash. Actually at this stage it's an array.
Ideally I would like to have all the code from build_list method, on downwards, to be in a totally separate class. Doing this would require me to pass the produced array data (from user input) to these other methods, in this new class. In this new class I would like to have the completed playlist printed. So I would like to spit this example code into two classes, with the second class doing all of the processing work on the user supplied artists. I hope this is clearer.
require 'lib/uri.rb'
require 'json'
require 'rest_client'
require 'colorize'
class Playlist
attr_accessor :artistInput, :artist_list
def initialize
#artistInput = artistInput
#artist_list = []
#artist = #artist
end
def self.start #welcome to the application + rules
puts "-------------------------------------------------------------------------------------------------------------------"
puts "|This Playlist Creator application will create you a static playlist from a given list of artists supplied by you.|".colorize(:color => :white, :background => :black)
puts "-------------------------------------------------------------------------------------------------------------------"
puts "Hint: To stop adding artists, type '\N\' + the [enter] key at any time."
# puts "Hint: For artists with spaces, eg \'Lady Gaga\' - please remove the spaces and write as such \'LadyGaga\'."
end
system "clear"
def artistInput #loop that creates an artist_list array of an infinate amount of artists, stops at the keyword 'N'
#artist_list = []
while #artist != ""
puts "\nWhat artist would you like to add to the Playlist?"
#artist = gets.gsub(/\s+/, "")
if #artist != 'N'
puts "You have chosen to add #{#artist} to the Playlist."
end
break if #artist =~ /N/
#artist_list << #artist.gsub(/\s+/, "") #.gsub removes spaces. 'Lady Gaga' => 'LadyGaga'
end
def build_list
##artist_list
list = #artist_list
list.join('&artist=')
end
def build_url_string
string = build_list
url = 'http://developer.echonest.com/api/v4/playlist/static?api_key=G3ABIRIXCOIGGCCRQ&artist=' + string
end
system "clear"
def parse_url
url = build_url_string
response = JSON.parse(RestClient.get url)
song = response['response']['songs'].each do |song|
song.delete("artist_id")
song.delete("id")
puts
song.each {|key, value| puts "#{key.colorize(:white)}: \'#{value.colorize(:color => :white, :background => :black)}\'" }
end
# a = response['response']['songs']
# a.fetch('id')
end
a = parse_url
puts "\nThere are #{a.length} songs in this Playlist\n\n"
#Uncomment this section to see the raw data feed
# puts "////////////////raw data hash feed//////////////"
# puts "#{a.to_s}"
end
end
Its going to be really difficult to answer your question without code samples. But I am going to try my best with this example
class A
def get_hash
{"A"=>"1", "B"=>"2"}
end
end
class B
def process_hash(hash)
#do something with the hash
end
end
hash = A.new.get_hash
B.new.process_hash(hash)

Ruby Mock a file with StringIO

I am trying to mock file read with the help of StringIO in Ruby.
The following is my test and next to that is my method in the main class.
def test_get_symbols_from_StringIO_file
s = StringIO.new("YHOO,141414")
assert_equal(["YHOO,141414"], s.readlines)
end
def get_symbols_from_file (file_name)
IO.readlines(file_name, ',')
end
I want to know if this is the way we mock the file read and also I would like to know if there is some other method to mock the method in the class rather than doing assert equal with contents.
Your method get_symbols_from_file is never called in the test. You're just testing that StringIO#readlines works, i.e.:
StringIO.new("YHOO,141414").readlines == ["YHOO,141414"] #=> true
If you want to use a StringIO instance as a placeholder for your file, you have to change your method to take a File instance rather than a file name:
def get_symbols_from_file(file)
file.readlines(',')
end
Both, File and StringIO instances respond to readlines, so the above implementation can handle both:
def test_get_symbols_from_file
s = StringIO.new("YHOO,141414")
assert_equal(["YHOO,141414"], get_symbols_from_file(s))
end
This test however fails: readlines includes the line separator, so it returns an array with two elements "YHOO," (note the comma) and "141414". You are expecting an array with one element "YHOO,141414".
Maybe you're looking for something like this:
def test_get_symbols_from_file
s = StringIO.new("YHOO,141414")
assert_equal(["YHOO", "141414"], get_symbols_from_file(s))
end
def get_symbols_from_file(file)
file.read.split(',')
end
If you really want to use IO::readlines you could create a Tempfile:
require 'tempfile'
def test_get_symbols_from_file
Tempfile.open("foo") { |f|
f.write "YHOO,141414"
f.close
assert_equal(["YHOO", "141414"], get_symbols_from_file(f.path))
}
end

How can I convert this code to meta-programming, so I can stop duplicating it?

I've got a small but growing framework for building .net systems with ruby / rake , that I've been working on for a while now. In this code base, I have the following:
require 'rake/tasklib'
def assemblyinfo(name=:assemblyinfo, *args, &block)
Albacore::AssemblyInfoTask.new(name, *args, &block)
end
module Albacore
class AssemblyInfoTask < Albacore::AlbacoreTask
def execute(name)
asm = AssemblyInfo.new
asm.load_config_by_task_name(name)
call_task_block(asm)
asm.write
fail if asm.failed
end
end
end
the pattern that this code follows is repeated about 20 times in the framework. The difference in each version is the name of the class being created/called (instead of AssemblyInfoTask, it may be MSBuildTask or NUnitTask), and the contents of the execute method. Each task has it's own execute method implementation.
I'm constantly fixing bugs in this pattern of code and I have to repeat the fix 20 times, every time I need a fix.
I know it's possible to do some meta-programming magic and wire up this code for each of my tasks from a single location... but I'm having a really hard time getting it to work.
my idea is that I want to be able to call something like this:
create_task :assemblyinfo do |name|
asm = AssemblyInfo.new
asm.load_config_by_task_name(name)
call_task_block(asm)
asm.write
fail if asm.failed
end
and this would wire up everything I need.
I need help! tips, suggestions, someone willing to tackle this... how can I keep from having to repeat this pattern of code over and over?
Update: You can get the full source code here: http://github.com/derickbailey/Albacore/ the provided code is /lib/rake/assemblyinfotask.rb
Ok, here's some metaprogramming that will do what you want (in ruby18 or ruby19)
def create_task(taskname, &execute_body)
taskclass = :"#{taskname}Task"
taskmethod = taskname.to_s.downcase.to_sym
# open up the metaclass for main
(class << self; self; end).class_eval do
# can't pass a default to a block parameter in ruby18
define_method(taskmethod) do |*args, &block|
# set default name if none given
args << taskmethod if args.empty?
Albacore.const_get(taskclass).new(*args, &block)
end
end
Albacore.const_set(taskclass, Class.new(Albacore::AlbacoreTask) do
define_method(:execute, &execute_body)
end)
end
create_task :AssemblyInfo do |name|
asm = AssemblyInfo.new
asm.load_config_by_task_name(name)
call_task_block(asm)
asm.write
fail if asm.failed
end
The key tools in the metaprogrammers tool box are:
class<<self;self;end - to get at the metaclass for any object, so you can define methods on that object
define_method - so you can define methods using current local variables
Also useful are
const_set, const_get: allow you to set/get constants
class_eval : allows you to define methods using def as if you were in a class <Classname> ... end region
Something like this, tested on ruby 1.8.6:
class String
def camelize
self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
end
end
class AlbacoreTask; end
def create_task(name, &block)
klass = Class.new AlbacoreTask
klass.send :define_method, :execute, &block
Object.const_set "#{name.to_s.camelize}Task", klass
end
create_task :test do |name|
puts "test: #{name}"
end
testing = TestTask.new
testing.execute 'me'
The core piece is the "create_task" method, it:
Creates new class
adds execute method
Names the class and exposes it

Resources