From b3472f21ef436044a5e62c367d613840d8b50541 Mon Sep 17 00:00:00 2001 From: Melchor De La Rosa Date: Thu, 19 Sep 2024 21:21:32 -0400 Subject: [PATCH 1/8] Finished Setting up my coupons model and generating my schema and contoller and validations and shoulda matchers --- .../api/v1/merchants/coupons_controller.rb | 2 + app/models/coupon.rb | 6 ++ app/models/invoice.rb | 1 + config/routes.rb | 1 + db/migrate/20240919204137_create_coupons.rb | 14 ++++ .../20240919204828_add_coupon_to_invoices.rb | 5 ++ db/schema.rb | 77 ++++++++++++++++++- spec/factories/coupons.rb | 9 +++ spec/models/coupon_spec.rb | 37 +++++++++ spec/models/invoice_spec.rb | 3 + spec/requests/coupons_spec.rb | 13 ++++ 11 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 app/controllers/api/v1/merchants/coupons_controller.rb create mode 100644 app/models/coupon.rb create mode 100644 db/migrate/20240919204137_create_coupons.rb create mode 100644 db/migrate/20240919204828_add_coupon_to_invoices.rb create mode 100644 spec/factories/coupons.rb create mode 100644 spec/models/coupon_spec.rb create mode 100644 spec/requests/coupons_spec.rb diff --git a/app/controllers/api/v1/merchants/coupons_controller.rb b/app/controllers/api/v1/merchants/coupons_controller.rb new file mode 100644 index 0000000..d11eb81 --- /dev/null +++ b/app/controllers/api/v1/merchants/coupons_controller.rb @@ -0,0 +1,2 @@ +class API::V1::Merchants::CouponsController < ApplicationController +end diff --git a/app/models/coupon.rb b/app/models/coupon.rb new file mode 100644 index 0000000..08d5356 --- /dev/null +++ b/app/models/coupon.rb @@ -0,0 +1,6 @@ +class Coupon < ApplicationRecord + belongs_to :merchant + has_many :invoices + validates :code, uniqueness: true + validates :name, :code, presence: true +end diff --git a/app/models/invoice.rb b/app/models/invoice.rb index e00462e..fb65f24 100644 --- a/app/models/invoice.rb +++ b/app/models/invoice.rb @@ -1,6 +1,7 @@ class Invoice < ApplicationRecord belongs_to :customer belongs_to :merchant + belongs_to :coupon, optional: true has_many :invoice_items, dependent: :destroy has_many :transactions, dependent: :destroy diff --git a/config/routes.rb b/config/routes.rb index b1a302b..ddd41cb 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,6 +24,7 @@ resources :items, only: :index, controller: "merchants/items" resources :customers, only: :index, controller: "merchants/customers" resources :invoices, only: :index, controller: "merchants/invoices" + resources :coupons, only: :index, controller: "merchants/coupons" end end end diff --git a/db/migrate/20240919204137_create_coupons.rb b/db/migrate/20240919204137_create_coupons.rb new file mode 100644 index 0000000..c8ba581 --- /dev/null +++ b/db/migrate/20240919204137_create_coupons.rb @@ -0,0 +1,14 @@ +class CreateCoupons < ActiveRecord::Migration[7.1] + def change + create_table :coupons do |t| + t.string :name + t.string :code + t.decimal :discount_value + t.boolean :active + t.references :merchant, null: false, foreign_key: true + + t.timestamps + end + add_index :coupons, :code, unique: true + end +end diff --git a/db/migrate/20240919204828_add_coupon_to_invoices.rb b/db/migrate/20240919204828_add_coupon_to_invoices.rb new file mode 100644 index 0000000..77a2ad2 --- /dev/null +++ b/db/migrate/20240919204828_add_coupon_to_invoices.rb @@ -0,0 +1,5 @@ +class AddCouponToInvoices < ActiveRecord::Migration[7.1] + def change + add_reference :invoices, :coupon, null: true, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index f913998..bb950c2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,8 +10,83 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 0) do +ActiveRecord::Schema[7.1].define(version: 2024_09_19_204828) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "coupons", force: :cascade do |t| + t.string "name" + t.string "code" + t.decimal "discount_value" + t.boolean "active" + t.bigint "merchant_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["merchant_id"], name: "index_coupons_on_merchant_id" + end + + create_table "customers", force: :cascade do |t| + t.string "first_name" + t.string "last_name" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "invoice_items", force: :cascade do |t| + t.bigint "item_id" + t.bigint "invoice_id" + t.integer "quantity" + t.float "unit_price" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["invoice_id"], name: "index_invoice_items_on_invoice_id" + t.index ["item_id"], name: "index_invoice_items_on_item_id" + end + + create_table "invoices", force: :cascade do |t| + t.bigint "customer_id" + t.bigint "merchant_id" + t.string "status" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.bigint "coupon_id" + t.index ["coupon_id"], name: "index_invoices_on_coupon_id" + t.index ["customer_id"], name: "index_invoices_on_customer_id" + t.index ["merchant_id"], name: "index_invoices_on_merchant_id" + end + + create_table "items", force: :cascade do |t| + t.string "name" + t.string "description" + t.float "unit_price" + t.bigint "merchant_id" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["merchant_id"], name: "index_items_on_merchant_id" + end + + create_table "merchants", force: :cascade do |t| + t.string "name" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + end + + create_table "transactions", force: :cascade do |t| + t.bigint "invoice_id" + t.string "credit_card_number" + t.string "credit_card_expiration_date" + t.string "result" + t.datetime "created_at", precision: nil, null: false + t.datetime "updated_at", precision: nil, null: false + t.index ["invoice_id"], name: "index_transactions_on_invoice_id" + end + + add_foreign_key "coupons", "merchants" + add_foreign_key "invoice_items", "invoices" + add_foreign_key "invoice_items", "items" + add_foreign_key "invoices", "coupons" + add_foreign_key "invoices", "customers" + add_foreign_key "invoices", "merchants" + add_foreign_key "items", "merchants" + add_foreign_key "transactions", "invoices" end diff --git a/spec/factories/coupons.rb b/spec/factories/coupons.rb new file mode 100644 index 0000000..0770573 --- /dev/null +++ b/spec/factories/coupons.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :coupon do + name { "MyString" } + code { "MyString" } + discount_value { "9.99" } + active { false } + merchant + end +end diff --git a/spec/models/coupon_spec.rb b/spec/models/coupon_spec.rb new file mode 100644 index 0000000..ec1144b --- /dev/null +++ b/spec/models/coupon_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +RSpec.describe Coupon, type: :model do + before(:each) do + @merchant = Merchant.create!(name: "Sample Merchant") + end + describe 'relationships' do + it {should belong_to(:merchant)} + it {should have_many(:invoices)} + end + + describe 'validations' do + it 'is valid with valid attributes' do + coupon = Coupon.new(name: "Sample Coupon", code: "COUPON001", merchant: @merchant) + expect(coupon).to be_valid + end + + it 'is not valid without a name' do + coupon = Coupon.new(name: nil, code: "COUPON001", merchant: @merchant) + expect(coupon).to_not be_valid + expect(coupon.errors[:name]).to include("can't be blank") + end + + it 'is not valid without a code' do + coupon = Coupon.new(name: "Sample Coupon", code: nil, merchant: @merchant) + expect(coupon).to_not be_valid + expect(coupon.errors[:code]).to include("can't be blank") + end + + it 'is not valid with a duplicate code for the same merchant' do + Coupon.create!(name: "Existing Coupon", code: "COUPON001", merchant: @merchant) + coupon = Coupon.new(name: "New Coupon", code: "COUPON001", merchant: @merchant) + expect(coupon).to_not be_valid + expect(coupon.errors[:code]).to include("has already been taken") + end + end +end diff --git a/spec/models/invoice_spec.rb b/spec/models/invoice_spec.rb index 8b08005..a0268e7 100644 --- a/spec/models/invoice_spec.rb +++ b/spec/models/invoice_spec.rb @@ -3,5 +3,8 @@ RSpec.describe Invoice do it { should belong_to :merchant } it { should belong_to :customer } + it { should belong_to(:coupon).optional} + it { should have_many(:invoice_items).dependent(:destroy) } + it { should have_many(:transactions).dependent(:destroy) } it { should validate_inclusion_of(:status).in_array(%w(shipped packaged returned)) } end \ No newline at end of file diff --git a/spec/requests/coupons_spec.rb b/spec/requests/coupons_spec.rb new file mode 100644 index 0000000..298ed72 --- /dev/null +++ b/spec/requests/coupons_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +RSpec.describe "Coupons", type: :request do + describe "GET /show" do + it 'return one coupon by id' do + merchant = Merchant.create!(id: 1, name: "Sample Merchant") + coupon = Coupon.create!(name: "Buy One Get One 50", code: "BOGO50", discount_value: 50, active: true, merchant_id: merchant.id) + + get "/api/v1/merchants/#{merchant.id}/coupons/#{coupon.id}" + + end + end +end From 219eef29754c4349e5b14bcec7a6987eefca3d35 Mon Sep 17 00:00:00 2001 From: Melchor De La Rosa Date: Thu, 19 Sep 2024 22:15:40 -0400 Subject: [PATCH 2/8] Added test for happy/sad path for coupon show and added show action --- .../api/v1/merchants/coupons_controller.rb | 7 +++++- app/serializers/coupon_serializer.rb | 4 +++ config/routes.rb | 2 +- db/schema.rb | 1 + spec/requests/coupons_spec.rb | 25 +++++++++++++++++++ 5 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 app/serializers/coupon_serializer.rb diff --git a/app/controllers/api/v1/merchants/coupons_controller.rb b/app/controllers/api/v1/merchants/coupons_controller.rb index d11eb81..394bb90 100644 --- a/app/controllers/api/v1/merchants/coupons_controller.rb +++ b/app/controllers/api/v1/merchants/coupons_controller.rb @@ -1,2 +1,7 @@ -class API::V1::Merchants::CouponsController < ApplicationController +class Api::V1::Merchants::CouponsController < ApplicationController + + def show + coupon = Coupon.find(params[:id]) + render json: CouponSerializer.new(coupon) + end end diff --git a/app/serializers/coupon_serializer.rb b/app/serializers/coupon_serializer.rb new file mode 100644 index 0000000..28cba70 --- /dev/null +++ b/app/serializers/coupon_serializer.rb @@ -0,0 +1,4 @@ +class CouponSerializer + include JSONAPI::Serializer + attributes :id, :name, :code, :discount_value, :active, :merchant_id +end diff --git a/config/routes.rb b/config/routes.rb index ddd41cb..df8b2ec 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,7 +24,7 @@ resources :items, only: :index, controller: "merchants/items" resources :customers, only: :index, controller: "merchants/customers" resources :invoices, only: :index, controller: "merchants/invoices" - resources :coupons, only: :index, controller: "merchants/coupons" + resources :coupons, only: [:index, :show], controller: "merchants/coupons" end end end diff --git a/db/schema.rb b/db/schema.rb index bb950c2..9420aca 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -22,6 +22,7 @@ t.bigint "merchant_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.index ["code"], name: "index_coupons_on_code", unique: true t.index ["merchant_id"], name: "index_coupons_on_merchant_id" end diff --git a/spec/requests/coupons_spec.rb b/spec/requests/coupons_spec.rb index 298ed72..c9aa5e1 100644 --- a/spec/requests/coupons_spec.rb +++ b/spec/requests/coupons_spec.rb @@ -7,7 +7,32 @@ coupon = Coupon.create!(name: "Buy One Get One 50", code: "BOGO50", discount_value: 50, active: true, merchant_id: merchant.id) get "/api/v1/merchants/#{merchant.id}/coupons/#{coupon.id}" + + expect(response).to have_http_status(200) + expect(response).to be_successful + + json_response = JSON.parse(response.body) + expect(json_response['data']['id']).to eq(coupon.id.to_s) + expect(json_response['data']['type']).to eq('coupon') + expect(json_response['data']['attributes']['name']).to eq(coupon.name) + expect(json_response['data']['attributes']['code']).to eq(coupon.code) + expect(json_response['data']['attributes']['discount_value']).to be_a(String) + expect(json_response['data']['attributes']['active']).to eq(coupon.active?) + expect(json_response['data']['attributes']['merchant_id']).to eq(coupon.merchant_id) + end + + it 'returns a 404 when a coupon does not exist' do + merchant = Merchant.create!(id: 1, name: "Sample Merchant") + + get "/api/v1/merchants/#{merchant.id}/coupons/9999" + + expect(response).to have_http_status(404) + json_response = JSON.parse(response.body) + + + expect(json_response).to include("message" => "Your query could not be completed") + expect(json_response['errors']).to include("Couldn't find Coupon with 'id'=9999") end end end From 6f639074729972d1d50814ac59e04e32c7e4706e Mon Sep 17 00:00:00 2001 From: Melchor De La Rosa Date: Fri, 20 Sep 2024 10:11:38 -0400 Subject: [PATCH 3/8] Added test for happ/sad path coupon index, and action for coupon contoller --- .../api/v1/merchants/coupons_controller.rb | 6 ++ app/models/merchant.rb | 1 + spec/models/merchant_spec.rb | 1 + .../api/v1/merchant/coupons_request_spec.rb | 83 +++++++++++++++++++ spec/requests/coupons_spec.rb | 38 --------- 5 files changed, 91 insertions(+), 38 deletions(-) create mode 100644 spec/requests/api/v1/merchant/coupons_request_spec.rb delete mode 100644 spec/requests/coupons_spec.rb diff --git a/app/controllers/api/v1/merchants/coupons_controller.rb b/app/controllers/api/v1/merchants/coupons_controller.rb index 394bb90..cfe87ff 100644 --- a/app/controllers/api/v1/merchants/coupons_controller.rb +++ b/app/controllers/api/v1/merchants/coupons_controller.rb @@ -4,4 +4,10 @@ def show coupon = Coupon.find(params[:id]) render json: CouponSerializer.new(coupon) end + + def index + merchant = Merchant.find(params[:merchant_id]) + coupons = merchant.coupons + render json: CouponSerializer.new(coupons) + end end diff --git a/app/models/merchant.rb b/app/models/merchant.rb index c1ff568..6fbe2ad 100644 --- a/app/models/merchant.rb +++ b/app/models/merchant.rb @@ -3,6 +3,7 @@ class Merchant < ApplicationRecord has_many :items, dependent: :destroy has_many :invoices, dependent: :destroy has_many :customers, through: :invoices + has_many :coupons # has_many :invoice_items, through: :invoices # has_many :transactions, through: :invoices diff --git a/spec/models/merchant_spec.rb b/spec/models/merchant_spec.rb index 76ffb70..cd4b194 100644 --- a/spec/models/merchant_spec.rb +++ b/spec/models/merchant_spec.rb @@ -9,6 +9,7 @@ it { should have_many :items } it { should have_many :invoices } it { should have_many(:customers).through(:invoices) } + it { should have_many(:coupons)} end describe "class methods" do diff --git a/spec/requests/api/v1/merchant/coupons_request_spec.rb b/spec/requests/api/v1/merchant/coupons_request_spec.rb new file mode 100644 index 0000000..dd4dc79 --- /dev/null +++ b/spec/requests/api/v1/merchant/coupons_request_spec.rb @@ -0,0 +1,83 @@ +require 'rails_helper' + +RSpec.describe "Coupons", type: :request do + describe "GET /show" do + it 'return one coupon by id' do + merchant = Merchant.create!(id: 1, name: "Sample Merchant") + coupon = Coupon.create!(name: "Buy One Get One 50", code: "BOGO50", discount_value: 50, active: true, merchant_id: merchant.id) + + get "/api/v1/merchants/#{merchant.id}/coupons/#{coupon.id}" + + expect(response).to have_http_status(200) + expect(response).to be_successful + + json_response = JSON.parse(response.body) + + expect(json_response['data']['id']).to eq(coupon.id.to_s) + expect(json_response['data']['type']).to eq('coupon') + expect(json_response['data']['attributes']['name']).to eq(coupon.name) + expect(json_response['data']['attributes']['code']).to eq(coupon.code) + expect(json_response['data']['attributes']['discount_value']).to be_a(String) + expect(json_response['data']['attributes']['active']).to eq(coupon.active?) + expect(json_response['data']['attributes']['merchant_id']).to eq(coupon.merchant_id) + end + + it 'returns a 404 when a coupon does not exist' do + merchant = Merchant.create!(id: 1, name: "Sample Merchant") + + get "/api/v1/merchants/#{merchant.id}/coupons/9999" + + expect(response).to have_http_status(404) + json_response = JSON.parse(response.body) + + expect(json_response['message']).to include("Your query could not be completed") + expect(json_response['errors']).to include("Couldn't find Coupon with 'id'=9999") + end + end + + describe 'GET/index' do + it 'returns all coupons for certain merchant id' do + merchant = Merchant.create!(id: 1, name: "Sample Merchant") + Coupon.create!(name: "Buy One Get One 50", code: "BOGO50", discount_value: 50, active: true, merchant_id: merchant.id) + Coupon.create!(name: "Buy One Get One 40", code: "BOGO40", discount_value: 40, active: true, merchant_id: merchant.id) + Coupon.create!(name: "Buy One Get One 30", code: "BOGO30", discount_value: 30, active: false, merchant_id: merchant.id) + Coupon.create!(name: "Buy One Get One 20", code: "BOGO20", discount_value: 20, active: false, merchant_id: merchant.id) + + get "/api/v1/merchants/#{merchant.id}/coupons" + + expect(response).to be_successful + + json_response = JSON.parse(response.body, symbolize_names: true)[:data] + expect(json_response.size).to eq(4) + + json_response.each do |coupon| + expect(coupon[:id]).to be_a(String) + expect(coupon[:type]).to eq('coupon') + expect(coupon[:attributes]).to have_key(:id) + expect(coupon[:attributes][:id]).to be_an(Integer) + expect(coupon[:attributes][:name]).to be_a(String) + expect(coupon[:attributes][:code]).to be_a(String) + expect(coupon[:attributes][:discount_value]).to be_a(String) + expect([true, false]).to include(coupon[:attributes][:active]) + expect(coupon[:attributes][:merchant_id]).to be_an(Integer) + end + end + + it 'returns an error is there is no merchant with that id' do + merchant = Merchant.create!(id: 1, name: "Sample Merchant") + Coupon.create!(name: "Buy One Get One 50", code: "BOGO50", discount_value: 50, active: true, merchant_id: merchant.id) + Coupon.create!(name: "Buy One Get One 40", code: "BOGO40", discount_value: 40, active: true, merchant_id: merchant.id) + Coupon.create!(name: "Buy One Get One 30", code: "BOGO30", discount_value: 30, active: false, merchant_id: merchant.id) + Coupon.create!(name: "Buy One Get One 20", code: "BOGO20", discount_value: 20, active: false, merchant_id: merchant.id) + + get "/api/v1/merchants/9999/coupons" + + expect(response).to have_http_status(404) + + json_response = JSON.parse(response.body) + + expect(json_response['message']).to include("Your query could not be completed") + expect(json_response['errors']).to include("Couldn't find Merchant with 'id'=9999") + end + end +end \ No newline at end of file diff --git a/spec/requests/coupons_spec.rb b/spec/requests/coupons_spec.rb deleted file mode 100644 index c9aa5e1..0000000 --- a/spec/requests/coupons_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'rails_helper' - -RSpec.describe "Coupons", type: :request do - describe "GET /show" do - it 'return one coupon by id' do - merchant = Merchant.create!(id: 1, name: "Sample Merchant") - coupon = Coupon.create!(name: "Buy One Get One 50", code: "BOGO50", discount_value: 50, active: true, merchant_id: merchant.id) - - get "/api/v1/merchants/#{merchant.id}/coupons/#{coupon.id}" - - expect(response).to have_http_status(200) - expect(response).to be_successful - - json_response = JSON.parse(response.body) - - expect(json_response['data']['id']).to eq(coupon.id.to_s) - expect(json_response['data']['type']).to eq('coupon') - expect(json_response['data']['attributes']['name']).to eq(coupon.name) - expect(json_response['data']['attributes']['code']).to eq(coupon.code) - expect(json_response['data']['attributes']['discount_value']).to be_a(String) - expect(json_response['data']['attributes']['active']).to eq(coupon.active?) - expect(json_response['data']['attributes']['merchant_id']).to eq(coupon.merchant_id) - end - - it 'returns a 404 when a coupon does not exist' do - merchant = Merchant.create!(id: 1, name: "Sample Merchant") - - get "/api/v1/merchants/#{merchant.id}/coupons/9999" - - expect(response).to have_http_status(404) - json_response = JSON.parse(response.body) - - - expect(json_response).to include("message" => "Your query could not be completed") - expect(json_response['errors']).to include("Couldn't find Coupon with 'id'=9999") - end - end -end From 2792bc7433a3b6b26e1130895e81899844ef9ac7 Mon Sep 17 00:00:00 2001 From: Melchor De La Rosa Date: Fri, 20 Sep 2024 14:46:12 -0400 Subject: [PATCH 4/8] Fixed some things --- app/models/coupon.rb | 1 + spec/models/coupon_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/models/coupon.rb b/app/models/coupon.rb index 08d5356..b7dbc32 100644 --- a/app/models/coupon.rb +++ b/app/models/coupon.rb @@ -3,4 +3,5 @@ class Coupon < ApplicationRecord has_many :invoices validates :code, uniqueness: true validates :name, :code, presence: true + validates :active, inclusion: { in: [true, false] } end diff --git a/spec/models/coupon_spec.rb b/spec/models/coupon_spec.rb index ec1144b..259a887 100644 --- a/spec/models/coupon_spec.rb +++ b/spec/models/coupon_spec.rb @@ -11,7 +11,7 @@ describe 'validations' do it 'is valid with valid attributes' do - coupon = Coupon.new(name: "Sample Coupon", code: "COUPON001", merchant: @merchant) + coupon = Coupon.new(name: "Sample Coupon", code: "COUPON001",discount_value: 1, active: true, merchant: @merchant) expect(coupon).to be_valid end @@ -28,8 +28,8 @@ end it 'is not valid with a duplicate code for the same merchant' do - Coupon.create!(name: "Existing Coupon", code: "COUPON001", merchant: @merchant) - coupon = Coupon.new(name: "New Coupon", code: "COUPON001", merchant: @merchant) + Coupon.create!(name: "Sample Coupon", code: "COUPON001",discount_value: 1, active: true, merchant: @merchant) + coupon = Coupon.new(name: "New Coupon", code: "COUPON001", active: true, merchant: @merchant) expect(coupon).to_not be_valid expect(coupon.errors[:code]).to include("has already been taken") end From f6fc3bcee168748d2e09cb5ab701144671e37d52 Mon Sep 17 00:00:00 2001 From: Melchor De La Rosa Date: Fri, 20 Sep 2024 14:51:07 -0400 Subject: [PATCH 5/8] Fixed --- spec/models/coupon_spec.rb | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/spec/models/coupon_spec.rb b/spec/models/coupon_spec.rb index 259a887..a528519 100644 --- a/spec/models/coupon_spec.rb +++ b/spec/models/coupon_spec.rb @@ -3,7 +3,7 @@ RSpec.describe Coupon, type: :model do before(:each) do @merchant = Merchant.create!(name: "Sample Merchant") - end + end describe 'relationships' do it {should belong_to(:merchant)} it {should have_many(:invoices)} @@ -11,27 +11,34 @@ describe 'validations' do it 'is valid with valid attributes' do - coupon = Coupon.new(name: "Sample Coupon", code: "COUPON001",discount_value: 1, active: true, merchant: @merchant) + coupon = Coupon.create!(name: "Sample Coupon", code: "COUPON001", discount_value: 1, active: true, merchant: @merchant) expect(coupon).to be_valid end it 'is not valid without a name' do - coupon = Coupon.new(name: nil, code: "COUPON001", merchant: @merchant) - expect(coupon).to_not be_valid - expect(coupon.errors[:name]).to include("can't be blank") + expect { + Coupon.create!(name: nil, code: "COUPON001", merchant: @merchant) + }.to raise_error(ActiveRecord::RecordInvalid) do |error| + expect(error.record.errors[:name]).to include("can't be blank") + end end it 'is not valid without a code' do - coupon = Coupon.new(name: "Sample Coupon", code: nil, merchant: @merchant) - expect(coupon).to_not be_valid - expect(coupon.errors[:code]).to include("can't be blank") + expect { + Coupon.create!(name: "Sample Coupon", code: nil, merchant: @merchant) + }.to raise_error(ActiveRecord::RecordInvalid) do |error| + expect(error.record.errors[:code]).to include("can't be blank") + end end it 'is not valid with a duplicate code for the same merchant' do - Coupon.create!(name: "Sample Coupon", code: "COUPON001",discount_value: 1, active: true, merchant: @merchant) - coupon = Coupon.new(name: "New Coupon", code: "COUPON001", active: true, merchant: @merchant) - expect(coupon).to_not be_valid - expect(coupon.errors[:code]).to include("has already been taken") + Coupon.create!(name: "Sample Coupon", code: "COUPON001", discount_value: 1, active: true, merchant: @merchant) + + expect { + Coupon.create!(name: "New Coupon", code: "COUPON001", active: true, merchant: @merchant) + }.to raise_error(ActiveRecord::RecordInvalid) do |error| + expect(error.record.errors[:code]).to include("has already been taken") + end end end end From 077b1b163b499f347c3f9b5981c6b961906037ac Mon Sep 17 00:00:00 2001 From: Melchor De La Rosa Date: Fri, 20 Sep 2024 15:12:13 -0400 Subject: [PATCH 6/8] refactored coupon model spec --- spec/models/coupon_spec.rb | 37 ++++--------------------------------- 1 file changed, 4 insertions(+), 33 deletions(-) diff --git a/spec/models/coupon_spec.rb b/spec/models/coupon_spec.rb index a528519..6b32ec0 100644 --- a/spec/models/coupon_spec.rb +++ b/spec/models/coupon_spec.rb @@ -7,38 +7,9 @@ describe 'relationships' do it {should belong_to(:merchant)} it {should have_many(:invoices)} - end - - describe 'validations' do - it 'is valid with valid attributes' do - coupon = Coupon.create!(name: "Sample Coupon", code: "COUPON001", discount_value: 1, active: true, merchant: @merchant) - expect(coupon).to be_valid - end - - it 'is not valid without a name' do - expect { - Coupon.create!(name: nil, code: "COUPON001", merchant: @merchant) - }.to raise_error(ActiveRecord::RecordInvalid) do |error| - expect(error.record.errors[:name]).to include("can't be blank") - end - end - - it 'is not valid without a code' do - expect { - Coupon.create!(name: "Sample Coupon", code: nil, merchant: @merchant) - }.to raise_error(ActiveRecord::RecordInvalid) do |error| - expect(error.record.errors[:code]).to include("can't be blank") - end - end - - it 'is not valid with a duplicate code for the same merchant' do - Coupon.create!(name: "Sample Coupon", code: "COUPON001", discount_value: 1, active: true, merchant: @merchant) - - expect { - Coupon.create!(name: "New Coupon", code: "COUPON001", active: true, merchant: @merchant) - }.to raise_error(ActiveRecord::RecordInvalid) do |error| - expect(error.record.errors[:code]).to include("has already been taken") - end - end + it { should validate_presence_of(:name) } + it { should validate_presence_of(:code) } + it { should validate_uniqueness_of(:code).scoped_to(:merchant_id) } # Scope to merchant_id if needed + it { should validate_inclusion_of(:active).in_array([true, false]) } end end From fbdd4cb235c571d617acb3d9f79ebd75f8303d70 Mon Sep 17 00:00:00 2001 From: Melchor De La Rosa Date: Fri, 20 Sep 2024 15:22:23 -0400 Subject: [PATCH 7/8] Fixed --- app/models/coupon.rb | 2 +- spec/models/coupon_spec.rb | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/app/models/coupon.rb b/app/models/coupon.rb index b7dbc32..8519a4d 100644 --- a/app/models/coupon.rb +++ b/app/models/coupon.rb @@ -1,7 +1,7 @@ class Coupon < ApplicationRecord belongs_to :merchant has_many :invoices - validates :code, uniqueness: true + validates :code, uniqueness: { scope: :merchant_id, message: "has already been taken" } validates :name, :code, presence: true validates :active, inclusion: { in: [true, false] } end diff --git a/spec/models/coupon_spec.rb b/spec/models/coupon_spec.rb index 6b32ec0..7c7c137 100644 --- a/spec/models/coupon_spec.rb +++ b/spec/models/coupon_spec.rb @@ -1,15 +1,24 @@ require 'rails_helper' +require 'rails_helper' + RSpec.describe Coupon, type: :model do - before(:each) do - @merchant = Merchant.create!(name: "Sample Merchant") - end - describe 'relationships' do - it {should belong_to(:merchant)} - it {should have_many(:invoices)} - it { should validate_presence_of(:name) } - it { should validate_presence_of(:code) } - it { should validate_uniqueness_of(:code).scoped_to(:merchant_id) } # Scope to merchant_id if needed - it { should validate_inclusion_of(:active).in_array([true, false]) } + before do + @merchant = Merchant.create(name: "Test Merchant") + end + + it { should belong_to(:merchant) } + it { should have_many(:invoices) } + + it { should validate_presence_of(:name) } + it { should validate_presence_of(:code) } + it { should validate_inclusion_of(:active).in_array([true, false]) } + + it 'validates uniqueness of code scoped to merchant_id' do + Coupon.create!(name: "Existing Coupon", code: "UNIQUECODE", active: true, merchant: @merchant) + new_coupon = Coupon.new(name: "New Coupon", code: "UNIQUECODE", active: true, merchant: @merchant) + + expect(new_coupon).not_to be_valid + expect(new_coupon.errors[:code]).to include("has already been taken") end end From 6d9677545c5d6a68544124cae665e67a892d76c5 Mon Sep 17 00:00:00 2001 From: Melchor De La Rosa Date: Fri, 20 Sep 2024 17:08:01 -0400 Subject: [PATCH 8/8] Added create route and post test for happy path, and create action --- .../api/v1/merchants/coupons_controller.rb | 16 ++++++++++++++++ config/routes.rb | 2 +- .../api/v1/merchant/coupons_request_spec.rb | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/app/controllers/api/v1/merchants/coupons_controller.rb b/app/controllers/api/v1/merchants/coupons_controller.rb index cfe87ff..df61519 100644 --- a/app/controllers/api/v1/merchants/coupons_controller.rb +++ b/app/controllers/api/v1/merchants/coupons_controller.rb @@ -10,4 +10,20 @@ def index coupons = merchant.coupons render json: CouponSerializer.new(coupons) end + + def create + merchant = Merchant.find(params[:merchant_id]) + coupon = merchant.coupons.new(coupon_params) + if coupon.save + render json: CouponSerializer.new(coupon), status: :created + else + render json: { errors: coupon.errors.full_messages }, status: :unprocessable_entity + end + end + + private + + def coupon_params + params.require(:coupon).permit(:name, :code, :discount_value, :active) + end end diff --git a/config/routes.rb b/config/routes.rb index df8b2ec..ff1388a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -24,7 +24,7 @@ resources :items, only: :index, controller: "merchants/items" resources :customers, only: :index, controller: "merchants/customers" resources :invoices, only: :index, controller: "merchants/invoices" - resources :coupons, only: [:index, :show], controller: "merchants/coupons" + resources :coupons, only: [:index, :show, :create], controller: "merchants/coupons" end end end diff --git a/spec/requests/api/v1/merchant/coupons_request_spec.rb b/spec/requests/api/v1/merchant/coupons_request_spec.rb index dd4dc79..5051aad 100644 --- a/spec/requests/api/v1/merchant/coupons_request_spec.rb +++ b/spec/requests/api/v1/merchant/coupons_request_spec.rb @@ -80,4 +80,22 @@ expect(json_response['errors']).to include("Couldn't find Merchant with 'id'=9999") end end + + describe 'POST' do + it 'creates a new coupon' do + merchant = Merchant.create!(name: "Sample Merchant") + coupon_params = { name: "Buy One Get One 50", code: "BOGO50", discount_value: 50, active: true } + + post "/api/v1/merchants/#{merchant.id}/coupons", params: { coupon: coupon_params } + + expect(response).to have_http_status(201) + + created_coupon = Coupon.last + expect(created_coupon.name).to eq("Buy One Get One 50") + expect(created_coupon.code).to eq("BOGO50") + expect(created_coupon.discount_value).to eq(50) + expect(created_coupon.active).to be_truthy + expect(created_coupon.merchant_id).to eq(merchant.id) + end + end end \ No newline at end of file