forme 1.9.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +70 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +396 -202
  5. data/lib/forme/bs3.rb +19 -5
  6. data/lib/forme/erb.rb +18 -16
  7. data/lib/forme/form.rb +151 -118
  8. data/lib/forme/input.rb +1 -1
  9. data/lib/forme/rails.rb +41 -72
  10. data/lib/forme/raw.rb +2 -2
  11. data/lib/forme/sinatra.rb +6 -2
  12. data/lib/forme/tag.rb +3 -12
  13. data/lib/forme/template.rb +118 -0
  14. data/lib/forme/transformers/error_handler.rb +46 -1
  15. data/lib/forme/transformers/formatter.rb +36 -35
  16. data/lib/forme/transformers/helper.rb +0 -1
  17. data/lib/forme/transformers/inputs_wrapper.rb +6 -6
  18. data/lib/forme/transformers/labeler.rb +19 -0
  19. data/lib/forme/transformers/wrapper.rb +1 -1
  20. data/lib/forme/version.rb +2 -2
  21. data/lib/forme.rb +15 -2
  22. data/lib/roda/plugins/forme.rb +1 -1
  23. data/lib/roda/plugins/forme_erubi_capture.rb +62 -0
  24. data/lib/roda/plugins/forme_route_csrf.rb +16 -20
  25. data/lib/roda/plugins/forme_set.rb +177 -0
  26. data/lib/sequel/plugins/forme.rb +42 -55
  27. data/lib/sequel/plugins/forme_i18n.rb +3 -1
  28. data/lib/sequel/plugins/forme_set.rb +50 -28
  29. data/spec/all.rb +1 -1
  30. data/spec/bs3_reference_spec.rb +18 -18
  31. data/spec/bs3_sequel_plugin_spec.rb +7 -7
  32. data/spec/bs3_spec.rb +23 -11
  33. data/spec/erb_helper.rb +73 -58
  34. data/spec/erubi_capture_helper.rb +202 -0
  35. data/spec/forme_spec.rb +80 -29
  36. data/spec/rails_integration_spec.rb +47 -24
  37. data/spec/roda_integration_spec.rb +459 -48
  38. data/spec/sequel_helper.rb +0 -1
  39. data/spec/sequel_i18n_helper.rb +1 -1
  40. data/spec/sequel_i18n_plugin_spec.rb +3 -2
  41. data/spec/sequel_plugin_spec.rb +25 -8
  42. data/spec/sequel_set_plugin_spec.rb +10 -3
  43. data/spec/shared_erb_specs.rb +75 -0
  44. data/spec/sinatra_integration_spec.rb +5 -6
  45. data/spec/spec_helper.rb +23 -5
  46. metadata +30 -8
  47. data/lib/forme/erb_form.rb +0 -74
@@ -1,8 +1,7 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
2
- require File.join(File.dirname(File.expand_path(__FILE__)), 'sequel_helper.rb')
3
- require File.join(File.dirname(File.expand_path(__FILE__)), 'erb_helper.rb')
1
+ require_relative 'spec_helper'
2
+ require_relative 'sequel_helper'
3
+ require_relative 'erb_helper'
4
4
 
5
- require 'rubygems'
6
5
  begin
7
6
  require 'roda'
8
7
  require 'tilt'
@@ -17,26 +16,42 @@ rescue LoadError
17
16
  rescue LoadError
18
17
  require 'tilt/erb'
19
18
  end
20
- end
21
-
22
- class FormeRodaTest < Roda
23
- use Rack::Session::Cookie, :secret => "__a_very_long_string__"
24
-
25
- def erb(s, opts={})
26
- render(opts.merge(:inline=>s))
19
+ else
20
+ begin
21
+ require 'erubi/capture_end'
22
+ require_relative 'erubi_capture_helper'
23
+ rescue LoadError
27
24
  end
25
+ end
28
26
 
