rabl-rails 0.3.0 → 0.3.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.
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.3.2 (unreleased)
4
+
5
+ ## 0.3.1
6
+ * Add `merge` keywork
7
+ * Format can be passed as a string or a symbol
8
+ * Avoid to unexpectedly change cached templates (johnbintz)
9
+ * Add full template stack support to `glue` (fnordfish)
10
+ * Allow format to be a symbol (lloydmeta)
11
+
3
12
  ## 0.3.0
4
13
  * Travis integration
5
14
  * Add test for keywords used as variable names
data/Gemfile CHANGED
@@ -6,6 +6,9 @@ gem 'plist'
6
6
 
7
7
  platforms :ruby do
8
8
  gem 'oj'
9
+ end
10
+
11
+ platforms :mri do
9
12
  gem 'libxml-ruby'
10
13
  end
11
14
 
data/README.md CHANGED
@@ -165,7 +165,8 @@ attributes title: :foo, to_s: :bar
165
165
 
166
166
  ### Child nodes
167
167
 
168
- You can include nested information from data associated with the parent model. You can also alias these associations.
168
+ You can include informations from data associated with the parent model or arbitrary data. These informations can be grouped under a node or directly merged into current node.
169
+
169
170
  For example if you have a `Post` model that belongs to a `User`
170
171
 
171
172
  ```ruby
@@ -183,9 +184,19 @@ child(:@users) do
183
184
  end
184
185
  ```
185
186
 
187
+ If you want to merge directly into current node, you can use the `glue` keywork
188
+
189
+ ```ruby
190
+ attribute :title
191
+ glue(:user) do
192
+ attributes :name => :author_name
193
+ end
194
+ # => { "post" : { "title" : "Foo", "author_name" : "John D." } }
195
+ ```
196
+
186
197
  ### Custom nodes
187
198
 
188
- You can create custom node in your response, based on the result of the given block
199
+ You can create custom node in your response, based on the result of the given block.
189
200
 
190
201
  ```ruby
191
202
  object :@user
@@ -193,7 +204,7 @@ node(:full_name) { |u| u.first_name + " " + u.last_name }
193
204
  # => { "user" : { "full_name" : "John Doe" } }
194
205
  ```
195
206
 
196
- You can add the node only if a condition is true
207
+ You can add condition on your custom nodes (if the condition is evaluated to false, the node will not be included).
197
208
 
198
209
  ```ruby
199
210
  node(:email, if: -> { |u| u.valid_email? }) do |u|
@@ -207,6 +218,14 @@ Nodes are evaluated at the rendering time, so you can use any instance variables
207
218
  node(:url) { |post| post_url(post) }
208
219
  ```
209
220
 
221
+ If you want to include directly the result into the current node, use the `merge` keyword (result returned from the block should be a hash)
222
+
223
+ ```ruby
224
+ object :@user
225
+ merge { |u| { name: u.first_name + " " + u.last_name } }
226
+ # => { "user" : { "name" : "John Doe" } }
227
+ ```
228
+
210
229
  Custom nodes are really usefull to create flexible representations of your resources.
211
230
 
212
231
  ### Extends & Partials
@@ -5,7 +5,7 @@ module RablRails
5
5
  #
6
6
  class Compiler
7
7
  def initialize
8
- @i = 0
8
+ @i = -1
9
9
  end
10
10
 
11
11
  #
@@ -80,9 +80,7 @@ module RablRails
80
80
  #
81
81
  def glue(data)
82
82
  return unless block_given?
83
- name = :"_glue#{@i}"
84
- @i += 1
85
- @template[name] = sub_compile(data) { yield }
83
+ @template[sequence('glue')] = sub_compile(data) { yield }
86
84
  end
87
85
 
88
86
  #
@@ -93,7 +91,8 @@ module RablRails
93
91
  # node(:name) { |user| user.first_name + user.last_name }
94
92
  # node(:role, if: ->(u) { !u.admin? }) { |u| u.role }
95
93
  #
96
- def node(name, options = {}, &block)
94
+ def node(name = nil, options = {}, &block)
95
+ name ||= sequence('merge')
97
96
  condition = options[:if]
98
97
 
99
98
  if condition
@@ -108,6 +107,17 @@ module RablRails
108
107
  end
109
108
  alias_method :code, :node
110
109
 
110
+ #
111
+ # Merge arbitrary data into json output. Given block should
112
+ # return a hash.
113
+ # Example:
114
+ # merge { |item| partial("specific/#{item.to_s}", object: item) }
115
+ #
116
+ def merge(&block)
117
+ return unless block_given?
118
+ node(sequence('merge'), &block)
119
+ end
120
+
111
121
  #
112
122
  # Extends an existing rabl template
113
123
  # Example:
@@ -127,13 +137,19 @@ module RablRails
127
137
  #
