Skip to content
This repository has been archived by the owner on Oct 19, 2021. It is now read-only.

[JSON-API] Add support for "include" query #95

Merged
merged 1 commit into from May 20, 2015

Conversation

janko
Copy link
Collaborator

@janko janko commented May 19, 2015

I would like to get some code review on this, especially if I wrote the tests good. I didn't manage to figure out how to run Muntant for this, I ran PATTERN="Yaks::Behaviour::OptionalIncludes*" bundle exec rake mutant, but the output looked like nothing has been run.

Fixes #83

@janko janko force-pushed the jsonapi-include branch 2 times, most recently from 145bad4 to 82d18a7 Compare May 19, 2015 08:58

# ...

yask.call(user, env: @rack_env)
Copy link
Collaborator

Choose a reason for hiding this comment

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

typo? Should that fancy gem (ataru) have picked this up?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, forgot about Ataru, fixed :)

@plexus
Copy link
Owner

plexus commented May 19, 2015

I'm aware I'm being a bit cheeky here, but what do you think of this version 😉

require "rack/utils"

module Yaks
  module Behaviour
    module OptionalIncludes
      RACK_KEY = "yaks.optional_includes".freeze

      def associations
        super.select(&method(:include_association?))
      end

      private

      def include_association?(association)
        includes = env.fetch(RACK_KEY) do
          query_string = env.fetch("QUERY_STRING", "")
          query = Rack::Utils.parse_query(query_string)
          env[RACK_KEY] = query.fetch("include", "").split(",").map { |r| r.split(".") }
        end

        includes.any? do |relationship|
          relationship[mapper_stack.size] == association.name.to_s
        end
      end
    end
  end
end


def has_many(name, options = {}) # rubocop:disable Style/PredicateName
super name, options.merge(if: ->{include_association?(name)})
end
Copy link
Collaborator

Choose a reason for hiding this comment

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

So what do we think about merging only if the key doesn't already exist in the options. That way we could always or never include a particular sub-resource simply by putting something in the config for :if

Can you see any issues with that approach?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

With the new implementation proposed by @plexus it's no longer valid to implement it this way. However, I think my original implementation had the downside of overriding the :if, and I think the user would want to keep OptionalInclude's :if and still add their own.

Now we just need to think how to specify these options. @plexus, any ideas?

@danelowe
Copy link
Collaborator

Nice. thanks @janko-m!

@danelowe
Copy link
Collaborator

Yeah as @groyoh said, I always make that typo myself.

@janko
Copy link
Collaborator Author

janko commented May 19, 2015

@plexus For me it seems dangerous to cache it in Rack env, I think it could cause threading issues. But it would be useful to somehow cache the parsing of include, although it will usually be a very short string, so if we don't find a way I think we wouldn't compromise performance.

@janko
Copy link
Collaborator Author

janko commented May 19, 2015

@plexus But I really like the rest of the changes, I will implement those.

@plexus
Copy link
Owner

plexus commented May 19, 2015

For me it seems dangerous to cache it in Rack env, I think it could cause threading issues.

Each request gets a new Rack env, so even if you run multi-threaded, each request will be handled by a single thread

@janko
Copy link
Collaborator Author

janko commented May 19, 2015

You're right. Since I agree with everything you've written, I simply copy-pasted your implementation :)

end

# ...

Copy link
Collaborator

Choose a reason for hiding this comment

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

To fix ataru, try adding this in ataru_setup.rb at top level:

User = Struct.new

this in the Setup module:

def user
  User.new
end

and this in the README here:

yaks = Yaks.new

and run ataru locally with ataru check.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thanks, I didn't know how to run Ataru. Instead of adding anything to ataru_setup.rb, I reused the existing objects.

Btw, how do you run RuboCop locally? I tried bundle exec rake rubocop from both yaks/ and top-level directory, and it says that the task is missing.

Copy link
Owner

Choose a reason for hiding this comment

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

bundle exec rake rubocop from the top level should work. Does it show up when you do bundle exec rake -T?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Actaully, I meant to say that from the top-level it works, but it throws a lot of offenses unrelated to my files, so it doesn't seem like the same Rake task that is run on Travis.

@plexus
Copy link
Owner

plexus commented May 19, 2015

😄

@groyoh
Copy link
Collaborator

groyoh commented May 19, 2015

For mutant, have your tried bundle exec rake mutant PATTERN=Yaks::Behaviour::OptionalIncludes?

@janko
Copy link
Collaborator Author

janko commented May 19, 2015

Thanks, that did work. I only had to change it to

bundle exec rake mutant -r yaks/behaviour/optional_includes PATTERN="Yaks::Behaviour::OptionalIncludes"

Because I'm not requiring that behaviour by default. I will go on fixing those.

@plexus
Copy link
Owner

plexus commented May 19, 2015

With the new version users will still be able to provide their own :if, but it will be checked in addition to the includes, not instead of it. Is that good enough?

The main other case I can see is where an association is not optional, i.e. will always be included regardless of the ?include=

@groyoh
Copy link
Collaborator

groyoh commented May 19, 2015

