Skip to content

Commit 073de75

Browse files
committed
Add support for Props::Options object.
This addresses #56 by adding an options object. For simplicity and backward compatibility we inherit from Hash. Here's an example of usage: ``` json.note Props::Options.new .partial(“club_notes/notes”, locals: {club_note: @club_note} .fragment(“club_note_#{@club_note.id}”) ``` We also retain backward compat with the old syntax here: ``` json.note( partial: [ "club_notes/note", fragment: "club_note_#{@club_note.id}", locals: { club_note: @club_note } ] ) do end ```
1 parent 51bb252 commit 073de75

14 files changed

+347
-22
lines changed

lib/props_template.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require "active_support/core_ext/array"
1010
require "props_template/searcher"
1111
require "props_template/handler"
12+
require "props_template/options"
1213
require "props_template/version"
1314

1415
require "active_support"
@@ -28,7 +29,7 @@ class << self
2829
:deferred!,
2930
:fragments!,
3031
:disable_deferments!,
31-
:set_block_content!,
32+
:set_content!,
3233
:traveled_path!,
3334
:fragment_context!,
3435
to: :builder!

lib/props_template/base.rb

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def initialize(encoder = nil)
1414
@scope = nil
1515
end
1616

17-
def set_block_content!(options = {})
17+
def set_content!(options = {})
1818
@scope = nil
1919
yield
2020
if @scope.nil?
@@ -26,7 +26,7 @@ def set_block_content!(options = {})
2626
def handle_set_block(key, options)
2727
key = format_key(key)
2828
@stream.push_key(key)
29-
set_block_content!(options) do
29+
set_content!(options) do
3030
yield
3131
end
3232
end
@@ -64,7 +64,7 @@ def refine_item_options(item, options)
6464
end
6565

6666
def handle_collection_item(collection, item, index, options)
67-
set_block_content!(options) do
67+
set_content!(options) do
6868
yield
6969
end
7070
end
@@ -97,13 +97,20 @@ def array!(collection = nil, options = {})
9797
raise InvalidScopeForArrayError.new("array! expects exclusive use of this block")
9898
end
9999

100-
if collection.nil?
101-
@child_index = nil
102-
yield
103-
else
104-
handle_collection(collection, options) do |item, index|
105-
yield item, index
100+
if block_given?
101+
if collection.nil?
102+
@child_index = nil
103+
yield
104+
else
105+
handle_collection(collection, options) do |item, index|
106+
yield item, index
107+
end
106108
end
109+
elsif options.is_a?(Props::Options)
110+
options.valid_for_set!
111+
handle_collection(collection, options) {}
112+
else
113+
raise ArgumentError.new("array! requires a block when no Props::Options object is given")
107114
end
108115

109116
@scope = :array
@@ -147,7 +154,7 @@ def child!(options = {})
147154
child_index += 1
148155

149156
# this changes the scope to nil so child in a child will break
150-
set_block_content!(options) do
157+
set_content!(options) do
151158
yield
152159
end
153160

lib/props_template/base_with_extensions.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def traveled_path!
2626
@traveled_path.join(".")
2727
end
2828

29-
def set_block_content!(options = {})
29+
def set_content!(options = {})
3030
return super if !@em.has_extensions(options)
3131

3232
@em.handle(options) do
@@ -51,11 +51,16 @@ def format_key(key)
5151
end
5252

5353
def set!(key, options = {}, &block)
54-
if block
54+
if block || options.is_a?(Props::Options)
5555
options = @em.refine_options(options)
5656
end
5757

58-
super
58+
if !block && options.is_a?(Props::Options)
59+
options.valid_for_set!
60+
super {}
61+
else
62+
super
63+
end
5964
end
6065

6166
def handle_set_block(key, options)

lib/props_template/extension_manager.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def handle(options)
5353
@fragment.handle(options)
5454
else
5555
handle_cache(options) do
56-
base.set_block_content! do
56+
base.set_content! do
5757
if options[:partial]
5858
@fragment.handle(options)
5959
@partialer.handle(options)

lib/props_template/extensions/partial_renderer.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,3 @@ def refine_options(options, item = nil)
167167
end
168168
end
169169
end
170-

lib/props_template/options.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
module Props
2+
class Options < Hash
3+
def partial(partial_name, opts = {})
4+
self[:partial] = [partial_name, opts]
5+
self
6+
end
7+
8+
def defer(type, placeholder: {}, **opts)
9+
self[:defer] = [type, {placeholder: placeholder}.merge(opts)]
10+
self
11+
end
12+
13+
def fragment(fragment)
14+
raise "Fragment can't be defined without a partial. Please use `partial` first" if !self[:partial]
15+
16+
self[:partial][1][:fragment] = fragment
17+
self
18+
end
19+
20+
def cache(id_or_block)
21+
return unless id_or_block
22+
23+
self[:cache] = id_or_block
24+
self
25+
end
26+
27+
def id_key(key_name)
28+
self[:key] = key_name
29+
self
30+
end
31+
32+
def valid_for_set!
33+
raise "The partial option must be used with an inline `set!`" if !key?(:partial)
34+
end
35+
end
36+
end

lib/props_template/searcher.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,12 @@ def found!
4141
[@found_block, @traveled_path, pass_opts, @fragment_path, fragment_context]
4242
end
4343

44-
def set_block_content!(*args)
44+
def set_content!(*args)
4545
yield
4646
end
4747

4848
def set!(key, options = {}, &block)
49-
return if @found_block || !block
49+
return if @found_block || !block && !options.is_a?(Props::Options)
5050

