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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1db2a592daf12e0ddf1b93c67469f400540e58a81b8ca96ee516dccb7a03dc20
4
- data.tar.gz: 4e5abd3355753c8f09d79e16431007a0b268066e1106330c4251649262f92fe2
3
+ metadata.gz: 6e8a8a9d34cdcedea81beaadb7a2db76e36a53754e6edf224e65066249cd4b94
4
+ data.tar.gz: 3c98242f205d111da4e51d7fe6caae53fcad4ae9da9ddd8482a167845d5a7895
5
5
  SHA512:
6
- metadata.gz: 92c4c50ca28769e76767bcc11fc74c742520386cddf55c4559133b95251161dd71d28e2a9e8d3fbddde62b95d9adf6e72f5666b7893ae9f94374132d66af37b6
7
- data.tar.gz: d71c002620d3959812057383f6714eb01f9863370351d4436cf419e4b7a2ece6f78a2848ca057209fe6b68956fe42e04bdcd1f75731d6880c06d06b960f358b2
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
- gem_file = roots.filter_map { |root|
50
- files = Dir[File.join(root, "#{name}-*.gem")]
51
- files.sort.last if files.any?
52
- }.max
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
- append_to_file "db/seeds.rb", "Locale.create({ id: 1, name: 'en', title: 'English' })\n"
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 roles:has_and_belongs_to_many _enabled:yes _list_order:title _list_search:title _presentation:'#{title}'}
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 mount_uploader :attachment, AttachmentUploader\n monetize :amount_cents\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 :roles (info_list), :header_meta, and the timestamps after
1143
- # the rich_text row. The generator does not emit a row for
1144
- # `roles:has_and_belongs_to_many` (relation? is true), so we add it
1145
- # manually here.
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 [ :roles, :info_list ], \n [ :header_meta, :header ], \n [ :created_at, :info ], \n [ :updated_at, :info ], \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
- roles: Roles
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
- # Join table for has_and_belongs_to_many :roles. Mirrors the
1203
- # roles_users join migration created for the user model above.
1204
- say "- Creating form_element_showcases_roles join migration..."
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}_create_join_table_form_element_showcases_roles.rb", <<-HABTM_MIGRATION.strip_heredoc
1208
- class CreateJoinTableFormElementShowcasesRoles < ActiveRecord::Migration[8.1]
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 :form_element_showcases_roles, id: false, force: true do |t|
1268
+ create_table :form_element_showcases_locales, id: false, force: true do |t|
1211
1269
  t.integer :form_element_showcase_id
1212
- t.integer :role_id
1270
+ t.integer :locale_id
1213
1271
  end
1214
1272
  end
1215
1273
 
1216
1274
  def self.down
1217
- drop_table :form_element_showcases_roles
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
- if defined?(Role) && Role.exists?(1) && full.roles.empty?
1253
- full.roles << Role.find(1)
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
@@ -1,6 +1,6 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module InlineFormsInstaller
3
- VERSION = "8.1.8"
3
+ VERSION = "8.1.10"
4
4
 
5
5
  # Written into generated apps' `.ruby-version` (must match gemspec `required_ruby_version`).
6
6
  TARGET_RUBY_VERSION = "ruby-4.0.4"
@@ -0,0 +1 @@
1
+ Sample attachment file generated by inline_forms_installer for the FormElementShowcase demo. You can replace this with any 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) + numericality validation regression
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
- assert_equal "99.95", @showcase.reload.price
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
- roles
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.roles << @role unless @full.roles.where(id: @role.id).exists?
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 roles and
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, @role.name,
107
- "expected info_list to render the role's _presentation"
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-roles placeholder" do
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}_roles"
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 roles frame")
125
+ "expected info_list empty placeholder `--` inside locales_display frame")
123
126
  end
124
127
  end
@@ -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
- roles
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.8
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