rabl 0.11.1 → 0.11.2

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