I am new to ruby and I am trying to create a hangman game, to do so I need to create a new game each time the user click on a button.
First step is to create the same object each time the methode create will be called (it will be different because of the .sample.
I am trying to create an object with the initialize method in my model.rbfile.
Here is my code :
class Game < ApplicationRecord
has_many :guesses
def initialize(*)
super
#word_to_guess = word_to_guess
#health_bar = 5
#game_status = game_status
end
def word_to_guess
words = [
"spokesperson", "firefighter", "headquarters", "confession", "difficulty", "attachment", "mechanical",
"accumulation", "hypothesis", "systematic", "attraction", "distribute", "dependence", "environment",
"jurisdiction", "demonstrator", "constitution", "constraint", "consumption", "presidency", "incredible",
"miscarriage", "foundation", "photography", "constituency", "experienced", "background", "obligation",
"diplomatic", "discrimination", "entertainment", "grandmother", "girlfriend", "conversation", "convulsion",
"constellation", "leadership", "insistence", "projection", "transparent", "researcher", "reasonable","continental",
"excavation", "opposition", "interactive", "pedestrian", "announcement", "charismatic", "strikebreaker",
"resolution", "professional", "commemorate", "disability", "collection", "cooperation", "embarrassment",
"contradiction", "unpleasant", "retirement", "conscience", "satisfaction", "acquaintance", "expression",
"difference", "unfortunate", "accountant", "information", "fastidious", "conglomerate", "shareholder",
"accessible", "advertising", "battlefield", "laboratory", "manufacturer", "acquisition", "operational",
"expenditure", "fashionable", "allocation", "complication", "censorship", "population", "withdrawal",
"sensitivity", "exaggerate", "transmission", "philosophy", "memorandum", "superintendent", "responsibility",
"extraterrestrial", "hypothesize", "ghostwriter", "representative", "rehabilitation", "disappointment",
"understanding", "supplementary", "preoccupation"
]
words.sample
end
def game_status
game_status = ["Game not started yet", "In Game", "You win", "You loose"]
game_status[0]
end
end
The problem is that when I do a Game.new in console, it returns :
[1] pry(main)> Game.new
=> #<Game:0x00007fa79b613290 id: nil, word_to_guess: nil, health_bar: nil, created_at: nil, updated_at: nil, guesse_id: nil, game_status: nil>
So the instance is not created and I don't know why.
As you see, instance was created
But I don't recommend to use instance variables such way. Instance variables are stored in memory
Also it's not good idea to override "getters" for rails attributes such way
It's better to store such data in database as rails attributes
You can do like this
after_initialize do
self.word_to_guess ||= generate_word_to_guess
self.health_bar ||= 5
self.game_status ||= "Game not started yet"
end
Also it looks that for health_bar and game_status is better to define default values in database
You can read more about callbacks:
https://guides.rubyonrails.org/active_record_callbacks.html
Related
Is there a way to create a custom formatter where the passed test details with a list of except is showed?
A bit of a background for this question: we are trying to migrate to RSpec for our hardware integration and system test. The results should be pushed to CouchDB. What I am trying to achieve is a reporter that could generate a similar YAML output like the following snippet:
{
"_id": "0006b6f0-c1bd-0135-1a98-455c37fe87f1",
"_rev": "1-9c9786b4b4681ee8493f182d4fc56ef9",
"sha1_repo": "68bb327b540097c10683830f0d82acbe54a47f03",
"steps": [
{
"result": "pass",
"description": "Time for Routing expect OK: 126 micro seconds (DLC and Data also OK)"
},
{
"result": "pass",
"description": "Time for Routing expect OK: 146 micro seconds (DLC and Data also OK)"
},
{
"result": "pass",
"description": "Time for Routing expect OK: 162 micro seconds (DLC and Data also OK)"
}
],
"time_start": "1513119108000",
"time_end": "1513119108000",
"result": "pass",
"testcase_title": "Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03",
"testcase_id": "TC_1zu1_BAF_Komfort_TSG_HBFS_03_to_Komfort2_TSG_HBFS_03",
"hierarchy": [
"Hardware Integration Test",
"1 - Routing",
"1.1 Normal Routing",
"1zu1_BAF_TestCases",
"CAN_to_CAN"
]
}
With failed test there is no problem to achieve this, but we need also the results from passed test in order to be able to create long term statistics.
I can override the passed event of RSPec but the example object delivers only the description and no more info.
class EliteReporter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def example_passed(passed)
#output.printf "pass \n #{passed.example.description}"
end
end
Thank you in advance for any help.
Finally with the help of my colleague and thanks of the Tip from RSPec Emailing list I could do this.
I have created a Recorder class that collects the test results, than override the Expect methode. This way in the custom formatter I can collect all the passed results:
class ExpectWrapper
def initialize(_expect, _recorder, _description)
#expect = _expect
#recorder = _recorder
#description = _description
end
def to(matcher, failure_message=nil)
begin
expect_ret = #expect.to(matcher, failure_message) # test
# for tests that aggregate failures
if expect_ret.instance_of?(TrueClass)
#recorder.record(matcher.actual, matcher.description, #description)
else
#recorder.record_error(matcher.actual, matcher.description, failure_message, #description)
end
expect_ret
rescue RSpec::Expectations::ExpectationNotMetError => e
# for test that do not aggregate failures
#recorder.record_error(matcher.actual, matcher.description, failure_message, #description)
raise e
end
end
end
class Recorder
def self.start
##data = []
return Recorder.new
end
def record(expect, data, description)
##data << { :pass => true, :expect => expect, :value => data, :description => description }
self
end
def record_error(expect, data, failure_message, description)
##data << { :pass => false, :expect => expect, :value => data, :message => failure_message, :description => description }
self
end
def self.data
##data
end
def expect(object, value, description = "")
return ExpectWrapper.new(object.expect(value), self, description)
end
end
The custom formatter would look the following, is just an example, the data could be than put to JSON and pushed to Couch:
class EliteVerboseFormatter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def initialize(output)
#output = output
end
def example_passed(notification)
#output.puts( format_output(notification.example, Recorder) )
end
def get_test_name( group, description)
"#{group.example.example_group}/#{description}".gsub('RSpec::ExampleGroups::','')
end
def format_output( example, recorder )
test_case = get_test_name( example.example_group, example.description)
str = "**********TEST: #{test_case} ************\n"
recorder.data.each do |d|
str += sprintf("%s: ---> expected '%-10s' to '%-20s' DESC: %s \n", d[:pass] ? 'PASS' : 'FAIL', d[:expect], d[:value], d[:description])
end
str
end
def example_failed(notification)
#output.puts(format_output( notification.example, Recorder))
exception = notification.exception
message_lines = notification.fully_formatted_lines(nil, RSpec::Core::Notifications::NullColorizer)
exception_details = if exception
{
# drop 2 removes the description (regardless of newlines) and leading blank line
:message => message_lines.drop(2).join("\n"),
:backtrace => notification.formatted_backtrace.join("\n"),
}
end
#output.puts RSpec::Core::Formatters::ConsoleCodes.wrap(exception_details[:message], :failure)
end
end
I think you can read the Module: RSpec::Core::Formatters
you might find something helpful.
P.S. I have used Cucumber for many times, and I once wanted to custom cucumber formatter to display every step's details no matter it failed or passed. I finally got the solution by reading cucumber core documents.so I think maybe rspec core document can help you to find the solution.
I find that I cannot put the code in comment, so I put it here.
edit your code as below:
class EliteReporter
RSpec::Core::Formatters.register self, :example_started, :example_passed, :example_failed, :example_finished
def example_passed(example)
example_failed(example)
end
end
I hope it can be helpful: )
My problem:
I'm trying to stub a class method that returns an instance of that class, but I'm getting the following error for the test entitled "creates an instance with CSV data":
Failures:
1) QuestionData.load_questions creates an instance with CSV data
Failure/Error: expect(question_data_class).to receive(:new).with(data).and_return(question_data_instance)
(QuestionData (class)).new([{:time_limit=>10, :text=>"Who was the legendary Benedictine monk who invented champagne?", :correct_...the world?", :correct_answer=>"Lake Superior", :option_2=>"Lake Victoria", :option_3=>"Lake Huron"}])
expected: 1 time with arguments: ([{:time_limit=>10, :text=>"Who was the legendary Benedictine monk who invented champagne?", :correct_...the world?", :correct_answer=>"Lake Superior", :option_2=>"Lake Victoria", :option_3=>"Lake Huron"}])
received: 0 times
The context:
The code (shown below) works - QuestionData.load_questions loads data from a CSV file and calls QuestionData.new with the data as an argument. My test for the .load_questions method however, is giving the above error. When it's called, the double of the QuestionData class isn't receiving the stub of .new with the data double.
I've tried researching how to test stubs that return another stub or an instance, but can't seem to find a relevant answer.
I'd really appreciate any help or advice, thanks very much in advance!
The code:
require "csv"
class QuestionData
attr_reader :questions
def initialize(questions)
#questions = questions
end
def self.load_questions(file = './app/lib/question_list.csv', questions = [])
self.parse_csv(file, questions)
self.new(questions)
end
def self.parse_csv(file, questions)
CSV.foreach(file) do |row|
time_limit, text, correct_answer, option_2, option_3 = row[0],
row[1], row[2], row[3], row[4]
questions << { time_limit: time_limit, text: text,
correct_answer: correct_answer, option_2: option_2, option_3: option_3
}
end
end
end
The test file:
require './app/models/question_data'
describe QuestionData do
subject(:question_data_instance) { described_class.new(data) }
let(:question_data_class) { described_class }
let(:CSV) { double(:CSV, foreach: nil) }
let(:questions) { [] }
let(:file) { double(:file) }
let(:data) do
[{
time_limit: 10,
text: "Who was the legendary Benedictine monk who invented champagne?",
correct_answer: "Dom Perignon",
option_2: "Ansgar",
option_3: "Willibrord"
},
{
time_limit: 12,
text: "Name the largest freshwater lake in the world?",
correct_answer: "Lake Superior",
option_2: "Lake Victoria",
option_3: "Lake Huron"
}]
end
describe '#questions' do
it "has an array of question data from CSV" do
expect(question_data_instance.questions).to eq(data)
end
end
describe '.parse_csv' do
it "parses CSV data into hash data" do
expect(CSV).to receive(:foreach).with(file)
question_data_class.parse_csv(file, questions)
end
end
describe '.load_questions' do
it "calls '.parse_csv' method" do
expect(question_data_class).to receive(:parse_csv).with(file, questions)
question_data_class.load_questions(file, questions)
end
it "creates an instance with CSV data" do
allow(question_data_class).to receive(:load_questions).with(file, questions).and_return(question_data_instance)
allow(question_data_class).to receive(:new).with(data).and_return(question_data_instance)
expect(question_data_class).to receive(:new).with(data).and_return(question_data_instance)
question_data_class.load_questions(file, questions)
end
end
describe '.new' do
it "creates a new instance with CSV data" do
expect(question_data_class).to receive(:new).with(data).and_return(question_data_instance)
question_data_class.new(data)
end
end
end
The thing is that you are stubbing the call on:
allow(question_data_class).to receive(:load_questions).with(file)
If you still want that the call executes you need to add a:
and_call_original
Therefore the original method will be executed and your code will call the new method on the original block.
But the thing is that you don't need to stub the class you just need to change the stubs because you are calling the method on a double, and it will try to execute it in a class, so you might need to change your second test to:
describe '.load_questions' do
it "creates an instance containing CSV data" do
expect(described_class).to receive(:new).with(data).and_return(question_data_instance)
described_class.load_questions(file)
end
end
I am completely new to Rspec, and it's my first time trying to test outside of the rails framework. I am simply trying to understand how I can possibly mock behavior of my app when the implementation is pretty complex.
I want to be able to mimic the behavior of calling customize_gender inputting a choice and checking that when 1 is entered the result is 'Male', when 2 is entered the result is 'Female', etc.
I also want to be able to check if the instance variable of #gender was correctly set, which is why I added the attr_reader :gender in the first place. I have been trying a few things, but I guess I do not understand how mocks in general work to be able to find a solution. I have looked at similar questions but they do not seem to work for my scenario. Any insight is greatly appreciated!
Main file (person.rb)
class Person
attr_reader :gender
GENDER = { male: 'Male', female: 'Female', other: 'Other'}
def initialize
puts customize_gender
end
def customize_gender
display_hash_option GENDER, 'What is your gender? '
choice = gets.chomp.to_i
#gender =
case choice
when 1
GENDER[:male]
when 2
GENDER[:female]
when 3
print 'Enter your preferred gender: '
gets.chomp.downcase
else
puts 'Error: Person -> customize_gender()'
end
end
private
def display_hash_option(hash, saying = '')
print saying
hash.each_with_index { |(key, _value), index| print "#{index.next}) #{key} " }
end
end
Rspec File (spec/person_spec.rb)
require_relative "../person"
describe Person do
let(:person) { Person.new }
allow(Person).to receive(:gets).and_return(1,2,3)
person.customize_gender
expect(person.gender).to eq 'Male'
# allow(person).to receive(:customize_gender).and_return('Male')
# expect(Person).to receive(:puts).with('What is your gender?')
# allow(Person).to receive(:gets) { 1 }
# expect(person.gender).to eq 'Male'
end
Here's how you could do it, the only thing mocked here is that gets is set to '1' (remember it's a string in this case as gets input is always a string)
RSpec.describe Person do
subject { Person.new }
it 'returns male as gender when male is chosen' do
allow(subject).to receive(:gets).and_return('1')
subject.customize_gender
expect(subject.gender).to eq('Male')
end
end
For when 3 you could use the following.
RSpec.describe Person do
subject { Person.new }
it 'returns other as gender when other has been entered' do
allow(subject).to receive(:gets).and_return('3', 'other')
subject.customize_gender
expect(subject.gender).to eq('other')
end
end
I have two arrays which contain objects of assets, now I want to subtract to get only objects from the first array which the second array doesn't have. I should use "-" right?
Here is my object
class Asset
attr_accessor :id, :url, :text, :img_url, :type, :created_at
def initialize(url, text, type, img_url, created_at)
#id, #url, #text, #img_url, #type, #created_at = "", url, text, img_url, type, created_at
end
def eql?(another_asset)
self_domain = UrlUtils::get_domain(self.url)
another_asset_domain = UrlUtils::get_domain(another_asset.url)
if self_domain == 'youtube' && another_asset_domain == 'youtube'
self_youtube_id = UrlUtils::get_parameter(self.url, "v")
another_asset_youtube_id = UrlUtils::get_parameter(another_asset.url, "v")
return self_youtube_id.eql?(another_asset_youtube_id)
end
return self.url.eql?(another_asset.url)
end
def hash
#created_at.hash + 32 * #url.hash
end
end
The idea is one asset can contain url from youtube which every url might be different but it's the same video, so I have to compare each url with parameter "v" (youtube_id).
And this is my test which is wrong at the moment, because it doesn't do the subtraction correctly.
it "should substract duplicated youtube from mixed assets" do
mixed_assets = Array.new
all_assets = Array.new
google = Asset.new("http://www.google.com", "", "", "", Time.now)
youtube = Asset.new("http://www.youtube.com?v=1", "", "", "", Time.now)
mixed_assets.push(google)
mixed_assets.push(youtube)
another_youtube = Asset.new("http://www.youtube.com?v=1&a=1", "", "", "", Time.now)
all_assets.push(another_youtube)
mixed_assets = mixed_assets - all_assets
mixed_assets.length.should eql 1
mixed_assets[0].url.should eql "http://www.google.com"
end
I'm very new to ruby and I did some research that I should implement "hash" method as well, but I couldn't find any example how to do that.
Thanks.
Array subtraction works via hashes, so you're correct. I couldn't test since I don't know what UrlUtils is, but something similar to the following is likely what you need added to the Asset class:
def hash
domain = UrlUtils::get_domain(self.url)
v = domain == 'youtube' ? UrlUtils::get_parameter(self.url, "v") : ''
domain.hash ^ v.hash
end
You might also need an eql? method. There's a bunch of additional information in this post that you probably will want to look over; it covers this, as well as a bunch of related topics.
Hey this is my first post so tell me if I am not giving you enough information
So, I am trying out ruby1.9.2 and using mongoid2.2.0 with mongodb1.8.2 and I am having trouble persisting database documents in the code with a many to many relationship.
require 'mongoid'
require 'mongo'
Mongoid.load!("../Configurations/mongoid.yml")
Mongoid.configure do |config|
config.master = Mongo::Connection.new.db("godfather")
end
connection = Mongo::Connection.new
connection.drop_database("godfather")
database = connection.db("godfather")
class Project
include Mongoid::Document
field :name, type: String
key :name
field :numPeople, type: Integer
has_and_belongs_to_many :people
end
class Person
include Mongoid::Document
field :name, type: String
key :name
field :numProjects, type: Integer, default: 0
has_and_belongs_to_many :projects
def add_project(project_name)
project = Project.create(name: project_name)
self.numProjects = self.numProjects + 1
self.projects << project
self.save
end
def has_project?(project_name)
self.projects.each do |project|
if project.name.upcase == project_name.upcase
return true
end
end
return false
end
end
database = Project.create(name: "Database")
alice = Person.create(name: "Alice")
alice.add_project("Database")
puts "has project? #{alice.has_project?("Database")}"#outputs true
puts "Alice has #{alice.numProjects} projects"#outputs 1
puts "Alice really has #{alice.projects.size} projects"#outputs 1
editor = Project.create(name: "Editor")
john = Person.create(name: "John")
john.has_project?("Editor")
john.add_project("Editor")
puts "has project? #{john.has_project?("Editor")}"#outputs false
puts "John has #{john.numProjects} projects"#outputs 1
puts "John really has #{john.projects.size} projects"#outputs 0
operatingSystem = Project.create(name: "OperatingSystem")
drinking = Project.create(name: "Drinking")
henry = Person.create(name: "Henry")
henry.add_project("OperatingSystem")
henry.has_project?("OperatingSystem")
henry.add_project("drinking")
henry.add_project("Editor")
puts "Henry has #{henry.numProjects} projects"#outputs 3
puts "Henry really has #{henry.projects.size} projects"# outputs 3
So the problem is John outputs that he has 0 projects when I have added one.
The weird thing is when I use the mongo terminal to inspect the database it shows john having the one project "Editor" which the code did not find in john.projects.size.
Ideally the order in which i call add_project and has_project? should not affect the code and matter.
thanks
Can't see anything wrong with your code but I got the same results as you running it here.
Looks like it could be a known issue - https://github.com/mongoid/mongoid/issues/1198
try to set autosave to true
see: http://mongoid.org/docs/upgrading.html (search on the page for autosave)