Rails 3 way working with multiple if statements and form create/new - activerecord

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

Related

rails sort collection by custom method

I have a method which takes ID of project and find all payments, do the math and the output is percentage of success (target amount vs collected)
def projectCollectedMoneyPerc(id)
#project= Project.find(id)
#collected = Payment.where('project_id = ? and confirmed = true', #project.id)
#col = #collected.sum(:amount)
#perc = ((#col.to_f / #project.amount) * 100).round(0)
end
now I need to find projects which have most % success. My idea was to call this method by sort_by but I have no idea how to put ID from collection to this sort
my collection is simple
#projects=Project.where('enabled = true and enddate > ?', Time.now)
thanks
I would define a method like to following in your model:
# in app/models/project.rb
has_many :payments
def collected_money_percentage
sum = payments.where(confirmed: true).sum(:amount)
(100.0 * sum / amount ).round
end
Then you cound use that method like this:
Project.where('enabled = true and enddate > ?', Time.now)
.sort_by(&:collected_money_percentage)
Please note that this first loads all matching record and then calculations the percentage in memory. It would probably be faster to calculate this values in your database:
Project.joins(:payments)
.where('enabled = true and enddate > ?', Time.now)
.group('projects.id')
.order('SUM(payments.amount) / projects.amount')

error when creating hash in simple Credit Card class for ruby

I am creating a simple CC class that can create and update a credit card. To do this, I have created cc_bal{} as an instance object so it can update respect credit cards. The hash is to save and update a person and the amount on their cc. I end up getting an output of just the original amount that was created and not the updated amount
Heres the code:
class CC
def initialize(cc_name, cc_bal = {}, open_cc = false)
#cc_name = cc_name
#cc_bal = cc_bal
#open_cc = open_cc
end
def create(initAmount, person)
if initAmount > 0
#open_cc = true
#cc_bal[:person]=initAmount
puts "congrats #{person} on opening your new #{#cc_name} CC! with $#{#cc_bal[:person]}"
else
puts "sorry not enough funds"
end
end
def update(amount, person)
if #open_cc == true
#cc_bal[:person] + amount
else
puts "sorry no account created, #{person}"
end
puts "#{person} has CC balance of #{#cc_bal[:person]}"
end
end
#chase = Bank.new("JP Morgan Chase")
#wells_fargo = Bank.new("Wells Fargo")
me = Person.new("Shehzan", 500)
friend1 = Person.new("John", 1000)
#chase.open_account(me)
#chase.open_account(friend1)
#wells_fargo.open_account(me)
#wells_fargo.open_account(friend1)
#chase.deposit(me, 200)
#chase.deposit(friend1, 300)
#chase.withdraw(me, 50)
#chase.transfer(me, wells_fargo, 100)
#chase.deposit(me, 5000)
#chase.withdraw(me, 5000)
#puts chase.total_cash_in_bank
#puts wells_fargo.total_cash_in_bank
credit_card = CC.new("Visa")
credit_card.create(10,me)
credit_card.update(50,me)
credit_card.create(20,friend1)
credit_card.update(40,friend1)
Please disregard the function calls that are commented out.
Any idea why the CC's are not updatiing?
if #open_cc == true
#cc_bal[:person] + amount
else
You increase the amount, but you don't set the new value anywhere. It should be
if #open_cc == true
#cc_bal[:person] += amount
else
Note. The code needs some serious refactoring and cleanup.

Create instance of class within another class