5151
if @search_path[@depth] == key.to_s
5252
@traveled_path.push(key)

spec/extensions/defer_extension_spec.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,29 @@
3030
})
3131
end
3232

33+
it "defers a block from loading using an options object" do
34+
json = render(<<~PROPS)
35+
json.outer do
36+
json.inner(Props::Options.new.defer(:auto)) do
37+
json.greeting do
38+
json.foo 'hello world'
39+
end
40+
end
41+
end
42+
43+
json.deferred json.deferred!
44+
PROPS
45+
46+
expect(json).to eql_json({
47+
outer: {
48+
inner: {}
49+
},
50+
deferred: [
51+
{url: "/some_url?props_at=outer.inner", path: "outer.inner", type: "auto"}
52+
]
53+
})
54+
end
55+
3356
it "disables deferments" do
3457
json = render(<<~PROPS)
3558
json.disable_deferments!
@@ -79,6 +102,28 @@
79102
]
80103
})
81104
end
105+
it "defers a block from loading, and replaces with a custom placeholder from an options object" do
106+
json = render(<<~PROPS)
107+
json.outer do
108+
json.inner(Props::Options.new.defer(:auto, placeholder: [])) do
109+
json.greeting do
110+
json.foo 'hello world'
111+
end
112+
end
113+
end
114+
115+
json.deferred json.deferred!
116+
PROPS
117+
118+
expect(json).to eql_json({
119+
outer: {
120+
inner: []
121+
},
122+
deferred: [
123+
{url: "/some_url?props_at=outer.inner", path: "outer.inner", type: "auto"}
124+
]
125+
})
126+
end
82127

83128
it "defers a block from loading, and passes success and fail types" do
84129
json = render(<<~PROPS)
@@ -103,6 +148,29 @@
103148
})
104149
end
105150

151+
it "defers a block from loading, and passes success and fail types using an options object" do
152+
json = render(<<~PROPS)
153+
json.outer do
154+
json.inner(Props::Options.new.defer(:auto, success_action: 'SUCCESS', fail_action: 'FAIL')) do
155+
json.greeting do
156+
json.foo 'hello world'
157+
end
158+
end
159+
end
160+
161+
json.deferred json.deferred!
162+
PROPS
163+
164+
expect(json).to eql_json({
165+
outer: {
166+
inner: {}
167+
},
168+
deferred: [
169+
{url: "/some_url?props_at=outer.inner", path: "outer.inner", type: "auto", successAction: "SUCCESS", failAction: "FAIL"}
170+
]
171+
})
172+
end
173+
106174
it "defers a block from loading and populates with a manual type" do
107175
json = render(<<~PROPS)
108176
json.outer do

spec/extensions/fragments_spec.rb

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,35 @@
3030
})
3131
end
3232

33+
it "renders with a partial and populates fragments using an inline option object" do
34+
json = render(<<~PROPS)
35+
json.outer do
36+
fragment_opts = Props::Options.new
37+
.partial('simple')
38+
.fragment(:simple)
39+
40+
json.inner(fragment_opts)
41+
json.inner2(fragment_opts)
42+
end
43+
json.fragments json.fragments!
44+
PROPS
45+
46+
expect(json).to eql_json({
47+
outer: {
48+
inner: {
49+
foo: "bar"
50+
},
51+
inner2: {
52+
foo: "bar"
53+
}
54+
},
55+
fragments: [
56+
{id: :simple, path: "outer.inner"},
57+
{id: :simple, path: "outer.inner2"}
58+
]
59+
})
60+
end
61+
3362
it "renders with a partial and populates fragments even when caching" do
3463
render(<<~PROPS)
3564
json.outer do
@@ -78,15 +107,15 @@
78107

79108
expect(json).to eql_json({
80109
outer: {
81-
inner: {foo:"bar"}
110+
inner: {foo: "bar"}
82111
},
83112
fragments: [
84113
{id: "simple", path: "outer.inner"}
85114
],
86115
deferred: []
87116
})
88117
end
89-
118+
90119
it "renders deferment and populates fragments" do
91120
json = render(<<~PROPS)
92121
json.outer do
@@ -240,10 +269,10 @@
240269
expect(json).to eql_json({
241270
data: [
242271
{
243-
email: "joe@red.com",
272+
email: "joe@red.com"
244273
},
245274
{
246-
email: "foo@red.com",
275+
email: "foo@red.com"
247276
}
248277
],
249278
fragments: [

spec/extensions/key_extension_spec.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,36 @@
3232
})
3333
end
3434

35+
it "renders an array of items with id using :key as the method_name via an options object" do
36+
json = render(<<~PROPS)
37+
klass = Struct.new(:email, :id)
38+
39+
users = [
40+
klass.new('joe@red.com', 1),
41+
klass.new('foo@red.com', 2)
42+
]
43+
44+
json.data do
45+
json.array! users, Props::Options.new.id_key(:id) do |person|
46+
json.email person.email
47+
end
48+
end
49+
PROPS
50+
51+
expect(json).to eql_json({
52+
data: [
53+
{
54+
email: "joe@red.com",
55+
id: 1
56+
},
57+
{
58+
email: "foo@red.com",
59+
id: 2
60+
}
61+
]
62+
})
63+
end
64+
3565
it "preserves the id option when digging is exact" do
3666
json = render(<<~PROPS)
3767
klass = Struct.new(:email, :id)

0 commit comments

Comments
 (0)