yaks 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/CHANGELOG.md +44 -3
  4. data/README.md +90 -33
  5. data/Rakefile +10 -0
  6. data/bench/bench.rb +0 -1
  7. data/bench/bench_1000.rb +60 -0
  8. data/lib/yaks/breaking_changes.rb +22 -0
  9. data/lib/yaks/config/dsl.rb +114 -27
  10. data/lib/yaks/config.rb +39 -54
  11. data/lib/yaks/default_policy.rb +32 -14
  12. data/lib/yaks/format/collection_json.rb +4 -4
  13. data/lib/yaks/format/hal.rb +20 -3
  14. data/lib/yaks/format/json_api.rb +3 -3
  15. data/lib/yaks/format.rb +54 -9
  16. data/lib/yaks/fp/callable.rb +9 -0
  17. data/lib/yaks/fp/hash_updatable.rb +2 -0
  18. data/lib/yaks/fp/updatable.rb +2 -0
  19. data/lib/yaks/fp.rb +8 -0
  20. data/lib/yaks/mapper/link.rb +2 -2
  21. data/lib/yaks/mapper.rb +6 -6
  22. data/lib/yaks/primitivize.rb +2 -2
  23. data/lib/yaks/resource/link.rb +0 -4
  24. data/lib/yaks/runner.rb +90 -0
  25. data/lib/yaks/util.rb +4 -0
  26. data/lib/yaks/version.rb +1 -1
  27. data/lib/yaks.rb +3 -0
  28. data/spec/acceptance/acceptance_spec.rb +6 -1
  29. data/spec/json/confucius.collection.json +5 -16
  30. data/spec/json/plant_collection.collection.json +32 -0
  31. data/spec/spec_helper.rb +2 -1
  32. data/spec/support/deep_eql.rb +14 -7
  33. data/spec/support/pet_mapper.rb +0 -2
  34. data/spec/unit/yaks/collection_mapper_spec.rb +24 -2
  35. data/spec/unit/yaks/config/dsl_spec.rb +6 -10
  36. data/spec/unit/yaks/config_spec.rb +40 -99
  37. data/spec/unit/yaks/default_policy_spec.rb +20 -0
  38. data/spec/unit/yaks/format/collection_json_spec.rb +41 -0
  39. data/spec/unit/yaks/format/hal_spec.rb +38 -3
  40. data/spec/unit/yaks/format/json_api_spec.rb +2 -2
  41. data/spec/unit/yaks/format_spec.rb +28 -3
  42. data/spec/unit/yaks/fp/callable_spec.rb +13 -0
  43. data/spec/unit/yaks/mapper_spec.rb +226 -126
  44. data/spec/unit/yaks/resource/link_spec.rb +2 -3
  45. data/spec/unit/yaks/resource_spec.rb +15 -0
  46. data/spec/unit/yaks/runner_spec.rb +260 -0
  47. data/spec/unit/yaks/util_spec.rb +7 -1
  48. data/yaks.gemspec +4 -1
  49. metadata +72 -15
  50. /data/spec/json/{hal_plant_collection.json → plant_collection.hal.json} +0 -0
@@ -39,19 +39,18 @@ module Matchers
39
39
  end.join
40
40
  end
41
41
 
42
- def failure_message(message)
42
+ def add_failure_message(message)
43
43
  diffs << "at %s, %s" % [stack_as_jsonpath, message]
44
44
  @result = false
45
45
  end
46
46
 
47
47
  def compare(key)
48
- #require 'pry' ; binding.pry
49
48
  push key
50
49
  if target[key] != expectation[key]
51
50
  if [Hash, Array].any?{|klz| target[key].is_a? klz }
52
51
  recurse(target[key], expectation[key])
53
52
  else
54
- failure_message begin
53
+ add_failure_message begin
55
54
  if expectation[key].class == target[key].class
56
55
  "expected #{expectation[key].inspect}, got #{target[key].inspect}"
57
56
  else
@@ -72,13 +71,19 @@ module Matchers
72
71
  when Hash
73
72
  if target.is_a?(Hash)
74
73
  if target.class != expectation.class # e.g. HashWithIndifferentAccess
75
- failure_message("expected #{expectation.class}, got #{target.class}")
74
+ add_failure_message("expected #{expectation.class}, got #{target.class}")
75
+ end
76
+ (expectation.keys - target.keys).each do |key|
77
+ add_failure_message "Expected key #{key}"
78
+ end
79
+ (target.keys - expectation.keys).each do |key|
80
+ add_failure_message "Unexpected key #{key}"
76
81
  end
