rabl 0.11.1 → 0.11.2

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
- NGY4MzA3YzkzMGUxMjA5ZjNhOGYxMzRmZTZmMmY0MmMzMzY3ZDg5Zg==
4
+ MmEyODQ2MGU5MWRhYzc1YjU2YWI2YWMxMTQ2ZGJhYTBhYWJiYTgzNg==
5
5
  data.tar.gz: !binary |-
6
- OGNjZDBkZWYwM2MxYmQ1YmM4NWY3MDkyNzE3MWZhNjZmM2Q5MGIyNQ==
6
+ OWI4ZWQwNDZiOGIyZWMzOTQ5ZGFhNDAwMjQ3ZjRlNTg5YTViNjM2OA==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- Y2I5OGZiYjk1NzM3YWVlMjA4NThmYTBiZmJhYzk5YWEwM2JlNzUwNGEyMjVk
10
- ZDZjNGY1ODA5OWU2MmNmNGIyMWQ4YzM2ODJjZjk1Nzc2YzNjMDYwYjBmOGUw
11
- NjEwNDQwNTg4MzhjZjM5MjBjMGU5ODRlODMxZGE3MWRjNjM0MWQ=
9
+ NDk5OTc0ZjU5ZDM4OTBiYWJhOWNmMzg0MjdlYjk2NTdiNjc0MzdkODhmMTNi
10
+ YTQyNmE1NDczYjU5N2VlMzU1NjQwYWY4ZWU4NGJhNjljNzhiNzY0MzQ3MzI1
11
+ MjVjMGViNzE2NTNhOWIzNjE1Y2RlOWNmYzFiYTM3NzA5OGEyZGQ=
12
12
  data.tar.gz: !binary |-
13
- ZDNkZGFjOGViN2ZkOWRjNjcwZTU1YzI3MGYxMDNmNGZiYjM2YjM4MmQxNDRk
14
- ODNmOWZjNjdhMTIzM2E1YThiYzlmZjZjODdlMzFlODkwOWMyNmNiNmE3ZDM4
15
- MjgwNjYzMmJmNzkzNGMzOTM5ZDE4Yjc0ODEzMWQ0YzYyY2IyZmY=
13
+ MWJmZGNkMWIyZGZiYmE0ZGE1NDhlZTRkNDJiNzQwN2YwNGQwNmU2NjQ5OTAx
14
+ NzE4ZmQ5NDcxZDE0YzIyZDFkYjViOGEzNGVlY2VjYmFhYjEyNmM3OGFlMDY0
15
+ M2RmMmZmMjI1ZDY4NDEzNzRkN2EyMjFkMzliYjE5Yzc3MTBlNTk=
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.11.2 (October 26th)
4
+
5
+ * FIX Don't put nil into builder engines when glueing nil object (@DouweM)
6
+
3
7
  ## 0.11.1 (October 19th)
4
8
 
5
9
  * NEW #417 Major rewrite caching layer to be much faster using read_multi (@ahlatimer, @DouweM)
