autoforme 1.9.1 → 1.10.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: 7d0bdcb16302f8c39805290a5f31dc4e6317aacb91eea042662d949fe5688c6f
4
- data.tar.gz: 3e9d217d00951f5bb008d6278ec2642fb6a2f1cc2a6511d8a76cfb3f71c1a46d
3
+ metadata.gz: 6f9fb399d144c4bfc7eed6e2a4f7497cab7bc25b8409d555c4f67fb91bad9bb7
4
+ data.tar.gz: bffb9114ef162400c639abb1ee17b7eb42ced481294d152e2603711c01752274
5
5
  SHA512:
6
- metadata.gz: 3f8701563f00846a37ad405852ef015c4c24c8634e7a5ecf2e28844fca78dee54c3b46cda1d3e7747eb40b8f5e3ed93df1c703c9c3d933cb0e7f4a8d485f592f
7
- data.tar.gz: e54401c38c27521d6d55ac706bdd0d1867c4adf15771b38f6ce4ce364b20ff6b01715a9b37195e5e1df571de866ae91f005b085e3b4de93d492f2351b758a79b
6
+ metadata.gz: 15cd700c5edc91d860dcec0209dae57dd3db48ebe2bc6cf66f19b9d786d7a51b23f909ec80a0a2333af8d9c42c165690dd6710c26ebfadfbfa93c9d17618976b
7
+ data.tar.gz: 0c4a42c4e34e398209f98e85303558ef95447eb6a1e8f6351a182f214a1dab7b754f86aca8d81d35ba32d5890a09fa417a28c7e8212a2284df5d265c776533e8
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ === 1.10.0 (2021-08-27)
2
+
3
+ * Do not consider read_only many_to_many associations to be editable (jeremyevans)
4
+
5
+ * Ignore unique constraint violations when adding associated objects in mtm_update (jeremyevans)
6
+
7
+ * Handle search fields that cannot be typecast correctly by returning no results (jeremyevans)
8
+
1
9
  === 1.9.1 (2019-07-22)
2
10
 
3
11
  * [SECURITY] Escape object display name when displaying association links (adam12)
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
@@ -238,6 +238,66 @@ use AutoForme in Rails development mode. The best way to handle it is to call
238
238
  +AutoForme.for+ in the related controller file, and have an initializer reference
239
239
  the controller class, causing the controller file to be loaded.
240
240
 
241
+ = Roda
242
+
243
+ Because Roda uses a routing tree, unlike Rails and Sinatra, with Roda you need to
244
+ dispatch to the autoforme routes at the point in the routing tree where you want
245
+ to mount them. Additionally, the Roda support offers a Roda plugin for easier
246
+ configuration.
247
+
248
+ To mount the autoforme routes in the root of the application, you could do:
249
+
250
+ class App < Roda
251
+ plugin :autoforme do
252
+ model Artist
253
+ end
254
+
255
+ route do
256
+ # rest of routing tree
257
+ autoforme
258
+ end
259
+ end
260
+
261
+ To mount the routes in a subpath:
262
+
263
+ class App < Roda
264
+ plugin :autoforme do
265
+ model Artist
266
+ end
267
+
268
+ route do
269
+ r.on "admin" do
270
+ autoforme
271
+ end
272
+
273
+ # rest of routing tree
274
+ end
275
+ end
276
+
277
+ To handle multiple autoforme configurations, mounted at different subpaths:
278
+
279
+ class App < Roda
280
+ plugin :autoforme
281
+
282
+ autoforme(name: 'artists')
283
+ model Artist
284
+ end
285
+ autoforme(name: 'albums')
286
+ model Album
287
+ end
288
+
289
+ route do
290
+ r.on "artists" do
291
+ autoforme('artists')
292
+ end
293
+ r.on "albums" do
294
+ autoforme('albums')
295
+ end
296
+
297
+ # rest of routing tree
298
+ end
299
+ end
300
+
241
301
  = TODO
242
302
 
243
303
  * capybara-webkit tests for ajax behavior
@@ -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,11 @@ 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
+ end
297
320
  end
298
321
  end
299
322
  end
@@ -6,11 +6,11 @@ module AutoForme
6
6
  MAJOR = 1
7
7
 
8
8
  # The minor version of AutoForme, updated for new feature releases of AutoForme.
9
- MINOR = 9
9
+ MINOR = 10
10
10
 
11
11
  # The patch version of AutoForme, updated only for bug fixes from the last
12
12
  # feature release.
13
- TINY = 1
13
+ TINY = 0
14
14
 
15
15
  # Version constant, use <tt>AutoForme.version</tt> instead.
16
16
  VERSION = "#{MAJOR}.#{MINOR}.#{TINY}".freeze
data/spec/basic_spec.rb CHANGED
@@ -954,19 +954,29 @@ describe AutoForme do
954
954
  after(:all) do
955
955
  Object.send(:remove_const, :Artist)
956
956
  end
957
-
958
- it "should display decimals in float format in tables" do
957
+ before do
959
958
  app_setup(Artist)
960
959
  visit("/Artist/new")
961
960
  page.title.must_equal 'Artist - New'
962
961
  fill_in 'Num', :with=>'1.01'
963
962
  click_button 'Create'
963
+ visit("/Artist/browse")
964
+ end
965
+
966
+ it "should display decimals in float format in tables" do
964
967
  click_link 'Artist'