29
- route do |r|
30
- r.get 'use_request_specific_token', :use do |use|
31
- render :inline=>"[#{Base64.strict_encode64(send(:csrf_secret))}]<%= form({:method=>:post}, {:use_request_specific_token=>#{use == '1'}}) %>"
27
+ def FormeRodaTest(block=ERB_BLOCK)
28
+ Class.new(Roda) do
29
+ opts[:check_dynamic_arity] = opts[:check_arity] = :warn
30
+
31
+ if defined?(Roda::RodaVersionNumber) && Roda::RodaVersionNumber >= 30100
32
+ require 'roda/session_middleware'
33
+ opts[:sessions_convert_symbols] = true
34
+ use RodaSessionMiddleware, :secret=>SecureRandom.random_bytes(64), :key=>'rack.session'
35
+ else
36
+ use Rack::Session::Cookie, :secret => "__a_very_long_string__"
32
37
  end
33
- r.get 'hidden_tags' do |use|
34
- render :inline=>"<%= form({:method=>:post}, {:hidden_tags=>[{:foo=>'bar'}]}) %>"
38
+
39
+ def erb(s, opts={})
40
+ render(opts.merge(:inline=>s))
35
41
  end
36
- r.get 'csrf', :use do |use|
37
- render :inline=>"<%= form({:method=>:post}, {:csrf=>#{use == '1'}}) %>"
42
+
43
+ route do |r|
44
+ r.get 'use_request_specific_token', :use do |use|
45
+ render :inline=>"[#{Base64.strict_encode64(send(:csrf_secret))}]<%= form({:method=>:post}, {:use_request_specific_token=>#{use == '1'}}) %>"
46
+ end
47
+ r.get 'hidden_tags2' do |use|
48
+ render :inline=>"<%= form({:method=>:post}, {:hidden_tags=>[{:foo=>'bar'}]}) %>"
49
+ end
50
+ r.get 'csrf', :use do |use|
51
+ render :inline=>"<%= form({:method=>:post}, {:csrf=>#{use == '1'}}) %>"
52
+ end
53
+ instance_exec(r, &block)
38
54
  end
39
- instance_exec(r, &ERB_BLOCK)
40
55
  end
41
56
  end
42
57
 
@@ -46,7 +61,7 @@ rescue LoadError
46
61
  warn "unable to load rack/csrf, skipping roda csrf plugin spec"
47
62
  else
48
63
  describe "Forme Roda ERB integration with roda forme and csrf plugins" do
49
- app = FormeRodaCSRFTest = Class.new(FormeRodaTest)
64
+ app = FormeRodaCSRFTest = FormeRodaTest()
50
65
  app.plugin :csrf
51
66
  app.plugin :forme
52
67
 
@@ -60,58 +75,454 @@ describe "Forme Roda ERB integration with roda forme and csrf plugins" do
60
75
  end
61
76
  end
62
77
 
78
+ module FormeRouteCsrfSpecs
79
+ extend Minitest::Spec::DSL
80
+ include FormeErbSpecs
81
+
82
+ it "should have a valid CSRF tag" do
83
+ output = sin_get('/use_request_specific_token/1')
84
+ output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
85
+ secret = $1
86
+ token = $2
87
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
88
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
89
+ end
90
+
91
+ it "should handle the :use_request_specific_token => true option" do
92
+ output = sin_get('/use_request_specific_token/1')
93
+ output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
94
+ secret = $1
95
+ token = $2
96
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
97
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
98
+ end
99
+
100
+ it "should handle the :use_request_specific_token => false option" do
101
+ output = sin_get('/use_request_specific_token/0')
102
+ output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
103
+ secret = $1
104
+ token = $2
105
+ app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/0', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal(plugin_opts.empty? ? false : true)
106
+ end
107
+
108
+ silence_warnings "should include :hidden_tags option" do
109
+ sin_get('/hidden_tags2').must_include 'name="foo" type="hidden" value="bar"'
110
+ end
111
+
112
+ it "should handle the :csrf option" do
113
+ sin_get('/csrf/1').must_include '<input name="_csrf" type="hidden" value="'
114
+ sin_get('/csrf/0').wont_include '<input name="_csrf" type="hidden" value="'
115
+ end
116
+ end
117
+
63
118
  begin