data/README.md CHANGED
@@ -664,11 +664,18 @@ Check out the [Issues](https://github.com/nesquena/rabl/issues) tab for a full l
664
664
 
665
665
  ## Authors and Contributors ##
666
666
 
667
+ Active Maintainers:
668
+
669
+ * [Douwe Maan](https://github.com/DouweM)
670
+ * [Nathan Esquenazi](https://github.com/nesquena)
671
+
667
672
  Thanks to [Miso](http://gomiso.com) for allowing me to create this for our applications and release this project!
668
673
 
669
674
  * [Nathan Esquenazi](https://github.com/nesquena) - Creator of the project
670
675
  * [Arthur Chiu](https://github.com/achiu) - Core Maintainer, Riot Testing Guru
671
676
  * [Tim Lee](https://github.com/timothy1ee) - RABL was a great name chosen by the Miso CTO.
677
+ * [Douwe Maan](https://github.com/DouweM) - Multi support for caching, fixed test suite
678
+ * [Andrew Latimer](https://github.com/ahlatimer) - Multi support for caching
672
679
  * [David Sommers](https://github.com/databyte) - Template resolution, caching support, and much more
673
680
  * [Rick Thomas](https://github.com/rickthomasjr) - Added options for extends and Sinatra testing
674
681
  * [Benjamin Yu](https://github.com/byu) - Added msgpack format support
@@ -709,4 +716,3 @@ Thanks again for all of these great projects.
709
716
  ## Copyright ##
710
717
 
711
718
  Copyright © 2011-2013 Nathan Esquenazi. See [MIT-LICENSE](https://github.com/nesquena/rabl/blob/master/MIT-LICENSE) for details.
712
-
data/lib/rabl.rb CHANGED
@@ -7,6 +7,7 @@ require 'active_support/core_ext/hash/slice'
7
7
 
8
8
  require 'rabl/version'
9
9
  require 'rabl/helpers'
10
+ require 'rabl/sources'
10
11
  require 'rabl/partials'
11
12
  require 'rabl/engine'
12
13
  require 'rabl/builder'
@@ -58,13 +59,11 @@ module Rabl
58
59
  def source_cache(file, view_path, &block)
59
60
  return yield unless Rabl.configuration.cache_sources
60
61
 
61
- @_source_cache ||= {}
62
62
  cache_key = [file, view_path].compact.join(":")
63
- if cached_result = @_source_cache[cache_key]
64
- cached_result
65
- else # store result of block
66
- @_source_cache[cache_key] = yield
67
- end
63
+
64
+ @_source_cache ||= {}
65
+
66
+ @_source_cache[cache_key] ||= yield
68
67
  end
69
68
 
70
69
  # Resets the RABL source cache
@@ -77,7 +76,6 @@ module Rabl
77
76
  def render(object, source, options = {})
78
77
  Rabl::Renderer.new(source, object, options).render
79
78
  end
80
-
81
79
  end
82
80
  end
83
81
 
data/lib/rabl/builder.rb CHANGED
@@ -1,279 +1,273 @@
1
1
  module Rabl
2
2
  class Builder
3
- include Rabl::Partials
3
+ include Helpers
4
+ include Partials
4
5
 
5
6
  SETTING_TYPES = {
6
- :extends => :file,
7
- :node => :name,
8
- :child => :data,
9
- :glue => :data
10
- } unless const_defined? :SETTING_TYPES
7
+ :attributes => :name,
8
+ :node => :name,
9
+ :child => :data,
10
+ :glue => :data,
11
+ :extends => :file
12
+ } unless const_defined?(:SETTING_TYPES)
11
13
 
12
14
  # Constructs a new rabl hash based on given object and options
13
15
  # options = { :format => "json", :root => true, :child_root => true,
14
16
  # :attributes, :node, :child, :glue, :extends }
15
17
  #
16
- def initialize(options={}, &block)
17
- @options = options
18
- @_scope = options[:scope]
19
- @_view_path = options[:view_path]
20
- end
21
-
22
- # Given an object and options, returns the hash representation
23
- # build(@user, :format => "json", :attributes => { ... }, :root_name => "user")
24
- def build(object, options={})
18
+ def initialize(object, settings = {}, options = {}, &block)
25
19
  @_object = object
26
- compile_engines
27
20
 
28
- unless options[:keep_engines]
29
- cache_results { compile_hash(options) }
30
- end
21
+ @settings = settings
22
+ @options = options
23
+ @_context_scope = options[:scope]
24
+ @_view_path = options[:view_path]
31
25
  end
32
26
 
33
27
  def engines
34
- @_engines ||= []
28
+ return @_engines if defined?(@_engines)
29
+
30
+ @_engines = []
31
+
32
+ # Append onto @_engines
33
+ compile_settings(:extends)
34
+ compile_settings(:child)
35
+ compile_settings(:glue)
36
+
37
+ @_engines
35
38
  end
36
39
 
37
40
  def replace_engine(engine, value)
38
41
  engines[engines.index(engine)] = value
39
42
  end
40
43
 
41
- def to_hash(options={})
42
- cache_results { compile_hash(options) }
43
- end
44
+ def to_hash(object = nil, settings = {}, options = {})
45
+ @_object = object if object
46
+ @options.merge!(options) if options
47
+ @settings.merge!(settings) if settings
48
+
49
+ cache_results do
50
+ @_result = {}
44
51
 
45
- protected
52
+ # Merges directly into @_result
53
+ compile_settings(:attributes)
46
54
 
47
- # Returns the builder with all engine-producing options evaluated.
48
- # (extends, node, children, glues)
49
- def compile_engines
50
- @_engines = []
55
+ merge_engines_into_result
51
56
 
52
- update_settings(:extends)
53
- update_settings(:child)
54
- update_settings(:glue)
55
- end
57
+ # Merges directly into @_result
58
+ compile_settings(:node)
56
59
 
57
- # Returns a hash representation of the data object
58
- # compile_hash(:root_name => false)
59
- # compile_hash(:root_name => "user")
60
- def compile_hash(options={})
61
- @_result = {}
62
-
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
60
+ replace_nil_values if Rabl.configuration.replace_nil_values_with_empty_strings
61
+ replace_empty_string_values if Rabl.configuration.replace_empty_string_values_with_nil_values
62
+ remove_nil_values if Rabl.configuration.exclude_nil_values
83
63
 
84
- @_result.merge!(engine) if engine.is_a?(Hash)
64
+ result = @_result
65
+ result = { @options[:root_name] => result } if @options[:root_name].present?
66
+ result
85
67
  end
68
+ end
86
69
 
87
- @_engines = []
88
-
89
- update_settings(:node)
70
+ protected
71
+ def replace_nil_values
72
+ @_result = deep_replace_nil_values(@_result)
73
+ end
90
74
 
91
- wrap_result(options[:root_name])
75
+ def deep_replace_nil_values(hash)
76
+ hash.inject({}) do |new_hash, (k, v)|
77
+ new_hash[k] = if v.is_a?(Hash)
78
+ deep_replace_nil_values(v)
79
+ else
80
+ v.nil? ? '' : v
81
+ end
82
+ new_hash
83
+ end
84
+ end
92
85
 
93
- replace_nil_values if Rabl.configuration.replace_nil_values_with_empty_strings
94
- replace_empty_string_values if Rabl.configuration.replace_empty_string_values_with_nil_values
95
- remove_nil_values if Rabl.configuration.exclude_nil_values
86
+ def replace_empty_string_values
87
+ @_result = deep_replace_empty_string_values(@_result)
88
+ end
96
89
 
97
- # Return Results
98
- @_root_name ? { @_root_name => @_result } : @_result
99
- end
90
+ def deep_replace_empty_string_values(hash)
91
+ hash.inject({}) do |new_hash, (k, v)|
92
+ new_hash[k] = if v.is_a?(Hash)
93
+ deep_replace_empty_string_values(v)
94
+ else
95
+ (!v.nil? && v != "") ? v : nil
96
+ end
100
97
 
101
- def replace_nil_values
102
- @_result = deep_replace_nil_values(@_result)
103
- end
98
+ new_hash
99
+ end
100
+ end
104
101
 
105
- def deep_replace_nil_values(hash)
106
- hash.inject({}) do |hsh, (k, v)|
107
- hsh[k] = if v.is_a?(Hash)
108
- deep_replace_nil_values(v)
109
- else
110
- v.nil? ? '' : v
102
+ def remove_nil_values
103
+ @_result = @_result.inject({}) do |new_hash, (k, v)|
104
+ new_hash[k] = v unless v.nil?
105
+ new_hash
111
106
  end
112
- hsh
113
107
  end
114
- end
115
108
 
116
- def replace_empty_string_values
117
- @_result = deep_replace_empty_string_values(@_result)
118
- end
109
+ def compile_settings(type)
110
+ return unless @settings.has_key?(type)
119
111
 
120
- def deep_replace_empty_string_values(hash)
121
- hash.inject({}) do |hsh, (k, v)|
122
- hsh[k] = if v.is_a?(Hash)
123
- deep_replace_empty_string_values(v)
124
- else
125
- (!v.nil? && v != "") ? v : nil
112
+ settings_type = SETTING_TYPES[type]
113
+ @settings[type].each do |setting|
114
+ send(type, setting[settings_type], setting[:options] || {}, &setting[:block])
126
115
  end
127
- hsh
128
116
  end
129
- end
130
117
 
131
- def remove_nil_values
132
- @_result = @_result.inject({}) do |hash, (k, v)|
133
- hash[k] = v unless v.nil?
134
- hash
135
- end
136
- end
118
+ def merge_engines_into_result
119
+ engines.each do |engine|
120
+ # engine was stored in the form { name => #<Engine> }
121
+ if engine.is_a?(Hash)
122
+ engine.each do |key, value|
123
+ if value.is_a?(Engine)
124
+ value = value.render
125
+
126
+ if value
127
+ engine[key] = value
128
+ else
129
+ engine.delete(key)
130
+ end
131
+ end
132
+ end
133
+ elsif engine.is_a?(Engine)
134
+ engine = engine.render
135
+ end
137
136
 
138
- def wrap_result(root_name)
139
- if root_name.present?
140
- @_root_name = root_name
141
- else # no root
142
- @_root_name = nil
137
+ @_result.merge!(engine) if engine.is_a?(Hash)
138
+ end
143
139
  end
144
- end
145
140
 
146
- def update_settings(type)
147
- settings_type = SETTING_TYPES[type]
148
- @options[type].each do |settings|
149
- send(type, settings[settings_type], settings[:options], &settings[:block])
150
- end if @options.has_key?(type)
151
- end
152
-
153
- def update_attributes
154
- @options[:attributes].each_pair do |attribute, settings|
155
- attribute(attribute, settings)
156
- end if @options.has_key?(:attributes)
157
- end
141
+ # Indicates an attribute or method should be included in the json output
142
+ # attribute :foo, :as => "bar"
143
+ # attribute :foo, :as => "bar", :if => lambda { |m| m.foo }
144
+ def attribute(name, options = {})
145
+ return unless @_object && attribute_present?(name) && resolve_condition(options)
158
146
 
159
- # Indicates an attribute or method should be included in the json output
160
- # attribute :foo, :as => "bar"
161
- # attribute :foo, :as => "bar", :if => lambda { |m| m.foo }
162
- def attribute(name, options={})
163
- if @_object && attribute_present?(name) && resolve_condition(options)
164
147
  attribute = data_object_attribute(name)
165
148
  name = (options[:as] || name).to_sym
166
149
  @_result[name] = attribute
167
150
  end
168
- end
169
- alias_method :attributes, :attribute
170
-
171
- # Creates an arbitrary node that is included in the json output
172
- # node(:foo) { "bar" }
173
- # node(:foo, :if => lambda { |m| m.foo.present? }) { "bar" }
174
- def node(name, options={}, &block)
175
- return unless resolve_condition(options)
176
- result = block.call(@_object)
177
- if name.present?
178
- @_result[name.to_sym] = result
179
- elsif result.respond_to?(:each_pair) # merge hash into root hash
180
- @_result.merge!(result)
151
+ alias_method :attributes, :attribute
152
+
153
+ # Creates an arbitrary node that is included in the json output
154
+ # node(:foo) { "bar" }
155
+ # node(:foo, :if => lambda { |m| m.foo.present? }) { "bar" }
156
+ def node(name, options = {}, &block)
157
+ return unless resolve_condition(options)
158
+
159
+ result = block.call(@_object)
160
+ if name.present?
161
+ @_result[name.to_sym] = result
162
+ elsif result.is_a?(Hash) # merge hash into root hash
163
+ @_result.merge!(result)
164
+ end
181
165
  end
182
- end
183
- alias_method :code, :node
184
-
185
- # Creates a child node that is included in json output
186
- # child(@user) { attribute :full_name }
187
- # child(@user => :person) { ... }
188
- # child(@users => :people) { ... }
189
- def child(data, options={}, &block)
190
- return false unless data.present? && resolve_condition(options)
191
- name = is_name_value?(options[:root]) ? options[:root] : data_name(data)
192
- object = data_object(data)
193
- include_root = is_collection?(object) && options.fetch(:object_root, @options[:child_root]) # child @users
194
- engine_options = @options.slice(:child_root).merge(:root => include_root)
195
- engine_options.merge!(:object_root_name => options[:object_root]) if is_name_value?(options[:object_root])
196
- object = { object => name } if data.respond_to?(:each_pair) && object # child :users => :people
197
- @_engines << { name.to_sym => self.object_to_engine(object, engine_options, &block) }
198
- end
166
+ alias_method :code, :node
199
167
 
200
- # Glues data from a child node to the json_output
201
- # glue(@user) { attribute :full_name => :user_full_name }
202
- def glue(data, options={}, &block)
203
- return false unless data.present? && resolve_condition(options)
204
- object = data_object(data)
205
- @_engines << self.object_to_engine(object, :root => false, &block)
206
- end
168
+ # Creates a child node that is included in json output
169
+ # child(@user) { attribute :full_name }
170
+ # child(@user => :person) { ... }
171
+ # child(@users => :people) { ... }
172
+ def child(data, options = {}, &block)
173
+ return unless data.present? && resolve_condition(options)
207
174
 
208
- # Extends an existing rabl template with additional attributes in the block
209
- # extends("users/show") { attribute :full_name }
210
- def extends(file, options={}, &block)
211
- return unless resolve_condition(options)
212
- options = @options.slice(:child_root).merge(:object => @_object).merge(options)
213
- @_engines << self.partial_as_engine(file, options, &block)
214
- end
175
+ name = is_name_value?(options[:root]) ? options[:root] : data_name(data)
176
+ object = data_object(data)
177
+
178
+ include_root = is_collection?(object) && options.fetch(:object_root, @options[:child_root]) # child @users
179
+ engine_options = @options.slice(:child_root).merge(:root => include_root)
180
+ engine_options.merge!(:object_root_name => options[:object_root]) if is_name_value?(options[:object_root])
181
+
182
+ object = { object => name } if data.is_a?(Hash) && object # child :users => :people
215
183
 
216
- # Evaluate conditions given a symbol to evaluate
217
- def call_condition_proc(condition, object, &blk)
218
- blk = lambda { |v| v } unless block_given?
219
- if condition.respond_to?(:call)
220
- # condition is a block to pass to the block
221
- blk.call(condition.call(object))
222
- elsif condition.is_a?(Symbol) && object.respond_to?(condition)
223
- # condition is a property of the object
224
- blk.call(object.send(condition))
225
- else
226
- false
184
+ engines << { name.to_sym => object_to_engine(object, engine_options, &block) }
227
185
  end
228
- end
229
186
 
230
- # resolve_condition(:if => true) => true
231
- # resolve_condition(:if => lambda { |m| false }) => false
232
- # resolve_condition(:unless => lambda { |m| true }) => true
233
- def resolve_condition(options)
234
- return true if options[:if].nil? && options[:unless].nil?
235
- result = nil
236
- if options.has_key?(:if)
237
- result = options[:if] == true || call_condition_proc(options[:if], @_object)
187
+ # Glues data from a child node to the json_output
188
+ # glue(@user) { attribute :full_name => :user_full_name }
189
+ def glue(data, options = {}, &block)
190
+ return unless data.present? && resolve_condition(options)
191
+
192
+ object = data_object(data)
193
+ engine = object_to_engine(object, :root => false, &block)
194
+ engines << engine if engine
238
195
  end
239
- if options.has_key?(:unless)
240
- inverse_proc = lambda { |r| !r }
241
- result = options[:unless] == false || call_condition_proc(options[:unless], @_object, &inverse_proc)
196
+
197
+ # Extends an existing rabl template with additional attributes in the block
198
+ # extends("users/show") { attribute :full_name }
199
+ def extends(file, options = {}, &block)
200
+ return unless resolve_condition(options)
201
+
202
+ options = @options.slice(:child_root).merge(:object => @_object).merge(options)
203
+ engines << partial_as_engine(file, options, &block)
242
204
  end
243
- result
244
- end
245
205
 
246
- private
206
+ # Evaluate conditions given a symbol to evaluate
207
+ def call_condition_proc(condition, object, &block)
208
+ block = lambda { |v| v } unless block_given?
247
209
 
248
- # Checks if an attribute is present. If not, check if the configuration specifies that this is an error
249
- # attribute_present?(created_at) => true
250
- def attribute_present?(name)
251
- if @_object.respond_to?(name)
252
- return true
253
- elsif Rabl.configuration.raise_on_missing_attribute
254
- raise "Failed to render missing attribute #{name}"
255
- else
256
- return false
210
+ if condition.respond_to?(:call)
211
+ # condition is a block to pass to the block
212
+ block.call(condition.call(object))
213
+ elsif condition.is_a?(Symbol) && object.respond_to?(condition)
214
+ # condition is a property of the object
215
+ block.call(object.send(condition))
216
+ else
217
+ false
218
+ end
257
219
  end
258
- end
259
220
 
260
- # Returns a guess at the format in this scope
261
- # request_format => "xml"
262
- def request_format
263
- format = @options[:format]
264
- format && format != "hash" ? format : 'json'
265
- end
221
+ # resolve_condition(:if => true) => true
222
+ # resolve_condition(:if => lambda { |m| false }) => false
223
+ # resolve_condition(:unless => lambda { |m| true }) => true
224
+ def resolve_condition(options)
225
+ return true if options[:if].nil? && options[:unless].nil?
226
+
227
+ result = nil
228
+ if options.has_key?(:if)
229
+ result = options[:if] == true || call_condition_proc(options[:if], @_object)
230
+ end
231
+
232
+ if options.has_key?(:unless)
233
+ inverse_proc = lambda { |r| !r }
234
+ result = options[:unless] == false || call_condition_proc(options[:unless], @_object, &inverse_proc)
235
+ end
266
236
 
267
- # Caches the results of the block based on object cache_key
268
- # cache_results { compile_hash(options) }
269
- def cache_results(&block)
270
- if template_cache_configured? && Rabl.configuration.cache_all_output && @_object.respond_to?(:cache_key)
271
- result_cache_key = [@_object, @options[:root_name], @options[:format]]
272
- fetch_result_from_cache(result_cache_key, &block)
273
- else # skip cache
274
- yield
237
+ result
275
238
  end
276
- end
277
239
 
240
+ private
241
+ # Checks if an attribute is present. If not, check if the configuration specifies that this is an error
242
+ # attribute_present?(created_at) => true
243
+ def attribute_present?(name)
244
+ if @_object.respond_to?(name)
245
+ true
246
+ elsif Rabl.configuration.raise_on_missing_attribute
247
+ raise "Failed to render missing attribute #{name}"
248
+ else
249
+ false
250
+ end
251
+ end
252
+
253
+ # Returns a guess at the format in this context_scope
254
+ # request_format => "xml"
255
+ def request_format
256
+ format = @options[:format]
257
+ format = "json" if !format || format == "hash"
258
+ format
259
+ end
260
+
261
+ # Caches the results of the block based on object cache_key
262
+ # cache_results { compile_hash(options) }
263
+ def cache_results(&block)
264
+ if template_cache_configured? && Rabl.configuration.cache_all_output && @_object.respond_to?(:cache_key)
265
+ cache_key = [@_object, @options[:root_name], @options[:format]]
266
+
267
+ fetch_result_from_cache(cache_key, &block)
268
+ else # skip cache
269
+ yield
270
+ end
271
+ end
278
272
  end
279
273
  end