Make sure the directory sync services returns consistent object types#148
Make sure the directory sync services returns consistent object types#148
Conversation
Codecov Report
@@ Coverage Diff @@
## main #148 +/- ##
==========================================
- Coverage 98.29% 97.68% -0.61%
==========================================
Files 38 39 +1
Lines 822 865 +43
==========================================
+ Hits 808 845 +37
- Misses 14 20 +6
📣 Codecov can now indicate which changes are the most critical in Pull Requests. Learn more |
| # Warn the user not to use the Hash-style access for attributes. | ||
| warning_message = "WARNING: The Hash style access for DirectoryUser attributes is deprecated and will be removed | ||
| in a future version. Please use user.#{attribute_name} instead of user['#{attribute_name}']" | ||
| puts warning_message |
There was a problem hiding this comment.
I think we want to use warn instead of puts for logging warnings.
Looks like there's also a category option and a :deprecated value that can be passed to warn, but I'm not sure if that option is supported on some of the older Ruby versions we apparently support.
| extend T::Sig | ||
|
|
||
| attr_accessor :id, :name | ||
| attr_accessor :id, :name, :custom_attributes, :raw_attributes |
There was a problem hiding this comment.
I noticed that DirectoryUser exposes :custom_attributes and :raw_attributes through attr_accessor, so adding them here as well for consistency. I was originally planning to exclude these two from the hash, but since they're available for access on DirectoryUser, I figured it would be safer to keep them (and add them to DirectoryGroup for consistency).
| } | ||
| end | ||
|
|
||
| def to_hash |
There was a problem hiding this comment.
I think the idiomatic naming for this method would be to_h, similar to Array#to_h.
Also, do we want to be adding a to_h to the public API for just these objects? I'd be fine if we left this out, and didn't mention hash conversion in our warning message.
There was a problem hiding this comment.
I went back and forth on to_h vs to_hash and eventually settled on to_hash, imitating the one on the Hash itself - https://apidock.com/ruby/Hash/to_hash (and because it's marginally more descriptive). But I don't have a strong opinion on this one way or another - happy to change to to_h, if you think that's better/more consistent.
As far as leaving it out altogether, I'm a little uncomfortable with that - if somebody was using a Hash method on the result, then all of a sudden, we don't really have support for it. This way, they at least have an easy work around. Counter-argument: we can just suggest that they call to_json as a workaround (which is what I'm doing here anyway), but that starts to feel a bit hacky. Also, converting the User or Group info into a Hash kinda makes sense to me - we start off with basically a Hash of data from the IdP, doesn't seem like that much of a stretch to give them a direct way to convert it back to a Hash?
There was a problem hiding this comment.
hmm, well Hash implements both to_h and to_hash, and between the two, it sounds like to_h is the more generally applicable method. But I don't feel strongly about this either, so go with your gut.
I'm still not convinced that we need to support to_hash going forward. Isn't it part of the DeprecatedHash API?
In either case, we can punt on making a decision here until we are ready to remove DeprecatedHash completely.
There was a problem hiding this comment.
👍 that's compelling enough :), went ahead and switched to using to_h. As far as whether we need it or not, we can't quite punt until removing DeprecatedHash, or at least not without potentially breaking users again 😭 - my current warning message suggests using to_h to get a Hash, if you're trying to use a Hash method on the results... so it wouldn't be great if we removed support for the method we were telling people to use 😅.
Isn't it part of the
DeprecatedHashAPI
I currently have the method independent of DeprecatedHash - DirectoryGroup and DirectoryUser both implement it, so I've not really been treating as a part of DeprecatedHash, as much as a backwards compatibility helper for anyone who really wants to be able to get the results as a Hash.
There was a problem hiding this comment.
Ah, good point.
I think there's two options here:
- We consider
to_has deprecated, and remove it when we removeDeprecatedHash, since none of the other resource objects (WorkOS::Profile, etc.) implement ato_h. So then we would also want to update that warning message now, and avoid tell them to useto_h. - We keep
to_hhere, but then we also implement it on the rest of the resource objects as well.
So, my goal here is that we don't end up with a different kind of inconsistency in our API with DirectoryUser and DirectoryGroup being the only classes that implement to_h,
Does that make sense?
My initial preference was for option 1 since it seemed like less work, but I would be fine with option 2 as well.
There was a problem hiding this comment.
Yeah, that 💯 makes sense... as for the options ... option 1 is definitely less work, but 🤔 I still feel bad for taking the Hash away after users have potentially been using it this whole time. So, I guess I prefer option 2...
Though that also commits us to implementing a to_h method any time we add a new class in the future, which is also annoying and liable to end up with inconsistent behavior if we forget to. So, I'm waffling on my preferences a bit.
There was a problem hiding this comment.
Though that also commits us to implementing a to_h method any time we add a new class in the future, which is also annoying and liable to end up with inconsistent behavior if we forget to.
Probably wouldn't be a bad idea to refactor those classes, and maybe have them all inherit from a base class that provides to_h, etc.
But yeah, we'd have to commit to doing that work.
There was a problem hiding this comment.
👍 to a shared base class to include the to_h implementation... though it still doesn't prevent future classes from showing up that don't inherit from the base class 😭. But we can probably add tests and/or linters to try and catch that.
Either way, I think it's worth shipping this PR as it is now, then coming back with a separate one to add to_h to everything else - does that seem reasonable to you?
There was a problem hiding this comment.
Either way, I think it's worth shipping this PR as it is now, then coming back with a separate one to add to_h to everything else - does that seem reasonable to you?
That sounds good! I think we are good to go here.
|
|
||
| def object_name | ||
| case self | ||
| when WorkOS::DirectoryUser |
There was a problem hiding this comment.
It's a little inverted to have a superclass know about the specific classes that inherit from it.
We could have DeprecatedHash have a default implementation like def object_name = "object", and the other classes can override it with their specific name.
However, given we don't plan on supporting this going forward, not a big deal if we stick with this for simplification of removal later.
There was a problem hiding this comment.
Hmmm, good point... I can just make it more generic. We'll end up with directory_user instead of user in the warning message, but I think that's ok(?)
|
|
||
| module WorkOS | ||
| VERSION = '2.2.0' | ||
| VERSION = '2.2.1' |
There was a problem hiding this comment.
Do you mind submitting these version changes in a separate PR after we get these behavior changes merged?
That's usually how we do it.
| # call the original implementation of :replace in Hash, | ||
| # so we don't show the deprecation warning | ||
| def replace_without_warning(new_hash) | ||
| method(:replace).super_method&.call(new_hash) |
There was a problem hiding this comment.
This is a very pragmatic solution. Love it. 🎉
|
Yup! Unless any last second issues turn up, I'm planning to merge this in today. I'll then start a separate, follow-up PR to address #148 (comment), and once that's in, I think we can go ahead and publish a new gem? |
Closes #147
#147 reports an inconsistency in the data types that the Ruby SDK returns for the
DirectorySyncmodule:list_usersendpoint returns an Array ofWorkOS::DirectoryUserobjects, however, retrieving an individual user record via theget_userendpoint simply returns a Hash of the attributes of the user.list_groupsandget_groupendpoints.The unfortunate result of this discrepancy is that we need to write different code in order to access attributes on the objects returned by the two endpoints. e.g. for the
usernamefield:list_usersresult, access as an object property:results.data.first.usernameget_userresult, access as a value in a Hash:result["username"]Making a change here to have all the endpoints return their results as objects. In order to support backwards compatibility for the Hash-style access (which is currently required to access attributes on the results of the
get_userandget_groupcalls), adding a[]method to both classes, which maintains Hash-style access for the models' attributes.Long term, we'd like to standardize on always treating the results as objects, not Hashes, so the Hash-style accessors will eventually be removed. Currently, using them will output a warning, noting the upcoming deprecation.