64
119
  require 'roda/plugins/route_csrf'
120
+ require 'roda/plugins/capture_erb'
121
+ require 'roda/plugins/inject_erb'
65
122
  rescue LoadError
66
- warn "unable to load roda/plugins/route_csrf, skipping forme_route_csrf plugin spec"
123
+ warn "unable to load necessary Roda plugins, skipping forme_erubi_capture plugin spec"
67
124
  else
125
+ describe "Forme Roda Erubi::CaptureEnd integration with roda forme_route_csrf" do
126
+ app = FormeRodaTest(ERUBI_CAPTURE_BLOCK)
127
+ app.plugin :forme_erubi_capture
128
+ app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureEndEngine}}
129
+
130
+ define_method(:app){app}
131
+ define_method(:plugin_opts){{}}
132
+ define_method(:sin_get) do |path|
133
+ s = String.new
134
+ app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
135
+ s.gsub(/\s+/, ' ').strip
136
+ end
137
+
138
+ include FormeRouteCsrfSpecs
139
+ end if defined?(ERUBI_CAPTURE_BLOCK)
140
+
68
141
  [{}, {:require_request_specific_tokens=>false}].each do |plugin_opts|
69
142
  describe "Forme Roda ERB integration with roda forme_route_csrf and route_csrf plugin with #{plugin_opts}" do
70
- app = Class.new(FormeRodaTest)
143
+ app = FormeRodaTest()
71
144
  app.plugin :forme_route_csrf
72
145
  app.plugin :route_csrf, plugin_opts
73
146
 
147
+ define_method(:app){app}
148
+ define_method(:plugin_opts){plugin_opts}
74
149
  define_method(:sin_get) do |path|
75
150
  s = String.new
76
151
  app.call(@rack.merge('PATH_INFO'=>path))[2].each{|str| s << str}
77
152
  s.gsub(/\s+/, ' ').strip
78
153
  end
79
154
 
