Rails engine for auctions of items. Can be used on any ActiveRecord models.
:item- object/model, which should be auctioned (frommain_app):user- source forseller,owner,buyerandbidder(frommain_app)sale- sale of:item; can be directretail, byauctionor some other typeauctionfor one:item(it is Forward auction) (v češtině "Položka aukce")seller- person/company which sells:itemin specificsalebidder- registered person/user allowed to bid in specificauctionbuyer- person/company which buys:itemin specificsale( eg. winner of auction)bid- one bid of onebidderinauctionsales_pack- pack of sales(auctions), mostly time framed, Can have physical location or be online. (v češtině "Aukce")auctioneer- (needed?) company organizingauction
:item1 : 0-Nsale, (sale time periods cannot overlap):itemN : 1owner(can change in time)saleN : 1sellersaleN : 1buyersale::auction1 : Nbidders(troughbidder_registrations) 1 : Nbidssales_pack0-1 : Nsales(sales_packis optional forsale)auctionN : 1auctioneer(?)
Sale::Auction- full body auction of one item for many registered buyer (as bidders)Sale::Retail- direct sale of item to one buyer
- generator to copy auction's views to main app (so they can be modified) (engine checks main_app views first)
sale belongs_to item polymorphic , STI typed
sale belongs_to seller polymorphic
sale belongs_to buyer polymorphic
auction has_many bidder_registrations (?bidders?)
bidder_registration belongs buyer polymorphic
If item.owner exists, it is used as auction.seller (or Select from all aucitfied sellers)
- if bidder adds bid in
:prolonging_limitminutes before auction ends, end time is extended forbid.time + prolonging_limit(so auction end when there are no bids in last:prolonging_limitminutes) - for each auction, there can be set of rules for minimal
bidaccording to currentauctioned_price(default can be(1..Infinity) => 1) real exampleminimal_bids = { (0...5_000) => 100, (5_000...20_000) => 500, (20_000...100_000) => 1_000, (100_000...500_000) => 5_000, (500_000...1_000_000) => 10_000, (1_000_000...2_000_000) => 50_000, (2_000_000..) => 100_000 } - first bid do not increase
auctioned_priceit stays onbase_price - bidder cannot "overbid" themselves (no following bids from same bidder)
- auctioneer can define format of auction numbers (eg. "YY#####") and sales_pack numbers
- there should be ability to follow
auction(notification before end) oritem.author(new item in auction) - item can have category and user can select auction by categories.
- SalePack can be
(un)published /public - SalePack can be
open (adding items , bidding)/ closed(bidding ended) - auctioneer_commision_from_buyer is in % and adds to sold_price in checkout
- auction have
start_timeandend_time - auction can be
highlightedfor cover pages - auction stores all bids history even those cancelled by admin
- there should be two types of bid
- one with maximal price (amount => maximal bid price; placed in small bids as needed), system will increase bid automagicaly unless it reaches maximum
- second direct bid (amount => bid price; immediattely placed)
- sales numbering: YY#### (210001, 210002, …, 259999)
In ActiveRecord model use class method auctify_as with params :buyer,:seller, :item.
classUser < ApplicationRecordauctify_as:buyer,:seller# this will add method like `sales`, `puchases` ....endclassPainting < ApplicationRecordauctify_as:item# this will add method like `sales` ....endAuctify expects that auctifyied model instances responds to to_label and :item to reponds to owner (which should lead to object auctified as :seller)!
Add this line to your application's Gemfile:
gem'auctify'
And then execute:
$ bundle
Or install it yourself as:
$ gem install auctify
classUser < ApplicationRecordauctify_as:buyer,:seller# this will add method like `sales`, `puchases` ....endclassPainting < ApplicationRecordauctify_as:item# this will add method like `sales` ....end
Enqueuing of
BiddingCloserJob(for each auction) is done by peridically performedAuctify::EnsureAuctionsClosingJob. This is up to You how you run it, but interval between runs must be shorter thanAuctify.configuration.auction_prolonging_limit_in_seconds.- optional
Auctify.configuredo |config| config.autoregister_as_bidders_all_instances_of_classes=["User"]# default is []config.auction_prolonging_limit_in_seconds=10.minutes# default is 1.minute, can be overriden in `SalePack#auction_prolonging_limit_in_seconds` attributeconfig.auctioneer_commission_in_percent=10# so buyer will pay: auction.current_price * ((100 + 10)/100)config.autofinish_auction_after_bidding=true# after `auction.close_bidding!` immediatelly proces result to `auction.sold_in_auction!` or `auction.not_sold_in_auction!`; default falseconfig.when_to_notify_bidders_before_end_of_bidding=30.minutes# default `nil` => no notifyingconfig.restrict_overbidding_yourself_to_max_price_increasing=false# default is `true` so only bids with `max_price` can be applied if You are winner.end
If model autified as
:buyerresponds to:bidding_allowed?, check is done before eachauction.bid!. Also ifbuyer.bidding_allowed? => true, registration to auction is created on first bid.
For available auction callback methods to override see `app/concerns/auctify/sale/auction_callbacks.rb`
banksy=User.find_by(nickname: "Banksy")bidder1=User.find_by(nickname: "Bidder1")bidder2=User.find_by(nickname: "Bidder2")piece=Painting.find_by(title: "Love is in the bin")piece.owner == banksy# => (not actually) :trueauction=banksy.offer_to_sale!(piece,{in: :auction,price: 100})auction.offered?# => :trueauction.item == piece# => :trueauction.seller == banksy# => :trueauction.offered_price# => 100.0banksy.sales# => [auction]banksy.auction_sales# => [auction]banksy.retail_sales# => []pieces.sales# => [auction]auction.bidder_registrations# => [] unless config.autoregister_as_bidders_all_instances_of_classes is setauction.bidder_registrations.create(bidder: bidder1)# => error, not allowed ("Aukce aktuálně nepovoluje nové registrace")auction.accept_offer!b1_reg=auction.bidder_registrations.create(bidder: bidder1)b2_reg=auction.bidder_registrations.create(bidder: bidder2)auction.bidder_registrations.size# => 2auction.current_price# => nilauction.start_sale!auction.current_price# => 100.0aucion.bid!(Auctify::Bid.new(registration: b1_reg,price: nil,max_price: 150))# auction.bid_appended! is called after succesfull bid, You can override it# auction.bid_not_appended!(errors) is called after unsuccesfull bid, You can override itauction.current_price# => 100.0auction.bidding_result.winner# => bidder1auction.bidding_result.current_price# => 100.0auction.bidding_result.current_minimal_bid# => 101.0 `auction.bid_steps_ladder` is empty, we increasing by 1aucion.bid!(Auctify::Bid.new(registration: b2_reg,price: 145,max_price: nil))# some auto bidding is doneauction.current_price# => 146.0auction.bidding_result.winner# => bidder1auction.bidding_result.current_price# => 146.0auction.bidding_result.current_minimal_bid# => 147.0auction.winner# => nilaucion.bid!(Auctify::Bid.new(registration: b2_reg,price: 149,max_price: 155))# some auto bidding is doneauction.current_price# => 151.0auction.bidding_result.winner# => bidder2auction.bidding_result.current_price# => 151.0auction.bidding_result.current_minimal_bid# => 152.0auction.close_bidding!auction.bidding_ended?# => trueauction.buyer# => nilauction.winner# => bidder2auction.sold_in_auction!(buyer: bidder2,price: 149,sold_at: currently_ends_at)# it is verified against bids!auction.auctioned_successfully?# => trueauction.buyer# => bidder2# when all negotiations went wellauction.sell!auction.sold?# => true
Look into tests
test/models/auctify/sale/auction_bidding_test.rbandtest/services/auctify/bid_appender_test.rbfor more info about bidding process.To protect accidential deletions, many associations are binded with
dependent: restrict_with_error. Correct order ofdeletion isbids=>sales=>sales_packs.
Contribution directions go here.
The gem is available as open source under the terms of the MIT License.