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

Add update on classes #125

Closed
wants to merge 4 commits into from

Conversation

fonji
Copy link
Contributor

@fonji fonji commented Jul 11, 2018

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!

@cbisnett
Copy link
Collaborator

Can you rebase this on master and fix any conflicts? Thanks.

@cbisnett
Copy link
Collaborator

I've rebased this onto the current master and fixed the conflicts. Tests are passing and this PR is ready for review.

@cbisnett cbisnett self-assigned this Nov 21, 2018
@@ -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)
Copy link
Collaborator

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?

Copy link
Contributor

@SViccari SViccari Nov 21, 2018

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?

Copy link

@jesseclark jesseclark Jun 18, 2019

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?

Copy link
Collaborator

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)
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 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.

Copy link
Contributor Author

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.

Copy link
Contributor

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!
Copy link
Contributor

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?

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")}
Copy link
Contributor

@SViccari SViccari Nov 21, 2018

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)
Copy link
Contributor

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.

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}") }
Copy link
Contributor

@SViccari SViccari Nov 21, 2018

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.

@SViccari
Copy link
Contributor

@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.

@fonji
Copy link
Contributor Author

fonji commented Nov 22, 2018

Hello!
Thank you for all the work and comments!
I'm sorry as I don't have much time to change this, can you guys handle it from now on? 😥

@cbisnett
Copy link
Collaborator

@fonji Don't worry about making these updates. I'll finish them and get this merged. Thanks for your work on the initial PR.

@SViccari
Copy link
Contributor

Before pushing this along further, I think it's worth settling on the direction that's proposed in PR #171. PR #171 is just pending on @cbisnett's approval.

@jesseclark
Copy link

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.

@fonji
Copy link
Contributor Author

fonji commented Nov 8, 2019

Replaced by #209

@fonji fonji closed this Nov 8, 2019
@fonji fonji deleted the add_update_on_classes branch November 8, 2019 15:47
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.

4 participants