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

Refactor Reservation Scopes / Validations #1288

Closed
orenyk opened this issue Aug 16, 2015 · 30 comments
Closed

Refactor Reservation Scopes / Validations #1288

orenyk opened this issue Aug 16, 2015 · 30 comments

Comments

@orenyk
Copy link
Contributor

orenyk commented Aug 16, 2015

Let's refactor the ReservationScopes module to use Concerns Query Objects; this will hopefully help address #1097. We'll also try to use Form Objects to clean up the validations and finally rid ourselves of the ReservationScopes and ReservationValidations modules!

@orenyk orenyk self-assigned this Aug 16, 2015
@orenyk orenyk added this to the 5.3.0 milestone Aug 16, 2015
orenyk added a commit that referenced this issue Aug 16, 2015
Resolves #1288
- switch ReservationScopes module over to concern format
- switch ReservationValidations module over as well
- move both files to app/models/concerns
@orenyk
Copy link
Contributor Author

orenyk commented Aug 16, 2015

Ok, this is done and appears not to break anything. I'll check and see if that helped with the CodeClimate score and open a PR if it does.

@orenyk
Copy link
Contributor Author

orenyk commented Aug 16, 2015

Damn that didn't help... one option I've seen is to refactor scopes into multiple files (see here), but that seems overly complex. I'll do a bit more research to see if there are any other ways we can fix the scope clutter...

@orenyk
Copy link
Contributor Author

orenyk commented Aug 16, 2015

I don't know if this helps us, necessarily, but this is a great article with some fairly advanced techniques for improving Models.

EDIT Nvm, look at 4 - Extract Query Objects. I think we're on to something...

@orenyk
Copy link
Contributor Author

orenyk commented Aug 16, 2015

Yup, here's some more stuff that might be useful:

1
2

@caseywatts
Copy link
Collaborator

+1 to query objects! :D

@caseywatts
Copy link
Collaborator

If it would help, I'm happy to talk through the architecture of this :)

@orenyk orenyk changed the title Refactor Reservation Scopes Refactor Reservation Scopes / Validations Aug 17, 2015
@orenyk orenyk added diff: 5 and removed diff: 1 labels Aug 17, 2015
@orenyk orenyk modified the milestones: 5.4.0, 5.3.0 Sep 7, 2015
@orenyk
Copy link
Contributor Author

orenyk commented Oct 13, 2015

