Skip to content

Commit b27eed7

Browse files
committed
Merge pull request ruby-grape#413 from Bugagazavr/representers
Representers
2 parents 193c781 + 2c205a9 commit b27eed7

30 files changed

+1047
-482
lines changed

.rubocop_todo.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ Lint/UselessAssignment:
2323

2424
# Offense count: 27
2525
Metrics/AbcSize:
26-
Max: 68
26+
Max: 69
2727

2828
# Offense count: 3
2929
# Configuration parameters: CountComments.
3030
Metrics/ClassLength:
31-
Max: 234
31+
Max: 284
3232

3333
# Offense count: 11
3434
Metrics/CyclomaticComplexity:

.travis.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ sudo: false
44

55
matrix:
66
include:
7+
- rvm: 2.3.1
8+
env: MODEL_PARSER=grape-swagger-entity
9+
- rvm: 2.3.1
10+
env: MODEL_PARSER=grape-swagger-representable
711
- rvm: 2.3.1
812
env: GRAPE_VERSION=0.12.0
913
- rvm: 2.3.1

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
### Next
22

3+
#### Features
4+
5+
* [#413](https://github.com/ruby-grape/grape-swagger/pull/413): Move all model parsing logic to separate gems `grape-swagger-entity` and added representable parser `grape-swagger` - [@Bugagazavr](https://github.com/Bugagazavr).
36
* Your contribution here.
47

58
### 0.20.3 (May 9, 2016)

Gemfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ when 'HEAD'
88
else
99
gem 'grape', version
1010
end
11+
12+
gem ENV['MODEL_PARSER'], github: "bugagazavr/#{ENV['MODEL_PARSER']}" if ENV.key?('MODEL_PARSER')

README.md

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,71 @@ The grape-swagger gem provides an autogenerated documentation for your [Grape](h
2727

2828
These screenshot is based on the [Hussars](https://github.com/LeFnord/hussars) sample app.
2929

30+
## Model Parsers
31+
32+
Since 0.21.0, `Grape::Entity` is not a part of grape-swagger, you need to add `grape-swagger-entity` manually to your Gemfile.
33+
Also added support for [representable](https://github.com/apotonick/representable) via `grape-swagger-representable`.
34+
35+
```ruby
36+
# For Grape::Entity ( https://github.com/ruby-grape/grape-entity )
37+
gem 'grape-swagger-entity'
38+
# For representable ( https://github.com/apotonick/representable )
39+
gem 'grape-swagger-representable'
40+
```
41+
42+
##### Custom Model Parsers
43+
44+
You can create your own model parser, for example for [roar](https://github.com/apotonick/roar).
45+
46+
```rb
47+
module GrapeSwagger
48+
module Roar
49+
class Parser
50+
attr_reader :model
51+
attr_reader :endpoint
52+
53+
def initialize(model, endpoint)
54+
@model = model
55+
@endpoint = endpoint
56+
end
57+
58+
def call
59+
# Parse your model and return hash with model schema for swagger
60+
end
61+
end
62+
end
63+
end
64+
```
65+
66+
Then you should register your custom parser.
67+
68+
```rb
69+
GrapeSwagger.model_parsers.register(GrapeSwagger::Roar::Parser, Roar::Decorator)
70+
```
71+
72+
To control model parsers sequence, you can insert your parser before or after another parser.
73+
74+
#### insert_before
75+
76+
```rb
77+
GrapeSwagger.model_parsers.insert_before(GrapeSwagger::Representable::Parser, GrapeSwagger::Roar::Parser, Roar::Decorator)
78+
```
79+
80+
#### insert_after
81+
82+
```rb
83+
GrapeSwagger.model_parsers.insert_after(GrapeSwagger::Roar::Parser, GrapeSwagger::Representable::Parser, Representable::Decorator)
84+
```
85+
86+
As we know, `Roar::Decorator` uses as superclass `Representable::Decorator`, this allow to avoid problem when Roar objects will be processed by `GrapeSwagger::Representable::Parser`, instead `GrapeSwagger::Roar::Parser`.
3087

3188
<a name="related" />
3289
## Related Projects
3390

3491
* [Grape](https://github.com/ruby-grape/grape)
35-
* [Grape Entity](https://github.com/ruby-grape/grape-entity)
92+
* [Grape Swagger Entity](https://github.com/ruby-grape/grape-swagger-entity)
93+
* [Grape Entity](https://github.com/ruby-grape/grape-entity)
94+
* [Grape Swagger Representable](https://github.com/ruby-grape/grape-swagger-representable)
3695
* [Swagger UI](https://github.com/wordnik/swagger-ui)
3796

3897

@@ -41,12 +100,13 @@ These screenshot is based on the [Hussars](https://github.com/LeFnord/hussars) s
41100

42101
The following versions of grape, grape-entity and grape-swagger can currently be used together.
43102

44-
grape-swagger | swagger spec | grape | grape-entity
45-
--------------|--------------|-------------------------|-------------
46-
0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0
47-
0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0
48-
0.20.1 | 2.0 | >= 0.12.0 ... <= 0.14.0 | <= 0.5.1
49-
0.20.3 | 2.0 | >= 0.12.0 ... ~> 0.16.2 | ~> 0.5.1
103+
grape-swagger | swagger spec | grape | grape-entity | representable |
104+
--------------|--------------|-------------------------|--------------|---------------|
105+
0.10.5 | 1.2 | >= 0.10.0 ... <= 0.14.0 | < 0.5.0 | n/a |
106+
0.11.0 | 1.2 | >= 0.16.2 | < 0.5.0 | n/a |
107+
0.20.1 | 2.0 | >= 0.12.0 ... <= 0.14.0 | <= 0.5.1 | n/a |
108+
0.20.3 | 2.0 | >= 0.12.0 ... ~> 0.16.2 | ~> 0.5.1 | n/a |
109+
0.21.0 (next) | 2.0 | >= 0.12.0 ... <= 0.16.2 | <= 0.5.1 | >= 2.4.1 |
50110

51111
<a name="swagger-spec" />
52112
## Swagger-Spec

UPGRADING.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
Upgrading Grape-swagger
22
=======================
33

4+
### Upgrading to >= 0.21.0
5+
6+
With grape >= 0.21.0, `grape-entity` support moved to separate gem `grape-swagger-entity`, if you use grape entity, update your Gemfile:
7+
8+
```ruby
9+
gem 'grape-swagger'
10+
gem 'grape-swagger-entity'
11+
```
12+
413
### Upgrading to >= 0.10.2
514

615
With grape >= 0.12.0, support for `notes` is replaced by passing a block `detail` option specified. For future compatibility, update your code:

grape-swagger.gemspec

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ Gem::Specification.new do |s|
1212
s.license = 'MIT'
1313

1414
s.add_runtime_dependency 'grape', '>= 0.12.0'
15-
s.add_runtime_dependency 'grape-entity'
1615
s.add_runtime_dependency 'awesome_print'
1716

1817
s.add_development_dependency 'rake'

lib/grape-swagger.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,21 @@
77
require 'grape-swagger/errors'
88

99
require 'grape-swagger/doc_methods'
10+
require 'grape-swagger/model_parsers'
1011

1112
require 'grape-swagger/markdown/kramdown_adapter'
1213
require 'grape-swagger/markdown/redcarpet_adapter'
1314

1415
require 'awesome_print'
1516

17+
module GrapeSwagger
18+
class << self
19+
def model_parsers
20+
@model_parsers ||= GrapeSwagger::ModelParsers.new
21+
end
22+
end
23+
end
24+
1625
module Grape
1726
class API
1827
class << self

lib/grape-swagger/endpoint.rb

Lines changed: 9 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -226,55 +226,20 @@ def parse_request_params(required)
226226
end
227227
end
228228

229-
def parse_response_params(params)
230-
return if params.nil?
231-
232-
params.each_with_object({}) do |x, memo|
233-
next if x[1].fetch(:documentation, {}).fetch(:in, nil).to_s == 'header'
234-
x[0] = x.last[:as] if x.last[:as]
235-
236-
model = x.last[:using] if x.last[:using].present?
237-
model ||= x.last[:documentation][:type] if x.last[:documentation] && could_it_be_a_model?(x.last[:documentation])
238-
239-
if model
240-
name = expose_params_from_model(model)
241-
memo[x.first] = if x.last[:documentation] && x.last[:documentation][:is_array]
242-
{ 'type' => 'array', 'items' => { '$ref' => "#/definitions/#{name}" } }
243-
else
244-
{ '$ref' => "#/definitions/#{name}" }
245-
end
246-
else
247-
documented_type = x.last[:type]
248-
documented_type ||= x.last[:documentation][:type] if x.last[:documentation]
249-
data_type = GrapeSwagger::DocMethods::DataType.call(documented_type)
250-
251-
if GrapeSwagger::DocMethods::DataType.primitive?(data_type)
252-
data = GrapeSwagger::DocMethods::DataType.mapping(data_type)
253-
memo[x.first] = { type: data.first, format: data.last }
254-
else
255-
memo[x.first] = { type: data_type }
256-
end
257-
258-
memo[x.first][:enum] = x.last[:values] if x.last[:values] && x.last[:values].is_a?(Array)
259-
end
260-
memo[x.first][:description] = x.last[:documentation][:desc] if x.last[:documentation] && x.last[:documentation][:desc]
261-
end
262-
end
263-
264229
def expose_params_from_model(model)
265230
model_name = model_name(model)
266231

267-
# TODO: this should only be a temporary hack ;)
268-
if GrapeEntity::VERSION =~ /0\.4\.\d/
269-
parameters = model.exposures ? model.exposures : model.documentation
270-
elsif GrapeEntity::VERSION =~ /0\.5\.\d/
271-
parameters = model.root_exposures.each_with_object({}) do |value, memo|
272-
memo[value.attribute] = value.send(:options)
273-
end
232+
properties = {}
233+
GrapeSwagger.model_parsers.each do |klass, ancestor|
234+
next unless model.ancestors.map(&:to_s).include?(ancestor)
235+
236+
model_class = klass.new(model, self)
237+
properties = model_class.call
238+
239+
break
274240
end
275-
properties = parse_response_params(parameters)
276241

277-
@definitions[model_name] = { type: 'object', properties: properties }
242+
@definitions[model_name] = { type: 'object', properties: properties || {} }
278243

279244
model_name
280245
end
@@ -283,17 +248,6 @@ def model_name(name)
283248
name.respond_to?(:name) ? name.name.demodulize.camelize : name.split('::').last
284249
end
285250

286-
def could_it_be_a_model?(value)
287-
(
288-
value[:type].to_s.include?('Entity') || value[:type].to_s.include?('Entities')
289-
) || (
290-
value[:type] &&
291-
value[:type].is_a?(Class) &&
292-
!GrapeSwagger::DocMethods::DataType.primitive?(value[:type].name.downcase) &&
293-
!value[:type] == Array
294-
)
295-
end
296-
297251
def hidden?(route)
298252
route_hidden = route.options[:hidden]
299253
route_hidden = route_hidden.call if route_hidden.is_a?(Proc)

lib/grape-swagger/model_parsers.rb

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module GrapeSwagger
2+
class ModelParsers
3+
include Enumerable
4+
5+
def initialize
6+
@parsers = {}
7+
end
8+
9+
def register(klass, ancestor)
10+
@parsers[klass] = ancestor.to_s
11+
end
12+
13+
def insert_before(before_klass, klass, ancestor)
14+
subhash = @parsers.except(klass).to_a
15+
insert_at = subhash.index(subhash.assoc(before_klass))
16+
insert_at = subhash.length - 1 if insert_at.nil?
17+
@parsers = Hash[subhash.insert(insert_at, [klass, ancestor])]
18+
end
19+
20+
def insert_after(after_klass, klass, ancestor)
21+
subhash = @parsers.except(klass).to_a
22+
insert_at = subhash.index(subhash.assoc(after_klass))
23+
insert_at = subhash.length - 1 if insert_at.nil?
24+
@parsers = Hash[subhash.insert(insert_at + 1, [klass, ancestor])]
25+
end
26+
27+
def each
28+
@parsers.each_pair do |klass, ancestor|
29+
yield klass, ancestor
30+
end
31+
end
32+
end
33+
end

spec/lib/model_parsers_spec.rb

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
require 'spec_helper'
2+
3+
describe GrapeSwagger::ModelParsers do
4+
let(:model_parsers) { described_class.new }
5+
let(:parser) { Class.new }
6+
let(:parser2) { Class.new }
7+
let(:parser3) { Class.new }
8+
9+
describe '#register' do
10+
describe 'successfully register new parser' do
11+
before do
12+
model_parsers.register(parser, Class)
13+
end
14+
15+
specify do
16+
expect(model_parsers.to_a).to eq([[parser, 'Class']])
17+
end
18+
end
19+
20+
describe 'should be empty if no registred parsers' do
21+
specify do
22+
expect(model_parsers.to_a).to be_empty
23+
end
24+
end
25+
end
26+
27+
describe '#insert_before' do
28+
describe 'SomeModelParser2 should be first parser' do
29+
before do
30+
model_parsers.register(parser, Class)
31+
model_parsers.register(parser3, Class)
32+
model_parsers.insert_before(parser, parser2, Class)
33+
end
34+
35+
specify do
36+
expect(model_parsers.count).to eq(3)
37+
expect(model_parsers.to_a.first).to eq([parser2, Class])
38+
end
39+
end
40+
41+
describe 'SomeModelParser2 should be inserted anyway if SomeModelParser not registred' do
42+
before do
43+
model_parsers.register(parser3, Class)
44+
model_parsers.insert_before(parser, parser2, Class)
45+
end
46+
47+
specify do
48+
expect(model_parsers.count).to eq(2)
49+
expect(model_parsers.to_a).to include([parser2, Class])
50+
end
51+
end
52+
53+
describe 'SomeModelParser2 should be inserted anyway if model parsers is empty' do
54+
before do
55+
model_parsers.insert_before(parser, parser2, Class)
56+
end
57+
58+
specify do
59+
expect(model_parsers.count).to eq(1)
60+
expect(model_parsers.to_a).to include([parser2, Class])
61+
end
62+
end
63+
end
64+
65+
describe '#insert_after' do
66+
describe 'SomeModelParser2 should be second parser' do
67+
before do
68+
model_parsers.register(parser, Class)
69+
model_parsers.register(parser3, Class)
70+
model_parsers.insert_after(parser, parser2, Class)
71+
end
72+
73+
specify do
74+
expect(model_parsers.count).to eq(3)
75+
expect(model_parsers.to_a[1]).to eq([parser2, Class])
76+
end
77+
end
78+
79+
describe 'SomeModelParser2 should be inserted anyway if SomeModelParser not registred' do
80+
before do
81+
model_parsers.register(parser3, Class)
82+
model_parsers.insert_after(parser, parser2, Class)
83+
end
84+
85+
specify do
86+
expect(model_parsers.count).to eq(2)
87+
expect(model_parsers.to_a).to include([parser2, Class])
88+
end
89+
end
90+
91+
describe 'SomeModelParser2 should be inserted anyway if model parsers is empty' do
92+
before do
93+
model_parsers.insert_after(parser, parser2, Class)
94+
end
95+
96+
specify do
97+
expect(model_parsers.count).to eq(1)
98+
expect(model_parsers.to_a).to include([parser2, Class])
99+
end
100+
end
101+
end
102+
end

0 commit comments

Comments
 (0)