autoforme 1.8.0 → 1.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5112ef1a062e3fa4a586f605365004eb733e25c7e49c7d52d0399d2d5596a692
4
- data.tar.gz: 3142feb7e88e43ec825eedb9dfa700dd2c710cdce59b7839241117ad66b6568a
3
+ metadata.gz: '0198a23636933034920c066694a4acd8e58d5529fbf21ada5f542a7de444cc2c'
4
+ data.tar.gz: 6cf1f310f06018a91573472c7410c1dda1842b7f96593a82deaf9be5f2bdd524
5
5
  SHA512:
6
- metadata.gz: 937e7ced506f742fbd9ec7632cf69a6a93b1996d884a63996162a38ad949c0ef3b6022a910fe51748f9792c9d9ce3703ffa6691e321f93bcd9917e05f3f24677
7
- data.tar.gz: a369fc3b50aef5cf9546fb512c0d92a63de9cc0f15b8ac16f84b5fa2291f688ea10f0a711ddf9573b3e1c6257015174c147f05af6e7f073ae37be1e392386c85
6
+ metadata.gz: 8931ddb513d806df30b8a4fe36fb20583597ac4e9f45e1144c5732cff66d09f92a14967a5ac4c35724ac339bfd23af866b454490ffabe99b327dfaff2e1aa787
7
+ data.tar.gz: '097239f8074dc20537dec29064f24c2e5fbb521cf3586bb828726afc2b436eed190794d2b276e9b108c60bb6a4c3116a35dc651d54e32af5ff74d34e4a6b586e'
data/CHANGELOG CHANGED
@@ -1,3 +1,29 @@
1
+ === 1.11.0 (2021-11-30)
2
+
3
+ * Require forme 2.0.0 (jeremyevans)
4
+
5
+ * Add support for view_options framework option (nmb, jeremyevans) (#42)
6
+
7
+ * Drop support for Ruby 1.8 (jeremyevans)
8
+
9
+ === 1.10.0 (2021-08-27)
10
+
11
+ * Do not consider read_only many_to_many associations to be editable (jeremyevans)
12
+
13
+ * Ignore unique constraint violations when adding associated objects in mtm_update (jeremyevans)
14
+
15
+ * Handle search fields that cannot be typecast correctly by returning no results (jeremyevans)
16
+
17
+ === 1.9.1 (2019-07-22)
18
+
19
+ * [SECURITY] Escape object display name when displaying association links (adam12)
20
+
21
+ === 1.9.0 (2018-07-18)
22
+
23
+ * Add support for using flash string keys in the Roda support, to work with Roda's sessions plugin (jeremyevans)
24
+
25
+ * Show correct page title on error pages (jeremyevans)
26
+
1
27
  === 1.8.0 (2018-06-11)
2
28
 
3
29
  * Add support for Roda route_csrf plugin for request-specific CSRF tokens (jeremyevans)
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2018 Jeremy Evans
1
+ Copyright (c) 2013-2021 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/README.rdoc CHANGED
@@ -22,8 +22,7 @@ flexible in terms of configuration.
22
22
  Demo Site :: http://autoforme-demo.jeremyevans.net
23
23
  RDoc :: http://autoforme.jeremyevans.net
24
24
  Source :: https://github.com/jeremyevans/autoforme
25
- IRC :: irc://irc.freenode.net/forme
26
- Google Group :: https://groups.google.com/forum/#!forum/ruby-forme
25
+ Discussion Forum :: https://github.com/jeremyevans/autoforme/discussions
27
26
  Bug Tracker :: https://github.com/jeremyevans/autoforme/issues
28
27
 
29
28
  = Features
@@ -161,12 +160,13 @@ These options are related to displayed output:
161
160
 
162
161
  form_attributes :: Hash of attributes to use for any form tags
163
162
  form_options :: Hash of Forme::Form options to pass for any forms created
164
- class_display_name :: The string to use when referring to the model class
163
+ class_display_name :: The string to use on pages when referring to the model class.
164
+ This defaults to the full class name.
165
165
  display_name :: The string to use when referring to a model instance. Can either be a symbol
166
166
  representing an instance method call, or a Proc called with the model object,
167
167
  the model object and type symbol, or the model object, type symbol, and request,
168
168
  depending on the arity of the Proc.
169
- link_name :: The string to use in links for the class
169
+ link_name :: The string to use in links for the class. This defaults to +class_display_name+.
170
170
  edit_html :: The html to use for a particular object edit field. Should be a proc that takes the
171
171
  model object, column symbol, type symbol, and request and returns the html to use.
172
172
  page_footer :: Override the default footer used for pages
@@ -174,6 +174,8 @@ page_header :: Override the default header used for pages
174
174
  show_html :: The html to use for displaying the value for an object field. Should be a proc that takes the
175
175
  model object, column symbol, type symbol, and request and returns the html to use.
176
176
  table_class :: The html class string to use for the browse and search tables
177
+ view_options :: Hash with options passed when rendering the view (how these options are used varies
178
+ in each of the supported web frameworks), e.g. <tt>view_options: {:layout => :formelayout}</tt>
177
179
 
178
180
  These hook options should be callable objects that are called with the model object and the request.
179
181
 
@@ -238,6 +240,66 @@ use AutoForme in Rails development mode. The best way to handle it is to call
238
240
  +AutoForme.for+ in the related controller file, and have an initializer reference
239
241
  the controller class, causing the controller file to be loaded.
240
242
 
243
+ = Roda
244
+
245
+ Because Roda uses a routing tree, unlike Rails and Sinatra, with Roda you need to
246
+ dispatch to the autoforme routes at the point in the routing tree where you want
247
+ to mount them. Additionally, the Roda support offers a Roda plugin for easier
248
+ configuration.
249
+
250
+ To mount the autoforme routes in the root of the application, you could do:
251
+
252
+ class App < Roda
253
+ plugin :autoforme do
254
+ model Artist
255
+ end
256
+
257
+ route do
258
+ # rest of routing tree
259
+ autoforme
260
+ end
261
+ end
262
+
263
+ To mount the routes in a subpath:
264
+
265
+ class App < Roda
266
+ plugin :autoforme do
267
+ model Artist
268
+ end
269
+
270
+ route do
271
+ r.on "admin" do
272
+ autoforme
273
+ end
274
+
275
+ # rest of routing tree
276
+ end
277
+ end
278
+
279
+ To handle multiple autoforme configurations, mounted at different subpaths:
280
+
281
+ class App < Roda
282
+ plugin :autoforme
283
+
284
+ autoforme(name: 'artists')
285
+ model Artist
286
+ end
287
+ autoforme(name: 'albums')
288
+ model Album
289
+ end
290
+
291
+ route do
292
+ r.on "artists" do
293
+ autoforme('artists')
294
+ end
295
+ r.on "albums" do
296
+ autoforme('albums')
297
+ end
298
+
299
+ # rest of routing tree
300
+ end
301
+ end
302
+
241
303
  = TODO
242
304
 
243
305
  * capybara-webkit tests for ajax behavior
@@ -87,7 +87,7 @@ module AutoForme
87
87
  else
88
88
  return false unless model.supported_action?(normalized_type, request)
89
89
 
90
- if title = TITLE_MAP[type]
90
+ if title = TITLE_MAP[@normalized_type]
91
91
  @title = "#{model.class_name} - #{title}"
92
92
  end
93
93
  end
@@ -227,10 +227,15 @@ module AutoForme
227
227
  # Options to use for the form. If the form uses POST, automatically adds the CSRF token.
228
228
  def form_opts(action=nil)
229
229
  opts = model.form_options_for(type, request).dup
230
- hidden_tags = opts[:hidden_tags] = []
231
- if csrf = request.csrf_token_hash(action)
232
- hidden_tags << lambda{|tag| csrf if (tag.attr[:method] || tag.attr['method']).to_s.upcase == 'POST'}
230
+
231
+ opts[:_before_post] = lambda do |form|
232
+ if csrf_hash = request.csrf_token_hash(action)
233
+ csrf_hash.each do |name, value|
234
+ form.tag(:input, :type=>:hidden, :name=>name, :value=>value)
235
+ end
236
+ end
233
237
  end
238
+
234
239
  opts
235
240
  end
236
241
 
@@ -626,13 +631,13 @@ module AutoForme
626
631
  # page.
627
632
  def association_link(mc, assoc_obj)
628
633
  if mc
629
- t = mc.object_display_name(:association, request, assoc_obj)
634
+ t = h(mc.object_display_name(:association, request, assoc_obj))
630
635
  if mc.supported_action?(type, request)
631
636
  t = "<a href=\"#{base_url_for("#{mc.link}/#{type}/#{mc.primary_key_value(assoc_obj)}")}\">#{t}</a>"
632
637
  end
633
638
  t
634
639
  else
635
- model.default_object_display_name(assoc_obj)
640
+ h(model.default_object_display_name(assoc_obj))
636
641
  end
637
642
  end
638
643
 
@@ -58,7 +58,11 @@ module AutoForme
58
58
  elsif @autoforme_action.request.xhr?
59
59
  render :html=>@autoforme_text.html_safe
60
60
  else
61
- render :inline=>"<%=raw @autoforme_text %>", :layout=>true
61
+ opts = framework.opts[:view_options]
62
+ opts = opts ? opts.dup : {}
63
+ opts[:layout] = true unless opts.has_key?(:layout)
64
+ opts[:inline] = "<%=raw @autoforme_text %>"
65
+ render opts
62
66
  end
63
67
  else
64
68
  render :plain=>'Unhandled Request', :status=>404
@@ -37,22 +37,42 @@ module AutoForme
37
37
  @env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i
38
38
  end
39
39
 
40
+ # Set the flash at notice level when redirecting, so it shows
41
+ # up on the redirected page.
42
+ def set_flash_notice(message)
43
+ @controller.flash[flash_symbol_keys? ? :notice : 'notice'] = message
44
+ end
45
+
46
+ # Set the current flash at error level, used when displaying
47
+ # pages when there is an error.
48
+ def set_flash_now_error(message)
49
+ @controller.flash.now[flash_symbol_keys? ? :error : 'error'] = message
50
+ end
51
+
40
52
  # Use Rack::Csrf for csrf protection if it is defined.
41
53
  def csrf_token_hash(action=nil)
42
54
  if @controller.respond_to?(:check_csrf!)
43
55
  # Using route_csrf plugin
44
- # :nocov:
45
56
  token = if @controller.use_request_specific_csrf_tokens?
46
57
  @controller.csrf_token(@controller.csrf_path(action))
58
+ # :nocov:
47
59
  else
48
60
  @controller.csrf_token
61
+ # :nocov:
49
62
  end
50
63
  {@controller.csrf_field=>token}
51
64
  # :nocov:
52
65
  elsif defined?(::Rack::Csrf)
53
66
  {::Rack::Csrf.field=>::Rack::Csrf.token(@env)}
67
+ # :nocov:
54
68
  end
55
69
  end
70
+
71
+ private
72
+
73
+ def flash_symbol_keys?
74
+ !@controller.opts[:sessions_convert_symbols]
75
+ end
56
76
  end
57
77
 
58
78
  attr_reader :route_proc
@@ -88,7 +108,10 @@ module AutoForme
88
108
  elsif @autoforme_action.request.xhr?
89
109
  @autoforme_text
90
110
  else
91
- view(:content=>@autoforme_text)
111
+ opts = framework.opts[:view_options]
112
+ opts = opts ? opts.dup : {}
113
+ opts[:content] = @autoforme_text
114
+ view(opts)
92
115
  end
93
116
  end
94
117
  end
@@ -50,7 +50,9 @@ module AutoForme
50
50
  elsif @autoforme_action.request.xhr?
51
51
  @autoforme_text
52
52
  else
53
- erb "<%= @autoforme_text %>".dup
53
+ opts = framework.opts[:view_options]
54
+ opts = opts ? opts.dup : {}
55
+ erb("<%= @autoforme_text %>".dup, opts ? opts.dup : {})
54
56
  end
55
57
  else
56
58
  pass
@@ -389,7 +389,7 @@ module AutoForme
389
389
 
390
390
  def normalize_mtm_associations(assocs)
391
391
  if assocs == :all
392
- mtm_association_names
392
+ editable_mtm_association_names
393
393
  else
394
394
  Array(assocs)
395
395
  end
@@ -98,14 +98,26 @@ module AutoForme
98
98
  ref[:keys].zip(ref[:primary_keys].map{|k| obj.send(k)})
99
99
  end
100
100
 
101
+ # Array of many to many association name strings for editable
102
+ # many to many associations.
103
+ def editable_mtm_association_names
104
+ association_names([:many_to_many]) do |r|
105
+ model.method_defined?(r.add_method) && model.method_defined?(r.remove_method)
106
+ end
107
+ end
108
+
101
109
  # Array of many to many association name strings.
102
110
  def mtm_association_names
103
111
  association_names([:many_to_many])
104
112
  end
105
113
 
106
- # Array of association name strings for given association types
114
+ # Array of association name strings for given association types. If a block is
115
+ # given, only include associations where the block returns truthy.
107
116
  def association_names(types=SUPPORTED_ASSOCIATION_TYPES)
108
- model.all_association_reflections.select{|r| types.include?(r[:type])}.map{|r| r[:name]}.sort_by(&:to_s)
117
+ model.all_association_reflections.
118
+ select{|r| types.include?(r[:type]) && (!block_given? || yield(r))}.
119
+ map{|r| r[:name]}.
120
+ sort_by(&:to_s)
109
121
  end
110
122
 
111
123
  # Save the object, returning the object if successful, or nil if not.
@@ -158,7 +170,7 @@ module AutoForme
158
170
  params = request.params
159
171
  ds = apply_associated_eager(:search, request, all_dataset_for(type, request))
160
172
  columns_for(:search_form, request).each do |c|
161
- if (v = params[c.to_s]) && !v.empty?
173
+ if (v = params[c.to_s]) && !(v = v.to_s).empty?
162
174
  if association?(c)
163
175
  ref = model.association_reflection(c)
164
176
  ads = ref.associated_dataset
@@ -168,9 +180,16 @@ module AutoForme
168
180
  primary_key = S.qualify(ref.associated_class.table_name, ref.primary_key)
169
181
  ds = ds.where(S.qualify(model.table_name, ref[:key])=>ads.where(primary_key=>v).select(primary_key))
170
182
  elsif column_type(c) == :string
171
- ds = ds.where(S.ilike(S.qualify(model.table_name, c), "%#{ds.escape_like(v.to_s)}%"))
183
+ ds = ds.where(S.ilike(S.qualify(model.table_name, c), "%#{ds.escape_like(v)}%"))
172
184
  else
173
- ds = ds.where(S.qualify(model.table_name, c)=>model.db.typecast_value(column_type(c), v))
185
+ begin
186
+ typecasted_value = model.db.typecast_value(column_type(c), v)
187
+ rescue S::InvalidValue
188
+ ds = ds.where(false)
189
+ break
190
+ else
191
+ ds = ds.where(S.qualify(model.table_name, c)=>typecasted_value)
192
+ end
174
193
  end
175
194
  end
176
195
  end
@@ -293,7 +312,17 @@ module AutoForme
293
312
  ids.each do |id|
294
313
  next if id.to_s.empty?
295
314
  ret = assoc_class ? assoc_class.with_pk(:association, request, id) : obj.send(:_apply_association_options, ref, ref.associated_class.dataset.clone).with_pk!(id)
296
- obj.send(meth, ret)
315
+ begin
316
+ model.db.transaction(:savepoint=>true){obj.send(meth, ret)}
317
+ rescue S::UniqueConstraintViolation
318
+ # Already added, safe to ignore
319
+ rescue S::ConstraintViolation
320
+ # Old versions of sqlite3 and jdbc-sqlite3 can raise generic
321
+ # ConstraintViolation instead of UniqueConstraintViolation
322
+ # :nocov:
323
+ raise unless model.db.database_type == :sqlite
324
+ # :nocov:
325
+ end
297
326
  end
298
327
  end
299
328
  end
@@ -6,7 +6,7 @@ module AutoForme
6
6
  MAJOR = 1
7
7
 
8
8
  # The minor version of AutoForme, updated for new feature releases of AutoForme.
9
- MINOR = 8
9
+ MINOR = 11
10
10
 
11
11
  # The patch version of AutoForme, updated only for bug fixes from the last
12
12
  # feature release.
data/lib/autoforme.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
3
  require 'forme'
4
- require 'thread'
5
4
  require 'rack/utils'
6
5
 
7
6
  module AutoForme
@@ -50,10 +49,10 @@ module AutoForme
50
49
  end
51
50
  end
52
51
 
53
- require 'autoforme/opts_attributes'
54
- require 'autoforme/model'
55
- require 'autoforme/framework'
56
- require 'autoforme/request'
57
- require 'autoforme/action'
58
- require 'autoforme/table'
59
- require 'autoforme/version'
52
+ require_relative 'autoforme/opts_attributes'
53
+ require_relative 'autoforme/model'
54
+ require_relative 'autoforme/framework'
55
+ require_relative 'autoforme/request'
56
+ require_relative 'autoforme/action'
57
+ require_relative 'autoforme/table'
58
+ require_relative 'autoforme/version'
@@ -1,6 +1,6 @@
1
1
  # frozen-string-literal: true
2
2
 
3
- require 'autoforme'
3
+ require_relative '../../autoforme'
4
4
 
5
5
  class Roda
6
6
  module RodaPlugins
data/spec/all.rb CHANGED
@@ -1,2 +1,2 @@
1
1
  puts "Running specs with #{ENV['FRAMEWORK']||'roda'} framework"
2
- Dir['./spec/*_spec.rb'].each{|f| require f}
2
+ Dir.new(File.dirname(__FILE__)).each{|f| require_relative f if f.end_with?('_spec.rb')}
@@ -1,4 +1,4 @@
1
- require './spec/spec_helper'
1
+ require_relative 'spec_helper'
2
2
 
3
3
  describe AutoForme do
4
4
  before(:all) do
@@ -51,6 +51,50 @@ describe AutoForme do
51
51
  page.all('td').map{|s| s.text}.must_equal ["Album1b", "Artist2", "Show", "Edit", "Delete"]
52
52
  end
53
53
 
54
+ it "should escape display names in association links" do
55
+ app_setup do
56
+ model Artist
57
+ model Album do
58
+ columns [:name, :artist]
59
+ end
60
+ association_links :all
61
+ end
62
+
63
+ visit("/Artist/new")
64
+ fill_in 'Name', :with=>'Art&"ist2'
65
+ click_button 'Create'
66
+
67
+ visit("/Album/new")
68
+ fill_in 'Name', :with=>'Album1'
69
+ select 'Art&"ist2'
70
+ click_button 'Create'
71
+
72
+ click_link 'Edit'
73
+ select 'Album1'
74
+ click_button 'Edit'
75
+ page.html.must_match(%r{- <a href="/Artist/edit/\d+">Art&amp;&quot;ist2})
76
+ end
77
+
78
+ it "should escape display names in association links" do
79
+ app_setup do
80
+ model Album do
81
+ columns [:name, :artist]
82
+ end
83
+ association_links :all
84
+ end
85
+
86
+ Artist.create(:name=>'Art&"ist2')
87
+ visit("/Album/new")
88
+ fill_in 'Name', :with=>'Album1'
89
+ select 'Art&"ist2'
90
+ click_button 'Create'
91
+
92
+ click_link 'Edit'
93
+ select 'Album1'
94
+ click_button 'Edit'
95
+ page.html.must_include("- Art&amp;&quot;ist2")
96
+ end
97
+
54
98
  it "should use text boxes for associated objects on new/edit/search forms if associated model uses autocompleting" do
55
99
  app_setup do
56
100
  model Artist do
data/spec/basic_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require './spec/spec_helper'
1
+ require_relative 'spec_helper'
2
2
 
3
3
  describe AutoForme do
4
4
  before(:all) do
@@ -12,6 +12,7 @@ describe AutoForme do
12
12
  it "should have basic functionality working" do
13
13
  app_setup(Artist)
14
14
  visit("/Artist/new")
15
+ page.html.must_include '<!DOCTYPE html>'
15
16
  page.title.must_equal 'Artist - New'
16
17
  fill_in 'Name', :with=>'TestArtistNew'
17
18
  click_button 'Create'
@@ -573,7 +574,7 @@ describe AutoForme do
573
574
  page.all('td').map{|s| s.text}.must_equal []
574
575
  end
575
576
 
576
- it "should correct handle validation errors" do
577
+ it "should correctly handle validation errors" do
577
578
  app_setup(Artist)
578
579
  Artist.send(:define_method, :validate) do
579
580
  errors.add(:name, "bad name") if name == 'Foo'
@@ -583,6 +584,7 @@ describe AutoForme do
583
584
  page.title.must_equal 'Artist - New'
584
585
  fill_in 'Name', :with=>'Foo'
585
586
  click_button 'Create'
587
+ page.title.must_equal 'Artist - New'
586
588
  page.html.must_include 'Error Creating Artist'
587
589
  page.html.must_include 'bad name'
588
590
  fill_in 'Name', :with=>'TestArtistNew'
@@ -597,6 +599,7 @@ describe AutoForme do
597
599
  click_button 'Edit'
598
600
  fill_in 'Name', :with=>'Foo'
599
601
  click_button 'Update'
602
+ page.title.must_equal 'Artist - Edit'
600
603
  page.html.must_include 'Error Updating Artist'
601
604
  page.html.must_include 'bad name'
602
605
  fill_in 'Name', :with=>'TestArtistUpdate'
@@ -605,6 +608,13 @@ describe AutoForme do
605
608
  page.html.must_match(/Name.+TestArtistUpdate/m)
606
609
  page.current_path.must_match %r{/Artist/edit/\d+}
607
610
  end
611
+
612
+ it "should support view_options" do
613
+ app_setup(Artist, view_options: {:layout=>false}){}
614
+ visit("/Artist/browse")
615
+ page.html.wont_include '<!DOCTYPE html>'
616
+ page.all('table').first['id'].must_equal 'autoforme_table'
617
+ end
608
618
  end
609
619
 
610
620
  describe AutoForme do
@@ -952,19 +962,29 @@ describe AutoForme do
952
962
  after(:all) do
953
963
  Object.send(:remove_const, :Artist)
954
964
  end
955
-
956
- it "should display decimals in float format in tables" do
965
+ before do
957
966
  app_setup(Artist)
958
967
  visit("/Artist/new")
959
968
  page.title.must_equal 'Artist - New'
960
969
  fill_in 'Num', :with=>'1.01'
961
970
  click_button 'Create'
971
+ visit("/Artist/browse")
972
+ end
973
+
974
+ it "should display decimals in float format in tables" do
962
975
  click_link 'Artist'
963
976
  page.all('tr td:first-child').map{|s| s.text}.must_equal %w'1.01'
964
977
  click_link 'Search'
965
978
  click_button 'Search'
966
979
  page.all('tr td:first-child').map{|s| s.text}.must_equal %w'1.01'
967
980
  end
981
+
982
+ it "should treat invalid search fields as returning no results" do
983
+ click_link 'Search'
984
+ fill_in 'Num', :with=>'3/3/2020'
985
+ click_button 'Search'
986
+ page.all('tr td:first-child').map{|s| s.text}.must_equal []
987
+ end
968
988
  end
969
989
 
970
990
  describe AutoForme do
data/spec/mtm_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require './spec/spec_helper'
1
+ require_relative 'spec_helper'
2
2
 
3
3
  describe AutoForme do
4
4
  before(:all) do
@@ -507,3 +507,115 @@ describe AutoForme do
507
507
  page.all('select')[1].all('option').map{|s| s.text}.must_equal ["Album2", "Album3"]
508
508
  end
509
509
  end
510
+
511
+ describe AutoForme do
512
+ before(:all) do
513
+ db_setup(:artists=>[[:name, :string]], :albums=>[[:name, :string]], :albums_artists=>proc{column :album_id, :integer, :table=>:albums; column :artist_id, :integer, :table=>:artists; primary_key [:album_id, :artist_id]})
514
+ model_setup(:Artist=>[:artists, [[:many_to_many, :albums]]], :Album=>[:albums, [[:many_to_many, :artists]]])
515
+ end
516
+ after(:all) do
517
+ Object.send(:remove_const, :Album)
518
+ Object.send(:remove_const, :Artist)
519
+ end
520
+
521
+ it "should handle unique constraint violation errors when adding associated objects" do
522
+ app_setup do
523
+ model Artist do
524
+ mtm_associations :albums
525
+ end
526
+ model Album
527
+ end
528
+
529
+ artist = Artist.create(:name=>'Artist1')
530
+ album = Album.create(:name=>'Album1')
531
+
532
+ visit("/Artist/mtm_edit")
533
+ page.title.must_equal 'Artist - Many To Many Edit'
534
+ select("Artist1")
535
+ click_button "Edit"
536
+
537
+ find('h2').text.must_equal 'Edit Albums for Artist1'
538
+ page.all('select')[0].all('option').map{|s| s.text}.must_equal ["Album1"]
539
+ page.all('select')[1].all('option').map{|s| s.text}.must_equal []
540
+ select("Album1", :from=>"Associate With")
541
+ artist.add_album(album)
542
+ click_button "Update"
543
+ page.html.must_include 'Updated albums association for Artist'
544
+ Artist.first.albums.map{|x| x.name}.must_equal %w'Album1'
545
+ end
546
+
547
+ it "should handle unique constraint violation errors when adding associated objects" do
548
+ app_setup do
549
+ model Artist do
550
+ mtm_associations :albums
551
+ end
552
+ model Album
553
+ end
554
+
555
+ artist = Artist.create(:name=>'Artist1')
556
+ album = Album.create(:name=>'Album1')
557
+ artist.add_album(album)
558
+
559
+ visit("/Artist/mtm_edit")
560
+ page.title.must_equal 'Artist - Many To Many Edit'
561
+ select("Artist1")
562
+ click_button "Edit"
563
+
564
+ find('h2').text.must_equal 'Edit Albums for Artist1'
565
+ page.all('select')[0].all('option').map{|s| s.text}.must_equal []
566
+ page.all('select')[1].all('option').map{|s| s.text}.must_equal ["Album1"]
567
+ select("Album1", :from=>"Disassociate From")
568
+ artist.remove_album(album)
569
+ click_button "Update"
570
+ page.html.must_include 'Updated albums association for Artist'
571
+ Artist.first.albums.map{|x| x.name}.must_equal []
572
+ end
573
+ end
574
+
575
+ describe AutoForme do
576
+ before(:all) do
577
+ db_setup(:artists=>[[:name, :string]], :albums=>[[:name, :string]], :albums_artists=>[[:album_id, :integer, {:table=>:albums}], [:artist_id, :integer, {:table=>:artists}]])
578
+ model_setup(:Artist=>[:artists, [[:many_to_many, :albums, :read_only=>true]]], :Album=>[:albums, [[:many_to_many, :artists, :read_only=>true]]])
579
+ end
580
+ after(:all) do
581
+ Object.send(:remove_const, :Album)
582
+ Object.send(:remove_const, :Artist)
583
+ end
584
+
585
+ it "should not automatically setup mtm support for read-only associations" do
586
+ app_setup do
587
+ model Artist do
588
+ mtm_associations :all
589
+ association_links :all
590
+ end
591
+ model Album do
592
+ mtm_associations :all
593
+ association_links :all
594
+ end
595
+ end
596
+
597
+ visit("/Artist/new")
598
+ page.html.wont_include 'Artist/mtm_edit'
599
+ fill_in 'Name', :with=>'Artist1'
600
+ click_button 'Create'
601
+ click_link 'Edit'
602
+ select 'Artist1'
603
+ click_button 'Edit'
604
+ page.html.must_include 'Albums'
605
+ page.html.wont_include '>associate<'
606
+ visit("/Artist/mtm_edit")
607
+ page.html.must_include 'Unhandled Request'
608
+
609
+ visit("/Album/new")
610
+ page.html.wont_include 'Album/mtm_edit'
611
+ fill_in 'Name', :with=>'Album1'
612
+ click_button 'Create'
613
+ click_link 'Edit'
614
+ select 'Album1'
615
+ click_button 'Edit'
616
+ page.html.must_include 'Artists'
617
+ page.html.wont_include '>associate<'
618
+ visit("/Album/mtm_edit")
619
+ page.html.must_include 'Unhandled Request'
620
+ end
621
+ end
@@ -1,8 +1,13 @@
1
- require 'rubygems'
1
+ require 'rails'
2
2
  require 'action_controller/railtie'
3
- require 'autoforme'
3
+ require_relative '../lib/autoforme'
4
4
 
5
5
  class AutoFormeSpec::App
6
+ class << self
7
+ # Workaround for action_view railtie deleting the finalizer
8
+ attr_accessor :av_finalizer
9
+ end
10
+
6
11
  def self.autoforme(klass=nil, opts={}, &block)
7
12
  sc = Class.new(Rails::Application)
8
13
  def sc.name
@@ -16,7 +21,7 @@ class AutoFormeSpec::App
16
21
  resolver = Class.new(ActionView::Resolver)
17
22
  resolver.class_eval do
18
23
  template = ActionView::Template
19
- t = [template.new(<<HTML, "layout", template.handler_for_extension(:erb), {:virtual_path=>'layout', :format=>'erb', :updated_at=>Time.now})]
24
+ code = (<<HTML)
20
25
  <!DOCTYPE html>
21
26
  <html>
22
27
  <head><title><%= @autoforme_action.title if @autoforme_action %></title></head>
@@ -30,6 +35,11 @@ class AutoFormeSpec::App
30
35
  <%= yield %>
31
36
  </body></html>"
32
37
  HTML
38
+ if Rails.version > '6'
39
+ t = [template.new(code, "layout", template.handler_for_extension(:erb), :virtual_path=>'layout', :format=>'erb', :locals=>[])]
40
+ else
41
+ t = [template.new(code, "layout", template.handler_for_extension(:erb), :virtual_path=>'layout', :format=>'erb', :updated_at=>Time.now)]
42
+ end
33
43
 
34
44
  define_method(:find_templates){|*args| t}
35
45
  end
@@ -57,19 +67,39 @@ HTML
57
67
  get 'session/set', :controller=>'autoforme', :action=>'session_set'
58
68
  end.inspect
59
69
  config.secret_token = st if Rails.respond_to?(:version) && Rails.version < '5.2'
70
+ config.hosts << "www.example.com" if config.respond_to?(:hosts)
60
71
  config.active_support.deprecation = :stderr
61
72
  config.middleware.delete(ActionDispatch::ShowExceptions)
62
73
  config.middleware.delete(Rack::Lock)
63
74
  config.secret_key_base = st*15
64
75
  config.eager_load = true
76
+ if Rails.version.start_with?('4')
77
+ # Work around issue in backported openssl environments where
78
+ # secret is 64 bytes intead of 32 bytes
79
+ require 'active_support/message_encryptor'
80
+ def (ActiveSupport::MessageEncryptor).new(secret, *signature_key_or_options)
81
+ obj = allocate
82
+ obj.send(:initialize, secret[0, 32], *signature_key_or_options)
83
+ obj
84
+ end
85
+ end
65
86
  if Rails.version > '4.2'
66
87
  config.action_dispatch.cookies_serializer = :json
67
88
  end
68
89
  if Rails.version > '5'
69
90
  # Force Rails to dispatch to correct controller
70
91
  ActionDispatch::Routing::RouteSet::Dispatcher.class_eval do
92
+ alias controller controller
71
93
  define_method(:controller){|_| controller}
72
94
  end
95
+ config.session_store :cookie_store, :key=>'_autoforme_test_session'
96
+ end
97
+ if Rails.version > '6'
98
+ if AutoFormeSpec::App.av_finalizer
99
+ config.action_view.finalize_compiled_template_methods = AutoFormeSpec::App.av_finalizer
100
+ else
101
+ AutoFormeSpec::App.av_finalizer = config.action_view.finalize_compiled_template_methods
102
+ end
73
103
  end
74
104
  initialize!
75
105
  end
data/spec/roda_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require './spec/spec_helper'
1
+ require_relative 'spec_helper'
2
2
 
3
3
  describe AutoForme do
4
4
  before(:all) do
@@ -1,11 +1,9 @@
1
- require 'rubygems'
2
1
  require 'roda'
3
- require 'autoforme'
2
+ require_relative '../lib/autoforme'
4
3
  require 'rack/csrf'
5
4
 
6
5
  begin
7
- require 'erubis'
8
- require 'tilt/erubis'
6
+ require 'tilt/erubi'
9
7
  rescue LoadError
10
8
  require 'tilt/erb'
11
9
  end
@@ -14,23 +12,38 @@ class AutoFormeSpec::App < Roda
14
12
  opts[:unsupported_block_result] = :raise
15
13
  opts[:unsupported_matcher] = :raise
16
14
  opts[:verbatim_string_matcher] = true
15
+ opts[:check_dynamic_arity] = opts[:check_arity] = :warn
17
16
 
18
17
  LAYOUT = <<HTML
19
18
  <!DOCTYPE html>
20
19
  <html>
21
20
  <head><title><%= @autoforme_action.title if @autoforme_action %></title></head>
22
21
  <body>
23
- <% if flash[:notice] %>
24
- <div class="alert alert-success"><p><%= flash[:notice] %></p></div>
22
+ <% if notice = opts[:sessions_convert_symbols] ? flash['notice'] : flash[:notice] %>
23
+ <div class="alert alert-success"><p><%= notice %></p></div>
25
24
  <% end %>
26
- <% if flash[:error] %>
27
- <div class="alert alert-error"><p><%= flash[:error] %></p></div>
25
+ <% if error = opts[:sessions_convert_symbols] ? flash['error'] : flash[:error] %>
26
+ <div class="alert alert-error"><p><%= error %></p></div>
28
27
  <% end %>
29
28
  <%= yield %>
30
29
  </body></html>"
31
30
  HTML
32
31
 
33
- use Rack::Session::Cookie, :secret => '1'
32
+ plugin :flash
33
+
34
+ if defined?(Roda::RodaVersionNumber) && Roda::RodaVersionNumber >= 30100
35
+ if ENV['RODA_ROUTE_CSRF'] == '0'
36
+ require 'roda/session_middleware'
37
+ opts[:sessions_convert_symbols] = true
38
+ use RodaSessionMiddleware, :secret=>SecureRandom.random_bytes(64)
39
+ else
40
+ ENV['RODA_ROUTE_CSRF'] ||= '1'
41
+ plugin :sessions, :secret=>SecureRandom.random_bytes(64)
42
+ end
43
+ else
44
+ use Rack::Session::Cookie, :secret => '1'
45
+ end
46
+
34
47
  if ENV['RODA_ROUTE_CSRF'].to_i > 0
35
48
  plugin :route_csrf, :require_request_specific_tokens=>ENV['RODA_ROUTE_CSRF'] == '1'
36
49
  else
@@ -42,7 +55,6 @@ HTML
42
55
  plugin :not_found do
43
56
  'Unhandled Request'
44
57
  end
45
- plugin :flash
46
58
 
47
59
  def self.autoforme(klass=nil, opts={}, &block)
48
60
  sc = Class.new(self)
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  require 'sequel'
3
2
  require 'logger'
4
3
 
@@ -1,6 +1,5 @@
1
- require 'rubygems'
2
1
  require 'sinatra/base'
3
- require 'autoforme'
2
+ require_relative '../lib/autoforme'
4
3
  require 'sinatra/flash'
5
4
  require 'rack/csrf'
6
5
 
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,3 @@
1
- require 'rubygems'
2
1
  $: << File.expand_path(File.join(__FILE__, '../../lib'))
3
2
  ENV['FRAMEWORK'] ||= 'roda'
4
3
 
@@ -19,7 +18,7 @@ if ENV['COVERAGE']
19
18
  end
20
19
  end
21
20
 
22
- require "./spec/#{ENV['FRAMEWORK']}_spec_helper"
21
+ require_relative "#{ENV['FRAMEWORK']}_spec_helper"
23
22
 
24
23
  require 'capybara'
25
24
  require 'capybara/dsl'
@@ -27,7 +26,7 @@ require 'rack/test'
27
26
 
28
27
  ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
29
28
  gem 'minitest'
30
- require 'minitest/autorun'
29
+ require 'minitest/global_expectations/autorun'
31
30
  require 'minitest/hooks/default'
32
31
 
33
32
  if ENV['WARNING']
@@ -35,7 +34,7 @@ if ENV['WARNING']
35
34
  Warning.ignore([:missing_ivar, :fixnum, :not_reached])
36
35
  end
37
36
 
38
- require './spec/sequel_spec_helper'
37
+ require_relative 'sequel_spec_helper'
39
38
 
40
39
  class Minitest::HooksSpec
41
40
  include Rack::Test::Methods
data/spec/unit_spec.rb CHANGED
@@ -1,4 +1,4 @@
1
- require './spec/spec_helper'
1
+ require_relative 'spec_helper'
2
2
 
3
3
  describe AutoForme do
4
4
  before(:all) do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autoforme
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.11.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-11 00:00:00.000000000 Z
11
+ date: 2021-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: forme
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 1.6.0
19
+ version: 2.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.6.0
26
+ version: 2.0.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rack
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: 1.1.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest-global_expectations
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: capybara
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -243,7 +257,12 @@ files:
243
257
  homepage: http://github.com/jeremyevans/autoforme
244
258
  licenses:
245
259
  - MIT
246
- metadata: {}
260
+ metadata:
261
+ bug_tracker_uri: https://github.com/jeremyevans/autoforme/issues
262
+ changelog_uri: http://autoforme.jeremyevans.net/files/CHANGELOG.html
263
+ documentation_uri: http://autoforme.jeremyevans.net
264
+ mailing_list_uri: https://github.com/jeremyevans/autoforme/discussions
265
+ source_code_uri: https://github.com/jeremyevans/autoforme
247
266
  post_install_message:
248
267
  rdoc_options:
249
268
  - "--quiet"
@@ -259,15 +278,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
259
278
  requirements:
260
279
  - - ">="
261
280
  - !ruby/object:Gem::Version
262
- version: '0'
281
+ version: 1.9.2
263
282
  required_rubygems_version: !ruby/object:Gem::Requirement
264
283
  requirements:
265
284
  - - ">="
266
285
  - !ruby/object:Gem::Version
267
286
  version: '0'
268
287
  requirements: []
269
- rubyforge_project:
270
- rubygems_version: 2.7.6
288
+ rubygems_version: 3.2.32
271
289
  signing_key:
272
290
  specification_version: 4
273
291
  summary: Web Administrative Console for Roda/Sinatra/Rails and Sequel::Model