Many to many persistence ruby mongoid - ruby

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)

Related

Ruby reads .50 as .5

I have an Item class, and I initialized five variables. I am trying to match the output in my terminal to the value of the expected_summary. I am calling Item.summary in the following code:
class Item
attr_reader :name, :description, :manufacturer, :price, :summary
def initialize (name, manufacturer, price, description=nil, summary=nil)
#name = name
#manufacturer = manufacturer
#price = price
#description = description
if description
#summary = "Name: #{name}
Description: #{description}
Manufacturer: #{manufacturer}
Price: $#{price}"
else
#summary = "Name: #{name}
Manufacturer: #{manufacturer}
Price: $#{price}"
end
end
end
#expected_summary = %q(Name: Spy Notebook
#Manufacturer: Spys-R-Us
#Price: $10.50)
item = Item.new("Spy Notebook", "Spys-R-Us", 10.50)
puts item.summary
When I pass a number 10.50 as the price argument, it returns as 10.5. I cannot figure out why. Why does Ruby read 10.50 as 10.5? Is there a way to correct this?
The answer is in the string format operator which allows you to coerce the float into a string with 2 decimal places. This method will also work to do the rounding of 3 digit numbers, I didn't try it any further, but I'm fairly certain it will work. Here is your original code modified to showcase exactly how it would work.
class Item
attr_reader :name, :description, :manufacturer, :price, :summary
def initialize (name, manufacturer, price, description=nil, summary=nil)
#name = name
#manufacturer = manufacturer
#price = "%.2f" % price
#description = description
if description
#summary = "Name: #{name}
Description: #{description}
Manufacturer: #{manufacturer}
Price: $#{#price}"
else
#summary = "Name: #{name}
Manufacturer: #{manufacturer}
Price: $#{#price}"
end
end
def price
#price
end
end
EDIT: I didn't see #tadman's comment until after posting this, he beat me to the answer.
Maybe you can define your custom method to format the number as string with two decimals:
def two_decimals(number)
number = Float(number)
price_split = number.to_s.split('.')
price_split[1] = price_split.last + '0'*(2-price_split.last.size) if price_split.last.size < 2
price_split.join('.')
end
two_decimals(10.25) #=> "10.25"
two_decimals(10.2) #=> "10.20"
two_decimals(10) #=> "10.00"
Or something better...
The usual way of formatting numbers is:
'%.02f' % number
Where that is the printf-style notation for describing how you want something formatted. This is inherited from C and shows up in a lot of other languages:
'%f' % number # A floating-point number with default precision
'%.02f' % number # A floating-point number rounded to 2 places
Within Rails you also have helper methods like number_with_precision which can handle localization cases where the decimal separator is not a dot:
number_with_precision(number, precision: 2, locale: :fr)
# => 1,50

Custom RSpec formatter to display passed test and result of except

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: )

How to stub/mock multiple options depending on user input with Rspec 3.4

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

How to dump a class into another class to create a save state

