diff options
author | David Gay <david@davidgay.org> | 2021-05-23 17:06:34 -0400 |
---|---|---|
committer | David Gay <david@davidgay.org> | 2021-05-23 17:06:34 -0400 |
commit | aba212e13062c43a1192ef885b05145ac1cc9fe7 (patch) | |
tree | 61e3850ec0c86913a7e2d42076bcd73aa59f480a | |
parent | 44facc2e567eb3c045ce082428f42276e45b0202 (diff) |
Bazaar with buy orders, sell orders, and order cancellation
-rw-r--r-- | app/controllers/bazaar_controller.rb | 91 | ||||
-rw-r--r-- | app/models/bazaar_order.rb | 12 | ||||
-rw-r--r-- | app/models/character.rb | 14 | ||||
-rw-r--r-- | app/models/character_item.rb | 14 | ||||
-rw-r--r-- | app/models/concerns/destroy_if_zero_quantity.rb | 18 | ||||
-rw-r--r-- | app/views/application/_navbar.html.erb | 3 | ||||
-rw-r--r-- | app/views/bazaar/index.html.erb | 128 | ||||
-rw-r--r-- | config/routes.rb | 7 | ||||
-rw-r--r-- | data/items.yml | 3 | ||||
-rw-r--r-- | db/migrate/20210523151747_create_bazaar_orders.rb | 13 | ||||
-rw-r--r-- | db/schema.rb | 16 | ||||
-rw-r--r-- | test/fixtures/bazaar_orders.yml | 15 | ||||
-rw-r--r-- | test/models/bazaar_order_test.rb | 7 |
13 files changed, 322 insertions, 19 deletions
diff --git a/app/controllers/bazaar_controller.rb b/app/controllers/bazaar_controller.rb new file mode 100644 index 0000000..b31e8fb --- /dev/null +++ b/app/controllers/bazaar_controller.rb @@ -0,0 +1,91 @@ +class BazaarController < ApplicationController + def index + @bazaar_orders = BazaarOrder.all + @items = Item.all + end + + def create_order + item = Item.find(params[:item_id]) + quantity = params[:quantity].to_i + price_each = params[:price_each].to_i + if params[:buy_order] == "1" + BazaarOrder.transaction do + current_char.shift_item("vestige", (-price_each * quantity)) + current_char.bazaar_orders.create!(item: item, quantity: quantity, + price_each: price_each, buy_order: true) + flash[:notice] = "Posted buy order for #{item.name} at #{price_each} vg each." + end + else + BazaarOrder.transaction do + current_char.shift_item(item, -quantity) + current_char.bazaar_orders.create!(item: item, quantity: quantity, + price_each: price_each, buy_order: false) + flash[:notice] = "Posted sell order for #{item.name} at #{price_each} vg each." + end + end + rescue ItemQuantityError + flash[:alert] = "Failed to post order. Make sure you have vestige or items." + ensure + redirect_to bazaar_path + end + + def accept_offer + @bazaar_order = BazaarOrder.find(params[:id]) + if @bazaar_order.buy_order? + BazaarOrder.transaction do + quantity = [params[:quantity].to_i, @bazaar_order.quantity].min + profit = quantity * @bazaar_order.price_each + current_char.shift_item(@bazaar_order.item, -quantity) + current_char.shift_item("vestige", profit) + @bazaar_order.character.shift_item(@bazaar_order.item, quantity) + @bazaar_order.decrement!(:quantity, quantity) + @bazaar_order.save! + flash[:notice] = "You sold #{quantity} #{@bazaar_order.item.name} and got #{profit} vg." + end + else + BazaarOrder.transaction do + quantity = [params[:quantity].to_i, @bazaar_order.quantity].min + cost = quantity * @bazaar_order.price_each + current_char.shift_item("vestige", -cost) + current_char.shift_item(@bazaar_order.item, quantity) + @bazaar_order.character.shift_item("vestige", cost) + @bazaar_order.decrement!(:quantity, quantity) + @bazaar_order.save! + flash[:notice] = "You bought #{quantity} #{@bazaar_order.item.name} for #{cost} vg." + end + end + rescue ItemQuantityError + flash[:alert] = "Failed to accept order. Make sure you have vestige or items." + ensure + redirect_to bazaar_path + end + + def cancel_offer + @bazaar_order = BazaarOrder.find(params[:id]) + unless @bazaar_order.character == current_char + flash[:alert] = "You can't cancel someone else's order." + redirect_to bazaar_path and return + end + if @bazaar_order.buy_order? + BazaarOrder.transaction do + vestige_to_return = @bazaar_order.quantity * @bazaar_order.price_each + @bazaar_order.character.shift_item("vestige", vestige_to_return) + @bazaar_order.destroy! + flash[:notice] = "You canceled your buy order for #{@bazaar_order.item.name} and got" \ + " #{vestige_to_return} vg back." + end + else + BazaarOrder.transaction do + items_to_return = @bazaar_order.quantity + @bazaar_order.character.shift_item(@bazaar_order.item, items_to_return) + @bazaar_order.destroy! + flash[:notice] = "You canceled your sell order for #{@bazaar_order.item.name} and got" \ + " #{items_to_return} of them back." + end + end + rescue ItemQuantityError + flash[:alert] = "Failed to cancel order." + ensure + redirect_to bazaar_path + end +end diff --git a/app/models/bazaar_order.rb b/app/models/bazaar_order.rb new file mode 100644 index 0000000..d36d520 --- /dev/null +++ b/app/models/bazaar_order.rb @@ -0,0 +1,12 @@ +class BazaarOrder < ApplicationRecord + include DestroyIfZeroQuantity + + belongs_to :character + belongs_to :item + + validates :price_each, :quantity, + numericality: { greater_than_or_equal_to: 0, + less_than_or_equal_to: 2_000_000_000, only_integer: true } + validates :buy_order, inclusion: { in: [true, false] } + +end diff --git a/app/models/character.rb b/app/models/character.rb index bad2bb2..003f8af 100644 --- a/app/models/character.rb +++ b/app/models/character.rb @@ -11,6 +11,7 @@ class Character < ApplicationRecord has_many :items, through: :character_items has_many :character_skills has_many :chat_messages + has_many :bazaar_orders validates :name, presence: true validates_length_of :name, maximum: 15, message: "can't be longer than 15 characters" validates_uniqueness_of :name, message: "is already being used" @@ -35,12 +36,17 @@ class Character < ApplicationRecord def wildscour_level; skill_level("wildscour"); end def worldcall_level; skill_level("worldcall"); end + def vestige + vestige = self.character_items.find_by(item: Item.find_by_gid("vestige")) + vestige ? vestige.quantity : 0 + end + def shift_item(item, amount) item = Item.find_by_gid(item) if item.is_a? String CharacterItem.transaction do ci = self.character_items.find_or_initialize_by(item: item) - ci.increment(:quantity, amount) - ci.save + ci.increment!(:quantity, amount) + ci.save! end end @@ -71,7 +77,7 @@ class Character < ApplicationRecord open_slots = self.open_slots_for(item) raise EquipmentError unless open_slots.any? self.shift_item(item, -1) - self.equipment.create(item: item, slot: open_slots.first) + self.equipment.create!(item: item, slot: open_slots.first) end end @@ -80,7 +86,7 @@ class Character < ApplicationRecord equipment = self.equipment.find_by(slot: slot) raise EquipmentError unless equipment item = equipment.item - equipment.destroy + equipment.destroy! self.shift_item(item, 1) end end diff --git a/app/models/character_item.rb b/app/models/character_item.rb index b54f799..6ee13ab 100644 --- a/app/models/character_item.rb +++ b/app/models/character_item.rb @@ -1,4 +1,6 @@ class CharacterItem < ApplicationRecord + include DestroyIfZeroQuantity + belongs_to :character belongs_to :item validates :quantity, presence: true @@ -9,17 +11,5 @@ class CharacterItem < ApplicationRecord end end - after_save :destroy_if_zero_quantity - scope :ordered_by_item_name, -> { includes(:item).order("items.name") } - - private - def destroy_if_zero_quantity - if self.quantity == 0 - destroy - elsif self.quantity < 0 - # TODO: Can improve this (at the least, with reporting, later). - raise ItemQuantityError - end - end end diff --git a/app/models/concerns/destroy_if_zero_quantity.rb b/app/models/concerns/destroy_if_zero_quantity.rb new file mode 100644 index 0000000..6fdfb3e --- /dev/null +++ b/app/models/concerns/destroy_if_zero_quantity.rb @@ -0,0 +1,18 @@ +module DestroyIfZeroQuantity + extend ActiveSupport::Concern + + included do + after_save :destroy_if_zero_quantity + end + + private + def destroy_if_zero_quantity + byebug + if self.quantity == 0 + destroy + elsif self.quantity < 0 + # TODO: Can improve this (at the least, with reporting, later). + raise ItemQuantityError + end + end +end diff --git a/app/views/application/_navbar.html.erb b/app/views/application/_navbar.html.erb index ef81d6c..c0e2761 100644 --- a/app/views/application/_navbar.html.erb +++ b/app/views/application/_navbar.html.erb @@ -12,5 +12,8 @@ <li class="mr-6 inline"> <%= link_to "Hearth", character_hearth_path(current_char) %> </li> + <li class="mr-6 inline"> + <%= link_to "Bazaar", bazaar_path %> + </li> <% end %> </ul> diff --git a/app/views/bazaar/index.html.erb b/app/views/bazaar/index.html.erb new file mode 100644 index 0000000..63d708f --- /dev/null +++ b/app/views/bazaar/index.html.erb @@ -0,0 +1,128 @@ +<h1 class="text-3xl mb-4">Numedite Bazaar</h1> + +<p class="mb-2">The small, cloaked people known as the Numedites travel this way and that across the planes. + Because their ways are spiritual in nature, they use their innate magic to facilitate + distant trade at no cost to their clientele.</p> + +<p class="mb-2">You have <span class="font-bold"><%= current_char.vestige %></span> vestige.</p> + +<div class="my-8"> + <h2 class="text-xl mb-4">Post Order</h2> + + <p class="mb-4">Sell orders can be posted with a caravan of numedites, offering items for vestige. + Buy orders can also be posted, offering vestige for items.</p> + + <%= form_with url: bazaar_order_path, class: "my-4" do |f| %> + <div> + <%= f.label :item_id, "Item" %> + <%= f.select :item_id, @items.map { |i| [i.name, i.id] } %> + </div> + <div> + <%= f.label :quantity, "Quantity" %> + <%= f.number_field :quantity, required: true, min: 1, max: 2_000_000_000 %> + </div> + <div> + <%= f.label :price_each, "Price Each" %> + <%= f.number_field :price_each, required: true, min: 1, max: 2_000_000_000 %> + </div> + <div> + <%= f.label :buy_order, "Post as buy order" %> + <%= f.check_box :buy_order %> + </div> + <div> + <%= f.submit "Post Order" %> + </div> + <% end %> +</div> + +<div class="my-8"> + <h2 class="text-xl mb-4">Your Orders</h2> + <table class="table-auto mb-8"> + <thead> + <tr> + <th class="table-header-padded">Item</th> + <th class="table-header-padded">Quantity</th> + <th class="table-header-padded">Type</th> + <th class="table-header-padded">Price Each</th> + <th class="table-header-padded">Cancel</th> + </tr> + </thead> + <tbody> + <% @bazaar_orders.where(character: current_char).each do |bo| %> + <tr> + <td class="table-cell-padded"><%= bo.item.name %></td> + <td class="table-cell-padded"><%= bo.quantity %></td> + <td class="table-cell-padded"><%= bo.buy_order? ? "Buy" : "Sell" %></td> + <td class="table-cell-padded"><%= bo.price_each %></td> + <td class="table-cell-padded"> + <%= button_to "Cancel", bazzar_cancel_offer_path(id: bo.id), method: :delete %> + </td> + </tr> + <% end %> + </tbody> + </table> +</div> + +<div class="my-8"> + <h2 class="text-xl mb-4">Sell Orders</h2> + + <table class="table-auto mb-8"> + <thead> + <tr> + <th class="table-header-padded">Item</th> + <th class="table-header-padded">Quantity</th> + <th class="table-header-padded">From</th> + <th class="table-header-padded">Price Each</th> + <th class="table-header-padded">Buy</th> + </tr> + </thead> + <tbody> + <% @bazaar_orders.where(buy_order: false).each do |bo| %> + <tr> + <td class="table-cell-padded"><%= bo.item.name %></td> + <td class="table-cell-padded"><%= bo.quantity %></td> + <td class="table-cell-padded"><%= bo.character.name %></td> + <td class="table-cell-padded"><%= bo.price_each %></td> + <td class="table-cell-padded"> + <%= form_with url: bazzar_accept_offer_path(id: bo.id) do |f| %> + <%= f.number_field :quantity, value: 1, min: 1, max: 2_000_000_000, required: true %> + <%= f.submit "Buy" %> + <% end %> + </td> + </tr> + <% end %> + </tbody> + </table> +</div> + +<div class="my-8"> + <h2 class="text-xl mb-4">Buy Orders</h2> + + <table class="table-auto mb-8"> + <thead> + <tr> + <th class="table-header-padded">Item</th> + <th class="table-header-padded">Quantity</th> + <th class="table-header-padded">From</th> + <th class="table-header-padded">Price Each</th> + <th class="table-header-padded">Sell</th> + </tr> + </thead> + <tbody> + <% @bazaar_orders.where(buy_order: true).each do |bo| %> + <tr> + <td class="table-cell-padded"><%= bo.item.name %></td> + <td class="table-cell-padded"><%= bo.quantity %></td> + <td class="table-cell-padded"><%= bo.character.name %></td> + <td class="table-cell-padded"><%= bo.price_each %></td> + <td class="table-cell-padded"> + <%= form_with url: bazzar_accept_offer_path(id: bo.id) do |f| %> + <%= f.number_field :quantity, value: 1, min: 1, max: 2_000_000_000, required: true %> + <%= f.submit "Sell" %> + <% end %> + </td> + </tr> + <% end %> + </tbody> + </table> +</div> diff --git a/config/routes.rb b/config/routes.rb index f9c1f7b..4eee2a0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -12,7 +12,7 @@ Rails.application.routes.draw do end resources :chat_messages, only: [:index, :create] - + resources :locations, only: [:index, :show] resources :activities, only: [:show] resources :characters, only: [:show, :new, :create] do @@ -28,7 +28,10 @@ Rails.application.routes.draw do end end - resources :locations, only: [:index, :show] + get "/bazaar", to: "bazaar#index" + post "/bazaar", to: "bazaar#create_order", as: :bazaar_order + post "/bazaar/accept/:id", to: "bazaar#accept_offer", as: :bazzar_accept_offer + delete "/bazaar/cancel/:id", to: "bazaar#cancel_offer", as: :bazzar_cancel_offer post "/start_activity", to: "activities#start" post "/finish_activity", to: "game#finish_activity" diff --git a/data/items.yml b/data/items.yml index ae1db0f..f97d339 100644 --- a/data/items.yml +++ b/data/items.yml @@ -50,3 +50,6 @@ mending_salve: wood: name: "wood" description: "A bit of wood." +vestige: + name: "vestige" + description: "Faintly glimmering remnants of magical events. Used as currency." diff --git a/db/migrate/20210523151747_create_bazaar_orders.rb b/db/migrate/20210523151747_create_bazaar_orders.rb new file mode 100644 index 0000000..24bded3 --- /dev/null +++ b/db/migrate/20210523151747_create_bazaar_orders.rb @@ -0,0 +1,13 @@ +class CreateBazaarOrders < ActiveRecord::Migration[6.1] + def change + create_table :bazaar_orders do |t| + t.references :character, null: false, foreign_key: true + t.references :item, null: false, foreign_key: true + t.integer :quantity + t.integer :price_each + t.boolean :buy_order + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 6dfbe87..2568430 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_05_22_201259) do +ActiveRecord::Schema.define(version: 2021_05_23_151747) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -28,6 +28,18 @@ ActiveRecord::Schema.define(version: 2021_05_22_201259) do t.index ["location_id"], name: "index_activities_on_location_id" end + create_table "bazaar_orders", force: :cascade do |t| + t.bigint "character_id", null: false + t.bigint "item_id", null: false + t.integer "quantity" + t.integer "price_each" + t.boolean "buy_order" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["character_id"], name: "index_bazaar_orders_on_character_id" + t.index ["item_id"], name: "index_bazaar_orders_on_item_id" + end + create_table "built_hearth_amenities", force: :cascade do |t| t.bigint "hearth_id", null: false t.bigint "hearth_amenity_id", null: false @@ -213,6 +225,8 @@ ActiveRecord::Schema.define(version: 2021_05_22_201259) do t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true end + add_foreign_key "bazaar_orders", "characters" + add_foreign_key "bazaar_orders", "items" add_foreign_key "built_hearth_amenities", "hearth_amenities" add_foreign_key "built_hearth_amenities", "hearths" add_foreign_key "character_items", "characters" diff --git a/test/fixtures/bazaar_orders.yml b/test/fixtures/bazaar_orders.yml new file mode 100644 index 0000000..9245a91 --- /dev/null +++ b/test/fixtures/bazaar_orders.yml @@ -0,0 +1,15 @@ +# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html + +one: + character: one + item: one + quantity: 1 + price_each: 1 + buy_order: false + +two: + character: two + item: two + quantity: 1 + price_each: 1 + buy_order: false diff --git a/test/models/bazaar_order_test.rb b/test/models/bazaar_order_test.rb new file mode 100644 index 0000000..d65862b --- /dev/null +++ b/test/models/bazaar_order_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class BazaarOrderTest < ActiveSupport::TestCase + # test "the truth" do + # assert true + # end +end |