Ok, back on this issue. I wrote my first query object but am having trouble with loading it (since I put them in app/queries and as far as I can tell app/models loads first, so when it tries to find the query object it fails. I'll get back to this later.

@orenyk
Copy link
Contributor Author

orenyk commented Oct 13, 2015

Nope, it was just a typo in my class name 😞

@orenyk
Copy link
Contributor Author

orenyk commented Oct 13, 2015

Alright, it looks like this improves our CodeClimate score for the reservation_scopes file, so I'm moving them all over 😄

@orenyk
Copy link
Contributor Author

orenyk commented Oct 13, 2015

I've discovered that our upcoming checkin e-mail was potentially including reservations that had not been checked out (e.g. a 1-day reservation) and or reservations that had already been checked in... looks like we messed up our scope at some point. I'll fix that in this issue, increasing priority.

@orenyk
Copy link
Contributor Author

orenyk commented Oct 13, 2015

@esoterik: looks like we broke this in 766e9ab - any idea why we removed the not_returned scope? Seems like this might have potentially broken a bunch of stuff...

@orenyk
Copy link
Contributor Author

orenyk commented Oct 13, 2015

Seems like our tests are insufficient if we broke this... we need to add some edge cases to our mailer / rake task specs (or add specs for the query objects themselves)? I'm opening this up a separate issue and I'll move over the current scopes as they are.

@orenyk orenyk added pri: 2 and removed pri: 5 labels Oct 13, 2015
@orenyk
Copy link
Contributor Author

orenyk commented Oct 14, 2015

Also note that we'll be removing the delegate hackery and add #new calls to each of the Query Objects for clarity (so it's obvious we're passing an instance of the object, and not the class itself).

@orenyk
Copy link
Contributor Author

orenyk commented Oct 20, 2015

@caseywatts: let's shift discussion of this issue over here (re: your Slack comments). Based on what you wrote I'm going to move multipurpose scopes to the model file and then use Query Objects for single-use scopes. I'll also be writing tests for our scopes as well - where/how does one generally test Query Objects (since it seems somewhat coupled to the resource / model itself)? Do you just rely on the tests for the use case itself (kinda like an integration tests)?

@orenyk
Copy link
Contributor Author

orenyk commented Nov 1, 2015

Started working on getting rid of the delegation stuff, might have broken some stuff. Also read a bunch about how people generally handle scopes, will keep working on cleaning this up this week.

@orenyk
Copy link
Contributor Author

orenyk commented Nov 3, 2015

More cleanup... still not 100% sure on how best to structure these scopes and where to add specs.

@orenyk
Copy link
Contributor Author

orenyk commented Nov 11, 2015

After starting to rearrange the scopes (and moving them back into the Reservation model for clarity), we're understandably seeing a decrease in the score for the Reservation model but benefiting from the removal of the separate scopes module. I'll still keep complex scopes in separate Query Objects but any scope based on a single parameter seems as though it belongs inside the model rather than creating a separate PORO for it.

Hoping to wrap this up today and making sure that we haven't broken anything 😄.

@orenyk
Copy link
Contributor Author

orenyk commented Nov 11, 2015

Gotta run to a meeting, but it seems as though changing the future scope to a query object broke things since it's not chaining properly. I'll look more into this later (there's one other thing broken in the specs that I haven't had time to look into).

@orenyk
Copy link
Contributor Author

orenyk commented Nov 16, 2015

Fixed up the rubocop errors, now trying to debug the failing spec.

@orenyk
Copy link
Contributor Author

orenyk commented Nov 16, 2015

Weird, according to this post we should be getting chainable queries with Query Objects due to the way that Rails handles things. That doesn't seem to be the case, so I'll have to investigate further.

@orenyk
Copy link
Contributor Author

orenyk commented Nov 16, 2015

all specs pass, now it's just time to figure out why chaining broke...

@orenyk
Copy link
Contributor Author

orenyk commented Nov 17, 2015

Ok, I think the chaining issue is because we didn't end up originally keeping the delegate call in our Query Objects. My guess is because the scopes were defined as Reservations::ScopeQuery.new, and since we weren't passing in a relation it was defaulting to Reservation.all for all queries. The only way we can chain scopes is if we use the delegation method as suggested in this article, which seems like it ensures that each time we send call to the query object, it delegates it to a new instance of that query object. (this isn't entirely wrong but I've done a bunch of reading since then so I'm just going to strike it out for clarity)

... a few hours of reading later ...

Ok, after investigating further I think I figured this out. If we define the scope as follows:


class FooQuery
  def initialize(relation=default)
    # stuff
  end

  def call
    # more stuff
  end
end

...

scope :foo, FooQuery.new

then Rails instantiates a copy of the FooQuery class when initializing Rails, and since no relation is passed in as a parameter all calls of that scope use the default relation as the base. However, if we use delegation in the query, so that we have:

class FooQuery
  class << self
    delegate :call, to: :new
  end

  def initialize(relation=default)
    # stuff
  end

  def call
    # more stuff
  end
end

...

scope :foo, FooQuery

then we're instantiating a new copy of FooQuery each time we call the scope, and Rails is appropriately passing a chained relation as a parameter to FooQuery.new so everything works. If we wanted to use the first definition for Query Objects, we'd have to manually instantiate a new copy of the object, also manually passing the chained relation to new.

@caseywatts can you review this and make sure it makes sense / I haven't missed anything? In the meantime, I'm going to set this branch up with the latter formulation since that makes more sense to me at the moment. Thanks!

@orenyk orenyk modified the milestones: 5.5.0, 6.0.0 Nov 23, 2015
orenyk added a commit that referenced this issue Jan 4, 2016
Resolves #1288
- add a set of POROs for Query Objects where appropriate
- move reservation scopes back into the model file
- update code where necessary
orenyk added a commit that referenced this issue Jan 12, 2016
Resolves #1288
- add a set of POROs for Query Objects where appropriate
- move reservation scopes back into the model file
- update code where necessary
orenyk added a commit that referenced this issue Jan 12, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

3 participants