garterbelt 0.0.2
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.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +38 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +21 -0
- data/Rakefile +46 -0
- data/TODO +3 -0
- data/VERSION +1 -0
- data/garterbelt.gemspec +165 -0
- data/lib/garterbelt.rb +23 -0
- data/lib/page.rb +46 -0
- data/lib/renderers/cache.rb +35 -0
- data/lib/renderers/closed_tag.rb +60 -0
- data/lib/renderers/comment.rb +14 -0
- data/lib/renderers/content_rendering.rb +41 -0
- data/lib/renderers/content_tag.rb +36 -0
- data/lib/renderers/doctype.rb +24 -0
- data/lib/renderers/renderer.rb +33 -0
- data/lib/renderers/text.rb +28 -0
- data/lib/renderers/xml.rb +9 -0
- data/lib/stocking.rb +11 -0
- data/lib/support/string.rb +165 -0
- data/lib/view.rb +341 -0
- data/spec/benchmark/templates/erector.rb +37 -0
- data/spec/benchmark/templates/garterbelt.rb +37 -0
- data/spec/benchmark/vs_erector.rb +53 -0
- data/spec/garterbelt_spec.rb +49 -0
- data/spec/integration/expectations/general_view.html +17 -0
- data/spec/integration/expectations/variables/view_with_user_and_params.html +23 -0
- data/spec/integration/expectations/variables/view_with_user_email.html +23 -0
- data/spec/integration/expectations/view_partial_nest.html +24 -0
- data/spec/integration/expectations/view_with_tags.html +19 -0
- data/spec/integration/templates/view_partial_nest.rb +22 -0
- data/spec/integration/templates/view_with_cache.rb +30 -0
- data/spec/integration/templates/view_with_partial.rb +36 -0
- data/spec/integration/templates/view_with_partial_2.rb +36 -0
- data/spec/integration/templates/view_with_tags.rb +26 -0
- data/spec/integration/templates/view_with_vars.rb +32 -0
- data/spec/integration/view_spec.rb +57 -0
- data/spec/page_spec.rb +99 -0
- data/spec/renderers/cache_spec.rb +85 -0
- data/spec/renderers/closed_tag_spec.rb +172 -0
- data/spec/renderers/comment_spec.rb +68 -0
- data/spec/renderers/content_tag_spec.rb +150 -0
- data/spec/renderers/doctype_spec.rb +46 -0
- data/spec/renderers/text_spec.rb +68 -0
- data/spec/spec_helper.rb +17 -0
- data/spec/support/mock_view.rb +14 -0
- data/spec/support/puters.rb +10 -0
- data/spec/view/view_basics_spec.rb +106 -0
- data/spec/view/view_caching_spec.rb +132 -0
- data/spec/view/view_partial_spec.rb +63 -0
- data/spec/view/view_rails_type_helpers.rb +148 -0
- data/spec/view/view_render_spec.rb +408 -0
- data/spec/view/view_variables_spec.rb +159 -0
- metadata +367 -0
data/lib/stocking.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
require 'active_support/inflector/methods'
|
2
|
+
require 'active_support/inflector/inflections'
|
3
|
+
|
4
|
+
# including the string extension directly require i18l gem, which is lame when not needed
|
5
|
+
# String inflections define new methods on the String class to transform names for different purposes.
|
6
|
+
# For instance, you can figure out the name of a database from the name of a class.
|
7
|
+
#
|
8
|
+
# "ScaleScore".tableize # => "scale_scores"
|
9
|
+
#
|
10
|
+
|
11
|
+
class String
|
12
|
+
# Returns the plural form of the word in the string.
|
13
|
+
#
|
14
|
+
# "post".pluralize # => "posts"
|
15
|
+
# "octopus".pluralize # => "octopi"
|
16
|
+
# "sheep".pluralize # => "sheep"
|
17
|
+
# "words".pluralize # => "words"
|
18
|
+
# "the blue mailman".pluralize # => "the blue mailmen"
|
19
|
+
# "CamelOctopus".pluralize # => "CamelOctopi"
|
20
|
+
def pluralize
|
21
|
+
ActiveSupport::Inflector.pluralize(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
# The reverse of +pluralize+, returns the singular form of a word in a string.
|
25
|
+
#
|
26
|
+
# "posts".singularize # => "post"
|
27
|
+
# "octopi".singularize # => "octopus"
|
28
|
+
# "sheep".singularize # => "sheep"
|
29
|
+
# "word".singularize # => "word"
|
30
|
+
# "the blue mailmen".singularize # => "the blue mailman"
|
31
|
+
# "CamelOctopi".singularize # => "CamelOctopus"
|
32
|
+
def singularize
|
33
|
+
ActiveSupport::Inflector.singularize(self)
|
34
|
+
end
|
35
|
+
|
36
|
+
# +constantize+ tries to find a declared constant with the name specified
|
37
|
+
# in the string. It raises a NameError when the name is not in CamelCase
|
38
|
+
# or is not initialized.
|
39
|
+
#
|
40
|
+
# Examples
|
41
|
+
# "Module".constantize # => Module
|
42
|
+
# "Class".constantize # => Class
|
43
|
+
def constantize
|
44
|
+
ActiveSupport::Inflector.constantize(self)
|
45
|
+
end
|
46
|
+
|
47
|
+
# By default, +camelize+ converts strings to UpperCamelCase. If the argument to camelize
|
48
|
+
# is set to <tt>:lower</tt> then camelize produces lowerCamelCase.
|
49
|
+
#
|
50
|
+
# +camelize+ will also convert '/' to '::' which is useful for converting paths to namespaces.
|
51
|
+
#
|
52
|
+
# "active_record".camelize # => "ActiveRecord"
|
53
|
+
# "active_record".camelize(:lower) # => "activeRecord"
|
54
|
+
# "active_record/errors".camelize # => "ActiveRecord::Errors"
|
55
|
+
# "active_record/errors".camelize(:lower) # => "activeRecord::Errors"
|
56
|
+
def camelize(first_letter = :upper)
|
57
|
+
case first_letter
|
58
|
+
when :upper then ActiveSupport::Inflector.camelize(self, true)
|
59
|
+
when :lower then ActiveSupport::Inflector.camelize(self, false)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
alias_method :camelcase, :camelize
|
63
|
+
|
64
|
+
# Capitalizes all the words and replaces some characters in the string to create
|
65
|
+
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
|
66
|
+
# used in the Rails internals.
|
67
|
+
#
|
68
|
+
# +titleize+ is also aliased as +titlecase+.
|
69
|
+
#
|
70
|
+
# "man from the boondocks".titleize # => "Man From The Boondocks"
|
71
|
+
# "x-men: the last stand".titleize # => "X Men: The Last Stand"
|
72
|
+
def titleize
|
73
|
+
ActiveSupport::Inflector.titleize(self)
|
74
|
+
end
|
75
|
+
alias_method :titlecase, :titleize
|
76
|
+
|
77
|
+
# The reverse of +camelize+. Makes an underscored, lowercase form from the expression in the string.
|
78
|
+
#
|
79
|
+
# +underscore+ will also change '::' to '/' to convert namespaces to paths.
|
80
|
+
#
|
81
|
+
# "ActiveRecord".underscore # => "active_record"
|
82
|
+
# "ActiveRecord::Errors".underscore # => active_record/errors
|
83
|
+
def underscore
|
84
|
+
ActiveSupport::Inflector.underscore(self)
|
85
|
+
end
|
86
|
+
|
87
|
+
# Replaces underscores with dashes in the string.
|
88
|
+
#
|
89
|
+
# "puni_puni" # => "puni-puni"
|
90
|
+
def dasherize
|
91
|
+
ActiveSupport::Inflector.dasherize(self)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Removes the module part from the constant expression in the string.
|
95
|
+
#
|
96
|
+
# "ActiveRecord::CoreExtensions::String::Inflections".demodulize # => "Inflections"
|
97
|
+
# "Inflections".demodulize # => "Inflections"
|
98
|
+
def demodulize
|
99
|
+
ActiveSupport::Inflector.demodulize(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
|
103
|
+
#
|
104
|
+
# ==== Examples
|
105
|
+
#
|
106
|
+
# class Person
|
107
|
+
# def to_param
|
108
|
+
# "#{id}-#{name.parameterize}"
|
109
|
+
# end
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# @person = Person.find(1)
|
113
|
+
# # => #<Person id: 1, name: "Donald E. Knuth">
|
114
|
+
#
|
115
|
+
# <%= link_to(@person.name, person_path %>
|
116
|
+
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
|
117
|
+
def parameterize(sep = '-')
|
118
|
+
ActiveSupport::Inflector.parameterize(self, sep)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Creates the name of a table like Rails does for models to table names. This method
|
122
|
+
# uses the +pluralize+ method on the last word in the string.
|
123
|
+
#
|
124
|
+
# "RawScaledScorer".tableize # => "raw_scaled_scorers"
|
125
|
+
# "egg_and_ham".tableize # => "egg_and_hams"
|
126
|
+
# "fancyCategory".tableize # => "fancy_categories"
|
127
|
+
def tableize
|
128
|
+
ActiveSupport::Inflector.tableize(self)
|
129
|
+
end
|
130
|
+
|
131
|
+
# Create a class name from a plural table name like Rails does for table names to models.
|
132
|
+
# Note that this returns a string and not a class. (To convert to an actual class
|
133
|
+
# follow +classify+ with +constantize+.)
|
134
|
+
#
|
135
|
+
# "egg_and_hams".classify # => "EggAndHam"
|
136
|
+
# "posts".classify # => "Post"
|
137
|
+
#
|
138
|
+
# Singular names are not handled correctly.
|
139
|
+
#
|
140
|
+
# "business".classify # => "Busines"
|
141
|
+
def classify
|
142
|
+
ActiveSupport::Inflector.classify(self)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Capitalizes the first word, turns underscores into spaces, and strips '_id'.
|
146
|
+
# Like +titleize+, this is meant for creating pretty output.
|
147
|
+
#
|
148
|
+
# "employee_salary" # => "Employee salary"
|
149
|
+
# "author_id" # => "Author"
|
150
|
+
def humanize
|
151
|
+
ActiveSupport::Inflector.humanize(self)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Creates a foreign key name from a class name.
|
155
|
+
# +separate_class_name_and_id_with_underscore+ sets whether
|
156
|
+
# the method should put '_' between the name and 'id'.
|
157
|
+
#
|
158
|
+
# Examples
|
159
|
+
# "Message".foreign_key # => "message_id"
|
160
|
+
# "Message".foreign_key(false) # => "messageid"
|
161
|
+
# "Admin::Post".foreign_key # => "post_id"
|
162
|
+
def foreign_key(separate_class_name_and_id_with_underscore = true)
|
163
|
+
ActiveSupport::Inflector.foreign_key(self, separate_class_name_and_id_with_underscore)
|
164
|
+
end
|
165
|
+
end
|
data/lib/view.rb
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
module Garterbelt
|
2
|
+
class View
|
3
|
+
include RuPol::Swimsuit
|
4
|
+
|
5
|
+
attr_accessor :output, :buffer, :level, :escape
|
6
|
+
attr_reader :curator
|
7
|
+
|
8
|
+
def initialize(opts={})
|
9
|
+
self.buffer = []
|
10
|
+
self.level = (opts.delete(:level) || 0)
|
11
|
+
self.output = ""
|
12
|
+
self.escape = true
|
13
|
+
|
14
|
+
self.curator = opts.delete(:curator) || self
|
15
|
+
|
16
|
+
params = self.class.default_variables.merge(opts)
|
17
|
+
keys = params.keys
|
18
|
+
|
19
|
+
unless ((self.class.required || []) - keys).empty?
|
20
|
+
raise ArgumentError, "#{(self.class.required - keys).inspect} required as an initialization option"
|
21
|
+
end
|
22
|
+
|
23
|
+
if self.class.selective_require && keys != self.class.required
|
24
|
+
raise ArgumentError, "Allowed initalization options are only #{self.class.required.inspect}"
|
25
|
+
end
|
26
|
+
|
27
|
+
params.each do |key, value|
|
28
|
+
self.class.add_accssor(key) unless respond_to?(key)
|
29
|
+
send("#{key}=", value)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def curator=(parent_view)
|
34
|
+
@curator = parent_view
|
35
|
+
if parent_view != self
|
36
|
+
self.buffer = parent_view.buffer
|
37
|
+
self.level = parent_view.level
|
38
|
+
self.output = parent_view.output
|
39
|
+
self.escape = parent_view.escape
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def curated?
|
44
|
+
curator === self
|
45
|
+
end
|
46
|
+
|
47
|
+
# VARIABLE ACCESS -----------------------------
|
48
|
+
class << self
|
49
|
+
attr_accessor :required, :selective_require
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.add_accssor key
|
53
|
+
key = key.to_s
|
54
|
+
return if accessories.include?(key)
|
55
|
+
if instance_methods.include?(key)
|
56
|
+
raise ArgumentError, ":#{key} cannot be a required variable because it maps to an existing method"
|
57
|
+
end
|
58
|
+
|
59
|
+
accessories << key.to_s
|
60
|
+
attr_accessor key
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.accessories
|
64
|
+
@accessories ||= superclass? ? superclass.accessories.dup : []
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.superclass?
|
68
|
+
@superclassed ||= superclass.respond_to?( :required )
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.super_required
|
72
|
+
superclass? ? superclass.required || [] : []
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.default_variables
|
76
|
+
@default_variables ||= superclass? ? superclass.default_variables.dup : Hash.new
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.requires *args
|
80
|
+
if args.last.is_a?(Hash)
|
81
|
+
self.default_variables.merge!(args.pop)
|
82
|
+
args += default_variables.keys.map{ |x| x.to_sym }
|
83
|
+
end
|
84
|
+
|
85
|
+
args = super_required + args
|
86
|
+
self.required = args.uniq
|
87
|
+
build_accessors
|
88
|
+
required
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.requires_only(*args)
|
92
|
+
self.selective_require = true
|
93
|
+
requires(*args)
|
94
|
+
end
|
95
|
+
|
96
|
+
class << self
|
97
|
+
alias :needs :requires
|
98
|
+
alias :needs_only :requires_only
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.build_accessors
|
102
|
+
required.each do |m|
|
103
|
+
add_accssor m
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# TAG HELPERS -----------------------
|
108
|
+
|
109
|
+
def add_to_buffer(renderer)
|
110
|
+
buffer << renderer
|
111
|
+
renderer
|
112
|
+
end
|
113
|
+
|
114
|
+
def tag(type, *args, &block)
|
115
|
+
add_to_buffer ContentTag.new(parse_tag_arguments(type, args), &block)
|
116
|
+
end
|
117
|
+
|
118
|
+
def closed_tag(type, *args)
|
119
|
+
add_to_buffer ClosedTag.new(parse_tag_arguments(type, args))
|
120
|
+
end
|
121
|
+
|
122
|
+
def non_escape_tag(*args, &block)
|
123
|
+
if escape
|
124
|
+
curator.escape = false
|
125
|
+
t = tag(*args, &block)
|
126
|
+
curator.escape = true
|
127
|
+
t
|
128
|
+
else
|
129
|
+
tag(*args, &block)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def text(content)
|
134
|
+
add_to_buffer Text.new(:view => curator, :content => content)
|
135
|
+
end
|
136
|
+
|
137
|
+
alias :h :text
|
138
|
+
|
139
|
+
def raw_text(content)
|
140
|
+
if escape
|
141
|
+
curator.escape = false
|
142
|
+
t = text(content)
|
143
|
+
curator.escape = true
|
144
|
+
t
|
145
|
+
else
|
146
|
+
text(content)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
alias :raw :raw_text
|
150
|
+
alias :rawtext :raw_text
|
151
|
+
|
152
|
+
def comment(content)
|
153
|
+
add_to_buffer Comment.new(:view => curator, :content => content)
|
154
|
+
end
|
155
|
+
|
156
|
+
def doctype(type=:transitional)
|
157
|
+
add_to_buffer Doctype.new(:view => curator, :type => type)
|
158
|
+
end
|
159
|
+
|
160
|
+
def xml(opts={})
|
161
|
+
opts = {:version => 1.0, :encoding => 'utf-8'}.merge(opts)
|
162
|
+
add_to_buffer Xml.new(parse_tag_arguments(:xml, [opts]))
|
163
|
+
end
|
164
|
+
|
165
|
+
def parse_tag_arguments(type, args)
|
166
|
+
opts = {:type => type, :view => curator}
|
167
|
+
if args.size == 2
|
168
|
+
opts[:content] = args.shift
|
169
|
+
opts[:attributes] = args.first
|
170
|
+
else
|
171
|
+
if args.first.is_a?(Hash)
|
172
|
+
opts[:attributes] = args.first
|
173
|
+
else
|
174
|
+
opts[:content] = args.first
|
175
|
+
end
|
176
|
+
end
|
177
|
+
opts
|
178
|
+
end
|
179
|
+
|
180
|
+
CONTENT_TAGS = [
|
181
|
+
'a', 'abbr', 'acronym', 'address',
|
182
|
+
'b', 'bdo', 'big', 'blockquote', 'body', 'button',
|
183
|
+
'caption', 'center', 'cite', 'colgroup',
|
184
|
+
'dd', 'del', 'dfn', 'div', 'dl', 'dt', 'em',
|
185
|
+
'embed',
|
186
|
+
'fieldset', 'form', 'frameset',
|
187
|
+
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'html', 'i',
|
188
|
+
'iframe', 'ins', 'kbd', 'label', 'legend', 'li', 'map',
|
189
|
+
'noframes', 'noscript',
|
190
|
+
'object', 'ol', 'optgroup', 'option', 'p', 'param',
|
191
|
+
'q', 's',
|
192
|
+
'samp', 'script', 'select', 'small', 'span',
|
193
|
+
'strong', 'style', 'sub', 'sup',
|
194
|
+
'table', 'tbody', 'td', 'textarea', 'tfoot',
|
195
|
+
'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var'
|
196
|
+
]
|
197
|
+
CONTENT_TAGS.each do |type|
|
198
|
+
class_eval <<-RUBY
|
199
|
+
def #{type}(*args, &block)
|
200
|
+
tag(:#{type}, *args, &block)
|
201
|
+
end
|
202
|
+
RUBY
|
203
|
+
end
|
204
|
+
|
205
|
+
NON_ESCAPE_TAGS = ['code', 'pre']
|
206
|
+
NON_ESCAPE_TAGS.each do |type|
|
207
|
+
class_eval <<-RUBY
|
208
|
+
def #{type}(*args, &block)
|
209
|
+
non_escape_tag(:#{type}, *args, &block)
|
210
|
+
end
|
211
|
+
RUBY
|
212
|
+
end
|
213
|
+
|
214
|
+
CLOSED_TAGS = ['area', 'br', 'col', 'frame', 'hr', 'img', 'input']
|
215
|
+
CLOSED_TAGS.each do |type|
|
216
|
+
class_eval <<-RUBY
|
217
|
+
def #{type}(*args)
|
218
|
+
closed_tag(:#{type}, *args)
|
219
|
+
end
|
220
|
+
RUBY
|
221
|
+
end
|
222
|
+
|
223
|
+
HEAD_TAGS = ['base', 'meta', 'link']
|
224
|
+
HEAD_TAGS.each do |type|
|
225
|
+
class_eval <<-RUBY
|
226
|
+
def _#{type}(*args)
|
227
|
+
closed_tag(:#{type}, *args)
|
228
|
+
end
|
229
|
+
RUBY
|
230
|
+
end
|
231
|
+
|
232
|
+
def page_title(*args, &block)
|
233
|
+
tag(:title, *args, &block)
|
234
|
+
end
|
235
|
+
|
236
|
+
def stylesheet_link(path)
|
237
|
+
_link(:rel => "stylesheet", 'type' => "text/css", :href => "#{path}.css")
|
238
|
+
end
|
239
|
+
|
240
|
+
def javascript_link(path)
|
241
|
+
script(:src => "#{path}.js", 'type' => "text/javascript")
|
242
|
+
end
|
243
|
+
|
244
|
+
# RENDERING -------------------------
|
245
|
+
|
246
|
+
def content
|
247
|
+
raise NotImplementedError, "Implement #content in #{self.class}!"
|
248
|
+
end
|
249
|
+
|
250
|
+
def render(content_method = :content)
|
251
|
+
self.output = "" if curated?
|
252
|
+
if content_method == :content
|
253
|
+
content
|
254
|
+
else
|
255
|
+
send(content_method)
|
256
|
+
end
|
257
|
+
render_buffer
|
258
|
+
output
|
259
|
+
end
|
260
|
+
|
261
|
+
alias :to_s :render
|
262
|
+
alias :to_html :render
|
263
|
+
|
264
|
+
def render_buffer
|
265
|
+
array = buffer.dup
|
266
|
+
buffer.clear
|
267
|
+
array.each do |item|
|
268
|
+
if item.respond_to?(:render)
|
269
|
+
item.render
|
270
|
+
item.recycle
|
271
|
+
else
|
272
|
+
output << item.to_s
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def self.render(opts={})
|
278
|
+
content_method = opts[:method]
|
279
|
+
view = new
|
280
|
+
output = content_method ? view.render(content_method) : view.render
|
281
|
+
view.recycle
|
282
|
+
output
|
283
|
+
end
|
284
|
+
|
285
|
+
def partial(*args, &block)
|
286
|
+
if (klass = args.first).is_a?(Class)
|
287
|
+
args.shift
|
288
|
+
view = klass.new(*args)
|
289
|
+
else
|
290
|
+
view = args.first
|
291
|
+
end
|
292
|
+
view.curator = curator
|
293
|
+
self.buffer << view
|
294
|
+
view
|
295
|
+
end
|
296
|
+
|
297
|
+
alias :widget :partial
|
298
|
+
|
299
|
+
# CACHING ---------------------------
|
300
|
+
|
301
|
+
class << self
|
302
|
+
attr_writer :cache_store_key, :cache_key_base
|
303
|
+
end
|
304
|
+
|
305
|
+
attr_writer :cache_store_key, :cache_key_base
|
306
|
+
|
307
|
+
|
308
|
+
def self.cache_store_key
|
309
|
+
@cache_store_key ||= :default
|
310
|
+
end
|
311
|
+
|
312
|
+
def cache_store_key
|
313
|
+
@cache_store_key ||= self.class.cache_store_key
|
314
|
+
end
|
315
|
+
|
316
|
+
def cache_store
|
317
|
+
@cache ||= Garterbelt.cache(cache_store_key)
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.cache_key_base
|
321
|
+
@cache_key_base ||= self.to_s.underscore
|
322
|
+
end
|
323
|
+
|
324
|
+
def cache_key_base
|
325
|
+
@cache_key_base ||= self.class.cache_key_base
|
326
|
+
end
|
327
|
+
|
328
|
+
CACHE_DETAIL_DEFAULT = 'default'
|
329
|
+
|
330
|
+
def cache_key(detail = CACHE_DETAIL_DEFAULT)
|
331
|
+
detail ||= CACHE_DETAIL_DEFAULT
|
332
|
+
"#{cache_key_base}_#{detail}"
|
333
|
+
end
|
334
|
+
|
335
|
+
def cache(key, &block)
|
336
|
+
renderer = Cache.new(:view => self, :key => cache_key(key), &block)
|
337
|
+
buffer << renderer
|
338
|
+
renderer
|
339
|
+
end
|
340
|
+
end
|
341
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class ErectorTemplate < Erector::Widget
|
2
|
+
needs :user, :flash => nil
|
3
|
+
|
4
|
+
def content
|
5
|
+
html do
|
6
|
+
head do
|
7
|
+
title "Benchmarking Templates"
|
8
|
+
link :rel => 'stylesheet', :href => '/stylesheet/application.css'
|
9
|
+
link :rel => 'stylesheet', :href => '/stylesheet/ie.css'
|
10
|
+
end
|
11
|
+
|
12
|
+
body :class => self.class.to_s.underscore do
|
13
|
+
div :id => :wrapper, :class => 'line' do
|
14
|
+
if flash
|
15
|
+
div :id => 'flash', :class => 'inner' do
|
16
|
+
text flash
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
dl :class => ['inner', 'user_info'] do
|
21
|
+
dt 'username'
|
22
|
+
dd user.username
|
23
|
+
|
24
|
+
dt 'email'
|
25
|
+
dd user.email
|
26
|
+
|
27
|
+
dt 'name'
|
28
|
+
dd user.name
|
29
|
+
|
30
|
+
dt 'number of assigned tasks'
|
31
|
+
dd rand(20)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class GarterbeltTemplate < Garterbelt::View
|
2
|
+
needs :user, :flash => nil
|
3
|
+
|
4
|
+
def content
|
5
|
+
html do
|
6
|
+
head do
|
7
|
+
title "Benchmarking Templates"
|
8
|
+
link :rel => 'stylesheet', :href => '/stylesheet/application.css'
|
9
|
+
link :rel => 'stylesheet', :href => '/stylesheet/ie.css'
|
10
|
+
end
|
11
|
+
|
12
|
+
body :class => self.class.to_s.underscore do
|
13
|
+
div :id => :wrapper, :class => 'line' do
|
14
|
+
if flash
|
15
|
+
div :id => 'flash', :class => 'inner' do
|
16
|
+
text flash
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
dl :class => ['inner', 'user_info'] do
|
21
|
+
dt 'username'
|
22
|
+
dd user.username
|
23
|
+
|
24
|
+
dt 'email'
|
25
|
+
dd user.email
|
26
|
+
|
27
|
+
dt 'name'
|
28
|
+
dd user.name
|
29
|
+
|
30
|
+
dt 'number of assigned tasks'
|
31
|
+
dd rand(20)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require "rbench"
|
5
|
+
require 'hashie'
|
6
|
+
|
7
|
+
require 'erector'
|
8
|
+
require File.dirname(__FILE__) + "/../../lib/garterbelt"
|
9
|
+
|
10
|
+
require File.dirname(__FILE__) + '/templates/stocking'
|
11
|
+
require File.dirname(__FILE__) + '/templates/erector'
|
12
|
+
|
13
|
+
TIMES = 500_000
|
14
|
+
|
15
|
+
RBench.run(TIMES) do
|
16
|
+
LoungeTemplate.max_pool_size TIMES/10
|
17
|
+
column :garterbelt
|
18
|
+
column :erector
|
19
|
+
|
20
|
+
report "Simple Page Rendering" do
|
21
|
+
user = Hashie::Mash.new(:username => 'baccigalupi', :email => 'baccigalupi@example.com', :name => 'Kane Baccigalupi')
|
22
|
+
erector { ErectorTemplate.new(:user => user) }
|
23
|
+
garterbelt { GarterbeltTemplate.new(:user => user) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# 4/11/2011, version 0.0.1, standard pooling
|
28
|
+
# GARTERBELT = pooling at standard 1000 instances
|
29
|
+
# GARTERBELT_2 = pooling at 10% of sample time
|
30
|
+
|
31
|
+
# 10_000 times
|
32
|
+
# GARTERBELT | ERECTOR |
|
33
|
+
# ---------------------------------------------------------------------
|
34
|
+
# Simple Page Rendering 0.164 | 0.205 | 20% faster
|
35
|
+
|
36
|
+
# 100_000 times
|
37
|
+
# GARTERBELT | GARTERBELT_2 | ERECTOR |
|
38
|
+
# ---------------------------------------------------------------------
|
39
|
+
# Simple Page Rendering 1.857 | 1.828 | 1.932 | 3.8-5.3%/ faster
|
40
|
+
|
41
|
+
# 200_000 times
|
42
|
+
# GARTERBELT | GARTERBELT_2 | ERECTOR |
|
43
|
+
# ---------------------------------------------------------------------
|
44
|
+
# Simple Page Rendering 3.743 | 3.660 | 3.846 | 2.7-4.8% faster
|
45
|
+
|
46
|
+
# 500_000 times
|
47
|
+
# GARTERBELT | GARTERBELT_2 | ERECTOR |
|
48
|
+
# ---------------------------------------------------------------------
|
49
|
+
# Simple Page Rendering 9.420 | 9.422 | 9.637 | 2.3-2.2% faster
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Garterbelt do
|
4
|
+
describe 'cache module level methods' do
|
5
|
+
describe '#cache_hash' do
|
6
|
+
it 'creates a default hash when accessed the first time' do
|
7
|
+
cache_hash = Garterbelt.cache_hash.dup
|
8
|
+
cache_hash.class.should == Hash
|
9
|
+
cache_hash[:default].is_a?(Moneta::Memory).should == true
|
10
|
+
cache_hash.keys.size.should == 1
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'gives direct access to the hash' do
|
14
|
+
Garterbelt.cache_hash[:foo] = 'foo'
|
15
|
+
Garterbelt.cache_hash[:foo].should == 'foo'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#cache=' do
|
20
|
+
it 'creates a Moneta cache' do
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
describe 'options' do
|
25
|
+
it 'creates a memory Moneta cache correctly'
|
26
|
+
it 'creates a file system Moneta cache'
|
27
|
+
it 'creates alternative types as requested'
|
28
|
+
end
|
29
|
+
it 'sets the default key when not provided a argument'
|
30
|
+
it 'sets other keys when not '
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#cache' do
|
34
|
+
it 'returns the :default if no arugment is received' do
|
35
|
+
Garterbelt.cache.should == Garterbelt.cache_hash[:default]
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns an alternative cache by key' do
|
39
|
+
Garterbelt.cache_hash[:foo] = 'foo'
|
40
|
+
Garterbelt.cache(:foo).should == 'foo'
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'raises an error when accessing via a key that has no been configured' do
|
44
|
+
Garterbelt.cache_hash[:bar].should be_nil
|
45
|
+
lambda { Garterbelt.cache(:bar) }.should raise_error( "Cache :bar has not yet been configured" )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|