128
138
  def condition(proc)
129
139
  return unless block_given?
130
- name = :"_if#{@i}"
131
- @i += 1
132
- @template[name] = Condition.new(proc, sub_compile(nil) { yield })
140
+ @template[sequence('if')] = Condition.new(proc, sub_compile(nil) { yield })
133
141
  end
134
142
 
135
143
  protected
136
144
 
145
+ #
146
+ # Return unique symbol starting with given name
147
+ #
148
+ def sequence(name)
149
+ @i += 1
150
+ :"_#{name}#{@i}"
151
+ end
152
+
137
153
  #
138
154
  # Extract data root_name and root name
139
155
  # Example:
@@ -14,21 +14,23 @@ module RablRails
14
14
 
15
15
  compiled_template = compile_template_from_source(source, path)
16
16
 
17
- format = context.params[:format] || 'json'
18
- Renderers.const_get(format.upcase!).new(context, locals).render(compiled_template)
17
+ format = context.params[:format] ? context.params[:format].to_s : 'json'
18
+ format.upcase!
19
+ Renderers.const_get(format).new(context, locals).render(compiled_template)
19
20
  end
20
21
 
21
22
  def compile_template_from_source(source, path = nil)
22
23
  if path && RablRails.cache_templates?
23
24
  @cached_templates[path] ||= Compiler.new.compile_source(source)
25
+ @cached_templates[path].dup
24
26
  else
25
27
  Compiler.new.compile_source(source)
26
28
  end
27
29
  end
28
30
 
29
31
  def compile_template_from_path(path)
30
- template = @cached_templates[path]
31
- return template if template
32
+ return @cached_templates[path].dup if @cached_templates.has_key?(path)
33
+
32
34
  t = @lookup_context.find_template(path, [], false)
33
35
  compile_template_from_source(t.source, path)
34
36
  end
@@ -15,7 +15,7 @@ module RablRails
15
15
 
16
16
  def initialize(view_path, format)
17
17
  @view_path = view_path || RablRails::Renderer.view_path
18
- @format = format
18
+ @format = format.to_s.downcase
19
19
  end
20
20
 
21
21
  #
@@ -54,7 +54,15 @@ module RablRails
54
54
  when Symbol
55
55
  data.send(value) # attributes
56
56
  when Proc
57
- instance_exec data, &value # node
57
+ result = instance_exec data, &value
58
+
59
+ if key.to_s.start_with?('_') # merge
60
+ raise PartialError, '`merge` block should return a hash' unless result.is_a?(Hash)
61
+ output.merge!(result)
62
+ next output
63
+ else # node
64
+ result
65
+ end
58
66
  when Array # node with condition
59
67
  next output if !instance_exec data, &(value.first)
60
68
  instance_exec data, &(value.last)
@@ -70,9 +78,7 @@ module RablRails
70
78
  end
71
79
 
72
80
  if key.to_s.start_with?('_') # glue
73
- current_value.each_pair { |k, v|
74
- output[k] = object.send(v)
75
- }
81
+ output.merge!(render_resource(object, current_value))
76
82
  next output
77
83
  else # child
78
84
  object.respond_to?(:each) ? render_collection(object, current_value) : render_resource(object, current_value)
@@ -7,5 +7,10 @@ module RablRails
7
7
  def initialize
8
8
  @source = {}
9
9
  end
10
+
11
+ def initialize_dup(other)
12
+ super
13
+ self.source = other.source.dup
14
+ end
10
15
  end
11
16
  end
@@ -1,3 +1,3 @@
1
1
  module RablRails
2
- VERSION = '0.3.0'
2
+ VERSION = '0.3.1'
3
3
  end
@@ -18,10 +18,11 @@ class CacheTemplatesTest < ActiveSupport::TestCase
18
18
 
19
19
  test "cached templates should not be modifiable in place" do
20
20
  ActionController::Base.stub(:perform_caching).and_return(true)
21
- @library.compile_template_from_source('', 'some/path')
22
- t = @library.compile_template_from_source("attribute :id", 'some/path')
21
+ t = @library.compile_template_from_source('', 'some/path')
23
22
 
24
- assert_equal({}, t.source)
23
+ t.merge!(:_data => :foo)
24
+
25
+ assert_equal({}, @library.compile_template_from_path('some/path').source)
25
26
  end
26
27
 
27
28
  test "don't cache templates cache_templates is enabled but perform_caching is not active" do
@@ -112,7 +112,7 @@ class CompilerTest < ActiveSupport::TestCase
112
112
  RablRails::Library.instance.stub(:compile_template_from_path).with('users/base').and_return(mock_template)
113
113
 
114
114
  t = @compiler.compile_source(%{child(:user, :partial => 'users/base') })
115
- assert_equal( {:user => { :_data => :user, :id => :id } }, t.source)
115
+ assert_equal({:user => { :_data => :user, :id => :id } }, t.source)
116
116
  end
117
117
 
118
118
  test "glue is compiled as a child but with anonymous name" do
@@ -132,6 +132,18 @@ class CompilerTest < ActiveSupport::TestCase
132
132
  }, t.source)
133
133
  end
134
134
 
135
+ test "glue accepts all dsl in its body" do
136
+ t = @compiler.compile_source(%{
137
+ glue :@user do node(:foo) { |u| u.name } end
138
+ })
139
+
140
+ assert_not_nil(t.source[:_glue0])
141
+ s = t.source[:_glue0]
142
+
143
+ assert_equal(:@user, s[:_data])
144
+ assert_instance_of(Proc, s[:foo])
145
+ end
146
+
135
147
  test "extends use other template source as itself" do
136
148
  template = mock('template', :source => { :id => :id })
137
149
  RablRails::Library.reset_instance
@@ -153,6 +165,16 @@ class CompilerTest < ActiveSupport::TestCase
153
165
  assert_equal 2, t.source[:foo].size
154
166
  end
155
167
 
168
+ test "node can take no arguments and behave like a merge" do
169
+ t = @compiler.compile_source(%{ node do |m| m.foo end })
170
+ assert_instance_of Proc, t.source[:_merge0]
171
+ end
172
+
173
+ test "merge compile like a node but with a reserved keyword as name" do
174
+ t = @compiler.compile_source(%{ merge do |m| m.foo end })
175
+ assert_instance_of Proc, t.source[:_merge0]
176
+ end
177
+
156
178
  test "conditionnal block compile nicely" do
157
179
  t = @compiler.compile_source(%{ condition(->(u) {}) do attributes :secret end })
158
180
  assert_instance_of RablRails::Condition, t.source[:_if0]
data/test/render_test.rb CHANGED
@@ -60,4 +60,17 @@ class RenderTest < ActiveSupport::TestCase
60
60
  assert_equal %q({"user":{"extended_name":"Marty"}}), RablRails.render(@user, 'extend', view_path: @tmp_path)
61
61
  end
62
62
 
63
+ test "format can be passed as symbol or a string" do
64
+ File.open(@tmp_path + "show.json.rabl", "w") do |f|
65
+ f.puts %q{
66
+ object :@user
67
+ attributes :id, :name
68
+ }
69
+ end
70
+
71
+ assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path, format: :json)
72
+ assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path, format: 'json')
73
+ assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path, format: 'JSON')
74
+ end
75
+
63
76
  end
@@ -61,6 +61,11 @@ class TestJsonRenderer < ActiveSupport::TestCase
61
61
  assert_equal %q({"name":"foobar"}), render_json_output
62
62
  end
63
63
 
64
+ test "render glued node" do
65
+ @template.source = { :_glue0 => { :_data => :@data, :foo => lambda { |u| u.name } } }
66
+ assert_equal(%q({"foo":"foobar"}), render_json_output)
67
+ end
68
+
64
69
  test "render collection with attributes" do
65
70
  @data = [User.new(1, 'foo', 'male'), User.new(2, 'bar', 'female')]
66
71
  @context.assigns['data'] = @data
@@ -152,4 +157,14 @@ class TestJsonRenderer < ActiveSupport::TestCase
152
157
  @template.source = { :id => :id, :name => :name }
153
158
  assert_equal %q({"id":1,"name":"foobar"}), render_json_output
154
159
  end
160
+
161
+ test "merge should raise is return from given block is not a hash" do
162
+ @template.source = { :_merge0 => ->(c) { 'foo' } }
163
+ assert_raises(RablRails::Renderers::PartialError) { render_json_output }
164
+ end
165
+
166
+ test "result from merge is merge inside current response" do
167
+ @template.source = { :_merge0 => ->(c) { { :custom => c.name } } }
168
+ assert_equal %q({"custom":"foobar"}), render_json_output
169
+ end
155
170
  end
data/test/test_helper.rb CHANGED
@@ -26,7 +26,7 @@ require 'plist'
26
26
 
27
27
  if RUBY_ENGINE == 'jruby'
28
28
  require 'nokogiri'
29
- else
29
+ elsif RUBY_ENGINE == 'ruby'
30
30
  require 'libxml'
31
31
  end
32
32
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rabl-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-14 00:00:00.000000000 Z
12
+ date: 2013-03-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -119,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
119
  version: '0'
120
120
  requirements: []
121
121
  rubyforge_project:
122
- rubygems_version: 1.8.21
122
+ rubygems_version: 1.8.24
123
123
  signing_key:
124
124
  specification_version: 3
125
125
  summary: Fast Rails 3+ templating system with JSON and XML support