Forgive me if my question isn't completely clear. I have been awake for way too long and I'm feeling a little brain dead.
I'm doing a Ruby exercise and I can't figure out why my rspec test isn't passing for something I thought would work.
require 'date'
class Product
attr_accessor :photo_src, :promotion, :initial_date
attr_reader :default_photo, :default_price, :current_price
def initialize(name, photo, price)
#name = name
#default_photo = photo
#photo_src = photo
#default_price = price
#current_price = price
#initial_date = Date.today.yday
#promotion = false
end
def price_change(sale_price)
calculator = RedPencilCalculator.new(self)
if promotion
if sale_price > #current_price
calculator.end_promotion!
elsif sale_price < (#default_price - (#default_price * 0.3))
calculator.end_promotion!
end
else
calculator.start_promotion!
end
#current_price = sale_price
end
end
class RedPencilCalculator
attr_accessor :promotion_start, :product
def initialize(product)
#product = product
end
def start_promotion!
if start_promotion?
product.promotion = true
product.photo_src = "redX.png"
#promotion_start = Date.today.yday
end
end
#would need to run daily
def end_promotion?
promotion_duration
if #duration == 30 || #duration == 335
end_promotion!
end
end
def end_promotion!
product.promotion = false
product.photo_src = product.default_photo
product.initial_date = Date.today.yday
end
private
def calculate_range
#min_discount = product.default_price - (product.default_price * 0.05)
#max_discount = product.default_price - (product.default_price * 0.3)
end
def start_promotion?
calculate_range
#max_discount <= product.current_price && product.current_price <= #min_discount && Date.today.yday - product.initial_date >= 30
end
def promotion_duration
current_date = Date.today.yday
#duration = current_date - #promotion_start
end
end
Rspec
This doesn't work:
describe Product do
let(:shoes) { Product.new("shoes", "item.png", 100) }
it 'should change the photo_src and promotion attribute if applicable' do
allow(shoes).to receive(:initial_date) { 100 }
shoes.price_change(75)
expect(shoes.promotion).to eq(true)
expect(shoes.photo_src).to eq("redX.png")
end
end
This does:
describe Product do
let(:shoes) { Product.new("shoes", "item.png", 100) }
let(:calculator) { RedPencilCalculator.new(shoes) }
it 'should change the photo_src and promotion attribute if applicable' do
allow(shoes).to receive(:initial_date) { 100 }
shoes.price_change(75)
calculator.start_promotion!
expect(shoes.promotion).to eq(true)
expect(shoes.photo_src).to eq("redX.png")
end
end
So it seems to me that the start_promotion! method call in the price_change method just isn't working.
I don't have a specific answer to your bug but some suggestions on how to pinpoint the problem.
You're testing too much in one unit test. There's so much that can go wrong it's hard (as you've found) to track down where the bug lies. Even if you work it out now, when something changes down the track (as it inevitably will) it will be at least as difficult as it is now to debug.
Simplify the initializer. It should only set #name, #photo, #price. The other instance variables should be methods (write tests unless they're private).
You suspect RedPencilCalculator#start_promotion! has a bug. Write a test to eliminate that possibility.
With more tests in place, the bug will eventually be cornered and crushed!
Lastly - this is easier said than done - but try writing tests first. It is hard but gets easier and even enjoyable!
ok, I put a puts inside of start_promotion? like this:
p "got past calc range: #{#max_discount.inspect} and #{#min_discount.inspect} and #{product.current_price}"
and got:
"got past calc range: 70.0 and 95.0 and 100"
given that the following line checks that current-price is less than the min-discount...
that's the line you've gotta check/fix to make things work

Calling multiple methods on a CSV object

