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 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