auctify 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +229 -0
- data/Rakefile +34 -0
- data/app/assets/config/auctify_manifest.js +1 -0
- data/app/assets/javascripts/folio/console/main_app.coffee +1 -0
- data/app/assets/stylesheets/auctify/application.css +15 -0
- data/app/assets/stylesheets/auctify/bidder_registrations.css +4 -0
- data/app/assets/stylesheets/auctify/bids.css +4 -0
- data/app/assets/stylesheets/auctify/sales.css +4 -0
- data/app/assets/stylesheets/auctify/sales_packs.css +4 -0
- data/app/assets/stylesheets/folio/console/_main_app.sass +1 -0
- data/app/assets/stylesheets/scaffold.css +80 -0
- data/app/cells/folio/console/atoms/previews/main_app.coffee +1 -0
- data/app/concerns/auctify/behavior/base.rb +20 -0
- data/app/concerns/auctify/behavior/buyer.rb +16 -0
- data/app/concerns/auctify/behavior/item.rb +27 -0
- data/app/concerns/auctify/behavior/seller.rb +36 -0
- data/app/concerns/auctify/sale/auction_callbacks.rb +44 -0
- data/app/controllers/auctify/api/v1/auctions_controller.rb +61 -0
- data/app/controllers/auctify/api/v1/base_controller.rb +22 -0
- data/app/controllers/auctify/api/v1/console/bids_controller.rb +37 -0
- data/app/controllers/auctify/application_controller.rb +8 -0
- data/app/controllers/auctify/bidder_registrations_controller.rb +65 -0
- data/app/controllers/auctify/bids_controller.rb +64 -0
- data/app/controllers/auctify/sales_controller.rb +75 -0
- data/app/controllers/auctify/sales_packs_controller.rb +75 -0
- data/app/helpers/auctify/application_helper.rb +10 -0
- data/app/helpers/auctify/bidder_registrations_helper.rb +6 -0
- data/app/helpers/auctify/bids_helper.rb +6 -0
- data/app/helpers/auctify/sales_helper.rb +15 -0
- data/app/helpers/auctify/sales_packs_helper.rb +6 -0
- data/app/jobs/auctify/application_job.rb +6 -0
- data/app/jobs/auctify/bidding_closer_job.rb +23 -0
- data/app/jobs/auctify/bidding_is_close_to_end_notifier_job.rb +29 -0
- data/app/mailers/auctify/application_mailer.rb +8 -0
- data/app/models/auctify/application_record.rb +14 -0
- data/app/models/auctify/behaviors.rb +27 -0
- data/app/models/auctify/bid.rb +72 -0
- data/app/models/auctify/bidder_registration.rb +84 -0
- data/app/models/auctify/sale/auction.rb +404 -0
- data/app/models/auctify/sale/base.rb +210 -0
- data/app/models/auctify/sale/retail.rb +97 -0
- data/app/models/auctify/sales_pack.rb +101 -0
- data/app/serializers/auctify/sale/auction_serializer.rb +46 -0
- data/app/services/auctify/bids_appender.rb +241 -0
- data/app/services/auctify/service_base.rb +78 -0
- data/app/types/auctify/minimal_bids_ladder_type.rb +77 -0
- data/app/views/auctify/api/v1/console/bids/index.html.erb +1 -0
- data/app/views/auctify/api/v1/console/bids/show.html.erb +1 -0
- data/app/views/auctify/bidder_registrations/_form.html.erb +27 -0
- data/app/views/auctify/bidder_registrations/edit.html.erb +6 -0
- data/app/views/auctify/bidder_registrations/index.html.erb +33 -0
- data/app/views/auctify/bidder_registrations/new.html.erb +5 -0
- data/app/views/auctify/bidder_registrations/show.html.erb +24 -0
- data/app/views/auctify/bids/_form.html.erb +32 -0
- data/app/views/auctify/bids/edit.html.erb +6 -0
- data/app/views/auctify/bids/index.html.erb +31 -0
- data/app/views/auctify/bids/new.html.erb +5 -0
- data/app/views/auctify/bids/show.html.erb +19 -0
- data/app/views/auctify/sales/_form.html.erb +32 -0
- data/app/views/auctify/sales/edit.html.erb +6 -0
- data/app/views/auctify/sales/index.html.erb +36 -0
- data/app/views/auctify/sales/new.html.erb +5 -0
- data/app/views/auctify/sales/show.html.erb +19 -0
- data/app/views/auctify/sales_packs/_form.html.erb +57 -0
- data/app/views/auctify/sales_packs/edit.html.erb +6 -0
- data/app/views/auctify/sales_packs/index.html.erb +40 -0
- data/app/views/auctify/sales_packs/new.html.erb +5 -0
- data/app/views/auctify/sales_packs/show.html.erb +44 -0
- data/app/views/layouts/auctify/application.html.erb +15 -0
- data/config/locales/auctify.cs.yml +121 -0
- data/config/locales/default.cs.yml +220 -0
- data/config/locales/default.en.yml +215 -0
- data/config/routes.rb +23 -0
- data/db/migrate/20210212163000_create_auctify_sales.rb +16 -0
- data/db/migrate/20210216151350_add_type_to_auctify_sales.rb +7 -0
- data/db/migrate/20210217101246_add_state_to_auctify_sales.rb +7 -0
- data/db/migrate/20210217110819_add_prices_to_auctify_sales.rb +9 -0
- data/db/migrate/20210222103031_add_published_at_to_auctify_sale.rb +7 -0
- data/db/migrate/20210223090240_create_auctify_bidder_registrations.rb +15 -0
- data/db/migrate/20210223152139_create_auctify_bids.rb +13 -0
- data/db/migrate/20210225103227_add_bid_steps_ladder_to_auctify_sales.rb +7 -0
- data/db/migrate/20210412114319_add_reserve_price_to_auctify_sales.rb +7 -0
- data/db/migrate/20210416140603_create_auctify_sales_packs.rb +17 -0
- data/db/migrate/20210416141111_add_pack_reference_to_auctify_sales.rb +7 -0
- data/db/migrate/20210419075003_add_ends_at_to_auctify_sales.rb +7 -0
- data/db/migrate/20210419082800_add_position_to_auctify_sales.rb +8 -0
- data/db/migrate/20210419083321_add_indexes.rb +12 -0
- data/db/migrate/20210421093652_add_number_to_auctify_sales.rb +7 -0
- data/db/migrate/20210423071326_removing_polymorphic_assoc_sale_to_item.rb +7 -0
- data/db/migrate/20210423071534_add_currently_ends_at_to_auctify_sales.rb +7 -0
- data/db/migrate/20210427121353_change_column_published_on_auctify_sale.rb +15 -0
- data/db/migrate/20210427122315_add_featured_to_auctify_sales.rb +7 -0
- data/db/migrate/20210428045651_add_sales_bool_indices.rb +9 -0
- data/db/migrate/20210428064615_allow_seller_to_be_nil.rb +13 -0
- data/db/migrate/20210429051508_add_sales_pack_start_and_end.rb +15 -0
- data/db/migrate/20210430080419_add_sales_slug.rb +11 -0
- data/db/migrate/20210503095704_add_cancelled_to_auctify_bids.rb +7 -0
- data/db/migrate/20210504072041_add_commission_and_contract_to_auctify_sales.rb +8 -0
- data/db/migrate/20210506064100_add_winner_to_auctify_sales.rb +7 -0
- data/db/migrate/20210506072438_add_bids_count_to_auctify_sales.rb +7 -0
- data/db/migrate/20210507085845_rename_bids_count_to_applied_bids_count.rb +7 -0
- data/db/migrate/20210510063146_add_dont_confirm_bids_to_registrations.rb +7 -0
- data/db/migrate/20210511081556_add_index_on_cancelled_on.rb +7 -0
- data/db/migrate/20210512162942_add_sold_at_to_auctify_sales.rb +7 -0
- data/db/migrate/20210607113440_add_commission_in_percent_to_sales_packs.rb +7 -0
- data/db/migrate/20210617062509_add_index_to_auctify_sales_currently_ends_at.rb +7 -0
- data/db/migrate/20210625125732_add_current_winner_to_auctify_sales.rb +7 -0
- data/lib/auctify.rb +7 -0
- data/lib/auctify/configuration.rb +43 -0
- data/lib/auctify/engine.rb +25 -0
- data/lib/auctify/version.rb +5 -0
- data/lib/tasks/auctify_tasks.rake +23 -0
- metadata +442 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auctify
|
|
4
|
+
class BidsAppender < ServiceBase
|
|
5
|
+
attr_reader :auction, :bid
|
|
6
|
+
|
|
7
|
+
def initialize(auction:, bid: nil)
|
|
8
|
+
super()
|
|
9
|
+
|
|
10
|
+
@auction = auction
|
|
11
|
+
@bid = bid
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def build_result
|
|
15
|
+
self.current_price = auction.current_price
|
|
16
|
+
@updated_win_bid = nil
|
|
17
|
+
|
|
18
|
+
if bid
|
|
19
|
+
set_price_for_limit_bid
|
|
20
|
+
|
|
21
|
+
if approved_bid?
|
|
22
|
+
solve_winner(winning_bid, bid)
|
|
23
|
+
append_bids!
|
|
24
|
+
update_auction! unless failed?
|
|
25
|
+
else
|
|
26
|
+
fail!
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@result = result_struct
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
attr_accessor :current_price
|
|
35
|
+
|
|
36
|
+
def result_struct
|
|
37
|
+
OpenStruct.new(
|
|
38
|
+
current_price: current_price,
|
|
39
|
+
current_minimal_bid: new_current_minimal_bid,
|
|
40
|
+
winning_bid: winning_bid,
|
|
41
|
+
winner: winner,
|
|
42
|
+
won_price: (overcame_reserve_price? ? current_price : nil)
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def set_price_for_limit_bid
|
|
47
|
+
return unless bid.price.blank? && bid.with_limit?
|
|
48
|
+
|
|
49
|
+
if changing_own_limit?
|
|
50
|
+
bid.price = winning_bid.price
|
|
51
|
+
elsif bid.max_price <= new_current_minimal_bid
|
|
52
|
+
bid.price = bid.max_price
|
|
53
|
+
else
|
|
54
|
+
bid.price = new_current_minimal_bid
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def approved_bid?
|
|
59
|
+
@approved_bid ||= begin
|
|
60
|
+
check_bidder
|
|
61
|
+
changing_own_limit? ? check_max_price_increasing : check_price_minimum
|
|
62
|
+
check_same_bidder
|
|
63
|
+
check_auction_state
|
|
64
|
+
|
|
65
|
+
errors.add_from_hash(bid.errors.to_hash)
|
|
66
|
+
|
|
67
|
+
errors.empty?
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def append_bids!
|
|
72
|
+
fail! unless bid.save
|
|
73
|
+
|
|
74
|
+
# saving last, so it will still be winning, even if both have same price
|
|
75
|
+
if @updated_win_bid.present?
|
|
76
|
+
fail! unless @updated_win_bid.save
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def update_auction!
|
|
81
|
+
@winning_bid = nil
|
|
82
|
+
@bids = bids.reload
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
fail! unless auction.succesfull_bid!(price: new_current_price,
|
|
86
|
+
winner: winner,
|
|
87
|
+
time: bid.reload.created_at)
|
|
88
|
+
self.current_price = auction.current_price
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def new_current_price
|
|
92
|
+
winning_bid.price
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def new_current_minimal_bid
|
|
96
|
+
return current_price if first_bid?
|
|
97
|
+
|
|
98
|
+
increase_price(current_price)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def winning_bid
|
|
102
|
+
@winning_bid ||= bids.first
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def winner
|
|
106
|
+
(overcame_reserve_price? ? winning_bid.bidder : nil)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def overcame_reserve_price?
|
|
110
|
+
return false if winning_bid.blank?
|
|
111
|
+
return false if auction.reserve_price.present? && auction.current_price < auction.reserve_price
|
|
112
|
+
|
|
113
|
+
true
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def first_bid?
|
|
117
|
+
bids.empty?
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def bids
|
|
121
|
+
@bids ||= auction.ordered_applied_bids
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def check_bidder
|
|
125
|
+
unless bid.registration&.auction == auction
|
|
126
|
+
bid.errors.add(:auction, :bidder_is_not_registered_for_this_auction)
|
|
127
|
+
end
|
|
128
|
+
unless auction.bidding_allowed_for?(bid.bidder)
|
|
129
|
+
bid.errors.add(:bidder, :you_are_not_allowed_to_bid)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def check_max_price_increasing
|
|
134
|
+
bid.errors.add(:bidder, :you_can_only_increase_your_max_price) if bid.max_price.present? && (bid.max_price.to_i <= winning_bid.max_price.to_i)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def check_price_minimum
|
|
138
|
+
if bid.price < new_current_minimal_bid
|
|
139
|
+
att = bid.with_limit? ? :max_price : :price
|
|
140
|
+
bid.errors.add(att,
|
|
141
|
+
:price_is_bellow_minimal_bid,
|
|
142
|
+
minimal_bid: ActionController::Base.helpers.number_to_currency(new_current_minimal_bid, precision: 0))
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def check_same_bidder
|
|
147
|
+
return if overbidding_yourself_allowed? || changing_own_limit?
|
|
148
|
+
|
|
149
|
+
if winning_bid.present? && same_bidder?(winning_bid, bid)
|
|
150
|
+
bid.errors.add(:bidder, :you_cannot_overbid_yourself)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def check_auction_state
|
|
155
|
+
# comparing time with seconds precision, use `.to_i`
|
|
156
|
+
return if auction.in_sale? && bid.created_at.to_i <= auction.currently_ends_at.to_i
|
|
157
|
+
|
|
158
|
+
bid.errors.add(:auction, :auction_is_not_accepting_bids_now)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def solve_winner(winning_bid, new_bid)
|
|
162
|
+
return if winning_bid.blank? || changing_own_limit?
|
|
163
|
+
|
|
164
|
+
solve_limits_fight(winning_bid, new_bid) if new_bid.with_limit? && winning_bid.with_limit?
|
|
165
|
+
increase_bid_price(winning_bid, new_bid) if new_bid.with_limit? && !winning_bid.with_limit?
|
|
166
|
+
duplicate_increased_win_bid(winning_bid, new_bid) if !new_bid.with_limit? && winning_bid.with_limit?
|
|
167
|
+
# do nothing, all is solved already if !bid.with_limit? && !winning_bid.with_limit?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def solve_limits_fight(winning_bid, new_bid)
|
|
171
|
+
if winning_bid.max_price < new_bid.max_price
|
|
172
|
+
update_winning_bid_to(winning_bid.max_price)
|
|
173
|
+
new_bid.price = [new_bid.price, increase_price_to(overcome: winning_bid.max_price, ceil: new_bid.max_price)].max
|
|
174
|
+
else
|
|
175
|
+
new_bid.price = new_bid.max_price
|
|
176
|
+
update_winning_bid_to(increase_price_to(overcome: new_bid.max_price, ceil: winning_bid.max_price))
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def increase_bid_price(winning_bid, new_bid)
|
|
181
|
+
if winning_bid.price < new_bid.max_price
|
|
182
|
+
new_bid.price = [new_bid.price, increase_price_to(overcome: winning_bid.price, ceil: new_bid.max_price)].max
|
|
183
|
+
else
|
|
184
|
+
new_bid.price = new_bid.max_price
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def duplicate_increased_win_bid(winning_bid, new_bid)
|
|
189
|
+
if winning_bid.max_price < new_bid.price
|
|
190
|
+
update_winning_bid_to(winning_bid.max_price)
|
|
191
|
+
elsif overbidding_yourself_allowed? && same_bidder?(winning_bid, new_bid)
|
|
192
|
+
update_winning_bid_to(new_bid.price, force_if_equal: true)
|
|
193
|
+
else
|
|
194
|
+
update_winning_bid_to(increase_price_to(overcome: new_bid.price, ceil: winning_bid.max_price))
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def update_winning_bid_to(price, force_if_equal: false)
|
|
199
|
+
return if price < winning_bid.price
|
|
200
|
+
return if price == winning_bid.price && !force_if_equal
|
|
201
|
+
|
|
202
|
+
@updated_win_bid = winning_bid.dup
|
|
203
|
+
@updated_win_bid.price = [price, winning_bid.max_price].min
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def increase_price(price)
|
|
207
|
+
return price + 1 if bid_steps_ladder.blank?
|
|
208
|
+
|
|
209
|
+
_range, increase_step = bid_steps_ladder.detect { |range, step| range.cover?(price) }
|
|
210
|
+
price + increase_step
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def increase_price_to(overcome:, ceil:)
|
|
214
|
+
return ceil if overcome == ceil
|
|
215
|
+
raise ":ceil is lower than :overcome" if ceil < overcome
|
|
216
|
+
|
|
217
|
+
running_price = current_price
|
|
218
|
+
while running_price <= overcome
|
|
219
|
+
running_price = increase_price(running_price)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
[running_price, ceil].min
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def bid_steps_ladder
|
|
226
|
+
@bid_steps_ladder ||= auction.bid_steps_ladder
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def changing_own_limit?
|
|
230
|
+
bid.with_limit? && winning_bid.present? && (winning_bid.bidder == bid.bidder)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def overbidding_yourself_allowed?
|
|
234
|
+
Auctify.configuration.restrict_overbidding_yourself_to_max_price_increasing == false
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def same_bidder?(winning_bid, new_bid)
|
|
238
|
+
winning_bid.bidder == new_bid.bidder
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Base (surprise!) for Auctify services.
|
|
4
|
+
# Ascendants shoudl implement own methods `initialize` and `build_result`(which assigns to `@result`).
|
|
5
|
+
# In succesfull flow, call `srv = SomeService.call(*args)`
|
|
6
|
+
# will return itself with result accessible by `srv.result`, errors by`srv.errors` and `srv.success? => true`
|
|
7
|
+
# In failed call (calling `fail!` when building result),
|
|
8
|
+
# there can be still some result, even errors and `srv.success? => false` (or `srv.failed? => true`)
|
|
9
|
+
# Failing and errors are independent!
|
|
10
|
+
# Errors are like ActiveRecord errors, use `errors.add(:key, "message)`
|
|
11
|
+
# You can pass some information through `srv.flashes` hash
|
|
12
|
+
|
|
13
|
+
module Auctify
|
|
14
|
+
class ServiceErrors < Hash
|
|
15
|
+
def add(key, value, _opts = {})
|
|
16
|
+
self[key] ||= []
|
|
17
|
+
self[key] << value
|
|
18
|
+
self[key].uniq!
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add_from_hash(errors_hash)
|
|
22
|
+
errors_hash.each do |key, values|
|
|
23
|
+
values.each { |value| add key, value }
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def full_messages
|
|
28
|
+
f_msgs = []
|
|
29
|
+
each_one { |field, message| f_msgs << "#{field}: #{message}" }
|
|
30
|
+
f_msgs
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def each_one
|
|
34
|
+
each_pair do |field, messages|
|
|
35
|
+
messages.each { |message| yield field, message }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class ServiceBase
|
|
41
|
+
attr_reader :result, :errors, :flashes
|
|
42
|
+
|
|
43
|
+
def self.call(*args, **keyword_args)
|
|
44
|
+
if args.blank?
|
|
45
|
+
new(**keyword_args).call
|
|
46
|
+
elsif keyword_args.blank?
|
|
47
|
+
new(*args).call
|
|
48
|
+
else
|
|
49
|
+
new(*args, **keyword_args).call
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def call
|
|
54
|
+
build_result
|
|
55
|
+
self # always returnning service itself, to get to `errors`, `result`
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def initialize
|
|
59
|
+
@result = nil
|
|
60
|
+
@failed = false
|
|
61
|
+
@flashes = {}
|
|
62
|
+
@errors = ServiceErrors.new
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fail!
|
|
66
|
+
@failed = true
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def success?
|
|
70
|
+
!failure?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def failure?
|
|
74
|
+
@failed
|
|
75
|
+
end
|
|
76
|
+
alias failed? failure?
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Auctify
|
|
4
|
+
# in ruby/rails we expecting default format as hash with ranges { (0...1_000) => 200, (1_000..) => 500 }
|
|
5
|
+
# but acceppting (in setter) also hash with numbers or strings
|
|
6
|
+
# in DB we store JSON with minimal prices casted to nice strings : { "0" => 200, "1_000" => 500 }
|
|
7
|
+
class MinimalBidsLadderType < ActiveRecord::Type::Value
|
|
8
|
+
def type
|
|
9
|
+
:jsonb
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cast(value) # setter
|
|
13
|
+
# hash is expected as value
|
|
14
|
+
value = JSON.parse(value) if value.is_a?(String)
|
|
15
|
+
if value.blank?
|
|
16
|
+
{}
|
|
17
|
+
else
|
|
18
|
+
case value.keys.first
|
|
19
|
+
when Range # { (0...1_000) => 200, (1_000..) => 500 }
|
|
20
|
+
cast_from_ranges(value)
|
|
21
|
+
when String # { "0" => 200, "1_000" => 500 }
|
|
22
|
+
cast_from_min_strings(value)
|
|
23
|
+
when Numeric # { 0 => 200, 1000 => 500 }
|
|
24
|
+
cast_from_min_numbers(value)
|
|
25
|
+
else
|
|
26
|
+
raise "Uncovered ladder key type `#{value.keys.first}`"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def deserialize(db_value) # getter, from db data to ruby raw object
|
|
32
|
+
return {} if db_value.blank?
|
|
33
|
+
|
|
34
|
+
cast(ActiveSupport::JSON.decode(db_value))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def serialize(value) # modifier to store in db
|
|
38
|
+
return nil if value.nil? # so You are able to do `KLASS.where(bid_steps_ladder: nil)`
|
|
39
|
+
|
|
40
|
+
# value should be Hash with ranges as keys
|
|
41
|
+
result = {}
|
|
42
|
+
|
|
43
|
+
value.each_pair do |k_range, v|
|
|
44
|
+
min_price_string = ActiveSupport::NumberHelper.number_to_delimited(k_range.first, delimiter: "_")
|
|
45
|
+
result[min_price_string] = v
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
ActiveSupport::JSON.encode(result)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
def cast_from_ranges(hash)
|
|
53
|
+
hash # desired format
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def cast_from_min_strings(hash)
|
|
57
|
+
result = {}
|
|
58
|
+
hash.each_pair { |k, v| result[k.to_i] = v }
|
|
59
|
+
|
|
60
|
+
cast_from_min_numbers(result)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def cast_from_min_numbers(hash)
|
|
64
|
+
previous_step = nil
|
|
65
|
+
|
|
66
|
+
result = hash.to_a.sort.each_with_object({}) do |step, rslt|
|
|
67
|
+
unless previous_step.nil?
|
|
68
|
+
rslt[Range.new(previous_step.first, step.first, true)] = previous_step.last
|
|
69
|
+
end
|
|
70
|
+
previous_step = step
|
|
71
|
+
end
|
|
72
|
+
result[Range.new(previous_step.first, nil, false)] = previous_step.last
|
|
73
|
+
|
|
74
|
+
result
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p>Bids.count: <%= @bids.count %> </p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<p>BID: <%= @bid.to_json %></p>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<%= form_with(model: bidder_registration, local: true) do |form| %>
|
|
2
|
+
<% if bidder_registration.errors.any? %>
|
|
3
|
+
<div id="error_explanation">
|
|
4
|
+
<h2><%= pluralize(bidder_registration.errors.count, "error") %> prohibited this bidder_registration from being saved:</h2>
|
|
5
|
+
|
|
6
|
+
<ul>
|
|
7
|
+
<% bidder_registration.errors.full_messages.each do |message| %>
|
|
8
|
+
<li><%= message %></li>
|
|
9
|
+
<% end %>
|
|
10
|
+
</ul>
|
|
11
|
+
</div>
|
|
12
|
+
<% end %>
|
|
13
|
+
|
|
14
|
+
<div class="field">
|
|
15
|
+
<%= form.label :bidder_id %>
|
|
16
|
+
<%= form.text_field :bidder_id %>
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<div class="field">
|
|
20
|
+
<%= form.label :auction_id %>
|
|
21
|
+
<%= form.text_field :auction_id %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="actions">
|
|
25
|
+
<%= form.submit %>
|
|
26
|
+
</div>
|
|
27
|
+
<% end %>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<p id="notice"><%= notice %></p>
|
|
2
|
+
|
|
3
|
+
<h1>Bidder Registrations</h1>
|
|
4
|
+
|
|
5
|
+
<table>
|
|
6
|
+
<thead>
|
|
7
|
+
<tr>
|
|
8
|
+
<th>Bidder</th>
|
|
9
|
+
<th>Auction</th>
|
|
10
|
+
<th>Aasm state</th>
|
|
11
|
+
<th>Handled at</th>
|
|
12
|
+
<th colspan="3"></th>
|
|
13
|
+
</tr>
|
|
14
|
+
</thead>
|
|
15
|
+
|
|
16
|
+
<tbody>
|
|
17
|
+
<% @bidder_registrations.each do |bidder_registration| %>
|
|
18
|
+
<tr>
|
|
19
|
+
<td><%= bidder_registration.bidder_id %></td>
|
|
20
|
+
<td><%= bidder_registration.auction_id %></td>
|
|
21
|
+
<td><%= bidder_registration.aasm_state %></td>
|
|
22
|
+
<td><%= bidder_registration.handled_at %></td>
|
|
23
|
+
<td><%= link_to 'Show', bidder_registration %></td>
|
|
24
|
+
<td><%= link_to 'Edit', edit_auctify_bidder_registration_path(bidder_registration) %></td>
|
|
25
|
+
<td><%= link_to 'Destroy', bidder_registration, method: :delete, data: { confirm: 'Are you sure?' } %></td>
|
|
26
|
+
</tr>
|
|
27
|
+
<% end %>
|
|
28
|
+
</tbody>
|
|
29
|
+
</table>
|
|
30
|
+
|
|
31
|
+
<br>
|
|
32
|
+
|
|
33
|
+
<%= link_to 'New Bidder Registration', new_auctify_bidder_registration_path %>
|