inline_forms_installer 8.1.8 → 8.1.10
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/lib/inline_forms_installer/installer_core.rb +108 -24
- data/lib/inline_forms_installer/version.rb +1 -1
- data/lib/installer_templates/example_app_assets/sample.txt +1 -0
- data/lib/installer_templates/example_app_assets/sample.wav +0 -0
- data/lib/installer_templates/example_app_assets/sample_cover.png +0 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_showcase_locales_associations_test.rb +86 -0
- data/lib/installer_templates/example_app_tests/test/integration/example_app_showcase_numeric_fields_test.rb +79 -4
- data/lib/installer_templates/example_app_tests/test/integration/example_app_showcase_page_render_test.rb +15 -12
- data/lib/installer_templates/example_app_tests/test/models/example_app_form_element_showcase_test.rb +63 -1
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e8a8a9d34cdcedea81beaadb7a2db76e36a53754e6edf224e65066249cd4b94
|
|
4
|
+
data.tar.gz: 3c98242f205d111da4e51d7fe6caae53fcad4ae9da9ddd8482a167845d5a7895
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b61043ca27f2902b5cd1c3c3518ecd7bd267f3ad43179ceac4e0cd2af592d07978c86c004d4f569d2eeed8432d506a57906a8644346333a753a1ba0fe8608f3b
|
|
7
|
+
data.tar.gz: 003bea7e68f32bf009f7e681f4221e30788909856d045e2d16c94172200ddc699287b2bf6be878726166a350a78bf8e327087509faaea38242c9a585f9edf282
|
|
@@ -46,10 +46,17 @@ def install_prerelease_gems_from_roots!
|
|
|
46
46
|
return if roots.empty?
|
|
47
47
|
|
|
48
48
|
%w[validation_hints inline_forms inline_forms_installer].each do |name|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
# Pick the *highest version*, not the highest filename. String sort
|
|
50
|
+
# placed `inline_forms-8.1.7.gem` above `inline_forms-8.1.10.gem`
|
|
51
|
+
# because "7" > "1" lexicographically — silently picking up a stale
|
|
52
|
+
# gem build on every release once minor versions cross a digit
|
|
53
|
+
# boundary. Parse the version out of the filename with Gem::Version
|
|
54
|
+
# so the comparison is numeric.
|
|
55
|
+
candidates = roots.flat_map { |root| Dir[File.join(root, "#{name}-*.gem")] }
|
|
56
|
+
gem_file = candidates.max_by do |path|
|
|
57
|
+
ver_str = File.basename(path, ".gem").sub(/\A#{Regexp.escape(name)}-/, "")
|
|
58
|
+
Gem::Version.new(ver_str) rescue Gem::Version.new("0")
|
|
59
|
+
end
|
|
53
60
|
next unless gem_file && File.file?(gem_file)
|
|
54
61
|
|
|
55
62
|
say "- Installing #{File.basename(gem_file)} into app gemset..."
|
|
@@ -431,7 +438,16 @@ END_INITIALIZER
|
|
|
431
438
|
# Create Locales
|
|
432
439
|
say "- Create locales"
|
|
433
440
|
generate "inline_forms", "Locale name:string title:string #{user_cfg.table_name}:has_many _enabled:yes _list_order:title _presentation:\#{title}"
|
|
434
|
-
|
|
441
|
+
# Seed four locales so the FormElementShowcase HABTM :locales demo
|
|
442
|
+
# (added under --example) has something to check on/off. `id: 1` (en) is
|
|
443
|
+
# the default the admin user is bound to in the line below; the other
|
|
444
|
+
# three are inert until selected by the showcase rows.
|
|
445
|
+
append_to_file "db/seeds.rb", <<~LOCALE_SEED
|
|
446
|
+
Locale.create({ id: 1, name: 'en', title: 'English' })
|
|
447
|
+
Locale.create({ id: 2, name: 'nl', title: 'Nederlands' })
|
|
448
|
+
Locale.create({ id: 3, name: 'de', title: 'Deutsch' })
|
|
449
|
+
Locale.create({ id: 4, name: 'fr', title: 'Français' })
|
|
450
|
+
LOCALE_SEED
|
|
435
451
|
|
|
436
452
|
# Create Roles
|
|
437
453
|
say "- Create roles"
|
|
@@ -1056,7 +1072,7 @@ if ENV['install_example'] == 'true'
|
|
|
1056
1072
|
# ---------------------------------------------------------------------
|
|
1057
1073
|
say "- Generating FormElementShowcase (one resource per kept Tier 1 form_element)..."
|
|
1058
1074
|
sleep 1
|
|
1059
|
-
run %q{bundle exec rails g inline_forms FormElementShowcase title:string body_plain_area:plain_text_area count:integer_field price:decimal_field amount:money_field meeting_date:date_select meeting_time:time_select birth_month:month_select start_month:month_year_picker is_active:check_box gender:radio_button rating_int:dropdown_with_integers priority:dropdown_with_values priority2:dropdown_with_values stars:dropdown_with_values_with_stars scale_int:scale_with_integers scale_val:scale_with_values attachment:file_field jingle:audio_field cover:image_field description:rich_text
|
|
1075
|
+
run %q{bundle exec rails g inline_forms FormElementShowcase title:string body_plain_area:plain_text_area count:integer_field price:decimal_field amount:money_field latitude:decimal_field{9,6} longitude:decimal_field{10,6} meeting_date:date_select meeting_time:time_select birth_month:month_select start_month:month_year_picker is_active:check_box gender:radio_button rating_int:dropdown_with_integers priority:dropdown_with_values priority2:dropdown_with_values stars:dropdown_with_values_with_stars scale_int:scale_with_integers scale_val:scale_with_values attachment:file_field jingle:audio_field cover:image_field description:rich_text locales:has_and_belongs_to_many _enabled:yes _list_order:title _list_search:title _presentation:'#{title}'}
|
|
1060
1076
|
|
|
1061
1077
|
say "- Generating Attachment + Jingle uploaders (Cover reuses ImageUploader)..."
|
|
1062
1078
|
run "bundle exec rails generate uploader Attachment"
|
|
@@ -1101,8 +1117,28 @@ if ENV['install_example'] == 'true'
|
|
|
1101
1117
|
# keeps PaperTrail revert paths (and the seeded second row, which leaves
|
|
1102
1118
|
# count nil) valid. The integration test covers the explicit error
|
|
1103
1119
|
# path by POSTing `count: "abc"` to /form_element_showcases.
|
|
1120
|
+
#
|
|
1121
|
+
# `locales_display` is a virtual alias for the HABTM :locales association
|
|
1122
|
+
# so the same association can render twice in the attribute list: once
|
|
1123
|
+
# editable (`[:locales, :check_list]`) and once read-only
|
|
1124
|
+
# (`[:locales_display, :info_list]`). Inline-forms keys turbo frames by
|
|
1125
|
+
# attribute name, so we need a distinct name for the second row — info_list
|
|
1126
|
+
# has no `_update` method, hence the wrapper rather than a separate column.
|
|
1127
|
+
#
|
|
1128
|
+
# NOTE: this has to be `def locales_display; locales; end`, not
|
|
1129
|
+
# `alias_method :locales_display, :locales`. `alias_method` resolves the
|
|
1130
|
+
# source method at class-load time, but the `has_and_belongs_to_many
|
|
1131
|
+
# :locales` declaration that defines `#locales` is injected lower in the
|
|
1132
|
+
# file (line ~12), so an alias_method here raises
|
|
1133
|
+
# `NameError: undefined method 'locales' for class 'FormElementShowcase'`.
|
|
1134
|
+
# A `def` body is parsed but only resolved at call time, side-stepping
|
|
1135
|
+
# the ordering hazard.
|
|
1104
1136
|
inject_into_file "app/models/form_element_showcase.rb",
|
|
1105
|
-
"\n validates :count, numericality: { only_integer: true }, allow_blank: true\n
|
|
1137
|
+
"\n validates :count, numericality: { only_integer: true }, allow_blank: true\n" \
|
|
1138
|
+
" validates :price, numericality: true, allow_blank: true\n" \
|
|
1139
|
+
" validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }, allow_blank: true\n" \
|
|
1140
|
+
" validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }, allow_blank: true\n" \
|
|
1141
|
+
" mount_uploader :attachment, AttachmentUploader\n monetize :amount_cents\n\n def locales_display\n locales\n end\n",
|
|
1106
1142
|
after: "class FormElementShowcase < ApplicationRecord\n"
|
|
1107
1143
|
|
|
1108
1144
|
# Value-bearing rows for every form_element that needs a values hash
|
|
@@ -1139,13 +1175,18 @@ if ENV['install_example'] == 'true'
|
|
|
1139
1175
|
gsub_file "app/models/form_element_showcase.rb", from, to
|
|
1140
1176
|
end
|
|
1141
1177
|
|
|
1142
|
-
# Insert :
|
|
1143
|
-
#
|
|
1144
|
-
#
|
|
1145
|
-
#
|
|
1178
|
+
# Insert :locales (editable check_list), :locales_display (read-only
|
|
1179
|
+
# info_list mirror of the same association — see the alias_method
|
|
1180
|
+
# above), :header_meta, and the timestamps after the rich_text row.
|
|
1181
|
+
# The generator does not emit a row for `locales:has_and_belongs_to_many`
|
|
1182
|
+
# (relation? is true), so we add the rows manually here. Locale (not
|
|
1183
|
+
# Role) was chosen because Role is reserved for the user/Member model
|
|
1184
|
+
# in the inline_forms example app (the join sits under roles_users);
|
|
1185
|
+
# Locale already has a `_presentation` returning `title`, which is
|
|
1186
|
+
# exactly what `info_list_show` renders per row.
|
|
1146
1187
|
gsub_file "app/models/form_element_showcase.rb",
|
|
1147
1188
|
"[ :description, :rich_text ], \n",
|
|
1148
|
-
"[ :description, :rich_text ], \n [ :
|
|
1189
|
+
"[ :description, :rich_text ], \n [ :locales, :check_list ], \n [ :locales_display, :info_list ], \n [ :header_meta, :header ], \n [ :created_at, :info ], \n [ :updated_at, :info ], \n"
|
|
1149
1190
|
|
|
1150
1191
|
# Locale keys for the showcase attributes. Headers + the timestamps
|
|
1151
1192
|
# need explicit labels so `human_attribute_name` does not fall back to
|
|
@@ -1169,8 +1210,10 @@ if ENV['install_example'] == 'true'
|
|
|
1169
1210
|
amount: Amount
|
|
1170
1211
|
meeting_date: Meeting date
|
|
1171
1212
|
meeting_time: Meeting time
|
|
1213
|
+
latitude: Latitude
|
|
1214
|
+
longitude: Longitude
|
|
1172
1215
|
birth_month: Birth month
|
|
1173
|
-
start_month: Start month
|
|
1216
|
+
start_month: Start month and year
|
|
1174
1217
|
is_active: Is active
|
|
1175
1218
|
gender: Gender
|
|
1176
1219
|
rating_int: Rating
|
|
@@ -1183,7 +1226,8 @@ if ENV['install_example'] == 'true'
|
|
|
1183
1226
|
jingle: Jingle
|
|
1184
1227
|
cover: Cover
|
|
1185
1228
|
description: Description
|
|
1186
|
-
|
|
1229
|
+
locales: Locales (editable)
|
|
1230
|
+
locales_display: Locales (read-only)
|
|
1187
1231
|
created_at: Created at
|
|
1188
1232
|
updated_at: Updated at
|
|
1189
1233
|
END_SHOWCASE_LOCALE
|
|
@@ -1199,22 +1243,36 @@ if ENV['install_example'] == 'true'
|
|
|
1199
1243
|
copy_file src, File.join("app/assets/images", "#{n}stars.png") if File.exist?(src)
|
|
1200
1244
|
end
|
|
1201
1245
|
|
|
1202
|
-
#
|
|
1203
|
-
#
|
|
1204
|
-
|
|
1246
|
+
# File/audio/image upload sample assets for the full-demo seed. Copied
|
|
1247
|
+
# under db/seed_uploads so the seed migration can read them at
|
|
1248
|
+
# db:migrate time and store them through CarrierWave. Mirrors the
|
|
1249
|
+
# db/seed_images convention used for the Photo gallery seed above.
|
|
1250
|
+
%w[sample.txt sample.wav sample_cover.png].each do |basename|
|
|
1251
|
+
src = File.join(showcase_assets_root, basename)
|
|
1252
|
+
copy_file src, File.join("db/seed_uploads", basename) if File.exist?(src)
|
|
1253
|
+
end
|
|
1254
|
+
|
|
1255
|
+
# Join table for has_and_belongs_to_many :locales. Mirrors the
|
|
1256
|
+
# roles_users join migration created for the user model above. Locale
|
|
1257
|
+
# (not Role) was chosen because Role is reserved for the Member/User
|
|
1258
|
+
# auth model; using it here would coincidentally share the same join
|
|
1259
|
+
# row pool as roles_users which is confusing for the demo. The four
|
|
1260
|
+
# locales seeded above (en/nl/de/fr) give the editable check_list
|
|
1261
|
+
# something interesting to toggle.
|
|
1262
|
+
say "- Creating form_element_showcases_locales join migration..."
|
|
1205
1263
|
sleep 1
|
|
1206
1264
|
habtm_ts = Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
1207
|
-
create_file "db/migrate/#{habtm_ts}
|
|
1208
|
-
class
|
|
1265
|
+
create_file "db/migrate/#{habtm_ts}_create_join_table_form_element_showcases_locales.rb", <<-HABTM_MIGRATION.strip_heredoc
|
|
1266
|
+
class CreateJoinTableFormElementShowcasesLocales < ActiveRecord::Migration[8.1]
|
|
1209
1267
|
def self.up
|
|
1210
|
-
create_table :
|
|
1268
|
+
create_table :form_element_showcases_locales, id: false, force: true do |t|
|
|
1211
1269
|
t.integer :form_element_showcase_id
|
|
1212
|
-
t.integer :
|
|
1270
|
+
t.integer :locale_id
|
|
1213
1271
|
end
|
|
1214
1272
|
end
|
|
1215
1273
|
|
|
1216
1274
|
def self.down
|
|
1217
|
-
drop_table :
|
|
1275
|
+
drop_table :form_element_showcases_locales
|
|
1218
1276
|
end
|
|
1219
1277
|
end
|
|
1220
1278
|
HABTM_MIGRATION
|
|
@@ -1235,6 +1293,11 @@ if ENV['install_example'] == 'true'
|
|
|
1235
1293
|
s.count = 7
|
|
1236
1294
|
s.price = "12.34"
|
|
1237
1295
|
s.amount = Money.from_amount(99.95, "USD") if s.respond_to?(:amount=)
|
|
1296
|
+
# Curaçao (Willemstad). Exactly representable in decimal(9,6)
|
|
1297
|
+
# and decimal(10,6) — useful for the precision-survival test
|
|
1298
|
+
# that asserts the round-trip is bit-identical, not float-fuzzy.
|
|
1299
|
+
s.latitude = BigDecimal("12.123456")
|
|
1300
|
+
s.longitude = BigDecimal("-68.987654")
|
|
1238
1301
|
s.meeting_date = Date.new(2026, 6, 1)
|
|
1239
1302
|
s.meeting_time = Time.utc(2000, 1, 1, 14, 30)
|
|
1240
1303
|
s.birth_month = 7
|
|
@@ -1249,9 +1312,30 @@ if ENV['install_example'] == 'true'
|
|
|
1249
1312
|
s.scale_val = 2
|
|
1250
1313
|
s.description = "<p>A rich-text paragraph for the showcase.</p>"
|
|
1251
1314
|
end
|
|
1252
|
-
|
|
1253
|
-
|
|
1315
|
+
# Attach the default locale (en) so the editable check_list and the
|
|
1316
|
+
# paired read-only info_list both have something to show. The other
|
|
1317
|
+
# seeded locales (nl/de/fr) stay unchecked so the toggle UX is
|
|
1318
|
+
# exercised when the user opens the check_list.
|
|
1319
|
+
if defined?(Locale) && Locale.exists?(1) && full.locales.empty?
|
|
1320
|
+
full.locales << Locale.find(1)
|
|
1321
|
+
end
|
|
1322
|
+
|
|
1323
|
+
# File/audio/image uploads for the full demo. The asset files are
|
|
1324
|
+
# copied into db/seed_uploads/ from lib/installer_templates/example_app_assets/
|
|
1325
|
+
# at install time (see the asset-copy block above), so this
|
|
1326
|
+
# migration can open them at db:migrate time and hand File handles
|
|
1327
|
+
# to CarrierWave for storage in public/uploads/.
|
|
1328
|
+
seed_uploads = Rails.root.join("db", "seed_uploads")
|
|
1329
|
+
{
|
|
1330
|
+
attachment: seed_uploads.join("sample.txt"),
|
|
1331
|
+
jingle: seed_uploads.join("sample.wav"),
|
|
1332
|
+
cover: seed_uploads.join("sample_cover.png"),
|
|
1333
|
+
}.each do |attr, path|
|
|
1334
|
+
next unless path.file?
|
|
1335
|
+
next if full.public_send(attr).present?
|
|
1336
|
+
File.open(path, "rb") { |io| full.public_send("\#{attr}=", io) }
|
|
1254
1337
|
end
|
|
1338
|
+
full.save! if full.changed?
|
|
1255
1339
|
|
|
1256
1340
|
# "Empty" refers to the role and uploader fields (their empty
|
|
1257
1341
|
# branches need to render). The other fields keep valid values
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Sample attachment file generated by inline_forms_installer for the FormElementShowcase demo. You can replace this with any file.
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../example_app/example_integration_test_case"
|
|
4
|
+
|
|
5
|
+
# HABTM helpers on FormElementShowcase:
|
|
6
|
+
# check_list (locales) -- editable multi-select
|
|
7
|
+
# info_list (locales_display) -- read-only mirror of the same association
|
|
8
|
+
# via `alias_method :locales_display, :locales`
|
|
9
|
+
#
|
|
10
|
+
# Locale (not Role) is the showcase association because Role is reserved
|
|
11
|
+
# for the auth Member/User model in this example app; reusing it on
|
|
12
|
+
# FormElementShowcase would coincidentally share rows with roles_users
|
|
13
|
+
# which is confusing.
|
|
14
|
+
class ExampleAppShowcaseLocalesAssociationsTest < ExampleAppIntegrationTestCase
|
|
15
|
+
setup do
|
|
16
|
+
@en = Locale.find_or_create_by!(name: "en") { |l| l.title = "English" }
|
|
17
|
+
@nl = Locale.find_or_create_by!(name: "nl") { |l| l.title = "Nederlands" }
|
|
18
|
+
@de = Locale.find_or_create_by!(name: "de") { |l| l.title = "Deutsch" }
|
|
19
|
+
|
|
20
|
+
@showcase = FormElementShowcase.find_or_create_by!(title: "Locales demo")
|
|
21
|
+
@showcase.locales.clear
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def field_frame(attr)
|
|
25
|
+
"form_element_showcase_#{@showcase.id}_#{attr}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def field_headers(attr)
|
|
29
|
+
{ "Turbo-Frame" => field_frame(attr), "Accept" => "text/html" }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "check_list locales toggles the HABTM association" do
|
|
33
|
+
frame = field_frame(:locales)
|
|
34
|
+
|
|
35
|
+
# Initially empty; PUT with two locales checked.
|
|
36
|
+
put form_element_showcase_path(
|
|
37
|
+
@showcase,
|
|
38
|
+
attribute: "locales",
|
|
39
|
+
form_element: "check_list",
|
|
40
|
+
update: frame
|
|
41
|
+
), params: { locales: { @en.id.to_s => 1, @de.id.to_s => 1 } }, headers: field_headers(:locales)
|
|
42
|
+
|
|
43
|
+
assert_response :success
|
|
44
|
+
assert_equal [@en.id, @de.id].sort, @showcase.reload.locale_ids.sort
|
|
45
|
+
|
|
46
|
+
# PUT again with only one locale; the missing one is uncoupled.
|
|
47
|
+
put form_element_showcase_path(
|
|
48
|
+
@showcase,
|
|
49
|
+
attribute: "locales",
|
|
50
|
+
form_element: "check_list",
|
|
51
|
+
update: frame
|
|
52
|
+
), params: { locales: { @nl.id.to_s => 1 } }, headers: field_headers(:locales)
|
|
53
|
+
|
|
54
|
+
assert_response :success
|
|
55
|
+
assert_equal [@nl.id], @showcase.reload.locale_ids
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
test "info_list locales_display mirrors the locales association read-only" do
|
|
59
|
+
@showcase.locales << @en unless @showcase.locales.where(id: @en.id).exists?
|
|
60
|
+
|
|
61
|
+
row_frame = "form_element_showcase_#{@showcase.id}"
|
|
62
|
+
get form_element_showcase_path(@showcase, update: row_frame),
|
|
63
|
+
headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
|
|
64
|
+
|
|
65
|
+
assert_response :success
|
|
66
|
+
info_frame = field_frame(:locales_display)
|
|
67
|
+
assert_includes @response.body, %(<turbo-frame id="#{info_frame}">),
|
|
68
|
+
"expected info_list frame for :locales_display"
|
|
69
|
+
# info_list_show renders each item's `_presentation`. Locale's is `title`.
|
|
70
|
+
assert_match(/<turbo-frame id="#{info_frame}">.*#{Regexp.escape(@en.title)}/m, @response.body,
|
|
71
|
+
"expected #{@en.title.inspect} inside the locales_display info_list frame")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
test "info_list locales_display empty-state renders the -- placeholder" do
|
|
75
|
+
@showcase.locales.clear
|
|
76
|
+
|
|
77
|
+
row_frame = "form_element_showcase_#{@showcase.id}"
|
|
78
|
+
get form_element_showcase_path(@showcase, update: row_frame),
|
|
79
|
+
headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
|
|
80
|
+
|
|
81
|
+
assert_response :success
|
|
82
|
+
info_frame = field_frame(:locales_display)
|
|
83
|
+
assert_match(/<turbo-frame id="#{info_frame}">[^<]*<div class='row [^']+'>--<\/div>/m, @response.body,
|
|
84
|
+
"expected empty `--` placeholder inside the locales_display frame")
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -3,8 +3,10 @@
|
|
|
3
3
|
require_relative "../example_app/example_integration_test_case"
|
|
4
4
|
|
|
5
5
|
# Numeric Tier 1 helpers on FormElementShowcase:
|
|
6
|
-
# integer_field (count)
|
|
7
|
-
# decimal_field (price)
|
|
6
|
+
# integer_field (count) + numericality validation regression
|
|
7
|
+
# decimal_field (price) default precision: 10, scale: 2
|
|
8
|
+
# decimal_field (latitude, longitude) explicit `{p,s}` CLI suffix:
|
|
9
|
+
# decimal(9,6) / decimal(10,6)
|
|
8
10
|
# money_field (amount, money-rails `monetize :amount_cents`)
|
|
9
11
|
class ExampleAppShowcaseNumericFieldsTest < ExampleAppIntegrationTestCase
|
|
10
12
|
setup do
|
|
@@ -30,7 +32,11 @@ class ExampleAppShowcaseNumericFieldsTest < ExampleAppIntegrationTestCase
|
|
|
30
32
|
assert_equal 42, @showcase.reload.count
|
|
31
33
|
end
|
|
32
34
|
|
|
33
|
-
test "decimal_field price round-trips" do
|
|
35
|
+
test "decimal_field price round-trips as BigDecimal with default precision/scale" do
|
|
36
|
+
# Since 8.1.10 the registry maps :decimal_field to a real :decimal
|
|
37
|
+
# column. Bare `price:decimal_field` (no `{p,s}` suffix) defaults to
|
|
38
|
+
# precision: 10, scale: 2 — so 99.95 round-trips losslessly *and*
|
|
39
|
+
# ActiveRecord returns a BigDecimal, not a String.
|
|
34
40
|
frame = "form_element_showcase_#{@showcase.id}_price"
|
|
35
41
|
headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
|
|
36
42
|
|
|
@@ -44,7 +50,76 @@ class ExampleAppShowcaseNumericFieldsTest < ExampleAppIntegrationTestCase
|
|
|
44
50
|
assert_response :success
|
|
45
51
|
assert_includes @response.body, %(<turbo-frame id="#{frame}">)
|
|
46
52
|
assert_includes @response.body, "99.95"
|
|
47
|
-
|
|
53
|
+
reloaded_price = @showcase.reload.price
|
|
54
|
+
assert_kind_of BigDecimal, reloaded_price,
|
|
55
|
+
"expected :decimal column to return BigDecimal, got #{reloaded_price.class}"
|
|
56
|
+
assert_equal BigDecimal("99.95"), reloaded_price
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
test "decimal_field{9,6} latitude round-trips at full scale (6 fractional digits)" do
|
|
60
|
+
# The `latitude:decimal_field{9,6}` CLI suffix should give us
|
|
61
|
+
# exactly 6 fractional digits of precision — enough for ~11cm
|
|
62
|
+
# accuracy in GPS terms. Pick a value that fully populates the
|
|
63
|
+
# scale so we'd catch off-by-one truncation.
|
|
64
|
+
frame = "form_element_showcase_#{@showcase.id}_latitude"
|
|
65
|
+
headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
|
|
66
|
+
|
|
67
|
+
put form_element_showcase_path(
|
|
68
|
+
@showcase,
|
|
69
|
+
attribute: "latitude",
|
|
70
|
+
form_element: "decimal_field",
|
|
71
|
+
update: frame
|
|
72
|
+
), params: { latitude: "12.123456" }, headers: headers
|
|
73
|
+
|
|
74
|
+
assert_response :success
|
|
75
|
+
reloaded = @showcase.reload.latitude
|
|
76
|
+
assert_kind_of BigDecimal, reloaded
|
|
77
|
+
assert_equal BigDecimal("12.123456"), reloaded
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
test "decimal_field{10,6} longitude accepts the full ±180 range" do
|
|
81
|
+
frame = "form_element_showcase_#{@showcase.id}_longitude"
|
|
82
|
+
headers = { "Turbo-Frame" => frame, "Accept" => "text/html" }
|
|
83
|
+
|
|
84
|
+
put form_element_showcase_path(
|
|
85
|
+
@showcase,
|
|
86
|
+
attribute: "longitude",
|
|
87
|
+
form_element: "decimal_field",
|
|
88
|
+
update: frame
|
|
89
|
+
), params: { longitude: "-179.999999" }, headers: headers
|
|
90
|
+
|
|
91
|
+
assert_response :success
|
|
92
|
+
assert_equal BigDecimal("-179.999999"), @showcase.reload.longitude
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
test "decimal_field price rejects non-numeric via top-level create" do
|
|
96
|
+
# Counterpart to the integer_field rejection test below. Without
|
|
97
|
+
# the `validates :price, numericality: true` line in the showcase
|
|
98
|
+
# model, ActiveRecord would silently cast "ace" to BigDecimal("0")
|
|
99
|
+
# and the create would succeed. The validation keeps the
|
|
100
|
+
# round-trip honest.
|
|
101
|
+
assert_no_difference "FormElementShowcase.count" do
|
|
102
|
+
post form_element_showcases_path(update: @list_frame),
|
|
103
|
+
params: {
|
|
104
|
+
title: "Bad Price",
|
|
105
|
+
price: "ace",
|
|
106
|
+
count: 1,
|
|
107
|
+
amount: "0.00",
|
|
108
|
+
start_month: "September 2026",
|
|
109
|
+
_form_element_showcase: {
|
|
110
|
+
rating_int: 1,
|
|
111
|
+
priority: 1,
|
|
112
|
+
priority2: 1,
|
|
113
|
+
stars: 1,
|
|
114
|
+
scale_int: 1,
|
|
115
|
+
scale_val: 1,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
headers: @list_headers
|
|
119
|
+
end
|
|
120
|
+
assert_response :success
|
|
121
|
+
assert_match(/is not a number|price[^<]*is not a number/i, @response.body,
|
|
122
|
+
"expected numericality error to render after invalid price")
|
|
48
123
|
end
|
|
49
124
|
|
|
50
125
|
test "money_field amount round-trips through monetize :amount_cents" do
|
|
@@ -14,6 +14,8 @@ class ExampleAppShowcasePageRenderTest < ExampleAppIntegrationTestCase
|
|
|
14
14
|
count
|
|
15
15
|
price
|
|
16
16
|
amount
|
|
17
|
+
latitude
|
|
18
|
+
longitude
|
|
17
19
|
meeting_date
|
|
18
20
|
meeting_time
|
|
19
21
|
birth_month
|
|
@@ -30,7 +32,8 @@ class ExampleAppShowcasePageRenderTest < ExampleAppIntegrationTestCase
|
|
|
30
32
|
jingle
|
|
31
33
|
cover
|
|
32
34
|
description
|
|
33
|
-
|
|
35
|
+
locales
|
|
36
|
+
locales_display
|
|
34
37
|
created_at
|
|
35
38
|
updated_at
|
|
36
39
|
].freeze
|
|
@@ -46,8 +49,7 @@ class ExampleAppShowcasePageRenderTest < ExampleAppIntegrationTestCase
|
|
|
46
49
|
].freeze
|
|
47
50
|
|
|
48
51
|
setup do
|
|
49
|
-
locale = Locale.find_or_create_by!(name: "en") { |l| l.title = "English" }
|
|
50
|
-
@role = Role.find_or_create_by!(name: "superadmin") { |r| r.description = "Super Admin" }
|
|
52
|
+
@locale = Locale.find_or_create_by!(name: "en") { |l| l.title = "English" }
|
|
51
53
|
|
|
52
54
|
@full = FormElementShowcase.find_or_create_by!(title: "Full demo") do |s|
|
|
53
55
|
s.body_plain_area = "Plain text body"
|
|
@@ -66,14 +68,16 @@ class ExampleAppShowcasePageRenderTest < ExampleAppIntegrationTestCase
|
|
|
66
68
|
s.scale_int = 3
|
|
67
69
|
s.scale_val = 2
|
|
68
70
|
s.amount = Money.from_amount(99.95, "USD") if s.respond_to?(:amount=) && defined?(Money)
|
|
71
|
+
s.latitude = BigDecimal("12.123456")
|
|
72
|
+
s.longitude = BigDecimal("-68.987654")
|
|
69
73
|
s.description = "<p>A rich-text body.</p>"
|
|
70
74
|
end
|
|
71
|
-
@full.
|
|
75
|
+
@full.locales << @locale unless @full.locales.where(id: @locale.id).exists?
|
|
72
76
|
|
|
73
77
|
@empty = FormElementShowcase.find_or_create_by!(title: "Empty demo") do |s|
|
|
74
78
|
# Keep dropdown/scale integers at valid indices so the show helpers
|
|
75
|
-
# do not crash on nil. The point of "empty demo" is that
|
|
76
|
-
# uploads are blank, not every integer attribute.
|
|
79
|
+
# do not crash on nil. The point of "empty demo" is that locales
|
|
80
|
+
# and uploads are blank, not every integer attribute.
|
|
77
81
|
s.gender = 1
|
|
78
82
|
s.rating_int = 1
|
|
79
83
|
s.priority = 1
|
|
@@ -103,22 +107,21 @@ class ExampleAppShowcasePageRenderTest < ExampleAppIntegrationTestCase
|
|
|
103
107
|
"expected header label for #{attr} (#{label.inspect})"
|
|
104
108
|
end
|
|
105
109
|
|
|
106
|
-
assert_includes @response.body, @
|
|
107
|
-
"expected info_list to render the
|
|
110
|
+
assert_includes @response.body, @locale.title,
|
|
111
|
+
"expected info_list to render the locale's _presentation"
|
|
108
112
|
end
|
|
109
113
|
|
|
110
|
-
test "empty showcase info_list renders the no-
|
|
114
|
+
test "empty showcase info_list renders the no-locales placeholder" do
|
|
111
115
|
row_frame = "form_element_showcase_#{@empty.id}"
|
|
112
116
|
get form_element_showcase_path(@empty, update: row_frame),
|
|
113
117
|
headers: { "Turbo-Frame" => row_frame, "Accept" => "text/html" }
|
|
114
118
|
|
|
115
119
|
assert_response :success
|
|
116
|
-
frame = "form_element_showcase_#{@empty.id}
|
|
120
|
+
frame = "form_element_showcase_#{@empty.id}_locales_display"
|
|
117
121
|
body = @response.body
|
|
118
122
|
assert_includes body, %(<turbo-frame id="#{frame}">)
|
|
119
|
-
frame_block = body[body.index(%(<turbo-frame id="#{frame}">"))..-1] rescue nil
|
|
120
123
|
# info_list_show emits a `--` placeholder for empty associations.
|
|
121
124
|
assert_match(/<turbo-frame id="#{frame}">[^<]*<div class='row [^']+'>--<\/div>/m, body,
|
|
122
|
-
"expected info_list empty placeholder `--` inside
|
|
125
|
+
"expected info_list empty placeholder `--` inside locales_display frame")
|
|
123
126
|
end
|
|
124
127
|
end
|
data/lib/installer_templates/example_app_tests/test/models/example_app_form_element_showcase_test.rb
CHANGED
|
@@ -16,6 +16,8 @@ class ExampleAppFormElementShowcaseTest < ActiveSupport::TestCase
|
|
|
16
16
|
count
|
|
17
17
|
price
|
|
18
18
|
amount
|
|
19
|
+
latitude
|
|
20
|
+
longitude
|
|
19
21
|
header_dates
|
|
20
22
|
meeting_date
|
|
21
23
|
meeting_time
|
|
@@ -36,7 +38,8 @@ class ExampleAppFormElementShowcaseTest < ActiveSupport::TestCase
|
|
|
36
38
|
cover
|
|
37
39
|
header_rich
|
|
38
40
|
description
|
|
39
|
-
|
|
41
|
+
locales
|
|
42
|
+
locales_display
|
|
40
43
|
header_meta
|
|
41
44
|
created_at
|
|
42
45
|
updated_at
|
|
@@ -81,4 +84,63 @@ class ExampleAppFormElementShowcaseTest < ActiveSupport::TestCase
|
|
|
81
84
|
showcase = FormElementShowcase.new(title: "X", count: nil)
|
|
82
85
|
assert showcase.valid?, "expected showcase to be valid with count=nil (allow_blank), got #{showcase.errors.full_messages.inspect}"
|
|
83
86
|
end
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------
|
|
89
|
+
# 8.1.10: :decimal_field now maps to a real :decimal column with
|
|
90
|
+
# precision/scale instead of the legacy varchar. The migration emitter
|
|
91
|
+
# applies (10, 2) by default and reads `{p,s}` overrides from the CLI
|
|
92
|
+
# type suffix (e.g. `latitude:decimal_field{9,6}` -> decimal(9,6)).
|
|
93
|
+
# ---------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
test "decimal_field maps to a real :decimal column with default precision/scale" do
|
|
96
|
+
price_col = FormElementShowcase.columns_hash["price"]
|
|
97
|
+
assert_not_nil price_col, "expected a :price column"
|
|
98
|
+
assert_equal :decimal, price_col.type,
|
|
99
|
+
"expected :price to be a :decimal column, got #{price_col.type.inspect} (#{price_col.sql_type.inspect})"
|
|
100
|
+
assert_equal 10, price_col.precision, "expected default precision: 10"
|
|
101
|
+
assert_equal 2, price_col.scale, "expected default scale: 2"
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
test "decimal_field{p,s} CLI suffix sets precision and scale" do
|
|
105
|
+
lat = FormElementShowcase.columns_hash["latitude"]
|
|
106
|
+
lon = FormElementShowcase.columns_hash["longitude"]
|
|
107
|
+
assert_equal :decimal, lat.type
|
|
108
|
+
assert_equal :decimal, lon.type
|
|
109
|
+
assert_equal [9, 6], [lat.precision, lat.scale],
|
|
110
|
+
"expected latitude:decimal_field{9,6} -> decimal(9,6), got decimal(#{lat.precision},#{lat.scale})"
|
|
111
|
+
assert_equal [10, 6], [lon.precision, lon.scale],
|
|
112
|
+
"expected longitude:decimal_field{10,6} -> decimal(10,6), got decimal(#{lon.precision},#{lon.scale})"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
test "decimal column round-trips BigDecimal losslessly at full scale" do
|
|
116
|
+
showcase = FormElementShowcase.create!(
|
|
117
|
+
title: "decimal-roundtrip",
|
|
118
|
+
latitude: BigDecimal("12.123456"),
|
|
119
|
+
longitude: BigDecimal("-68.987654"),
|
|
120
|
+
)
|
|
121
|
+
showcase.reload
|
|
122
|
+
assert_kind_of BigDecimal, showcase.latitude
|
|
123
|
+
assert_equal BigDecimal("12.123456"), showcase.latitude
|
|
124
|
+
assert_equal BigDecimal("-68.987654"), showcase.longitude
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
test "price rejects non-numeric input with a numericality error" do
|
|
128
|
+
showcase = FormElementShowcase.new(title: "X", price: "ace")
|
|
129
|
+
assert_not showcase.valid?
|
|
130
|
+
assert_includes showcase.errors.attribute_names, :price
|
|
131
|
+
assert showcase.errors[:price].any? { |m| m.match?(/not a number/i) },
|
|
132
|
+
"expected `not a number` error on :price, got #{showcase.errors[:price].inspect}"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
test "latitude rejects out-of-range values via numericality range" do
|
|
136
|
+
showcase = FormElementShowcase.new(title: "X", latitude: 91)
|
|
137
|
+
assert_not showcase.valid?
|
|
138
|
+
assert_includes showcase.errors.attribute_names, :latitude
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
test "longitude rejects out-of-range values via numericality range" do
|
|
142
|
+
showcase = FormElementShowcase.new(title: "X", longitude: 181)
|
|
143
|
+
assert_not showcase.valid?
|
|
144
|
+
assert_includes showcase.errors.attribute_names, :longitude
|
|
145
|
+
end
|
|
84
146
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: inline_forms_installer
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 8.1.
|
|
4
|
+
version: 8.1.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ace Suares
|
|
@@ -94,6 +94,9 @@ files:
|
|
|
94
94
|
- lib/installer_templates/example_app_assets/3stars.png
|
|
95
95
|
- lib/installer_templates/example_app_assets/4stars.png
|
|
96
96
|
- lib/installer_templates/example_app_assets/5stars.png
|
|
97
|
+
- lib/installer_templates/example_app_assets/sample.txt
|
|
98
|
+
- lib/installer_templates/example_app_assets/sample.wav
|
|
99
|
+
- lib/installer_templates/example_app_assets/sample_cover.png
|
|
97
100
|
- lib/installer_templates/example_app_tests/test/example_app/example_integration_test_case.rb
|
|
98
101
|
- lib/installer_templates/example_app_tests/test/integration/example_app_apartment_field_turbo_test.rb
|
|
99
102
|
- lib/installer_templates/example_app_tests/test/integration/example_app_apartment_name_list_test.rb
|
|
@@ -111,6 +114,7 @@ files:
|
|
|
111
114
|
- lib/installer_templates/example_app_tests/test/integration/example_app_routing_test.rb
|
|
112
115
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_choice_scale_fields_test.rb
|
|
113
116
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_date_time_fields_test.rb
|
|
117
|
+
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_locales_associations_test.rb
|
|
114
118
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_numeric_fields_test.rb
|
|
115
119
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_page_render_test.rb
|
|
116
120
|
- lib/installer_templates/example_app_tests/test/integration/example_app_showcase_text_fields_test.rb
|