Can't load bot data because no implicit conversion of nil into String (RuntimeError) - ruby

I'm currently going through "Beginning Ruby", Chapter 12, "Beginning Ruby-ChatterBox" where it builds a conversation bot. When running the basic_client.rb file, I get an error message:
: Can't load bot data because no implicit conversion of nil into String (RuntimeError)
from C:/RailsInstaller/Ruby2.1.0/bin/bot.rb:13:in `initialize'
from C:/RailsInstaller/Ruby2.1.0/bin/basic_client.rb:4:in `new'
from C:/RailsInstaller/Ruby2.1.0/bin/basic_client.rb:4:in `<main>'
Similar questions have been asked in the past. I consulted these examples but still can't solve this problem. Please I would appreciate if anyone could help with letting me know what I am doing wrong. Here is an extract of my code files. Please if extra information is needed, I would be glad to let you know.
bot.rb:
require 'yaml'
require_relative 'wordplay'
#A basic implementation of a chatterbox
class Bot
attr_reader :name
#Initialies the bot object, loads in the external YAML data
# file and sets bot's name. Raises an exception if
# the data loading process fails.
def initialize(options)
#name = options[:name] || "Unnamed Bot"
begin
#data = YAML.load(File.read(options[:data_file]))
rescue => e
raise "Can't load bot data because #{e}"
end
end
end
basic_client.rb:
require_relative 'bot'
bot = Bot.new(:name => ARGV[0], :data_file => ARGV[1])
puts bot.greeting
while input = $stdin.gets and input.chomp != 'end'
puts '>> ' + bot.response_to(input)
end
puts bot.farewell

The clue is in your error message:
from C:/RailsInstaller/Ruby2.1.0/bin/bot.rb:13:in `initialize'
Line 13 of bot.rb, in your initialize method.
no implicit conversion of nil into String (RuntimeError)
It can't turn nil into a string.
Your code specifies:
#name = options[:name] || "Unnamed Bot"
Which guards against options[:name] being nil
However
#data = YAML.load(File.read(options[:data_file]))
Specifies that it tries to load a file with a path of options[:data_file]. Which is nil.
So when running your client ensure to pass your name AND the data file where the yaml should be loaded from. OR create a default file and pass that:
#data = YAML.load(File.read(options[:data_file] || 'path/to/default.yml'))

YAML.load(nil)
TypeError: no implicit conversion of nil into String
so you should be careful: the result of File.read(options[:data_file]) is probably nil.
YAML.load(nil.to_s)
=> false

Related

How to test a Ruby Roda app using RSpec to pass an argument to app.new with initialize

