From 9b63049135c7dda0d345f937791743e4931ee576 Mon Sep 17 00:00:00 2001 From: "A.J. Krumholz" Date: Wed, 26 Oct 2022 11:49:56 -0400 Subject: [PATCH 1/3] initial commit --- Gemfile.lock | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9c8920e9..d3c6dd30 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -199,6 +199,7 @@ GEM PLATFORMS arm64-darwin-20 + arm64-darwin-21 DEPENDENCIES bootsnap (>= 1.1.0) @@ -224,4 +225,4 @@ RUBY VERSION ruby 2.7.4p191 BUNDLED WITH - 2.2.27 + 2.3.20 From fa3f6ff8c6b83972d5dc10d8244f5ba3166c0613 Mon Sep 17 00:00:00 2001 From: "A.J. Krumholz" Date: Wed, 26 Oct 2022 13:53:51 -0400 Subject: [PATCH 2/3] user story 1 --- Gemfile | 10 ++++++++- Gemfile.lock | 30 ++++++++++++++++++++----- app/controllers/merchants_controller.rb | 9 ++++++++ app/facades/merchants_facade.rb | 18 +++++++++++++++ app/poros/item.rb | 15 +++++++++++++ app/poros/merchant.rb | 9 ++++++++ app/services/merchants_service.rb | 22 ++++++++++++++++++ app/views/merchants/index.html.erb | 9 ++++++++ app/views/merchants/show.html.erb | 13 +++++++++++ config/routes.rb | 2 +- db/schema.rb | 18 +++++++++++++++ spec/factories/item.rb | 7 ++++++ spec/factories/merchants.rb | 11 +++++++++ spec/features/merchants/index_spec.rb | 29 ++++++++++++++++++++++++ spec/rails_helper.rb | 8 +++++++ spec/spec_helper.rb | 2 ++ 16 files changed, 205 insertions(+), 7 deletions(-) create mode 100644 app/controllers/merchants_controller.rb create mode 100644 app/facades/merchants_facade.rb create mode 100644 app/poros/item.rb create mode 100644 app/poros/merchant.rb create mode 100644 app/services/merchants_service.rb create mode 100644 app/views/merchants/index.html.erb create mode 100644 app/views/merchants/show.html.erb create mode 100644 db/schema.rb create mode 100644 spec/factories/item.rb create mode 100644 spec/factories/merchants.rb create mode 100644 spec/features/merchants/index_spec.rb diff --git a/Gemfile b/Gemfile index d4fbf7c0..a81e5a36 100644 --- a/Gemfile +++ b/Gemfile @@ -35,15 +35,23 @@ gem 'jbuilder', '~> 2.5' # Reduces boot times through caching; required in config/boot.rb gem 'bootsnap', '>= 1.1.0', require: false +gem 'faraday' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'rspec-rails' - gem 'capybara' gem 'pry' end +group :test do + gem 'vcr' + gem 'faker' + gem 'factory_bot_rails' + gem 'shoulda-matchers' + gem 'capybara' +end + group :development do # Access an interactive console on exception pages or by calling 'console' anywhere in the code. gem 'web-console', '>= 3.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index d3c6dd30..0e47dd3f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -42,15 +42,15 @@ GEM i18n (>= 0.7, < 2) minitest (~> 5.1) tzinfo (~> 1.1) - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) + addressable (2.8.1) + public_suffix (>= 2.0.2, < 6.0) arel (9.0.0) bindex (0.8.1) bootsnap (1.9.1) msgpack (~> 1.0) builder (3.2.4) byebug (11.1.3) - capybara (3.36.0) + capybara (3.37.1) addressable matrix mini_mime (>= 0.1.3) @@ -72,6 +72,17 @@ GEM diff-lcs (1.4.4) erubi (1.10.0) execjs (2.8.1) + factory_bot (6.2.1) + activesupport (>= 5.0.0) + factory_bot_rails (6.2.0) + factory_bot (~> 6.2.0) + railties (>= 5.0.0) + faker (2.19.0) + i18n (>= 1.6, < 2) + faraday (2.6.0) + faraday-net_http (>= 2.0, < 3.1) + ruby2_keywords (>= 0.0.4) + faraday-net_http (3.0.1) ffi (1.15.4) globalid (0.5.2) activesupport (>= 5.0) @@ -101,7 +112,7 @@ GEM pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) - public_suffix (4.0.6) + public_suffix (5.0.0) puma (3.12.6) racc (1.6.0) rack (2.2.3) @@ -135,7 +146,7 @@ GEM rb-fsevent (0.11.0) rb-inotify (0.10.1) ffi (~> 1.0) - regexp_parser (2.1.1) + regexp_parser (2.6.0) rspec-core (3.10.1) rspec-support (~> 3.10.0) rspec-expectations (3.10.1) @@ -153,6 +164,7 @@ GEM rspec-mocks (~> 3.10) rspec-support (~> 3.10) rspec-support (3.10.2) + ruby2_keywords (0.0.5) ruby_dep (1.5.0) sass (3.7.4) sass-listen (~> 4.0.0) @@ -165,6 +177,8 @@ GEM sprockets (>= 2.8, < 4.0) sprockets-rails (>= 2.0, < 4.0) tilt (>= 1.1, < 3) + shoulda-matchers (5.2.0) + activesupport (>= 5.2.0) spring (2.1.1) spring-watcher-listen (2.0.1) listen (>= 2.7, < 4.0) @@ -186,6 +200,7 @@ GEM thread_safe (~> 0.1) uglifier (4.2.0) execjs (>= 0.3.0, < 3) + vcr (6.1.0) web-console (3.7.0) actionview (>= 5.0) activemodel (>= 5.0) @@ -206,6 +221,9 @@ DEPENDENCIES byebug capybara coffee-rails (~> 4.2) + factory_bot_rails + faker + faraday jbuilder (~> 2.5) listen (>= 3.0.5, < 3.2) pg (>= 0.18, < 2.0) @@ -214,11 +232,13 @@ DEPENDENCIES rails (~> 5.2.6) rspec-rails sass-rails (~> 5.0) + shoulda-matchers spring spring-watcher-listen (~> 2.0.0) turbolinks (~> 5) tzinfo-data uglifier (>= 1.3.0) + vcr web-console (>= 3.3.0) RUBY VERSION diff --git a/app/controllers/merchants_controller.rb b/app/controllers/merchants_controller.rb new file mode 100644 index 00000000..302f03b3 --- /dev/null +++ b/app/controllers/merchants_controller.rb @@ -0,0 +1,9 @@ +class MerchantsController < ApplicationController + def index + @merchants = MerchantsFacade.merchants + end + + def show + @merchant = MerchantsFacade.merchant(params[:id]) + end +end \ No newline at end of file diff --git a/app/facades/merchants_facade.rb b/app/facades/merchants_facade.rb new file mode 100644 index 00000000..c1db98f5 --- /dev/null +++ b/app/facades/merchants_facade.rb @@ -0,0 +1,18 @@ +class MerchantsFacade + def self.merchants + merchants_hash = MerchantsService.merchants[:data] + merchants_hash.map { |merchant_info| Merchant.new(merchant_info)} + end + + def self.merchant(id) + merchant_hash = MerchantsService.merchant(id)[:data] + Merchant.new(merchant_hash, merchant_items(id)) + end + + private + + def self.merchant_items(merchant_id) + item_hash = MerchantsService.merchant_items(merchant_id)[:data] + item_hash.map { |item_info| Item.new(item_info) } + end +end \ No newline at end of file diff --git a/app/poros/item.rb b/app/poros/item.rb new file mode 100644 index 00000000..77b138d0 --- /dev/null +++ b/app/poros/item.rb @@ -0,0 +1,15 @@ +class Item + attr_reader :id, + :name, + :description, + :unit_price, + :merchant_id + + def initialize(data) + @id = data[:id] + @name = data[:attributes][:name] + @description = data[:attributes][:description] + @unit_price = data[:attributes][:unit_price] + @merchant_id = data[:attributes][:merchant_id] + end +end \ No newline at end of file diff --git a/app/poros/merchant.rb b/app/poros/merchant.rb new file mode 100644 index 00000000..b2c0060e --- /dev/null +++ b/app/poros/merchant.rb @@ -0,0 +1,9 @@ +class Merchant + attr_reader :name, :id, :items + + def initialize(data, items = []) + @name = data[:attributes][:name] + @id = data[:id] + @items = items + end +end \ No newline at end of file diff --git a/app/services/merchants_service.rb b/app/services/merchants_service.rb new file mode 100644 index 00000000..ea36482f --- /dev/null +++ b/app/services/merchants_service.rb @@ -0,0 +1,22 @@ +require 'faraday' +require 'json' + +class MerchantsService + def self.merchants + conn = Faraday.new(url: 'http://localhost:3000/api/v1/merchants') + response = conn.get + JSON.parse(response.body, symbolize_names: true) + end + + def self.merchant(id) + conn = Faraday.new(url: "http://localhost:3000/api/v1/merchants/#{id}") + response = conn.get + JSON.parse(response.body, symbolize_names: true) + end + + def self.merchant_items(id) + conn = Faraday.new(url: "http://localhost:3000/api/v1/merchants/#{id}/items") + response = conn.get + JSON.parse(response.body, symbolize_names: true) + end +end \ No newline at end of file diff --git a/app/views/merchants/index.html.erb b/app/views/merchants/index.html.erb new file mode 100644 index 00000000..47c4a888 --- /dev/null +++ b/app/views/merchants/index.html.erb @@ -0,0 +1,9 @@ +

