yaks 0.3.1 → 0.4.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile +0 -2
  5. data/LICENSE +7 -0
  6. data/README.md +160 -35
  7. data/Rakefile +2 -1
  8. data/lib/yaks/collection_mapper.rb +25 -18
  9. data/lib/yaks/collection_resource.rb +11 -17
  10. data/lib/yaks/config.rb +96 -0
  11. data/lib/yaks/default_policy.rb +34 -4
  12. data/lib/yaks/fp.rb +18 -0
  13. data/lib/yaks/mapper/association.rb +19 -27
  14. data/lib/yaks/mapper/class_methods.rb +4 -2
  15. data/lib/yaks/mapper/config.rb +24 -39
  16. data/lib/yaks/mapper/has_many.rb +7 -6
  17. data/lib/yaks/mapper/has_one.rb +4 -3
  18. data/lib/yaks/mapper/link.rb +52 -55
  19. data/lib/yaks/mapper.rb +38 -26
  20. data/lib/yaks/null_resource.rb +3 -3
  21. data/lib/yaks/primitivize.rb +29 -27
  22. data/lib/yaks/resource/link.rb +4 -0
  23. data/lib/yaks/resource.rb +18 -7
  24. data/lib/yaks/serializer/collection_json.rb +38 -0
  25. data/lib/yaks/serializer/hal.rb +55 -0
  26. data/lib/yaks/serializer/json_api.rb +61 -0
  27. data/lib/yaks/serializer.rb +25 -4
  28. data/lib/yaks/util.rb +2 -42
  29. data/lib/yaks/version.rb +1 -1
  30. data/lib/yaks.rb +10 -32
  31. data/notes.org +72 -0
  32. data/shaved_yak.gif +0 -0
  33. data/spec/acceptance/acceptance_spec.rb +46 -0
  34. data/spec/acceptance/models.rb +28 -0
  35. data/spec/integration/map_to_resource_spec.rb +11 -15
  36. data/spec/json/confucius.hal.json +23 -0
  37. data/spec/json/confucius.json_api.json +22 -0
  38. data/spec/json/john.hal.json +29 -0
  39. data/spec/json/youtypeitwepostit.collection.json +45 -0
  40. data/spec/spec_helper.rb +12 -1
  41. data/spec/support/shared_contexts.rb +7 -10
  42. data/spec/support/youtypeit_models_mappers.rb +20 -0
  43. data/spec/unit/yaks/collection_mapper_spec.rb +84 -0
  44. data/spec/unit/yaks/collection_resource_spec.rb +72 -0
  45. data/spec/unit/yaks/config_spec.rb +129 -0
  46. data/spec/unit/yaks/fp_spec.rb +31 -0
  47. data/spec/unit/yaks/mapper/association_spec.rb +80 -0
  48. data/spec/{yaks → unit/yaks}/mapper/class_methods_spec.rb +4 -4
  49. data/spec/unit/yaks/mapper/config_spec.rb +191 -0
  50. data/spec/unit/yaks/mapper/has_many_spec.rb +46 -0
  51. data/spec/unit/yaks/mapper/has_one_spec.rb +34 -0
  52. data/spec/unit/yaks/mapper/link_spec.rb +152 -0
  53. data/spec/unit/yaks/mapper_spec.rb +177 -0
  54. data/spec/unit/yaks/resource_spec.rb +40 -0
  55. data/spec/{yaks/hal_serializer_spec.rb → unit/yaks/serializer/hal_spec.rb} +2 -2
  56. data/spec/unit/yaks/serializer_spec.rb +12 -0
  57. data/spec/unit/yaks/util_spec.rb +43 -0
  58. data/spec/yaml/confucius.yaml +10 -0
  59. data/spec/yaml/youtypeitwepostit.yaml +9 -0
  60. data/yaks.gemspec +7 -8
  61. metadata +92 -53
  62. data/Gemfile.lock +0 -111
  63. data/lib/yaks/hal_serializer.rb +0 -59
  64. data/lib/yaks/json_api_serializer.rb +0 -59
  65. data/lib/yaks/link_lookup.rb +0 -23
  66. data/lib/yaks/mapper/lookup.rb +0 -19
  67. data/lib/yaks/mapper/map_links.rb +0 -17
  68. data/lib/yaks/profile_registry.rb +0 -60
  69. data/lib/yaks/rel_registry.rb +0 -20
  70. data/lib/yaks/shared_options.rb +0 -15
  71. data/spec/support/shorthands.rb +0 -22
  72. data/spec/yaks/collection_resource_spec.rb +0 -9
  73. data/spec/yaks/mapper/association_spec.rb +0 -21
  74. data/spec/yaks/mapper/config_spec.rb +0 -77
  75. data/spec/yaks/mapper/has_one_spec.rb +0 -16
  76. data/spec/yaks/mapper/link_spec.rb +0 -38
  77. data/spec/yaks/mapper/map_links_spec.rb +0 -46
  78. data/spec/yaks/mapper_spec.rb +0 -65
  79. data/spec/yaks/resource_spec.rb +0 -23
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Mapper::HasOne do
4
+ AuthorMapper = Class.new(Yaks::Mapper) { attributes :name }
5
+
6
+ let(:name) { 'William S. Burroughs' }
7
+ let(:mapper) { AuthorMapper }
8
+ let(:has_one) { described_class.new(:author, mapper, 'http://rel', Yaks::Undefined) }
9
+ let(:author) { double(:name => name) }
10
+ let(:policy) {
11
+ double(
12
+ Yaks::DefaultPolicy,
13
+ derive_type_from_mapper_class: 'author',
14
+ derive_mapper_from_association: AuthorMapper
15
+ )
16
+ }
17
+ let(:context) {{policy: policy, env: {}}}
18
+
19
+ it 'should map to a single Resource' do
20
+ expect(has_one.map_resource(author, context)).to eq Yaks::Resource.new(type: 'author', attributes: {name: name})
21
+ end
22
+
23
+ context 'with no mapper specified' do
24
+ let(:mapper) { Yaks::Undefined }
25
+
26
+ it 'should derive one based on policy' do
27
+ expect(has_one.create_subresource(nil, {author: author}, context)).to eql [
28
+ 'http://rel',
29
+ Yaks::Resource.new(type: 'author', attributes: {name: name})
30
+ ]
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,152 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Mapper::Link do
4
+ subject(:link) { described_class.new(rel, template, options) }
5
+
6
+ let(:rel) { :next }
7
+ let(:template) { '/foo/bar/{x}/{y}' }
8
+ let(:options) { {} }
9
+
10
+ its(:template_variables) { should eq [:x, :y] }
11
+ its(:uri_template) { should eq URITemplate.new(template) }
12
+ its(:expand?) { should be true }
13
+
14
+ describe '#rel?' do
15
+ it 'should return true if the relation matches' do
16
+ expect(link.rel?(:next)).to be true
17
+ end
18
+
19
+ it 'should return false if the relation does not match' do
20
+ expect(link.rel?(:previous)).to be false
21
+ end
22
+
23
+ context 'with URI rels' do
24
+ let(:rel) { 'http://foo/bar/rel' }
25
+
26
+ it 'should return true if the relation matches' do
27
+ expect(link.rel?('http://foo/bar/rel')).to be true
28
+ end
29
+
30
+ it 'should return false if the relation does not match' do
31
+ expect(link.rel?('http://foo/bar/other')).to be false
32
+ end
33
+ end
34
+ end
35
+
36
+ describe '#expand_with' do
37
+ it 'should look up expansion values through the provided callable' do
38
+ expect(link.expand_with(->(var){ var.upcase })).to eq '/foo/bar/X/Y'
39
+ end
40
+
41
+ context 'with expansion turned off' do
42
+ let(:options) { {expand: false} }
43
+
44
+ it 'should keep the template in the response' do
45
+ expect(link.expand_with(->{ })).to eq '/foo/bar/{x}/{y}'
46
+ end
47
+
48
+ its(:expand?) { should be false }
49
+ end
50
+
51
+ context 'with a URI without expansion variables' do
52
+ let(:template) { '/orders' }
53
+
54
+ it 'should return the link as is' do
55
+ expect(link.expand_with(->{ })).to eq '/orders'
56
+ end
57
+ end
58
+
59
+ context 'with partial expansion' do
60
+ let(:options) { { expand: [:y] } }
61
+
62
+ it 'should only expand the given variables' do
63
+ expect(link.expand_with({:y => 7}.method(:[]))).to eql '/foo/bar/{x}/7'
64
+ end
65
+ end
66
+
67
+ context 'with a symbol for a template' do
68
+ let(:template) { :a_symbol }
69
+
70
+ it 'should use the lookup mechanism for finding the link' do
71
+ expect(link.expand_with({:a_symbol => '/foo/foo'}.method(:[]))).to eq '/foo/foo'
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#map_to_resource_link' do
77
+ subject(:resource_link) { link.map_to_resource_link(mapper) }
78
+
79
+ its(:rel) { should eq :next }
80
+
81
+ let(:object) { Struct.new(:x,:y).new(3,4) }
82
+
83
+ let(:mapper) do
84
+ Yaks::Mapper.new(object, nil)
85
+ end
86
+
87
+ context 'with attributes' do
88
+ it 'should not have a title' do
89
+ expect(resource_link.options.key?(:title)).to be false
90
+ end
91
+
92
+ it 'should not be templated' do
93
+ expect(resource_link.options[:templated]).to be_falsey
94
+ end
95
+
96
+ context 'with extra options' do
97
+ let(:options) { {title: 'foo', expand: [:x], foo: :bar} }
98
+
99
+ it 'should pass on unknown options' do
100
+ expect(resource_link.options[:foo]).to eql :bar
101
+ end
102
+ end
103
+
104
+ it 'should create an instance of Yaks::Resource::Link' do
105
+ expect(resource_link).to be_a(Yaks::Resource::Link)
106
+ end
107
+
108
+ it 'should expand the URI template' do
109
+ expect(resource_link.uri).to eq '/foo/bar/3/4'
110
+ end
111
+ end
112
+
113
+ context 'with expansion turned off' do
114
+ let(:options) { {expand: false} }
115
+
116
+ it 'should be templated' do
117
+ expect(resource_link.options[:templated]).to be true
118
+ end
119
+
120
+ it 'should not propagate :expand' do
121
+ expect(resource_link.options.key?(:expand)).to be false
122
+ end
123
+ end
124
+
125
+ context 'with partial expansion' do
126
+ let(:options) { {expand: [:x]} }
127
+
128
+ it 'should be templated' do
129
+ expect(resource_link.options[:templated]).to be true
130
+ end
131
+ end
132
+
133
+ context 'with a title set' do
134
+ let(:options) { { title: 'link title' } }
135
+
136
+ it 'should set the title on the resource link' do
137
+ expect(resource_link.title).to eq 'link title'
138
+ end
139
+ end
140
+
141
+ context 'with a title lambda' do
142
+ let(:options) { { title: -> { "say #{mapper_method}" } } }
143
+
144
+ it 'should evaluate the lambda in the context of the mapper' do
145
+ expect(mapper).to receive(:mapper_method).and_return('hello')
146
+ expect(resource_link.title).to eq 'say hello'
147
+ end
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,177 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Mapper do
4
+ subject(:mapper) { mapper_class.new(instance, context) }
5
+
6
+ let(:mapper_class) { Class.new(Yaks::Mapper) { type 'foo' } }
7
+ let(:instance) { double(foo: 'hello', bar: 'world') }
8
+ let(:policy) { nil }
9
+ let(:options) { {} }
10
+ let(:context) {{policy: policy, env: {}}}
11
+
12
+ describe '#map_attributes' do
13
+ before do
14
+ mapper_class.attributes :foo, :bar
15
+ end
16
+
17
+ it 'should make the configured attributes available on the instance' do
18
+ expect(mapper.attributes).to eq [:foo, :bar]
19
+ end
20
+
21
+ it 'should load them from the model' do
22
+ expect(mapper.map_attributes).to eq(foo: 'hello', bar: 'world')
23
+ end
24
+
25
+ context 'with attribute filtering' do
26
+ before do
27
+ mapper_class.class_eval do
28
+ def filter(attrs)
29
+ attrs.to_a - [:foo]
30
+ end
31
+ end
32
+ end
33
+
34
+ it 'should only map the non-filtered attributes' do
35
+ expect(mapper.map_attributes).to eq(:bar => 'world')
36
+ end
37
+ end
38
+ end
39
+
40
+ describe '#map_links' do
41
+ before do
42
+ mapper_class.link :profile, 'http://foo/bar'
43
+ end
44
+
45
+ it 'should map the link' do
46
+ expect(mapper.map_links).to eq [
47
+ Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
48
+ ]
49
+ end
50
+
51
+ it 'should use the link in the resource' do
52
+ expect(mapper.to_resource.links).to include Yaks::Resource::Link.new(:profile, 'http://foo/bar', {})
53
+ end
54
+
55
+ context 'with the same link rel defined multiple times' do
56
+ before do
57
+ mapper_class.class_eval do
58
+ link(:self, 'http://foo/bam')
59
+ link(:self, 'http://foo/baz')
60
+ link(:self, 'http://foo/baq')
61
+ end
62
+ end
63
+
64
+ it 'should map all the links' do
65
+ expect(mapper.map_links).to eq [
66
+ Yaks::Resource::Link.new(:profile, 'http://foo/bar', {}),
67
+ Yaks::Resource::Link.new(:self, 'http://foo/bam', {}),
68
+ Yaks::Resource::Link.new(:self, 'http://foo/baz', {}),
69
+ Yaks::Resource::Link.new(:self, 'http://foo/baq', {})
70
+ ]
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#map_subresources' do
76
+ let(:instance) { double(widget: widget) }
77
+ let(:widget) { double(type: 'super_widget') }
78
+ let(:widget_mapper) { Class.new(Yaks::Mapper) { type 'widget' } }
79
+ let(:policy) { double('Policy') }
80
+
81
+ describe 'has_one' do
82
+ let(:has_one_opts) do
83
+ { mapper: widget_mapper,
84
+ rel: 'http://foo.bar/rels/widgets' }
85
+ end
86
+
87
+ before do
88
+ widget_mapper.attributes :type
89
+ mapper_class.has_one(:widget, has_one_opts)
90
+ end
91
+
92
+
93
+ it 'should have the subresource in the resource' do
94
+ expect(mapper.to_resource.subresources).to eq("http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"}))
95
+ end
96
+
97
+ context 'with explicit mapper and rel' do
98
+ it 'should delegate to the given mapper' do
99
+ expect(mapper.map_subresources).to eq(
100
+ "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
101
+ )
102
+ end
103
+ end
104
+
105
+ context 'with unspecified mapper' do
106
+ let(:has_one_opts) do
107
+ { rel: 'http://foo.bar/rels/widgets' }
108
+ end
109
+
110
+ it 'should derive the mapper based on policy' do
111
+ expect(policy).to receive(:derive_mapper_from_association) {|assoc|
112
+ expect(assoc).to be_a Yaks::Mapper::HasOne
113
+ widget_mapper
114
+ }
115
+ expect(mapper.map_subresources).to eq(
116
+ "http://foo.bar/rels/widgets" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
117
+ )
118
+ end
119
+ end
120
+
121
+ context 'with unspecified rel' do
122
+ let(:has_one_opts) do
123
+ { mapper: widget_mapper }
124
+ end
125
+
126
+ it 'should derive the rel based on policy' do
127
+ expect(policy).to receive(:derive_rel_from_association) {|parent_mapper, assoc|
128
+ expect(parent_mapper).to equal mapper
129
+ expect(assoc).to be_a Yaks::Mapper::HasOne
130
+ 'http://rel/rel'
131
+ }
132
+ expect(mapper.map_subresources).to eq(
133
+ "http://rel/rel" => Yaks::Resource.new(type: 'widget', attributes: {:type => "super_widget"})
134
+ )
135
+ end
136
+ end
137
+
138
+ context 'with the association filtered out' do
139
+ before do
140
+ mapper_class.class_eval do
141
+ def filter(attrs) [] end
142
+ end
143
+ end
144
+
145
+ it 'should not map the resource' do
146
+ expect(mapper.map_subresources).to eq({})
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ describe '#load_attributes' do
153
+ context 'when the mapper implements a method with the attribute name' do
154
+ before do
155
+ mapper_class.class_eval do
156
+ attributes :fooattr, :bar
157
+
158
+ def fooattr
159
+ "#{object.foo} my friend"
160
+ end
161
+ end
162
+ end
163
+
164
+ it 'should get the attribute from the mapper' do
165
+ expect(mapper.map_attributes).to eq(fooattr: 'hello my friend', bar: 'world')
166
+ end
167
+ end
168
+ end
169
+
170
+ describe '#to_resource' do
171
+ let(:instance) { nil }
172
+
173
+ it 'should return a NullResource when the subject is nil' do
174
+ expect(mapper.to_resource).to be_a Yaks::NullResource
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Resource do
4
+ subject(:resource) { described_class.new(init_opts) }
5
+ let(:init_opts) { {} }
6
+
7
+ its(:type) { should be_nil }
8
+ its(:attributes) { should eql({}) }
9
+ its(:links) { should eql [] }
10
+ its(:subresources) { should eql({}) }
11
+
12
+ context 'with a type' do
13
+ let(:init_opts) { { type: 'post' } }
14
+ its(:type) { should eql 'post' }
15
+ end
16
+
17
+ context 'with attributes' do
18
+ let(:init_opts) { { attributes: {name: 'Arne', age: 31} } }
19
+
20
+ it 'should delegate [] to attribute access' do
21
+ expect(resource[:name]).to eql 'Arne'
22
+ end
23
+ end
24
+
25
+ context 'with links' do
26
+ let(:init_opts) { { links: [Yaks::Resource::Link.new(:self, '/foo/bar', {})] } }
27
+ its(:links) { should eql [Yaks::Resource::Link.new(:self, '/foo/bar', {})] }
28
+ end
29
+
30
+ context 'with subresources' do
31
+ let(:init_opts) { { subresources: { 'comments' => [Yaks::Resource.new(type: 'comment')] } } }
32
+ its(:subresources) { should eql 'comments' => [Yaks::Resource.new(type: 'comment')] }
33
+ end
34
+
35
+ its(:collection?) { should equal false }
36
+
37
+ it 'should act as a collection of one' do
38
+ expect(resource.each.to_a).to eql [resource]
39
+ end
40
+ end
@@ -1,9 +1,9 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Yaks::HalSerializer do
3
+ RSpec.describe Yaks::Serializer::Hal do
4
4
  include_context 'plant collection resource'
5
5
 
6
- subject { described_class.new(resource).serialize }
6
+ subject { Yaks::Primitivize.create.call(described_class.new(resource).serialize) }
7
7
 
8
8
  it { should eq(load_json_fixture('hal_plant_collection')) }
9
9
  end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe Yaks::Serializer do
4
+ describe '.by_name' do
5
+ specify { expect(Yaks::Serializer.by_name(:hal)).to eql Yaks::Serializer::Hal }
6
+ specify { expect(Yaks::Serializer.by_name(:json_api)).to eql Yaks::Serializer::JsonApi }
7
+ end
8
+
9
+ describe '.by_mime_type' do
10
+ specify { expect(Yaks::Serializer.by_mime_type('application/hal+json')).to eql Yaks::Serializer::Hal }
11
+ end
12
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Yaks::Util do
4
+ include Yaks::Util
5
+
6
+ describe '#Resolve' do
7
+ it 'should return non-proc-values' do
8
+ expect(Resolve('foo')).to eql 'foo'
9
+ end
10
+
11
+ it 'should resolve a proc' do
12
+ expect(Resolve(->{ 123 })).to eql 123
13
+ end
14
+
15
+ it 'should resolve the proc in the given context' do
16
+ expect(Resolve(->{ upcase }, 'foo')).to eql 'FOO'
17
+ end
18
+
19
+ it 'should resolve a proc without context in the context it was lexically defined' do
20
+ expect(Resolve(->{ self })).to be_a RSpec::Core::ExampleGroup
21
+ end
22
+
23
+ it 'should receive the context as an argument when it has an arity > 0' do
24
+ expect(Resolve(->(s){ s.upcase }, 'foo')).to eql 'FOO'
25
+ end
26
+
27
+ it 'should work with method objects' do
28
+ expect(Resolve('foo'.method(:upcase))).to eql 'FOO'
29
+ end
30
+ end
31
+
32
+ describe '#camelize' do
33
+ it 'should camelize' do
34
+ expect(camelize('foo_bar/baz')).to eql 'FooBar::Baz'
35
+ end
36
+ end
37
+
38
+ describe '#underscore' do
39
+ it 'should underscorize' do
40
+ expect(underscore('FooBar::Baz-Quz::Quux')).to eql 'foo_bar/baz__quz/quux'
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,10 @@
1
+ --- !ruby/object:Scholar
2
+ id: 9
3
+ name: "孔子"
4
+ pinyin: "Kongzi"
5
+ latinized: "Confucius"
6
+ works:
7
+ - !ruby/object:Work
8
+ id: 11
9
+ chinese_name: "論語"
10
+ english_name: "Analects"
@@ -0,0 +1,9 @@
1
+ ---
2
+ - !ruby/object:Youtypeitwepostit::Message
3
+ id: 12091295723803341
4
+ text: "massage"
5
+ date_posted: "2014-05-29T07:56:58.035Z"
6
+ - !ruby/object:Youtypeitwepostit::Message
7
+ id: 613856331910938
8
+ text: "Squid!"
9
+ date_posted: "2013-03-28T21:51:08.406Z"
data/yaks.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |gem|
7
7
  gem.version = Yaks::VERSION
8
8
  gem.authors = [ 'Arne Brasseur' ]
9
9
  gem.email = [ 'arne@arnebrasseur.net' ]
10
- gem.description = 'Serialize to JSON-API and similar'
10
+ gem.description = 'Serialize to hypermedia. HAL, JSON-API, etc.'
11
11
  gem.summary = gem.description
12
12
  gem.homepage = 'https://github.com/plexus/yaks'
13
13
  gem.license = 'MIT'
@@ -17,15 +17,14 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = `git ls-files -- spec`.split($/)
18
18
  gem.extra_rdoc_files = %w[README.md]
19
19
 
20
- gem.add_runtime_dependency 'hamster'
21
- gem.add_runtime_dependency 'inflection' , '~> 1.0.0' # 12k , flog: 226.8
22
- gem.add_runtime_dependency 'concord' , '~> 0.1.4' # 8k , flog: 62.3
23
- gem.add_runtime_dependency 'uri_template' , '~> 0.6.0' # 104k , flog: 1521.4
24
-
25
- # For comparison, ActiveSupport has a flog score of 11134.7
20
+ gem.add_runtime_dependency 'inflection' , '~> 1.0'
21
+ gem.add_runtime_dependency 'concord' , '~> 0.1.4'
22
+ gem.add_runtime_dependency 'uri_template' , '~> 0.6.0'
23
+ gem.add_runtime_dependency 'rack-accept' , '~> 0.4.5'
26
24
 
27
25
  gem.add_development_dependency 'virtus'
28
- gem.add_development_dependency 'rspec', '~> 2.14'
26
+ gem.add_development_dependency 'rspec', '~> 3.0'
29
27
  gem.add_development_dependency 'rake'
30
28
  gem.add_development_dependency 'mutant-rspec'
29
+ gem.add_development_dependency 'rspec-its'
31
30
  end