80
- include FormeErbSpecs
155
+ include FormeRouteCsrfSpecs
156
+ end
157
+
158
+ describe "Forme Roda ERB Sequel integration with roda forme_set plugin and route_csrf plugin with #{plugin_opts}" do
159
+ before do
160
+ @app = FormeRodaTest()
161
+ @app.plugin :route_csrf, plugin_opts
162
+ @app.plugin(:forme_set, :secret=>'1'*64)
163
+
164
+ @ab = Album.new
165
+ end
166
+
167
+ def forme_parse(*args, &block)
168
+ _forme_set(:forme_parse, *args, &block)
169
+ end
170
+
171
+ def forme_set(*args, &block)
172
+ _forme_set(:forme_set, *args, &block)
173
+ end
174
+
175
+ def forme_call(params)
176
+ @app.call('REQUEST_METHOD'=>'POST', 'rack.input'=>StringIO.new, :params=>params)
177
+ end
178
+
179
+ def _forme_set(meth, obj, orig_hash, *form_args, &block)
180
+ hash = {}
181
+ forme_set_block = orig_hash.delete(:forme_set_block)
182
+ orig_hash.each{|k,v| hash[k.to_s] = v}
183
+ album = @ab
184
+ ret, _, data, hmac = nil
185
+
186
+ @app.route do |r|
187
+ r.get do
188
+ if @block = env[:block]
189
+ render(:inline=>'<% form(*env[:args]) do |f| %><%= @block.call(f) %><% end %>')
190
+ else
191
+ form(*env[:args])
192
+ end
193
+ end
194
+ r.post do
195
+ r.params.replace(env[:params])
196
+ ret = send(meth, album, &forme_set_block)
197
+ nil
198
+ end
199
+ end
200
+ body = @app.call('REQUEST_METHOD'=>'GET', :args=>[album, *form_args], :block=>block)[2].join
201
+ body =~ %r|<input name="_csrf" type="hidden" value="([^"]+)"/>.*<input name="_forme_set_data" type="hidden" value="([^"]+)"/><input name="_forme_set_data_hmac" type="hidden" value="([^"]+)"/>|n
202
+ csrf = $1
203
+ data = $2
204
+ hmac = $3
205
+ data.gsub!("&quot;", '"') if data
206
+ h = {"album"=>hash, "_forme_set_data"=>data, "_forme_set_data_hmac"=>hmac, "_csrf"=>csrf}
207
+ if data && hmac
208
+ forme_call(h)
209
+ end
210
+ meth == :forme_parse ? ret : h
211
+ end
212
+
213
+ it "should have subform work correctly" do
214
+ @app.route do |r|
215
+ @album = Album.load(:name=>'N', :copies_sold=>2, :id=>1)
216
+ @album.associations[:artist] = Artist.load(:name=>'A', :id=>2)
217
+ erb <<END
218
+ 0
219
+ <% form(@album, {:action=>'/baz'}, :button=>'Sub') do |f| %>
220
+ 1
221
+ <%= f.subform(:artist, :inputs=>[:name], :legend=>'Foo', :grid=>true, :labels=>%w'Name') %>
222
+ 2
223
+ <% end %>
224
+ 3
225
+ END
226
+ end
227
+
228
+ body = @app.call('REQUEST_METHOD'=>'GET')[2].join.gsub("\n", ' ').gsub(/ +/, ' ').chomp(' ')
229
+ body.sub(%r{<input name="_csrf" type="hidden" value="([^"]+)"/>}, '<input name="_csrf" type="hidden" value="csrf"/>').must_equal '0 <form action="/baz" class="forme album" method="post"><input name="_csrf" type="hidden" value="csrf"/> 1 <table><caption>Foo</caption><thead><tr><th>Name</th></tr></thead><tbody><input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="2"/><tr><td class="string"><input id="album_artist_attributes_name" maxlength="255" name="album[artist_attributes][name]" type="text" value="A"/></td></tr></tbody></table> 2 <input type="submit" value="Sub"/></form>3'
230
+ end
231
+
232
+ it "#forme_set should include HMAC values if form includes inputs for obj" do
233
+ h = forme_set(@ab, :name=>'Foo')
234
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
235
+ @ab.name.must_be_nil
236
+ @ab.copies_sold.must_be_nil
237
+
238
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
239
+ hmac = h.delete("_forme_set_data_hmac")
240
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
241
+ proc{forme_call(h.merge("_forme_set_data_hmac"=>hmac+'1'))}.must_raise Roda::RodaPlugins::FormeSet::Error
242
+ data = h["_forme_set_data"]
243
+ data.sub!(/"csrf":\["_csrf","./, "\"csrf\":[\"_csrf\",\"|")
244
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, '1'*64, data)
245
+ proc{forme_call(h.merge("_forme_set_data_hmac"=>hmac))}.must_raise Roda::RodaPlugins::FormeSet::Error
246
+ @ab.name.must_equal 'Foo'
247
+ @ab.copies_sold.must_be_nil
81
248
 
82
- it "should handle the :hidden_tags option" do
83
- output = sin_get('/use_request_specific_token/1')
84
- output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
85
- secret = $1
86
- token = $2
87
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
88
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
249
+ forme_set(@ab, :copies_sold=>100){|f| f.input(:name)}
250
+ @ab.name.must_be_nil
251
+ @ab.copies_sold.must_be_nil
89
252
  end
90
253
 
