grape 0.2.6 → 0.3.0
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.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- data/{CHANGELOG.markdown → CHANGELOG.md} +21 -1
- data/Gemfile +1 -0
- data/{README.markdown → README.md} +178 -125
- data/grape.gemspec +1 -1
- data/lib/grape.rb +25 -3
- data/lib/grape/api.rb +43 -20
- data/lib/grape/endpoint.rb +32 -13
- data/lib/grape/exceptions/base.rb +50 -1
- data/lib/grape/exceptions/invalid_formatter.rb +13 -0
- data/lib/grape/exceptions/invalid_versioner_option.rb +14 -0
- data/lib/grape/exceptions/invalid_with_option_for_represent.rb +15 -0
- data/lib/grape/exceptions/missing_mime_type.rb +14 -0
- data/lib/grape/exceptions/missing_option.rb +13 -0
- data/lib/grape/exceptions/missing_vendor_option.rb +13 -0
- data/lib/grape/exceptions/unknown_options.rb +14 -0
- data/lib/grape/exceptions/unknown_validator.rb +12 -0
- data/lib/grape/exceptions/{validation_error.rb → validation.rb} +3 -1
- data/lib/grape/formatter/xml.rb +2 -1
- data/lib/grape/locale/en.yml +20 -0
- data/lib/grape/middleware/base.rb +0 -5
- data/lib/grape/middleware/error.rb +1 -2
- data/lib/grape/middleware/formatter.rb +9 -5
- data/lib/grape/middleware/versioner.rb +1 -1
- data/lib/grape/middleware/versioner/header.rb +16 -6
- data/lib/grape/middleware/versioner/param.rb +1 -1
- data/lib/grape/middleware/versioner/path.rb +1 -1
- data/lib/grape/util/content_types.rb +0 -2
- data/lib/grape/validations.rb +7 -14
- data/lib/grape/validations/coerce.rb +2 -1
- data/lib/grape/validations/presence.rb +2 -1
- data/lib/grape/validations/regexp.rb +2 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +150 -5
- data/spec/grape/endpoint_spec.rb +51 -157
- data/spec/grape/entity_spec.rb +142 -520
- data/spec/grape/exceptions/invalid_formatter_spec.rb +18 -0
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +18 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +24 -0
- data/spec/grape/exceptions/missing_option_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_options_spec.rb +18 -0
- data/spec/grape/exceptions/unknown_validator_spec.rb +18 -0
- data/spec/grape/middleware/formatter_spec.rb +40 -34
- data/spec/grape/middleware/versioner/header_spec.rb +78 -20
- data/spec/grape/middleware/versioner/path_spec.rb +12 -8
- data/spec/grape/validations/coerce_spec.rb +1 -0
- data/spec/grape/validations/presence_spec.rb +8 -8
- data/spec/grape/validations_spec.rb +26 -3
- data/spec/spec_helper.rb +3 -6
- metadata +44 -9
- data/lib/grape/entity.rb +0 -386
@@ -58,6 +58,29 @@ describe Grape::Validations do
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
+
context 'group' do
|
62
|
+
before do
|
63
|
+
subject.params {
|
64
|
+
group :items do
|
65
|
+
requires :key
|
66
|
+
end
|
67
|
+
}
|
68
|
+
subject.get '/required' do 'required works'; end
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'errors when param not present' do
|
72
|
+
get '/required'
|
73
|
+
last_response.status.should == 400
|
74
|
+
last_response.body.should == 'missing parameter: items[key]'
|
75
|
+
end
|
76
|
+
|
77
|
+
it "doesn't throw a missing param when param is present" do
|
78
|
+
get '/required', { :items => [:key => 'hello', :key => 'world'] }
|
79
|
+
last_response.status.should == 200
|
80
|
+
last_response.body.should == 'required works'
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
61
84
|
context 'custom validation' do
|
62
85
|
module CustomValidations
|
63
86
|
class Customvalidator < Grape::Validations::Validator
|
@@ -147,19 +170,19 @@ describe Grape::Validations do
|
|
147
170
|
end
|
148
171
|
end
|
149
172
|
end
|
150
|
-
|
173
|
+
|
151
174
|
specify 'the parent namespace uses the validator' do
|
152
175
|
get '/nested/one', { :custom => 'im wrong, validate me'}
|
153
176
|
last_response.status.should == 400
|
154
177
|
last_response.body.should == 'custom: is not custom!'
|
155
178
|
end
|
156
|
-
|
179
|
+
|
157
180
|
specify 'the nested namesapce inherits the custom validator' do
|
158
181
|
get '/nested/nested/two', { :custom => 'im wrong, validate me'}
|
159
182
|
last_response.status.should == 400
|
160
183
|
last_response.body.should == 'custom: is not custom!'
|
161
184
|
end
|
162
|
-
|
185
|
+
|
163
186
|
specify 'peer namesapces does not have the validator' do
|
164
187
|
get '/peer/one', { :custom => 'im not validated' }
|
165
188
|
last_response.status.should == 200
|
data/spec/spec_helper.rb
CHANGED
@@ -2,20 +2,18 @@ $LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
2
2
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
3
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support'))
|
4
4
|
|
5
|
-
# $stdout = StringIO.new
|
6
|
-
|
7
5
|
require 'grape'
|
8
6
|
|
7
|
+
# require 'grape-entity'
|
8
|
+
|
9
9
|
require 'rubygems'
|
10
10
|
require 'bundler'
|
11
11
|
Bundler.setup :default, :test
|
12
12
|
|
13
13
|
require 'rack/test'
|
14
14
|
require 'pry'
|
15
|
-
|
16
15
|
require 'base64'
|
17
|
-
|
18
|
-
require 'hashie/hash'
|
16
|
+
require 'cookiejar'
|
19
17
|
|
20
18
|
Dir["#{File.dirname(__FILE__)}/support/*.rb"].each do |file|
|
21
19
|
require file
|
@@ -24,4 +22,3 @@ end
|
|
24
22
|
RSpec.configure do |config|
|
25
23
|
config.include Rack::Test::Methods
|
26
24
|
end
|
27
|
-
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -155,6 +155,22 @@ dependencies:
|
|
155
155
|
- - ! '>='
|
156
156
|
- !ruby/object:Gem::Version
|
157
157
|
version: '0'
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: grape-entity
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: 0.2.0
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: 0.2.0
|
158
174
|
- !ruby/object:Gem::Dependency
|
159
175
|
name: rake
|
160
176
|
requirement: !ruby/object:Gem::Requirement
|
@@ -262,24 +278,31 @@ files:
|
|
262
278
|
- .rspec
|
263
279
|
- .travis.yml
|
264
280
|
- .yardopts
|
265
|
-
- CHANGELOG.
|
281
|
+
- CHANGELOG.md
|
266
282
|
- Gemfile
|
267
283
|
- Guardfile
|
268
284
|
- LICENSE
|
269
|
-
- README.
|
285
|
+
- README.md
|
270
286
|
- Rakefile
|
271
287
|
- grape.gemspec
|
272
288
|
- lib/grape.rb
|
273
289
|
- lib/grape/api.rb
|
274
290
|
- lib/grape/cookies.rb
|
275
291
|
- lib/grape/endpoint.rb
|
276
|
-
- lib/grape/entity.rb
|
277
292
|
- lib/grape/error_formatter/base.rb
|
278
293
|
- lib/grape/error_formatter/json.rb
|
279
294
|
- lib/grape/error_formatter/txt.rb
|
280
295
|
- lib/grape/error_formatter/xml.rb
|
281
296
|
- lib/grape/exceptions/base.rb
|
282
|
-
- lib/grape/exceptions/
|
297
|
+
- lib/grape/exceptions/invalid_formatter.rb
|
298
|
+
- lib/grape/exceptions/invalid_versioner_option.rb
|
299
|
+
- lib/grape/exceptions/invalid_with_option_for_represent.rb
|
300
|
+
- lib/grape/exceptions/missing_mime_type.rb
|
301
|
+
- lib/grape/exceptions/missing_option.rb
|
302
|
+
- lib/grape/exceptions/missing_vendor_option.rb
|
303
|
+
- lib/grape/exceptions/unknown_options.rb
|
304
|
+
- lib/grape/exceptions/unknown_validator.rb
|
305
|
+
- lib/grape/exceptions/validation.rb
|
283
306
|
- lib/grape/formatter/base.rb
|
284
307
|
- lib/grape/formatter/json.rb
|
285
308
|
- lib/grape/formatter/serializable_hash.rb
|
@@ -312,6 +335,12 @@ files:
|
|
312
335
|
- spec/grape/api_spec.rb
|
313
336
|
- spec/grape/endpoint_spec.rb
|
314
337
|
- spec/grape/entity_spec.rb
|
338
|
+
- spec/grape/exceptions/invalid_formatter_spec.rb
|
339
|
+
- spec/grape/exceptions/invalid_versioner_option_spec.rb
|
340
|
+
- spec/grape/exceptions/missing_mime_type_spec.rb
|
341
|
+
- spec/grape/exceptions/missing_option_spec.rb
|
342
|
+
- spec/grape/exceptions/unknown_options_spec.rb
|
343
|
+
- spec/grape/exceptions/unknown_validator_spec.rb
|
315
344
|
- spec/grape/middleware/auth/basic_spec.rb
|
316
345
|
- spec/grape/middleware/auth/digest_spec.rb
|
317
346
|
- spec/grape/middleware/auth/oauth2_spec.rb
|
@@ -348,7 +377,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
348
377
|
version: '0'
|
349
378
|
segments:
|
350
379
|
- 0
|
351
|
-
hash:
|
380
|
+
hash: 1796003249167814741
|
352
381
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
353
382
|
none: false
|
354
383
|
requirements:
|
@@ -357,10 +386,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
357
386
|
version: '0'
|
358
387
|
segments:
|
359
388
|
- 0
|
360
|
-
hash:
|
389
|
+
hash: 1796003249167814741
|
361
390
|
requirements: []
|
362
391
|
rubyforge_project: grape
|
363
|
-
rubygems_version: 1.8.
|
392
|
+
rubygems_version: 1.8.25
|
364
393
|
signing_key:
|
365
394
|
specification_version: 3
|
366
395
|
summary: A simple Ruby framework for building REST-like APIs.
|
@@ -368,6 +397,12 @@ test_files:
|
|
368
397
|
- spec/grape/api_spec.rb
|
369
398
|
- spec/grape/endpoint_spec.rb
|
370
399
|
- spec/grape/entity_spec.rb
|
400
|
+
- spec/grape/exceptions/invalid_formatter_spec.rb
|
401
|
+
- spec/grape/exceptions/invalid_versioner_option_spec.rb
|
402
|
+
- spec/grape/exceptions/missing_mime_type_spec.rb
|
403
|
+
- spec/grape/exceptions/missing_option_spec.rb
|
404
|
+
- spec/grape/exceptions/unknown_options_spec.rb
|
405
|
+
- spec/grape/exceptions/unknown_validator_spec.rb
|
371
406
|
- spec/grape/middleware/auth/basic_spec.rb
|
372
407
|
- spec/grape/middleware/auth/digest_spec.rb
|
373
408
|
- spec/grape/middleware/auth/oauth2_spec.rb
|
data/lib/grape/entity.rb
DELETED
@@ -1,386 +0,0 @@
|
|
1
|
-
require 'hashie'
|
2
|
-
|
3
|
-
module Grape
|
4
|
-
# An Entity is a lightweight structure that allows you to easily
|
5
|
-
# represent data from your application in a consistent and abstracted
|
6
|
-
# way in your API. Entities can also provide documentation for the
|
7
|
-
# fields exposed.
|
8
|
-
#
|
9
|
-
# @example Entity Definition
|
10
|
-
#
|
11
|
-
# module API
|
12
|
-
# module Entities
|
13
|
-
# class User < Grape::Entity
|
14
|
-
# expose :first_name, :last_name, :screen_name, :location
|
15
|
-
# expose :field, :documentation => {:type => "string", :desc => "describe the field"}
|
16
|
-
# expose :latest_status, :using => API::Status, :as => :status, :unless => {:collection => true}
|
17
|
-
# expose :email, :if => {:type => :full}
|
18
|
-
# expose :new_attribute, :if => {:version => 'v2'}
|
19
|
-
# expose(:name){|model,options| [model.first_name, model.last_name].join(' ')}
|
20
|
-
# end
|
21
|
-
# end
|
22
|
-
# end
|
23
|
-
#
|
24
|
-
# Entities are not independent structures, rather, they create
|
25
|
-
# **representations** of other Ruby objects using a number of methods
|
26
|
-
# that are convenient for use in an API. Once you've defined an Entity,
|
27
|
-
# you can use it in your API like this:
|
28
|
-
#
|
29
|
-
# @example Usage in the API Layer
|
30
|
-
#
|
31
|
-
# module API
|
32
|
-
# class Users < Grape::API
|
33
|
-
# version 'v2'
|
34
|
-
#
|
35
|
-
# desc 'User index', { :object_fields => API::Entities::User.documentation }
|
36
|
-
# get '/users' do
|
37
|
-
# @users = User.all
|
38
|
-
# type = current_user.admin? ? :full : :default
|
39
|
-
# present @users, :with => API::Entities::User, :type => type
|
40
|
-
# end
|
41
|
-
# end
|
42
|
-
# end
|
43
|
-
class Entity
|
44
|
-
attr_reader :object, :options
|
45
|
-
|
46
|
-
# The Entity DSL allows you to mix entity functionality into
|
47
|
-
# your existing classes.
|
48
|
-
module DSL
|
49
|
-
def self.included(base)
|
50
|
-
base.extend ClassMethods
|
51
|
-
ancestor_entity_class = base.ancestors.detect{|a| a.entity_class if a.respond_to?(:entity_class)}
|
52
|
-
base.const_set(:Entity, Class.new(ancestor_entity_class || Grape::Entity)) unless const_defined?(:Entity)
|
53
|
-
end
|
54
|
-
|
55
|
-
module ClassMethods
|
56
|
-
# Returns the automatically-created entity class for this
|
57
|
-
# Class.
|
58
|
-
def entity_class(search_ancestors=true)
|
59
|
-
klass = const_get(:Entity) if const_defined?(:Entity)
|
60
|
-
klass ||= ancestors.detect{|a| a.entity_class(false) if a.respond_to?(:entity_class) } if search_ancestors
|
61
|
-
klass
|
62
|
-
end
|
63
|
-
|
64
|
-
# Call this to make exposures to the entity for this Class.
|
65
|
-
# Can be called with symbols for the attributes to expose,
|
66
|
-
# a block that yields the full Entity DSL (See Grape::Entity),
|
67
|
-
# or both.
|
68
|
-
#
|
69
|
-
# @example Symbols only.
|
70
|
-
#
|
71
|
-
# class User
|
72
|
-
# include Grape::Entity::DSL
|
73
|
-
#
|
74
|
-
# entity :name, :email
|
75
|
-
# end
|
76
|
-
#
|
77
|
-
# @example Mixed.
|
78
|
-
#
|
79
|
-
# class User
|
80
|
-
# include Grape::Entity::DSL
|
81
|
-
#
|
82
|
-
# entity :name, :email do
|
83
|
-
# expose :latest_status, using: Status::Entity, if: :include_status
|
84
|
-
# expose :new_attribute, :if => {:version => 'v2'}
|
85
|
-
# end
|
86
|
-
# end
|
87
|
-
def entity(*exposures, &block)
|
88
|
-
entity_class.expose *exposures if exposures.any?
|
89
|
-
entity_class.class_eval(&block) if block_given?
|
90
|
-
entity_class
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
# Instantiates an entity version of this object.
|
95
|
-
def entity
|
96
|
-
self.class.entity_class.new(self)
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# This method is the primary means by which you will declare what attributes
|
101
|
-
# should be exposed by the entity.
|
102
|
-
#
|
103
|
-
# @option options :as Declare an alias for the representation of this attribute.
|
104
|
-
# @option options :if When passed a Hash, the attribute will only be exposed if the
|
105
|
-
# runtime options match all the conditions passed in. When passed a lambda, the
|
106
|
-
# lambda will execute with two arguments: the object being represented and the
|
107
|
-
# options passed into the representation call. Return true if you want the attribute
|
108
|
-
# to be exposed.
|
109
|
-
# @option options :unless When passed a Hash, the attribute will be exposed if the
|
110
|
-
# runtime options fail to match any of the conditions passed in. If passed a lambda,
|
111
|
-
# it will yield the object being represented and the options passed to the
|
112
|
-
# representation call. Return true to prevent exposure, false to allow it.
|
113
|
-
# @option options :using This option allows you to map an attribute to another Grape
|
114
|
-
# Entity. Pass it a Grape::Entity class and the attribute in question will
|
115
|
-
# automatically be transformed into a representation that will receive the same
|
116
|
-
# options as the parent entity when called. Note that arrays are fine here and
|
117
|
-
# will automatically be detected and handled appropriately.
|
118
|
-
# @option options :proc If you pass a Proc into this option, it will
|
119
|
-
# be used directly to determine the value for that attribute. It
|
120
|
-
# will be called with the represented object as well as the
|
121
|
-
# runtime options that were passed in. You can also just supply a
|
122
|
-
# block to the expose call to achieve the same effect.
|
123
|
-
# @option options :documentation Define documenation for an exposed
|
124
|
-
# field, typically the value is a hash with two fields, type and desc.
|
125
|
-
def self.expose(*args, &block)
|
126
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
127
|
-
|
128
|
-
if args.size > 1
|
129
|
-
raise ArgumentError, "You may not use the :as option on multi-attribute exposures." if options[:as]
|
130
|
-
raise ArgumentError, "You may not use block-setting on multi-attribute exposures." if block_given?
|
131
|
-
end
|
132
|
-
|
133
|
-
raise ArgumentError, "You may not use block-setting when also using format_with" if block_given? && options[:format_with].respond_to?(:call)
|
134
|
-
|
135
|
-
options[:proc] = block if block_given?
|
136
|
-
|
137
|
-
args.each do |attribute|
|
138
|
-
exposures[attribute.to_sym] = options
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Returns a hash of exposures that have been declared for this Entity or ancestors. The keys
|
143
|
-
# are symbolized references to methods on the containing object, the values are
|
144
|
-
# the options that were passed into expose.
|
145
|
-
def self.exposures
|
146
|
-
@exposures ||= {}
|
147
|
-
|
148
|
-
if superclass.respond_to? :exposures
|
149
|
-
@exposures = superclass.exposures.merge(@exposures)
|
150
|
-
end
|
151
|
-
|
152
|
-
@exposures
|
153
|
-
end
|
154
|
-
|
155
|
-
# Returns a hash, the keys are symbolized references to fields in the entity,
|
156
|
-
# the values are document keys in the entity's documentation key. When calling
|
157
|
-
# #docmentation, any exposure without a documentation key will be ignored.
|
158
|
-
def self.documentation
|
159
|
-
@documentation ||= exposures.inject({}) do |memo, value|
|
160
|
-
unless value[1][:documentation].nil? || value[1][:documentation].empty?
|
161
|
-
memo[value[0]] = value[1][:documentation]
|
162
|
-
end
|
163
|
-
memo
|
164
|
-
end
|
165
|
-
|
166
|
-
if superclass.respond_to? :documentation
|
167
|
-
@documentation = superclass.documentation.merge(@documentation)
|
168
|
-
end
|
169
|
-
|
170
|
-
@documentation
|
171
|
-
end
|
172
|
-
|
173
|
-
# This allows you to declare a Proc in which exposures can be formatted with.
|
174
|
-
# It take a block with an arity of 1 which is passed as the value of the exposed attribute.
|
175
|
-
#
|
176
|
-
# @param name [Symbol] the name of the formatter
|
177
|
-
# @param block [Proc] the block that will interpret the exposed attribute
|
178
|
-
#
|
179
|
-
#
|
180
|
-
#
|
181
|
-
# @example Formatter declaration
|
182
|
-
#
|
183
|
-
# module API
|
184
|
-
# module Entities
|
185
|
-
# class User < Grape::Entity
|
186
|
-
# format_with :timestamp do |date|
|
187
|
-
# date.strftime('%m/%d/%Y')
|
188
|
-
# end
|
189
|
-
#
|
190
|
-
# expose :birthday, :last_signed_in, :format_with => :timestamp
|
191
|
-
# end
|
192
|
-
# end
|
193
|
-
# end
|
194
|
-
#
|
195
|
-
# @example Formatters are available to all decendants
|
196
|
-
#
|
197
|
-
# Grape::Entity.format_with :timestamp do |date|
|
198
|
-
# date.strftime('%m/%d/%Y')
|
199
|
-
# end
|
200
|
-
#
|
201
|
-
def self.format_with(name, &block)
|
202
|
-
raise ArgumentError, "You must pass a block for formatters" unless block_given?
|
203
|
-
formatters[name.to_sym] = block
|
204
|
-
end
|
205
|
-
|
206
|
-
# Returns a hash of all formatters that are registered for this and it's ancestors.
|
207
|
-
def self.formatters
|
208
|
-
@formatters ||= {}
|
209
|
-
|
210
|
-
if superclass.respond_to? :formatters
|
211
|
-
@formatters = superclass.formatters.merge(@formatters)
|
212
|
-
end
|
213
|
-
|
214
|
-
@formatters
|
215
|
-
end
|
216
|
-
|
217
|
-
# This allows you to set a root element name for your representation.
|
218
|
-
#
|
219
|
-
# @param plural [String] the root key to use when representing
|
220
|
-
# a collection of objects. If missing or nil, no root key will be used
|
221
|
-
# when representing collections of objects.
|
222
|
-
# @param singular [String] the root key to use when representing
|
223
|
-
# a single object. If missing or nil, no root key will be used when
|
224
|
-
# representing an individual object.
|
225
|
-
#
|
226
|
-
# @example Entity Definition
|
227
|
-
#
|
228
|
-
# module API
|
229
|
-
# module Entities
|
230
|
-
# class User < Grape::Entity
|
231
|
-
# root 'users', 'user'
|
232
|
-
# expose :id
|
233
|
-
# end
|
234
|
-
# end
|
235
|
-
# end
|
236
|
-
#
|
237
|
-
# @example Usage in the API Layer
|
238
|
-
#
|
239
|
-
# module API
|
240
|
-
# class Users < Grape::API
|
241
|
-
# version 'v2'
|
242
|
-
#
|
243
|
-
# # this will render { "users": [ {"id":"1"}, {"id":"2"} ] }
|
244
|
-
# get '/users' do
|
245
|
-
# @users = User.all
|
246
|
-
# present @users, :with => API::Entities::User
|
247
|
-
# end
|
248
|
-
#
|
249
|
-
# # this will render { "user": {"id":"1"} }
|
250
|
-
# get '/users/:id' do
|
251
|
-
# @user = User.find(params[:id])
|
252
|
-
# present @user, :with => API::Entities::User
|
253
|
-
# end
|
254
|
-
# end
|
255
|
-
# end
|
256
|
-
def self.root(plural, singular=nil)
|
257
|
-
@collection_root = plural
|
258
|
-
@root = singular
|
259
|
-
end
|
260
|
-
|
261
|
-
# This convenience method allows you to instantiate one or more entities by
|
262
|
-
# passing either a singular or collection of objects. Each object will be
|
263
|
-
# initialized with the same options. If an array of objects is passed in,
|
264
|
-
# an array of entities will be returned. If a single object is passed in,
|
265
|
-
# a single entity will be returned.
|
266
|
-
#
|
267
|
-
# @param objects [Object or Array] One or more objects to be represented.
|
268
|
-
# @param options [Hash] Options that will be passed through to each entity
|
269
|
-
# representation.
|
270
|
-
#
|
271
|
-
# @option options :root [String] override the default root name set for the
|
272
|
-
# entity. Pass nil or false to represent the object or objects with no
|
273
|
-
# root name even if one is defined for the entity.
|
274
|
-
def self.represent(objects, options = {})
|
275
|
-
inner = if objects.respond_to?(:to_ary)
|
276
|
-
objects.to_ary().map{|o| self.new(o, {:collection => true}.merge(options))}
|
277
|
-
else
|
278
|
-
self.new(objects, options)
|
279
|
-
end
|
280
|
-
|
281
|
-
root_element = if options.has_key?(:root)
|
282
|
-
options[:root]
|
283
|
-
else
|
284
|
-
objects.respond_to?(:to_ary) ? @collection_root : @root
|
285
|
-
end
|
286
|
-
root_element ? { root_element => inner } : inner
|
287
|
-
end
|
288
|
-
|
289
|
-
def initialize(object, options = {})
|
290
|
-
@object, @options = object, options
|
291
|
-
end
|
292
|
-
|
293
|
-
def exposures
|
294
|
-
self.class.exposures
|
295
|
-
end
|
296
|
-
|
297
|
-
def documentation
|
298
|
-
self.class.documentation
|
299
|
-
end
|
300
|
-
|
301
|
-
def formatters
|
302
|
-
self.class.formatters
|
303
|
-
end
|
304
|
-
|
305
|
-
# The serializable hash is the Entity's primary output. It is the transformed
|
306
|
-
# hash for the given data model and is used as the basis for serialization to
|
307
|
-
# JSON and other formats.
|
308
|
-
#
|
309
|
-
# @param runtime_options [Hash] Any options you pass in here will be known to the entity
|
310
|
-
# representation, this is where you can trigger things from conditional options
|
311
|
-
# etc.
|
312
|
-
def serializable_hash(runtime_options = {})
|
313
|
-
return nil if object.nil?
|
314
|
-
opts = options.merge(runtime_options || {})
|
315
|
-
exposures.inject({}) do |output, (attribute, exposure_options)|
|
316
|
-
if (exposure_options.has_key?(:proc) || object.respond_to?(attribute)) && conditions_met?(exposure_options, opts)
|
317
|
-
partial_output = value_for(attribute, opts)
|
318
|
-
output[key_for(attribute)] =
|
319
|
-
if partial_output.respond_to? :serializable_hash
|
320
|
-
partial_output.serializable_hash(runtime_options)
|
321
|
-
elsif partial_output.kind_of?(Array) && !partial_output.map {|o| o.respond_to? :serializable_hash}.include?(false)
|
322
|
-
partial_output.map {|o| o.serializable_hash}
|
323
|
-
else
|
324
|
-
partial_output
|
325
|
-
end
|
326
|
-
end
|
327
|
-
output
|
328
|
-
end
|
329
|
-
end
|
330
|
-
|
331
|
-
alias :as_json :serializable_hash
|
332
|
-
|
333
|
-
def to_json(options = {})
|
334
|
-
options = options.to_h if options && options.respond_to?(:to_h)
|
335
|
-
MultiJson.dump(serializable_hash(options))
|
336
|
-
end
|
337
|
-
|
338
|
-
protected
|
339
|
-
|
340
|
-
def key_for(attribute)
|
341
|
-
exposures[attribute.to_sym][:as] || attribute.to_sym
|
342
|
-
end
|
343
|
-
|
344
|
-
def value_for(attribute, options = {})
|
345
|
-
exposure_options = exposures[attribute.to_sym]
|
346
|
-
|
347
|
-
if exposure_options[:proc]
|
348
|
-
exposure_options[:proc].call(object, options)
|
349
|
-
elsif exposure_options[:using]
|
350
|
-
using_options = options.dup
|
351
|
-
using_options.delete(:collection)
|
352
|
-
using_options[:root] = nil
|
353
|
-
exposure_options[:using].represent(object.send(attribute), using_options)
|
354
|
-
elsif exposure_options[:format_with]
|
355
|
-
format_with = exposure_options[:format_with]
|
356
|
-
|
357
|
-
if format_with.is_a?(Symbol) && formatters[format_with]
|
358
|
-
formatters[format_with].call(object.send(attribute))
|
359
|
-
elsif format_with.is_a?(Symbol)
|
360
|
-
self.send(format_with, object.send(attribute))
|
361
|
-
elsif format_with.respond_to? :call
|
362
|
-
format_with.call(object.send(attribute))
|
363
|
-
end
|
364
|
-
else
|
365
|
-
object.send(attribute)
|
366
|
-
end
|
367
|
-
end
|
368
|
-
|
369
|
-
def conditions_met?(exposure_options, options)
|
370
|
-
if_condition = exposure_options[:if]
|
371
|
-
unless_condition = exposure_options[:unless]
|
372
|
-
|
373
|
-
case if_condition
|
374
|
-
when Hash; if_condition.each_pair{|k,v| return false if options[k.to_sym] != v }
|
375
|
-
when Proc; return false unless if_condition.call(object, options)
|
376
|
-
end
|
377
|
-
|
378
|
-
case unless_condition
|
379
|
-
when Hash; unless_condition.each_pair{|k,v| return false if options[k.to_sym] == v}
|
380
|
-
when Proc; return false if unless_condition.call(object, options)
|
381
|
-
end
|
382
|
-
|
383
|
-
true
|
384
|
-
end
|
385
|
-
end
|
386
|
-
end
|