hanami-helpers 0.0.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,20 +4,24 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
  require 'hanami/helpers/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
- spec.name = "hanami-helpers"
7
+ spec.name = 'hanami-helpers'
8
8
  spec.version = Hanami::Helpers::VERSION
9
- spec.authors = ["Luca Guidi"]
10
- spec.email = ["me@lucaguidi.com"]
9
+ spec.authors = ['Luca Guidi', 'Trung Lê', 'Alfonso Uceda']
10
+ spec.email = ['me@lucaguidi.com', 'trung.le@ruby-journal.com', 'uceda73@gmail.com']
11
+ spec.summary = %q{Hanami helpers}
12
+ spec.description = %q{View helpers for Ruby applications}
13
+ spec.homepage = 'http://hanamirb.org'
14
+ spec.license = 'MIT'
11
15
 
12
- spec.summary = %q{The web, with simplicity}
13
- spec.description = %q{Hanami is a web framework for Ruby}
14
- spec.homepage = "http://hanamirb.org"
16
+ spec.files = `git ls-files -- lib/* CHANGELOG.md LICENSE.md README.md hanami-helpers.gemspec`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+ spec.required_ruby_version = '>= 2.0.0'
15
21
 
16
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
- spec.bindir = "exe"
18
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
- spec.require_paths = ["lib"]
22
+ spec.add_dependency 'hanami-utils', '~> 0.7'
20
23
 
21
- spec.add_development_dependency "bundler", "~> 1.11"
22
- spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency 'bundler', '~> 1.6'
25
+ spec.add_development_dependency 'rake', '~> 10.0'
26
+ spec.add_development_dependency 'minitest', '~> 5.5'
23
27
  end
@@ -0,0 +1 @@
1
+ require 'hanami/helpers'
@@ -1,7 +1,33 @@
1
- require "hanami/helpers/version"
1
+ require 'hanami/helpers/version'
2
+ require 'hanami/helpers/html_helper'
3
+ require 'hanami/helpers/escape_helper'
4
+ require 'hanami/helpers/routing_helper'
5
+ require 'hanami/helpers/link_to_helper'
6
+ require 'hanami/helpers/form_helper'
7
+ require 'hanami/helpers/number_formatting_helper'
2
8
 
3
9
  module Hanami
10
+ # View helpers for Ruby applications
11
+ #
12
+ # @since 0.1.0
4
13
  module Helpers
5
- # Your code goes here...
14
+ # Override for Module.included
15
+ #
16
+ # It injects all the available helpers.
17
+ #
18
+ # @since 0.1.0
19
+ # @api private
20
+ #
21
+ # @see http://www.ruby-doc.org/core/Module.html#method-i-included
22
+ def self.included(base)
23
+ base.class_eval do
24
+ include Hanami::Helpers::HtmlHelper
25
+ include Hanami::Helpers::EscapeHelper
26
+ include Hanami::Helpers::RoutingHelper
27
+ include Hanami::Helpers::LinkToHelper
28
+ include Hanami::Helpers::FormHelper
29
+ include Hanami::Helpers::NumberFormattingHelper
30
+ end
31
+ end
6
32
  end
7
33
  end
