Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[51] Add status enum to Draws with intent deadline #95

Merged
merged 1 commit into from
Jan 13, 2017

Conversation

orenyk
Copy link
Contributor

@orenyk orenyk commented Jan 6, 2017

Resolves #51

  • Add status enum to draws (current values 'draft' and 'pre-lottery', with 'lottery' and 'post-lottery' to be implemented in the future)
  • Add button to initiate draw on draw show view with appropriate route
  • Add DrawStarter to check validity of draw setup and handle transition to pre-lottery stage.

@orenyk orenyk force-pushed the 51_draw_phase_intent_deadline branch from c644d0b to a4034ee Compare January 6, 2017 06:18
Copy link
Collaborator

@esoterik esoterik left a comment

Choose a reason for hiding this comment

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

a few comments!

also, I think that this should possibly add draw status validations to groups? that might be enough to be its own issue, though.

module DrawHelper
# Check to see if a draw is startable by the current user. Returns true if the
# draw is in the 'draft' status and the user satisfies the Pundit policy,
# false otherwise.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this should be handled entirely in pundit

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ha, thanks for catching that. I forgot we could do more than assert on user in policies 😛. I'll update.

# Class method to permit calling :start on the class without instantiating the
# service object directly
#
# @attr draw [Draw] the draw in question
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should just enforce instantiation or make this a module instead of a class

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I actually prefer this paradigm for service objects and I'm surprised that I forgot to mention it when we started implementing them. I picked it up from the Upcase forums when I was doing some of the Ruby challenges: this is the thread and here's a great article on it. Take a look and see if it changes your mind; the reason why we wouldn't want a module is because modules don't maintain state, making refactoring and method extraction difficult, and instantiation just feels unnecessary for single-use objects like this. I really think we should stick with it moving forward and potentially even refactor our controllers and existing service objects to follow that model.

@@ -12,5 +12,8 @@
</tbody>
</table>
</div>
<% if can_start_draw?(draw: @draw, policy: policy(@draw)) %>
Copy link
Collaborator

Choose a reason for hiding this comment

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

policy(@draw).start? should work given my suggested change to the policy above

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yea it's what I originally had and then I starting thinking about @draw.status and for some reason forgot that the policy could include that as well 😕.


expect(page).not_to have_link('Begin draw process')
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm hesitant to leave these permission-based feature specs long-term. I think they're good when behavior differs greatly between roles, but might be overkill for things like seeing a button.

Perhaps we want to have some view specs instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, maybe? I don't have a lot of experience with View specs in Rails; I've done a bit of that in Phoenix but there everything is basically unit testable so it's really easy. I'll take a look and see about moving everything around.

@@ -9,6 +9,10 @@ def update?
user.rep? || user.admin?
end

def start?
user.admin?
Copy link
Collaborator

Choose a reason for hiding this comment

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

changing this to user.admin? && record.draft? eliminates the need for DrawHelper

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Way to keep hammering the point home :-).

end

def create_mock_draw_starter(param_hash)
draw_starter = double('draw_starter')
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that instance_spy is generally the preferred mock method (thoughtbot post), though there are trade-offs.

If this is kept as a standard mock, though, it should be a verifying double so it checks that the stubbed methods are present on the actual class/instance of said class

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good catch, I wasn't 100% sure why we kept using instance methods so figured I'd keep it simple for now, thanks for the reference.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea after reading the post I'm convinced. I'll switch them everywhere in the tests.

@@ -31,6 +33,11 @@ def destroy
handle_action(**result)
end

def start
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is super nit-picky but I feel like start is too ambiguous between opening up the draw to students and running the lottery -- open might be better for this transition?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed; especially since starting the draw could mean starting the actual draw / lottery phase (this was already bothering me). How about initiate? open doesn't really work for me - I get that the implication is "open for business," so to speak, but it feels like weird verb to use DrawOpener vs DrawInitiator. Thoughts?

Copy link
Collaborator

@esoterik esoterik left a comment

Choose a reason for hiding this comment

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

