rabl 0.11.0 → 0.11.1
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 +8 -8
- data/CHANGELOG.md +6 -0
- data/fixtures/rails3_2/test/functional/posts_controller_test.rb +1 -1
- data/fixtures/rails3_2/test/functional/users_controller_test.rb +2 -2
- data/fixtures/rails4/Gemfile +2 -0
- data/fixtures/rails4/test/functional/posts_controller_test.rb +1 -1
- data/fixtures/rails4/test/functional/users_controller_test.rb +2 -2
- data/lib/rabl.rb +1 -0
- data/lib/rabl/builder.rb +54 -10
- data/lib/rabl/cache_engine.rb +14 -0
- data/lib/rabl/configuration.rb +3 -1
- data/lib/rabl/engine.rb +48 -5
- data/lib/rabl/helpers.rb +20 -10
- data/lib/rabl/multi_builder.rb +89 -0
- data/lib/rabl/partials.rb +11 -6
- data/lib/rabl/renderer.rb +1 -1
- data/lib/rabl/template.rb +4 -3
- data/lib/rabl/version.rb +1 -1
- data/test/builder_test.rb +29 -11
- data/test/engine_test.rb +4 -1
- data/test/integration/rails3_2/posts_controller_test.rb +1 -1
- data/test/integration/rails3_2/users_controller_test.rb +2 -2
- data/test/integration/rails4/posts_controller_test.rb +1 -1
- data/test/integration/rails4/users_controller_test.rb +2 -2
- data/test/multi_builder_test.rb +70 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
NGY4MzA3YzkzMGUxMjA5ZjNhOGYxMzRmZTZmMmY0MmMzMzY3ZDg5Zg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
OGNjZDBkZWYwM2MxYmQ1YmM4NWY3MDkyNzE3MWZhNjZmM2Q5MGIyNQ==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
Y2I5OGZiYjk1NzM3YWVlMjA4NThmYTBiZmJhYzk5YWEwM2JlNzUwNGEyMjVk
|
10
|
+
ZDZjNGY1ODA5OWU2MmNmNGIyMWQ4YzM2ODJjZjk1Nzc2YzNjMDYwYjBmOGUw
|
11
|
+
NjEwNDQwNTg4MzhjZjM5MjBjMGU5ODRlODMxZGE3MWRjNjM0MWQ=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
ZDNkZGFjOGViN2ZkOWRjNjcwZTU1YzI3MGYxMDNmNGZiYjM2YjM4MmQxNDRk
|
14
|
+
ODNmOWZjNjdhMTIzM2E1YThiYzlmZjZjODdlMzFlODkwOWMyNmNiNmE3ZDM4
|
15
|
+
MjgwNjYzMmJmNzkzNGMzOTM5ZDE4Yjc0ODEzMWQ0YzYyY2IyZmY=
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 0.11.1 (October 19th)
|
4
|
+
|
5
|
+
* NEW #417 Major rewrite caching layer to be much faster using read_multi (@ahlatimer, @DouweM)
|
6
|
+
* FIX #589 Use set to speed up is_collection method check (@ccocchi)
|
7
|
+
* FIX #584 Fix issues with newer releases of oj gem (@DouweM)
|
8
|
+
|
3
9
|
## 0.11.0 (August 16th)
|
4
10
|
|
5
11
|
* Restore ruby 1.8 compatibility (@s01ipsist, @patrickdavey)
|
@@ -126,7 +126,7 @@ context "PostsController" do
|
|
126
126
|
asserts("contains post body") { topic['body'] }.equals { @post1.body }
|
127
127
|
|
128
128
|
# Attributes (custom name)
|
129
|
-
asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.
|
129
|
+
asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.iso8601 }
|
130
130
|
|
131
131
|
# Child
|
132
132
|
asserts("contains post user child username") { topic["user"]["username"] }.equals { @post1.user.username }
|
@@ -35,7 +35,7 @@ context "UsersController" do
|
|
35
35
|
# Attributes (custom name)
|
36
36
|
asserts("contains registered_at") do
|
37
37
|
json_output.map { |u| u["user"]["registered_at"] }
|
38
|
-
end.equals { @users.map(&:created_at).map
|
38
|
+
end.equals { @users.map(&:created_at).map { |t| t.iso8601 } }
|
39
39
|
|
40
40
|
# Node (renders based on attribute)
|
41
41
|
asserts("contains role") do
|
@@ -64,7 +64,7 @@ context "UsersController" do
|
|
64
64
|
asserts("contains email") { json_output["person"]["email"] }.equals { @user1.email }
|
65
65
|
asserts("contains location") { json_output["person"]["location"] }.equals { @user1.location }
|
66
66
|
# Attributes (custom name)
|
67
|
-
asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.
|
67
|
+
asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.iso8601 }
|
68
68
|
# Node (renders based on attribute)
|
69
69
|
asserts("contains role node") { json_output["person"]["role"] }.equals "normal"
|
70
70
|
|
data/fixtures/rails4/Gemfile
CHANGED
@@ -111,7 +111,7 @@ context "PostsController" do
|
|
111
111
|
asserts("contains post body") { topic['body'] }.equals { @post1.body }
|
112
112
|
|
113
113
|
# Attributes (custom name)
|
114
|
-
asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.
|
114
|
+
asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.iso8601(3) }
|
115
115
|
|
116
116
|
# Child
|
117
117
|
asserts("contains post user child username") { topic["user"]["username"] }.equals { @post1.user.username }
|
@@ -35,7 +35,7 @@ context "UsersController" do
|
|
35
35
|
# Attributes (custom name)
|
36
36
|
asserts("contains registered_at") do
|
37
37
|
json_output.map { |u| u["user"]["registered_at"] }
|
38
|
-
end.equals { @users.map(&:created_at).map
|
38
|
+
end.equals { @users.map(&:created_at).map { |t| t.iso8601(3) } }
|
39
39
|
|
40
40
|
# Node (renders based on attribute)
|
41
41
|
asserts("contains role") do
|
@@ -64,7 +64,7 @@ context "UsersController" do
|
|
64
64
|
asserts("contains email") { json_output["person"]["email"] }.equals { @user1.email }
|
65
65
|
asserts("contains location") { json_output["person"]["location"] }.equals { @user1.location }
|
66
66
|
# Attributes (custom name)
|
67
|
-
asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.
|
67
|
+
asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.iso8601(3) }
|
68
68
|
# Node (renders based on attribute)
|
69
69
|
asserts("contains role node") { json_output["person"]["role"] }.equals "normal"
|
70
70
|
|
data/lib/rabl.rb
CHANGED
data/lib/rabl/builder.rb
CHANGED
@@ -23,24 +23,70 @@ module Rabl
|
|
23
23
|
# build(@user, :format => "json", :attributes => { ... }, :root_name => "user")
|
24
24
|
def build(object, options={})
|
25
25
|
@_object = object
|
26
|
+
compile_engines
|
26
27
|
|
27
|
-
|
28
|
-
compile_hash(options)
|
28
|
+
unless options[:keep_engines]
|
29
|
+
cache_results { compile_hash(options) }
|
29
30
|
end
|
30
31
|
end
|
31
32
|
|
33
|
+
def engines
|
34
|
+
@_engines ||= []
|
35
|
+
end
|
36
|
+
|
37
|
+
def replace_engine(engine, value)
|
38
|
+
engines[engines.index(engine)] = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_hash(options={})
|
42
|
+
cache_results { compile_hash(options) }
|
43
|
+
end
|
44
|
+
|
32
45
|
protected
|
33
46
|
|
47
|
+
# Returns the builder with all engine-producing options evaluated.
|
48
|
+
# (extends, node, children, glues)
|
49
|
+
def compile_engines
|
50
|
+
@_engines = []
|
51
|
+
|
52
|
+
update_settings(:extends)
|
53
|
+
update_settings(:child)
|
54
|
+
update_settings(:glue)
|
55
|
+
end
|
56
|
+
|
34
57
|
# Returns a hash representation of the data object
|
35
58
|
# compile_hash(:root_name => false)
|
36
59
|
# compile_hash(:root_name => "user")
|
37
60
|
def compile_hash(options={})
|
38
61
|
@_result = {}
|
39
|
-
|
62
|
+
|
40
63
|
update_attributes
|
64
|
+
|
65
|
+
# Turn engines into hashes
|
66
|
+
@_engines.each do |engine|
|
67
|
+
# engine was stored in the form { name => #<Rabl::Engine> }
|
68
|
+
if engine.is_a?(Hash)
|
69
|
+
engine.each do |key, value|
|
70
|
+
if value.is_a?(Rabl::Engine)
|
71
|
+
value = value.render
|
72
|
+
|
73
|
+
if value
|
74
|
+
engine[key] = value
|
75
|
+
else
|
76
|
+
engine.delete(key)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
elsif engine.is_a?(Rabl::Engine)
|
81
|
+
engine = engine.render
|
82
|
+
end
|
83
|
+
|
84
|
+
@_result.merge!(engine) if engine.is_a?(Hash)
|
85
|
+
end
|
86
|
+
|
87
|
+
@_engines = []
|
88
|
+
|
41
89
|
update_settings(:node)
|
42
|
-
update_settings(:child)
|
43
|
-
update_settings(:glue)
|
44
90
|
|
45
91
|
wrap_result(options[:root_name])
|
46
92
|
|
@@ -148,7 +194,7 @@ module Rabl
|
|
148
194
|
engine_options = @options.slice(:child_root).merge(:root => include_root)
|
149
195
|
engine_options.merge!(:object_root_name => options[:object_root]) if is_name_value?(options[:object_root])
|
150
196
|
object = { object => name } if data.respond_to?(:each_pair) && object # child :users => :people
|
151
|
-
@
|
197
|
+
@_engines << { name.to_sym => self.object_to_engine(object, engine_options, &block) }
|
152
198
|
end
|
153
199
|
|
154
200
|
# Glues data from a child node to the json_output
|
@@ -156,8 +202,7 @@ module Rabl
|
|
156
202
|
def glue(data, options={}, &block)
|
157
203
|
return false unless data.present? && resolve_condition(options)
|
158
204
|
object = data_object(data)
|
159
|
-
|
160
|
-
@_result.merge!(glued_attributes) if glued_attributes
|
205
|
+
@_engines << self.object_to_engine(object, :root => false, &block)
|
161
206
|
end
|
162
207
|
|
163
208
|
# Extends an existing rabl template with additional attributes in the block
|
@@ -165,8 +210,7 @@ module Rabl
|
|
165
210
|
def extends(file, options={}, &block)
|
166
211
|
return unless resolve_condition(options)
|
167
212
|
options = @options.slice(:child_root).merge(:object => @_object).merge(options)
|
168
|
-
|
169
|
-
@_result.merge!(result) if result.is_a?(Hash)
|
213
|
+
@_engines << self.partial_as_engine(file, options, &block)
|
170
214
|
end
|
171
215
|
|
172
216
|
# Evaluate conditions given a symbol to evaluate
|
data/lib/rabl/cache_engine.rb
CHANGED
@@ -21,5 +21,19 @@ module Rabl
|
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
24
|
+
def write(key, value, options={})
|
25
|
+
if defined?(Rails)
|
26
|
+
Rails.cache.write(key, value, options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def read_multi(*keys)
|
31
|
+
options = keys.extract_options!
|
32
|
+
if defined?(Rails)
|
33
|
+
Rails.cache.read_multi(*keys, options)
|
34
|
+
else
|
35
|
+
keys.inject({}) { |hash, key| hash[key] = nil; hash }
|
36
|
+
end
|
37
|
+
end
|
24
38
|
end
|
25
39
|
end
|
data/lib/rabl/configuration.rb
CHANGED
@@ -19,7 +19,7 @@ end
|
|
19
19
|
# Set default options for Oj json parser (if exists)
|
20
20
|
begin
|
21
21
|
require 'oj'
|
22
|
-
Oj.default_options = { :mode => :compat, :time_format => :ruby }
|
22
|
+
Oj.default_options = { :mode => :compat, :time_format => :ruby, :use_to_json => true }
|
23
23
|
rescue LoadError
|
24
24
|
end
|
25
25
|
|
@@ -47,6 +47,7 @@ module Rabl
|
|
47
47
|
attr_accessor :cache_engine
|
48
48
|
attr_accessor :raise_on_missing_attribute
|
49
49
|
attr_accessor :perform_caching
|
50
|
+
attr_accessor :use_read_multi
|
50
51
|
attr_accessor :replace_nil_values_with_empty_strings
|
51
52
|
attr_accessor :replace_empty_string_values_with_nil_values
|
52
53
|
attr_accessor :exclude_nil_values
|
@@ -75,6 +76,7 @@ module Rabl
|
|
75
76
|
@view_paths = []
|
76
77
|
@cache_engine = Rabl::CacheEngine.new
|
77
78
|
@perform_caching = false
|
79
|
+
@use_read_multi = true
|
78
80
|
@replace_nil_values_with_empty_strings = false
|
79
81
|
@replace_empty_string_values_with_nil_values = false
|
80
82
|
@exclude_nil_values = false
|
data/lib/rabl/engine.rb
CHANGED
@@ -19,12 +19,28 @@ module Rabl
|
|
19
19
|
end
|
20
20
|
|
21
21
|
# Renders the representation based on source, object, scope and locals
|
22
|
-
# Rabl::Engine.new("...source...", { :format => "xml" }).
|
23
|
-
def
|
22
|
+
# Rabl::Engine.new("...source...", { :format => "xml" }).apply(scope, { :foo => "bar", :object => @user })
|
23
|
+
def apply(scope, locals, &block)
|
24
24
|
reset_options!(scope)
|
25
25
|
set_instance_variables!(scope, locals, &block)
|
26
26
|
instance_exec(root_object, &block) if block_given?
|
27
|
-
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Renders the representation based on a previous apply
|
32
|
+
# Rabl::Engine.new("...source...", { :format => "xml" }).apply(scope, { :foo => "bar", :object => @user }).render
|
33
|
+
def render(scope = nil, locals = nil, &block)
|
34
|
+
apply(scope, locals) if scope || locals
|
35
|
+
cache_results { self.send("to_#{@_options[:format]}", @_options) }
|
36
|
+
end
|
37
|
+
|
38
|
+
# Returns the cache key of the engine
|
39
|
+
def cache_key
|
40
|
+
_cache = @_cache if defined?(@_cache)
|
41
|
+
cache_key, _ = *_cache || nil
|
42
|
+
return nil if cache_key.nil?
|
43
|
+
Array(cache_key) + [@_options[:root_name], @_options[:format]]
|
28
44
|
end
|
29
45
|
|
30
46
|
# Returns a hash representation of the data object
|
@@ -38,7 +54,11 @@ module Rabl
|
|
38
54
|
if is_object?(data) || !data # object @user
|
39
55
|
result = builder.build(data, options)
|
40
56
|
elsif is_collection?(data) # collection @users
|
41
|
-
result =
|
57
|
+
result = if template_cache_configured? && Rabl.configuration.use_read_multi
|
58
|
+
read_multi(*data, options)
|
59
|
+
else
|
60
|
+
data.map { |object| builder.build(object, options) }
|
61
|
+
end
|
42
62
|
result = result.map(&:presence).compact if Rabl.configuration.exclude_empty_values_in_collections
|
43
63
|
end
|
44
64
|
Rabl.configuration.escape_all_output ? escape_output(result) : result
|
@@ -208,6 +228,11 @@ module Rabl
|
|
208
228
|
end
|
209
229
|
alias_method :helpers, :helper
|
210
230
|
|
231
|
+
# Disables reading (but not writing) from the cache when rendering.
|
232
|
+
def cache_read_on_render=(c)
|
233
|
+
@_cache_read_on_render = c
|
234
|
+
end
|
235
|
+
|
211
236
|
protected
|
212
237
|
|
213
238
|
# Returns a guess at the default object for this template
|
@@ -263,6 +288,10 @@ module Rabl
|
|
263
288
|
vars.each { |name| instance_variable_set(name, object.instance_variable_get(name)) }
|
264
289
|
end
|
265
290
|
|
291
|
+
def cache_read_on_render
|
292
|
+
@_cache_read_on_render = @_cache_read_on_render.nil? ? true : @_cache_read_on_render
|
293
|
+
end
|
294
|
+
|
266
295
|
private
|
267
296
|
|
268
297
|
# Resets the options parsed from a rabl template.
|
@@ -273,6 +302,7 @@ module Rabl
|
|
273
302
|
@_options[:glue] = []
|
274
303
|
@_options[:extends] = []
|
275
304
|
@_options[:root_name] = nil
|
305
|
+
@_options[:read_multi] = false
|
276
306
|
@_options[:scope] = scope
|
277
307
|
end
|
278
308
|
|
@@ -287,12 +317,25 @@ module Rabl
|
|
287
317
|
else # fallback for Rails 3, and Non-Rails app
|
288
318
|
cache_key_simple(cache_key)
|
289
319
|
end
|
290
|
-
|
320
|
+
|
321
|
+
if self.cache_read_on_render
|
322
|
+
fetch_result_from_cache(result_cache_key, cache_options, &block)
|
323
|
+
else
|
324
|
+
write_result_to_cache(result_cache_key, cache_options, &block)
|
325
|
+
end
|
291
326
|
else # skip caching
|
292
327
|
yield
|
293
328
|
end
|
294
329
|
end
|
295
330
|
|
331
|
+
# Uses read_multi to render a collection of cache keys,
|
332
|
+
# falling back to a normal render in the event of a miss.
|
333
|
+
def read_multi(*data)
|
334
|
+
options = data.extract_options!
|
335
|
+
builder = Rabl::MultiBuilder.new(data, options)
|
336
|
+
builder.to_a
|
337
|
+
end
|
338
|
+
|
296
339
|
def digestor_available?
|
297
340
|
defined?(Rails) && Rails.version =~ /^[4]/
|
298
341
|
end
|
data/lib/rabl/helpers.rb
CHANGED
@@ -1,21 +1,24 @@
|
|
1
1
|
require 'active_support/inflector' # for the sake of pluralizing
|
2
|
+
require 'set'
|
2
3
|
|
3
4
|
module Rabl
|
4
5
|
module Helpers
|
5
6
|
# Set of class names known to be objects, not collections
|
6
|
-
KNOWN_OBJECT_CLASSES = ['Struct', 'Hashie::Mash']
|
7
|
+
KNOWN_OBJECT_CLASSES = Set.new(['Struct', 'Hashie::Mash'])
|
7
8
|
|
8
9
|
# data_object(data) => <AR Object>
|
9
10
|
# data_object(@user => :person) => @user
|
10
11
|
# data_object(:user => :person) => @_object.send(:user)
|
11
12
|
def data_object(data)
|
12
13
|
data = (data.is_a?(Hash) && data.keys.size == 1) ? data.keys.first : data
|
13
|
-
data.is_a?(Symbol) && defined?(@_object) && @_object ? @_object.__send__(data) : data
|
14
|
+
data.is_a?(Symbol) && defined?(@_object) && @_object && @_object.respond_to?(data) ? @_object.__send__(data) : data
|
14
15
|
end
|
15
16
|
|
16
17
|
# data_object_attribute(data) => @_object.send(data)
|
17
18
|
def data_object_attribute(data)
|
18
|
-
@_object.__send__(data)
|
19
|
+
attribute = @_object.__send__(data)
|
20
|
+
attribute = attribute.as_json if is_collection?(attribute, false) && attribute.respond_to?(:as_json)
|
21
|
+
attribute
|
19
22
|
end
|
20
23
|
|
21
24
|
# data_name(data) => "user"
|
@@ -29,7 +32,7 @@ module Rabl
|
|
29
32
|
data = data_object(data_token)
|
30
33
|
if is_collection?(data) # data is a collection
|
31
34
|
object_name = data.table_name if data.respond_to?(:table_name)
|
32
|
-
if
|
35
|
+
if object_name.nil? && data.respond_to?(:first)
|
33
36
|
first = data.first
|
34
37
|
object_name = data_name(first).to_s.pluralize if first.present?
|
35
38
|
end
|
@@ -65,17 +68,17 @@ module Rabl
|
|
65
68
|
# is_object?(@user) => true
|
66
69
|
# is_object?([]) => false
|
67
70
|
# is_object?({}) => false
|
68
|
-
def is_object?(obj)
|
69
|
-
obj &&
|
70
|
-
(KNOWN_OBJECT_CLASSES & obj.class.ancestors.map(&:name)).any?)
|
71
|
+
def is_object?(obj, follow_symbols = true)
|
72
|
+
obj && !is_collection?(obj, follow_symbols)
|
71
73
|
end
|
72
74
|
|
73
75
|
# Returns true if the obj is a collection of items
|
74
76
|
# is_collection?(@user) => false
|
75
77
|
# is_collection?([]) => true
|
76
|
-
def is_collection?(obj)
|
77
|
-
|
78
|
-
|
78
|
+
def is_collection?(obj, follow_symbols = true)
|
79
|
+
data_obj = follow_symbols ? data_object(obj) : obj
|
80
|
+
data_obj && data_obj.respond_to?(:map) && data_obj.respond_to?(:each) &&
|
81
|
+
obj.class.ancestors.none? { |a| KNOWN_OBJECT_CLASSES.include? a.name }
|
79
82
|
end
|
80
83
|
|
81
84
|
# Returns the scope wrapping this engine, used for retrieving data, invoking methods, etc
|
@@ -110,6 +113,13 @@ module Rabl
|
|
110
113
|
Rabl.configuration.cache_engine.fetch(expanded_cache_key, cache_options, &block)
|
111
114
|
end
|
112
115
|
|
116
|
+
def write_result_to_cache(cache_key, cache_options=nil, &block)
|
117
|
+
expanded_cache_key = ActiveSupport::Cache.expand_cache_key(cache_key, :rabl)
|
118
|
+
result = yield
|
119
|
+
Rabl.configuration.cache_engine.write(expanded_cache_key, result, cache_options)
|
120
|
+
result
|
121
|
+
end
|
122
|
+
|
113
123
|
# Returns true if the cache has been enabled for the application
|
114
124
|
def template_cache_configured?
|
115
125
|
if defined?(Rails)
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Rabl
|
2
|
+
class MultiBuilder
|
3
|
+
# Constructs a new MultiBuilder given the data and options.
|
4
|
+
# The options will be re-used for all Rabl::Builders.
|
5
|
+
# Rabl::MultiBuilder.new([#<User ...>, #<User ...>, ...], { :format => 'json', :child_root => true })
|
6
|
+
def initialize(data, options={})
|
7
|
+
@data = data
|
8
|
+
@options = options
|
9
|
+
@builders = []
|
10
|
+
@engine_to_builder = {}
|
11
|
+
@cache_key_to_engine = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the result of all of the builders as an array
|
15
|
+
def to_a
|
16
|
+
generate_builders
|
17
|
+
read_cache_results
|
18
|
+
replace_engines_with_cache_results
|
19
|
+
|
20
|
+
@builders.map { |builder| builder.to_hash(@options) }
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# Creates a builder for each of the data objects
|
26
|
+
# and maps the cache keys for each of the engines
|
27
|
+
# the builders generated
|
28
|
+
def generate_builders
|
29
|
+
@data.each do |object|
|
30
|
+
builder = Rabl::Builder.new(@options)
|
31
|
+
builder.build(object, @options.merge(:keep_engines => true))
|
32
|
+
|
33
|
+
@builders << builder
|
34
|
+
|
35
|
+
builder.engines.each do |engine|
|
36
|
+
@engine_to_builder[engine] = builder
|
37
|
+
|
38
|
+
map_cache_key_to_engine(engine)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Maps a cache key to an engine
|
44
|
+
def map_cache_key_to_engine(engine)
|
45
|
+
if cache_key = cache_key_for(engine)
|
46
|
+
result_cache_key = ActiveSupport::Cache.expand_cache_key(cache_key, :rabl)
|
47
|
+
@cache_key_to_engine[result_cache_key] = engine
|
48
|
+
disable_cache_read_on_render(engine)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def disable_cache_read_on_render(engine)
|
53
|
+
if engine.is_a?(Hash)
|
54
|
+
disable_cache_read_on_render(engine.values.first)
|
55
|
+
else
|
56
|
+
engine.cache_read_on_render = false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def cache_key_for(engine)
|
61
|
+
if engine.is_a?(Hash)
|
62
|
+
cache_key_for(engine.values.first)
|
63
|
+
else
|
64
|
+
engine.cache_key
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the items that were found in the cache
|
69
|
+
def read_cache_results
|
70
|
+
@cache_results ||= begin
|
71
|
+
mutable_keys = @cache_key_to_engine.keys.map { |k| k.dup }
|
72
|
+
if mutable_keys.empty?
|
73
|
+
{}
|
74
|
+
else
|
75
|
+
Rabl.configuration.cache_engine.read_multi(*mutable_keys)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Maps the results from the cache back to the builders
|
81
|
+
def replace_engines_with_cache_results
|
82
|
+
@cache_results.each do |key, value|
|
83
|
+
engine = @cache_key_to_engine[key]
|
84
|
+
builder = @engine_to_builder[engine]
|
85
|
+
builder.replace_engine(engine, value) if value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
data/lib/rabl/partials.rb
CHANGED
@@ -2,29 +2,34 @@ module Rabl
|
|
2
2
|
module Partials
|
3
3
|
include Rabl::Helpers
|
4
4
|
|
5
|
-
#
|
5
|
+
# Returns a hash representing the partial
|
6
6
|
# partial("users/show", :object => @user)
|
7
7
|
# options must have :object
|
8
8
|
# options can have :view_path, :child_root, :root
|
9
9
|
def partial(file, options={}, &block)
|
10
|
+
engine = self.partial_as_engine(file, options, &block)
|
11
|
+
engine.is_a?(Rabl::Engine) ? engine.render : engine
|
12
|
+
end
|
13
|
+
|
14
|
+
def partial_as_engine(file, options={}, &block)
|
10
15
|
raise ArgumentError, "Must provide an :object option to render a partial" unless options.has_key?(:object)
|
11
16
|
object, view_path = options.delete(:object), options[:view_path] || @_view_path
|
12
17
|
source, location = self.fetch_source(file, :view_path => view_path)
|
13
18
|
engine_options = options.merge(:source => source, :source_location => location, :template => file)
|
14
|
-
self.
|
19
|
+
self.object_to_engine(object, engine_options, &block)
|
15
20
|
end
|
16
21
|
|
17
|
-
# Returns
|
22
|
+
# Returns an Engine based representation of any data object given ejs template block
|
18
23
|
# object_to_hash(@user) { attribute :full_name } => { ... }
|
19
24
|
# object_to_hash(@user, :source => "...") { attribute :full_name } => { ... }
|
20
25
|
# object_to_hash([@user], :source => "...") { attribute :full_name } => { ... }
|
21
26
|
# options must have :source (rabl file contents)
|
22
27
|
# options can have :source_location (source filename)
|
23
|
-
def
|
24
|
-
return object
|
28
|
+
def object_to_engine(object, options={}, &block)
|
29
|
+
return object unless !object.nil?
|
25
30
|
return [] if is_collection?(object) && object.blank? # empty collection
|
26
31
|
engine_options = options.reverse_merge(:format => "hash", :view_path => @_view_path, :root => (options[:root] || false))
|
27
|
-
Rabl::Engine.new(options[:source], engine_options).
|
32
|
+
Rabl::Engine.new(options[:source], engine_options).apply(@_scope, :object => object, :locals => options[:locals], &block)
|
28
33
|
end
|
29
34
|
|
30
35
|
# Returns source for a given relative file
|
data/lib/rabl/renderer.rb
CHANGED
@@ -47,7 +47,7 @@ module Rabl
|
|
47
47
|
context_scope = context_scope ? context_scope : options.delete(:scope) || self
|
48
48
|
set_instance_variable(object) if context_scope == self
|
49
49
|
locals = options.fetch(:locals, {}).reverse_merge(:object => object)
|
50
|
-
engine.
|
50
|
+
engine.apply(context_scope, locals).render
|
51
51
|
end
|
52
52
|
|
53
53
|
protected
|
data/lib/rabl/template.rb
CHANGED
@@ -12,7 +12,7 @@ if defined?(Tilt)
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def evaluate(scope, locals, &block)
|
15
|
-
@engine.
|
15
|
+
@engine.apply(scope, locals, &block).render
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -52,11 +52,12 @@ if defined?(ActionView) && defined?(Rails) && Rails.version.to_s =~ /^[34]/
|
|
52
52
|
source = template.source
|
53
53
|
|
54
54
|
%{ ::Rabl::Engine.new(#{source.inspect}).
|
55
|
-
|
55
|
+
apply(self, assigns.merge(local_assigns)).
|
56
|
+
render }
|
56
57
|
end # call
|
57
58
|
end # rabl class
|
58
59
|
end # handlers
|
59
60
|
end
|
60
61
|
|
61
62
|
ActionView::Template.register_template_handler :rabl, ActionView::Template::Handlers::Rabl
|
62
|
-
end
|
63
|
+
end
|
data/lib/rabl/version.rb
CHANGED
data/test/builder_test.rb
CHANGED
@@ -178,29 +178,37 @@ context "Rabl::Builder" do
|
|
178
178
|
|
179
179
|
asserts "that it generates with an object" do
|
180
180
|
b = builder :child => [{ :data => @user, :options => {}, :block => lambda { |u| attribute :name } }]
|
181
|
+
e = Rabl::Engine.new('')
|
181
182
|
mock(b).data_name(@user) { :user }
|
182
|
-
mock(
|
183
|
+
mock(e).render.returns('xyz')
|
184
|
+
mock(b).object_to_engine(@user, { :root => false }).returns(e).subject
|
183
185
|
b.build(@user)
|
184
186
|
end.equivalent_to({ :user => 'xyz'})
|
185
187
|
|
186
188
|
asserts "that it generates with an collection and child_root" do
|
187
189
|
b = builder :child => [{ :data => @users, :options => {}, :block => lambda { |u| attribute :name } }], :child_root => true
|
190
|
+
e = Rabl::Engine.new('')
|
188
191
|
mock(b).data_name(@users) { :users }
|
189
|
-
mock(
|
192
|
+
mock(e).render.returns('xyz')
|
193
|
+
mock(b).object_to_engine(@users, { :root => true, :child_root => true }).returns(e).subject
|
190
194
|
b.build(@user)
|
191
195
|
end.equivalent_to({ :users => 'xyz'})
|
192
196
|
|
193
197
|
asserts "that it generates with an collection and no child root" do
|
194
198
|
b = builder :child => [{ :data => @users, :options => {}, :block => lambda { |u| attribute :name } }], :child_root => false
|
199
|
+
e = Rabl::Engine.new('')
|
195
200
|
mock(b).data_name(@users) { :users }
|
196
|
-
mock(
|
201
|
+
mock(e).render.returns('xyz')
|
202
|
+
mock(b).object_to_engine(@users, { :root => false, :child_root => false }).returns(e).subject
|
197
203
|
b.build(@user)
|
198
204
|
end.equivalent_to({ :users => 'xyz'})
|
199
205
|
|
200
206
|
asserts "that it generates with an collection and a specified object_root_name and root" do
|
201
207
|
ops = { :object_root => "person", :root => :people }
|
202
208
|
b = builder :child => [{ :data => @users, :options => ops, :block => lambda { |u| attribute :name } }], :child_root => true
|
203
|
-
|
209
|
+
e = Rabl::Engine.new('')
|
210
|
+
mock(e).render.returns('xyz')
|
211
|
+
mock(b).object_to_engine(@users, { :root => "person", :object_root_name => "person", :child_root => true }).returns(e).subject
|
204
212
|
b.build(@user)
|
205
213
|
end.equivalent_to({ :people => 'xyz'})
|
206
214
|
|
@@ -225,7 +233,9 @@ context "Rabl::Builder" do
|
|
225
233
|
|
226
234
|
asserts "that it generates the glue attributes" do
|
227
235
|
b = builder :glue => [{ :data => @user, :options => {}, :block => lambda { |u| attribute :name }}]
|
228
|
-
|
236
|
+
e = Rabl::Engine.new('')
|
237
|
+
mock(e).render.returns({:user => 'xyz'})
|
238
|
+
mock(b).object_to_engine(@user, { :root => false }).returns(e).subject
|
229
239
|
b.build(@user)
|
230
240
|
end.equivalent_to({ :user => 'xyz' })
|
231
241
|
|
@@ -236,27 +246,35 @@ context "Rabl::Builder" do
|
|
236
246
|
|
237
247
|
asserts "that it does not generate new attributes if no glue attributes are present" do
|
238
248
|
b = builder :glue => [{ :data => @user, :options => {}, :block => lambda { |u| attribute :name }}]
|
239
|
-
|
249
|
+
e = Rabl::Engine.new('')
|
250
|
+
mock(e).render.returns({})
|
251
|
+
mock(b).object_to_engine(@user,{ :root => false }).returns(e).subject
|
240
252
|
b.build(@user)
|
241
253
|
end.equals({})
|
242
254
|
end
|
243
255
|
|
244
|
-
context "#
|
245
|
-
asserts "that it does not
|
256
|
+
context "#extends" do
|
257
|
+
asserts "that it does not generate if no data is present" do
|
246
258
|
b = builder :extends => [{ :file => 'users/show', :options => {}, :block => lambda { |u| attribute :name }}]
|
247
|
-
|
259
|
+
e = Rabl::Engine.new('users/show')
|
260
|
+
mock(b).partial_as_engine('users/show',{ :object => @user}).returns(e)
|
261
|
+
mock(e).render.returns({}).subject
|
248
262
|
b.build(@user)
|
249
263
|
end.equals({})
|
250
264
|
|
251
265
|
asserts "that it generates if data is present" do
|
252
266
|
b = builder :extends => [{ :file => 'users/show', :options => {}, :block => lambda { |u| attribute :name }}]
|
253
|
-
|
267
|
+
e = Rabl::Engine.new('users/show')
|
268
|
+
mock(b).partial_as_engine('users/show',{ :object => @user}).returns(e)
|
269
|
+
mock(e).render.returns({:user => 'xyz'}).subject
|
254
270
|
b.build(@user)
|
255
271
|
end.equivalent_to({:user => 'xyz'})
|
256
272
|
|
257
273
|
asserts "that it generates if local data is present but object is false" do
|
258
274
|
b = builder :extends => [{ :file => 'users/show', :options => { :object => @user }, :block => lambda { |u| attribute :name }}]
|
259
|
-
|
275
|
+
e = Rabl::Engine.new('users/show')
|
276
|
+
mock(b).partial_as_engine('users/show',{ :object => @user}).returns(e)
|
277
|
+
mock(e).render.returns({:user => 'xyz'}).subject
|
260
278
|
b.build(false)
|
261
279
|
end.equivalent_to({:user => 'xyz'})
|
262
280
|
end
|
data/test/engine_test.rb
CHANGED
@@ -442,9 +442,12 @@ context "Rabl::Engine" do
|
|
442
442
|
scope = Object.new
|
443
443
|
@user = User.new(:name => 'leo', :city => 'LA', :age => 12)
|
444
444
|
scope.instance_variable_set :@user, @user
|
445
|
+
e = Rabl::Engine.new(nil)
|
446
|
+
mock(e).render.returns({ :name => 'leo', :city => 'LA', :age => 12 })
|
447
|
+
|
445
448
|
any_instance_of(Rabl::Engine) do |b|
|
446
449
|
mock(b).fetch_source("foo/bar", :view_path => nil).once
|
447
|
-
mock(b).
|
450
|
+
mock(b).object_to_engine(@user, :locals => { :foo => "bar" }, :source => nil, :source_location => nil, :template => 'foo/bar').returns(e)
|
448
451
|
end
|
449
452
|
JSON.parse(template.render(scope))
|
450
453
|
end.equals JSON.parse("{ \"foo\" : {\"name\":\"leo\",\"city\":\"LA\",\"age\":12} }")
|
@@ -126,7 +126,7 @@ context "PostsController" do
|
|
126
126
|
asserts("contains post body") { topic['body'] }.equals { @post1.body }
|
127
127
|
|
128
128
|
# Attributes (custom name)
|
129
|
-
asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.
|
129
|
+
asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.iso8601 }
|
130
130
|
|
131
131
|
# Child
|
132
132
|
asserts("contains post user child username") { topic["user"]["username"] }.equals { @post1.user.username }
|
@@ -35,7 +35,7 @@ context "UsersController" do
|
|
35
35
|
# Attributes (custom name)
|
36
36
|
asserts("contains registered_at") do
|
37
37
|
json_output.map { |u| u["user"]["registered_at"] }
|
38
|
-
end.equals { @users.map(&:created_at).map
|
38
|
+
end.equals { @users.map(&:created_at).map { |t| t.iso8601 } }
|
39
39
|
|
40
40
|
# Node (renders based on attribute)
|
41
41
|
asserts("contains role") do
|
@@ -64,7 +64,7 @@ context "UsersController" do
|
|
64
64
|
asserts("contains email") { json_output["person"]["email"] }.equals { @user1.email }
|
65
65
|
asserts("contains location") { json_output["person"]["location"] }.equals { @user1.location }
|
66
66
|
# Attributes (custom name)
|
67
|
-
asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.
|
67
|
+
asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.iso8601 }
|
68
68
|
# Node (renders based on attribute)
|
69
69
|
asserts("contains role node") { json_output["person"]["role"] }.equals "normal"
|
70
70
|
|
@@ -111,7 +111,7 @@ context "PostsController" do
|
|
111
111
|
asserts("contains post body") { topic['body'] }.equals { @post1.body }
|
112
112
|
|
113
113
|
# Attributes (custom name)
|
114
|
-
asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.
|
114
|
+
asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.iso8601(3) }
|
115
115
|
|
116
116
|
# Child
|
117
117
|
asserts("contains post user child username") { topic["user"]["username"] }.equals { @post1.user.username }
|
@@ -35,7 +35,7 @@ context "UsersController" do
|
|
35
35
|
# Attributes (custom name)
|
36
36
|
asserts("contains registered_at") do
|
37
37
|
json_output.map { |u| u["user"]["registered_at"] }
|
38
|
-
end.equals { @users.map(&:created_at).map
|
38
|
+
end.equals { @users.map(&:created_at).map { |t| t.iso8601(3) } }
|
39
39
|
|
40
40
|
# Node (renders based on attribute)
|
41
41
|
asserts("contains role") do
|
@@ -64,7 +64,7 @@ context "UsersController" do
|
|
64
64
|
asserts("contains email") { json_output["person"]["email"] }.equals { @user1.email }
|
65
65
|
asserts("contains location") { json_output["person"]["location"] }.equals { @user1.location }
|
66
66
|
# Attributes (custom name)
|
67
|
-
asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.
|
67
|
+
asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.iso8601(3) }
|
68
68
|
# Node (renders based on attribute)
|
69
69
|
asserts("contains role node") { json_output["person"]["role"] }.equals "normal"
|
70
70
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'json'
|
2
|
+
require File.expand_path('../teststrap', __FILE__)
|
3
|
+
require 'rabl/template'
|
4
|
+
require File.expand_path('../models/ormless', __FILE__)
|
5
|
+
|
6
|
+
context "Rabl::MultiBuilder" do
|
7
|
+
helper(:multi_builder) { |objects, options| Rabl::MultiBuilder.new(objects, options) }
|
8
|
+
helper(:builder) { Rabl::Builder.new({}) }
|
9
|
+
helper(:engine) { |object| Rabl::Engine.new("").apply(nil, :object => object) }
|
10
|
+
|
11
|
+
context "#initialize" do
|
12
|
+
setup do
|
13
|
+
Rabl::MultiBuilder.new([], {})
|
14
|
+
end
|
15
|
+
|
16
|
+
asserts_topic.assigns :options
|
17
|
+
asserts_topic.assigns :data
|
18
|
+
asserts_topic.assigns :engine_to_builder
|
19
|
+
asserts_topic.assigns :cache_key_to_engine
|
20
|
+
end
|
21
|
+
|
22
|
+
context "#to_a" do
|
23
|
+
setup do
|
24
|
+
Rabl::MultiBuilder.new([], {})
|
25
|
+
end
|
26
|
+
|
27
|
+
asserts "returns an array" do
|
28
|
+
topic.to_a
|
29
|
+
end.is_a?(Array)
|
30
|
+
end
|
31
|
+
|
32
|
+
context "#map_cache_key_to_engine" do
|
33
|
+
asserts "maps the cache keys to the engines" do
|
34
|
+
mb = multi_builder [], {}
|
35
|
+
b = builder
|
36
|
+
e = engine User.new
|
37
|
+
mock(e).cache_key.returns(['cache key'])
|
38
|
+
mb.send(:map_cache_key_to_engine, e)
|
39
|
+
mb.instance_variable_get('@cache_key_to_engine').values.include?(e)
|
40
|
+
end.equals(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
context "#read_cache_results" do
|
44
|
+
setup do
|
45
|
+
mb = multi_builder [], {}
|
46
|
+
mb.instance_variable_set('@cache_key_to_engine', { 'cache_key' => engine(User.new) })
|
47
|
+
mb
|
48
|
+
end
|
49
|
+
|
50
|
+
asserts "uses read_multi to find all of the cached values with keys" do
|
51
|
+
mock(Rabl.configuration.cache_engine).read_multi('cache_key').returns({})
|
52
|
+
topic.send(:read_cache_results)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context "replace_engines_with_cache_results" do
|
57
|
+
asserts "replaces the builders' engines with the cache results" do
|
58
|
+
mb = multi_builder [], {}
|
59
|
+
e = engine User.new
|
60
|
+
b = builder
|
61
|
+
mb.instance_variable_set('@cache_key_to_engine', { 'cache_key' => e })
|
62
|
+
mb.instance_variable_set('@engine_to_builder', { e => b })
|
63
|
+
b.instance_variable_set('@_engines', [e])
|
64
|
+
mb.instance_variable_set('@cache_results', { 'cache_key' => '{}' })
|
65
|
+
|
66
|
+
mock(b).replace_engine(e, '{}')
|
67
|
+
mb.send(:replace_engines_with_cache_results)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rabl
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Esquenazi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-10-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -415,6 +415,7 @@ files:
|
|
415
415
|
- lib/rabl/digestor.rb
|
416
416
|
- lib/rabl/engine.rb
|
417
417
|
- lib/rabl/helpers.rb
|
418
|
+
- lib/rabl/multi_builder.rb
|
418
419
|
- lib/rabl/partials.rb
|
419
420
|
- lib/rabl/railtie.rb
|
420
421
|
- lib/rabl/renderer.rb
|
@@ -438,6 +439,7 @@ files:
|
|
438
439
|
- test/models/ormless.rb
|
439
440
|
- test/models/user.rb
|
440
441
|
- test/msgpack_engine_test.rb
|
442
|
+
- test/multi_builder_test.rb
|
441
443
|
- test/partials_test.rb
|
442
444
|
- test/plist_engine_test.rb
|
443
445
|
- test/renderer_test.rb
|
@@ -485,6 +487,7 @@ test_files:
|
|
485
487
|
- test/models/ormless.rb
|
486
488
|
- test/models/user.rb
|
487
489
|
- test/msgpack_engine_test.rb
|
490
|
+
- test/multi_builder_test.rb
|
488
491
|
- test/partials_test.rb
|
489
492
|
- test/plist_engine_test.rb
|
490
493
|
- test/renderer_test.rb
|