I have a small class which is for a character and we can assign to it from outside the class.
I need to know how I can dump all the information in that class into another that can be used to create a YAML file.
require "yaml"
module Save
filename = "data.yaml"
character = []
sex = []
race = []
stats = [Str=[], Dex=[], Con=[], Int=[], Wis=[], Cha=[]]
inventory = []
saving_throws = [fortitude=[], reflex=[], will=[]]
#Armor Class, Flat footed Armor Class, and Touch armor Class
armor_class = [ac=[], fac=[], tac=[]]
armor_worn = [head=[], eyes=[], neck=[], shoulders=[], body=[], torso=[], arms_wrists=[], hands=[], ring1=[], ring2=[], waist=[], feet=[]]
money = []
god = []
speciality_school = [] #wizard
companion = [] #also used for familirs and psicrystals
skills = []
class_race_traits = []
feats = []
languages = []
program_data = {
character: character,
sex: sex,
race: race,
stats: stats,
inventory: inventory,
saving_throws: saving_throws,
armor_class: armor_class,
armor_worn: armor_worn,
mony: money,
god: god,
speciality_school: speciality_school,
companion: companion,
skills: skills,
class_race_traits: class_race_traits,
feats: feats,
languages: languages
}
File.write filename, YAML.dump(program_data)
end
This is the code I want to use to obtain the user content from the player:
class Character
attr_reader :name, :race, :description
def initialize (name, race, description)
#name = name
#race = race
#description = description
end
end
def prompt
print "Enter Command >"
end
puts "What is your name?"
prompt; name = gets.chomp.downcase
puts "What is your race?"
prompt; race = gets.chomp.downcase
puts "What do you look like?"
prompt; desc = gets.chomp.downcase
player_one = Character.new(name, race, desc)
puts player_one
I'm stuck on how to load it back and refill the character content to make it continue where the player left off.
Meditate on this bit of fictional code:
require 'yaml'
SAVED_STATE_FILE = 'saved_state.yaml'
class User
def initialize(name=nil, address=nil)
#name = name
#address = address
end
def to_h
{
'name' => #name,
'address' => #address
}
end
def save
File.write(SAVED_STATE_FILE, self.to_h.to_yaml)
end
def reload
state = YAML.load_file(SAVED_STATE_FILE)
#name, #address = state.values
end
end
We can create a new user with some properties:
user = User.new('Popeye', '123 E. Main St.')
# => #<User:0x007fe361097058 #name="Popeye", #address="123 E. Main St.">
To write that information to a file you should probably start by using YAML, which results in a very readable output and is readable by many different languages, making the data file reusable. A hash results in a very readable output:
user.to_h
# => {"name"=>"Popeye", "address"=>"123 E. Main St."}
user.to_h.to_yaml
# => "---\nname: Popeye\naddress: 123 E. Main St.\n"
Save the YAML serialized hash:
user.save
Create a new version of the user without any state:
user = User.new
# => #<User:0x007fe361094a88 #name=nil, #address=nil>
Load the saved information from the file back into the blank object:
user.reload
Which results in:
user
# => #<User:0x007fe361094a88 #name="Popeye", #address="123 E. Main St.">
That will give you enough to work from.
Your current code isn't going to work well though; I'd recommend reading some tutorials about Ruby classes and modules, as a Module isn't what you want, at least for your initial code.

Refactor the Ruby CLI program

I'm new to programming in Ruby.
How do I make the output show Revenue and Profit or Loss?
How can I refactor the following code to look neater? I know it's wrong but I have no idea how to take my if profit out of the initialize method.
class Theater
attr_accessor :ticket_price, :number_of_attendees, :revenue, :cost
def initialize
puts "What is your selling price of the ticket?"
#ticket_price = gets.chomp.to_i
puts "How many audience are there?"
#number_of_attendees = gets.chomp.to_i
#revenue = (#number_of_attendees * #ticket_price)
#cost = (#number_of_attendees * 3) + 180
#profit = (#revenue - #cost)
if #profit > 0
puts "Profit made: $#{#profit}"
else
puts "Loss incurred: $#{#profit.abs}"
end
end
end
theater = Theater.new
# theater.profit
# puts "Revenue for the theater is RM#{theater.revenue}."
# I hope to put my Profit/Loss here
#
# puts theater.revenue
Thanks guys.
Do not initialize the object with input from the user, make your object accept the needed values. Make a method to read the needed input and return you new Theater. Last of all put the if in separate method like #report_profit.
Remember constructors are for setting up the initial state of the object, making sure it is in a valid state. The constructor should not have side effects(in your case system input/output). This is something to be aware for all programming languages, not just ruby.
Try this:
class Theatre
COST = { running: 3, fixed: 180 }
attr_accessor :number_of_audience, :ticket_price
def revenue
#number_of_audience * #ticket_price
end
def total_cost
COST[:fixed] + (#number_of_audience * COST[:running])
end
def net
revenue - total_cost
end
def profit?
net > 0
end
end
class TheatreCLI
def initialize
#theatre = Theatre.new
end
def seek_number_of_attendes
print 'Number of audience: '
#theatre.number_of_audience = gets.chomp.to_i
end
def seek_ticket_price
print 'Ticket price: '
#theatre.ticket_price = gets.chomp.to_i
end
def print_revenue
puts "Revenue for the theatre is RM #{#theatre.revenue}."
end
def print_profit
message_prefix = #theatre.profit? ? 'Profit made' : 'Loss incurred'
puts "#{message_prefix} #{#theatre.net.abs}."
end
def self.run
TheatreCLI.new.instance_eval do
seek_ticket_price
seek_number_of_attendes
print_revenue
print_profit
end
end
end
TheatreCLI.run
Notes:
Never use your constructor (initialize method) for anything other than initial setup.
Try to keep all methods under 5 lines.
Always try to keep each class handle a single responsibility; for instance, printing and formatting output is not something the Theatre class needs to care.
Try extracting all hard coded values; eg see the COST hash.
Use apt variables consistent to the domain. Eg: net instead of profit makes the intent clear.

Resources