just took a look at the spec persistence / mocking issues

@@ -0,0 +1,91 @@
# frozen_string_literal: true
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is missing require rails_helper

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ha good catch. That's the second time I did that 😕

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wait, if that's the case, why is this test even running? Do we even need it?

Copy link
Collaborator

Choose a reason for hiding this comment

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

it configures DatabaseCleaner and other things, so we need it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea good point.

# doesn't check for enum validity. I tried switching to FactoryGirl's
# `build_stubbed` but I ran into major issues with data being persisted or
# being unable to generate a valid draw for the service object without
# persisting all the things. Thoughts?
Copy link
Collaborator

Choose a reason for hiding this comment

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

re: verifying doubles not verifying:
I tested this a bit locally and I'm surprised because I didn't run into any of this when refactoring specs for reservations. I did a small amount of research and you're right, it has to do with the enum methods being dynamically defined.

It does mean that we're opening ourselves up to some risk when mocking, but I think it's worth it overall to not persist data for testing method details. We'll still have integration tests to cover us on major behaviors, and I don't think there's a better solution for verifying the mocks.

re: issues with build_stubbed:
ActiveRecord associations do not play well when some of your data is mocked and some of it is real, I know that pain. Here I don't think we'll have to deal with it (I think I fixed the persisted spec above, though I could be wrong), but long-term we might want to pull over my Mocker class from reservations to make dealing with associations easier when we don't want to persist data.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for confirming my suspicions and validating that I wasn't being entirely stupid. I'm open to implementing the Mocker again if we feel we need it - I think we've been ok without the additional complexity and just manually dealing with instance_spys, but we can come back to this if it comes up.

result = described_class.activate(draw: draw)

expect(result[:obj]).to be_pre_lottery
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

I was able to get the following to pass without persistence:

  draw = instance_spy('Draw', draft?: true, has_students?: true,
                              has_enough_beds?: true)

  described_class.activate(draw: draw)

  expect(draw).to have_received(:update_attributes)
    .with(status: 'pre_lottery')

Which I think tests what we want it to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea..... I don't think it solves my problem (the enum could change and this spec would still pass), but that may be fine as long as we have at least one integration test that would fail if the enum changed, which is what we want. I was implicitly testing this by stubbing update_attributes(status: 'pre_lottert') to return the updated spy with pre_lottery?: true stubbed, but your assertion is better as we're really testing the message sent out of the service object, not the outcome.

That said, should we? I feel like we should only send the invitation e-mails if that update is successful, so as a sanity check should we make that action dependent on the result of update_attributes?

@orenyk orenyk changed the title WIP: [51] Add status enum to Draws with intent deadline [51] Add status enum to Draws with intent deadline Jan 10, 2017
@orenyk
Copy link
Contributor Author

orenyk commented Jan 10, 2017

@esoterik ok I think this is ready!

@orenyk
Copy link
Contributor Author

orenyk commented Jan 10, 2017

This is going to fail rubocop, btw, I'll fix those after the build.

@orenyk
Copy link
Contributor Author

orenyk commented Jan 10, 2017

I'll also check out codeclimate in a bit.

@orenyk
Copy link
Contributor Author

orenyk commented Jan 10, 2017

The only CodeClimate failures at this point should be some TODO comments in the code - can we disable that check? I'm kind of a fan of leaving notes to our future dev selves in the code where relevant. Let me know what you think!

Copy link
Collaborator

@esoterik esoterik left a comment

Choose a reason for hiding this comment

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

a few small comments!

# the intent metrics to render properly.
def calculate_metrics
@intent_metrics = IntentMetricsQuery.call(@draw)
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that this is fine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

#
# Mailer class for user e-mails
class StudentMailer < ApplicationMailer
default from: 'no-reply@vesta.app' # SHOULD WE HAVE AN ADMIN ADDRESS?
Copy link
Collaborator

Choose a reason for hiding this comment

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

we should probably have an admin address; it will probably make sense for students to want to respond to emails individually in some circumstances.

Copy link
Collaborator

