pirj-sinatra-contrib 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +135 -0
  3. data/Rakefile +61 -0
  4. data/ideas.md +29 -0
  5. data/lib/sinatra/capture.rb +42 -0
  6. data/lib/sinatra/config_file.rb +151 -0
  7. data/lib/sinatra/content_for.rb +111 -0
  8. data/lib/sinatra/contrib.rb +39 -0
  9. data/lib/sinatra/contrib/all.rb +2 -0
  10. data/lib/sinatra/contrib/setup.rb +53 -0
  11. data/lib/sinatra/contrib/version.rb +45 -0
  12. data/lib/sinatra/decompile.rb +113 -0
  13. data/lib/sinatra/engine_tracking.rb +96 -0
  14. data/lib/sinatra/extension.rb +95 -0
  15. data/lib/sinatra/json.rb +134 -0
  16. data/lib/sinatra/link_header.rb +132 -0
  17. data/lib/sinatra/namespace.rb +282 -0
  18. data/lib/sinatra/reloader.rb +384 -0
  19. data/lib/sinatra/respond_with.rb +245 -0
  20. data/lib/sinatra/streaming.rb +267 -0
  21. data/lib/sinatra/test_helpers.rb +87 -0
  22. data/sinatra-contrib.gemspec +121 -0
  23. data/spec/capture_spec.rb +80 -0
  24. data/spec/config_file/key_value.yml +6 -0
  25. data/spec/config_file/missing_env.yml +4 -0
  26. data/spec/config_file/with_envs.yml +7 -0
  27. data/spec/config_file/with_nested_envs.yml +11 -0
  28. data/spec/config_file_spec.rb +44 -0
  29. data/spec/content_for/different_key.erb +1 -0
  30. data/spec/content_for/different_key.erubis +1 -0
  31. data/spec/content_for/different_key.haml +2 -0
  32. data/spec/content_for/different_key.slim +2 -0
  33. data/spec/content_for/layout.erb +1 -0
  34. data/spec/content_for/layout.erubis +1 -0
  35. data/spec/content_for/layout.haml +1 -0
  36. data/spec/content_for/layout.slim +1 -0
  37. data/spec/content_for/multiple_blocks.erb +4 -0
  38. data/spec/content_for/multiple_blocks.erubis +4 -0
  39. data/spec/content_for/multiple_blocks.haml +8 -0
  40. data/spec/content_for/multiple_blocks.slim +8 -0
  41. data/spec/content_for/multiple_yields.erb +3 -0
  42. data/spec/content_for/multiple_yields.erubis +3 -0
  43. data/spec/content_for/multiple_yields.haml +3 -0
  44. data/spec/content_for/multiple_yields.slim +3 -0
  45. data/spec/content_for/passes_values.erb +1 -0
  46. data/spec/content_for/passes_values.erubis +1 -0
  47. data/spec/content_for/passes_values.haml +1 -0
  48. data/spec/content_for/passes_values.slim +1 -0
  49. data/spec/content_for/same_key.erb +1 -0
  50. data/spec/content_for/same_key.erubis +1 -0
  51. data/spec/content_for/same_key.haml +2 -0
  52. data/spec/content_for/same_key.slim +2 -0
  53. data/spec/content_for/takes_values.erb +1 -0
  54. data/spec/content_for/takes_values.erubis +1 -0
  55. data/spec/content_for/takes_values.haml +3 -0
  56. data/spec/content_for/takes_values.slim +3 -0
  57. data/spec/content_for_spec.rb +201 -0
  58. data/spec/decompile_spec.rb +44 -0
  59. data/spec/extension_spec.rb +33 -0
  60. data/spec/json_spec.rb +115 -0
  61. data/spec/link_header_spec.rb +100 -0
  62. data/spec/namespace/foo.erb +1 -0
  63. data/spec/namespace/nested/foo.erb +1 -0
  64. data/spec/namespace_spec.rb +623 -0
  65. data/spec/okjson.rb +581 -0
  66. data/spec/reloader/app.rb.erb +40 -0
  67. data/spec/reloader_spec.rb +441 -0
  68. data/spec/respond_with/bar.erb +1 -0
  69. data/spec/respond_with/bar.json.erb +1 -0
  70. data/spec/respond_with/foo.html.erb +1 -0
  71. data/spec/respond_with/not_html.sass +2 -0
  72. data/spec/respond_with_spec.rb +289 -0
  73. data/spec/spec_helper.rb +6 -0
  74. data/spec/streaming_spec.rb +436 -0
  75. metadata +252 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2011 Nicolas Sanguinetti, entp.com, Konstantin Haase
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ 'Software'), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
18
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
20
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,135 @@
1
+ Collection of common Sinatra extensions, semi-officially supported.
2
+
3
+ # Goals
4
+
5
+ * For every future Sinatra release, have at least one fully compatible release
6
+ * High code quality, high test coverage
7
+ * Include plugins people usually ask for a lot
8
+
9
+ # TODO
10
+
11
+ * Write documentation, integrate into Sinatra website
12
+ * Finish imports and rewrites
13
+ * Wrap up first release
14
+ * Find contributors (both code and docs)
15
+
16
+ # Included extensions
17
+
18
+ ## Common Extensions
19
+
20
+ These are common extension which will not add significant overhead or change any
21
+ behavior of already existing APIs. They do not add any dependencies not already
22
+ installed with this gem.
23
+
24
+ Currently included:
25
+
26
+ * `sinatra/capture`: Let's you capture the content of blocks in templates.
27
+
28
+ * `sinatra/config_file`: Allows loading configuration from yaml files.
29
+
30
+ * `sinatra/content_for`: Adds Rails-style `content_for` helpers to Haml, Erb,
31
+ Erubis and Slim.
32
+
33
+ * `sinatra/cookies`: A `cookies` helper for reading and writing cookies.
34
+
35
+ * `sinatra/engine_tracking`: Adds methods like `haml?` that allow helper
36
+ methods to check whether they are called from within a template.
37
+
38
+ * `sinatra/json`: Adds a `#json` helper method to return JSON documents.
39
+
40
+ * `sinatra/link_header`: Helpers for generating `link` HTML tags and
41
+ corresponding `Link` HTTP headers. Adds `link`, `stylesheet` and `prefetch`
42
+ helper methods.
43
+
44
+ * `sinatra/multi_route`: Adds ability to define one route block for multiple
45
+ routes and multiple or custom HTTP verbs.
46
+
47
+ * `sinatra/namespace`: Adds namespace support to Sinatra.
48
+
49
+ * `sinatra/respond_with`: Choose action and/or template depending automatically
50
+ depending on the incoming request. Adds helpers `respond_to` and
51
+ `respond_with`.
52
+
53
+
54
+ ## Custom Extensions
55
+
56
+ These extensions may add additional dependencies and enhance the behavior of the
57
+ existing APIs.
58
+
59
+ Currently included:
60
+
61
+ * `sinatra/decompile`: Recreates path patterns from Sinatra's internal data
62
+ structures (used by other extensions).
63
+
64
+ * `sinatra/reloader`: Automatically reloads Ruby files on code changes.
65
+
66
+ ## Other Tools
67
+
68
+ * `sinatra/extension`: Mixin for writing your own Sinatra extensions.
69
+
70
+ * `sinatra/test_helpers`: Helper methods to ease testing your Sinatra
71
+ application. Partly extracted from Sinatra. Testing framework agnostic
72
+
73
+ # Usage
74
+
75
+ ## Classic Style
76
+
77
+ A single extension (example: sinatra-content-for):
78
+
79
+ ``` ruby
80
+ require 'sinatra'
81
+ require 'sinatra/content_for'
82
+ ```
83
+
84
+ Common extensions:
85
+
86
+ ``` ruby
87
+ require 'sinatra'
88
+ require 'sinatra/contrib'
89
+ ```
90
+
91
+ All extensions:
92
+
93
+ ``` ruby
94
+ require 'sinatra'
95
+ require 'sinatra/contrib/all'
96
+ ```
97
+
98
+ ## Modular Style
99
+
100
+ A single extension (example: sinatra-content-for):
101
+
102
+ ``` ruby
103
+ require 'sinatra/base'
104
+ require 'sinatra/content_for'
105
+ require 'sinatra/namespace'
106
+
107
+ class MyApp < Sinatra::Base
108
+ # Note: Some modules are extensions, some helpers, see the specific
109
+ # documentation or the source
110
+ helpers Sinatra::ContentFor
111
+ register Sinatra::Namespace
112
+ end
113
+ ```
114
+
115
+ Common extensions:
116
+
117
+ ``` ruby
118
+ require 'sinatra/base'
119
+ require 'sinatra/contrib'
120
+
121
+ class MyApp < Sinatra::Base
122
+ register Sinatra::Contrib
123
+ end
124
+ ```
125
+
126
+ All extensions:
127
+
128
+ ``` ruby
129
+ require 'sinatra/base'
130
+ require 'sinatra/contrib'
131
+
132
+ class MyApp < Sinatra::Base
133
+ register Sinatra::Contrib
134
+ end
135
+ ```
@@ -0,0 +1,61 @@
1
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
2
+ require 'open-uri'
3
+ require 'yaml'
4
+
5
+ desc "run specs"
6
+ task(:spec) { ruby '-S rspec spec -c' }
7
+ task(:test => :spec)
8
+ task(:default => :spec)
9
+
10
+ namespace :doc do
11
+ task :readmes do
12
+ Dir.glob 'lib/sinatra/*.rb' do |file|
13
+ excluded_files = %w[lib/sinatra/contrib.rb lib/sinatra/capture.rb lib/sinatra/engine_tracking.rb]
14
+ next if excluded_files.include?(file)
15
+ doc = File.read(file)[/^module Sinatra(\n)+( #[^\n]*\n)*/m].scan(/^ *#(?!#) ?(.*)\n/).join("\n")
16
+ file = "doc/#{file[4..-4].tr("/_", "-")}.rdoc"
17
+ Dir.mkdir "doc" unless File.directory? "doc"
18
+ puts "writing #{file}"
19
+ File.open(file, "w") { |f| f << doc }
20
+ end
21
+ end
22
+
23
+ task :all => [:readmes]
24
+ end
25
+
26
+ desc "generate documentation"
27
+ task :doc => 'doc:all'
28
+
29
+ desc "generate gemspec"
30
+ task 'sinatra-contrib.gemspec' do
31
+ require 'sinatra/contrib/version'
32
+ content = File.read 'sinatra-contrib.gemspec'
33
+
34
+ fields = {
35
+ :authors => `git shortlog -sn`.scan(/[^\d\s].*/),
36
+ :email => `git shortlog -sne`.scan(/[^<]+@[^>]+/),
37
+ :files => `git ls-files`.split("\n").reject { |f| f =~ /^(\.|Gemfile)/ }
38
+ }
39
+
40
+ fields.each do |field, values|
41
+ updated = " s.#{field} = ["
42
+ updated << values.map { |v| "\n %p" % v }.join(',')
43
+ updated << "\n ]"
44
+ content.sub!(/ s\.#{field} = \[\n( .*\n)* \]/, updated)
45
+ end
46
+
47
+ content.sub! /(s\.version.*=\s+).*/, "\\1\"#{Sinatra::Contrib::VERSION}\""
48
+ File.open('sinatra-contrib.gemspec', 'w') { |f| f << content }
49
+ end
50
+
51
+ task :gemspec => 'sinatra-contrib.gemspec'
52
+
53
+ desc 'update travis config to correspond to sinatra'
54
+ task :travis, [:branch] do |t, a|
55
+ a.with_defaults :branch => :master
56
+ data = YAML.load open("https://raw.github.com/sinatra/sinatra/#{a.branch}/.travis.yml")
57
+ data["notifications"]["recipients"] << "ohhgabriel@gmail.com"
58
+ File.open('.travis.yml', 'w') { |f| f << data.to_yaml }
59
+ system 'git add .travis.yml && git diff --cached .travis.yml'
60
+ end
61
+
@@ -0,0 +1,29 @@
1
+ * Extension that does something like this:
2
+
3
+ def build(*)
4
+ if settings.memcached?
5
+ use Rack::Cache, :backend => :memcached
6
+ use Rack::Session::Memcached
7
+ # ...
8
+ end
9
+ super
10
+ end
11
+
12
+ * `sinatra-smart-cache`: update cache header only if arguments are more
13
+ restrictive than curent value, set caching headers that way for most helper
14
+ methods (i.e. `sass` or `send_file`)
15
+
16
+ * Some verbose logging extension: Log what filters, routes, error handlers,
17
+ templates, and so on is used.
18
+
19
+ * Form helpers, with forms as first class objects that accepts hashes or
20
+ something, so the form meta data can also be used to expose a JSON API or
21
+ similar, possibly defining routes (like "Sinatra's Hat"), strictly using
22
+ the ActiveModel API.
23
+
24
+ * Extend `sinatra-content-for` to support Liquid, Radius, Markaby, Nokogiri and
25
+ Builder. At least the first two probably involve patching Tilt.
26
+
27
+ * Rewrite of `sinatra-compass`?
28
+
29
+ * Helpers for HTML escaping and such.
@@ -0,0 +1,42 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/engine_tracking'
3
+ require 'backports'
4
+
5
+ module Sinatra
6
+ module Capture
7
+ include Sinatra::EngineTracking
8
+
9
+ DUMMIES = {
10
+ :haml => "!= capture_haml(*args, &block)",
11
+ :erb => "<% @capture = yield(*args) %>",
12
+ :slim => "== yield(*args)"
13
+ }
14
+
15
+ DUMMIES[:erubis] = DUMMIES[:erb]
16
+
17
+ def capture(*args, &block)
18
+ @capture = nil
19
+ if current_engine == :ruby
20
+ result = block[*args]
21
+ else
22
+ buffer = eval '_buf if defined?(_buf)', block.binding
23
+ old_buffer = buffer.dup if buffer
24
+ dummy = DUMMIES.fetch(current_engine)
25
+ options = { :layout => false, :locals => {:args => args, :block => block }}
26
+
27
+ buffer.try :clear
28
+ result = render(current_engine, dummy, options, &block)
29
+ end
30
+ result.strip.empty? && @capture ? @capture : result
31
+ ensure
32
+ buffer.try :replace, old_buffer
33
+ end
34
+
35
+ def capture_later(&block)
36
+ engine = current_engine
37
+ proc { |*a| with_engine(engine) { @capture = capture(*a, &block) }}
38
+ end
39
+ end
40
+
41
+ helpers Capture
42
+ end
@@ -0,0 +1,151 @@
1
+ require 'sinatra/base'
2
+ require 'yaml'
3
+
4
+ module Sinatra
5
+
6
+ # = Sinatra::ConfigFile
7
+ #
8
+ # <tt>Sinatra::ConfigFile</tt> is an extension that allows you to load the
9
+ # application's configuration from YAML files. It automatically detects if
10
+ # the files contains specific environment settings and it will use the
11
+ # corresponding to the current one.
12
+ #
13
+ # Within the application you can access those options through +settings+. If
14
+ # you try to get the value for a setting that hasn't been defined in the
15
+ # config file for the current environment, you will get whatever it was set
16
+ # to in the application.
17
+ #
18
+ # == Usage
19
+ #
20
+ # Once you have written your configurations to a YAML file you can tell the
21
+ # extension to load them. See below for more information about how these
22
+ # files are interpreted.
23
+ #
24
+ # For the examples, lets assume the following config.yml file:
25
+ #
26
+ # greeting: Welcome to my file configurable application
27
+ #
28
+ # === Classic Application
29
+ #
30
+ # require "sinatra"
31
+ # require "sinatra/config_file"
32
+ #
33
+ # config_file 'path/to/config.yml'
34
+ #
35
+ # get '/' do
36
+ # @greeting = settings.greeting
37
+ # haml :index
38
+ # end
39
+ #
40
+ # # The rest of your classic application code goes here...
41
+ #
42
+ # === Modular Application
43
+ #
44
+ # require "sinatra/base"
45
+ # require "sinatra/config_file"
46
+ #
47
+ # class MyApp < Sinatra::Base
48
+ # register Sinatra::ConfigFile
49
+ #
50
+ # config_file 'path/to/config.yml'
51
+ #
52
+ # get '/' do
53
+ # @greeting = settings.greeting
54
+ # haml :index
55
+ # end
56
+ #
57
+ # # The rest of your modular application code goes here...
58
+ # end
59
+ #
60
+ # === Config File Format
61
+ #
62
+ # In its most simple form this file is just a key-value list:
63
+ #
64
+ # foo: bar
65
+ # something: 42
66
+ # nested:
67
+ # a: 1
68
+ # b: 2
69
+ #
70
+ # But it also can provide specific environment configuration. There are two
71
+ # ways to do that: at the file level and at the setting level. They are
72
+ # illustrated, repsectively, as follows:
73
+ #
74
+ # development:
75
+ # foo: development
76
+ # bar: bar
77
+ # test:
78
+ # foo: test
79
+ # bar: bar
80
+ # production:
81
+ # foo: production
82
+ # bar: bar
83
+ #
84
+ # and
85
+ #
86
+ # foo:
87
+ # development: development
88
+ # test: test
89
+ # production: production
90
+ # bar: bar
91
+ #
92
+ # In either case, <tt>settings.foo</tt> will return the environment name, and
93
+ # <tt>settings.bar</tt> will return <tt>"bar"</tt>.
94
+ #
95
+ # Be aware that if you have a different environment, besides development,
96
+ # test and production, you will also need to adjust the +environments+
97
+ # setting. For instance, when you also have a staging environment:
98
+ #
99
+ # set :environments, %w{development test production staging}
100
+ #
101
+ module ConfigFile
102
+
103
+ # When the extension is registered sets the +environments+ setting to the
104
+ # traditional environments: development, test and production.
105
+ def self.registered(base)
106
+ base.set :environments, %w[test production development]
107
+ end
108
+
109
+ # Loads the configuration from the YAML files whose +paths+ are passed as
110
+ # arguments, filtering the settings for the current environment. Note that
111
+ # these +paths+ can actually be globs.
112
+ def config_file(*paths)
113
+ Dir.chdir(root || '.') do
114
+ paths.each do |pattern|
115
+ Dir.glob(pattern) do |file|
116
+ $stderr.puts "loading config file '#{file}'" if logging?
117
+ yaml = config_for_env(YAML.load_file(file)) || {}
118
+ yaml.each_pair do |key, value|
119
+ for_env = config_for_env(value)
120
+ set key, for_env unless value and for_env.nil? and respond_to? key
121
+ end
122
+ end
123
+ end
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ # Given a +hash+ with some application configuration it returns the
130
+ # settings applicable to the current environment. Note that this can only
131
+ # be done when all the keys of +hash+ are environment names included in the
132
+ # +environments+ setting (which is an Array of Strings). Also, the
133
+ # returned config is a indifferently accessible Hash, which means that you
134
+ # can get its values using Strings or Symbols as keys.
135
+ def config_for_env(hash)
136
+ if hash.respond_to? :keys and hash.keys.all? { |k| environments.include? k.to_s }
137
+ hash = hash[environment.to_s] || hash[environment.to_sym]
138
+ end
139
+
140
+ if hash.respond_to? :to_hash
141
+ indifferent_hash = Hash.new {|hash,key| hash[key.to_s] if Symbol === key }
142
+ indifferent_hash.merge hash.to_hash
143
+ else
144
+ hash
145
+ end
146
+ end
147
+ end
148
+
149
+ register ConfigFile
150
+ Delegator.delegate :config_file
151
+ end
@@ -0,0 +1,111 @@
1
+ require 'sinatra/base'
2
+ require 'sinatra/capture'
3
+
4
+ module Sinatra
5
+
6
+ # = Sinatra::ContentFor
7
+ #
8
+ # <tt>Sinatra::ContentFor</tt> is a set of helpers that allows you to capture
9
+ # blocks inside views to be rendered later during the request. The most
10
+ # common use is to populate different parts of your layout from your view.
11
+ #
12
+ # The currently supported engines are: Erb, Erubis, Haml and Slim.
13
+ #
14
+ # == Usage
15
+ #
16
+ # You call +content_for+, generally from a view, to capture a block of markup
17
+ # giving it an identifier:
18
+ #
19
+ # # index.erb
20
+ # <% content_for :some_key do %>
21
+ # <chunk of="html">...</chunk>
22
+ # <% end %>
23
+ #
24
+ # Then, you call +yield_content+ with that identifier, generally from a
25
+ # layout, to render the captured block:
26
+ #
27
+ # # layout.erb
28
+ # <%= yield_content :some_key %>
29
+ #
30
+ # === Classic Application
31
+ #
32
+ # To use the helpers in a classic application all you need to do is require
33
+ # them:
34
+ #
35
+ # require "sinatra"
36
+ # require "sinatra/content_for"
37
+ #
38
+ # # Your classic application code goes here...
39
+ #
40
+ # === Modular Application
41
+ #
42
+ # To use the helpers in a modular application you need to require them, and
43
+ # then, tell the application you will use them:
44
+ #
45
+ # require "sinatra/base"
46
+ # require "sinatra/content_for"
47
+ #
48
+ # class MyApp < Sinatra::Base
49
+ # register Sinatra::ContentFor
50
+ #
51
+ # # The rest of your modular application code goes here...
52
+ # end
53
+ #
54
+ # == And How Is This Useful?
55
+ #
56
+ # For example, some of your views might need a few javascript tags and
57
+ # stylesheets, but you don't want to force this files in all your pages.
58
+ # Then you can put <tt><% yield_content :scripts_and_styles %></tt> on your
59
+ # layout, inside the <head> tag, and each view can call <tt>content_for</tt>
60
+ # setting the appropriate set of tags that should be added to the layout.
61
+ #
62
+ module ContentFor
63
+ include Capture
64
+
65
+ # Capture a block of content to be rendered later. For example:
66
+ #
67
+ # <% content_for :head do %>
68
+ # <script type="text/javascript" src="/foo.js"></script>
69
+ # <% end %>
70
+ #
71
+ # You can call +content_for+ multiple times with the same key
72
+ # (in the example +:head+), and when you render the blocks for
73
+ # that key all of them will be rendered, in the same order you
74
+ # captured them.
75
+ #
76
+ # Your blocks can also receive values, which are passed to them
77
+ # by <tt>yield_content</tt>
78
+ def content_for(key, &block)
79
+ content_blocks[key.to_sym] << capture_later(&block)
80
+ end
81
+
82
+ # Render the captured blocks for a given key. For example:
83
+ #
84
+ # <head>
85
+ # <title>Example</title>
86
+ # <%= yield_content :head %>
87
+ # </head>
88
+ #
89
+ # Would render everything you declared with <tt>content_for
90
+ # :head</tt> before closing the <tt><head></tt> tag.
91
+ #
92
+ # You can also pass values to the content blocks by passing them
93
+ # as arguments after the key:
94
+ #
95
+ # <%= yield_content :head, 1, 2 %>
96
+ #
97
+ # Would pass <tt>1</tt> and <tt>2</tt> to all the blocks registered
98
+ # for <tt>:head</tt>.
99
+ def yield_content(key, *args)
100
+ content_blocks[key.to_sym].map { |b| capture(*args, &b) }.join
101
+ end
102
+
103
+ private
104
+
105
+ def content_blocks
106
+ @content_blocks ||= Hash.new {|h,k| h[k] = [] }
107
+ end
108
+ end
109
+
110
+ helpers ContentFor
111
+ end