This question probably has a simple answer but I can't find any examples for using Roda with RSpec3, so it is difficult to troubleshoot.
I am using Marston and Dees "Effective Testing w/ RSpec3" book which uses Sinatra instead of Roda. I am having difficulty passing an object to API.new, and, from the book, this is what works with Sinatra but fails with a "wrong number of arguments" error when I substitute Roda.
Depending on whether I pass arguments with super or no arguments with super(), the error switches to indicate that the failure occurs either at the initialize method or in the call to Rack::Test::Methods post in the spec.
I see that in Rack::Test, in the Github repo README, I may have to use Rack::Builder.parse_file("config.ru") but that didn't help.
Here are the two errors that rspec shows when using super without brackets:
Failures:
1) MbrTrak::API POST /users when the user is successfully recorded returns the user id
Failure/Error: post '/users', JSON.generate(user)
ArgumentError:
wrong number of arguments (given 1, expected 0)
# ./spec/unit/app/api_spec.rb:21:in `block (4 levels) in <module:MbrTrak>'
And when using super():
1) MbrTrak::API POST /users when the user is successfully recorded returns the user id
Failure/Error: super()
ArgumentError:
wrong number of arguments (given 0, expected 1)
# ./app/api.rb:8:in `initialize'
# ./spec/unit/app/api_spec.rb:10:in `new'
# ./spec/unit/app/api_spec.rb:10:in `app'
# ./spec/unit/app/api_spec.rb:21:in `block (4 levels) in <module:MbrTrak>'
This is my api_spec.rb:
require_relative '../../../app/api'
require 'rack/test'
module MbrTrak
RecordResult = Struct.new(:success?, :expense_id, :error_message)
RSpec.describe API do
include Rack::Test::Methods
def app
API.new(directory: directory)
end
let(:directory) { instance_double('MbrTrak::Directory')}
describe 'POST /users' do
context 'when the user is successfully recorded' do
it 'returns the user id' do
user = { 'some' => 'user' }
allow(directory).to receive(:record)
.with(user)
.and_return(RecordResult.new(true, 417, nil))
post '/users', JSON.generate(user)
parsed = JSON.parse(last_response.body)
expect(parsed).to include('user_id' => 417)
end
end
end
end
end
And here is my api.rb file:
require 'roda'
require 'json'
module MbrTrak
class API < Roda
def initialize(directory: Directory.new)
#directory = directory
super()
end
plugin :render, escape: true
plugin :json
route do |r|
r.on "users" do
r.is Integer do |id|
r.get do
JSON.generate([])
end
end
r.post do
user = JSON.parse(request.body.read)
result = #directory.record(user)
JSON.generate('user_id' => result.user_id)
end
end
end
end
end
My config.ru is:
require "./app/api"
run MbrTrak::API
Well roda has defined initialize method that receives env as an argument which is being called by the app method of the class. Looks atm like this
def self.app
...
lambda{|env| new(env)._roda_handle_main_route}
...
end
And the constructor of the app looks like this
def initialize(env)
When you run your config.ru with run MbrTrack::API you are actually invoking the call method of the roda class which looks like this
def self.call(env)
app.call(env)
end
Because you have redefined the constructor to accept hash positional argument this no longer works and it throws the error you are receiving
ArgumentError:
wrong number of arguments (given 0, expected 1)
Now what problem are you trying to solve, if you want to make your API class configurable one way to go is to try out dry-configurable which is part of the great dry-ruby gem collection.
If you want to do something else feel free to ask.
It has been a long time since you posted your question so hope you will still find this helpful.

Facing "no implicit conversion of nil into String" error inside eval statement

Exercise :Creating Classes from CSV Started
Read a csv format file and construct a new class with the name of the file dynamically. So if the csv is persons.csv, the ruby class should be person, if it's places.csv, the ruby class should be places
Also create methods for reading and displaying each value in "csv" file and values in first row of csv file will act as name of the function.
Construct an array of objects and associate each object with the row of a csv file.
For example the content of the csv file could be
name,age,city
gaurav,23,karnal
vilok,23,hissar
My expected output is correct. I am able to print the method name as first row of CSV and remaining lines of code has been evaluated and printed as strings on console.
But along with expected output I'm facing below error.
classes_from_CSV.rb:21:in `eval': no implicit conversion of nil into String (TypeError)
from classes_from_CSV.rb:21:in `block (2 levels) in new_method'
from classes_from_CSV.rb:25:in `call'
from classes_from_CSV.rb:34:in `<main>'
I just want to know how should I remove this error and what is the correct way of evaluating string type user input inside dynamic methods.
require 'csv'
class CsvManipulator
def first_line_csv
CSV.open("Input.csv", 'r') { |csv| csv.first }
end
def remaining_line_csv
text = File.readlines("Input.csv")[1..-1].join()
csv = CSV.parse(text, headers: true)
end
end
class MethodCreator < CsvManipulator
def initialize(class_name)
#klass = Class.new
Object.const_set(class_name, #klass)
end
def new_method(method_name, code_str)
#klass.class_eval do
puts define_method(method_name) { eval(puts"#{code_str}") }
end
end
def call(method_name)
#klass.new.send(method_name)
end
end
class_name = "Input"
obj = MethodCreator.new(class_name)
method_name = (obj.first_line_csv).join(', ')
code_str = obj.remaining_line_csv
obj.new_method(method_name, code_str)
puts obj.call(method_name)
Expected output :
name, age, city
gaurav,23,karnal
vilok,23,hissar
Actual output :
name, age, city
gaurav,23,karnal
vilok,23,hissar
classes_from_CSV.rb:21:in `eval': no implicit conversion of nil into String (TypeError)
from classes_from_CSV.rb:21:in `block (2 levels) in new_method
from classes_from_CSV.rb:25:in `call'
from classes_from_CSV.rb:34:in `<main>'
You are only calling eval in one place, so the error is not hard to find:
eval(puts"#{code_str}")
puts returns nil, so you are effectively calling
eval(nil)
However, eval must be called with a string.

Getting undefined error in mass text app

I'm creating a app that sends mass texts using a JSON file with the numbers and names. Every time I test load the app in IRB I get the error:
NameError: undefined local variable or method `data_from_file' for main:Object
from /home/qc/tep/Coding Stuff/Ruby/text app/main.rb:14:in `contacts_from_file'
I understand what the error means, but I don't understand why I'm getting the error, here's the source code:
require 'json'
def sanatize(numbers)
"+1" + number.gsub(/^1|\D/, "")
end
def numbers_from_file
file = open('numbers.json').read
JSON.parse(file)
end
def contacts_from_file
contacts= { }
data_from_file['feed']['entry'].each do |entry|
name = entry['gsx$name']['$t']
number = entry['gsx$number']['$t']
contacts[sanatize(number)] = name
end
contacts
end
def contact_numbers
contacts_from_file.keys
end
def contact_name
contacts_from_file[number]
end
And here's the JSON file:
{
'1**********' => 'Big Bird'
'1**********'} => 'Josh'
}
If anybody could help me and tell me why the data_from_file is "undefined" it would be extremely helpful, thank you ahead of time.
You never define data_from_file, you just try to read from it in the contects_from_file method.
Perhaps you meant numbers_from_file instead of data_from_file?

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.

