the_garage 2.0.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +162 -0
- data/Rakefile +33 -0
- data/app/assets/javascripts/garage/application.js +16 -0
- data/app/assets/javascripts/garage/docs/console.js.coffee +90 -0
- data/app/assets/javascripts/garage/docs/jquery.colorbox.js +1026 -0
- data/app/assets/stylesheets/garage/application.css +14 -0
- data/app/assets/stylesheets/garage/colorbox.scss +62 -0
- data/app/assets/stylesheets/garage/style.scss +59 -0
- data/app/assets/stylesheets/vendor/bootstrap.min.css +9 -0
- data/app/controllers/garage/application_controller.rb +4 -0
- data/app/controllers/garage/docs/resources_controller.rb +103 -0
- data/app/controllers/garage/meta/docs_controller.rb +20 -0
- data/app/controllers/garage/meta/services_controller.rb +20 -0
- data/app/helpers/garage/application_helper.rb +4 -0
- data/app/helpers/garage/docs/resources_helper.rb +24 -0
- data/app/models/garage/hash_representer.rb +11 -0
- data/app/views/garage/docs/resources/_layout_navigation.html.haml +5 -0
- data/app/views/garage/docs/resources/_navigation.html.haml +6 -0
- data/app/views/garage/docs/resources/callback.html.haml +5 -0
- data/app/views/garage/docs/resources/console.html.haml +45 -0
- data/app/views/garage/docs/resources/index.html.haml +2 -0
- data/app/views/garage/docs/resources/show.html.haml +16 -0
- data/app/views/layouts/garage/application.html.haml +26 -0
- data/config/routes.rb +0 -0
- data/lib/garage/app_responder.rb +22 -0
- data/lib/garage/authorizable.rb +26 -0
- data/lib/garage/config.rb +76 -0
- data/lib/garage/controller_helper.rb +110 -0
- data/lib/garage/docs/anchor_building.rb +28 -0
- data/lib/garage/docs/application.rb +24 -0
- data/lib/garage/docs/config.rb +61 -0
- data/lib/garage/docs/console_link_building.rb +14 -0
- data/lib/garage/docs/document.rb +141 -0
- data/lib/garage/docs/engine.rb +35 -0
- data/lib/garage/docs/example.rb +26 -0
- data/lib/garage/docs/renderer.rb +17 -0
- data/lib/garage/docs/toc_renderer.rb +14 -0
- data/lib/garage/docs.rb +9 -0
- data/lib/garage/exceptions.rb +49 -0
- data/lib/garage/hypermedia_filter.rb +44 -0
- data/lib/garage/hypermedia_responder.rb +120 -0
- data/lib/garage/meta/engine.rb +16 -0
- data/lib/garage/meta/remote_service.rb +78 -0
- data/lib/garage/meta.rb +6 -0
- data/lib/garage/meta_resource.rb +17 -0
- data/lib/garage/nested_field_query.rb +183 -0
- data/lib/garage/optional_response_body_responder.rb +16 -0
- data/lib/garage/paginating_responder.rb +113 -0
- data/lib/garage/permission.rb +13 -0
- data/lib/garage/permissions.rb +75 -0
- data/lib/garage/representer.rb +214 -0
- data/lib/garage/resource_casting_responder.rb +13 -0
- data/lib/garage/restful_actions.rb +219 -0
- data/lib/garage/strategy/access_token.rb +57 -0
- data/lib/garage/strategy/auth_server.rb +200 -0
- data/lib/garage/strategy/no_authentication.rb +13 -0
- data/lib/garage/strategy/test.rb +44 -0
- data/lib/garage/strategy.rb +4 -0
- data/lib/garage/test/migrator.rb +31 -0
- data/lib/garage/token_scope.rb +134 -0
- data/lib/garage/utils.rb +28 -0
- data/lib/garage/version.rb +3 -0
- data/lib/garage.rb +23 -0
- metadata +275 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
module Garage
|
2
|
+
module NestedFieldQuery
|
3
|
+
class InvalidQuery < StandardError; end
|
4
|
+
class InvalidData < StandardError; end
|
5
|
+
|
6
|
+
class Parser
|
7
|
+
def self.parse(*args)
|
8
|
+
new.parse(*args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse(given_query, indent = nil)
|
12
|
+
parse_recurse(given_query.to_s.dup, indent)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def parse_recurse(query, indent)
|
18
|
+
result = []
|
19
|
+
current = nil
|
20
|
+
while query.sub!(/^(?:\s*([\w\.\*]+)|(,)|(\[)|(\]))/, '')
|
21
|
+
if $1
|
22
|
+
current = $1.to_s
|
23
|
+
elsif $2
|
24
|
+
if current
|
25
|
+
result << current
|
26
|
+
current = nil
|
27
|
+
else
|
28
|
+
raise InvalidQuery, "Expected field name: #{query}"
|
29
|
+
end
|
30
|
+
elsif $3
|
31
|
+
if current
|
32
|
+
current = { current => parse_recurse(query, 1) }
|
33
|
+
else
|
34
|
+
raise InvalidQuery, "'[' should come after field: #{query}"
|
35
|
+
end
|
36
|
+
elsif $4
|
37
|
+
if indent
|
38
|
+
if current
|
39
|
+
result << current
|
40
|
+
return merge(result)
|
41
|
+
else
|
42
|
+
raise InvalidQuery, "']' should be after '[field': #{query}"
|
43
|
+
end
|
44
|
+
else
|
45
|
+
raise InvalidQuery, "']' should be after '[field': #{query}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
if current
|
51
|
+
result << current
|
52
|
+
else
|
53
|
+
raise InvalidQuery, "premature end of query"
|
54
|
+
end
|
55
|
+
|
56
|
+
merge(result)
|
57
|
+
end
|
58
|
+
|
59
|
+
def merge(result)
|
60
|
+
hash = Hash.new
|
61
|
+
result.each do |res|
|
62
|
+
if res.is_a?(Hash)
|
63
|
+
hash.merge!(res)
|
64
|
+
else
|
65
|
+
hash[res] = nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
hash
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Builder
|
73
|
+
def self.build(*args)
|
74
|
+
new.build(*args)
|
75
|
+
end
|
76
|
+
|
77
|
+
def build(arg)
|
78
|
+
val = ''
|
79
|
+
|
80
|
+
case arg
|
81
|
+
when Hash
|
82
|
+
val << arg.map { |key, value|
|
83
|
+
if value.nil?
|
84
|
+
key
|
85
|
+
else
|
86
|
+
"#{key}[#{build(value)}]"
|
87
|
+
end
|
88
|
+
}.join(',')
|
89
|
+
when Symbol, String
|
90
|
+
val << arg.to_s
|
91
|
+
else
|
92
|
+
raise InvalidData, "Can't encode data type: #{arg.class}"
|
93
|
+
end
|
94
|
+
|
95
|
+
val
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class DefaultSelector
|
100
|
+
# kinda NullObject pattern
|
101
|
+
|
102
|
+
# Doesn't specify anything - includes/excludes returns both false :)
|
103
|
+
|
104
|
+
def includes?(field)
|
105
|
+
false
|
106
|
+
end
|
107
|
+
|
108
|
+
def excludes?(field)
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
112
|
+
def [](name)
|
113
|
+
DefaultSelector.new
|
114
|
+
end
|
115
|
+
|
116
|
+
def canonical
|
117
|
+
''
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class FullSelector < DefaultSelector
|
122
|
+
def includes?(field)
|
123
|
+
true
|
124
|
+
end
|
125
|
+
|
126
|
+
def excludes?(field)
|
127
|
+
false
|
128
|
+
end
|
129
|
+
|
130
|
+
def [](name)
|
131
|
+
FullSelector.new
|
132
|
+
end
|
133
|
+
|
134
|
+
def canonical
|
135
|
+
'*'
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Selector
|
140
|
+
# includes eager loading
|
141
|
+
|
142
|
+
def self.build(fields)
|
143
|
+
if fields.present?
|
144
|
+
build_parsed(Parser.parse(fields))
|
145
|
+
else
|
146
|
+
NestedFieldQuery::DefaultSelector.new
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def self.build_parsed(fields)
|
151
|
+
if fields.key? '*'
|
152
|
+
FullSelector.new
|
153
|
+
else
|
154
|
+
self.new(fields)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def initialize(fields = {})
|
159
|
+
@fields = fields
|
160
|
+
end
|
161
|
+
|
162
|
+
def [](name)
|
163
|
+
if @fields[name].nil?
|
164
|
+
DefaultSelector.new
|
165
|
+
else
|
166
|
+
Selector.build_parsed(@fields[name])
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def canonical
|
171
|
+
Builder.build(@fields)
|
172
|
+
end
|
173
|
+
|
174
|
+
def includes?(field)
|
175
|
+
@fields.has_key?(field)
|
176
|
+
end
|
177
|
+
|
178
|
+
def excludes?(field)
|
179
|
+
!@fields.has_key?('__default__') && !@fields.has_key?(field)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Garage::OptionalResponseBodyResponder
|
2
|
+
protected
|
3
|
+
|
4
|
+
def api_behavior(*)
|
5
|
+
case
|
6
|
+
when put? && options[:put] && options[:put][:body]
|
7
|
+
display resource, status: options[:put][:status] || :ok
|
8
|
+
when patch? && options[:patch] && options[:patch][:body]
|
9
|
+
display resource, status: options[:patch][:status] || :ok
|
10
|
+
when delete? && options[:delete] && options[:delete][:body]
|
11
|
+
display resource, status: options[:delete][:status] || :ok
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Garage
|
2
|
+
module PaginatingResponder
|
3
|
+
def display(resource, *args)
|
4
|
+
if @options[:paginate]
|
5
|
+
resource = paginate resource
|
6
|
+
end
|
7
|
+
super(resource, *args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def max_per_page=(count)
|
11
|
+
@max_per_page = count
|
12
|
+
end
|
13
|
+
|
14
|
+
def reveal_total!
|
15
|
+
@options.delete(:hard_limit)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def distinct?
|
21
|
+
!!@options[:distinct_by]
|
22
|
+
end
|
23
|
+
|
24
|
+
def hide_total?
|
25
|
+
!!@options[:hard_limit]
|
26
|
+
end
|
27
|
+
|
28
|
+
def hard_limit
|
29
|
+
@options[:hard_limit]
|
30
|
+
end
|
31
|
+
|
32
|
+
def max_per_page
|
33
|
+
@options[:max_per_page] || @max_per_page || 100
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_total_count(rs, per_page)
|
37
|
+
if hard_limit
|
38
|
+
limit = hard_limit
|
39
|
+
rs.instance_variable_set(:@total_count, limit)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def total_count(rs)
|
44
|
+
if distinct?
|
45
|
+
rs.total_count(@options[:distinct_by], distinct: true)
|
46
|
+
else
|
47
|
+
rs.total_count
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def paginate(rs)
|
52
|
+
@options[:hard_limit] ||= 1000 if @options[:hide_total] # backward compat for hide_total
|
53
|
+
|
54
|
+
per_page = [ max_per_page, (controller.params[:per_page] || @options[:per_page] || 20).to_i ].min
|
55
|
+
|
56
|
+
rs = rs.page(controller.params[:page] || 1).per(per_page)
|
57
|
+
|
58
|
+
set_total_count(rs, per_page)
|
59
|
+
|
60
|
+
unless hide_total?
|
61
|
+
controller.response.headers['X-List-TotalCount'] = total_count(rs).to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
# FIXME construct_links must be called after calling rs.total_count to avoid invalid count cache
|
65
|
+
construct_links(rs, per_page)
|
66
|
+
|
67
|
+
if hide_total?
|
68
|
+
if rs.offset_value > hard_limit
|
69
|
+
rs = []
|
70
|
+
elsif rs.offset_value + per_page > hard_limit
|
71
|
+
rs = rs.slice 0, (hard_limit - rs.offset_value) # becomes Array here, and hope it's ok
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
rs
|
76
|
+
end
|
77
|
+
|
78
|
+
def construct_links(rs, per_page)
|
79
|
+
build_link_hash(rs, links={})
|
80
|
+
add_link_header(links, per_page) unless links.empty?
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_link_hash(rs, links)
|
84
|
+
unless rs.first_page?
|
85
|
+
links[:first] = 1
|
86
|
+
links[:prev] = rs.current_page - 1
|
87
|
+
end
|
88
|
+
|
89
|
+
if rs.current_page < rs.total_pages
|
90
|
+
links[:next] = rs.current_page + 1
|
91
|
+
end
|
92
|
+
|
93
|
+
unless rs.last_page? || hide_total?
|
94
|
+
links[:last] = rs.total_pages
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def build_path_for(params)
|
99
|
+
parameters = controller.request.query_parameters.merge(params).tap {|p| p.delete(:access_token) }
|
100
|
+
"#{controller.request.path}?#{parameters.to_query}"
|
101
|
+
end
|
102
|
+
|
103
|
+
def add_link_header(links, per_page)
|
104
|
+
headers = []
|
105
|
+
links.each do |rel, page|
|
106
|
+
url = build_path_for(:page => page, :per_page => per_page)
|
107
|
+
headers << "<#{url}>; rel=\"#{rel}\"; page=\"#{page}\""
|
108
|
+
end
|
109
|
+
controller.response.headers['Link'] = headers.join ', '
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# Public: represents permissions of the current request user against
|
2
|
+
# the resource and resource class.
|
3
|
+
#
|
4
|
+
# Examples
|
5
|
+
#
|
6
|
+
# class Post
|
7
|
+
# include Garage::Authorizable
|
8
|
+
#
|
9
|
+
# def build_permissions(perms, other)
|
10
|
+
# perms.permits! :read
|
11
|
+
# perms.permits! :write if owner == other
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# def self.build_permissions(perms, other, target)
|
15
|
+
# if target[:user]
|
16
|
+
# perms.permits! :read, :write if target[:user] == other
|
17
|
+
# else
|
18
|
+
# perms.permits! :read, :write
|
19
|
+
# end
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
require "garage/permission"
|
23
|
+
|
24
|
+
module Garage
|
25
|
+
class Permissions
|
26
|
+
attr_accessor :user, :resource_class
|
27
|
+
|
28
|
+
def initialize(user, resource_class, permissions = { read: :forbidden, write: :forbidden })
|
29
|
+
@user = user
|
30
|
+
@resource_class = resource_class
|
31
|
+
@perms = permissions
|
32
|
+
end
|
33
|
+
|
34
|
+
def authorize!(action)
|
35
|
+
exists? or raise PermissionError.new(user, action, resource_class, :not_found)
|
36
|
+
permits?(action) or raise PermissionError.new(user, action, resource_class, :forbidden)
|
37
|
+
end
|
38
|
+
|
39
|
+
def for(action)
|
40
|
+
Permission.new(@user, action, @perms[action])
|
41
|
+
end
|
42
|
+
|
43
|
+
def deleted!
|
44
|
+
@perms[:deleted] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def exists?
|
48
|
+
!@perms[:deleted]
|
49
|
+
end
|
50
|
+
|
51
|
+
def permits!(*actions)
|
52
|
+
actions.each do |action|
|
53
|
+
@perms[action] = :ok
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def forbids!(*actions)
|
58
|
+
actions.each do |action|
|
59
|
+
@perms[action] = :forbidden
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def permits?(action)
|
64
|
+
self.for(action).allowed?
|
65
|
+
end
|
66
|
+
|
67
|
+
def readable?
|
68
|
+
permits? :read
|
69
|
+
end
|
70
|
+
|
71
|
+
def writable?
|
72
|
+
permits? :write
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Garage::Representer
|
2
|
+
attr_accessor :params, :representer_attrs, :partial, :selector
|
3
|
+
|
4
|
+
def partial?
|
5
|
+
@partial
|
6
|
+
end
|
7
|
+
|
8
|
+
def render_hash(options={})
|
9
|
+
obj = {}
|
10
|
+
representer_attrs.each do |definition|
|
11
|
+
if definition.options[:if]
|
12
|
+
next unless definition.options[:if].call(self, options[:responder])
|
13
|
+
end
|
14
|
+
|
15
|
+
if definition.respond_to?(:encode)
|
16
|
+
next unless handle_definition?(selector, definition, options)
|
17
|
+
obj[definition.name] = definition.encode(self, options[:responder], selector[definition.name])
|
18
|
+
else
|
19
|
+
next if selector.excludes?('_links')
|
20
|
+
obj['_links'] ||= {}
|
21
|
+
obj['_links'][definition.rel.to_s] = { 'href' => definition.pathify(self) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
obj
|
25
|
+
end
|
26
|
+
|
27
|
+
def handle_definition?(selector, definition, options)
|
28
|
+
if definition.requires_select?
|
29
|
+
# definition is not selected by default - it's opt-in
|
30
|
+
selector.includes?(definition.name) && definition.selectable?(self, options[:responder])
|
31
|
+
else
|
32
|
+
# definition is selected by default - it's opt-out
|
33
|
+
!selector.excludes?(definition.name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_url_options
|
38
|
+
@default_url_options ||= {}
|
39
|
+
end
|
40
|
+
|
41
|
+
def represent!
|
42
|
+
self.representer_attrs ||= []
|
43
|
+
self.representer_attrs += self.class.representer_attrs
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.representers
|
47
|
+
@representers ||= []
|
48
|
+
end
|
49
|
+
|
50
|
+
def resource_class
|
51
|
+
self.class
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_resource
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def link_path_for(rel)
|
59
|
+
represent! unless representer_attrs
|
60
|
+
representer_attrs.grep(Link).find { |link| link.rel === rel }.try(:pathify, self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.included(base)
|
64
|
+
self.representers << base
|
65
|
+
|
66
|
+
base.class_eval do
|
67
|
+
if Rails.application
|
68
|
+
include Rails.application.routes.url_helpers
|
69
|
+
end
|
70
|
+
extend ClassMethods
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
module ClassMethods
|
75
|
+
attr_writer :representer_attrs
|
76
|
+
|
77
|
+
def inherited(child)
|
78
|
+
super
|
79
|
+
child.representer_attrs = self.representer_attrs.clone
|
80
|
+
end
|
81
|
+
|
82
|
+
def representer_attrs
|
83
|
+
@representer_attrs ||= []
|
84
|
+
end
|
85
|
+
|
86
|
+
def property(name, options={})
|
87
|
+
representer_attrs << Definition.new(name, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def link(rel, options={}, &block)
|
91
|
+
representer_attrs << Link.new(rel, options, block)
|
92
|
+
end
|
93
|
+
|
94
|
+
def collection(name, options={})
|
95
|
+
representer_attrs << Collection.new(name, options)
|
96
|
+
end
|
97
|
+
|
98
|
+
def oauth_scope(scope)
|
99
|
+
->(resource, responder){
|
100
|
+
# FIXME: this only works with User resource for now
|
101
|
+
# partial representation will not render request scope-specific fields for better caching
|
102
|
+
!resource.partial? && responder.controller.requested_by?(resource) && responder.controller.has_scope?(scope)
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def accessible(*args)
|
107
|
+
->(resource, responder){
|
108
|
+
responder.controller.allow_access?(*args)
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
# represents the representer's schema in JSON format
|
113
|
+
def metadata
|
114
|
+
{:definitions => representer_attrs.grep(Definition).map {|definition| definition.name},
|
115
|
+
:links => representer_attrs.grep(Link).map {|link| link.options[:as] ? {link.rel => {'as' => link.options[:as]}} : link.rel}
|
116
|
+
}
|
117
|
+
end
|
118
|
+
|
119
|
+
def param(*keys)
|
120
|
+
keys.each {|key| params << key }
|
121
|
+
end
|
122
|
+
|
123
|
+
def params
|
124
|
+
@params ||= []
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
class NonEncodableValue < StandardError; end
|
129
|
+
|
130
|
+
class Definition
|
131
|
+
attr_reader :options
|
132
|
+
|
133
|
+
def initialize(name, options={})
|
134
|
+
@name = name
|
135
|
+
@options = options
|
136
|
+
end
|
137
|
+
|
138
|
+
def requires_select?
|
139
|
+
@options[:selectable]
|
140
|
+
end
|
141
|
+
|
142
|
+
def selectable?(*args)
|
143
|
+
if boolean?(@options[:selectable])
|
144
|
+
@options[:selectable]
|
145
|
+
else
|
146
|
+
@options[:selectable].call(*args)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def name
|
151
|
+
(@options[:as] || @name).to_s
|
152
|
+
end
|
153
|
+
|
154
|
+
def encode(object, responder, selector = nil)
|
155
|
+
value = object.send(@name)
|
156
|
+
encode_value(value, responder, selector)
|
157
|
+
end
|
158
|
+
|
159
|
+
def encode_value(value, responder, selector)
|
160
|
+
if !value.nil? && value.respond_to?(:represent!)
|
161
|
+
responder.encode_to_hash(value, partial: true, selector: selector)
|
162
|
+
elsif primitive?(value)
|
163
|
+
value
|
164
|
+
else
|
165
|
+
raise NonEncodableValue, "#{value.class} can not be encoded directly. Forgot to include Garage::Representer?"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def primitive?(value)
|
170
|
+
[
|
171
|
+
ActiveSupport::TimeWithZone,
|
172
|
+
Date,
|
173
|
+
Time,
|
174
|
+
Bignum,
|
175
|
+
Fixnum,
|
176
|
+
Float,
|
177
|
+
Hash,
|
178
|
+
Array,
|
179
|
+
String,
|
180
|
+
NilClass,
|
181
|
+
TrueClass,
|
182
|
+
FalseClass,
|
183
|
+
Symbol,
|
184
|
+
].any? {|k| value.is_a?(k) }
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def boolean?(value)
|
190
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class Collection < Definition
|
195
|
+
def encode(object, responder, selector = nil)
|
196
|
+
value = object.send(@name)
|
197
|
+
value.map do |item|
|
198
|
+
encode_value(item, responder, selector)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
class Link
|
204
|
+
attr_reader :rel, :options, :block
|
205
|
+
|
206
|
+
def initialize(rel, options, block)
|
207
|
+
@rel, @options, @block = rel, options, block
|
208
|
+
end
|
209
|
+
|
210
|
+
def pathify(representer)
|
211
|
+
representer.instance_exec(&@block)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|