chobble-forms 0.5.6 → 0.7.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38897550927a4c901cada28fbe5f63e8447bf108e6eb3bedf5d045807e45c245
4
- data.tar.gz: ede19464d42338a4834967dda6a1df1048d2bef39599ddd89e079256c796ec15
3
+ metadata.gz: 114cd63966175b3b8f30be99441ff3da688526f9e46bc0b08c9add531c93d8eb
4
+ data.tar.gz: 225988209e98465f4c92d14729ad9650878519d1563cea5486f90b3342d8a28e
5
5
  SHA512:
6
- metadata.gz: c24eca1ed04bf5d7cbbe7d483451368ef3b636d3d0d42018e967a5f22ddbee88e4c7c8d2c5a7f6f104828af570610d395342bb05fad838c5366f4b84edd186db
7
- data.tar.gz: b486d795a2b856e0895b4736901f52fede072e35319acb7511aaef5b007919e8af5ea644365c34ef30839f0f36301a247fe0b791a5a12c98318ebdd078c11649
6
+ metadata.gz: 934333541f43b3fd5f8f0c24468f15a1d755b5567c0c341a856c6fedb67932f2a606d93452741ca26531a107d30cbd21363f1ec27f4acc7059a001edc3ca42ac
7
+ data.tar.gz: 535ab94d5f12ca207fd4c34d198e3938161465112819c0df18ea8cfa7c8f33ab29d8f44e9112aab6a011bf1889e49cf99e527223dadcdcb920f41830dbea7065
data/README.md CHANGED
@@ -18,36 +18,20 @@ bundle install
18
18
 
19
19
  ## Type Safety with Sorbet
20
20
 
21
- ChobbleForms uses Sorbet for static type checking, providing improved type safety and better IDE support. All library files use `typed: strict` with comprehensive type signatures.
21
+ ChobbleForms is fully typed with Sorbet (`typed: strict`) providing compile-time type safety and runtime validation. All field parameters must be symbols for a clean, consistent API.
22
22
 
23
23
  ### Type Checking
24
24
 
25
- Run type checking with:
26
-
27
25
  ```bash
28
26
  bundle exec srb tc
29
27
  ```
30
28
 
31
- Or use the provided Rake task:
32
-
33
- ```bash
34
- bundle exec rake typecheck
35
- ```
36
-
37
29
  ### Development Setup
38
30
 
39
- For contributors working on this gem:
40
-
41
31
  1. Install dependencies: `bundle install`
42
32
  2. Generate RBI files: `bundle exec rake sorbet_rbi`
43
33
  3. Run type checking: `bundle exec rake typecheck`
44
34
 
45
- All methods have full type signatures, providing:
46
- - Static type checking at development time
47
- - Runtime type validation
48
- - Better IDE autocomplete and documentation
49
- - Early detection of type mismatches
50
-
51
35
  ## CSS Styles
52
36
 
53
37
  ChobbleForms includes CSS for styling the form components. To use the included styles, add this to your application.css:
@@ -1,45 +1,60 @@
1
- # typed: false
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
2
5
 
3
6
  module ChobbleForms
4
7
  module FieldUtils
8
+ extend T::Sig
9
+
10
+ sig { params(field: Symbol).returns(Symbol) }
5
11
  def self.strip_field_suffix(field)
6
- field.to_s.gsub(/_pass$|_comment$/, "")
12
+ field.to_s.gsub(/_pass$|_comment$/, "").to_sym
7
13
  end
8
14
 
15
+ sig { params(field: Symbol, partial: Symbol).returns(T::Array[Symbol]) }
9
16
  def self.get_composite_fields(field, partial)
10
- fields = []
17
+ fields = T.let([], T::Array[Symbol])
11
18
  partial_str = partial.to_s
12
19
 
13
- if partial_str.include?("pass_fail") && !field.to_s.end_with?("_pass")
14
- fields << "#{field}_pass"
15
- end
20
+ fields << :"#{field}_pass" if partial_str.include?("pass_fail") && !field.to_s.end_with?("_pass")
16
21
 
17
22
  if partial_str.include?("comment")
18
23
  base = field.to_s.end_with?("_pass") ? strip_field_suffix(field) : field
19
- fields << "#{base}_comment"
24
+ fields << :"#{base}_comment"
20
25
  end
21
26
 
22
27
  fields
23
28
  end
24
29
 
30
+ sig { params(field: Symbol).returns(T::Boolean) }
25
31
  def self.is_pass_field?(field)
26
32
  field.to_s.end_with?("_pass")
27
33
  end
28
34
 
35
+ sig { params(field: Symbol).returns(T::Boolean) }
29
36
  def self.is_comment_field?(field)
30
37
  field.to_s.end_with?("_comment")
31
38
  end
32
39
 
40
+ sig { params(field: Symbol).returns(T::Boolean) }
33
41
  def self.is_composite_field?(field)
34
42
  is_pass_field?(field) || is_comment_field?(field)
35
43
  end
36
44
 
45
+ sig { params(field: Symbol).returns(Symbol) }
37
46
  def self.base_field_name(field)