@@ -0,0 +1,271 @@
1
+ require 'hanami/utils/escape'
2
+
3
+ module Hanami
4
+ module Helpers
5
+ # Escape helpers
6
+ #
7
+ # You can include this module inside your view and
8
+ # the view will have access all methods.
9
+ #
10
+ # By including <tt>Hanami::Helpers::EscapeHelper</tt> it will inject private
11
+ # methods as markup escape utilities.
12
+ #
13
+ # @since 0.1.0
14
+ module EscapeHelper
15
+ private
16
+ # Escape the given HTML tag content.
17
+ #
18
+ # This should be used only for untrusted contents: user input.
19
+ #
20
+ # This should be used only for tag contents.
21
+ # To escape tag attributes please use <tt>Hanami::Helpers::EscapeHelper#escape_html_attribute</tt>.
22
+ #
23
+ # @param input [String] the input
24
+ #
25
+ # @return [String] the escaped string
26
+ #
27
+ # @since 0.1.0
28
+ #
29
+ # @see Hanami::Helpers::EscapeHelper#escape_html_attribute
30
+ #
31
+ # @example Basic usage
32
+ # require 'hanami/helpers/escape_helper'
33
+ #
34
+ # class MyView
35
+ # include Hanami::Helpers::EscapeHelper
36
+ #
37
+ # def good_content
38
+ # h "hello"
39
+ # end
40
+ #
41
+ # def evil_content
42
+ # h "<script>alert('xss')</script>"
43
+ # end
44
+ # end
45
+ #
46
+ # view = MyView.new
47
+ #
48
+ # view.good_content
49
+ # # => "hello"
50
+ #
51
+ # view.evil_content
52
+ # # => "&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;"
53
+ #
54
+ # @example With HTML builder
55
+ # #
56
+ # # CONTENTS ARE AUTOMATICALLY ESCAPED
57
+ # #
58
+ # require 'hanami/helpers'
59
+ #
60
+ # class MyView
61
+ # include Hanami::Helpers
62
+ #
63
+ # def evil_content
64
+ # html.div do
65
+ # "<script>alert('xss')</script>"
66
+ # end
67
+ # end
68
+ # end
69
+ #
70
+ # view = MyView.new
71
+ # view.evil_content
72
+ # # => "<div>\n&lt;script&gt;alert(&apos;xss&apos;)&lt;&#x2F;script&gt;</div>"
73
+ def escape_html(input)
74
+ Utils::Escape.html(input)
75
+ end
76
+
77
+ # @since 0.1.0
78
+ alias_method :h, :escape_html
79
+
80
+ # Escape the given HTML tag attribute.
81
+ #
82
+ # This MUST be used for escaping HTML tag attributes.
83
+ #
84
+ # This should be used only for untrusted contents: user input.
85
+ #
86
+ # This can also be used to escape tag contents, but it's slower.
87
+ # For this purpose use <tt>Hanami::Helpers::EscapeHelper#escape_html</tt>.
88
+ #
89
+ # @param input [String] the input
90
+ #
91
+ # @return [String] the escaped string
92
+ #
93
+ # @since 0.1.0
94
+ #
95
+ # @see Hanami::Helpers::EscapeHelper#escape_html
96
+ #
97
+ # @example Basic usage
98
+ # require 'hanami/helpers/escape_helper'
99
+ #
100
+ # class MyView
101
+ # include Hanami::Helpers::EscapeHelper
102
+ #
103
+ # def good_attribute
104
+ # attribute = "small"
105
+ #
106
+ # %(<span class="#{ ha(attribute) }">hello</span>
107
+ # end
108
+ #
109
+ # def evil_attribute
110
+ # attribute = %(" onclick="javascript:alert('xss')" id=")
111
+ #
112
+ # %(<span class="#{ ha(attribute) }">hello</span>
113
+ # end
114
+ # end
115
+ #
116
+ # view = MyView.new
117
+ #
118
+ # view.good_attribute
119
+ # # => %(<span class="small">hello</span>)
120
+ #
121
+ # view.evil_attribute
122
+ # # => %(<span class="&quot;&#x20;onclick&#x3d;&quot;javascript&#x3a;alert&#x28;&#x27;xss&#x27;&#x29;&quot;&#x20;id&#x3d;&quot;">hello</span>
123
+ #
124
+ # @example With HTML builder
125
+ # #
126
+ # # ATTRIBUTES AREN'T AUTOMATICALLY ESCAPED
127
+ # #
128
+ # require 'hanami/helpers'
129
+ #
130
+ # class MyView
131
+ # include Hanami::Helpers
132
+ #
133
+ # def evil_attribute
134
+ # user_input_attribute = %(" onclick="javascript:alert('xss')" id=")
135
+ #
136
+ # html.span id: 'greet', class: ha(user_input_attribute) do
137
+ # "hello"
138
+ # end
139
+ # end
140
+ # end
141
+ #
142
+ # view = MyView.new
143
+ # view.evil_attribute
144
+ # # => %(<span class="&quot;&#x20;onclick&#x3d;&quot;javascript&#x3a;alert&#x28;&#x27;xss&#x27;&#x29;&quot;&#x20;id&#x3d;&quot;">hello</span>
145
+ def escape_html_attribute(input)
146
+ Utils::Escape.html_attribute(input)
147
+ end
148
+
149
+ # @since 0.1.0
150
+ alias_method :ha, :escape_html_attribute
151
+
152
+ # Escape an URL to be used in HTML attributes
153
+ #
154
+ # This allows only URLs with whitelisted schemes to pass the filter.
155
+ # Everything else is stripped.
156
+ #
157
+ # Default schemes are:
158
+ #
159
+ # * http
160
+ # * https
161
+ # * mailto
162
+ #
163
+ # If you want to allow a different set of schemes, you should pass it as
164
+ # second argument.
165
+ #
166
+ # This should be used only for untrusted contents: user input.
167
+ #
168
+ # @param input [String] the input
169
+ # @param schemes [Array<String>] an optional array of whitelisted schemes
170
+ #
171
+ # @return [String] the escaped string
172
+ #
173
+ # @since 0.1.0
174
+ #
175
+ # @see Hanami::Utils::Escape.url
176
+ # @see Hanami::Utils::Escape::DEFAULT_URL_SCHEMES
177
+ #
178
+ # @example Basic usage
179
+ # require 'hanami/helpers/escape_helper'
180
+ #
181
+ # class MyView
182
+ # include Hanami::Helpers::EscapeHelper
183
+ #
184
+ # def good_url
185
+ # url = "http://hanamirb.org"
186
+ #
187
+ # %(<a href="#{ hu(url) }">Hanami</a>
188
+ # end
189
+ #
190
+ # def evil_url
191
+ # url = "javascript:alert('xss')"
192
+ #
193
+ # %(<a href="#{ hu(url) }">Evil</a>
194
+ # end
195
+ # end
196
+ #
197
+ # view = MyView.new
198
+ #
199
+ # view.good_url
200
+ # # => %(<a href="http://hanamirb.org">Hanami</a>)
201
+ #
202
+ # view.evil_url
203
+ # # => %(<a href="">Evil</a>)
204
+ #
205
+ # @example Custom schemes
206
+ # require 'hanami/helpers/escape_helper'
207
+ #
208
+ # class MyView
209
+ # include Hanami::Helpers::EscapeHelper
210
+ #
211
+ # def ftp_link
212
+ # schemes = ['ftp', 'ftps']
213
+ # url = 'ftps://ftp.example.org'
214
+ #
215
+ # %(<a href="#{ hu(url, schemes) }">FTP</a>
216
+ # end
217
+ # end
218
+ #
219
+ # view = MyView.new
220
+ #
221
+ # view.ftp_link
222
+ # # => %(<a href="ftps://ftp.example.org">FTP</a>)
223
+ def escape_url(input, schemes = Utils::Escape::DEFAULT_URL_SCHEMES)
224
+ Utils::Escape.url(input, schemes)
225
+ end
226
+
227
+ # @since 0.1.0
228
+ alias_method :hu, :escape_url
229
+
230
+ # Bypass escape.
231
+ #
232
+ # Please notice that this can be really dangerous.
233
+ # Use at your own peril.
234
+ #
235
+ # @param input [String] the input
236
+ #
237
+ # @return [Hanami::Utils::Escape::SafeString] the string marked as safe string
238
+ #
239
+ # @since 0.1.0
240
+ #
241
+ # @example
242
+ # require 'hanami/helpers/escape_helper'
243
+ #
244
+ # class MyView
245
+ # include Hanami::Helpers::EscapeHelper
246
+ #
247
+ # def good_content
248
+ # raw "<p>hello</p>"
249
+ # end
250
+ #
251
+ # def evil_content
252
+ # raw "<script>alert('xss')</script>"
253
+ # end
254
+ # end
255
+ #
256
+ # view = MyView.new
257
+ #
258
+ # view.good_content
259
+ # # => "<p>hello</p>"
260
+ #
261
+ # #
262
+ # # !!! WE HAVE OPENED OUR APPLICATION TO AN XSS ATTACK !!!
263
+ # #
264
+ # view.evil_content
265
+ # # => "<script>alert('xss')</script>"
266
+ def raw(input)
267
+ Utils::Escape::SafeString.new(input)
268
+ end
269
+ end
270
+ end
271
+ end
@@ -0,0 +1,424 @@
1
+ require 'hanami/helpers/form_helper/form_builder'
2
+
3
+ module Hanami
4
+ module Helpers
5
+ # Form builder
6
+ #
7
+ # By including <tt>Hanami::Helpers::FormHelper</tt> it will inject one public method: <tt>form_for</tt>.
8
+ # This is a HTML5 form builder.
9
+ #
10
+ # To understand the general HTML5 builder syntax of this framework, please
11
+ # consider to have a look at <tt>Hanami::Helpers::HtmlHelper</tt> documentation.
12
+ #
13
+ # This builder is independent from any template engine.
14
+ # This was hard to achieve without a compromise: the form helper should be
15
+ # used in one output block in a template or as a method in a view (see the examples below).
16
+ #
17
+ # Features:
18
+ #
19
+ # * Support for complex markup without the need of concatenation
20
+ # * Auto closing HTML5 tags
21
+ # * Support for view local variables
22
+ # * Method override support (PUT/PATCH/DELETE HTTP verbs aren't understood by browsers)
23
+ # * Automatic generation of HTML attributes for inputs: <tt>id</tt>, <tt>name</tt>, <tt>value</tt>
24
+ # * Allow to override HTML attributes
25
+ # * Extract values from request params and fill <tt>value</tt> attributes
26
+ # * Automatic selection of current value for radio button and select inputs
27
+ # * Infinite nested fields
28
+ #
29
+ # Supported tags and inputs:
30
+ #
31
+ # * <tt>color_field</tt>
32
+ # * <tt>date_field</tt>
33
+ # * <tt>datetime_field</tt>
34
+ # * <tt>datetime_local_field</tt>
35
+ # * <tt>email_field</tt>
36
+ # * <tt>hidden_field</tt>
37
+ # * <tt>file_field</tt>
38
+ # * <tt>fields_for</tt>
39
+ # * <tt>form_for</tt>
40
+ # * <tt>label</tt>
41
+ # * <tt>text_area</tt>
42
+ # * <tt>text_field</tt>
43
+ # * <tt>password_field</tt>
44
+ # * <tt>radio_button</tt>
45
+ # * <tt>select</tt>
46
+ # * <tt>submit</tt>
47
+ #
48
+ # @since 0.2.0
49
+ #
50
+ # @see Hanami::Helpers::FormHelper#form_for
51
+ # @see Hanami::Helpers::HtmlHelper
52
+ #
53
+ # @example One output block (template)
54
+ # <%=
55
+ # form_for :book, routes.books_path do
56
+ # text_field :title
57
+ #
58
+ # submit 'Create'
59
+ # end
60
+ # %>
61
+ #
62
+ # @example Method (view)
63
+ # require 'hanami/helpers'
64
+ #
65
+ # class MyView
66
+ # include Hanami::Helpers::FormHelper
67
+ #
68
+ # def my_form
69
+ # form_for :book, routes.books_path do
70
+ # text_field :title
71
+ # end
72
+ # end
73
+ # end
74
+ #
75
+ # # Corresponding template:
76
+ # #
77
+ # # <%= my_form %>
78
+ module FormHelper
79
+ # Default HTTP method for form
80
+ #
81
+ # @since 0.2.0
82
+ # @api private
83
+ DEFAULT_METHOD = 'POST'.freeze
84
+
85
+ # Default charset
86
+ #
87
+ # @since 0.2.0
88
+ # @api private
89
+ DEFAULT_CHARSET = 'utf-8'.freeze
90
+
91
+ # CSRF Token session key
92
+ #
93
+ # This key is shared with <tt>hanamirb</tt>, <tt>hanami-controller</tt>.
94
+ #
95
+ # @since 0.2.0
96
+ # @api private
97
+ CSRF_TOKEN = :_csrf_token
98
+
99
+ # Form object
100
+ #
101
+ # @since 0.2.0
102
+ class Form
103
+ # @return [Symbol] the form name
104
+ #
105
+ # @since 0.2.0
106
+ # @api private
107
+ attr_reader :name
108
+
109
+ # @return [String] the form action
110
+ #
111
+ # @since 0.2.0
112
+ # @api private
113
+ attr_reader :url
114
+
115
+ # @return [::Hash] the form values
116
+ #
117
+ # @since 0.2.0
118
+ # @api private
119
+ attr_reader :values
120
+
121
+ # Initialize a form
122
+ #
123
+ # It accepts a set of values that are used in combination with request
124
+ # params to autofill <tt>value</tt> attributes for fields.
125
+ #
126
+ # The keys of this Hash, MUST correspond to the structure of the (nested)
127
+ # fields of the form.
128
+ #
129
+ # For a given input where the <tt>name</tt> is `book[title]`, Hanami will
130
+ # look for `:book` key in values.
131
+ #
132
+ # If the current params have the same key, it will be PREFERRED over the
133
+ # given values.
134
+ #
135
+ # For instance, if <tt>params.get('book.title')</tt> equals to
136
+ # <tt>"TDD"</tt> while <tt>values[:book].title</tt> returns
137
+ # <tt>"No test"</tt>, the first will win.
138
+ #
139
+ # @param name [Symbol] the name of the form
140
+ # @param url [String] the action of the form
141
+ # @param values [Hash,NilClass] a Hash of values to be used to autofill
142
+ # <tt>value</tt> attributes for fields
143
+ # @param attributes [Hash,NilClass] a Hash of attributes to pass to the
144
+ # <tt>form</tt> tag
145
+ #
146
+ # @since 0.2.0
147
+ #
148
+ # @example Pass A Value
149
+ # # Given the following view
150
+ #
151
+ # module Web::Views::Deliveries
152
+ # class Edit
153
+ # include Web::View
154
+ #
155
+ # def form
156
+ # Form.new(:delivery, routes.delivery_path(id: delivery.id),
157
+ # {delivery: delivery, customer: customer},
158
+ # {method: :patch})
159
+ # end
160
+ # end
161
+ # end
162
+ #
163
+ # # And the corresponding template:
164
+ #
165
+ # <%=
166
+ # form_for form do
167
+ # date_field :delivered_on
168
+ #
169
+ # fields_for :customer do
170
+ # text_field :name
171
+ #
172
+ # fields_for :address do
173
+ # # ...
174
+ # text_field :city
175
+ # end
176
+ # end
177
+ #
178
+ # submit 'Update'
179
+ # end
180
+ # %>
181
+ #
182
+ # # It will render:
183
+ # #
184
+ # # <form action="/deliveries/1" method="POST" accept-charset="utf-8">
185
+ # # <input type="hidden" name="_method" value="PATCH">
186
+ # #
187
+ # # # Value taken from delivery.delivered_on
188
+ # # <input type="date" name="delivery[delivered_on]" id="delivery-delivered-on" value="2015-05-27">
189
+ # #
190
+ # # # Value taken from customer.name
191
+ # # <input type="text" name="delivery[customer][name]" id="delivery-customer-name" value="Luca">
192
+ # #
193
+ # # # Value taken from customer.address.city
194
+ # # <input type="text" name="delivery[customer][address][city]" id="delivery-customer-address-city" value="Rome">
195
+ # #
196
+ # # <button type="submit">Update</button>
197
+ # # </form>
198
+ def initialize(name, url, values = {}, attributes = {})
199
+ @name = name
200
+ @url = url
201
+ @values = values
202
+ @attributes = attributes || {}
203
+ end
204
+
205
+ # Return the method specified by the given attributes or fall back to
206
+ # the default value
207
+ #
208
+ # @return [String] the method for the action
209
+ #
210
+ # @since 0.2.0
211
+ # @api private
212
+ #
213
+ # @see Hanami::Helpers::FormHelper::DEFAULT_METHOD
214
+ def verb
215
+ @attributes.fetch(:method, DEFAULT_METHOD)
216
+ end
217
+ end
218
+
219
+ # Instantiate a HTML5 form builder
220
+ #
221
+ # @overload form_for(name, url, options, &blk)
222
+ # Use inline values
223
+ # @param name [Symbol] the toplevel name of the form, it's used to generate
224
+ # input names, ids, and to lookup params to fill values.
225
+ # @param url [String] the form action URL
226
+ # @param options [Hash] HTML attributes to pass to the form tag and form values
227
+ # @option options [Hash] :values An optional payload of objects to pass
228
+ # @param blk [Proc] A block that describes the contents of the form
229
+ #
230
+ # @overload form_for(form, attributes, &blk)
231
+ # Use Form
232
+ # @param form [Hanami::Helpers::FormHelper::Form] a form object
233
+ # @param attributes [Hash] HTML attributes to pass to the form tag and form values
234
+ # @param blk [Proc] A block that describes the contents of the form
235
+ #
236
+ # @return [Hanami::Helpers::FormHelper::FormBuilder] the form builder
237
+ #
238
+ # @since 0.2.0
239
+ #
240
+ # @see Hanami::Helpers::FormHelper
241
+ # @see Hanami::Helpers::FormHelper::Form
242
+ # @see Hanami::Helpers::FormHelper::FormBuilder
243
+ #
244
+ # @example Inline Values In Template
245
+ # <%=
246
+ # form_for :book, routes.books_path, class: 'form-horizontal' do
247
+ # div do
248
+ # label :title
249
+ # text_field :title, class: 'form-control'
250
+ # end
251
+ #
252
+ # submit 'Create'
253
+ # end
254
+ # %>
255
+ #
256
+ # Output:
257
+ # # <form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
258
+ # # <div>
259
+ # # <label for="book-title">Title</label>
260
+ # # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
261
+ # # </div>
262
+ # #
263
+ # # <button type="submit">Create</button>
264
+ # # </form>
265
+ #
266
+ #
267
+ #
268
+ # @example Use In A View
269
+ #
270
+ # module Web::Views::Books
271
+ # class New
272
+ #
273
+ # def form
274
+ # form_for :book, routes.books_path, class: 'form-horizontal' do
275
+ # div do
276
+ # label :title
277
+ # text_field :title, class: 'form-control'
278
+ # end
279
+ #
280
+ # submit 'Create'
281
+ # end
282
+ # end
283
+ # end
284
+ #
285
+ # <%= form %>
286
+ #
287
+ # Output:
288
+ # # <form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
289
+ # # <div>
290
+ # # <label for="book-title">Title</label>
291
+ # # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
292
+ # # </div>
293
+ # #
294
+ # # <button type="submit">Create</button>
295
+ # # </form>
296
+ #
297
+ # @example Share Code Between Views
298
+ #
299
+ # # Given the following views to create and update a resource
300
+ # module Web::Views::Books
301
+ # class New
302
+ # include Web::View
303
+ #
304
+ # def form
305
+ # Form.new(:book, routes.books_path)
306
+ # end
307
+ #
308
+ # def submit_label
309
+ # 'Create'
310
+ # end
311
+ # end
312
+ #
313
+ # class Edit
314
+ # include Web::View
315
+ #
316
+ # def form
317
+ # Form.new(:book, routes.book_path(id: book.id),
318
+ # {book: book}, {method: :patch})
319
+ # end
320
+ #
321
+ # def submit_label
322
+ # 'Update'
323
+ # end
324
+ # end
325
+ # end
326
+ #
327
+ # # The respective templates can be identical:
328
+ #
329
+ # ## books/new.html.erb
330
+ # <%= render partial: 'books/form' %>
331
+ #
332
+ # ## books/edit.html.erb
333
+ # <%= render partial: 'books/form' %>
334
+ #
335
+ # # While the partial can have the following markup:
336
+ #
337
+ # ## books/_form.html.erb
338
+ # <%=
339
+ # form_for form, class: 'form-horizontal' do
340
+ # div do
341
+ # label :title
342
+ # text_field :title, class: 'form-control'
343
+ # end
344
+ #
345
+ # submit submit_label
346
+ # end
347
+ # %>
348
+ #
349
+ # Output:
350
+ # # <form action="/books" method="POST" accept-charset="utf-8" id="book-form" class="form-horizontal">
351
+ # # <div>
352
+ # # <label for="book-title">Title</label>
353
+ # # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
354
+ # # </div>
355
+ # #
356
+ # # <button type="submit">Create</button>
357
+ # # </form>
358
+ #
359
+ # @example Method override
360
+ # <%=
361
+ # form_for :book, routes.book_path(id: book.id), method: :put do
362
+ # text_field :title
363
+ #
364
+ # submit 'Update'
365
+ # end
366
+ # %>
367
+ #
368
+ # Output:
369
+ # # <form action="/books/23" accept-charset="utf-8" id="book-form" method="POST">
370
+ # # <input type="hidden" name="_method" value="PUT">
371
+ # # <input type="text" name="book[title]" id="book-title" value="Test Driven Development">
372
+ # #
373
+ # # <button type="submit">Update</button>
374
+ # # </form>
375
+ #
376
+ # @example Nested fields
377
+ # <%=
378
+ # form_for :delivery, routes.deliveries_path do
379
+ # text_field :customer_name
380
+ #
381
+ # fields_for :address do
382
+ # text_field :city
383
+ # end
384
+ #
385
+ # submit 'Create'
386
+ # end
387
+ # %>
388
+ #
389
+ # Output:
390
+ # # <form action="/deliveries" accept-charset="utf-8" id="delivery-form" method="POST">
391
+ # # <input type="text" name="delivery[customer_name]" id="delivery-customer-name" value="">
392
+ # # <input type="text" name="delivery[address][city]" id="delivery-address-city" value="">
393
+ # #
394
+ # # <button type="submit">Create</button>
395
+ # # </form>
396
+ def form_for(name, url, options = {}, &blk)
397
+ form = if name.is_a?(Form)
398
+ options = url
399
+ name
400
+ else
401
+ Form.new(name, url, options.delete(:values))
402
+ end
403
+
404
+ attributes = { action: form.url, method: form.verb, :'accept-charset' => DEFAULT_CHARSET, id: "#{ form.name }-form" }.merge(options)
405
+ FormBuilder.new(form, attributes, self, &blk)
406
+ end
407
+
408
+ # Returns CSRF Protection Token stored in session.
409
+ #
410
+ # It returns <tt>nil</tt> if sessions aren't enabled or the value is missing.
411
+ #
412
+ # @return [String,NilClass] token, if present
413
+ #
414
+ # @since 0.2.0
415
+ def csrf_token
416
+ if defined?(session)
417
+ session[CSRF_TOKEN]
418
+ elsif defined?(locals) && locals[:session]
419
+ locals[:session][CSRF_TOKEN]
420
+ end
421
+ end
422
+ end
423
+ end
424
+ end