props_template 0.13.0

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