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

@fancyremarker => Modify garnered_find to accept multiple arguments #63

Merged
merged 6 commits into from
Oct 18, 2013

Conversation

mzikherman
Copy link
Contributor

Modifies garnered_find to accept a splat to match the API of Mongoid's find .

You can now call Artist.garnered_find('andy-warhol','pablo-picasso') or Artist.garnered_find(['andy-warhol','pablo-picasso'])

I think this is a straightforward fix except for the following cases:

Artist.find('andy-warhol')  # Returns single object
Artist.find(['andy-warhol'])  # Returns an array of a single object

In these cases, the return type should match what was passed in. I couldn't figure out a great way to figure out from *args what the original arguments were, thus: https://github.com/artsy/garner/pull/63/files#diff-617bcfaf854a9b15e531702bca4eabafR64

That has the effect of properly returning an array of one (containing the object), vs the object itself (matching find).

@dblock
Copy link
Contributor

dblock commented Oct 16, 2013

This looks great. Could you please update CHANGELOG, too (add a Next Release). Thanks.

Monger.garnered_find([ "m1", "m2" ]).should == [ @object, @object2 ]
end

it "is invalidated when one of the objects is changed" do
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you need to add an additional spec for the array version:

Monger.garnered_find(["m1", "m2"]).should == [ @object, @object2]
@object2.update_attributes!({ :name => "M3" })
Monger.garnered_find(["m1", "m2"]).last.name.should == "M3"

I don't think it'll work with the above code. (See my comment there.)

Copy link
Contributor

Choose a reason for hiding this comment

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

So I double-checked and the above spec does pass. However, all the new specs pass on the old code, assuming that you change the garnered_find(arg1, arg2) calls to have an arity of 1 (garnered_find([arg1, arg2])). So let's try to construct a spec that will fail on the old code.

@fancyremarker
Copy link
Contributor

Thanks a ton for this! It looks great, with the small exception of the case of cache freshness on Mongoid.garnered_find(["m1", "m2"]), per my comment. Can you verify that with a test?

#
# @example Find by multiple id's in an array.
# Garner::Mixins::Mongoid::Document.garnered_find([ BSON::ObjectId.new, BSON::ObjectId.new ])
#
Copy link
Contributor

Choose a reason for hiding this comment

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

I might delete all but one of these commented lines.

@mzikherman
Copy link
Contributor Author

So, the specific issue in #62 failed as a spec here: https://github.com/artsy/garner/pull/63/files#diff-53b60d8911baf05f72df2717fd871d24R115 with my changes.

Digging in it seems that "m1" and ["m1"] was is binding to the same key, so explicitly adding a value when a singular object is passed as an array was the only way I saw to fix (w/o going into how they get generated- which I will do later today if I have time). This fix now seems kind of kludgy. What do you think?

@fancyremarker
Copy link
Contributor

Great work @mzikherman, and excellent spec reproducing the error.

I agree with you that any fix necessitates passing in different keys depending on whether garnered_find was called with array arguments (e.g., ["m1"]) versus single object arguments (e.g., "m1"). One way to make the solution less kludgy is to just use the actual arguments passed in as the key. So, instead of keying both key({ :array => true }) and key({ :source => :garnered_find }), we could just do a single key({ :garnered_find_args => args }). Something like this:

def self.garnered_find(*args)
  identity = Garner::Cache::Identity.new
  args.flatten.each do |arg|
    binding = identify(arg)
    identity = identity.bind(binding)
  end
  identity.key({ :garnered_find_args => args }) do
    find(*args)
  end
end

Would that work here?

@mzikherman
Copy link
Contributor Author

Great call on the keys @fancyremarker . Rather than including the semi-arbitrary :array => true or :source => :garnered_find , we have the args right there and we might as well include them. That also solves the 'special case' of an array of one item vs an item itself cleanly. I just pushed an update to the PR that incorporates this- I'd love your feedback. I can of course rebase/squash if and when this might be mergeable.

identity = Garner::Cache::Identity.new
identity.bind(binding).key({ :source => :garnered_find }) do
find(handle)
args.each do |arg|
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you may still want to args.flatten here. You'll get a valid result without flattening, but you likely will not get cacheability.

@fancyremarker
Copy link
Contributor

@mzikherman: This looks great. See my comment; I'd like to hear your thoughts as I may be mistaken. Also, no need to rebase/squash. Better to keep the context for our conversation intact, IMO.

@mzikherman
Copy link
Contributor Author

I'm still a little bit confused as to the need for flatten. Could you clarify a bit more and I'll try to write a spec? Just pushed a spec that find only gets called once when garnered_find called on an array of one object - is that what you meant?

@fancyremarker
Copy link
Contributor

The need for flatten comes when you have a single array argument with N members, instead of N arguments. Consider the following:

garnered_find("foo", "bar") yields

# args = ["foo", "bar"]

# within args.each loop...
binding = identify("foo")
identity = identity.bind(binding)

binding = identify("bar")
identity = identity.bind(binding)

While garnered_find(["foo", "bar"]) yields

# args = [["foo", "bar"]]

# within args.each loop...
binding = identify(["foo", "bar"])
identity = identity.bind(binding)

identify(["foo", "bar"]) won't resolve to a valid proxy_binding, since the array isn't a valid ID or slug, and thus won't have a garner_cache_key, which means it can't be cached.

Does that help explain things at all?

@mzikherman
Copy link
Contributor Author

Ah yes, it does. I just pushed a trivial spec that sure enough has find being called twice on foo in your example- indicating not caching when called with an array, and correctly with flatten (which makes total sense now- thanks!).

@mzikherman
Copy link
Contributor Author

Yea, that seems totally obvious now, dont know how I didn't get that.

@fancyremarker
Copy link
Contributor

Thanks @mzikherman, merging. Want to cut and release a new gem version?

fancyremarker pushed a commit that referenced this pull request Oct 18, 2013
@fancyremarker => Modify garnered_find to accept multiple arguments
@fancyremarker fancyremarker merged commit dbc92bb into artsy:master Oct 18, 2013
@mzikherman
Copy link
Contributor Author

AWESOME!! I've never done that before but would love to.

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

3 participants