38
47
  strip_field_suffix(field)
39
48
  end
40
49
 
50
+ sig { params(form: Symbol, field: Symbol).returns(String) }
41
51
  def self.form_field_label(form, field)
42
52
  I18n.t("forms.#{form}.fields.#{field}")
43
53
  end
54
+
55
+ sig { params(assessment_key: Symbol).returns(Symbol) }
56
+ def self.form_name_from_assessment(assessment_key)
57
+ assessment_key.to_s.gsub(/_assessment$/, "").to_sym
58
+ end
44
59
  end
45
60
  end
@@ -7,7 +7,35 @@ module ChobbleForms
7
7
  module Helpers
8
8
  extend T::Sig
9
9
 
10
- sig { params(field: T.any(Symbol, String), local_assigns: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
10
+ SelectOption = T.type_alias {
11
+ [String, T.any(String, Integer)]
12
+ }
13
+
14
+ LocalAssignValue = T.type_alias {
15
+ T.any(
16
+ String,
17
+ Symbol,
18
+ Integer,
19
+ Float,
20
+ T::Boolean,
21
+ T::Array[SelectOption],
22
+ T::Hash[Symbol, T.untyped]
23
+ )
24
+ }
25
+
26
+ FieldSetupResult = T.type_alias {
27
+ {
28
+ form_object: T.untyped,
29
+ i18n_base: String,
30
+ value: T.untyped,
31
+ prefilled: T::Boolean,
32
+ field_label: String,
33
+ field_hint: T.nilable(String),
34
+ field_placeholder: T.nilable(String)
35
+ }
36
+ }
37
+
38
+ sig { params(field: Symbol, local_assigns: T::Hash[Symbol, LocalAssignValue]).returns(FieldSetupResult) }
11
39
  def form_field_setup(field, local_assigns)
12
40
  validate_local_assigns(local_assigns)
13
41
  validate_form_context
@@ -19,17 +47,19 @@ module ChobbleForms
19
47
  build_field_setup_result(field_translations, value, prefilled)
20
48
  end
21
49
 
22
- sig { params(form_object: T.untyped, field: T.any(Symbol, String)).returns([T.untyped, T::Boolean]) }
50
+ sig { params(form_object: T.untyped, field: Symbol).returns([T.untyped, T::Boolean]) }
23
51
  def get_field_value_and_prefilled_status(form_object, field)
24
52
  return [nil, false] unless form_object&.object
53
+
25
54
  model = form_object.object
26
55
  resolved = resolve_field_value(model, field)
27
56
  [resolved[:value], resolved[:prefilled]]
28
57
  end
29
58
 
30
- sig { params(form: T.untyped, comment_field: T.any(Symbol, String), base_field_label: String).returns(T::Hash[Symbol, T.untyped]) }
59
+ sig { params(form: T.untyped, comment_field: Symbol, base_field_label: String).returns(T::Hash[Symbol, T.untyped]) }
31
60
  def comment_field_options(form, comment_field, base_field_label)
32
61
  raise ArgumentError, "form_object required" unless form
62
+
33
63
  model = form.object
34
64
 
35
65
  comment_value, comment_prefilled =
@@ -81,21 +111,16 @@ module ChobbleForms
81
111
  rows
82
112
  step
83
113
  type
84
- ], T::Array[Symbol])
114
+ ].freeze, T::Array[Symbol])
85
115
 
86
- sig { params(local_assigns: T::Hash[Symbol, T.untyped]).void }
116
+ sig { params(local_assigns: T::Hash[Symbol, LocalAssignValue]).void }
87
117
  def validate_local_assigns(local_assigns)
88
- if local_assigns[:field]&.respond_to?(:to_s) &&
89
- local_assigns[:field].to_s.match?(/^[A-Z]/)
90
- raise ArgumentError, "Field names must be snake_case symbols, not class names. Use :field, not Field."
91
- end
92
-
93
118
  locally_assigned_keys = local_assigns.keys
94
119
  disallowed_keys = locally_assigned_keys - ALLOWED_LOCAL_ASSIGNS
95
120
 
96
- if disallowed_keys.any?
97
- raise ArgumentError, "local_assigns contains #{disallowed_keys.inspect}"
98
- end
121
+ return unless disallowed_keys.any?
122
+
123
+ raise ArgumentError, "local_assigns contains #{disallowed_keys.inspect}"
99
124
  end
100
125
 
101
126
  sig { void }
@@ -106,10 +131,12 @@ module ChobbleForms
106
131
  raise ArgumentError, "missing form_object" unless form_obj
107
132
  end
108
133
 
109
- sig { params(field: T.any(Symbol, String)).returns(T::Hash[Symbol, T.nilable(String)]) }
134
+ sig { params(field: Symbol).returns(T::Hash[Symbol, T.nilable(String)]) }
110
135
  def build_field_translations(field)
111
136
  i18n_base = T.unsafe(instance_variable_get(:@_current_i18n_base))
