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

Telling a representer to represent nil adds all of that class's methods to nil #16

Open
j-clark opened this issue Nov 24, 2015 · 10 comments

Comments

@j-clark
Copy link

j-clark commented Nov 24, 2015

The code causing this behavior is https://github.com/ruby-grape/grape-roar/blob/master/lib/grape/roar/representer.rb#L10

Example

user = User.find_by(email: 'i_dont_exist@gmail.com')
# user => nil
present user, with: User::Representer
# User::Representer.ancestors => [..., Grape::Roar::Representer, ...]

Obviously there's a bug in this application code, but the result is that, since nil is a singleton, now every time the application asks for nil, it gets it, but with all kinds of fun stuff mixed in, like a custom #to_hash that, for instance, mucks with the instantiation of ActionController::Parameters and causes completely unrelated pages in an app to blow up

Thoughts? I'd be happy to submit a PR, but i only have a cursory understanding of all the interplay between grape, roar, grape-roar, representable, etc. and just tossing in a nil check might not be the best approach

@dblock
Copy link
Member

dblock commented Nov 25, 2015

This is happening here, maybe we should protect the code from extending singletons, nil being one, maybe there's a better way than comparing to a list? Also is there any case where we would want to represent what would otherwise be a singleton?

@dblock dblock added the bug? label Nov 25, 2015
@j-clark
Copy link
Author

j-clark commented Nov 25, 2015

It appears to be a design consequence of representable using inheritance (via mixin) instead of delegation (in the style of the Presenter pattern). One option aside from checking against a list is to see if the object is dupable.

def represent(object, _options = {})
  dup = object.dup
  dup.extend self
  dup
rescue TypeError
  fail TypeError.new "Can't represent #{object.class}"
end

This would be great, because even non-singletons aren't getting polluted when they're represented and then can continue to be used in another context. But this means that we can't represent anything that's not dupable. After a quick google, integers, symbols, true, false and floats aren't dupable and therefore would not be representable. I'm not sure if this is desirable behavior for grape-roar

@dblock
Copy link
Member

dblock commented Nov 25, 2015

Maybe we can just detect whether these are singleton types and make them non-representable?

@joeyAghion
Copy link

This bit us suddenly, and very strangely. We also were mistakenly calling present nil, ... in one endpoint. A later request to the same process would fail with surprising errors.

We're attempting to patch this by simply guarding against the nil case (object.extend self unless object.nil?), but as discussed above that might not be a complete fix.

@mach-kernel
Copy link
Member

@joeyAghion, I'm sorry that this bug has affected you today! My proposed solution can be one of two things:

  • We can use a blacklist, even though it is not my preferred solution, but it is well defined and I can have it out for you quickly + later reconcile it out properly if time to fix is a problem.
  • We can instead decorate the singleton classes of the instances of the objects that we receive. Additionally, we have a nice predicate for the objects we should avoid touching.

While not immediately obvious, the magic is in doing checks like this:

nil.singleton_class == nil.class #=> true

We would avoid that object, but we wouldn't avoid this one:

irb(main):013:0> class Foo; end;
=> nil
irb(main):014:0> x = Foo.new
=> #<Foo:0x007fc0e00ff138>
irb(main):015:0> x.singleton_class == x.class
=> false

Thoughts?

@dblock
Copy link
Member

dblock commented Oct 26, 2017

@ashkan18 was this your problem?

@dblock
Copy link
Member

dblock commented Oct 26, 2017

I'm interested in this (I work with @joeyAghion here :)) - I think the next step should be to properly spec it, @mach-kernel do you think you understand this enough to code up a failing spec?

@ashkan18
Copy link

Yes @dblock this was the issue in my case as well.

@mach-kernel
Copy link
Member

@dblock Yup! I'll draft up a spec for this.

@mach-kernel
Copy link
Member

I've opened #23 with the spec so we can explore fixes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants