props_template 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ require 'active_support/core_ext/array'
2
+
3
+ module Props
4
+ class KeyFormatter
5
+ def initialize(*args)
6
+ @format = {}
7
+ @cache = {}
8
+
9
+ options = args.extract_options!
10
+ args.each do |name|
11
+ @format[name] = []
12
+ end
13
+ options.each do |name, parameters|
14
+ @format[name] = parameters
15
+ end
16
+ end
17
+
18
+ def initialize_copy(original)
19
+ @cache = {}
20
+ end
21
+
22
+ def format(key)
23
+ @cache[key] ||= @format.inject(key.to_s) do |result, args|
24
+ func, args = args
25
+ if ::Proc === func
26
+ func.call result, *args
27
+ else
28
+ result.send func, *args
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,55 @@
1
+ module Props
2
+ module LayoutPatch
3
+ def render(context, options)
4
+ options[:locals] ||= {}
5
+ options[:locals][:json] = nil
6
+
7
+ @details = extract_details(options)
8
+ template = determine_template(options)
9
+
10
+ if template.handler == Props::Handler && options[:layout]
11
+ prepend_formats(template.format)
12
+ render_props_template(context, template, options[:layout], options[:locals])
13
+ else
14
+ super(context, options)
15
+ end
16
+ end
17
+
18
+ def render_props_template(view, template, path, locals)
19
+ layout_locals = locals.dup
20
+ layout_locals.delete(:json)
21
+
22
+ layout = resolve_props_layout(path, layout_locals, [formats.first])
23
+
24
+ body = layout.render(view, layout_locals) do |json|
25
+ locals[:json] = json
26
+ template.render(view, locals)
27
+ end
28
+
29
+ build_rendered_template(body, template)
30
+ end
31
+
32
+ def resolve_props_layout(layout, keys, formats)
33
+ details = @details.dup
34
+ details[:formats] = formats
35
+
36
+ case layout
37
+ when String
38
+ begin
39
+ if layout.start_with?("/")
40
+ ActiveSupport::Deprecation.warn "Rendering layouts from an absolute path is deprecated."
41
+ @lookup_context.with_fallbacks.find_template(layout, nil, false, [], details)
42
+ else
43
+ @lookup_context.find_template(layout, nil, false, [], details)
44
+ end
45
+ end
46
+ when Proc
47
+ resolve_layout(layout.call(@lookup_context, formats), keys, formats)
48
+ else
49
+ layout
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ActionView::TemplateRenderer.prepend(Props::LayoutPatch)
@@ -0,0 +1,22 @@
1
+ require 'rails/railtie'
2
+ require 'props_template'
3
+
4
+ module Props
5
+ class Railtie < ::Rails::Railtie
6
+ initializer :props_template do
7
+ ActiveSupport.on_load :action_view do
8
+ ActionView::Template.register_template_handler :props, Props::Handler
9
+ require 'props_template/dependency_tracker'
10
+ require 'props_template/layout_patch'
11
+ end
12
+ end
13
+
14
+ # if Rails::VERSION::MAJOR >= 4
15
+ # generators do |app|
16
+ # Rails::Generators.configure! app.config.generators
17
+ # Rails::Generators.hidden_namespaces.uniq!
18
+ # require 'generators/rails/scaffold_controller_generator'
19
+ # end
20
+ # end
21
+ end
22
+ end
@@ -0,0 +1,106 @@
1
+ require 'props_template/extensions/partial_renderer'
2
+
3
+ module Props
4
+ class Searcher
5
+ attr_reader :builder, :context, :fragments, :traveled_path
6
+
7
+ def initialize(builder, path=[], context = nil)
8
+ @search_path = path
9
+ @depth = 0
10
+ @context = context
11
+ @found_block = nil
12
+ @found_options = nil
13
+ @builder = builder
14
+ @traveled_path = []
15
+ @partialer = Partialer.new(self, context, builder)
16
+ end
17
+
18
+ def deferred!
19
+ []
20
+ end
21
+
22
+ def fragments!
23
+ []
24
+ end
25
+
26
+ def fragment_digest!
27
+ end
28
+
29
+ def found!
30
+ pass_opts = @found_options.clone || {}
31
+ pass_opts.delete(:defer)
32
+ traveled_path = @traveled_path[1..-1] || []
33
+ if !traveled_path.empty?
34
+ pass_opts[:path_suffix] = traveled_path
35
+ end
36
+
37
+ [@found_block, pass_opts]
38
+ end
39
+
40
+ def set_block_content!(*args)
41
+ yield
42
+ end
43
+
44
+ def set!(key, options={}, &block)
45
+ return if @found_block || !block_given?
46
+
47
+ if @search_path[@depth] == key.to_s
48
+ @traveled_path.push(key)
49
+
50
+ if @depth == @search_path.size - 1
51
+ @found_options = options
52
+ @found_block = block
53
+ return
54
+ end
55
+
56
+ @depth += 1
57
+ if options[:partial]
58
+ @partialer.handle(options)
59
+ else
60
+ yield
61
+ end
62
+ @depth -= 1
63
+ end
64
+
65
+ nil
66
+ end
67
+
68
+ def array!(collection, options = {}, &block)
69
+ return if @found_block
70
+
71
+ key_index = @search_path[@depth]
72
+ id_name, id_val = key_index.to_s.split('=')
73
+
74
+ if id_val
75
+ id_val = id_val.to_i
76
+ item = collection.member_by(id_name, id_val)
77
+ else
78
+ index = id_name.to_i
79
+ item = collection.member_at(index)
80
+ end
81
+
82
+ if item
83
+ pass_opts = @partialer.refine_options(options, item)
84
+ @traveled_path.push(key_index)
85
+
86
+ if @depth == @search_path.size - 1
87
+ @found_options = pass_opts
88
+ @found_block = Proc.new {
89
+ yield item, 0
90
+ }
91
+ return
92
+ end
93
+
94
+ @depth += 1
95
+ if pass_opts[:partial]
96
+ # todo: what happens when cached: true is passed?
97
+ # would there be any problems with not using the collection_rende?
98
+ @partialer.handle(pass_opts)
99
+ else
100
+ yield item, 0
101
+ end
102
+ @depth -= 1
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,16 @@
1
+ require_relative './support/helper'
2
+ require_relative './support/rails_helper'
3
+ require 'props_template/layout_patch'
4
+
5
+ RSpec.describe 'Props::Template' do
6
+ it 'uses a layout to render' do
7
+ view_path = File.join(File.dirname(__FILE__),'./fixtures')
8
+ controller = ActionView::TestCase::TestController.new
9
+ controller.prepend_view_path(view_path)
10
+ controller.response.headers['Content-Type']='application/json'
11
+ controller.request.path = '/some_url'
12
+
13
+ json = controller.render('200', layout: 'application')
14
+ expect(json.strip).to eql('{"data":{"success":"ok"}}')
15
+ end
16
+ end
@@ -0,0 +1,282 @@
1
+ require_relative './support/helper'
2
+
3
+ RSpec.describe 'Props::Base' do
4
+ it 'initializes' do
5
+ expect {
6
+ Props::Base.new
7
+ }.to_not raise_error
8
+ end
9
+
10
+ context 'result!' do
11
+ it 'returns {} when empty' do
12
+ json = Props::Base.new
13
+ expect(json.result!.strip).to eql('{}')
14
+ end
15
+ end
16
+
17
+ context 'set!' do
18
+ it 'sets a value' do
19
+ json = Props::Base.new
20
+ json.set! :foo, 'bar'
21
+ attrs = json.result!.strip
22
+
23
+ expect(attrs).to eql_json({
24
+ foo: 'bar'
25
+ })
26
+ end
27
+
28
+ it 'sets a empty obj when block is empty' do
29
+ json = Props::Base.new
30
+ json.set! :foo do
31
+ end
32
+ attrs = json.result!.strip
33
+
34
+ expect(attrs).to eql_json({
35
+ foo: {}
36
+ })
37
+ end
38
+
39
+ it 'sets a empty obj when nested block is empty' do
40
+ json = Props::Base.new
41
+ json.set! :foo do
42
+ json.set! :bar do
43
+ end
44
+ end
45
+ attrs = json.result!.strip
46
+
47
+ expect(attrs).to eql_json({
48
+ foo: {
49
+ bar: {}
50
+ }
51
+ })
52
+ end
53
+
54
+ it 'sets a null value' do
55
+ json = Props::Base.new
56
+ json.set! :foo, nil
57
+ attrs = json.result!.strip
58
+
59
+ expect(attrs).to eql_json({
60
+ foo: nil
61
+ })
62
+ end
63
+
64
+ it 'sets multiple values' do
65
+ json = Props::Base.new
66
+ json.set! :foo, 'bar'
67
+ json.set! :steve, 'cool'
68
+
69
+ attrs = json.result!.strip
70
+
71
+ expect(attrs).to eql_json({
72
+ foo: 'bar',
73
+ steve: 'cool'
74
+ })
75
+ end
76
+
77
+ it 'sets multiple values with the same key, the last one wins' do
78
+ json = Props::Base.new
79
+ json.set! :foo, 'bar'
80
+ json.set! :foo, 'cool'
81
+
82
+ attrs = JSON.parse(json.result!)
83
+
84
+ expect(attrs).to eql({
85
+ 'foo' => 'cool'
86
+ })
87
+ end
88
+
89
+ it 'throws InvalidScopeForObjError when the current scope is an array' do
90
+ json = Props::Base.new
91
+ json.array! [1, 2] do |item|
92
+ json.set! :foo, item
93
+ end
94
+ expect {
95
+ json.set! :bar, 'world'
96
+ }.to raise_error(Props::InvalidScopeForObjError)
97
+ end
98
+ end
99
+
100
+ context 'set! with a block' do
101
+ it 'creates a new nested object' do
102
+ json = Props::Base.new
103
+ json.set! :outer do
104
+ json.set! :inner, 'baz'
105
+ end
106
+
107
+ attrs = json.result!.strip
108
+
109
+ expect(attrs).to eql_json({
110
+ outer: {
111
+ inner: 'baz'
112
+ }
113
+ })
114
+ end
115
+
116
+ it 'creates a nested object' do
117
+ json = Props::Base.new
118
+ json.set! :outer do
119
+ json.set! :inner, 'baz'
120
+ end
121
+
122
+ attrs = json.result!.strip
123
+
124
+ expect(attrs).to eql_json({
125
+ outer: {
126
+ inner: 'baz'
127
+ }
128
+ })
129
+ end
130
+
131
+ it 'creates a nested array of objects' do
132
+ json = Props::Base.new
133
+ json.set! :outer do
134
+ json.array! [1, 2] do |item|
135
+ json.set! :foo, item
136
+ end
137
+ end
138
+
139
+ attrs = json.result!.strip
140
+
141
+ expect(attrs).to eql_json({
142
+ outer: [
143
+ {foo: 1},
144
+ {foo: 2}
145
+ ]
146
+ })
147
+ end
148
+
149
+ it 'treats the second argument as an options arg' do
150
+ json = Props::Base.new
151
+ json.set! :outer, {some: 'setting'} do
152
+ json.set! :inner, 'baz'
153
+ end
154
+
155
+ attrs = json.result!.strip
156
+
157
+ expect(attrs).to eql_json({
158
+ outer: {
159
+ inner: 'baz'
160
+ }
161
+ })
162
+ end
163
+ end
164
+
165
+ context 'array!' do
166
+ it 'creates an array of 1 object' do
167
+ json = Props::Base.new
168
+ json.array! [1] do |num|
169
+ json.set! :foo, num
170
+ end
171
+
172
+ attrs = json.result!.strip
173
+
174
+ expect(attrs).to eql_json([
175
+ {foo: 1}
176
+ ])
177
+ end
178
+
179
+ it 'passes the index as the second argument of yield' do
180
+ json = Props::Base.new
181
+ json.array! ['a', 'b'] do |item, index|
182
+ json.set! :foo, [item, index]
183
+ end
184
+
185
+ attrs = json.result!.strip
186
+
187
+ expect(attrs).to eql_json([
188
+ {foo: ['a', 0]},
189
+ {foo: ['b', 1]}
190
+ ])
191
+ end
192
+
193
+ it 'creates an empty array when passed an empty collection' do
194
+ json = Props::Base.new
195
+ json.array! [] do |num|
196
+ end
197
+
198
+ attrs = json.result!.strip
199
+
200
+ expect(attrs).to eql_json([
201
+ ])
202
+ end
203
+
204
+ it 'creates an array of empty arrays when passed an empty collection' do
205
+ json = Props::Base.new
206
+ json.array! [1] do |num|
207
+ json.array! [] do
208
+ end
209
+ end
210
+
211
+ attrs = json.result!.strip
212
+ expect(attrs).to eql_json([[]])
213
+ end
214
+
215
+ it 'creates an array of empties if set did not get called in array' do
216
+ json = Props::Base.new
217
+ json.array! [1, 2, 3] do |num|
218
+ end
219
+
220
+ attrs = json.result!.strip
221
+
222
+ expect(attrs).to eql_json([
223
+ {},
224
+ {},
225
+ {}
226
+ ])
227
+ end
228
+
229
+ it 'throws InvalidScopeForArray error when array is used twice' do
230
+ json = Props::Base.new
231
+ json.array! [1] do
232
+ json.set! :foo, 'first'
233
+ end
234
+
235
+ expect {
236
+ json.array! [2] do |num|
237
+ json.set! :foo, 'second'
238
+ end
239
+ }.to raise_error(Props::InvalidScopeForArrayError)
240
+ end
241
+
242
+ it 'creates an array of multiple objects' do
243
+ json = Props::Base.new
244
+ json.array! [1, 2] do |num|
245
+ json.set! :foo, num
246
+ end
247
+
248
+ attrs = json.result!.strip
249
+
250
+ expect(attrs).to eql_json([
251
+ {foo: 1},
252
+ {foo: 2}
253
+ ])
254
+ end
255
+
256
+ it 'creates an array of arrays' do
257
+ json = Props::Base.new
258
+ json.array! [[1, 2], [3, 4]] do |part|
259
+ json.array! part do |num|
260
+ json.set! :foo, num
261
+ end
262
+ end
263
+
264
+ attrs = json.result!.strip
265
+
266
+ expect(attrs).to eql_json([
267
+ [{foo: 1}, {foo: 2}],
268
+ [{foo: 3}, {foo: 4}]
269
+ ])
270
+ end
271
+
272
+ it 'throws InvalidScopeForArrayError when the current scope is already an object' do
273
+ json = Props::Base.new
274
+ json.set! :foo, 'bar'
275
+ expect {
276
+ json.array! [1, 2] do |num|
277
+ json.set! :foo, 'noop'
278
+ end
279
+ }.to raise_error(Props::InvalidScopeForArrayError)
280
+ end
281
+ end
282
+ end