112
- fields_key = "#{i18n_base}.fields.#{field}"
137
+
138
+ field_without_suffix = FieldUtils.strip_field_suffix(field)
139
+ fields_key = "#{i18n_base}.fields.#{field_without_suffix}"
113
140
  field_label = t(fields_key, raise: true)
114
141
 
115
142
  base_parts = i18n_base.split(".")
@@ -124,42 +151,37 @@ module ChobbleForms
124
151
  }
125
152
  end
126
153
 
127
- sig { params(field_translations: T::Hash[Symbol, T.nilable(String)], value: T.untyped, prefilled: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
154
+ sig { params(field_translations: T::Hash[Symbol, T.nilable(String)], value: T.untyped, prefilled: T::Boolean).returns(FieldSetupResult) }
128
155
  def build_field_setup_result(field_translations, value, prefilled)
129
156
  form_obj = T.unsafe(instance_variable_get(:@_current_form))
130
157
  i18n_base = T.unsafe(instance_variable_get(:@_current_i18n_base))
131
158
 
132
- {
133
- form_object: form_obj,
134
- i18n_base: i18n_base,
135
- value:,
136
- prefilled:
137
- }.merge(field_translations)
159
+ T.cast(
160
+ {
161
+ form_object: form_obj,
162
+ i18n_base: i18n_base,
163
+ value:,
164
+ prefilled:
165
+ }.merge(field_translations),
166
+ FieldSetupResult
167
+ )
138
168
  end
139
169
 
140
- sig { params(model: T.untyped, field: T.any(Symbol, String)).returns(T::Hash[Symbol, T.untyped]) }
170
+ sig { params(model: T.untyped, field: Symbol).returns({value: T.untyped, prefilled: T::Boolean}) }
141
171
  def resolve_field_value(model, field)
142
172
  field_str = field.to_s
143
173
 
144
- # Never return values for password fields
145
- if field_str.include?("password")
146
- return {value: nil, prefilled: false}
147
- end
174
+ return {value: nil, prefilled: false} if field_str.include?("password")
148
175
 
149
- # Check current model value
150
176
  current_value = model.send(field) if model.respond_to?(field)
151
177
 
152
- # Check if this field should be excluded from prefilling
153
- if defined?(InspectionsController::NOT_COPIED_FIELDS) &&
154
- InspectionsController::NOT_COPIED_FIELDS.include?(field_str)
155
- return {value: current_value, prefilled: false}
156
- end
178
+ # Check if this field should not be prefilled based on excluded fields list
179
+ excluded_fields = T.unsafe(instance_variable_get(:@_excluded_prefill_fields))
180
+ return {value: current_value, prefilled: false} if excluded_fields&.include?(field)
157
181
 
158
- # Extract previous value if available
159
182
  prev_inspection = T.unsafe(instance_variable_get(:@previous_inspection))
160
183
  previous_value = extract_previous_value(prev_inspection, model, field)
161
184
 
162
- # Return previous value if current is nil and previous exists
163
185
  if current_value.nil? && !previous_value.nil?
164
186
  return {
165
187
  value: format_numeric_value(previous_value),
@@ -170,12 +192,11 @@ module ChobbleForms
170
192
  if field_str.end_with?("_id") && field_str != "id"
171
193
  resolve_association_value(model, field_str)
172
194
  else
173
- # Always return current value, even if nil
174
195
  {value: current_value, prefilled: false}
175
196
  end
176
197
  end
177
198
 
178
- sig { params(previous_inspection: T.untyped, current_model: T.untyped, field: T.any(Symbol, String)).returns(T.untyped) }
199
+ sig { params(previous_inspection: T.untyped, current_model: T.untyped, field: Symbol).returns(T.untyped) }
179
200
  def extract_previous_value(previous_inspection, current_model, field)
180
201
  if !previous_inspection
181
202
  nil
@@ -2,5 +2,5 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  module ChobbleForms
5
- VERSION = "0.5.6"
5
+ VERSION = "0.7.0"
6
6
  end
@@ -8,6 +8,7 @@
8
8
  # url: Form submission URL (defaults to model's update path)
9
9
  # method: HTTP method (defaults to :patch for models, :post for scoped forms)
10
10
  # local: Use standard form submission instead of Turbo (defaults to false)
11
+ # excluded_prefill_fields: Array of field symbols that should not be prefilled
11
12
 
12
13
  if model
13
14
  url ||= url_for(model)
@@ -52,6 +53,11 @@ end %>
52
53
  <% @_current_form = form %>
53
54
  <% @_current_i18n_base = i18n_base %>
54
55
 
56
+ <% # Set excluded prefill fields if provided %>
57
+ <% if local_assigns[:excluded_prefill_fields] %>
58
+ <% @_excluded_prefill_fields = excluded_prefill_fields %>
59
+ <% end %>
60
+
55
61
  <%= yield form %>
56
62
 
57
63
  <% if model && model.respond_to?(:errors) && model.errors.any? %>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chobble-forms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.6
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chobble.com
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-08-12 00:00:00.000000000 Z
11
+ date: 2025-08-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails