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

Added Utility Methods to Aws::Record Model Interface #82

Merged
merged 19 commits into from
Jun 22, 2018
Merged

Added Utility Methods to Aws::Record Model Interface #82

merged 19 commits into from
Jun 22, 2018

Conversation

YashoSharma
Copy link
Contributor

@YashoSharma YashoSharma commented Jun 19, 2018

Description of changes:
Added:

destroyed?
new_record?
persisted?
update
update!
assign_attributes

Added unit tests to test this new functionality.
Performed integration tests to ensure no regression occurred.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

to_param
destroyed?
new_record?
persisted?
update
assign_attributes
Copy link
Member

@awood45 awood45 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good start, some changes proposed.

hkey = public_send(self.class.hash_key)
if self.class.range_key
rkey = public_send(self.class.range_key)
"#{hkey}:#{rkey}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we document why this is the separator? It's very possible and valid for these key values to contain the ":" character. Depending on what the API contract is, we may need to do more here.

end

# Updates model attributes and then performs a conditional save
def update(opts)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also add an #update! method, which essentially does the same thing except it raises an exception when the validation fails. Check the implementation of #save!

if !respond_to?("#{field}=")
raise ArgumentError.new "Invalid field: #{field} for model"
end
@data.set_attribute field, new_value
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Style point in this repo, we explicitly use () characters around any non-empty method calls. So:

@data.set_attribute(field, new_value)


# Updates model attributes and then performs a conditional save
def update(opts)
self.assign_attributes opts
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add () characters

@@ -151,6 +169,10 @@ def _perform_save(opts)
)
end
end
data = self.instance_variable_get("@data")
data.destroyed = false if data.destroyed
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the "if" condition here? It looks like the end condition is always going to be false or false-equivalent.

item.body = SecureRandom.uuid
item.save

new_key = SecureRandom.uuid
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd strongly encourage that we use explicit values in these tests. Why do we need randomness?

@@ -77,6 +77,23 @@ module Record
end
end

describe "#new_record" do
it 'marks records fetched from a client call as not being new' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should go ahead and add the "obvious" test that something created by hand is marked as a new record. Part of the test coverage notion is to avoid surprise regressions later.

@@ -236,6 +260,39 @@

end

describe "#update" do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also looks like we never actually test-call the #update method in these tests.

@@ -266,28 +303,60 @@
klass.configure_client(client: stub_client)
end

it 'should perform a hash based attribute assignment without persisting changes' do
it 'assign_attributes should perform a hash based attribute assignment without persisting changes' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should test this with ActiveModel::Model present as well.

hkey = public_send(self.class.hash_key)
if self.class.range_key
rkey = public_send(self.class.range_key)
"#{hkey}:#{rkey}"
"#{URI.encode(hkey)}##{URI.encode(rkey)}"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did we come up with the # character as a separator? We may need to do something more detailed like including the key names. That way, there's something concrete we can use as start/end points of most param values.

Copy link
Member

@awood45 awood45 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good on code. For completeness, we need to:

  • Get the new method documentation up to the standard of other methods in aws-record. Should have usage examples, parameters explained, and return values tagged. For example, bring the update methods in parity with the save methods.
  • Add Cucumber tests, especially for the update method.
  • Add CHANGELOG notes explaining what we added.

Copy link
Member

@awood45 awood45 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very close!

CHANGELOG.md Outdated
@@ -1,6 +1,10 @@
Unreleased Changes
------------------

* Feature - Aws::Record::DirtyTracking - Add the `persisted?`, `new_record?`, and `destroyed` methods to `Aws::Record::DirtyTracking`, which supports use cases where you'd like to see if a record has just been newly initialized, or has been deleted or was a preexisting record retrieved from DynamoDB. Note that these methods are present in `ActiveModel::Model` so you should require that module before `Aws::Record`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the changelog, let's avoid DirtyTracking and ItemOperations. These don't mean anything to an end user, they're basically code organization. Rather, just explain that these methods will now be tacked on to your models.

Also, we may need an "UPGRADING NOTES" bit on ActiveModel, I'll talk with you about that tomorrow.

@@ -91,14 +94,20 @@ def assign_attributes(opts)
end
end

# Updates model attributes and then performs a conditional save
# Mass assigns the attributes to the model and then performs a save
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add usage examples to these, since attribute assignment is involved.

#
# @param [Hash] opts
# @raise [Aws::Record::Errors::ValidationError] if any new values
# violate the models validations.
def update!(opts = {})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can #update! be called without any options? This seems to contradict what we did in #update. It possibly makes the most sense to require the hash to have some attributes.

Copy link
Member

@awood45 awood45 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code is well formed, but some interesting questions on design form. We may at least want to update examples.

# model.assign_attributes(id: 7, name: "Janeß")
# model.id # => 7
# model.name # => "Jane"
# model.persisted? # => false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, is this true? This should be a true value if I understand persisted? properly, and per the ActiveRecord documentation of the same. Is this a mistake?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might be thinking of the #dirty? method, which would be false after saving, and true again after this operation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I get this, we changed the key. A better example would NOT change the key, rather some non-key attributes.

#
# model.update(name: "Janeß")
# model.name # => "Jane"
# model.persisted? # => true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're thinking of the dirty? method here, which would be false since the attribute changes and save were concurrent. This also would benefit from an example where we are not changing the key.

end

# Updates model attributes and validates new values
#
# You can use the +:force+ option to perform a simple put/overwrite
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually raises some interesting semantic questions. In ActiveRecord, they generally step around this because the ID field is a semi-hidden field. Where here, #update could be a put_item operation depending on the context. I don't think this is inherently wrong, but we either want to:

  1. Call this out more explicitly in documentation.
  2. Prevent the changing of key attributes in #assign_attributes, ensuring a pure update style.

Thoughts?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While it initially seems wrong for the keys to be mutable, the current implementation is consistent with what happens if the user manually changes the key value and performs a save.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with you. I just think we should be explicit about this, because it has a chance to be a surprise to some users.

@awood45 awood45 merged commit b4dadfc into aws:master Jun 22, 2018
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.

None yet

2 participants