-
Notifications
You must be signed in to change notification settings - Fork 257
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
Add update on classes #125
Conversation
Can you rebase this on master and fix any conflicts? Thanks. |
90c7351
to
361a75b
Compare
I've rebased this onto the current master and fixed the conflicts. Tests are passing and this PR is ready for review. |
@@ -185,8 +197,7 @@ def [](property) | |||
# @param params [Hash] hash of properties to update | |||
# @return [Hubspot::Company] self | |||
def update!(params) | |||
query = {"properties" => Hubspot::Utils.hash_to_properties(params.stringify_keys!, key_name: "name")} | |||
Hubspot::Connection.put_json(UPDATE_COMPANY_PATH, params: { company_id: vid }, body: query) | |||
self.class.update!(vid, params) | |||
@properties.merge!(params) |
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 don't think this is a blocker for this PR since we didn't have this behavior already but we're only updating the properties with the changes from params
which means we're not updating dynamic properties such as timestamp
and versions
. I think the best approach is to copy the properties from the returned object to the current object which is what ActiveRecord
reload() does.
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.
Interesting catch 🎣 I agree that this isn't a blocker for this PR.
Based on your description, I'm imagining something like:
response = self.class.update!(vid, params)
# Re-initializes the object based on a hash of values from the API call
# and updates the state of the object
refresh_from(response)
** Updated **
I've created a ticket to capture this concern: https://trello.com/c/0nFLC7xU.
As someone who hasn't used the HubSpot API, do you think it's typical that users will want access to the version information?
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.
As noted by @fonji below, the HubSpot just returns a 204 so there isn't really anything in the response to refresh from is there?
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 Hubspot APIs are inconsistent with their return results from updates. Some only return a 204, some return a partial record with the changed fields, and others return the entire record.
I’ve mostly worked this functionality out on the master branch but I’ve not yet pushed it since I didn’t finish the tests yet.
# @param params [Hash] hash of properties to update | ||
def update!(vid, params = {}) | ||
query = {"properties" => Hubspot::Utils.hash_to_properties(params.stringify_keys!)} | ||
Hubspot::Connection.post_json(UPDATE_CONTACT_PATH, params: { contact_id: vid }, body: query) |
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 not consistent with the other added update!()
methods in that it doesn't return a new instance of the updated resource. This should return a new instance of the object.
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 problem is that this endpoint
Returns a 204 No Content response on success.
https://developers.hubspot.com/docs/methods/contacts/update_contact
So if you want to return a new instance, you'd have to fetch it, which makes two calls instead of one.
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.
@fonji Good point. For reasons like this, I think we are headed in the direction of always returning a Hubspot::Response
object instead of attempting to return an instance of the class. PR #171 moves us in that direction, starting with some of the update
actions. Once @cbisnett has time to review and approve this approach (and given it's the holidays, that may not be until after the new year) we can continue updating all the actions to either raise
or return a Hubspot::Response
, with the goal of releasing these changes as part of the v1
release.
# @param params [Hash] hash of properties to update | ||
# @return [Hubspot::Company] Company record | ||
def update!(vid, params) | ||
params.stringify_keys! |
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.
Do we need to call stringify_keys
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.
Seems unnecessary as it looks like Hubspot::Utils::hash_to_properties
calls to_s
on the keys:
https://github.com/adimichele/hubspot-ruby/blob/945a8717f260fc064ef6b34f427fc3333f28aaf4/lib/hubspot/utils.rb#L20
# @return [Hubspot::Company] Company record | ||
def update!(vid, params) | ||
params.stringify_keys! | ||
query = {"properties" => Hubspot::Utils.hash_to_properties(params, key_name: "name")} |
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.
Interesting, I've only looked at a handful of examples but it seems the HubSpot v1 endpoints use "property" and the v2 endpoints use "name". Good to know.
# @param vid [Integer] hubspot company vid | ||
# @param params [Hash] hash of properties to update | ||
# @return [Hubspot::Company] Company record | ||
def update!(vid, params) |
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 a bit trivial but what do you think of calling this method update
without the !
? I tend to avoid the use of !
unless there is a safe version of the function. For example, stringify_keys!
has the alternative stringify_keys
.
I find that avoiding defining bang (!) methods leads to more descriptive function names. Since !
is a rather debated topic, I find it's use doesn't hold much value, except in the event where !
means there's an alternative option.
Better Naming example: Sending an invite email to a user. Instead of writing invite!
to warn the reader something will happen, send_invite
is a clearer message that informs the reader that an invite will be sent.
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.
Other classes in the gem seem to use the !
on persistence style method calls. I was assuming this was following the ActiveRecord convention of bang versions of persistence methods raising exceptions whereas the non-bang version returns true/false.
@@ -49,6 +49,24 @@ | |||
its(['num_associated_contacts']) { should eql '1' } | |||
end | |||
|
|||
describe ".update!" do | |||
cassette "company_update_class" | |||
let(:company) { Hubspot::Company.create!("New Company #{Time.now.to_i}") } |
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.
What do you think of avoiding the use of its
, since its
is deprecated in RSpec3, and the use of lets
?
In regards to the use of lets
, I find that avoiding the use of lets
results in:
- easier to read tests because all of the actors referenced in the test are declared in the setup step within the
it
block. - less brittle tests, because each example only specifies the information it needs.
- faster tests, because each example is running with a smaller data set.
There's a great robots blog post here, if you'd like to read more: https://robots.thoughtbot.com/lets-not.
Alternative to lets
:
describe ".update!" do
it "updates the record" do
VCR.use_cassette("company_update_class") do
company = Hubspot::Company.create!("New Company #{Time.now.to_i}")
...any other test setup...
Hubspot::Company.update!(company.vid, params)
...expectations...
end
end
end
The DealPipeline spec follows a similar pattern.
@cbisnett Thank you so much for pushing this along! I left some comments and I'll be happy to take another look when you're ready. |
Hello! |
@fonji Don't worry about making these updates. I'll finish them and get this merged. Thanks for your work on the initial PR. |
The company I am working for needs this functionality too. We wrote our own class method to achieve this which re-uses the utils class and the global connection object. I was checking to see if it would make sense for us to contribute that work back to the gem and found this PR. Our implementation looks almost identical to what @fonji came up with here. We cache HubSpot 'vids' that correspond to objects in our system so having to retrieve the object before updating adds an unnecessary API call to the process. This would be a useful option to have on all the resource classes. |
Replaced by #209 |
It's kind of like
update_all
, except just for one element.Allows to update a company, contact or deal without the need of instanciating it.
Usefull if you have an application which knows vids and you want to avoid fetching from hubspot to update known fields.
Also adds specs for Deal#update!