Firsty I would say that I'm learning Ruby and TDD program, so please be forgiving for me. Acctualy I have two questions releted with each other but firsty please take a look on this code. Its part of my unit test for class Order:
context 'with products' do
let(:result) { instance_double('Money', value: 20, currency: 'EUR') }
let(:xxx) { instance_double('Money', value: 10, currency: 'EUR') }
let(:money2) { instance_double('Money', value: 10, currency: 'EUR',:+ => xxx , :to_s => '10.00 EUR' ) }
let(:money) { instance_double('Money', value: 10, currency: 'EUR', :+ => money2, :to_s => '10.00 EUR') }
let(:product1) { instance_double('Product', price: money2) }
let(:product2) { instance_double('Product', price: money) }
let(:products) { [product1, product2] }
it 'returns sum of product prices' do
#Real objects
product1 = Product.new
product1.price = Money.new('1.23', 'EUR')
product1.name = product1
product2 = Product.new
product2.price = Money.new('1.23', 'EUR')
product2.name = product2
products1 = [product1,product2]
puts products1.map!(&:price)
#Fake
puts "fakeproducts map"
fakeproducts = products.map!(&:price)
puts fakeproducts
puts "Sum of fakeproducts"
puts Money.sum(fakeproducts)
puts Money.sum(fakeproducts).to_s
puts "methods of sum fakeproducts"
puts Money.sum(fakeproducts).methods
expect(Order.new(full_name, date, products).total_amount).to eql result
end
end
Problem is that my real objects works,but mocked objects doens't . Error which I got:
Failure/Error: expect(Order.new(full_name, date, products).total_amount).to eql result
Double "Money (instance)" received unexpected message :price with (no args)
Total_amount function:
def total_amount
return 0 if products.empty?
asd = products.map!(&:price)
Money.sum(asd)
end
and Money.sum looks like this:
def self.sum(moneys)
moneys.group_by(&:currency).values.map(&:sum)
end
I suspect that, when I do some operations on my mocked object it lose its properties.
The questions are:
Is it normal that this things happen?
What is Solutions for this problem? Should I mock result of my function?
Your use of map! in total_amount is clobbering the products associated with the order, replacing them with their price, so that the next time you call price on the order's products (e.g. as in a subsequent call to total_amount), you are sending price to one of your money doubles.
You can avoid this particular symptom by using map instead of map! inside of total_amount.
As a related side, it's much easier to provide help with these kinds of questions if you provide the stack trace with your error and identify the corresponding source lines in your code.
Related
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
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've struggled with this problem for a while, and I'm finally going to ask here for help.
Take a very straightforward hash that represents some event:
{
:eventkey=>"someeventkey",
:web_id=>"77d5f434-5a40-4582-88e8-9667b7774c7d",
:apikey=>"eaf3b6e1-b020-41b6-b67f-98f1cc0a9590",
:details=> {
:phone=>"1-936-774-6886",
:email=>"dasia_schuster#wisokytrantow.com",
:pageUrl=>"http://ortiz.info/joe"
}
}
My goal is to create a 'master record' for the entire hash, with the fields in the record being all the keys that do not contain values that are also hashes. When I run into a value that is a hash (in this case 'details'), I need to create a separate record for each k/v pair in that hash bearing the same record id as the parent master record.
I'm not getting the recursion right somehow. Ideally I would get back a single primary record:
{
:recordid=>"some-generated-record-id",
:web_id=>"77d5f434-5a40-4582-88e8-9667b7774c7d",
:apikey=>"eaf3b6e1-b020-41b6-b67f-98f1cc0a9590",
:details=>nil
}
And a distinct entry for each key in the nested hash:
{
:recordid=>"some-generated-detail-record-id",
:parentid=>"the-parent-id-from-the-master-record",
:phone=>"1-936-774-6886"
}
{
:recordid=>"another-generated-detail-record-id",
:parentid=>"the-same-parent-id-from-the-master-record",
:email=>"dasia_schuster#wisokytrantow.com"
}
And so on. I'm trying to get this set of records back as an array of hashes.
I've gotten as far as being able to generate the master record, as well as a detail record, but the detail record contains all the keys in the detail.
def eventToBreakout(eventhash,sequenceid = -1, parentrecordid = nil, records = [])
recordid = SecureRandom.uuid
sequenceid += 1
recordstruc = {:record_id => recordid, :parent_record_id => parentrecordid, :record_processed_ts => Time.now, :sequence_id => sequenceid}
eventhash.each_pair do |k,v|
if recurse?(v)
eventToBreakout(v,sequenceid,recordid,records)
else
if !recordstruc.keys.include?(k)
recordstruc[k]=v
end
end
end
records << recordstruc
records
end
I've included my code and here is the output I'm currently getting from it.
[{:record_id=>"ed98be89-4c1f-496e-beb4-ede5f38dd549",
:parent_record_id=>"fa77299b-95b0-429d-ad8a-f5d365f2f357",
:record_processed_ts=>2016-04-25 16:46:10 -0500,
:sequence_id=>1,
:phone=>"1-756-608-8114",
:email=>"hipolito_wilderman#morar.co",
:pageUrl=>"http://haag.net/alexie.marvin"},
{:record_id=>"fa77299b-95b0-429d-ad8a-f5d365f2f357",
:parent_record_id=>nil,
:record_processed_ts=>2016-04-25 16:46:10 -0500,
:sequence_id=>0,
:eventts=>2016-04-25 22:10:32 -0500,
:web_id=>"a61c57ae-3a01-4994-8803-8d8292df3338",
:apikey=>"9adbc7a4-03ff-4fcc-ac81-ae8d0ee01ef0"}]
Maybe you want something along these lines?
input = { id: 'parent', value: 'parent value', child: { child_value: 1}}
record = {}
input.each do |k,v|
if v.is_a? Hash
v[:parent_id] = input[:id]
(record[:children] ||= []) << v
else
record[k] = v
end
end
puts record
# {:id=>"parent", :value=>"parent value", :children=>[{:child_value=>1, :parent_id=>"parent"}]}
By the way this is a good example to get started with "spec" or "test" frameworks like minitest or rspec (both can be used for both). You have defined input and expected output already and "just" need to code until all test/spec-runs are green.
I have a problem to approach and not sure what the most appropriate method will be to make this work. Here the background to begin:
There are two models I am working with Procedures and Appointments. The Appointments model belongs_to the Procedures model and Procedures model has_many Appointments.
Now on the procedures model there are two key points to focus on, rather, two key columns.
attr_accessible :visits, :occurence
visits is the specific number of times to schedule the Appointment(s).
occurence is the frequency of the visits. An example would be visits: "5", occurence: "weekly"
So when I am submitting my form I would like to write a method that looks at both visits: "x" and occurence: ["weekly", "biweekly", "monthly"] to then create a if or a switch -- php does switch still looking into ruby version -- but I suspect there is an elegant way to write this up.
My current create method looks like this:
def create
#appointment = Appointment.new(params[:appointment])
set_variables
if #appointment.save
flash[:success] = "Appointment scheduled!"
redirect_to patient_path(#current_patient)
else
redirect_to patient_path(#current_patient)
flash[:error] = "Appointment Date and Time cannot be blank, please try again."
end
end
What would be the best way to tackle a) identifying occurence: ["weekly", "biweekly", "monthly"] and then processing visits: "x" based on something similar to:
if #appointment.occurence == "weekly"
(x-1).times do |n|
submit same params but change within params appointment_date: = ((#appointment.appointment_date) + (n+1).week.to_formatted_s(:db)
#appointment.save
end
end
...and so on and so forth using (n+1).month for monthly occurrence (n+2).day and for bi-weekly occurrence(s).
Thank you in advance, hope this clarifies things. Just one item to note, do I need to store in database visits: and occurence:, I suspect not but would like to be certain they are used when hitting the models_controller create function.
Here's a slightly less cluttered solution that should do what you need, though it also assumes that you get rid of the :appointment_date field and change :appointment_time to a DateTime field. For more info on DateTimes check out:
(Stackoverflow will only allow me to post 2 links because I'm a n00b so search "DateTime Ruby" on your favorite search engine for documentation for the Ruby and rails methods for DateTime)
Formatting DateTime to string for views: http://apidock.com/ruby/DateTime/strftime
Intro on using DateTimes in forms: http://guides.rubyonrails.org/form_helpers.html#using-date-and-time-form-helpers
#appointment = Appointment.new(params[:appointment])
set_variables
if #appointment.save
if #procedure.occurence == "WEEKLY"
multiplier = 7
elsif #procedure.occurence == "BIWEEKLY"
multplier = 14
else
multiplier = 30
end
#visits = #procedure.visits - 1
#visits.times do |n|
Appointment.create!(
:procedure_id => #appointment.procedure_id,
:patient_id => #appointment.patient_id,
:appointment_time => (#appointment.appointment_time + (multiplier * n).days),
:attendance => "SCHEDULED"
)
end
else
flash.now[:error] = "There appears to be an error, please try again."
render 'new'
end
Solved for the moment, rather crude--as is my current ruby skill set--but it seems to have done the job.
#appointment = Appointment.new(params[:appointment])
set_variables
#appointment.save
if #procedure.occurence == "BIWEEKLY"
#visits = #procedure.visits - 1
#visits.times do |n|
procedure_id = #appointment.procedure_id
patient_id = #appointment.patient_id
appointment_date = (#appointment.appointment_date +
((n+2)*2).days).to_formatted_s(:db)
appointment_time = #appointment.appointment_time
appointment_notes = #appointment.appointment_notes
attendance = "SCHEDULED"
#scheduled = Appointment.create(procedure_id: procedure_id,
patient_id: patient_id, appointment_date: appointment_date,
appointment_time: appointment_time,
appointment_notes: appointment_notes, attendance: attendance)
end
end
if #procedure.occurence == "WEEKLY"
#visits = #procedure.visits - 1
#visits.times do |n|
procedure_id = #appointment.procedure_id
patient_id = #appointment.patient_id
appointment_date = (#appointment.appointment_date +
(n+1).week).to_formatted_s(:db)
appointment_time = #appointment.appointment_time
appointment_notes = #appointment.appointment_notes
attendance = "SCHEDULED"
#scheduled = Appointment.create(procedure_id: procedure_id,
patient_id: patient_id, appointment_date: appointment_date,
appointment_time: appointment_time,
appointment_notes: appointment_notes, attendance: attendance)
end
end
if #procedure.occurence == "MONTHLY"
#visits = #procedure.visits - 1
#visits.times do |n|
procedure_id = #appointment.procedure_id
patient_id = #appointment.patient_id
appointment_date = (#appointment.appointment_date + (n+1).month).to_formatted_s(:db)
appointment_time = #appointment.appointment_time
appointment_notes = #appointment.appointment_notes
attendance = "SCHEDULED"
#scheduled = Appointment.create(procedure_id: procedure_id,
patient_id: patient_id, appointment_date: appointment_date,
appointment_time: appointment_time,
appointment_notes: appointment_notes, attendance: attendance)
end
end
I am using Ruby 1.9.2, Rails 3.1. I have the following:
# review.rb
def calculate_rating
all_rating = Review.select("rating").where("reviewable_id = ?", self.reviewable_id)
all_rating.inject(:+)
end
# reviews_controller.rb
def create
#reviewable = find_reviewable
#review = #reviewable.reviews.where("user_id = ?", current_user).first
if #review.save
#review.calculate_rating
redirect_to :id => nil
else
flash[:error] = 'Error saving review. Please try again.'
redirect_to :id => nil
end
end
The idea behind this is that when a new review with rating is submitted and saved, it will find all ratings for all #reviewable, sum all the ratings and divide by the total number of ratings.
Problem that I am facing currently is this line: all_rating = Review.select("rating").where("reviewable_id = ?", self.reviewable_id) where all_rating returns an array of objects, like below:
[#<Review rating: #<BigDecimal:1050f0a40,'0.3E1',9(18)>>, #<Review rating: #<BigDecimal:1050f0928,'0.1E1',9(18)>>]
which I can't do any arithmetic calculation to it. I need it to be an array of numbers before I could use the inject to sum it and divide by the number of ratings.
Please advise me how I can get the inject to work. Many thanks!
AR/SQL (faster):
Review.select("rating").where(:reviewable_id => self.reviewable_id).sum(:rating)
Ruby (slower):
Review.select("rating").where(:reviewable_id => self.reviewable_id).map(&:rating).sum
How about just doing this:
def calculate_rating
all_rating = Review.select(:rating).where(:reviewable_id => reviewable_id).map(&:rating)
all_rating.inject(:+) # or you could just do all_rating.sum
end