I have constructed an Event Manager class that performs parsing actions on a CSV file, and produces html letters using erb. It is part of a jumpstart labs tutorial
The program works fine, but I am unable to call multiple methods on an object without the earlier methods interfering with the later methods. As a result, I have opted to create multiple objects to call instance methods on, which seems like a clunky inelegant solution. Is there a better way to do this, where I can create a single new object and call methods on it?
Like so:
eventmg = EventManager.new("event_attendees.csv")
eventmg.print_valid_phone_numbers
eventmg_2 = EventManager.new("event_attendees.csv")
eventmg_2.print_zipcodes
eventmg_3 = EventManager.new("event_attendees.csv")
eventmg_3.time_targeter
eventmg_4 = EventManager.new("event_attendees.csv")
eventmg_4.day_of_week
eventmg_5 = EventManager.new("event_attendees.csv")
eventmg_5.create_thank_you_letters
The complete code is as follows
require 'csv'
require 'sunlight/congress'
require 'erb'
class EventManager
INVALID_PHONE_NUMBER = "0000000000"
Sunlight::Congress.api_key = "e179a6973728c4dd3fb1204283aaccb5"
def initialize(file_name, list_selections = [])
puts "EventManager Initialized."
#file = CSV.open(file_name, {:headers => true,
:header_converters => :symbol} )
#list_selections = list_selections
end
def clean_zipcode(zipcode)
zipcode.to_s.rjust(5,"0")[0..4]
end
def print_zipcodes
puts "Valid Participant Zipcodes"
#file.each do |line|
zipcode = clean_zipcode(line[:zipcode])
puts zipcode
end
end
def clean_phone(phone_number)
converted = phone_number.scan(/\d/).join('').split('')
if converted.count == 10
phone_number
elsif phone_number.to_s.length < 10
INVALID_PHONE_NUMBER
elsif phone_number.to_s.length == 11 && converted[0] == 1
phone_number.shift
phone_number.join('')
elsif phone_number.to_s.length == 11 && converted[0] != 1
INVALID_PHONE_NUMBER
else
phone_number.to_s.length > 11
INVALID_PHONE_NUMBER
end
end
def print_valid_phone_numbers
puts "Valid Participant Phone Numbers"
#file.each do |line|
clean_number = clean_phone(line[:homephone])
puts clean_number
end
end
def time_targeter
busy_times = Array.new(24) {0}
#file.each do |line|
registration = line[:regdate]
prepped_time = DateTime.strptime(registration, "%m/%d/%Y %H:%M")
prepped_time = prepped_time.hour.to_i
# inserts filtered hour into the array 'list_selections'
#list_selections << prepped_time
end
# tallies number of registrations for each hour
i = 0
while i < #list_selections.count
busy_times[#list_selections[i]] += 1
i+=1
end
# delivers a result showing the hour and the number of registrations
puts "Number of Registered Participants by Hour:"
busy_times.each_with_index {|counter, hours| puts "#{hours}\t#{counter}"}
end
def day_of_week
busy_day = Array.new(7) {0}
d_of_w = ["Monday:", "Tuesday:", "Wednesday:", "Thursday:", "Friday:", "Saturday:", "Sunday:"]
#file.each do |line|
registration = line[:regdate]
# you have to reformat date because of parser format
prepped_date = Date.strptime(registration, "%m/%d/%y")
prepped_date = prepped_date.wday
# adds filtered day of week into array 'list selections'
#list_selections << prepped_date
end
i = 0
while i < #list_selections.count
# i is minus one since days of week begin at '1' and arrays begin at '0'
busy_day[#list_selections[i-1]] += 1
i+=1
end
#busy_day.each_with_index {|counter, day| puts "#{day}\t#{counter}"}
prepared = d_of_w.zip(busy_day)
puts "Number of Registered Participants by Day of Week"
prepared.each{|date| puts date.join(" ")}
end
def legislators_by_zipcode(zipcode)
Sunlight::Congress::Legislator.by_zipcode(zipcode)
end
def save_thank_you_letters(id,form_letter)
Dir.mkdir("output") unless Dir.exists?("output")
filename = "output/thanks_#{id}.html"
File.open(filename,'w') do |file|
file.puts form_letter
end
end
def create_thank_you_letters
puts "Thank You Letters Available in Output Folder"
template_letter = File.read "form_letter.erb"
erb_template = ERB.new template_letter
#file.each do |line|
id = line[0]
name = line[:first_name]
zipcode = clean_zipcode(line[:zipcode])
legislators = legislators_by_zipcode(zipcode)
form_letter = erb_template.result(binding)
save_thank_you_letters(id,form_letter)
end
end
end
The reason you're experiencing this problem is because when you apply each to the result of CSV.open you're moving the file pointer each time. When you get to the end of the file with one of your methods, there is nothing for anyone else to read.
An alternative is to read the contents of the file into an instance variable at initialization with readlines. You'll get an array of arrays which you can operate on with each just as easily.
"Is there a better way to do this, where I can create a single new object and call methods on it?"
Probably. If your methods are interfering with one another, it means you're changing state within the manager, instead of working on local variables.
Sometimes, it's the right thing to do (e.g. Array#<<); sometimes not (e.g. Fixnum#+)... Seeing your method names, it probably isn't.
Nail the offenders down and adjust the code accordingly. (I only scanned your code, but those Array#<< calls on an instance variable, in particular, look fishy.)

Convert array of objects into number for inject sum

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

Resources