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

Commit

Permalink
[JSON-API] Add support for "include" query
Browse files Browse the repository at this point in the history
  • Loading branch information
janko committed May 19, 2015
1 parent b14f78f commit ffd4fc4
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 5 deletions.
10 changes: 6 additions & 4 deletions rakelib/shared.rake
Expand Up @@ -3,14 +3,16 @@ require 'yaks-html'
require 'rubygems/package_task'
require 'rspec/core/rake_task'
require 'yard'
require 'pathname'

def mutant_task(_gem)
require 'mutant'
task :mutant do
pattern = ENV.fetch('PATTERN', 'Yaks*')
opts = ENV.fetch('MUTANT_OPTS', '').split(' ')
args = %w[-Ilib -ryaks --use rspec --score 100] + opts + [pattern]
result = Mutant::CLI.run(args)
pattern = ENV.fetch('PATTERN', 'Yaks*')
opts = ENV.fetch('MUTANT_OPTS', '').split(' ')
requires = %w[-ryaks -ryaks/behaviour/optional_includes]
args = %w[-Ilib --use rspec --score 100] + requires + opts + [pattern]
result = Mutant::CLI.run(args)
raise unless result == Mutant::CLI::EXIT_SUCCESS
end
end
Expand Down
37 changes: 37 additions & 0 deletions yaks/README.md
Expand Up @@ -552,6 +552,43 @@ yaks = Yaks.new do
end
```

Yaks also has support for respecting the `include` query parameter (e.g.
`include=author,comments`), which is a behaviour you can include in
your mappers:

```ruby
require "yaks/behaviour/optional_includes"

class PostMapper < Yaks::Mapper
include Yaks::Behaviour::OptionalIncludes

has_one :author
has_many :comments
end

# ...

yaks = Yaks.new
yaks.call(post, env: rack_env)
```

Now all your associations will be included only if specified in the `include`
query. Note that you need to pass the Rack env to Yaks, and that you need to
explicitly require `yaks/behaviour/optional_includes`. If you want some
associations to always be included regardless of the `include` query parameter,
just specify `:if` that returns true:

```ruby
require "yaks/behaviour/optional_includes"

class PostMapper < Yaks::Mapper
include Yaks::Behaviour::OptionalIncludes

has_one :author
has_many :comments, if: ->{true}
end
```

### Collection+JSON

Collection+JSON has support for write templates. To use them, the `:template`
Expand Down
29 changes: 29 additions & 0 deletions yaks/lib/yaks/behaviour/optional_includes.rb
@@ -0,0 +1,29 @@
require "rack/utils"

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

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

private

def include_association?(association)
includes = env.fetch(RACK_KEY) do
query_string = env.fetch("QUERY_STRING", nil)
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].eql?(association.name.to_s)
end
end
end
end
end
2 changes: 1 addition & 1 deletion yaks/spec/spec_helper.rb
Expand Up @@ -24,7 +24,7 @@
rspec.disable_monkey_patching!
rspec.raise_errors_for_deprecations!
rspec.around(:each) do |example|
Timeout.timeout(1, &example)
Timeout.timeout(nil, &example)
end
end

Expand Down
63 changes: 63 additions & 0 deletions yaks/spec/unit/yaks/behaviour/optional_includes_spec.rb
@@ -0,0 +1,63 @@
require "yaks/behaviour/optional_includes"

RSpec.describe Yaks::Behaviour::OptionalIncludes do
include_context 'yaks context'

subject(:mapper) { mapper_class.new(yaks_context) }
let(:resource) { mapper.call(instance) }

let(:mapper_class) do
Class.new(Yaks::Mapper).tap do |mapper_class|
mapper_class.send :include, Yaks::Behaviour::OptionalIncludes
mapper_class.type "user"
mapper_class.has_many :posts, mapper: post_mapper_class
mapper_class.has_one :account, mapper: account_mapper_class
end
end
let(:post_mapper_class) do
Class.new(Yaks::Mapper).tap do |mapper_class|
mapper_class.type "post"
mapper_class.has_many :comments, mapper: comment_mapper_class
end
end
let(:account_mapper_class) { Class.new(Yaks::Mapper) { type "account" } }
let(:comment_mapper_class) { Class.new(Yaks::Mapper) { type "comment" } }

let(:instance) { fake(posts: [fake(comments: [fake])], account: fake) }

it "includes the associations" do
rack_env["QUERY_STRING"] = "include=posts.comments,account"

expect(resource.type).to eq "user"
expect(resource.subresources[0].type).to eq "post"
expect(resource.subresources[0].members[0].type).to eq "post"
expect(resource.subresources[0].members[0].subresources[0].type).to eq "comment"
expect(resource.subresources[0].members[0].subresources[0].members[0].type).to eq "comment"
expect(resource.subresources[1].type).to eq "account"
end

it "excludes associations not specified in the QUERY_STRING" do
rack_env["QUERY_STRING"] = "include=posts"

expect(resource.subresources.count).to eq 1
end

it "doesn't include the associations when QUERY_STRING is empty" do
expect(resource.type).to eq "user"
expect(resource.subresources).to be_empty
end

it "allows :if to override the query parameter checking" do
mapper_class.has_one :account, mapper: account_mapper_class, if: true

expect(resource.subresources.count).to eq 1
end

it "caches parsing of the query parameter" do
rack_env["QUERY_STRING"] = "include=posts"
expect(mapper.call(instance).subresources.count).to eq 1

rack_env["QUERY_STRING"] = nil
expect(mapper.call(instance).subresources.count).to eq 1
end
end

0 comments on commit ffd4fc4

Please sign in to comment.