autoforme 1.8.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG +26 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +66 -4
- data/lib/autoforme/action.rb +11 -6
- data/lib/autoforme/frameworks/rails.rb +5 -1
- data/lib/autoforme/frameworks/roda.rb +25 -2
- data/lib/autoforme/frameworks/sinatra.rb +3 -1
- data/lib/autoforme/model.rb +1 -1
- data/lib/autoforme/models/sequel.rb +35 -6
- data/lib/autoforme/version.rb +1 -1
- data/lib/autoforme.rb +7 -8
- data/lib/roda/plugins/autoforme.rb +1 -1
- data/spec/all.rb +1 -1
- data/spec/associations_spec.rb +45 -1
- data/spec/basic_spec.rb +24 -4
- data/spec/mtm_spec.rb +113 -1
- data/spec/rails_spec_helper.rb +33 -3
- data/spec/roda_spec.rb +1 -1
- data/spec/roda_spec_helper.rb +22 -10
- data/spec/sequel_spec_helper.rb +0 -1
- data/spec/sinatra_spec_helper.rb +1 -2
- data/spec/spec_helper.rb +3 -4
- data/spec/unit_spec.rb +1 -1
- metadata +26 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0198a23636933034920c066694a4acd8e58d5529fbf21ada5f542a7de444cc2c'
|
4
|
+
data.tar.gz: 6cf1f310f06018a91573472c7410c1dda1842b7f96593a82deaf9be5f2bdd524
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
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
|
-
|
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
|
data/lib/autoforme/action.rb
CHANGED
@@ -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[
|
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
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/autoforme/model.rb
CHANGED
@@ -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.
|
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
|
183
|
+
ds = ds.where(S.ilike(S.qualify(model.table_name, c), "%#{ds.escape_like(v)}%"))
|
172
184
|
else
|
173
|
-
|
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
|
-
|
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
|
data/lib/autoforme/version.rb
CHANGED
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
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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'
|
data/spec/all.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
puts "Running specs with #{ENV['FRAMEWORK']||'roda'} framework"
|
2
|
-
Dir
|
2
|
+
Dir.new(File.dirname(__FILE__)).each{|f| require_relative f if f.end_with?('_spec.rb')}
|
data/spec/associations_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
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&"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&"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
|
-
|
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
|
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
|
-
|
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
|
data/spec/rails_spec_helper.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
|
-
require '
|
1
|
+
require 'rails'
|
2
2
|
require 'action_controller/railtie'
|
3
|
-
|
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
|
-
|
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
data/spec/roda_spec_helper.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'roda'
|
3
|
-
|
2
|
+
require_relative '../lib/autoforme'
|
4
3
|
require 'rack/csrf'
|
5
4
|
|
6
5
|
begin
|
7
|
-
require '
|
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><%=
|
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><%=
|
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
|
-
|
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)
|
data/spec/sequel_spec_helper.rb
CHANGED
data/spec/sinatra_spec_helper.rb
CHANGED
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
|
-
|
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
|
-
|
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
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.
|
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:
|
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:
|
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:
|
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:
|
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
|
-
|
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
|