Ruby Class, I suspect I am doing instance markers wrong - ruby

I want this class to initialize to receive a message to be saved and enter a filename for it. Am I drawing up an error because Ruby only wants values to be instantiated in the init method? Be gentle, I'm new to this. Traceback pasted below.
class DobbsyKretts
idea = 'arbitaryvalue'
def initialize
#Receive idea
puts "Enter an idea, a secret or anything else you want to encrypt. Hit enter to stop typing and save the file"
#idea.gets.reverse.upcase
#Filename and saving - to encrypt the file.
puts "Enter the file name you'd like to have this saved as. Type PLAN at the beginning for plans and REM for reminders"
#file_name.gets.strip
File::open("DobbsyKrett-"+ file_name + ".txt", "w") do |f|
f>>#idea
end
end
end
something = DobbsyKretts.new
Traceback:
testy.rb:11:in `initialize': private method `gets' called for nil:NilClass (NoMethodError)
from testy.rb:21:in `new'
from testy.rb:21:in `<main>'
Enter an idea, a secret or anything else you want to encrypt. Hit enter to stop typing and save the file

You are calling getson #idea before having assigned a value - that's one of the reasons why you get the error. Also, gets should not be called on the instance variable here. Try it like this:
class DobbsyKretts
def initialize
#Receive idea
puts "Enter an idea, a secret or anything else you want to encrypt. Hit enter to stop typing and save the file"
(#idea = gets).reverse.upcase
#Filename and saving - to encrypt the file.
puts "Enter the file name you'd like to have this saved as. Type PLAN at the beginning for plans and REM for reminders"
#file_name = gets.strip
File::open("DobbsyKrett-"+ #file_name + ".txt", "w") do |f|
f << #idea
end
end
end
something = DobbsyKretts.new
This works as you expected, but I just would like to remind you that it is a very bad idea to do something like this in the constructor. You should rather use a dedicated method for generating files and/or asking the user for input.

gets is either a Kernel#gets or IO#gets (I will omit ARGF#gets for brevity), #idea in your case is not an IO object (by default any instance variable is set to nil), and calling Kernel#gets with explicit receiver is prohibited. So correct code is #idea = gets.

Related

Wrong Number of Arguments in Initialize (given 0, expected 1)

I am following along with a tutorial at:
http://neurogami.com/content/neurogami-10_minutes_to_your_first_Ruby_app/#sidebar4
I have checked and rechecked the code, and I do not understand why ruby is not reading my variable app_map as a valid argument.
I have searched online for similar questions, and they exist, yet I can not understand why this variable is not working. I also am not exactly sure what initialize means, as I am an absolute beginner with Ruby. Any insight would be greatly appreciated.
#!/usr/bin/env ruby
class Launcher
def initialize (app_map)
#app_map = app_map
end
#execute the given file using the associate app
def run file_name
application = select_app file_name
system "#{application} #{file_name}"
end
#given a file, lookup the matching application
def select_app file_name
ftype = file_type file_name
#app_map[ ftype ]
end
#return the part of the file name string after the last '.'
def file_type file_name
File.extname( file_name ).gsub( /^\./, '' ).downcase
end
end
launcher = Launcher.new
end
I am not sure what this code is supposed to run, but I have multiple error messages.
tinyapp.rb:8:in `initialize': wrong number of arguments (given 0, expected 1) (ArgumentError)
from tinyapp.rb:30:in `new'
from tinyapp.rb:30:in `<main>'
In this line, you are instantiating a Launcher:
launcher = Launcher.new
That will call the initialize method on it. That method expects an argument:
def initialize (app_map)
#app_map = app_map
end
In order to resolve the error, you will need to pass in a parameter for the app_map argument. I don't know what it's supposed to actually be here, but that'll look something like this:
launcher = Launcher.new(the_app_map)

My very simple custom Puppet type and provider does not work

I am reading about how to create custom types and providers in Puppet.
But I am getting the error:
Error: Could not autoload puppet/provider/createfile/ruby: undefined method `[]' for nil:NilClass
when running the below code:
mymodule/lib/puppet/type/filecreate.rb
require 'fileutils'
Puppet::Type.newtype(:filecreate) do
ensurable do
defaultvalues
defaultto :present
end
#doc = "Create a file."
newproperty(:name, :namevar => true) do
desc "The name of the file"
end
newproperty(:path) do
desc "The path of the file"
end
end
mymodule/lib/puppet/provider/filecreate/ruby.rb
require 'fileutils'
Puppet::Type.type(:filecreate).provide(:ruby) do
desc "create file.."
puts resource[:name] # this line does not seem to work, why?
puts resource[:path] # this line does not seem to work, why?
def create
puts "create file..."
puts resource[:name]
end
def destroy
puts ("destroy file...")
FileUtils.rm resource[:path]+resource[:name]
end
# Exit method never seems to be called
def exists?
puts "is method beeing called???"
File.exists?(resource[:path])
end
end
I guess the way of fetching the parameter values, puts resource[:name] not is correct. So how can I fetch the filename file.txt declared as the namevar for my custom type filecreate (see below)?
Also, method exists does not seem to be called. Why?
And my init.pp contains this simple code:
class myclass {
filecreate{'file.txt':
ensure => present,
path => '/home/myuser/',
}
}
Your puts calls do not work because you try and access an instance attribute (resource) on the class level. It makes no semantic sense to access the values in this context. Remove those calls.
Generally, it is better to use Puppet.debug instead of puts to collect this kind of information.
To find out where such errors come from, call puppet with the --trace option.

YAML Ruby Load multiple environment variables

I inherited a tool that is working correctly but when I try to extend it it just fails. Since I am new to ruby and yaml I dont really know what is the reason why this fails...
So I have a class config that looks like this
class Configuration
def self.[] key
##config[key]
end
def self.load name
##config = nil
io = File.open( File.dirname(__FILE__) + "/../../../config/config.yml" )
YAML::load_documents(io) { |doc| ##config = doc[name] }
raise "Could not locate a configuration named \"#{name}\"" unless ##config
end
def self.[]=key, value
##config[key] = value
end
end
end
raise "Please set the A environment variable" unless ENV['A']
Helpers::Configuration.load(ENV['A'])
raise "Please set the D environment variable" unless ENV['D']
Helpers::Configuration.load(ENV['D'])
raise "Please set the P environment variable" unless ENV['P']
Helpers::Configuration.load(ENV['P'])
So I had a first version with the environment variable A that worked fine, then when I want to integrate 2 more environment variables it fails (they are different key/value sets). I did debug it and it looks like when it reads the second key/value it removes the other ones (such as reading the 3rd removes the previous 2, so I end up with ##config with only the 3rd key/value par instead of all the values I need).
It is probably easy to fix this, any idea how?
Thanks!
EDIT:
The config file use to look like:
Test:
position_x: “56”
position_y: “56”
Now I want to make it like
“x56”:
position_x: “56”
“x15”:
position_x: “15”
“y56”:
position_y: “56”
“y15”:
position_y: “15”
My idea is that I set them separately and I don’t need to create all the combinations…
Each time you call load you delete the previous configuration (in the line ##config = nil). If you want the configuration to be a merger of all files you will want to merge the new configuration to the existing configuration rather than overriding it.
Something like this:
def self.load name
##config ||= {}
io = File.open( File.dirname(__FILE__) + "/../../../config/config.yml" )
YAML::load_documents(io) do |doc|
raise "Could not locate a configuration named \"#{name}\"" unless doc[name]
##config.merge!(doc[name])
end
end
Be aware that if the code was written as it has been because the method was called more than once, and the configuration is expected to reset between reads, you will need to explicitly reset the configuration now:
class Configuration
# ...
def reset_configuration
#config = {}
end
end
Helpers::Configuration.reset_configuration
raise "Please set the A environment variable" unless ENV['A']
Helpers::Configuration.load(ENV['A'])
raise "Please set the D environment variable" unless ENV['D']
Helpers::Configuration.load(ENV['D'])
raise "Please set the P environment variable" unless ENV['P']
Helpers::Configuration.load(ENV['P'])
I'd access the YAML using:
YAML::load_file(File.expand_path("../../../config/config.yml", File.dirname(__FILE__)))
expand_path cleans up the '..' chain and returns the cleaned-up version, relative to FILE. For instance:
foo = '/path/to/a/file'
File.expand_path("../config.yml", File.dirname(foo)) # => "/path/to/config.yml"
load_file reads and parses the entire file and returns it.

Unable to run regex on file content after putting it?

I have created a simple class to handle opening, reading, and closing a file. In addition, I would like to run a regex on its contents to find a 4 digit date. However, when I run my code I get the following error:
file_class.rb:17:in `find_date': undefined method `match' for nil:NilClass (NoMethodError)
from file_class.rb:24:in `<main>'
This error only occurs if I run the read_file method before it, which simply puts the file contents. I am not sure why doing so would result in such an error.
Below is my code:
class MyFile
attr_reader :handle
def initialize(filename)
#handle = File.open(filename)
end
def read_file
puts #handle.gets
end
def finished
#handle.close
end
def find_date
matching = #handle.gets.match(/\d{4}/)
puts matching[0]
end
end
f = MyFile.new('text.txt')
f.read_file
f.find_date
f.finished
Thanks for the help.
I'm guessing your file had a single line of contents.
When you call gets on an open file handle, the handle returns the line it is currently looking at and moves its "cursor" down to the next line. After you've read the last line, gets will return nil.
Your class would be better (for a few reasons) if you read the file once and cache the contents, rather than caching the handle and attempting to read several times:
class MyFile
attr_reader :contents
def initialize(filename)
File.open(filename) do |f|
#contents = f.read
end
end
def find_date
matching = #contents.match(/\d{4}/)
puts matching[0]
end
end
This approach is better because:
You only need to read the file once.
You're reading the whole file at once, not one line at a time (File#read instead of File#gets).
Your class has better encapsulation - other code that wants to use it doesn't need to tell your class to read the file, then find a date, then close the file - all of the logic is internal to your class.
You need to write less code - attr_accessor makes contents available to calling code without you needing to write your own methods. This is good because it's quicker to write and, much more importantly, it's clearer to read.

read json in Ruby and set variables for use in another class

The need here is to read a json file and to make the variables which is done from one class and use them with in another class. What I have so far is
helper.rb
class MAGEINSTALLER_Helper
#note nonrelated items removed
require 'fileutils'
#REFACTOR THIS LATER
def load_settings()
require 'json'
file = File.open("scripts/installer_settings.json", "rb")
contents = file.read
file.close
#note this should be changed for a better content check.. ie:valid json
#so it's a hack for now
if contents.length > 5
begin
parsed = JSON.parse(contents)
rescue SystemCallError
puts "must redo the settings file"
else
puts parsed['bs_mode']
parsed.each do |key, value|
puts "#{key}=>#{value}"
instance_variable_set("#" + key, value) #better way?
end
end
else
puts "must redo the settings file"
end
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
Which is called in a file of Pre_start.rb
class Pre_start
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
mi_h = MAGEINSTALLER_Helper.new
mi_h.load_settings()
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
file="www/depo/newfile-#{bs_MAGEversion}.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{bs_MAGEversion}/file-#{bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
the josn file is valid json and is a simple object (note there is more just showing the relevant)
{
"bs_mode":"lite",
"bs_MAGEversion":"1.8.0.0"
}
The reason I need to have a json settings file is that I will need to pull settings from a bash script and later a php script. This file is the common thread that is used to pass settings each share and need to match.
Right now I end up with an empty string for the value.
The instance_variable_setis creating the variable inside MAGEINSTALLER_Helper class. That's the reason why you can't access these variables.
You can refactor it into a module, like this:
require 'fileutils'
require 'json'
module MAGEINSTALLER_Helper
#note nonrelated items removed
#REFACTOR THIS LATER
def load_settings()
content = begin
JSON.load_file('scripts/installer_settings.json')
rescue
puts 'must redo the settings file'
{} # return an empty Hash object
end
parsed.each {|key, value| instance_variable_set("##{key}", value)}
end
#a method to provide feedback simply
def download(from,to)
puts "completed download for #{from}\n"
end
end
class PreStart
include MAGEINSTALLER_Helper
#note nonrelated items removed
def initialize(params=nil)
puts 'World'
load_settings # The method is available inside the class
file="www/depo/newfile-#{#bs_MAGEversion}.tar.gz"
if !File.exist?(file)
download("http://www.dom.com/#{#bs_MAGEversion}/file-#{#bs_MAGEversion}.tar.gz",file)
else
puts "mage package exists"
end
end
end
I refactored a little bit to more Rubish style.
On this line:
bs_MAGEversion=instance_variable_get("#bs_MAGEversion") #doesn't seem to work
instance_variable_get isn't retrieving from the mi_h Object, which is where your value is stored. The way you've used it, that line is equivalent to:
bs_MAGEversion=#bs_MAGEversion
Changing it to mi_h.instance_variable_get would work. It would also be painfully ugly ruby. But I sense that's not quite what you're after. If I read you correctly, you want this line:
mi_h.load_settings()
to populate #bs_MAGEversion and #bs_mode in your Pre_start object. Ruby doesn't quite work that way. The closest thing to what you're looking for here would probably be a mixin, as described here:
http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_modules.html
We do something similar to this all the time in code at work. The problem, and solution, is proper use of variables and scoping in the main level of your code. We use YAML, you're using JSON, but the idea is the same.
Typically we define a constant, like CONFIG, which we load the YAML into, in our main code, and which is then available in all the code we require. For you, using JSON instead:
require 'json'
require_relative 'helper'
CONFIG = JSON.load_file('path/to/json')
At this point CONFIG would be available to the top-level code and in "helper.rb" code.
As an alternate way of doing it, just load your JSON in either file. The load-time is negligible and it'll still be the same data.
Since the JSON data should be static for the run-time of the program, it's OK to use it in a CONSTANT. Storing it in an instance variable only makes sense if the data would vary from instance to instance of the code, which makes no sense when you're loading data from a JSON or YAML-type file.
Also, notice that I'm using a method from the JSON class. Don't go through the rigamarole you're using to try to copy the JSON into the instance variable.
Stripping your code down as an example:
require 'fileutils'
require 'json'
CONTENTS = JSON.load_file('scripts/installer_settings.json')
class MAGEINSTALLER_Helper
def download(from,to)
puts "completed download for #{from}\n"
end
end
class Pre_start
def initialize(params=nil)
file = "www/depo/newfile-#{ CONFIG['bs_MAGEversion'] }.tar.gz"
if !File.exist?(file)
mi_h.download("http://www.dom.com/#{ CONFIG['bs_MAGEversion'] }/file-#{ CONFIG['bs_MAGEversion'] }.tar.gz", file)
else
puts "mage package exists"
end
end
end
CONFIG can be initialized/loaded in either file, just do it from the top-level before you need to access the contents.
Remember, Ruby starts executing it at the top of the first file and reads downward. Code that is outside of def, class and module blocks gets executed as it's encountered, so the CONFIG initialization will happen as soon as Ruby sees that code. If that happens before you start calling your methods and creating instances of classes then your code will be happy.

Resources