91
- it "should handle the :use_request_specific_token => true option" do
92
- output = sin_get('/use_request_specific_token/1')
93
- output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
94
- secret = $1
95
- token = $2
96
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/1', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal true
97
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/2', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal false
254
+ it "#forme_set should handle custom form namespaces" do
255
+ forme_set(@ab, {"album"=>{"name"=>'Foo', 'copies_sold'=>'100'}}, {}, :namespace=>'album'){|f| f.input(:name); f.input(:copies_sold)}
256
+ @ab.name.must_equal 'Foo'
257
+ @ab.copies_sold.must_equal 100
258
+
259
+ proc{forme_set(@ab, {"a"=>{"name"=>'Foo'}}, {}, :namespace=>'album'){|f| f.input(:name); f.input(:copies_sold)}}.must_raise Roda::RodaPlugins::FormeSet::Error
98
260
  end
99
261
 
100
- it "should handle the :use_request_specific_token => false option" do
101
- output = sin_get('/use_request_specific_token/0')
102
- output =~ /\[([^\]]+)\].*?value=\"([^\"]+)\"/
103
- secret = $1
104
- token = $2
105
- app.new({'SCRIPT_NAME'=>'', 'PATH_INFO'=>'/use_request_specific_token/0', 'REQUEST_METHOD'=>'POST', 'rack.session'=>{'_roda_csrf_secret'=>secret}, 'rack.input'=>StringIO.new}).valid_csrf?(:token=>token).must_equal(plugin_opts.empty? ? false : true)
262
+ it "#forme_set should call plugin block if there is an error with the form submission hmac not matching data" do
263
+ @app.plugin :forme_set do |error_type, _|
264
+ request.on{error_type.to_s}
265
+ end
266
+
267
+ h = forme_set(@ab, :name=>'Foo')
268
+ forme_call(h)[2].must_equal ['missing_data']
269
+
270
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
271
+ hmac = h.delete("_forme_set_data_hmac")
272
+ forme_call(h)[2].must_equal ['missing_hmac']
273
+
274
+ forme_call(h.merge("_forme_set_data_hmac"=>hmac+'1'))[2].must_equal ['hmac_mismatch']
275
+
276
+ data = h["_forme_set_data"]
277
+ data.sub!(/"csrf":\["_csrf","./, "\"csrf\":[\"_csrf\",\"|")
278
+ hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA512.new, '1'*64, data)
279
+ forme_call(h.merge("_forme_set_data_hmac"=>hmac))[2].must_equal ['csrf_mismatch']
280
+
281
+ h = forme_set(@ab, :name=>'Foo')
282
+ h.delete('album')
283
+ forme_call(h)[2].must_equal ['missing_namespace']
106
284
  end
107
285
 
108
- it "should handle the :hidden_tags option" do
109
- sin_get('/hidden_tags').must_include 'name="foo" type="hidden" value="bar"'
286
+ it "#forme_set should raise if plugin block does not raise or throw" do
287
+ @app.plugin :forme_set do |_, obj|
288
+ obj
289
+ end
290
+ h = forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
291
+ h.delete("_forme_set_data_hmac")
292
+ proc{forme_call(h)}.must_raise Roda::RodaPlugins::FormeSet::Error
110
293
  end
111
294
 
