So I've made a very simple command line app in Ruby - which loads product data from a JSON file, displays products to user, user can add them to their cart, the cart applies discount and displays the total.
I have the Products and Cart in modules and have managed to refactor the products so they are a class too (Which makes it easier for testing etc).
However I cannot work out how to successfully change the Cart into a class Cart inside the Cart module..
I don't want to create a new cart each time a product is added.. just want to add to the array
Any feedback and guidance is welcome :)
Files (5)
shop
require_relative './app/controller'
shop = Controller.new
shop.run
controller
require_relative './cart'
require_relative './product'
require_relative './menu'
class Controller
def initialize
#menu = Menu.new
end
def run
loop do
#menu.display_menu
end
end
end
menu
require 'tty-prompt'
require_relative './product.rb'
require_relative './cart.rb'
class Menu
puts "Hi, welcome to Bob's Bits the #1 CLI Shop"
def display_menu
puts "--------------------------------------------------------------"
prompt = TTY::Prompt.new
menu_selection = prompt.select("Select a menu item", ["View All Products", "View Shopping Cart", "Exit"])
case menu_selection
when "View All Products"
Products::view_all_products
when "View Shopping Cart"
Cart::view_shopping_cart
when "Exit"
puts "Goodbye"
exit
end
end
end
product
require 'json'
module Products
class Product
attr_accessor :uuid, :name, :price
def initialize(uuid, name, price)
#uuid = uuid
#name = name
#price = price
end
end
def self.view_all_products
# # Open & load products file (parse and read)
file = File.open "./products.json"
product_data = JSON.load file
product_list = product_data.each do | product |
Product.new(product["uuid"], product["name"], product["price"])
end
# Print out each items name for the menu selection
options = []
product_list.each do | item |
details = item["name"] + " $" + ('%.2f' % item["price"]).to_s
options.push(details)
end
# List product names so that user can select
prompt = TTY::Prompt.new
puts `clear`
item_to_add_to_cart = prompt.select("Select which items to add to cart", options)
# Match the name selected with product
product_for_cart = product_list.select do |product |
item_to_add_to_cart.include?(product["name"])
end
Cart::add_to_cart(product_for_cart)
end
end
cart
require 'tty-prompt'
module Cart
#shopping_cart = []
# Add the product selected to the shopping cart array
def self.add_to_cart(product_for_cart)
#shopping_cart.push(product_for_cart).flatten!
puts `clear`
end
def self.view_shopping_cart
# apply promo discounts based on cart subtotal
def self.discount_calc
# calculator subtotal of items in cart
subtotal = #shopping_cart.map {|item| item["price"]}.sum
#cart_data = {}
# track the discount for display & calc later on
if subtotal >= 100.0
#cart_data[:discount] = 20
elsif subtotal >= 50
#cart_data[:discount] = 15
elsif subtotal >= 20.0
#cart_data[:discount] = 10
else
#cart_data[:discount] = 0
end
#cart_data[:total] = (subtotal - (subtotal * #cart_data[:discount] / 100.00))
#cart_data[:savings] = subtotal - #cart_data[:total]
return #cart_data
end
# run the discount calculator
discount_calc
# Output cart to the user
puts ""
puts "Products in Shopping Cart:"
#shopping_cart.each_with_index do | item, i |
puts " #{i + 1}. #{item["name"]} - $#{'%.2f' % item["price"]}\n\n"
end
puts "Discount applied: #{#cart_data[:discount]}% (Total Savings $#{'%.2f' % #cart_data[:savings]}!)\n\n"
puts "Total After Discount: $#{'%.2f' % #cart_data[:total]}\n\n"
end
end
You can pass a cart as a parameter to constructors and methods
class Cart
def initialize
#shopping_cart = []
end
# ...
end
module Products
# ...
def self.view_all_products(cart)
# ...
cart.add_to_cart(product_for_cart)
end
end
class Menu
puts "Hi, welcome to Bob's Bits the #1 CLI Shop"
def initialize(cart)
#cart = cart
end
def display_menu
puts "--------------------------------------------------------------"
prompt = TTY::Prompt.new
menu_selection = prompt.select("Select a menu item", ["View All Products", "View Shopping Cart", "Exit"])
case menu_selection
when "View All Products"
Products::view_all_products(#cart)
when "View Shopping Cart"
#cart.view_shopping_cart
when "Exit"
puts "Goodbye"
exit
end
end
end
class Controller
def initialize
#cart = Cart.new
#menu = Menu.new(#cart)
end
def run
loop do
#menu.display_menu
end
end
end
shop = Controller.new
shop.run
Related
Please see below.
The delete method is not working and I do not know why.
I am trying to delete a customer without using rails and just plain ruby.
please can you help.
wrong number of arguments (given 0, expected 1) (ArgumentError)
from /Users/mustafaalomer/code/MustafaAlomer711/fullstack-challenges/02-OOP/05-Food-Delivery-Day-One/01-Food-Delivery/app/repositories/customer_repository.rb:28:in `delete'
from /Users/mustafaalomer/code/MustafaAlomer711/fullstack-challenges/02-OOP/05-Food-Delivery-Day-One/01-Food-Delivery/app/controllers/customers_controller.rb:33:in `destroy'
from /Users/mustafaalomer/code/MustafaAlomer711/fullstack-challenges/02-OOP/05-Food-Delivery-Day-One/01-Food-Delivery/router.rb:36:in `route_action'
from /Users/mustafaalomer/code/MustafaAlomer711/fullstack-challenges/02-OOP/05-Food-Delivery-Day-One/01-Food-Delivery/router.rb:13:in `run'
from app.rb:19:in `<main>'
require_relative "../views/customers_view"
require_relative "../models/customer"
class CustomersController
def initialize(customer_repository)
#customer_repository = customer_repository
#customers_view = CustomersView.new
end
def add
# ask user for a name
name = #customers_view.ask_user_for(:name)
# ask user for a address
address = #customers_view.ask_user_for(:address)
# make a new instance of a customer
customer = Customer.new(name: name, address: address)
# add the customer to the repository
#customer_repository.create(customer)
list
end
def list
customers = #customer_repository.all
#customers_view.display_list(customers)
end
def destroy
# ask user for the id to delete
list
id = #customers_view.ask_user_to_delete(:id)
# customer = #customer_repository.find(id)
# #customer_repository.delete(customer)
end
end
require 'csv'
require_relative '../models/customer'
class CustomerRepository
def initialize(csv_file)
#csv_file = csv_file
#customers = []
#next_id = 1
load_csv if File.exist?(csv_file)
end
def all
#customers
end
def create(customer)
customer.id = #next_id
#customers << customer
#next_id += 1
save_csv
end
def find(id)
#customers.find { |customer| customer.id == id}
end
def delete(id)
#customers.delete { |customer| customer.id == id}
end
private
def save_csv
CSV.open(#csv_file, "wb") do |csv|
csv << %w[id name address]
#customers.each do |customer|
csv << [customer.id, customer.name, customer.address]
end
end
end
def load_csv
CSV.foreach(#csv_file, headers: :first_row, header_converters: :symbol) do |row|
row[:id] = row[:id].to_i
#customers << Customer.new(row)
end
#next_id = #customers.last.id + 1 unless #customers.empty?
end
end
delete always takes an argument.
delete_if can be given a block and seems to be what you're looking for.
Writing my Ruby-Watir-cucumber bdd test I get this:
NoMethodError: undefined method `msg=' for #<Watir::Browser:0x0000558fbcdc0cc0>
./features/support/pages/message_page.rb:27:in `escribir'
./features/step_definitions/message_steps.rb:14:in `/^I'm able to write and send a "([^"]*)" successfully$/'
./features/send_messages.feature:12:in `Then I'm able to write and send a "Robot message" successfully'
./features/send_messages.feature:9:in `Then I'm able to write and send a "<message>" successfully'
1 scenario (1 failed)
4 steps (1 failed, 3 passed)
0m12.939s
Process finished with exit code 1
When I have already defined the method:
class MessagePage
include PageObject
##browser = Watir::Browser.new
page_url 'https://www.linkedin.com/messaging/'
text_field(:searchcontact, name: 'searchTerm')
div(:txtmessage,:role => "textbox")
button(:btnsend,:type => 'submit')
div(:txtfield,:xpath =>"//div//div[#role='textbox']")
text_field(:mensaje,:xpath =>"//div//div[#role='textbox']")
div(:msg,:role => "textbox") /// HERE!!!
def searchcontact contact
self.searchcontact = contact
#searchcontact(contact).send_keys(:enter)
wait 5
end
def buscar contact
wait_until do
searchcontact_element.visible?
self.searchcontact = contact
end
self.searchcontact = contact
end
def escribir (message)
self.msg = message
wait 5
end
def writemessage message
wait_until do
msg_element.visible?
self.msg = message
end
self.msg = message
end
def sendmessage
btnsend
end
end
The accessor div(:msg,:role => "textbox") does not generate a #msg= method. It only defines:
#msg - Gets the text of the div
#msg? - Check if the div is present
#msg_element - Get the PageObject::Elements::Element
You will need to either manually define the method or create a widget for content editable elements.
Manually Define Setter
Contenteditable elements can be be inputted using the #set method. You can use this to create a setter method:
class MessagePage
include PageObject
div(:msg, role: "textbox")
def msg=(value)
msg_element.set(value)
end
end
page = MessagePage.new(browser)
page.msg = 'your text'
p page.msg
#=> "your text"
Define a Widget
If you have to deal with multiple contenteditable elements, you should create a widget to eliminate the need to manually create each of the setters.
class Contentedtiable < PageObject::Elements::Element
def self.accessor_methods(widget, name)
#
# Set text
#
widget.send('define_method', "#{name}=") do |value|
self.send("#{name}_element").set(value)
end
#
# Get text
#
widget.send('define_method', "#{name}") do
self.send("#{name}_element").text
end
end
PageObject.register_widget :contenteditable, self, :element
end
class MyPage
include PageObject
contenteditable(:msg, tag_name: 'div', role: 'textbox')
end
page = MyPage.new(browser)
page.msg = 'your text'
p page.msg
#=> "your text"
Once you click on the textbox you could sendkeys to it. So something like
msg.click then msg.send_keys('blah')
Or maybe as it's a textbox you could operate directly with it depending on how your page is coded - So msg.send_keys('blah') May work directly.
I am trying to get albums.title (in the display_album method). Whenever I run the code through the terminal, it says undefine method 'title' for #. I think I put all instances correctly. I am not sure what I miss.
I tried to check if I put instances correctly and also debugging with p method to check if instance variables do not work across the class.
$genre = ['Null', 'Pop', 'Classic', 'Jazz', 'Rock']
class Album
attr_accessor :title, :artist, :genre, :tracks
def initialize(title, artist, genre, tracks)
#title = title
#artist = artist
#genre = genre
#tracks = tracks
end
end
def read_albums()
puts "write a file name"
afile = gets.chomp
file = File.new(afile, "r")
albums = Array.new()
if file
i =0
count = file.gets.to_i
while i<count
albums << read_album(file)
i +=1
end
file.close
end
albums
end
def read_album(file)
album_title = file.gets
album_artist = file.gets
album_genre = file.gets
album_tracks = read_tracks(file)
album = Album.new(album_title, album_artist, album_genre, album_tracks)
album
end
def display_albums(albums)
finished = false
begin
selection = read_integer_in_range("choose the number", 1, 3)
case selection
when 1
display_album(albums)
when 2
display_genres
when 3
finished = read_boolean 'Are you sure to exit ? (enter yes if yes)'
else
puts 'Please select again'
end
end
end
def display_album(albums)
puts "the album title is" + albums.title.to_s
end
def main_menu
finished = false
begin
selection = read_integer_in_range('please select the number between 1 and 5', 1, 5)
case selection
when 1
albums = read_albums
when 2
display_albums(albums)
when 3
play_album
when 4
update_album
when 5
finished = read_boolean 'Are you sure to exit ? (enter yes if yes)'
else
puts 'Please select again'
end
end until finished
end
main_menu
I expect to get a title name which is "Coldplay Viva la Vida or Death and All His Friends".
The error message I got is music_player.rb:103:in display_album': undefined methodtitle' for # (NoMethodError)
def display_album(albums)
puts "the album title is" + albums.title.to_s
end
You're feeding in an array of albums instead of one individual album. Change it to:
def display_album(album)
puts "the album title is" + album.title.to_s
end
And when you call the method, make sure you call it with just one album as an argument rather than an array. The error is telling you you were calling the 'title' method on an array of albums, when you need to call it on just one item.
How to click an element in RecyclerView by using Appium, Ruby. I tried using all ways but not able to click
I have tried all possible ways
require 'selenium-cucumber'
require 'appium_lib'
def alert1
$driver.find_element(xpath: "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.widget.ScrollView/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout[2]/android.widget.Button[2]")
end
def alert2; $driver.find_element(xpath: "/hierarchy/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.ViewGroup/android.widget.ScrollView/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout/android.widget.LinearLayout[2]/android.widget.Button[2]") end
def phoneNumber; $driver.find_element(id: "com.accushield.familyapp:id/phone_number") end
def sendCode; $driver.find_element(id: "com.accushield.familyapp:id/send_code") end
def confirmationCode; $driver.find_element(id: "com.accushield.familyapp:id/confirmation_code") end
def submitCode; $driver.find_element(id: "com.accushield.familyapp:id/submit_confirmation_code") end
def dashboardScreen1; $driver.find_element(xpath: "/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.view.ViewGroup/android.support.v7.widget.RecyclerView/android.widget.FrameLayout[1]/android.view.ViewGroup").click end
Then(/^do user Login with OTP$/) do
alert1.click
alert2.click
phoneNumber.send_keys("8131234567")
sendCode.click
confirmationCode.send_keys("123456")
submitCode.click
sleep 40
#residentClick.click
#$driver.find_element(xpath: "//android.widget.ImageView[#index='1']").click
dashboardScreen1.click
#if dashboardScreen.displayed?
# puts "User has signed in successfully"
# else
# puts "User sign in got failed"
# end
I need to click any element in recycler view. Please find the image for elements
https://i.stack.imgur.com/cmJ23.png
I am doing a task that requires me add some products together and give a 10% discount providing the total is above £60. I have done the following:
class Checkout
def initialize (rules)
#rules = rules
#cart = []
end
def scan (item)
if product == Product.find(item)
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
def total
#cart = #rules.apply #cart
end
def self.find item
[item]
end
co = Checkout.new(Promotional_Rules.new)
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(3)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(3)
co.scan(1)
puts "Total price: #{co.total}"
puts
co.empty_cart
co.scan(1)
co.scan(2)
co.scan(1)
co.scan(3)
puts "Total price: #{co.total}"
puts
However when I run this in irb I get undefined variable or method product. Sounds a bit daft but this should work.
You're using one too many equal signs
def scan (item)
# if product == Product.find(item)
if product = Product.find(item) # <--- should be this
#cart << product.clone
#Clone preserves frozen state whereas .dup() doesn't if use would raise a
#NoMethodError
end
end
Of course, then you'll get a different error since find doesn't exist on Product yet... which I think you're trying to define here:
def self.find item # self should be changed to Product
[item]
end
Then you're going to get an error for apply not existing for Promotional_Rules ...
One of the best ways to debug these errors is follow the stack traces. So for the last error I get the following message:
test.rb:53:in `total': undefined method `apply' for #<Promotional_Rules:0x007f94f48bc7a8> (NoMethodError)
from test.rb:72:in `<main>'
That's basically saying that at line 53 you'll find apply hasn't been defined for #rules which is an instance of Promotional_Rules. Looking at the Promotional_Rules class you've clearly defined that method as apply_to_item and not apply. If you keep following and fixing the rabbit trails like this for stack traces you'll be able to debug your program with ease!