(Disclaimer: I’m using Rails, but my issue isn’t with Rails. I also find
the quality of posts on the Ruby forum of slightly higher quality more
frequently than the Rails side. Also, this is a long post…)
I’m programming a bidding system, and I don’t like my code. I can
clearly see where it’s wrong, and even explain why, but I don’t know how
to make it better.
In a nutshell…
I have Part objects (billable things like envelopes), Vendor objects
(suppliers of said parts) and BidRequest objects (a request by the
client (me) to a specific vendor for prices regarding a particular part
– includes actual prices, too (not my choice)).
Now… the way our interface is laid out, users spend a lot of time
looking at parts. So when they want to request a bid from a vendor, they
do so from the viewpoint of a part. “I want a bid for this part from
these vendors.” They check off the vendors they want to get bids from,
the program creates BidRequest objects whose status is OPEN and an
e-mail gets sent off.
When the vendor gets the e-mail there’s a link to bid on the part. The
vendor is taken to a form to put in prices, and upon submission, the bid
request is completed and its status set to COMPLETE. The vendor can also
decline, in which case the status is set to DECLINED. There’s also
CANCELED, EXPIRED and DEFAULT statuses, too.
CANCELED and EXPIRED are easy enough. It’s DEFAULT that is (I think) the
source of the stench. Here’s why:
This method accepts vendor pricing and changes
the status to COMPLETE.
bid_request.bid_with(options)
This method is meant for us (the company) to
setup default pricing for a vendor. The default
prices are stored internally as a BidRequest
with a status of DEFAULT. They are inaccessible
to the vendor.
part.set_default_prices_for_vendor(options) # options includes vendor_id
I started to write this method when I realized I
already have that functionality with part.
bid_request.bid_with_as_default(options)
Gah, this is really difficult to explain.
Basically what it comes down to is that the
part#set_default_prices_for_vendor needs to set the status of the bid
request – thus violating encapsulation of the BidRequest. I believe
that only the BidRequest should know anything about possible statuses.
The code looks something like this…
def set_default_prices_for_vendor(options)
bid_requests.create(options.merge({ :status => ‘DEFAULT’ })
end
I’ve since removed that method from Part and wrote…
def bid_with_as_default(options)
ensure_expires_attribute_is_a_date(options)
update_attributes(options.merge({ :status => ‘DEFAULT’ })) ? true :
false
end
But this makes my controller code ugly (I think) because of the way the
interface is setup. The fact that it’s heavily biased towards parts and
not vendors leaves me wanting to write things like
part.request_bid_from_vendor and part.set_default_prices_for_vendor but
then Part knows too much about BidRequest.
If it were biased towards vendors I imagine I’d write the inverse of
what I just showed you. But is there a way to be biased towards neither?
Take a cue from REST and treat BidRequest like a resource?
I often think of things as stories and try to write objects to fulfill
the roles in those stories, but my results are ugly and smell bad. So
I’m left wondering…
-
Is it a design decision?
Does my method of programming not work as well as it could because the
overarching design doesn’t allow for it? -
Can meta-programming or functional programming help?
I don’t know enough about the how, why and when to even begin with this,
but since I didn’t write the spec (I hate that term) I’m kind of stuck.
(Kind of – if I could write a kick ass replacement that was super clean
I’m sure they’d go for it.) -
Is there a pattern that someone sees that I don’t?
I suppose it would help to be familiar with more than a few patterns
(I’m familiar with MVC and observer only because of Rails, I’ve read up
on the decorator and factory patterns – so I’ve no street smarts, only
what I’ve read and can remember.) -
Is the problem domain inherently difficult?
So… you’ve made it this far! I applaud and appreciate your effort.
Now, do you have any advice? Hehe… =)
Thank you very, very much!