Having 'allocator undefined for Data' when saving with ActiveResource

What I am missing? I am trying to use a rest service for with Active resource, I have the following:
class User < ActiveResource::Base
self.site = "http://localhost:3000/"
self.element_name = "users"
self.format = :json
end
user = User.new(
:name => "Test",
:email => "test.user#domain.com")
p user
if user.save
puts "success: #{user.uuid}"
else
puts "error: #{user.errors.full_messages.to_sentence}"
end
And the following output for the user:
#<User:0x1011a2d20 #prefix_options={}, #attributes={"name"=>"Test", "email"=>"test.user#domain.com"}>
and this error:
/Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `new': allocator undefined for Data (TypeError)
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `load'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `each'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `load'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1322:in `load_attributes_from_response'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1316:in `create_without_notifications'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1314:in `tap'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1314:in `create_without_notifications'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/observing.rb:11:in `create'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1117:in `save_without_validation'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/validations.rb:87:in `save_without_notifications'
from /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/observing.rb:11:in `save'
from import_rest.rb:22
If I user curl for my rest service it would be like:
curl -v -X POST -H 'Content-Type: application/json' -d '{"name":"test curl", "email":"test#gmail.com"}' http://localhost:3000/users
with the response:
{"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}
There is a built-in type named Data, whose purpose is rather mysterious. You appear to be bumping into it:
$ ruby -e 'Data.new'
-e:1:in `new': allocator undefined for Data (TypeError)
from -e:1
The question is, how did it get there? The last stack frame puts us here. So, it appears Data wandered out of a call to find_or_create_resource_for. The code branch here looks likely:
$ irb
>> class C
>> end
=> nil
>> C.const_get('Data')
=> Data
This leads me to suspect you have an attribute or similar floating around named :data or "data", even though you don't mention one above. Do you? Particularly, it seems we have a JSON response with a sub-hash whose key is "data".
Here's a script that can trigger the error for crafted input, but not from the response you posted:
$ cat ./activeresource-oddity.rb
#!/usr/bin/env ruby
require 'rubygems'
gem 'activeresource', '3.0.10'
require 'active_resource'
class User < ActiveResource::Base
self.site = "http://localhost:3000/"
self.element_name = "users"
self.format = :json
end
USER = User.new :name => "Test", :email => "test.user#domain.com"
def simulate_load_attributes_from_response(response_body)
puts "Loading #{response_body}.."
USER.load User.format.decode(response_body)
end
OK = '{"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}'
BORKED = '{"data":{"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}}'
simulate_load_attributes_from_response OK
simulate_load_attributes_from_response BORKED
produces..
$ ./activeresource-oddity.rb
Loading {"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}..
Loading {"data":{"email":"test#gmail.com","name":"test curl","admin":false,"uuid":"afb8c98b-562a-4603-bbe4-f8f0816cef0d","creation_limit":5}}..
/opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `new': allocator undefined for Data (TypeError)
from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1233:in `load'
from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `each'
from /opt/local/lib/ruby/gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb:1219:in `load'
from ./activeresource-oddity.rb:17:in `simulate_load_attributes_from_response'
from ./activeresource-oddity.rb:24
If I were you, I would open /Library/Ruby/Gems/1.8/gems/activeresource-3.0.10/lib/active_resource/base.rb, find load_attributes_from_response on line 1320 and temporarily change
load(self.class.format.decode(response.body))
to
load(self.class.format.decode(response.body).tap { |decoded| puts "Decoded: #{decoded.inspect}" })
..and reproduce the error again to see what is really coming out of your json decoder.
I just ran into the same error in the latest version of ActiveResource, and I found a solution that does not require monkey-patching the lib: create a Data class in the same namespace as the ActiveResource object. E.g.:
class User < ActiveResource::Base
self.site = "http://localhost:3000/"
self.element_name = "users"
self.format = :json
class Data < ActiveResource::Base; end
end
Fundamentally, the problem has to do with the way ActiveResource chooses the classes for the objects it instantiates from your API response. It will make an instance of something for every hash in your response. For example, it'll want to create User, Data and Pet objects for the following JSON:
{
"name": "Bob",
"email": "bob#example.com",
"data": {"favorite_color": "purple"},
"pets": [{"name": "Puffball", "type": "cat"}]
}
The class lookup mechanism can be found here. Basically, it checks the resource (User) and its ancestors for a constant matching the name of the sub-resource it wants to instantiate (i.e. Data here). The exception is caused by the fact that this lookup finds the top-level Data constant from the Stdlib; you can therefore avoid it by providing a more specific constant in the resource's namespace (User::Data). Making this class inherit from ActiveResource::Base replicates the behaviour you'd get if the constant was not found at all (see here).
Thanks to phs for his analysis - it got me pointed in the right direction.
I had no choice but to hack into ActiveResource to fix this problem because an external service over which I have no control had published an API where all attributes of the response were tucked away inside a top-level :data attribute.
Here's the hack I ended up putting in config/initializers/active_resource.rb to get this working for me using active resource 3.2.8:
class ActiveResource::Base
def load(attributes, remove_root = false)
raise ArgumentError, "expected an attributes Hash, got #{attributes.inspect}" unless attributes.is_a?(Hash)
#prefix_options, attributes = split_options(attributes)
if attributes.keys.size == 1
remove_root = self.class.element_name == attributes.keys.first.to_s
end
# THIS IS THE PATCH
attributes = ActiveResource::Formats.remove_root(attributes) if remove_root
if data = attributes.delete(:data)
attributes.merge!(data)
end
# END PATCH
attributes.each do |key, value|
#attributes[key.to_s] =
case value
when Array
resource = nil
value.map do |attrs|
if attrs.is_a?(Hash)
resource ||= find_or_create_resource_for_collection(key)
resource.new(attrs)
else
attrs.duplicable? ? attrs.dup : attrs
end
end
when Hash
resource = find_or_create_resource_for(key)
resource.new(value)
else
value.duplicable? ? value.dup : value
end
end
self
end
class << self
def find_every(options)
begin
case from = options[:from]
when Symbol
instantiate_collection(get(from, options[:params]))
when String
path = "#{from}#{query_string(options[:params])}"
instantiate_collection(format.decode(connection.get(path, headers).body) || [])
else
prefix_options, query_options = split_options(options[:params])
path = collection_path(prefix_options, query_options)
# THIS IS THE PATCH
body = (format.decode(connection.get(path, headers).body) || [])
body = body['data'] if body['data']
instantiate_collection( body, prefix_options )
# END PATCH
end
rescue ActiveResource::ResourceNotFound
# Swallowing ResourceNotFound exceptions and return nil - as per
# ActiveRecord.
nil
end
end
end
end
I solved this using a monkey-patch approach, that changes "data" to "xdata" before running find_or_create_resource_for (the offending method). This way when the find_or_create_resource_for method runs it won't search for the Data class (which would crash). It searches for the Xdata class instead, which hopefully doesn't exist, and will be created dynamically by the method. This will be a a proper class subclassed from ActiveResource.
Just add a file containig this inside config/initializers
module ActiveResource
class Base
alias_method :_find_or_create_resource_for, :find_or_create_resource_for
def find_or_create_resource_for(name)
name = "xdata" if name.to_s.downcase == "data"
_find_or_create_resource_for(name)
end
end
end

Resources