diff --git a/.gitignore b/.gitignore index d1a1edf06f..af4db5b46d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /**/.DS_Store /coverage +.env # Local cache of Rubocop remote config .rubocop-* diff --git a/Gemfile b/Gemfile index 35de4a7f0e..8df573aaf4 100644 --- a/Gemfile +++ b/Gemfile @@ -10,4 +10,6 @@ end group :development, :test do gem 'rubocop', '1.20' + gem 'twilio-ruby' + gem 'dotenv' end diff --git a/Gemfile.lock b/Gemfile.lock index 66064703c7..a7e5c7f069 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,93 @@ GEM remote: https://rubygems.org/ specs: + actionpack (7.0.2.4) + actionview (= 7.0.2.4) + activesupport (= 7.0.2.4) + rack (~> 2.0, >= 2.2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actionview (7.0.2.4) + activesupport (= 7.0.2.4) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activesupport (7.0.2.4) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) ansi (1.5.0) ast (2.4.2) + builder (3.2.4) + concurrent-ruby (1.1.10) + crass (1.0.6) diff-lcs (1.4.4) docile (1.4.0) + dotenv (2.7.6) + dotenv-rails (2.7.6) + dotenv (= 2.7.6) + railties (>= 3.2) + erubi (1.10.0) + faraday (1.10.0) + faraday-em_http (~> 1.0) + faraday-em_synchrony (~> 1.0) + faraday-excon (~> 1.1) + faraday-httpclient (~> 1.0) + faraday-multipart (~> 1.0) + faraday-net_http (~> 1.0) + faraday-net_http_persistent (~> 1.0) + faraday-patron (~> 1.0) + faraday-rack (~> 1.0) + faraday-retry (~> 1.0) + ruby2_keywords (>= 0.0.4) + faraday-em_http (1.0.0) + faraday-em_synchrony (1.0.0) + faraday-excon (1.1.0) + faraday-httpclient (1.0.1) + faraday-multipart (1.0.3) + multipart-post (>= 1.2, < 3) + faraday-net_http (1.0.1) + faraday-net_http_persistent (1.2.0) + faraday-patron (1.0.0) + faraday-rack (1.0.0) + faraday-retry (1.0.3) + i18n (1.10.0) + concurrent-ruby (~> 1.0) + jwt (2.3.0) + loofah (2.17.0) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + method_source (1.0.0) + mini_portile2 (2.8.0) + minitest (5.15.0) + multipart-post (2.1.1) + nokogiri (1.13.4) + mini_portile2 (~> 2.8.0) + racc (~> 1.4) parallel (1.20.1) parser (3.0.2.0) ast (~> 2.4.1) + racc (1.6.0) + rack (2.2.3) + rack-test (1.1.0) + rack (>= 1.0, < 3) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.4.2) + loofah (~> 2.3) + railties (7.0.2.4) + actionpack (= 7.0.2.4) + activesupport (= 7.0.2.4) + method_source + rake (>= 12.2) + thor (~> 1.0) + zeitwerk (~> 2.5) rainbow (3.0.0) + rake (13.0.6) regexp_parser (2.1.1) rexml (3.2.5) rspec (3.10.0) @@ -36,6 +115,7 @@ GEM rubocop-ast (1.11.0) parser (>= 3.0.1.1) ruby-progressbar (1.11.0) + ruby2_keywords (0.0.5) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -48,19 +128,30 @@ GEM simplecov_json_formatter (0.1.3) terminal-table (3.0.1) unicode-display_width (>= 1.1.1, < 3) + thor (1.2.1) + twilio-ruby (5.66.2) + faraday (>= 0.9, < 2.0) + jwt (>= 1.5, <= 2.5) + nokogiri (>= 1.6, < 2.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) unicode-display_width (2.0.0) + zeitwerk (2.5.4) PLATFORMS ruby DEPENDENCIES + dotenv + dotenv-rails rspec rubocop (= 1.20) simplecov simplecov-console + twilio-ruby RUBY VERSION ruby 3.0.2p107 BUNDLED WITH - 2.2.26 + 2.3.12 diff --git a/README.md b/README.md index dbcb154e43..8a9b1f2726 100644 --- a/README.md +++ b/README.md @@ -14,20 +14,54 @@ Takeaway Challenge ``` -Instructions +Status -> Work in Progress ------- -* Feel free to use google, your notes, books, etc. but work on your own -* If you refer to the solution of another coach or student, please put a link to that in your README -* If you have a partial solution, **still check in a partial solution** -* You must submit a pull request to this repo with your code by 9am Monday morning +* takeaway currently doesn't allow dishes to be deleted from order -> needs to be updated +* takeaway currently doesn't check for availability -> needs to be updated +* sms function currently not working +* classes need restructuring +* tests need updating +* rafactoring needed + +Navigate +----- + +``` +laura@Lauras-Air takeaway-challenge % irb + => +# take.view_menu +["1. Mattar Paneer £12.5", + "2. Black Daal £7.5", + "3. Raita £3.5", + "4. Garlic Naan £3.5"] +3.0.2 :003 > take.add_dish(1, 1) + => ["1 x Mattar Paneer £12.5/each, has been added to your order -> £[12.5]"] +3.0.2 :004 > take.add_dish(2, 1) + => ["1 x Black Daal £7.5/each, has been added to your order -> £[7.5]"] +3.0.2 :005 > take.add_dish(4, 1) + => ["1 x Garlic Naan £3.5/each, has been added to your order -> £[3.5]"] +3.0.2 :006 > take.add_dish(4, 2) + => ["2 x Garlic Naan £3.5/each, has been added to your order -> £[7.0]"] +3.0.2 :007 > take.add_dish(2, 1) + => ["1 x Black Daal £7.5/each, has been added to your order -> £[7.5]"] +3.0.2 :008 > take.view_basket + => +["1 x Mattar Paneer £12.5/each -> £[12.5]", + "2 x Black Daal £7.5/each -> £15.0", + "3 x Garlic Naan £3.5/each -> £10.5"] +3.0.2 :009 > take.order_total + => "Your order total is £38.0" +``` Task ----- * Fork this repo * Run the command 'bundle' in the project directory to ensure you have all the gems -* Write a Takeaway program with the following user stories: +* Takeaway program with the following user stories: ``` As a customer @@ -45,39 +79,4 @@ I would like to check that the total I have been given matches the sum of the va As a customer So that I am reassured that my order will be delivered on time I would like to receive a text such as "Thank you! Your order was placed and will be delivered before 18:52" after I have ordered -``` - -* Hints on functionality to implement: - * Ensure you have a list of dishes with prices - * The text should state that the order was placed successfully and that it will be delivered 1 hour from now, e.g. "Thank you! Your order was placed and will be delivered before 18:52". - * The text sending functionality should be implemented using Twilio API. You'll need to register for it. It’s free. - * Use the twilio-ruby gem to access the API - * Use the Gemfile to manage your gems - * Make sure that your Takeaway is thoroughly tested and that you use mocks and/or stubs, as necessary to not to send texts when your tests are run - * However, if your Takeaway is loaded into IRB and the order is placed, the text should actually be sent - * Note that you can only send texts in the same country as you have your account. I.e. if you have a UK account you can only send to UK numbers. - -* Advanced! (have a go if you're feeling adventurous): - * Implement the ability to place orders via text message. - -* A free account on Twilio will only allow you to send texts to "verified" numbers. Use your mobile phone number, don't worry about the customer's mobile phone. - -> :warning: **WARNING:** think twice before you push your **mobile number** or **Twilio API Key** to a public space like GitHub :eyes: -> -> :key: Now is a great time to think about security and how you can keep your private information secret. You might want to explore environment variables. - -* Finally submit a pull request before Monday at 9am with your solution or partial solution. However much or little amount of code you wrote please please please submit a pull request before Monday at 9am - - -In code review we'll be hoping to see: - -* All tests passing -* High [Test coverage](https://github.com/makersacademy/course/blob/main/pills/test_coverage.md) (>95% is good) -* The code is elegant: every class has a clear responsibility, methods are short etc. - -Reviewers will potentially be using this [code review rubric](docs/review.md). Referring to this rubric in advance will make the challenge somewhat easier. You should be the judge of how much challenge you want this at this moment. - -Notes on Test Coverage ------------------- - -You can see your [test coverage](https://github.com/makersacademy/course/blob/main/pills/test_coverage.md) when you run your tests. +``` \ No newline at end of file diff --git a/lib/confirmation.rb b/lib/confirmation.rb new file mode 100644 index 0000000000..a48dfb120e --- /dev/null +++ b/lib/confirmation.rb @@ -0,0 +1,24 @@ +require 'twilio-ruby' +require 'dotenv' +Dotenv.load + +class Confirmation + + def initialize + @to = ENV['TO_PHONE_NUMBER'] + from = ENV['TWILIO_PHONE_NUMBER'] + end + + def send + account_sid = ENV['TWILIO_ACCOUNT_SID'] + auth_token = ENV['TWILIO_AUTH_TOKEN'] + client = Twilio::REST::Client.new(account_sid, auth_token) + + client.messages.create( + from: from, + to: @to, + body: "Thank you for your order" + ) + end + +end diff --git a/lib/menu.rb b/lib/menu.rb new file mode 100644 index 0000000000..cc2d97909b --- /dev/null +++ b/lib/menu.rb @@ -0,0 +1,26 @@ +class Menu + + attr_reader :menu + + def initialize + @menu = [ + { :item => 1, :dish => "Mattar Paneer", :price => 12.50 }, + { :item => 2, :dish => "Black Daal", :price => 7.50 }, + { :item => 3, :dish => "Raita", :price => 3.50 }, + { :item => 4, :dish => "Garlic Naan", :price => 3.50 } + ] + end + + def view + @menu.map { |dish| "#{dish[:item]}. #{dish[:dish]} £#{dish[:price]}" } + end + + def existing_dish(number, qty) + @menu.select { |dish| dish[:item] == number } != [] + end + + def selection(number, qty) + @menu.select { |dish| dish[:item] == number } + end + +end diff --git a/lib/order.rb b/lib/order.rb new file mode 100644 index 0000000000..287a5ec440 --- /dev/null +++ b/lib/order.rb @@ -0,0 +1,14 @@ +class Order + + DELIVERY = 2700 + + def initialize(time = Time.new) + @time = time + end + + def delivery_time + duration = @time + DELIVERY + duration.strftime("%H:%M") + end + +end diff --git a/lib/take_away.rb b/lib/take_away.rb new file mode 100644 index 0000000000..49a25c95b8 --- /dev/null +++ b/lib/take_away.rb @@ -0,0 +1,68 @@ +class TakeAway + + attr_reader :menu, :order, :take_away + + def initialize(menu = Menu.new, confirmation = Confirmation.new) + @confirmation = confirmation + @menu = menu + @order = [] + @take_away = 0 + @total = 0 + @current_dish = [] + end + + def view_menu + @menu.view + end + + def add_dish(number, qty) + fail "please enter a valid dish number" if @menu.existing_dish(number, qty) != true + if duplicate_dish(number, qty) == true + update_qty(number, qty) + else + @order << current_dish(number, qty) + end + dish_added_message(number, qty) + end + + def view_basket + @order.map { |dish| "#{dish[:qty]} x #{dish[:dish]} £#{dish[:price]}/each -> £#{dish[:subtotal]}" } + end + + def order_total + "Your order total is £#{total}" + end + + def place_order + @confirmation.send + end + + private + + def duplicate_dish(number, qty) + @order.select { |dish| dish[:item] == number } != [] + end + + def update_qty(number, qty) + @order.map { |dish| (dish[:qty] = (dish[:qty] + qty)) && (dish[:subtotal] = dish[:qty] * dish[:price]) if (dish[:item] == number) } + end + + def current_dish(number, qty) + (@menu.selection(number, qty).push(:qty => qty, :subtotal => sub_total(number, qty))).reduce(&:merge) + end + + def dish_added_message(number, qty) + (@menu.selection(number, qty)).map do |dish| + "#{qty} x #{dish[:dish]} £#{dish[:price]}/each, has been added to your order -> £#{(sub_total(number, qty))}" + end + end + + def sub_total(number, qty) + sub_total = @menu.selection(number, qty) + sub_total.map { |item| item[:price] * qty } + end + + def total + @order.map { |item| (item[:subtotal]) }.flatten.sum + end +end diff --git a/spec/confirmation_spec.rb b/spec/confirmation_spec.rb new file mode 100644 index 0000000000..f205a21e68 --- /dev/null +++ b/spec/confirmation_spec.rb @@ -0,0 +1,11 @@ +require 'confirmation' + +describe Confirmation do + + subject(:confirmation) { Confirmation.new } + + it "should create an instance of class Confirmation" do + expect(confirmation).to be_instance_of(Confirmation) + end + +end diff --git a/spec/menu_spec.rb b/spec/menu_spec.rb new file mode 100644 index 0000000000..07f709648a --- /dev/null +++ b/spec/menu_spec.rb @@ -0,0 +1,29 @@ +require 'menu' + +describe Menu do + + subject(:menu) { Menu.new } + + it "should create an instance of class Menu" do + expect(menu).to be_instance_of(Menu) + end + + describe "#view" do + it "should show menu options" do + expect(menu.view).to eq(["1. Mattar Paneer £12.5", "2. Black Daal £7.5", "3. Raita £3.5", "4. Garlic Naan £3.5"]) + end + end + + describe "#existing_dish" do + it "should return false if entered item is not part of menu" do + expect(menu.existing_dish(14, 1)).to eq(false) + end + end + + describe "#selection" do + it "should filter the menu for the selected dish and return it" do + expect(menu.selection(2, 1)).to eq([{ :item => 2, :dish => "Black Daal", :price => 7.5 }]) + end + end + +end diff --git a/spec/order_spec.rb b/spec/order_spec.rb new file mode 100644 index 0000000000..63592a84c5 --- /dev/null +++ b/spec/order_spec.rb @@ -0,0 +1,11 @@ +require 'order' + +describe Order do + + subject(:order) { Order.new } + + it "should create an instance of class Order" do + expect(order).to be_instance_of(Order) + end + +end diff --git a/spec/takeaway_spec.rb b/spec/takeaway_spec.rb new file mode 100644 index 0000000000..3d43c71fc2 --- /dev/null +++ b/spec/takeaway_spec.rb @@ -0,0 +1,69 @@ +require 'take_away' + +describe TakeAway do + let(:menu) { instance_double("Menu") } + let(:confirmation) { instance_double("Order") } + + # let(:d1) { { :item => 1, :dish => "Mattar Paneer", :price => 12.50 } } + # let(:d1) { { :item => 2, :dish => "Black Daal", :price => 7.50 } } + # let(:d1) { { :item => 3, :dish => "Raita", :price => 3.50 } } + # let(:d1) { { :item => 4, :dish => "Garlic Naan", :price => 3.50 } } + + subject(:take_away) { TakeAway.new(menu, confirmation) } + + it "should create an instance of class TakeAway" do + expect(take_away).to be_instance_of(TakeAway) + end + + describe "#view_menu" do + it "should instruct menu to return dishes" do + expect(menu).to receive(:view) + take_away.view_menu + end + end + + describe "#add_dish(item_no, qty)" do + it { is_expected.to respond_to(:add_dish).with(2).argument } + + xit "should fail if wrong item number entered" do + expect(take_away.add_dish(1, 1)).to raise_error "please enter a valid dish number" + end + + xit "should add the selected dish to the order" do + take_away.add_dish(2, 1) + expect(take_away.order).to eq([{ :dish_order => [{ :item => 2, :dish => "Black Daal", :price => 7.5 }], :qty => 1, :line_price => [7.50] }]) + end + end + + describe "#view_basket" do + xit "should return all items added to basket, with duplicate items qty added" do + take_away.add_dish(1, 1) + take_away.add_dish(1, 1) + expect(take_away.basket).to eq(["2 x Mattar Paneer £12.5/each -> £25.0"]) + end + end + + describe "#order" do + xit "should show the order total" do + take_away.add_dish(1, 1) + take_away.add_dish(1, 1) + expect(take_away.order_total).to eq("Your order total is £25.0") + end + end + + describe "#total" do + xit { is_expected.to respond_to(:total) } + + xit "should add up all sub-totals and return total" do + take_away.add_dish(2, 2) + take_away.add_dish(1, 3) + take_away.add_dish(4, 1) + expect(take_away.total).to eq(56.0) + end + end + + describe "#place_order" do + xit { is_expected.to respond_to(:place_order) } + end + +end