Skip to content
This repository has been archived by the owner on Jul 30, 2019. It is now read-only.

WIP: Move states into separate tables #1494

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from

Conversation

adelevie
Copy link
Contributor

Working with @baccigalupi to pay down some technical debt by moving many of the groupings of fields on Auction into their own state objects. The disambiguation will make the code easier to reason about, and easier to modify as state needs change.

@adelevie adelevie changed the title Move states into separate tables WIP: Move states into separate tables Jan 11, 2017
@@ -71,8 +86,12 @@ def auction_rejected?
parsed_attributes[:status] == 'rejected'
end

def parser
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noticed I need to be smarter about the memoization. I could fix this by memoizing attributes when AuctionParser is instantiated.

@@ -1,6 +1,8 @@
db:
image: postgres:9
web:
tty: true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful for prying in using docker-compose and docker attach.

Copy link
Contributor

@baccigalupi baccigalupi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start. I wrote lots of comments on the testing, and a few other places.

@@ -8,6 +8,7 @@ class Auction < ActiveRecord::Base
belongs_to :customer
has_many :bids
has_many :bidders, through: :bids
has_many :states, foreign_key: 'auction_id', class_name: 'AuctionState'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need the foreign_key? I thought you only needed to specify that if it went against convention, like with the class_name in this case.

private

def changing?(key, value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This additional abstraction is confusing to me. What is it for?

create_published_state
# add more state creation here, as needed
end

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This I get!

publishing = parser.publishing?

expect(publishing).to be true
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are going to want to test the opposite too, when not publishing to make sure that works.

# do we want to low-level test for the published state object, like this?
published_state = auction.states.find {|state| state.name == 'published'}
expect(published_state.state_value).to eq('published')

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^^ that seems like the important part to test. You should probably also test what happens when an auction moves from 'archived' to 'published' in that case it will already have a record, so changing the count of records should not happen.

I forgot to mention that you can put a constraint in the database on that not happening by adding a uniq index on ['auction_id', 'name']. Then if a unique index error is thrown you revert back to updating. Solves some obscure bugs that Rails generates via their expectation that there is always just one server running in one thread.

expect(published_state.state_value).to eq('published')

# and/or this?:
# expect(auction.published?).to be true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem important to me. It seems like that is the interface expansion that I was complaining about. Special methods for states instead of just setting state and reading state. Since the states are distributed now, we will need a nice way to read states. auction.state_for('published') # "archived" ???

# and/or this?:
# published = AuctionPublishedState.new(auction).perform
# expect(published).to be true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would only test this as the return value if you already had plans to use it somewhere. Otherwise the maintenance is overhead.

# and/or this?:
# published = FindAuctionState.new(auction, name: 'published').perform
# expect(published).to be true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Future class? I don't have a problem with a separate object for getting state ... probably good. But we are going to need an easy way to read state from an auction that is less wordy.

@adelevie
Copy link
Contributor Author

adelevie commented Jan 13, 2017

An idea for a state builder:

ChangeState.new(auction, published: :unpublished).perform

Although maybe raw ActiveRecord is still fine. The abstractions don't seem to abstract too much other than the column names of AuctionState, which I'm not even sure need to be hidden.

UPDATE

Ended up writing something close:

ChangeState.new(auction, 'published', 'unpublished').perform

This helps simplify UpdateAuction in that UpdateAuction#create_auction_states no longer needs to know about whether or not an AuctionState needs to be created or found-and-updated. This will be handy as we add more state changes to UpdateAuction.

@adelevie adelevie force-pushed the move-states-into-separate-tables branch from af347c8 to 8870f1f Compare January 13, 2017 17:16
end

private

attr_reader :auction, :params, :current_user

def create_auction_states
create_published_state
Copy link
Contributor Author

@adelevie adelevie Jan 13, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's consider changing the published state name to something like visibility.

So far:
- created auction_states migration
- added hook into UpdateAuction to create_auction_states
- create auction state when publishing an auction
- test that UpdateAuction creates an AuctionStatus when publishing
In doing so, also:

- swept ArchiveAuction into UpdateAuction (so all updates pass through it and no forked behavior in the controller)
- added AuctionParser#archiving?
- added tests to UpdateAuction to test for archiving and to ensure published isn't inappropriately set
@adelevie adelevie force-pushed the move-states-into-separate-tables branch from 8870f1f to 601e0b3 Compare January 26, 2017 20:52
CreateAuction makes it easier to implement and test calls to ChangeState.
In CreateAuction we will make explicit an previously implicit state: 'unpublished'.

unpublished is the default published state for auctions thanks to a default call in a migration.
But we will also ensure that any newly-created auctions also get a State object created reflecting this state.
@adelevie adelevie force-pushed the move-states-into-separate-tables branch from bab91c5 to 2d7ed6a Compare January 27, 2017 20:54
This implementation isn't perfect. There's some trickness in figuring
out the order of building and saving auctions and their child objects, states.
ActiveRecord doesn't make this very clean to paper over. Rather than have overly-complex
persistence-management logic, I'm naively sending potentially extra #save calls to the db.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants