rabl-rails 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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