I'd like some help to work out how to properly use starts_with? to use the below code to hide a sunday shipping rate if the post code starts with one in the list. I've got it to the position where it will hide my sunday rate if the post code INCLUDES one of the values from my list, but not if it STARTS WITH.
I thought I could update the includes to be starts with but I'm not well versed enough to understand where I'm going wrong here as I've only been learning about JS/Ruby for a few weeks.
# ============ PostCodeIdentifier ========================
class PostCodeIdentifier
def initialize(code_trigger)
#codes = code_trigger[:codes].map(&:downcase)
#trigger_when = code_trigger[:trigger_when].downcase
end
def match?(post_code)
case #trigger_when
when :matches
return true if #codes.any? {|code| post_code.downcase.include?(code)}
when :does_not_match
return true unless #codes.any? {|code| post_code.downcase.include?(code)}
else
return false
end
end
end
# ============ PostCodeHideRateCampaign =================
class PostCodeHideRateCampaign
def initialize(post_code_triggers)
#post_code_triggers = post_code_triggers
end
def run(cart, shipping_rates)
return unless cart.shipping_address.zip
#post_code_triggers.each do |post_code_trigger|
post_code_identifier = PostCodeIdentifier.new(post_code_trigger)
if post_code_identifier.match?(cart.shipping_address.zip)
shipping_rates.delete_if do |rate|
rate.name.downcase.include?(post_code_trigger[:rate_to_hide_trigger].downcase)
end
end
end
end
end
# ========================================== DO NOT SHOW ==========================================
POST_CODE_TRIGGERS_1 = [
{codes: [
'AB33' , 'AB34' , 'AB35' , 'AB36' , 'AB37' , 'AB38' , 'AB39' , 'AB40' , 'AB41' , 'AB42' , 'AB43' , 'AB44' , 'AB45' , 'AB46' , 'AB47' , 'AB48' , 'AB49'
],
rate_to_hide_trigger: 'SUNDAY',
trigger_when: :matches },
]
CAMPAIGNS = [
PostCodeHideRateCampaign.new(
POST_CODE_TRIGGERS_1
)
]
CAMPAIGNS.each do |campaign|
campaign.run(Input.cart, Input.shipping_rates)
end
# ========================================== END POSTCODE RESTRICTIONS ==========================================
Tried changing around the includes and matches to start_with? where I thought it would work, but I'm missing something.
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.
I got Ruby to travel to a web site, iterate through a list of campaigns and scrape the pages for specific data. The problem I have now is getting it from the structure Nokogiri gives me, and outputting it into a readable form.
campaign_list = Array.new
campaign_list.push(1042360, 1042386, 1042365, 992307)
browser = Watir::Browser.new :chrome
browser.goto '<redacted>'
browser.text_field(:id => 'email').set '<redacted>'
browser.text_field(:id => 'password').set '<redacted>'
browser.send_keys :enter
file = File.new('hourlysales.csv', 'w')
data = {}
campaign_list.each do |campaign|
browser.goto "<redacted>"
if browser.text.include? "Application Error"
puts "Error loading page, I recommend restarting script"
# Possibly automatic restart of script
else
hourly_data = Nokogiri::HTML.parse(browser.html).text
# file.write data
puts hourly_data
end
This is the output I get:
{"views":[[17,145],[18,165],[19,99],[20,71],[21,31],[22,26],[23,10],[0,15],[1,1], [2,18],[3,19],[4,35],[5,47],[6,44],[7,67],[8,179],[9,141],[10,112],[11,95],[12,46],[13,82],[14,79],[15,70],[16,103]],"orders":[[17,10],[18,9],[19,5],[20,1],[21,1],[22,0],[23,0],[0,1],[1,0],[2,1],[3,0],[4,1],[5,2],[6,1],[7,5],[8,11],[9,6],[10,5],[11,3],[12,1],[13,2],[14,4],[15,6],[16,7]],"conversion_rates":[0.06870229007633588,0.05442176870748299,0.050505050505050504,0.014084507042253521,0.03225806451612903,0.0,0.0,0.06666666666666667,0.0,0.05555555555555555,0.0,0.02857142857142857,0.0425531914893617,0.022727272727272728,0.07462686567164178,0.06134969325153374,0.0425531914893617,0.044642857142857144,0.031578947368421054,0.021739130434782608,0.024390243902439025,0.05063291139240506,0.08571428571428572,0.06741573033707865]}
The arrays stand for { views [[hour, # of views], [hour, # of views], etc. }. Same with orders. I don't need conversion rates.
I also need to add the values up for each key, so after doing this for 5 pages, I have one key for each hour of the day, and the total number of views for that hour. I tried a couple each loops, but couldn't make any progress.
I appreciate any help you guys can give me.
It looks like the output (which from your code I assume is the content of hourly_data) is JSON. In that case, it's easy to parse and add up the numbers. Something like this:
require "json" # at the top of your script
# ...
def sum_hours_values(data, hours_values=nil)
# Start with an empty hash that automatically initializes missing keys to `0`
hours_values ||= Hash.new {|hsh,hour| hsh[hour] = 0 }
# Iterate through the [hour, value] arrays, adding `value` to the running
# count for that `hour`, and return `hours_values`
data.each_with_object(hours_values) do |(hour, value), hsh|
hsh[hour] += value
end
end
# ... Watir/Nokogiri stuff here...
# Initialize these so they persist outside the loop
hours_views, orders_views = nil
campaign_list.each do |campaign|
browser.goto "<redacted>"
if browser.text.include? "Application Error"
# ...
else
# ...
hourly_data_parsed = JSON.parse(hourly_data)
hours_views = sum_hours_values(hourly_data_parsed["views"], hours_views)
hours_orders = sum_hours_values(hourly_data_parsed["orders"], orders_views)
end
end
puts "Views by hour:"
puts hours_views.sort.map {|hour_views| "%2i\t%4i" % hour_views }
puts "Orders by hour:"
puts hours_orders.sort.map {|hour_orders| "%2i\t%4i" % hour_orders }
P.S. There's a really nice recursive version of sum_hours_values I didn't include since the iterative version is clearer to most Ruby programmers. If you're into recursion I leave it as an exercise for you. ;)
The below is my test in RSPEC. It is failing and I can't figure out why! I am trying to get it to fill the van with bikes upto it's capacity but leave all the other bikes at the station. However it is deleting all the bikes except one in each at the moment.
let(:van) { Van.new }
let(:bike) { double :bike, broken?: false }
let(:broken_bike) { double :broken_bike, broken?: true }
let(:station) { double :station }
it 'can be filled with broken bikes' do
station = double :station, { release_x_broken_bikes: broken_bike }
expect(station).to receive(release_x_broken_bikes: broken_bike)
van.fill_with_broken_bikes_from station
expect(van.bike_count).to eq > 2
end
Relevant code in van.rb:
def initialize bikes = [], capacity = 10
#bikes = bikes
#capacity = capacity
end
def bike_count
#bikes.count
end
def fill_with_broken_bikes_from station
#bikes = station.release_x_broken_bikes(slots_available)
end
def slots_available
#capacity - bike_count
end
Code in station.rb:
def broken_bikes
#bikes.select { |bike| bike.broken? }
end
def release_broken_bike
#bikes.delete(broken_bikes.first)
end
def release_x_broken_bikes(x)
broken_bikes[0,x].map{ release_broken_bike}
end
In your spec:
expect(station).to receive(release_x_broken_bikes: broken_bike)
You're telling the station to expect to receive a release_x_broken_bikes with an argument of broken_bike? Looking at the method, it wants a number. broken_bike looks like a double to me.
Your error tells you exactly this:
1) Van can be filled with broken bikes Failure/Error: van.fill_with_broken_bikes_from station Double :station received unexpected message :release_x_broken_bikes with (10) # ./lib/van.rb:29:in fill_with_broken_bikes_from' # ./spec/van_spec.rb:51:in block (2 levels) in ' Finished in 0.00995 seconds 12 examples, 1 failure
It received a call to release_x_broken_bikes with 10, which seems to be what you should actually want. Tell it to expect that, and you can get to your next problem (if there is one).
I have the following method:
Now sometimes I want the limit attribute to be ignored
Therefore the .take method would be ignored, at the moment I do not know how to do this gracefully. As setting to nil errors the code.
Any help appreciated - new to ruby.
def articlesByCategory( category, extensions = [ "md" ], limit = 3 )
# Check category is an array
if !category.kind_of?(Array)
category = [ category ]
end
# Create Return Array
ret = []
# Get the resources that are
sitemap.resources.select { |r| ( category & Array( r.data.category ) ).present? }.take( limit ).each do |a|
ret << a
end
# Return
ret
end
First, your code can be significantly refactored to do the same thing it’s doing now. There’s no need to do an each to build an exact copy of the array, and we can apply Kernel#Array to category the same way your did to r.data.category. Finally, any? reads a bit better (IMO) than present?, especially since the value cannot be nil (only caveat is if nil or false is a valid category).
def articles_by_category category, limit = 3
category = Array(category)
sitemap.resources.select do |resource|
(Array(resource.data.category) & category).any?
end.take(limit)
end
We can easily pull the take out into a conditional to get what you want:
def articles_by_category category, limit = 3
category = Array(category)
articles = sitemap.resources.select do |resource|
(Array(resource.data.category) & category).any?
end
limit ? articles.take(limit) : articles
end
It might, however, make sense to just get rid of the limit entirely within the method and impose it externally. This is much more functional and keeps your method from doing a lot of things (what does a limit have to do with getting articles by category? (this method doesn’t even get articles by category, it gets whatever resources is (presumably articles…) for a given category)).
def articles_by_category category
category = Array(category)
sitemap.resources.select do |resource|
(Array(resource.data.category) & category).any?
end
end
articles_by_category('My Category').take(3)
Note that if category will never be an array (which seems likely given its singular name) than you can further simplify your method to:
def articles_by_category category
sitemap.resources.select do |resource|
resource.data.category == category
end
end
(And of course add the limit feature back in if so desired.)
You could simply do
category ||= []
Did it like this in the end
def articlesByCategory( category, extensions = [ "md" ], limit = 3 )
# Check category is an array
if !category.kind_of?(Array)
category = [ category ]
end
# Create Return Array
ret = []
# Get Resources
resources = sitemap.resources.select { |r| ( category & Array( r.data.category ) ).present? }
# Check limit is set
if !limit.nil?
resources = resources.take( limit )
end
# Get the resources that are
resources.each do |a|
ret << a
end
# Return
ret
end