rabl 0.11.0 → 0.11.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|