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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- ZTQ1YmY1YjA4YWM5YmIwNmI1Y2M3NDYzYjg0OWRmOGE3NTRiMjJiMg==
4
+ NGY4MzA3YzkzMGUxMjA5ZjNhOGYxMzRmZTZmMmY0MmMzMzY3ZDg5Zg==
5
5
  data.tar.gz: !binary |-
6
- N2QxNDFjMjUwOTk2NTRhYmVmMWNiMjE3ZWQ5MmU4OWEyZTI5YzNlMQ==
6
+ OGNjZDBkZWYwM2MxYmQ1YmM4NWY3MDkyNzE3MWZhNjZmM2Q5MGIyNQ==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- ZmE2N2I2MGZhNWM1ZWM3YTFkMDNmNzMyODhlNGM2NTFiMTRhYWE5MjJlOTlj
10
- YjljMzc5YWEzMmJjZjdjNDRkZTAyYjE2NDA1MTMxODg3NzE3NWU2ODZlNTNk
11
- MjczY2Y1YjgxNGI0YTBhNzE5NTY3MWQzOGIzZGFlNGQ5OTgxNjc=
9
+ Y2I5OGZiYjk1NzM3YWVlMjA4NThmYTBiZmJhYzk5YWEwM2JlNzUwNGEyMjVk
10
+ ZDZjNGY1ODA5OWU2MmNmNGIyMWQ4YzM2ODJjZjk1Nzc2YzNjMDYwYjBmOGUw
11
+ NjEwNDQwNTg4MzhjZjM5MjBjMGU5ODRlODMxZGE3MWRjNjM0MWQ=
12
12
  data.tar.gz: !binary |-
13
- MzBjMDE4YjY4ZjBiNmEzODY5NDZlMzI2OTI0ZjY4Mjk0M2YyMjVkYzY3YWZl
14
- MWQ1MWMxNjM3NDlmMTc1ZmVmOGRiODkxOTc3YTU4YTM1NWJlNjc5YWVkMjBi
15
- ZjNlNDZmNGZmODFiZWI5ZmJlNDAzYmQ3NjgyM2NiZGI2NmEwYzg=
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.utc.to_s }
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(&:utc).map(&:to_s) }
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.utc.to_s }
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
 
@@ -8,6 +8,8 @@ gem 'sqlite3'
8
8
  gem 'rabl', :path => File.expand_path(File.dirname(__FILE__) + "/../../")
9
9
  gem 'riot', :group => "test"
10
10
 
11
+ gem "oj"
12
+
11
13
  # Use SCSS for stylesheets
12
14
  gem 'sass-rails', '~> 4.0.0'
13
15
 
@@ -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.utc.to_s }
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(&:utc).map(&:to_s) }
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.utc.to_s }
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
@@ -10,6 +10,7 @@ require 'rabl/helpers'
10
10
  require 'rabl/partials'
11
11
  require 'rabl/engine'
12
12
  require 'rabl/builder'
13
+ require 'rabl/multi_builder'
13
14
  require 'rabl/configuration'
14
15
  require 'rabl/renderer'
15
16
  require 'rabl/cache_engine'
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
- cache_results do
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
- update_settings(:extends)
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
- @_result[name.to_sym] = self.object_to_hash(object, engine_options, &block)
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
- glued_attributes = self.object_to_hash(object, :root => false, &block)
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
- result = self.partial(file, options, &block)
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
@@ -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
@@ -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" }).render(scope, { :foo => "bar", :object => @user })
23
- def render(scope, locals, &block)
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
- cache_results { self.send("to_" + @_options[:format].to_s, @_options) }
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 = data.map { |object| builder.build(object, options) }
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
- fetch_result_from_cache(result_cache_key, cache_options, &block)
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 !object_name && data.respond_to?(:first)
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 && (!data_object(obj).respond_to?(:map) || !data_object(obj).respond_to?(:each) ||
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
- obj && data_object(obj).respond_to?(:map) && data_object(obj).respond_to?(:each) &&
78
- (KNOWN_OBJECT_CLASSES & obj.class.ancestors.map(&:name)).empty?
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
- # Renders a partial hash based on another rabl template
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.object_to_hash(object, engine_options, &block)
19
+ self.object_to_engine(object, engine_options, &block)
15
20
  end
16
21
 
17
- # Returns a hash based representation of any data object given ejs template block
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 object_to_hash(object, options={}, &block)
24
- return object if object.nil?
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).render(@_scope, :object => object, :locals => options[:locals], &block)
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.render(context_scope, locals)
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.render(scope, locals, &block)
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
- render(self, assigns.merge(local_assigns)) }
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
@@ -1,3 +1,3 @@
1
1
  module Rabl
2
- VERSION = "0.11.0"
2
+ VERSION = "0.11.1"
3
3
  end
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(b).object_to_hash(@user, { :root => false }).returns('xyz').subject
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(b).object_to_hash(@users, { :root => true, :child_root => true }).returns('xyz').subject
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(b).object_to_hash(@users, { :root => false, :child_root => false }).returns('xyz').subject
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
- mock(b).object_to_hash(@users, { :root => "person", :object_root_name => "person", :child_root => true }).returns('xyz').subject
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
- mock(b).object_to_hash(@user, { :root => false }).returns({:user => 'xyz'}).subject
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
- mock(b).object_to_hash(@user,{ :root => false }).returns({}).subject
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 "#extend" do
245
- asserts "that it does not genereate if no data is present" do
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
- mock(b).partial('users/show',{ :object => @user }).returns({}).subject
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
- mock(b).partial('users/show', { :object => @user }).returns({:user => 'xyz'}).subject
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
- mock(b).partial('users/show', { :object => @user }).returns({:user => 'xyz'}).subject
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).object_to_hash(@user, :locals => { :foo => "bar" }, :source => nil, :source_location => nil, :template => 'foo/bar').returns({ :name => 'leo', :city => 'LA', :age => 12 })
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.utc.to_s }
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(&:utc).map(&:to_s) }
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.utc.to_s }
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.utc.to_s }
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(&:utc).map(&:to_s) }
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.utc.to_s }
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.0
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-08-17 00:00:00.000000000 Z
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