Choose a reason for hiding this comment

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

this can be a separate issue at this point I think

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea, that's what I was thinking.

Copy link
Contributor Author

@orenyk orenyk Jan 12, 2017

Choose a reason for hiding this comment

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

I also think it can be replaced with our eventual config model, so I'll open an issue to that effect.

@user = user
@intent_deadline = user.draw.intent_deadline
# TODO: figure this out - singleton class, e.g. app config, or do we add a
# class for now that we later use for multitenancy?
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it should end up as an app config model; it'll work better when we have multi-tenancy

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea exactly. I think that some kind of high-level model for the root-level user (similar to Department in Shifts) will work fine.


# TODO: I had originally made this public but now I'm not sure it needs to be.
# Let me know if you'd like to keep it private or not and we can remove the
# YARD docs and the spec.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think its fine for these to be private

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, should I remove the documentation? I'll definitely remove the test.

validate
activate_draw if valid?
send_emails if valid?
valid? ? success : error
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it makes sense to wrap this all in one if

hmm, this isn't the biggest problem, but it's unclear that validity can change between the steps of this method. Not quite sure what to suggest, and it's also fine as it is -- just something I thought while reading through the class

Copy link
Contributor Author

Choose a reason for hiding this comment

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

changing the method name to currently_valid?

visit draw_path(draw)
expect(page).not_to have_link('Begin draw process')
end
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

I still think we're better off using view specs for these kinds of things

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll open an issue

# TODO: should we keep this? I originally wrote this before I decided to make
# #student_count private, but we can make it public again if we think it's
# worth it.
#
Copy link
Collaborator

Choose a reason for hiding this comment

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

might as well keep it, it doesn't persist data so it's fast, and we can always delete it later

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I had to modify the expectation since RSpec fails if you call a private method directly, so instead I used send. I also left a comment that it should be removed if it ever fails.

@@ -0,0 +1,95 @@
# frozen_string_literal: true
require 'spec_helper'
Copy link
Collaborator

Choose a reason for hiding this comment

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

notably our spec_helper and rails_helper configurations are different -- I've been using rails_helper everywhere because of the DatabaseCleaner config

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Should DatabaseCleaner be in spec_helper since it's not Rails specific?

Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm. I think that any tests that persist data will be Rails-specific, so it's probably fine how it is?

end

def create_valid_mock_draw_with_students(num_students: 2)
students = Array.new(num_students) { |_n| instance_spy('user') }
Copy link
Collaborator

Choose a reason for hiding this comment

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

The block argument isn't necessary here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good catch!

end
end

def create_mock_draw_activator(param_hash)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think that the create_ prefix can be dropped from these method names

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍

@esoterik
Copy link
Collaborator

re: codeclimate erroring on the TODOs:

I think we should keep the checks because otherwise TODOs end up getting lost in the codebase, but we can keep some of them in place.

Resolves #51
- Add status enum to draws (current values 'draft' and 'pre-lottery',
  with 'lottery' and 'post-lottery' to be implemented in the future)
- Add intent deadline to draws
- Add button to initiate draw on draw show view with appropriate route
- Add DrawActivator to check validity of draw setup and handle
  transition to pre-lottery stage
- Add StudentMailer with initial student invitation e-mail
- Add DrawsHelper with method to generate a string describing the
  relative time since/until the intent deadline
@orenyk
Copy link
Contributor Author

orenyk commented Jan 12, 2017

@esoterik ok I removed all but the one TODO we discussed and addressed the other issues. My only last concern is the rails_helper vs spec_helper question - see my note above about DatabaseCleaner. I'm fine to just use rails_helper everywhere, but I've read articles suggesting not to (here's one example). Let me know what you think.

Also squashed 😄. Thanks!

@esoterik
Copy link
Collaborator

this is good to go pending resolution of the DatabaseCleaner config

@orenyk orenyk merged commit 8091e70 into master Jan 13, 2017
@orenyk orenyk deleted the 51_draw_phase_intent_deadline branch February 7, 2017 04:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants