Skip to content

Commit 0e13e9a

Browse files
author
Dieter Späth
committed
introduced new desc dsl with a block instead of option Hash
1 parent 54aad37 commit 0e13e9a

8 files changed

+316
-11
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Your contribution here.
66
* [#745](https://github.com/intridea/grape/pull/745): Removed `atom+xml`, `rss+xml`, and `jsonapi` content-types - [@akabraham](https://github.com/akabraham).
77
* [#745](https://github.com/intridea/grape/pull/745): Added `:binary, application/octet-stream` content-type - [@akabraham](https://github.com/akabraham).
8+
* [#757](https://github.com/intridea/grape/pull/757): Changed `desc` can now be used with a block syntax - [@dspaeth-faber](https://github.com/dspaeth-faber).
89

910
0.9.0 (8/27/2014)
1011
=================

README.md

+31-5
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,34 @@ version 'v1', using: :param, parameter: "v"
358358
You can add a description to API methods and namespaces.
359359

360360
```ruby
361-
desc "Returns your public timeline."
361+
desc "Returns your public timeline." do
362+
detail 'more details'
363+
params API::Entities::Status.documentation
364+
success API::Entities::Entity
365+
failure [[401, 'Unauthorized', "Entities::Error"]]
366+
named 'My named route'
367+
headers [XAuthToken: {
368+
description: 'Valdates your identity',
369+
required: true
370+
},
371+
XOptionalHeader: {
372+
description: 'Not really needed',
373+
required: false
374+
}
375+
]
376+
end
362377
get :public_timeline do
363378
Status.limit(20)
364379
end
365380
```
366381

382+
* `detail`: A more enhanced description
383+
* `params`: Define parameters directly from an `Entity`
384+
* `success`: (former entity) The `Entity` to be used to present by default this route
385+
* `failure`: (former http_codes) A definition of the used failure HTTP Codes and Entities
386+
* `named`: A helper to give a route a name and find it with this name in the documentation Hash
387+
* `headers`: A definition of the used Headers
388+
367389
## Parameters
368390

369391
Request parameters are available through the `params` hash object. This includes `GET`, `POST`
@@ -1019,14 +1041,18 @@ end
10191041
The following example specifies the entity to use in the `http_codes` definition.
10201042

10211043
```
1022-
desc 'My Route', http_codes: [[408, 'Unauthorized', API::Error]]
1044+
desc 'My Route' do
1045+
failure [[408, 'Unauthorized', API::Error]]
1046+
end
10231047
error!({ message: 'Unauthorized' }, 408)
10241048
```
10251049

10261050
The following example specifies the presented entity explicitly in the error message.
10271051

10281052
```ruby
1029-
desc 'My Route', http_codes: [[408, 'Unauthorized']]
1053+
desc 'My Route' do
1054+
failure [[408, 'Unauthorized']]
1055+
end
10301056
error!({ message: 'Unauthorized', with: API::Error }, 408)
10311057
```
10321058

@@ -1462,9 +1488,9 @@ module API
14621488
class Statuses < Grape::API
14631489
version 'v1'
14641490

1465-
desc 'Statuses index', {
1491+
desc 'Statuses index' do
14661492
params: API::Entities::Status.documentation
1467-
}
1493+
end
14681494
get '/statuses' do
14691495
statuses = Status.all
14701496
type = current_user.admin? ? :full : :default

UPGRADING.md

+52
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,58 @@ The following content-types have been removed:
1313

1414
This is because they have never been properly supported.
1515

16+
17+
#### Changes to desc
18+
19+
New block syntax:
20+
21+
Former:
22+
23+
```ruby
24+
desc "some descs",
25+
detail: 'more details',
26+
entity: API::Entities::Entity,
27+
params; API::Entities::Status.documentation,
28+
named: 'a name',
29+
headers :[XAuthToken: {
30+
description: 'Valdates your identity',
31+
required: true
32+
}
33+
get nil, http_codes: [
34+
[401, 'Unauthorized', API::Entities::BaseError],
35+
[404, 'not found', API::Entities::Error]
36+
37+
] do
38+
39+
```
40+
41+
Now:
42+
43+
```ruby
44+
45+
desc "some descs" do
46+
detail 'more details'
47+
params API::Entities::Status.documentation
48+
success API::Entities::Entity
49+
failure [
50+
[401, 'Unauthorized', API::Entities::BaseError],
51+
[404, 'not found', API::Entities::Error]
52+
]
53+
named 'a name'
54+
headers [XAuthToken: {
55+
description: 'Valdates your identity',
56+
required: true
57+
},
58+
XOptionalHeader: {
59+
description: 'Not really needed',
60+
required: false
61+
}
62+
]
63+
end
64+
65+
66+
```
67+
1668
### Upgrading to >= 0.9.0
1769

1870
#### Changes in Authentication

lib/grape.rb

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ module Util
104104
autoload :InheritableValues, 'grape/util/inheritable_values'
105105
autoload :StackableValues, 'grape/util/stackable_values'
106106
autoload :InheritableSetting, 'grape/util/inheritable_setting'
107+
autoload :StrictHashConfiguration, 'grape/util/strict_hash_configuration'
107108
end
108109

109110
module DSL

lib/grape/dsl/configuration.rb

+39-3
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,22 @@ def logger(logger = nil)
1919
end
2020

2121
# Add a description to the next namespace or function.
22-
def desc(description, options = {})
23-
namespace_setting :description, options.merge(description: description)
24-
route_setting :description, options.merge(description: description)
22+
def desc(description, options = {}, &config_block)
23+
if block_given?
24+
config_class = Grape::DSL::Configuration.desc_container
25+
26+
config_class.configure do
27+
description description
28+
end
29+
30+
config_class.configure(&config_block)
31+
options = config_class.settings
32+
else
33+
options = options.merge(description: description)
34+
end
35+
36+
namespace_setting :description, options
37+
route_setting :description, options
2538
end
2639
end
2740

@@ -31,6 +44,29 @@ def stacked_hash_to_hash(settings)
3144
return nil if settings.nil? || settings.blank?
3245
settings.each_with_object(ActiveSupport::OrderedHash.new) { |value, result| result.deep_merge!(value) }
3346
end
47+
48+
def desc_container
49+
Module.new do
50+
include Grape::Util::StrictHashConfiguration.module(
51+
:description,
52+
:detail,
53+
:params,
54+
:entity,
55+
:http_codes,
56+
:named,
57+
:headers
58+
)
59+
60+
def config_context.success(*args)
61+
entity(*args)
62+
end
63+
64+
def config_context.failure(*args)
65+
http_codes(*args)
66+
end
67+
68+
end
69+
end
3470
end
3571
end
3672
end
+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
module Grape
2+
module Util
3+
module StrictHashConfiguration
4+
extend ActiveSupport::Concern
5+
6+
module DSL
7+
extend ActiveSupport::Concern
8+
9+
module ClassMethods
10+
def settings
11+
config_context.to_hash
12+
end
13+
14+
def configure(&block)
15+
config_context.instance_exec(&block)
16+
end
17+
end
18+
end
19+
20+
class SettingsContainer
21+
def initialize
22+
@settings = {}
23+
@contexts = {}
24+
end
25+
26+
def to_hash
27+
@settings.to_hash
28+
end
29+
end
30+
31+
def self.config_class(*args)
32+
new_config_class = Class.new(SettingsContainer)
33+
34+
args.each do |setting_name|
35+
if setting_name.respond_to? :values
36+
nested_settings_methods(setting_name, new_config_class)
37+
else
38+
simple_settings_methods(setting_name, new_config_class)
39+
end
40+
end
41+
42+
new_config_class
43+
end
44+
45+
def self.simple_settings_methods(setting_name, new_config_class)
46+
setting_name_sym = setting_name.to_sym
47+
new_config_class.class_eval do
48+
define_method setting_name do |new_value|
49+
@settings[setting_name_sym] = new_value
50+
end
51+
end
52+
end
53+
54+
def self.nested_settings_methods(setting_name, new_config_class)
55+
new_config_class.class_eval do
56+
setting_name.each_pair do |key, value|
57+
define_method "#{key}_context" do
58+
@contexts[key] ||= Grape::Util::StrictHashConfiguration.config_class(*value).new
59+
end
60+
61+
define_method key do |&block|
62+
send("#{key}_context").instance_exec(&block)
63+
end
64+
end
65+
66+
define_method 'to_hash' do
67+
merge_hash = setting_name.keys.each_with_object({}) { |k, hash| hash[k] = send("#{k}_context").to_hash }
68+
69+
@settings.to_hash.merge(
70+
merge_hash
71+
)
72+
end
73+
74+
end
75+
end
76+
77+
def self.module(*args)
78+
new_module = Module.new do
79+
extend ActiveSupport::Concern
80+
include DSL
81+
end
82+
83+
new_module.tap do |mod|
84+
85+
class_mod = create_class_mod(args)
86+
87+
mod.const_set(:ClassMethods, class_mod)
88+
89+
end
90+
end
91+
92+
def self.create_class_mod(args)
93+
new_module = Module.new do
94+
def config_context
95+
@config_context ||= config_class.new
96+
end
97+
end
98+
99+
new_module.tap do |class_mod|
100+
new_config_class = config_class(*args)
101+
102+
class_mod.send(:define_method, :config_class) do
103+
@config_context ||= new_config_class
104+
end
105+
end
106+
end
107+
end
108+
end
109+
end

spec/grape/dsl/configuration_spec.rb

+44-3
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,51 @@ class Dummy
2424

2525
describe '.desc' do
2626
it 'sets a description' do
27+
desc_text = 'The description'
2728
options = { message: 'none' }
28-
subject.desc options
29-
expect(subject.namespace_setting(:description)).to eq(description: options)
30-
expect(subject.route_setting(:description)).to eq(description: options)
29+
subject.desc desc_text, options
30+
expect(subject.namespace_setting(:description)).to eq(options.merge(description: desc_text))
31+
expect(subject.route_setting(:description)).to eq(options.merge(description: desc_text))
32+
end
33+
34+
it 'can be set with a block' do
35+
expected_options = {
36+
description: 'The description',
37+
detail: 'more details',
38+
params: { first: :param },
39+
entity: Object,
40+
http_codes: [[401, 'Unauthorized', "Entities::Error"]],
41+
named: "My named route",
42+
headers: [XAuthToken: {
43+
description: 'Valdates your identity',
44+
required: true
45+
},
46+
XOptionalHeader: {
47+
description: 'Not really needed',
48+
required: false
49+
}
50+
]
51+
}
52+
53+
subject.desc 'The description' do
54+
detail 'more details'
55+
params(first: :param)
56+
success Object
57+
failure [[401, 'Unauthorized', "Entities::Error"]]
58+
named 'My named route'
59+
headers [XAuthToken: {
60+
description: 'Valdates your identity',
61+
required: true
62+
},
63+
XOptionalHeader: {
64+
description: 'Not really needed',
65+
required: false
66+
}
67+
]
68+
end
69+
70+
expect(subject.namespace_setting(:description)).to eq(expected_options)
71+
expect(subject.route_setting(:description)).to eq(expected_options)
3172
end
3273
end
3374

0 commit comments

Comments
 (0)