112
- it "should handle the :csrf option" do
113
- sin_get('/csrf/1').must_include '<input name="_csrf" type="hidden" value="'
114
- sin_get('/csrf/0').wont_include '<input name="_csrf" type="hidden" value="'
295
+ it "#forme_set should only set values in the form" do
296
+ forme_set(@ab, :name=>'Foo')
297
+ @ab.name.must_be_nil
298
+
299
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name)}
300
+ @ab.name.must_equal 'Foo'
301
+
302
+ forme_set(@ab, 'copies_sold'=>'1'){|f| f.input(:name)}
303
+ @ab.name.must_be_nil
304
+ @ab.copies_sold.must_be_nil
305
+
306
+ forme_set(@ab, 'name'=>'Bar', 'copies_sold'=>'1'){|f| f.input(:name); f.input(:copies_sold)}
307
+ @ab.name.must_equal 'Bar'
308
+ @ab.copies_sold.must_equal 1
309
+ end
310
+
311
+ it "#forme_set should handle form_versions" do
312
+ h = forme_set(@ab, {:name=>'Foo'}){|f| f.input(:name)}
313
+ @ab.name.must_equal 'Foo'
314
+
315
+ obj = nil
316
+ version = nil
317
+ name = nil
318
+ forme_set_block = proc do |v, o|
319
+ obj = o
320
+ name = o.name
321
+ version = v
322
+ end
323
+ h2 = forme_set(@ab, {:name=>'Foo', :forme_set_block=>forme_set_block}, {}, :form_version=>1){|f| f.input(:name)}
324
+ obj.must_be_same_as @ab
325
+ name.must_equal 'Foo'
326
+ version.must_equal 1
327
+
328
+ forme_call(h)
329
+ obj.must_be_same_as @ab
330
+ version.must_be_nil
331
+
332
+ forme_set(@ab, {:name=>'Bar', :forme_set_block=>forme_set_block}, {}, :form_version=>2){|f| f.input(:name)}
333
+ obj.must_be_same_as @ab
334
+ name.must_equal 'Bar'
335
+ version.must_equal 2
336
+
337
+ h['album']['name'] = 'Baz'
338
+ forme_call(h)
339
+ obj.must_be_same_as @ab
340
+ name.must_equal 'Baz'
341
+ version.must_be_nil
342
+
343
+ forme_call(h2)
344
+ obj.must_be_same_as @ab
345
+ version.must_equal 1
346
+ end
347
+
348
+ it "#forme_set should work for forms without blocks" do
349
+ forme_set(@ab, {:name=>'Foo'}, {}, :inputs=>[:name])
350
+ @ab.name.must_equal 'Foo'
351
+ end
352
+
353
+ it "#forme_set should handle different ways to specify parameter names" do
354
+ [{:attr=>{:name=>'foo'}}, {:attr=>{'name'=>:foo}}, {:name=>'foo'}, {:name=>'bar[foo]'}, {:key=>:foo}].each do |opts|
355
+ forme_set(@ab, name=>'Foo'){|f| f.input(:name, opts)}
356
+ @ab.name.must_be_nil
357
+
358
+ forme_set(@ab, 'foo'=>'Foo'){|f| f.input(:name, opts)}
359
+ @ab.name.must_equal 'Foo'
360
+ end
361
+ end
362
+
363
+ it "#forme_set should ignore values where key is explicitly set to nil" do
364
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :key=>nil)}
365
+ @ab.forme_set(:name=>'Foo')
366
+ @ab.name.must_be_nil
367
+ @ab.forme_set(nil=>'Foo')
368
+ @ab.name.must_be_nil
369
+ end
370
+
371
+ it "#forme_set should skip inputs with disabled/readonly formatter set on input" do
372
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
373
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :formatter=>formatter)}
374
+ @ab.name.must_be_nil
375
+ end
376
+
377
+ forme_set(@ab, :name=>'Foo'){|f| f.input(:name, :formatter=>:default)}
378
+ @ab.name.must_equal 'Foo'
379
+ end
380
+
381
+ it "#forme_set should skip inputs with disabled/readonly formatter set on Form" do
382
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
383
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:disabled){|f| f.input(:name)}
384
+ @ab.name.must_be_nil
385
+ end
386
+
387
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:default){|f| f.input(:name)}
388
+ @ab.name.must_equal 'Foo'
389
+ end
390
+
391
+ it "#forme_set should skip inputs with disabled/readonly formatter set using with_opts" do
392
+ [:disabled, :readonly, ::Forme::Formatter::Disabled, ::Forme::Formatter::ReadOnly].each do |formatter|
393
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>formatter){f.input(:name)}}
394
+ @ab.name.must_be_nil
395
+ end
396
+
397
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:default){f.input(:name)}}
398
+ @ab.name.must_equal 'Foo'
399
+ end
400
+
401
+ it "#forme_set should prefer input formatter to with_opts formatter" do
402
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:default){f.input(:name, :formatter=>:readonly)}}
403
+ @ab.name.must_be_nil
404
+
405
+ forme_set(@ab, :name=>'Foo'){|f| f.with_opts(:formatter=>:readonly){f.input(:name, :formatter=>:default)}}
406
+ @ab.name.must_equal 'Foo'
407
+ end
408
+
409
+ it "#forme_set should prefer with_opts formatter to form formatter" do
410
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:default){|f| f.with_opts(:formatter=>:readonly){f.input(:name)}}
411
+ @ab.name.must_be_nil
412
+
413
+ forme_set(@ab, {:name=>'Foo'}, {}, :formatter=>:readonly){|f| f.with_opts(:formatter=>:default){f.input(:name)}}
414
+ @ab.name.must_equal 'Foo'
415
+ end
416
+
417
+ it "#forme_set should handle setting values for associated objects" do
418
+ forme_set(@ab, :artist_id=>'1')
419
+ @ab.artist_id.must_be_nil
420
+
421
+ forme_set(@ab, :artist_id=>'1'){|f| f.input(:artist)}
422
+ @ab.artist_id.must_equal 1
423
+
424
+ forme_set(@ab, 'tag_pks'=>%w'1 2'){|f| f.input(:artist)}
425
+ @ab.artist_id.must_be_nil
426
+ @ab.tag_pks.must_equal []
427
+
428
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'1 2'){|f| f.input(:artist); f.input(:tags)}
429
+ @ab.artist_id.must_equal 1
430
+ @ab.tag_pks.must_equal [1, 2]
431
+ end
432
+
433
+ it "#forme_set should handle validations for filtered associations" do
434
+ [
435
+ [{:dataset=>proc{|ds| ds.exclude(:id=>1)}},
436
+ {:dataset=>proc{|ds| ds.exclude(:id=>1)}}],
437
+ [{:options=>Artist.exclude(:id=>1).select_order_map([:name, :id])},
438
+ {:options=>Tag.exclude(:id=>1).select_order_map(:id), :name=>'tag_pks[]'}],
439
+ [{:options=>Artist.exclude(:id=>1).all, :text_method=>:name, :value_method=>:id},
440
+ {:options=>Tag.exclude(:id=>1).all, :text_method=>:name, :value_method=>:id}],
441
+ ].each do |artist_opts, tag_opts|
442
+ @ab.forme_validations.clear
443
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'1 2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
444
+ @ab.artist_id.must_equal 1
445
+ @ab.tag_pks.must_equal [1, 2]
446
+ @ab.valid?.must_equal false
447
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
448
+ @ab.errors[:tag_pks].must_equal ['invalid value submitted']
449
+
450
+ @ab.forme_validations.clear
451
+ forme_set(@ab, 'artist_id'=>'1', 'tag_pks'=>%w'2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
452
+ @ab.forme_set('artist_id'=>'1', 'tag_pks'=>['2'])
453
+ @ab.artist_id.must_equal 1
454
+ @ab.tag_pks.must_equal [2]
455
+ @ab.valid?.must_equal false
456
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
457
+ @ab.errors[:tag_pks].must_be_nil
458
+
459
+ @ab.forme_validations.clear
460
+ forme_set(@ab, 'artist_id'=>'2', 'tag_pks'=>%w'2'){|f| f.input(:artist, artist_opts); f.input(:tags, tag_opts)}
461
+ @ab.valid?.must_equal true
462
+ end
463
+ end
464
+
465
+ it "#forme_set should not require associated values for many_to_one association with select boxes" do
466
+ forme_set(@ab, {}){|f| f.input(:artist)}
467
+ @ab.valid?.must_equal true
468
+
469
+ forme_set(@ab, {'artist_id'=>nil}){|f| f.input(:artist)}
470
+ @ab.valid?.must_equal true
471
+
472
+ forme_set(@ab, {'artist_id'=>''}){|f| f.input(:artist)}
473
+ @ab.valid?.must_equal true
474
+ end
475
+
476
+ it "#forme_set should not require associated values for many_to_one association with radio buttons" do
477
+ forme_set(@ab, {}){|f| f.input(:artist, :as=>:radio)}
478
+ @ab.valid?.must_equal true
479
+ end
480
+
481
+ it "#forme_set should require associated values for many_to_one association with select boxes when :required is used" do
482
+ forme_set(@ab, {}){|f| f.input(:artist, :required=>true)}
483
+ @ab.valid?.must_equal false
484
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
485
+ end
486
+
487
+ it "#forme_set should require associated values for many_to_one association with radio buttons when :required is used" do
488
+ forme_set(@ab, {}){|f| f.input(:artist, :as=>:radio, :required=>true)}
489
+ @ab.valid?.must_equal false
490
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
491
+ end
492
+
493
+ it "#forme_set should handle cases where currently associated values is nil" do
494
+ def @ab.tag_pks; nil; end
495
+ forme_set(@ab, :tag_pks=>['1']){|f| f.input(:tags)}
496
+ @ab.valid?.must_equal true
497
+ end
498
+
499
+ it "#forme_parse should return hash with values and validations" do
500
+ forme_parse(@ab, :name=>'Foo'){|f| f.input(:name)}.must_equal(:values=>{:name=>'Foo'}, :validations=>{}, :form_version=>nil)
501
+
502
+ hash = forme_parse(@ab, :name=>'Foo', 'artist_id'=>'1') do |f|
503
+ f.input(:name)
504
+ f.input(:artist, :dataset=>proc{|ds| ds.exclude(:id=>1)})
505
+ end
506
+ hash.must_equal(:values=>{:name=>'Foo', :artist_id=>'1'}, :validations=>{:artist_id=>[:valid, false]}, :form_version=>nil)
507
+
508
+ @ab.set(hash[:values])
509
+ @ab.valid?.must_equal true
510
+
511
+ @ab.forme_validations.merge!(hash[:validations])
512
+ @ab.valid?.must_equal false
513
+ @ab.errors[:artist_id].must_equal ['invalid value submitted']
514
+
515
+ @ab = Album.new
516
+ hash = forme_parse(@ab, {:name=>'Foo', 'artist_id'=>'1'}, {}, :form_version=>1) do |f|
517
+ f.input(:name)
518
+ f.input(:artist, :dataset=>proc{|ds| ds.exclude(:id=>2)})
519
+ end
520
+ hash.must_equal(:values=>{:name=>'Foo', :artist_id=>'1'}, :validations=>{:artist_id=>[:valid, true]}, :form_version=>1)
521
+ @ab.set(hash[:values])
522
+ @ab.valid?.must_equal true
523
+
524
+ @ab.forme_validations.merge!(hash[:validations])
525
+ @ab.valid?.must_equal true
115
526
  end
116
527
  end
117
528
  end
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'sequel'
3
2
 
4
3
  db_url = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'jruby' ? 'jdbc:sqlite::memory:' : 'sqlite:/'
@@ -1,4 +1,4 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'sequel_helper.rb')
1
+ require_relative 'sequel_helper'
2
2
 
3
3
  gem 'i18n', '>= 0.7.0'
4
4
  require 'i18n'
@@ -1,7 +1,8 @@
1
- require File.join(File.dirname(File.expand_path(__FILE__)), 'spec_helper.rb')
1
+ require_relative 'spec_helper'
2
2
 
3
3
  begin
4
- require File.join(File.dirname(File.expand_path(__FILE__)), 'sequel_i18n_helper.rb')
4
+ raise LoadError if defined?(JRUBY_VERSION) && /\A9\.2\./.match(JRUBY_VERSION)
5
+ require_relative 'sequel_i18n_helper'
5
6
  rescue LoadError
6
7
  warn "unable to load i18n, skipping i18n Sequel plugin spec"
7
8
  else