-
Notifications
You must be signed in to change notification settings - Fork 2
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
Conversation
c644d0b
to
a4034ee
Compare
There was a problem hiding this 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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)) %> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 😕
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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_spy
s, 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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
?
@esoterik ok I think this is ready! |
This is going to fail rubocop, btw, I'll fix those after the build. |
I'll also check out codeclimate in a bit. |
The only CodeClimate failures at this point should be some |
There was a problem hiding this 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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. | ||
# |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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' |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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') } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
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
d4c1d86
to
065274c
Compare
@esoterik ok I removed all but the one TODO we discussed and addressed the other issues. My only last concern is the Also squashed 😄. Thanks! |
this is good to go pending resolution of the DatabaseCleaner config |
Resolves #51