merchants index

+ + \ No newline at end of file diff --git a/app/views/merchants/show.html.erb b/app/views/merchants/show.html.erb new file mode 100644 index 00000000..e31ba7d5 --- /dev/null +++ b/app/views/merchants/show.html.erb @@ -0,0 +1,13 @@ +

merchant show page

+ +

<%= @merchant.name %>

+ + \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 787824f8..ffaa495e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,3 @@ Rails.application.routes.draw do - # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + resources :merchants, only: %i[index show] end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 00000000..2611543b --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,18 @@ +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 0) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + +end diff --git a/spec/factories/item.rb b/spec/factories/item.rb new file mode 100644 index 00000000..757637b7 --- /dev/null +++ b/spec/factories/item.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :item do + name {Faker::Commerce.product_name} + description {Faker::Marketing.buzzwords} + unit_price {Faker::Number.decimal(l_digits: 2, r_digits: 2)} + end +end \ No newline at end of file diff --git a/spec/factories/merchants.rb b/spec/factories/merchants.rb new file mode 100644 index 00000000..031717bd --- /dev/null +++ b/spec/factories/merchants.rb @@ -0,0 +1,11 @@ +FactoryBot.define do + factory :merchant do + name { Faker::Company.name } + end + + def merchant_with_items(item_count = 10) + FactoryBot.create(:merchant) do |merchant| + FactoryBot.create_list(:item, item_count, merchant: merchant) + end + end +end \ No newline at end of file diff --git a/spec/features/merchants/index_spec.rb b/spec/features/merchants/index_spec.rb new file mode 100644 index 00000000..1efa59cc --- /dev/null +++ b/spec/features/merchants/index_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +RSpec.describe '/merchants' do + let!(:merchants) { MerchantsFacade.merchants } + let!(:merchant_1) { MerchantsFacade.merchant(merchants.first.id) } + let!(:merchant_2) { MerchantsFacade.merchant(merchants.third.id) } + + + before { visit merchants_path } + + it 'displays a list of merchants by name' do + merchants.each do |merchant| + expect(page).to have_content(merchant.name) + end + end + + it 'links to each merchant show page' do + click_on merchant_1.name + + expect(current_path).to eq(merchant_path(merchant_1.id)) + + expect(page).to have_content(merchant_1.name) + expect(page).not_to have_content(merchant_2.name) + + merchant_1.items.each do |item| + expect(page).to have_content(item.name) + end + end +end \ No newline at end of file diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 00345af7..d26bf53f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -5,6 +5,7 @@ # Prevent database truncation if the environment is production abort("The Rails environment is running in production mode!") if Rails.env.production? require 'rspec/rails' +require 'capybara/rails' # Add additional requires below this line. Rails is not loaded until this point! # Requires supporting ruby files with custom matchers and macros, etc, in @@ -62,3 +63,10 @@ # arbitrary gems may also be filtered via: # config.filter_gems_from_backtrace("gem name") end + +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ce33d66d..ae2c87b3 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,6 +11,8 @@ # a separate helper file that requires the additional dependencies and performs # the additional setup, and require it from the spec files that actually need # it. +require 'capybara/rspec' + # # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration RSpec.configure do |config| From ead31cbf09a7a0dac60680deeee22a46eaa33444 Mon Sep 17 00:00:00 2001 From: "A.J. Krumholz" Date: Thu, 27 Oct 2022 11:44:17 -0400 Subject: [PATCH 3/3] feat add items and search views --- Gemfile | 1 + Gemfile.lock | 3 +++ app/controllers/items_controller.rb | 9 ++++++++ app/controllers/welcome_controller.rb | 7 ++++++ app/facades/items_facade.rb | 11 ++++++++++ app/facades/merchants_facade.rb | 6 ++++++ app/services/items_service.rb | 13 +++++++++++ app/services/merchants_service.rb | 6 ++++++ app/views/items/index.html.erb | 9 ++++++++ app/views/items/show.html.erb | 4 ++++ app/views/welcome/index.html.erb | 16 ++++++++++++++ config/routes.rb | 2 ++ spec/features/items/index_spec.rb | 31 +++++++++++++++++++++++++++ spec/features/welcome/index_spec.rb | 26 ++++++++++++++++++++++ 14 files changed, 144 insertions(+) create mode 100644 app/controllers/items_controller.rb create mode 100644 app/controllers/welcome_controller.rb create mode 100644 app/facades/items_facade.rb create mode 100644 app/services/items_service.rb create mode 100644 app/views/items/index.html.erb create mode 100644 app/views/items/show.html.erb create mode 100644 app/views/welcome/index.html.erb create mode 100644 spec/features/items/index_spec.rb create mode 100644 spec/features/welcome/index_spec.rb diff --git a/Gemfile b/Gemfile index a81e5a36..b2ce5d21 100644 --- a/Gemfile +++ b/Gemfile @@ -50,6 +50,7 @@ group :test do gem 'factory_bot_rails' gem 'shoulda-matchers' gem 'capybara' + gem 'launchy' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index 0e47dd3f..391319a0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -90,6 +90,8 @@ GEM concurrent-ruby (~> 1.0) jbuilder (2.11.2) activesupport (>= 5.0.0) + launchy (2.5.0) + addressable (~> 2.7) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -225,6 +227,7 @@ DEPENDENCIES faker faraday jbuilder (~> 2.5) + launchy listen (>= 3.0.5, < 3.2) pg (>= 0.18, < 2.0) pry diff --git a/app/controllers/items_controller.rb b/app/controllers/items_controller.rb new file mode 100644 index 00000000..c4f65326 --- /dev/null +++ b/app/controllers/items_controller.rb @@ -0,0 +1,9 @@ +class ItemsController < ApplicationController + def index + @items = ItemsFacade.items + end + + def show + @item = ItemsFacade.item(params[:id]) + end +end \ No newline at end of file diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb new file mode 100644 index 00000000..42b76725 --- /dev/null +++ b/app/controllers/welcome_controller.rb @@ -0,0 +1,7 @@ +class WelcomeController < ApplicationController + def index + if params[:search].present? + @merchant = MerchantsFacade.find(params[:search]) + end + end +end \ No newline at end of file diff --git a/app/facades/items_facade.rb b/app/facades/items_facade.rb new file mode 100644 index 00000000..f02a71e6 --- /dev/null +++ b/app/facades/items_facade.rb @@ -0,0 +1,11 @@ +class ItemsFacade + def self.items + items_hash = ItemsService.items[:data] + items_hash.map { |item_info| Item.new(item_info)} + end + + def self.item(id) + item_info = ItemsService.item(id)[:data] + Item.new(item_info) + end +end \ No newline at end of file diff --git a/app/facades/merchants_facade.rb b/app/facades/merchants_facade.rb index c1db98f5..dc171b5c 100644 --- a/app/facades/merchants_facade.rb +++ b/app/facades/merchants_facade.rb @@ -9,6 +9,12 @@ def self.merchant(id) Merchant.new(merchant_hash, merchant_items(id)) end + def self.find(query) + merchant_hash = MerchantsService.find(query)[:data] + id = merchant_hash[:id] + Merchant.new(merchant_hash, merchant_items(id)) + end + private def self.merchant_items(merchant_id) diff --git a/app/services/items_service.rb b/app/services/items_service.rb new file mode 100644 index 00000000..c5d64a9a --- /dev/null +++ b/app/services/items_service.rb @@ -0,0 +1,13 @@ +class ItemsService + def self.items + conn = Faraday.new(url: 'http://localhost:3000/api/v1/items') + response = conn.get + JSON.parse(response.body, symbolize_names: true) + end + + def self.item(id) + conn = Faraday.new(url: "http://localhost:3000/api/v1/items/#{id}") + response = conn.get + JSON.parse(response.body, symbolize_names: true) + end +end \ No newline at end of file diff --git a/app/services/merchants_service.rb b/app/services/merchants_service.rb index ea36482f..d6ff3e58 100644 --- a/app/services/merchants_service.rb +++ b/app/services/merchants_service.rb @@ -19,4 +19,10 @@ def self.merchant_items(id) response = conn.get JSON.parse(response.body, symbolize_names: true) end + + def self.find(query) + conn = Faraday.new(url: "http://localhost:3000/api/v1/merchants/find?name=#{query}") + response = conn.get + JSON.parse(response.body, symbolize_names: true) + end end \ No newline at end of file diff --git a/app/views/items/index.html.erb b/app/views/items/index.html.erb new file mode 100644 index 00000000..b5e8b2da --- /dev/null +++ b/app/views/items/index.html.erb @@ -0,0 +1,9 @@ +

All Items

+ +
    + <% @items.each do |item| %> +
  • + <%= link_to item.name, item_path(item.id) %> +
  • + <% end %> +
\ No newline at end of file diff --git a/app/views/items/show.html.erb b/app/views/items/show.html.erb new file mode 100644 index 00000000..28b84509 --- /dev/null +++ b/app/views/items/show.html.erb @@ -0,0 +1,4 @@ +

<%= @item.name %>

+ +

<%= @item.description %>

+

Price: <%= @item.unit_price %>

diff --git a/app/views/welcome/index.html.erb b/app/views/welcome/index.html.erb new file mode 100644 index 00000000..8bfe0a65 --- /dev/null +++ b/app/views/welcome/index.html.erb @@ -0,0 +1,16 @@ +
+ <%= form_with url: root_path, method: :get do |f| %> + <%= f.text_field :search %> + <%= f.submit "Search Merchants by Name" %> + <% end %> +
+ +<% if !@merchant.nil? %> +
+
    +
  • + <%= link_to @merchant.name, merchant_path(@merchant.id) %> +
  • +
+
+<% end %> \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index ffaa495e..f282a010 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,5 @@ Rails.application.routes.draw do + root 'welcome#index' resources :merchants, only: %i[index show] + resources :items, only: %i[index show] end diff --git a/spec/features/items/index_spec.rb b/spec/features/items/index_spec.rb new file mode 100644 index 00000000..c3d6d0ec --- /dev/null +++ b/spec/features/items/index_spec.rb @@ -0,0 +1,31 @@ +require 'rails_helper' + +RSpec.describe '/items' do + describe 'when I visit the items index' do + let(:items) { ItemsFacade.items } + before { visit '/items' } + + it 'has a title' do + expect(page).to have_content("All Items") + end + + it 'displays all items in the db' do + item_1 = items.sample + item_2 = items.sample + + expect(page).to have_content(item_1.name) + expect(page).to have_content(item_2.name) + end + + it 'links to each items show page' do + item = items.sample + + click_on item.name + + expect(current_path).to eq(item_path(item.id)) + expect(page).to have_content(item.name) + expect(page).to have_content(item.description) + expect(page).to have_content(item.unit_price) + end + end +end \ No newline at end of file diff --git a/spec/features/welcome/index_spec.rb b/spec/features/welcome/index_spec.rb new file mode 100644 index 00000000..def8734c --- /dev/null +++ b/spec/features/welcome/index_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +RSpec.describe '/' do + describe 'when I visit the welcome page' do + let!(:merchants) { MerchantsFacade.merchants } + before { visit root_path } + + it 'shows a search form to find a merchant by name' do + expect(page).to have_field(:search) + end + + it 'can search for merchants' do + merchant = merchants.sample + + fill_in :search, with: merchant.name + click_on "Search Merchants by Name" + + expect(page).to have_content(merchant.name) + save_and_open_page + + click_on merchant.name + + expect(current_path).to eq(merchant_path(merchant.id)) + end + end +end \ No newline at end of file