@janko-m I think there is a require 'yaks/behaviour/optional_include' missing in yaks.rb.

@janko
Copy link
Collaborator Author

janko commented May 19, 2015

@groyoh I left it out on purpose, because I'm requiring rack/utils, and Yaks should be usable without Rack. Another option would be to require it inside a method, but I didn't want this. I don't know, what are your thoughts on that?

@plexus
Copy link
Owner

plexus commented May 19, 2015

I was also thinking not to load any of yaks/behavior/ by default, just because this namespace might become quite large, and most of it will remain unused by the average user. The dependency on Rack is also a good argumnent.

@groyoh
Copy link
Collaborator

groyoh commented May 19, 2015

Ok, it makes sense! 😉 I think it should be written explicitly in the README that we should require it (and not only put it in the code block). Shouldn't we also add -r yaks/behaviour/optional_includes to mutant rake task?

@janko
Copy link
Collaborator Author

janko commented May 19, 2015

@groyoh Good call, I will edit the README. Well, I'm not sure about Mutant, if we add another behaviour then we'll need to change again. Is there a better way?

@groyoh
Copy link
Collaborator

groyoh commented May 19, 2015

@janko-m actually I do not really have a better solution. It does not look like a huge trade off for me. We won't have too many behaviour files hopefully 😄
Maybe at best we could change rakelib/shared.rake like this:

requires = %w(
  -ryaks
  -ryaks/behaviour/optional_include
)
args = %w[-Ilib --use rspec --score 100] + requires + opts + [pattern]

@janko
Copy link
Collaborator Author

janko commented May 19, 2015

I have an existenial problem here. Mutant is replacing the env.fetch(RACK_KEY) with env.fetch(nil), and my tests are still passing. This is because I'm not actually testing the caching. The thing is, I don't want to test that I'm caching :), it's just a speed optimization.

So I'm in a situation where I can introduce a meaningless test just to make Mutant pass. What should I do?

@groyoh
Copy link
Collaborator

groyoh commented May 19, 2015

@janko-m Why do you say that the test is meaningless? If it's there, it should be tested IMO.

@janko janko force-pushed the jsonapi-include branch 2 times, most recently from ffd4fc4 to b8a0052 Compare May 19, 2015 22:37
@janko
Copy link
Collaborator Author

janko commented May 19, 2015

@groyoh You're right, I added a test. I also added your suggestion for Mutant and killed living mutants.

@plexus @danelowe I implemented it this way: if :if is passed in for an association, then don't check the include query for it. I think that's the most intuitive behaviour.


def associations
super.select do |association|
association.if != Undefined || include_association?(association)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Wouldn't that also include it even if we have if: ->{ false } for example?

Copy link
Owner

Choose a reason for hiding this comment

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

it would at this stage, map_associations handles evaluating the if and taking the association out

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@groyoh Yes, but this is exactly what I want. I want that if someone specifies :if, that include_association? checking is ignored. Because I want it to mean that their overriding this behaviour for that association. If someone specifies :if -> { false }, that should mean that they never want this association included (in which case of course they wouldn't even put it there).

@danelowe
Copy link
Collaborator

@janko-m The behaviour you described makes sense to me. I would expect that if I supply my own callback for :if, it would override all default behaviour. Would include_association? be available inside our custom callback as well?

@janko
Copy link
Collaborator Author

janko commented May 20, 2015

@danelowe Yes, include_association? will be available as an instance method on your mapper, so you can use it in :if.

@janko
Copy link
Collaborator Author

janko commented May 20, 2015

I think this is ready for merge, is there something more I need to do? 😃

include Yaks::Behaviour::OptionalIncludes

has_one :author
has_many :comments, if: ->{true}
Copy link
Owner

Choose a reason for hiding this comment

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

You're lucky the docs don't get Rubocopped. Oh hey there's an idea :D

@plexus
Copy link
Owner

plexus commented May 20, 2015

A few minor nitpicks. You want to fix them? I don't consider them blocking

plexus added a commit that referenced this pull request May 20, 2015
[JSON-API] Add support for "include" query
@plexus plexus merged commit 892f64f into plexus:master May 20, 2015
@janko janko deleted the jsonapi-include branch May 20, 2015 17:48
@janko
Copy link
Collaborator Author

janko commented May 20, 2015

Managed to correct the readme comment :)

@plexus
Copy link
Owner

plexus commented May 20, 2015

I saw that :) one more thing for next time you can immediately add a mention of your cool new feature in the CHANGELOG. I go through the changes before each release but it's nice if it's already mostly filled in.

knailed-it-pingles-lid

Thanks for the PR! Very useful feature.

@janko
Copy link
Collaborator Author

janko commented May 20, 2015

You're welcome! Thank you guys for such a detailed code review, it's really great that we merged our opinions together, I think this really helps keep quality of changes at a high level.

@plexus
Copy link
Owner

plexus commented May 20, 2015

I very much agree

@groyoh
Copy link
Collaborator

groyoh commented May 20, 2015

Good job! Keep up the good work 👍

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

Successfully merging this pull request may close these issues.

Supporting the include query parameter for JSON-API
4 participants