77
82
  (target.keys | expectation.keys).each do |key|
78
83
  compare key
79
84
  end
80
85
  else
81
- failure_message("expected Hash got #{@target.inspect}")
86
+ add_failure_message("expected Hash got #{@target.inspect}")
82
87
  end
83
88
 
84
89
  when Array
@@ -87,12 +92,12 @@ module Matchers
87
92
  compare idx
88
93
  end
89
94
  else
90
- failure_message("expected Array got #{@target.inspect}")
95
+ add_failure_message("expected Array got #{@target.inspect}")
91
96
  end
92
97
 
93
98
  else
94
99
  if target != expectation
95
- failure_message("expected #{expectation.inspect}, got #{@target.inspect}")
100
+ add_failure_message("expected #{expectation.inspect}, got #{@target.inspect}")
96
101
  end
97
102
  end
98
103
 
@@ -102,10 +107,12 @@ module Matchers
102
107
  def failure_message_for_should
103
108
  diffs.join("\n")
104
109
  end
110
+ alias failure_message failure_message_for_should
105
111
 
106
112
  def failure_message_for_should_not
107
113
  "expected #{@target.inspect} not to be in deep_eql with #{@expectation.inspect}"
108
114
  end
115
+ alias failure_message_when_negated failure_message_for_should_not
109
116
  end
110
117
  end
111
118
 
@@ -1,5 +1,3 @@
1
1
  class PetMapper < Yaks::Mapper
2
2
  attributes :id, :name, :species
3
-
4
- #link :collection, '/api/pets/{id*}'
5
3
  end
@@ -10,8 +10,7 @@ RSpec.describe Yaks::CollectionMapper do
10
10
  { item_mapper: item_mapper,
11
11
  policy: policy,
12
12
  env: {},
13
- mapper_stack: []
14
- }
13
+ mapper_stack: [] }
15
14
  }
16
15
 
17
16
  let(:collection) { [] }
@@ -33,6 +32,10 @@ RSpec.describe Yaks::CollectionMapper do
33
32
  let(:item_mapper) { PetMapper }
34
33
 
35
34
  it 'should map the members' do
