curly-templates 2.5.0 → 2.6.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.
- checksums.yaml +4 -4
- data/.gitignore +20 -0
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/.yardopts +3 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +4 -2
- data/README.md +76 -3
- data/Rakefile +3 -116
- data/circle.yml +1 -1
- data/curly-templates.gemspec +6 -83
- data/lib/curly.rb +1 -1
- data/lib/curly/compiler.rb +2 -12
- data/lib/curly/component_compiler.rb +6 -3
- data/lib/curly/presenter.rb +32 -20
- data/lib/curly/presenter_name_error.rb +16 -0
- data/lib/curly/rspec.rb +16 -0
- data/lib/curly/version.rb +3 -0
- metadata +14 -72
- data/perf/compile_benchmark.rb +0 -71
- data/perf/compile_profile.rb +0 -64
- data/perf/component_benchmark.rb +0 -41
- data/spec/attribute_scanner_spec.rb +0 -44
- data/spec/collection_blocks_spec.rb +0 -78
- data/spec/compiler/collections_spec.rb +0 -230
- data/spec/compiler/context_blocks_spec.rb +0 -106
- data/spec/compiler_spec.rb +0 -192
- data/spec/component_compiler_spec.rb +0 -107
- data/spec/component_scanner_spec.rb +0 -36
- data/spec/components_spec.rb +0 -43
- data/spec/conditional_blocks_spec.rb +0 -40
- data/spec/dummy/.gitignore +0 -1
- data/spec/dummy/app/controllers/application_controller.rb +0 -2
- data/spec/dummy/app/controllers/dashboards_controller.rb +0 -14
- data/spec/dummy/app/helpers/application_helper.rb +0 -5
- data/spec/dummy/app/presenters/dashboards/collection_presenter.rb +0 -7
- data/spec/dummy/app/presenters/dashboards/item_presenter.rb +0 -23
- data/spec/dummy/app/presenters/dashboards/new_presenter.rb +0 -29
- data/spec/dummy/app/presenters/dashboards/partials_presenter.rb +0 -5
- data/spec/dummy/app/presenters/dashboards/show_presenter.rb +0 -12
- data/spec/dummy/app/presenters/layouts/application_presenter.rb +0 -21
- data/spec/dummy/app/views/dashboards/_item.html.curly +0 -1
- data/spec/dummy/app/views/dashboards/collection.html.curly +0 -8
- data/spec/dummy/app/views/dashboards/new.html.curly +0 -7
- data/spec/dummy/app/views/dashboards/partials.html.curly +0 -3
- data/spec/dummy/app/views/dashboards/show.html.curly +0 -3
- data/spec/dummy/app/views/layouts/application.html.curly +0 -11
- data/spec/dummy/config.ru +0 -4
- data/spec/dummy/config/application.rb +0 -12
- data/spec/dummy/config/boot.rb +0 -5
- data/spec/dummy/config/environment.rb +0 -5
- data/spec/dummy/config/environments/test.rb +0 -36
- data/spec/dummy/config/routes.rb +0 -6
- data/spec/generators/controller_generator_spec.rb +0 -34
- data/spec/integration/application_layout_spec.rb +0 -22
- data/spec/integration/collection_blocks_spec.rb +0 -37
- data/spec/integration/context_blocks_spec.rb +0 -27
- data/spec/integration/partials_spec.rb +0 -24
- data/spec/matchers/have_structure.rb +0 -29
- data/spec/parser_spec.rb +0 -92
- data/spec/presenter_spec.rb +0 -247
- data/spec/scanner_spec.rb +0 -124
- data/spec/spec_helper.rb +0 -45
- data/spec/syntax_error_spec.rb +0 -12
- data/spec/template_handler_spec.rb +0 -209
data/spec/presenter_spec.rb
DELETED
@@ -1,247 +0,0 @@
|
|
1
|
-
describe Curly::Presenter do
|
2
|
-
class CircusPresenter < Curly::Presenter
|
3
|
-
module MonkeyComponents
|
4
|
-
def monkey
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
|
-
exposes_helper :foo
|
9
|
-
|
10
|
-
include MonkeyComponents
|
11
|
-
|
12
|
-
presents :midget, :clown, default: nil
|
13
|
-
presents :elephant, default: "Dumbo"
|
14
|
-
presents :puma, default: -> { 'block' }
|
15
|
-
presents(:lion) { @elephant.upcase }
|
16
|
-
presents(:something) { self }
|
17
|
-
|
18
|
-
attr_reader :midget, :clown, :elephant, :puma, :lion, :something
|
19
|
-
end
|
20
|
-
|
21
|
-
class FrenchCircusPresenter < CircusPresenter
|
22
|
-
presents :elephant, default: "Babar"
|
23
|
-
end
|
24
|
-
|
25
|
-
class FancyCircusPresenter < CircusPresenter
|
26
|
-
presents :champagne
|
27
|
-
end
|
28
|
-
|
29
|
-
class CircusPresenter::MonkeyPresenter < Curly::Presenter
|
30
|
-
end
|
31
|
-
|
32
|
-
describe "#initialize" do
|
33
|
-
let(:context) { double("context") }
|
34
|
-
|
35
|
-
it "sets the presented identifiers as instance variables" do
|
36
|
-
presenter = CircusPresenter.new(context,
|
37
|
-
midget: "Meek Harolson",
|
38
|
-
clown: "Bubbles"
|
39
|
-
)
|
40
|
-
|
41
|
-
presenter.midget.should == "Meek Harolson"
|
42
|
-
presenter.clown.should == "Bubbles"
|
43
|
-
end
|
44
|
-
|
45
|
-
it "raises an exception if a required identifier is not specified" do
|
46
|
-
expect {
|
47
|
-
FancyCircusPresenter.new(context, {})
|
48
|
-
}.to raise_exception(ArgumentError, "required identifier `champagne` missing")
|
49
|
-
end
|
50
|
-
|
51
|
-
it "allows specifying default values for identifiers" do
|
52
|
-
# Make sure subclasses can change default values.
|
53
|
-
french_presenter = FrenchCircusPresenter.new(context)
|
54
|
-
french_presenter.elephant.should == "Babar"
|
55
|
-
french_presenter.lion.should == 'BABAR'
|
56
|
-
french_presenter.puma.should be_a Proc
|
57
|
-
|
58
|
-
# The subclass shouldn't change the superclass' defaults, though.
|
59
|
-
presenter = CircusPresenter.new(context)
|
60
|
-
presenter.elephant.should == "Dumbo"
|
61
|
-
presenter.lion.should == 'DUMBO'
|
62
|
-
presenter.puma.should be_a Proc
|
63
|
-
end
|
64
|
-
|
65
|
-
it "doesn't call a block if given as a value for identifiers" do
|
66
|
-
lion = proc { 'Simba' }
|
67
|
-
presenter = CircusPresenter.new(context, lion: lion)
|
68
|
-
presenter.lion.should be lion
|
69
|
-
end
|
70
|
-
|
71
|
-
it "calls default blocks in the instance of the presenter" do
|
72
|
-
presenter = CircusPresenter.new(context)
|
73
|
-
presenter.something.should be presenter
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
describe "#method_missing" do
|
78
|
-
let(:context) { double("context") }
|
79
|
-
subject {
|
80
|
-
CircusPresenter.new(context,
|
81
|
-
midget: "Meek Harolson",
|
82
|
-
clown: "Bubbles")
|
83
|
-
}
|
84
|
-
|
85
|
-
it "delegates calls to the context" do
|
86
|
-
context.should receive(:undefined).once
|
87
|
-
subject.undefined
|
88
|
-
end
|
89
|
-
|
90
|
-
it "allows method calls on context-defined methods" do
|
91
|
-
context.should receive(:respond_to?).
|
92
|
-
with(:undefined, false).once.and_return(true)
|
93
|
-
subject.method(:undefined)
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
describe ".exposes_helper" do
|
98
|
-
let(:context) { double("context") }
|
99
|
-
subject {
|
100
|
-
CircusPresenter.new(context,
|
101
|
-
midget: "Meek Harolson",
|
102
|
-
clown: "Bubbles")
|
103
|
-
}
|
104
|
-
|
105
|
-
it "allows a method as a component" do
|
106
|
-
CircusPresenter.component_available?(:foo)
|
107
|
-
end
|
108
|
-
|
109
|
-
it "delegates the call to the context" do
|
110
|
-
context.should receive(:foo).once
|
111
|
-
subject.should_not receive(:method_missing)
|
112
|
-
subject.foo
|
113
|
-
end
|
114
|
-
|
115
|
-
it "doesn't delegate other calls to the context" do
|
116
|
-
expect { subject.bar }.to raise_error
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
describe ".presenter_for_path" do
|
121
|
-
it "returns the presenter class for the given path" do
|
122
|
-
presenter = double("presenter")
|
123
|
-
stub_const("Foo::BarPresenter", presenter)
|
124
|
-
|
125
|
-
Curly::Presenter.presenter_for_path("foo/bar").should == presenter
|
126
|
-
end
|
127
|
-
|
128
|
-
it "returns nil if there is no presenter for the given path" do
|
129
|
-
Curly::Presenter.presenter_for_path("foo/bar").should be_nil
|
130
|
-
end
|
131
|
-
|
132
|
-
it "does not swallow exceptions" do
|
133
|
-
error = NameError.new("omg!", :baz)
|
134
|
-
String.any_instance.stub(:constantize).and_raise(error)
|
135
|
-
|
136
|
-
expect do
|
137
|
-
Curly::Presenter.presenter_for_path("foo/bar")
|
138
|
-
end.to raise_error(NameError)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
describe ".presenter_for_name" do
|
143
|
-
it "returns the presenter class for the given name" do
|
144
|
-
CircusPresenter.presenter_for_name("monkey").should == CircusPresenter::MonkeyPresenter
|
145
|
-
end
|
146
|
-
|
147
|
-
it "looks in the namespace" do
|
148
|
-
CircusPresenter.presenter_for_name("french_circus").should == FrenchCircusPresenter
|
149
|
-
end
|
150
|
-
|
151
|
-
it "returns NameError if the presenter class doesn't exist" do
|
152
|
-
expect { CircusPresenter.presenter_for_name("clown") }.to raise_exception(NameError)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
|
156
|
-
describe ".available_components" do
|
157
|
-
it "includes the methods on the presenter" do
|
158
|
-
CircusPresenter.available_components.should include("midget")
|
159
|
-
end
|
160
|
-
|
161
|
-
it "does not include methods on the Curly::Presenter base class" do
|
162
|
-
CircusPresenter.available_components.should_not include("cache_key")
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
describe ".component_available?" do
|
167
|
-
it "returns true if the method is available" do
|
168
|
-
CircusPresenter.component_available?("midget").should == true
|
169
|
-
end
|
170
|
-
|
171
|
-
it "returns false if the method is not available" do
|
172
|
-
CircusPresenter.component_available?("bear").should == false
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
describe ".version" do
|
177
|
-
it "sets the version of the presenter" do
|
178
|
-
presenter1 = Class.new(Curly::Presenter) do
|
179
|
-
version 42
|
180
|
-
end
|
181
|
-
|
182
|
-
presenter2 = Class.new(Curly::Presenter) do
|
183
|
-
version 1337
|
184
|
-
end
|
185
|
-
|
186
|
-
presenter1.version.should == 42
|
187
|
-
presenter2.version.should == 1337
|
188
|
-
end
|
189
|
-
|
190
|
-
it "returns 0 if no version has been set" do
|
191
|
-
presenter = Class.new(Curly::Presenter)
|
192
|
-
presenter.version.should == 0
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
describe ".cache_key" do
|
197
|
-
it "includes the presenter's class name and version" do
|
198
|
-
presenter = Class.new(Curly::Presenter) { version 42 }
|
199
|
-
stub_const("Foo::BarPresenter", presenter)
|
200
|
-
|
201
|
-
Foo::BarPresenter.cache_key.should == "Foo::BarPresenter/42"
|
202
|
-
end
|
203
|
-
|
204
|
-
it "includes the cache keys of presenters in the dependency list" do
|
205
|
-
presenter = Class.new(Curly::Presenter) do
|
206
|
-
version 42
|
207
|
-
depends_on 'foo/bum'
|
208
|
-
end
|
209
|
-
|
210
|
-
dependency = Class.new(Curly::Presenter) do
|
211
|
-
version 1337
|
212
|
-
end
|
213
|
-
|
214
|
-
stub_const("Foo::BarPresenter", presenter)
|
215
|
-
stub_const("Foo::BumPresenter", dependency)
|
216
|
-
|
217
|
-
cache_key = Foo::BarPresenter.cache_key
|
218
|
-
cache_key.should == "Foo::BarPresenter/42/Foo::BumPresenter/1337"
|
219
|
-
end
|
220
|
-
|
221
|
-
it "uses the view path of a dependency if there is no presenter for it" do
|
222
|
-
presenter = Class.new(Curly::Presenter) do
|
223
|
-
version 42
|
224
|
-
depends_on 'foo/bum'
|
225
|
-
end
|
226
|
-
|
227
|
-
stub_const("Foo::BarPresenter", presenter)
|
228
|
-
|
229
|
-
cache_key = Foo::BarPresenter.cache_key
|
230
|
-
cache_key.should == "Foo::BarPresenter/42/foo/bum"
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
describe ".dependencies" do
|
235
|
-
it "returns the dependencies defined for the presenter" do
|
236
|
-
presenter = Class.new(Curly::Presenter) { depends_on 'foo' }
|
237
|
-
presenter.dependencies.to_a.should == ['foo']
|
238
|
-
end
|
239
|
-
|
240
|
-
it "includes the dependencies defined for parent classes" do
|
241
|
-
Curly::Presenter.dependencies
|
242
|
-
parent = Class.new(Curly::Presenter) { depends_on 'foo' }
|
243
|
-
presenter = Class.new(parent) { depends_on 'bar' }
|
244
|
-
presenter.dependencies.to_a.should =~ ['foo', 'bar']
|
245
|
-
end
|
246
|
-
end
|
247
|
-
end
|
data/spec/scanner_spec.rb
DELETED
@@ -1,124 +0,0 @@
|
|
1
|
-
describe Curly::Scanner, ".scan" do
|
2
|
-
it "returns the tokens in the source" do
|
3
|
-
scan("foo {{bar}} baz").should == [
|
4
|
-
[:text, "foo "],
|
5
|
-
[:component, "bar", nil, {}, []],
|
6
|
-
[:text, " baz"]
|
7
|
-
]
|
8
|
-
end
|
9
|
-
|
10
|
-
it "scans components with identifiers" do
|
11
|
-
scan("{{foo.bar}}").should == [
|
12
|
-
[:component, "foo", "bar", {}, []]
|
13
|
-
]
|
14
|
-
end
|
15
|
-
|
16
|
-
it "scans comments in the source" do
|
17
|
-
scan("foo {{!bar}} baz").should == [
|
18
|
-
[:text, "foo "],
|
19
|
-
[:comment, "bar"],
|
20
|
-
[:text, " baz"]
|
21
|
-
]
|
22
|
-
end
|
23
|
-
|
24
|
-
it "allows newlines in comments" do
|
25
|
-
scan("{{!\nfoo\n}}").should == [
|
26
|
-
[:comment, "\nfoo\n"]
|
27
|
-
]
|
28
|
-
end
|
29
|
-
|
30
|
-
it "scans to the end of the source" do
|
31
|
-
scan("foo\n").should == [
|
32
|
-
[:text, "foo\n"]
|
33
|
-
]
|
34
|
-
end
|
35
|
-
|
36
|
-
it "allows escaping Curly quotes" do
|
37
|
-
scan('foo {{{ bar').should == [
|
38
|
-
[:text, "foo "],
|
39
|
-
[:text, "{{"],
|
40
|
-
[:text, " bar"]
|
41
|
-
]
|
42
|
-
|
43
|
-
scan('foo }} bar').should == [
|
44
|
-
[:text, "foo }} bar"]
|
45
|
-
]
|
46
|
-
|
47
|
-
scan('foo {{{ lala! }} bar').should == [
|
48
|
-
[:text, "foo "],
|
49
|
-
[:text, "{{"],
|
50
|
-
[:text, " lala! }} bar"]
|
51
|
-
]
|
52
|
-
end
|
53
|
-
|
54
|
-
it "scans context block tags" do
|
55
|
-
scan('{{@search_form}}{{query_field}}{{/search_form}}').should == [
|
56
|
-
[:context_block_start, "search_form", nil, {}, []],
|
57
|
-
[:component, "query_field", nil, {}, []],
|
58
|
-
[:block_end, "search_form", nil, {}, []]
|
59
|
-
]
|
60
|
-
end
|
61
|
-
|
62
|
-
it "scans conditional block tags" do
|
63
|
-
scan('foo {{#bar?}} hello {{/bar?}}').should == [
|
64
|
-
[:text, "foo "],
|
65
|
-
[:conditional_block_start, "bar?", nil, {}, []],
|
66
|
-
[:text, " hello "],
|
67
|
-
[:block_end, "bar?", nil, {}, []]
|
68
|
-
]
|
69
|
-
end
|
70
|
-
|
71
|
-
it "scans conditional block tags with parameters and attributes" do
|
72
|
-
scan('{{#active.test? name="test"}}yo{{/active.test?}}').should == [
|
73
|
-
[:conditional_block_start, "active?", "test", { "name" => "test" }, []],
|
74
|
-
[:text, "yo"],
|
75
|
-
[:block_end, "active?", "test", {}, []]
|
76
|
-
]
|
77
|
-
end
|
78
|
-
|
79
|
-
it "scans inverse block tags" do
|
80
|
-
scan('foo {{^bar?}} hello {{/bar?}}').should == [
|
81
|
-
[:text, "foo "],
|
82
|
-
[:inverse_conditional_block_start, "bar?", nil, {}, []],
|
83
|
-
[:text, " hello "],
|
84
|
-
[:block_end, "bar?", nil, {}, []]
|
85
|
-
]
|
86
|
-
end
|
87
|
-
|
88
|
-
it "scans collection block tags" do
|
89
|
-
scan('foo {{*bar}} hello {{/bar}}').should == [
|
90
|
-
[:text, "foo "],
|
91
|
-
[:collection_block_start, "bar", nil, {}, []],
|
92
|
-
[:text, " hello "],
|
93
|
-
[:block_end, "bar", nil, {}, []]
|
94
|
-
]
|
95
|
-
end
|
96
|
-
|
97
|
-
it "treats quotes as text" do
|
98
|
-
scan('"').should == [
|
99
|
-
[:text, '"']
|
100
|
-
]
|
101
|
-
end
|
102
|
-
|
103
|
-
it "treats Ruby interpolation as text" do
|
104
|
-
scan('#{foo}').should == [
|
105
|
-
[:text, '#{foo}']
|
106
|
-
]
|
107
|
-
end
|
108
|
-
|
109
|
-
it "raises Curly::SyntaxError on unclosed components" do
|
110
|
-
["{{", "{{yolo"].each do |template|
|
111
|
-
expect { scan(template) }.to raise_error(Curly::SyntaxError)
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
it "raises Curly::SyntaxError on unclosed comments" do
|
116
|
-
["{{!", "{{! foo bar"].each do |template|
|
117
|
-
expect { scan(template) }.to raise_error(Curly::SyntaxError)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def scan(source)
|
122
|
-
Curly::Scanner.scan(source)
|
123
|
-
end
|
124
|
-
end
|
data/spec/spec_helper.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
ENV["RAILS_ENV"] = "test"
|
2
|
-
|
3
|
-
require 'dummy/config/environment'
|
4
|
-
require 'rspec/rails'
|
5
|
-
|
6
|
-
RSpec.configure do |config|
|
7
|
-
config.infer_spec_type_from_file_location!
|
8
|
-
end
|
9
|
-
|
10
|
-
module CompilationSupport
|
11
|
-
def define_presenter(name = "ShowPresenter", &block)
|
12
|
-
presenter_class = Class.new(Curly::Presenter, &block)
|
13
|
-
stub_const(name, presenter_class)
|
14
|
-
presenter_class
|
15
|
-
end
|
16
|
-
|
17
|
-
def render(source, options = {}, &block)
|
18
|
-
presenter = options.fetch(:presenter) do
|
19
|
-
define_presenter("ShowPresenter") unless defined?(ShowPresenter)
|
20
|
-
"ShowPresenter"
|
21
|
-
end.constantize
|
22
|
-
|
23
|
-
virtual_path = options.fetch(:virtual_path) do
|
24
|
-
presenter.name.underscore.gsub(/_presenter\z/, "")
|
25
|
-
end
|
26
|
-
|
27
|
-
identifier = options.fetch(:identifier) do
|
28
|
-
defined?(Rails.root) ? "#{Rails.root}/#{virtual_path}.html.curly" : virtual_path
|
29
|
-
end
|
30
|
-
|
31
|
-
details = { virtual_path: virtual_path }
|
32
|
-
details.merge! options.fetch(:details, {})
|
33
|
-
|
34
|
-
handler = Curly::TemplateHandler
|
35
|
-
template = ActionView::Template.new(source, identifier, handler, details)
|
36
|
-
view = ActionView::Base.new
|
37
|
-
view.lookup_context.stub(:find_template) { source }
|
38
|
-
|
39
|
-
begin
|
40
|
-
template.render(view, options.fetch(:locals, {}), &block)
|
41
|
-
rescue ActionView::Template::Error => e
|
42
|
-
raise e.original_exception
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
data/spec/syntax_error_spec.rb
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
describe Curly::SyntaxError, "#message" do
|
2
|
-
it "includes the context of the error in the message" do
|
3
|
-
source = "I am a very bad error that has snuck in"
|
4
|
-
error = Curly::SyntaxError.new(13, source)
|
5
|
-
|
6
|
-
error.message.should == <<-MESSAGE.strip_heredoc
|
7
|
-
invalid syntax near `a very bad error` on line 1 in template:
|
8
|
-
|
9
|
-
I am a very bad error that has snuck in
|
10
|
-
MESSAGE
|
11
|
-
end
|
12
|
-
end
|
@@ -1,209 +0,0 @@
|
|
1
|
-
describe Curly::TemplateHandler do
|
2
|
-
let :presenter_class do
|
3
|
-
Class.new do
|
4
|
-
def initialize(context, options = {})
|
5
|
-
@context = context
|
6
|
-
@cache_key = options.fetch(:cache_key, nil)
|
7
|
-
@cache_duration = options.fetch(:cache_duration, nil)
|
8
|
-
@cache_options = options.fetch(:cache_options, {})
|
9
|
-
end
|
10
|
-
|
11
|
-
def setup!
|
12
|
-
@context.content_for(:foo, "bar")
|
13
|
-
end
|
14
|
-
|
15
|
-
def foo
|
16
|
-
"FOO"
|
17
|
-
end
|
18
|
-
|
19
|
-
def bar
|
20
|
-
@context.bar
|
21
|
-
end
|
22
|
-
|
23
|
-
def cache_key
|
24
|
-
@cache_key
|
25
|
-
end
|
26
|
-
|
27
|
-
def cache_duration
|
28
|
-
@cache_duration
|
29
|
-
end
|
30
|
-
|
31
|
-
def cache_options
|
32
|
-
@cache_options
|
33
|
-
end
|
34
|
-
|
35
|
-
def self.component_available?(method)
|
36
|
-
true
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
let :context_class do
|
42
|
-
Class.new do
|
43
|
-
attr_reader :output_buffer
|
44
|
-
attr_reader :local_assigns, :assigns
|
45
|
-
|
46
|
-
def initialize
|
47
|
-
@cache = Hash.new
|
48
|
-
@local_assigns = Hash.new
|
49
|
-
@assigns = Hash.new
|
50
|
-
@clock = 0
|
51
|
-
end
|
52
|
-
|
53
|
-
def reset!
|
54
|
-
@output_buffer = ActiveSupport::SafeBuffer.new
|
55
|
-
end
|
56
|
-
|
57
|
-
def advance_clock(duration)
|
58
|
-
@clock += duration
|
59
|
-
end
|
60
|
-
|
61
|
-
def content_for(key, value = nil)
|
62
|
-
@contents ||= {}
|
63
|
-
@contents[key] = value if value.present?
|
64
|
-
@contents[key]
|
65
|
-
end
|
66
|
-
|
67
|
-
def cache(key, options = {})
|
68
|
-
fragment, expired_at = @cache[key]
|
69
|
-
|
70
|
-
if fragment.nil? || @clock >= expired_at
|
71
|
-
old_buffer = @output_buffer
|
72
|
-
@output_buffer = ActiveSupport::SafeBuffer.new
|
73
|
-
|
74
|
-
yield
|
75
|
-
|
76
|
-
fragment = @output_buffer.to_s
|
77
|
-
duration = options[:expires_in] || Float::INFINITY
|
78
|
-
|
79
|
-
@cache[key] = [fragment, @clock + duration]
|
80
|
-
|
81
|
-
@output_buffer = old_buffer
|
82
|
-
end
|
83
|
-
|
84
|
-
safe_concat(fragment)
|
85
|
-
|
86
|
-
nil
|
87
|
-
end
|
88
|
-
|
89
|
-
def safe_concat(str)
|
90
|
-
@output_buffer.safe_concat(str)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
let(:template) { double("template", virtual_path: "test") }
|
96
|
-
let(:context) { context_class.new }
|
97
|
-
|
98
|
-
before do
|
99
|
-
stub_const("TestPresenter", presenter_class)
|
100
|
-
end
|
101
|
-
|
102
|
-
it "passes in the presenter context to the presenter class" do
|
103
|
-
context.stub(:bar) { "BAR" }
|
104
|
-
template.stub(:source) { "{{bar}}" }
|
105
|
-
output.should == "BAR"
|
106
|
-
end
|
107
|
-
|
108
|
-
it "should fail if there's no matching presenter class" do
|
109
|
-
template.stub(:virtual_path) { "missing" }
|
110
|
-
template.stub(:source) { " FOO " }
|
111
|
-
expect { output }.to raise_exception(Curly::PresenterNotFound)
|
112
|
-
end
|
113
|
-
|
114
|
-
it "allows calling public methods on the presenter" do
|
115
|
-
template.stub(:source) { "{{foo}}" }
|
116
|
-
output.should == "FOO"
|
117
|
-
end
|
118
|
-
|
119
|
-
it "marks its output as HTML safe" do
|
120
|
-
template.stub(:source) { "{{foo}}" }
|
121
|
-
output.should be_html_safe
|
122
|
-
end
|
123
|
-
|
124
|
-
it "calls the #setup! method before rendering the view" do
|
125
|
-
template.stub(:source) { "{{foo}}" }
|
126
|
-
output
|
127
|
-
context.content_for(:foo).should == "bar"
|
128
|
-
end
|
129
|
-
|
130
|
-
context "caching" do
|
131
|
-
before do
|
132
|
-
template.stub(:source) { "{{bar}}" }
|
133
|
-
context.stub(:bar) { "BAR" }
|
134
|
-
end
|
135
|
-
|
136
|
-
it "caches the result with the #cache_key from the presenter" do
|
137
|
-
context.assigns[:cache_key] = "x"
|
138
|
-
output.should == "BAR"
|
139
|
-
|
140
|
-
context.stub(:bar) { "BAZ" }
|
141
|
-
output.should == "BAR"
|
142
|
-
|
143
|
-
context.assigns[:cache_key] = "y"
|
144
|
-
output.should == "BAZ"
|
145
|
-
end
|
146
|
-
|
147
|
-
it "doesn't cache when the cache key is nil" do
|
148
|
-
context.assigns[:cache_key] = nil
|
149
|
-
output.should == "BAR"
|
150
|
-
|
151
|
-
context.stub(:bar) { "BAZ" }
|
152
|
-
output.should == "BAZ"
|
153
|
-
end
|
154
|
-
|
155
|
-
it "adds the presenter class' cache key to the instance's cache key" do
|
156
|
-
# Make sure caching is enabled
|
157
|
-
context.assigns[:cache_key] = "x"
|
158
|
-
|
159
|
-
presenter_class.stub(:cache_key) { "foo" }
|
160
|
-
|
161
|
-
output.should == "BAR"
|
162
|
-
|
163
|
-
presenter_class.stub(:cache_key) { "bar" }
|
164
|
-
|
165
|
-
context.stub(:bar) { "FOOBAR" }
|
166
|
-
output.should == "FOOBAR"
|
167
|
-
end
|
168
|
-
|
169
|
-
it "expires the cache keys after #cache_duration" do
|
170
|
-
context.assigns[:cache_key] = "x"
|
171
|
-
context.assigns[:cache_duration] = 42
|
172
|
-
|
173
|
-
output.should == "BAR"
|
174
|
-
|
175
|
-
context.stub(:bar) { "FOO" }
|
176
|
-
|
177
|
-
# Cached fragment has not yet expired.
|
178
|
-
context.advance_clock(41)
|
179
|
-
output.should == "BAR"
|
180
|
-
|
181
|
-
# Now it has! Huzzah!
|
182
|
-
context.advance_clock(1)
|
183
|
-
output.should == "FOO"
|
184
|
-
end
|
185
|
-
|
186
|
-
it "passes #cache_options to the cache backend" do
|
187
|
-
context.assigns[:cache_key] = "x"
|
188
|
-
context.assigns[:cache_options] = { expires_in: 42 }
|
189
|
-
|
190
|
-
output.should == "BAR"
|
191
|
-
|
192
|
-
context.stub(:bar) { "FOO" }
|
193
|
-
|
194
|
-
# Cached fragment has not yet expired.
|
195
|
-
context.advance_clock(41)
|
196
|
-
output.should == "BAR"
|
197
|
-
|
198
|
-
# Now it has! Huzzah!
|
199
|
-
context.advance_clock(1)
|
200
|
-
output.should == "FOO"
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def output
|
205
|
-
code = Curly::TemplateHandler.call(template)
|
206
|
-
context.reset!
|
207
|
-
context.instance_eval(code)
|
208
|
-
end
|
209
|
-
end
|