965
968
  page.all('tr td:first-child').map{|s| s.text}.must_equal %w'1.01'
966
969
  click_link 'Search'
967
970
  click_button 'Search'
968
971
  page.all('tr td:first-child').map{|s| s.text}.must_equal %w'1.01'
969
972
  end
973
+
974
+ it "should treat invalid search fields as returning no results" do
975
+ click_link 'Search'
976
+ fill_in 'Num', :with=>'3/3/2020'
977
+ click_button 'Search'
978
+ page.all('tr td:first-child').map{|s| s.text}.must_equal []
979
+ end
970
980
  end
971
981
 
972
982
  describe AutoForme do
data/spec/mtm_spec.rb CHANGED
@@ -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 'MTM'
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 'MTM'
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,14 @@
1
1
  require 'rubygems'
2
+ require 'rails'
2
3
  require 'action_controller/railtie'
3
4
  require 'autoforme'
4
5
 
5
6
  class AutoFormeSpec::App
7
+ class << self
8
+ # Workaround for action_view railtie deleting the finalizer
9
+ attr_accessor :av_finalizer
10
+ end
11
+
6
12
  def self.autoforme(klass=nil, opts={}, &block)
7
13
  sc = Class.new(Rails::Application)
8
14
  def sc.name
@@ -16,7 +22,7 @@ class AutoFormeSpec::App
16
22
  resolver = Class.new(ActionView::Resolver)
17
23
  resolver.class_eval do
18
24
  template = ActionView::Template
19
- t = [template.new(<<HTML, "layout", template.handler_for_extension(:erb), {:virtual_path=>'layout', :format=>'erb', :updated_at=>Time.now})]
25
+ code = (<<HTML)
20
26
  <!DOCTYPE html>
21
27
  <html>
22
28
  <head><title><%= @autoforme_action.title if @autoforme_action %></title></head>
@@ -30,6 +36,11 @@ class AutoFormeSpec::App
30
36
  <%= yield %>
31
37
  </body></html>"
32
38
  HTML
39
+ if Rails.version > '6'
40
+ t = [template.new(code, "layout", template.handler_for_extension(:erb), :virtual_path=>'layout', :format=>'erb', :locals=>[])]
41
+ else
42
+ t = [template.new(code, "layout", template.handler_for_extension(:erb), :virtual_path=>'layout', :format=>'erb', :updated_at=>Time.now)]
43
+ end
33
44
 
34
45
  define_method(:find_templates){|*args| t}
35
46
  end
@@ -57,11 +68,23 @@ HTML
57
68
  get 'session/set', :controller=>'autoforme', :action=>'session_set'
58
69
  end.inspect
59
70
  config.secret_token = st if Rails.respond_to?(:version) && Rails.version < '5.2'
71
+ config.hosts << "www.example.com" if config.respond_to?(:hosts)
60
72
  config.active_support.deprecation = :stderr
61
73
  config.middleware.delete(ActionDispatch::ShowExceptions)
62
74
  config.middleware.delete(Rack::Lock)
63
75
  config.secret_key_base = st*15
64
76
  config.eager_load = true
77
+ if Rails.version.start_with?('4')
78
+ # Work around issue in backported openssl environments where
79
+ # secret is 64 bytes intead of 32 bytes
80
+ require 'active_support/message_encryptor'
81
+ ActiveSupport::MessageEncryptor.send :prepend, Module.new {
82
+ def initialize(secret, *signature_key_or_options)
83
+ secret = secret[0, 32]
84
+ super
85
+ end
86
+ }
87
+ end
65
88
  if Rails.version > '4.2'
66
89
  config.action_dispatch.cookies_serializer = :json
67
90
  end
@@ -70,6 +93,14 @@ HTML
70
93
  ActionDispatch::Routing::RouteSet::Dispatcher.class_eval do
71
94
  define_method(:controller){|_| controller}
72
95
  end
96
+ config.session_store :cookie_store, :key=>'_autoforme_test_session'
97
+ end
98
+ if Rails.version > '6'
99
+ if AutoFormeSpec::App.av_finalizer
100
+ config.action_view.finalize_compiled_template_methods = AutoFormeSpec::App.av_finalizer
101
+ else
102
+ AutoFormeSpec::App.av_finalizer = config.action_view.finalize_compiled_template_methods
103
+ end
73
104
  end
74
105
  initialize!
75
106
  end
data/spec/spec_helper.rb CHANGED
@@ -27,7 +27,7 @@ require 'rack/test'
27
27
 
28
28
  ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
29
29
  gem 'minitest'
30
- require 'minitest/autorun'
30
+ require 'minitest/global_expectations/autorun'
31
31
  require 'minitest/hooks/default'
32
32
 
33
33
  if ENV['WARNING']
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.9.1
4
+ version: 1.10.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: 2019-07-22 00:00:00.000000000 Z
11
+ date: 2021-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: forme
@@ -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"
@@ -266,7 +285,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
266
285
  - !ruby/object:Gem::Version
267
286
  version: '0'
268
287
  requirements: []
269
- rubygems_version: 3.0.3
288
+ rubygems_version: 3.2.22
270
289
  signing_key:
271
290
  specification_version: 4
272
291
  summary: Web Administrative Console for Roda/Sinatra/Rails and Sequel::Model