35
+ stub(policy).derive_mapper_from_object(any_args) do
36
+ raise ":item_mapper was specified explicitly, should not be derived from object"
37
+ end
38
+
36
39
  expect(mapper.call(collection)).to eql Yaks::CollectionResource.new(
37
40
  type: 'pet',
38
41
  links: [],
@@ -140,4 +143,23 @@ RSpec.describe Yaks::CollectionMapper do
140
143
  end
141
144
  end
142
145
 
146
+ context 'with an empty collection' do
147
+
148
+ context 'without an item_mapper specified' do
149
+ let(:context) { Yaks::Util.slice_hash(super(), :policy, :env) }
150
+
151
+ it 'should use a rel of "collection"' do
152
+ expect(mapper.([]).collection_rel).to eq 'collection'
153
+ end
154
+ end
155
+
156
+ context 'with an item_mapper specified' do
157
+ let(:context) { Yaks::Util.slice_hash(super(), :policy, :env, :item_mapper) }
158
+
159
+ it 'should derive the collection rel from the item mapper' do
160
+ expect(mapper.([]).collection_rel).to eq 'rel:the_types'
161
+ end
162
+ end
163
+
164
+ end
143
165
  end
@@ -43,7 +43,7 @@ RSpec.describe Yaks::Config::DSL do
43
43
 
44
44
  describe '#format_options' do
45
45
  configure { format_options :hal, singular_link: [:self] }
46
- specify { expect(yaks_config.format_options[:hal].should eq(singular_link: [:self])) }
46
+ specify { expect(yaks_config.format_options[:hal]).to eq(singular_link: [:self]) }
47
47
  end
48
48
 
49
49
  describe '#default_format' do
@@ -61,6 +61,11 @@ RSpec.describe Yaks::Config::DSL do
61
61
  specify { expect(yaks_config.policy_options[:rel_template]).to eql 'rels:{rel}' }
62
62
  end
63
63
 
64
+ describe '#json_serializer' do
65
+ configure { json_serializer { |i| "foo #{i}" } }
66
+ specify { expect(yaks_config.serializers[:json].call(7)).to eql 'foo 7' }
67
+ end
68
+
64
69
  describe '#mapper_namespace' do
65
70
  configure { mapper_namespace RSpec }
66
71
  specify { expect(yaks_config.policy_options[:namespace]).to eql RSpec }
@@ -79,13 +84,4 @@ RSpec.describe Yaks::Config::DSL do
79
84
  specify { expect(yaks_config.primitivize.call({:abc => Foo.new('hello')})).to eql 'abc' => 'hello' }
80
85
  end
81
86
 
82
- describe '#after' do
83
- configure do
84
- after {|x| x + 1}
85
- after {|x| x + 10}
86
- end
87
- it 'should register the block' do
88
- expect(yaks_config.steps.inject(0) {|memo, step| step.call(memo)}).to be 11
89
- end
90
- end
91
87
  end
@@ -7,57 +7,63 @@ RSpec.describe Yaks::Config do
7
7
  subject(:config) { described_class.new(&blk) }
8
8
  end
9
9
 
10
- context 'defaults' do
11
- configure {}
12
-
13
- its(:default_format) { should equal :hal }
14
- its(:policy_class) { should < Yaks::DefaultPolicy }
15
-
16
- it 'should have empty format options' do
17
- expect(config.options_for_format(:hal)).to eql({})
18
- end
19
- end
20
-
21
- context 'with a default format' do
22
- configure do
23
- default_format :json_api
10
+ describe '#initialize' do
11
+ context 'defaults' do
12
+ configure {}
13
+
14
+ its(:default_format) { should equal :hal }
15
+ its(:policy_class) { should < Yaks::DefaultPolicy }
16
+ its(:primitivize) { should be_a Yaks::Primitivize }
17
+ its(:serializers) { should eql({}) }
18
+ its(:hooks) { should eql([]) }
19
+
20
+ it 'should have empty format options' do
21
+ expect(config.format_options[:hal]).to eql({})
22
+ end
24
23
  end
25
24
 
26
- its(:default_format) { should equal :json_api }
27
- end
25
+ context 'with a default format' do
26
+ configure do
27
+ default_format :json_api
28
+ end
28
29
 
29
- context 'with a custom policy class' do
30
- MyPolicy = Struct.new(:options)
31
- configure do
32
- policy MyPolicy
30
+ its(:default_format) { should equal :json_api }
33
31
  end
34
32
 
35
- its(:policy_class) { should equal MyPolicy }
36
- its(:policy) { should be_a MyPolicy }
37
- end
33
+ context 'with a custom policy class' do
34
+ MyPolicy = Struct.new(:options)
35
+ configure do
36
+ policy MyPolicy
37
+ end
38
38
 
39
- context 'with a rel template' do
40
- configure do
41
- rel_template 'http://rel/foo'
39
+ its(:policy_class) { should equal MyPolicy }
40
+ its(:policy) { should be_a MyPolicy }
42
41
  end
43
42
 
44
- its(:policy_options) { should eql(rel_template: 'http://rel/foo') }
45
- end
43
+ context 'with a rel template' do
44
+ configure do
45
+ rel_template 'http://rel/foo'
46
+ end
46
47
 
47
- context 'with format options' do
48
- configure do
49
- format_options :hal, plural_links: [:self, :profile]
48
+ its(:policy_options) { should eql(rel_template: 'http://rel/foo') }
50
49
  end
51
50
 
52
- specify do
53
- expect(config.options_for_format(:hal)).to eql(plural_links: [:self, :profile])
51
+ context 'with format options' do
52
+ configure do
53
+ format_options :hal, plural_links: [:self, :profile]
54
+ end
55
+
56
+ specify do
57
+ expect(config.format_options[:hal]).to eql(plural_links: [:self, :profile])
58
+ end
54
59
  end
55
60
  end
56
61
 
57
- describe '#serialize' do
62
+ describe '#call' do
58
63
  configure do
59
64
  rel_template 'http://api.mysuperfriends.com/{rel}'
60
65
  format_options :hal, plural_links: [:copyright]
66
+ skip :serialize
61
67
  end
62
68
 
63
69
  specify do
@@ -65,69 +71,4 @@ RSpec.describe Yaks::Config do
65
71
  end
66
72
  end
67
73
 
68
- describe '#mapper_namespace' do
69
- module MyMappers
70
- class PetMapper < Yaks::Mapper
71
- end
72
- end
73
-
74
- configure do
75
- mapper_namespace MyMappers
76
- end
77
-
78
- specify do
79
- expect(config.policy.derive_mapper_from_object(boingboing)).to eql(MyMappers::PetMapper)
80
- end
81
- end
82
-
83
- describe '#map_to_primitive' do
84
- class TheMapper < Yaks::Mapper
85
- attributes :a_date
86
- end
87
-
88
- TheModel = Struct.new(:a_date)
89
-
90
- configure do
91
- map_to_primitive Date do |object|
92
- object.iso8601
93
- end
94
- end
95
-
96
- let(:model) {
97
- TheModel.new(Date.new(2014, 5, 6))
98
- }
99
-
100
- specify do
101
- expect(config.serialize(model, mapper: TheMapper)).to eq({"a_date"=>"2014-05-06"})
102
- end
103
- end
104
-
105
- describe '#format_class' do
106
- configure do
107
- default_format :collection_json
108
- end
109
-
110
- let(:rack_env) {
111
- { 'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json' }
112
- }
113
-
114
- it 'should fall back to the default when no HTTP_ACCEPT key is present' do
115
- expect(config.format_class({}, {})).to equal Yaks::Format::CollectionJson
116
- end
117
-
118
- it 'should detect format based on accept header' do
119
- rack_env = { 'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json' }
120
- expect(config.format_class({}, rack_env)).to equal Yaks::Format::JsonApi
121
- end
122
-
123
- it 'should know to pick the best match' do
124
- rack_env = { 'HTTP_ACCEPT' => 'application/hal+json;q=0.8, application/vnd.api+json;q=0.7' }
125
- expect(config.format_class({}, rack_env)).to equal Yaks::Format::Hal
126
- end
127
-
128
- it 'should fall back to the default when no mime type is recognized' do
129
- rack_env = { 'HTTP_ACCEPT' => 'text/html, application/json' }
130
- expect(config.format_class({}, rack_env)).to equal Yaks::Format::CollectionJson
131
- end
132
- end
133
74
  end
@@ -26,6 +26,20 @@ RSpec.describe Yaks::DefaultPolicy do
26
26
  end
27
27
  end
28
28
 
29
+ describe '#derive_type_from_collection' do
30
+ specify do
31
+ expect(
32
+ policy.derive_type_from_collection([Soy.new])
33
+ ).to eql 'soy'
34
+ end
35
+
36
+ specify do
37
+ expect(
38
+ policy.derive_type_from_collection([])
39
+ ).to be_nil
40
+ end
41
+ end
42
+
29
43
  describe '#derive_mapper_from_association' do
30
44
  let(:options) { { namespace: Namespace } }
31
45
 
@@ -47,4 +61,10 @@ RSpec.describe Yaks::DefaultPolicy do
47
61
  end
48
62
  end
49
63
 
64
+ describe '#serializer_for_format' do
65
+ specify {
66
+ expect(policy.serializer_for_format(Yaks::Format::JsonAPI).call('foo' => [1,2])).to eql "{\n \"foo\": [\n 1,\n 2\n ]\n}"
67
+ }
68
+ end
69
+
50
70
  end
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Format::CollectionJson do
4
+ context 'with the plant collection resource' do
5
+ include_context 'plant collection resource'
6
+
7
+ subject { Yaks::Primitivize.create.call(described_class.new.call(resource)) }
8
+
9
+ it { should deep_eql(load_json_fixture('plant_collection.collection')) }
10
+ end
11
+
12
+ context 'with a link without title' do
13
+ let(:resource) {
14
+ Yaks::Resource.new(
15
+ attributes: {foo: 'fooval', bar: 'barval'},
16
+ links: [Yaks::Resource::Link.new('the_rel', 'the_uri', {})]
17
+ )
18
+ }
19
+
20
+ subject {
21
+ Yaks::Primitivize.create.call(described_class.new.call(resource))
22
+ }
23
+
24
+ it 'should not render a name' do
25
+ should deep_eql(
26
+ "collection" => {
27
+ "version" => "1.0",
28
+ "items" => [
29
+ {
30
+ "data" => [
31
+ { "name"=>"foo", "value"=>"fooval" },
32
+ { "name"=>"bar", "value"=>"barval" }
33
+ ],
34
+ "links" => [{"rel"=>"the_rel", "href"=>"the_uri"}]
35
+ }
36
+ ]
37
+ }
38
+ )
39
+ end
40
+ end
41
+ end
@@ -1,9 +1,44 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Yaks::Format::Hal do
4
- include_context 'plant collection resource'
4
+ context 'with the plant collection resource' do
5
+ include_context 'plant collection resource'
5
6
 
6
- subject { Yaks::Primitivize.create.call(described_class.new.call(resource)) }
7
+ subject { Yaks::Primitivize.create.call(described_class.new.call(resource)) }
7
8
 
8
- it { should eq(load_json_fixture('hal_plant_collection')) }
9
+ it { should deep_eql(load_json_fixture('plant_collection.hal')) }
10
+ end
11
+
12
+ context 'with multiple links on the same rel' do
13
+ let(:format) {
14
+ described_class.new(:plural_links => 'my_plural_rel')
15
+ }
16
+
17
+ let(:resource) {
18
+ Yaks::Resource.new(
19
+ attributes: {foo: 'fooval', bar: 'barval'},
20
+ links: [
21
+ Yaks::Resource::Link.new('my_plural_rel', 'the_uri1', {}),
22
+ Yaks::Resource::Link.new('my_plural_rel', 'the_uri2', {})
23
+ ]
24
+ )
25
+ }
26
+
27
+ subject {
28
+ Yaks::Primitivize.create.call(format.call(resource))
29
+ }
30
+
31
+ it 'should render both links' do
32
+ should deep_eql(
33
+ 'foo' => 'fooval',
34
+ 'bar' => 'barval',
35
+ '_links' => {
36
+ "my_plural_rel" => [
37
+ {"href"=>"the_uri1"},
38
+ {"href"=>"the_uri2"}
39
+ ]
40
+ }
41
+ )
42
+ end
43
+ end
9
44
  end
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  # Mainly tested through the acceptance tests, here covering a few specific edge cases
4
- RSpec.describe Yaks::Format::JsonApi do
5
- let(:format) { Yaks::Format::JsonApi.new }
4
+ RSpec.describe Yaks::Format::JsonAPI do
5
+ let(:format) { Yaks::Format::JsonAPI.new }
6
6
 
7
7
  context 'with no subresources' do
8
8
  let(:resource) { Yaks::Resource.new(type: 'wizard', attributes: {foo: :bar}) }
@@ -2,11 +2,36 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe Yaks::Format do
4
4
  describe '.by_name' do
5
- specify { expect(Yaks::Format.by_name(:hal)).to eql Yaks::Format::Hal }
6
- specify { expect(Yaks::Format.by_name(:json_api)).to eql Yaks::Format::JsonApi }
5
+ specify do
6
+ expect(Yaks::Format.by_name(:hal)).to eql Yaks::Format::Hal
7
+ end
8
+ specify do
9
+ expect(Yaks::Format.by_name(:json_api)).to eql Yaks::Format::JsonAPI
10
+ end
7
11
  end
8
12
 
9
13
  describe '.by_mime_type' do
10
- specify { expect(Yaks::Format.by_mime_type('application/hal+json')).to eql Yaks::Format::Hal }
14
+ specify do
15
+ expect(Yaks::Format.by_mime_type('application/hal+json')).to eql Yaks::Format::Hal
16
+ end
17
+ end
18
+
19
+ describe '.by_accept_header' do
20
+ specify do
21
+ expect(Yaks::Format.by_accept_header('application/hal+json;q=0.8, application/vnd.api+json')).to eql Yaks::Format::JsonAPI
22
+ end
23
+ specify do
24
+ expect(Yaks::Format.by_accept_header('application/hal+json;q=0.8, application/vnd.api+json;q=0.7')).to eql Yaks::Format::Hal
25
+ end
26
+ end
27
+
28
+ describe '.mime_types' do
29
+ specify do
30
+ expect(Yaks::Format.mime_types).to eql(
31
+ collection_json: "application/vnd.collection+json",
32
+ hal: "application/hal+json",
33
+ json_api: "application/vnd.api+json"
34
+ )
35
+ end
11
36
  end
12
37
  end
@@ -0,0 +1,13 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::FP::Callable do
4
+ it 'should delegate to_proc to method(:call)' do
5
+ obj = Class.new do
6
+ include Yaks::FP::Callable
7
+
8
+ def call(x) ; x * x ; end
9
+ end.new
10
+
11
+ expect([1,2,3].map(&obj)).to eql [1,4,9]
12
+ end
13
+ end