phlexi-field 0.1.0 → 0.2.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: 5ba4c98ff7c81ff95503b3045f7564f86e91a529789a4408d51e100e764d91e8
4
- data.tar.gz: 940a9ff9e19deb86247956307e8903f24f3ce54691646742fb36e5394fe39b53
3
+ metadata.gz: 23043c21dcd13b30389b9abde3064b5ad3cd6e3a3d113632812b08a81146e50d
4
+ data.tar.gz: 807e164c121afec7d2cfc376f1d09b9bed972d3adf8cfef73774ee95b48cd3e4
5
5
  SHA512:
6
- metadata.gz: 780d445e6b6e4501c89c6030c46a405858ff6dfc2de75c5d2cec398309152bdfdd65536f09fe736ac0dd31f284b86db3f8405e6943e414aa9566d2bef162d8c8
7
- data.tar.gz: b5371cb3beb7e43475a68e704e53558191c469cc2f5f80156e0db8ce5be2cd435d012fd14c6b9f74683b7d60a5c9cb23d46ef532f0623fde1677ba3937239366
6
+ metadata.gz: bb81612c85d3d0bca6391940735150391e53c6a40208791142ba97dff7bb35257a7bfa02bf5d908025758e2b6f62c20635299170c9ebfbabc9b52b2ae19d00e9
7
+ data.tar.gz: 1a0c409e68029ba5b702ad3642f7e2f8720ccbfcbe5ea25564a72e3759ac91dabb46fd8b7588fb3bb1b7e0c5ee1c3c2a9e11fa56003afe2852a0f0d12a5aa7c9
data/.standard.yml ADDED
@@ -0,0 +1,2 @@
1
+ ignore:
2
+ - 'gemfiles/**/*'
data/Appraisals CHANGED
@@ -1,8 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  appraise "default" do
2
- # gem "phlex", "~> 1.10"
4
+ # Default dependencies
3
5
  end
4
6
 
5
- appraise "rails-7" do
6
- gem "rails", "~> 7.1.3", ">= 7.1.3.4"
7
+ appraise "rails-7.1" do
8
+ gem "rails", "~> 7.1.3", ">= 7.1.3.4", "< 8.0.0"
7
9
  gem "sqlite3", "~> 1.4"
10
+ gem "phlex-rails", "~> 2.2"
11
+ end
12
+
13
+ appraise "rails-8" do
14
+ gem "rails", "~> 8.0.0"
15
+ gem "sqlite3", "~> 2.1"
16
+ gem "phlex-rails", "~> 2.2"
8
17
  end
data/README.md CHANGED
@@ -1,195 +1,334 @@
1
- # Phlexi::Form
1
+ # Phlexi::Field
2
2
 
3
- Phlexi::Form is a flexible and powerful form builder for Ruby applications. It provides a more customizable and extensible way to create forms compared to traditional form helpers.
3
+ [![Gem Version](https://badge.fury.io/rb/phlexi-field.svg)](https://badge.fury.io/rb/phlexi-field)
4
+ [![CI](https://github.com/radioactive-labs/phlexi-field/actions/workflows/main.yml/badge.svg)](https://github.com/radioactive-labs/phlexi-field/actions/workflows/main.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
6
 
5
- [![Ruby](https://github.com/radioactive-labs/phlexi-form/actions/workflows/main.yml/badge.svg)](https://github.com/radioactive-labs/phlexi-form/actions/workflows/main.yml)
7
+ Phlexi::Field is a field component framework for Ruby applications that provides a unified field abstraction across forms, displays, and tables. Built on a namespace-based architecture for handling complex object relationships.
8
+ It's designed to work with [Phlex](https://github.com/phlex-ruby/phlex), a framework for building view components in Ruby.
6
9
 
7
- ## Features
10
+ ## Design Philosophy & Purpose
8
11
 
9
- - Customizable form components (input, select, checkbox, radio button, etc.)
10
- - Automatic field type and attribute inference based on model attributes
11
- - Built-in support for validations and error handling
12
- - Flexible form structure with support for nested attributes
13
- - Works with Phlex or erb views
14
- - Extract input from parameters that match your form definition. No need to strong paramters.
15
- - Rails compatible form inputs
12
+ Phlexi::Field serves as the foundation for field rendering across multiple contexts in web applications. Unlike traditional form builders that focus solely on forms, Phlexi::Field creates a unified abstraction for working with fields that can be used for:
16
13
 
14
+ - Form inputs
15
+ - Read-only displays
16
+ - Table columns
17
+ - Any other UI context where object fields are presented
18
+
19
+ It is designed around a **namespace-based architecture** that creates a hierarchical tree structure for complex object graphs, handling nested relationships and collections gracefully.
17
20
 
18
21
  ## Installation
19
22
 
20
23
  Add this line to your application's Gemfile:
21
24
 
22
25
  ```ruby
23
- gem 'phlexi-form'
26
+ gem 'phlexi-field'
24
27
  ```
25
28
 
26
29
  And then execute:
27
30
 
28
- ```
31
+ ```bash
29
32
  $ bundle install
30
33
  ```
31
34
 
32
35
  Or install it yourself as:
33
36
 
34
- ```
35
- $ gem install phlexi-form
37
+ ```bash
38
+ $ gem install phlexi-field
36
39
  ```
37
40
 
38
- ## Usage
41
+ ## Requirements
42
+
43
+ - Ruby >= 3.2.2
44
+ - Phlex ~> 2.0
45
+ - ActiveSupport >= 7.1
39
46
 
40
- There are 2 ways to use Phlexi::Form:
47
+ ## Core Architecture
41
48
 
42
- ### Direct Usage
49
+ ### Namespace Pattern
50
+
51
+ The foundation of Phlexi::Field is the `Namespace` concept:
52
+
53
+ 1. A Namespace maps an object (like an ActiveRecord model) to a key
54
+ 2. Fields are created within a namespace
55
+ 3. Namespaces can be nested to represent complex object relationships
43
56
 
44
57
  ```ruby
45
- Phlexi::Form(User.new) do
46
- field(:name) do |name|
47
- render name.label_tag
48
- render name.input_tag
49
- end
58
+ # Create a root namespace for a user object
59
+ namespace = Phlexi::Field::Structure::Namespace.new(
60
+ :user, # The key/name
61
+ parent: nil, # Root namespace has no parent
62
+ builder_klass: Phlexi::Field::Builder, # Field builder class
63
+ object: @user # The data object
64
+ )
65
+
66
+ # Create a field within the namespace
67
+ email_field = namespace.field(:email)
68
+
69
+ # Access field properties
70
+ email_field.dom.id # => "user_email"
71
+ email_field.dom.name # => "user[email]"
72
+ email_field.value # => The email value from @user
73
+ ```
50
74
 
51
- field(:email) do |email|
52
- render email.label_tag
53
- render email.input_tag
54
- end
75
+ ### Nested Objects and Collections
55
76
 
56
- nest_one(:address) do |address|
57
- address.field(:street) do |street|
58
- render street.label_tag
59
- render street.input_tag
60
- end
77
+ One of Phlexi::Field's strengths is handling complex object graphs:
61
78
 
62
- address.field(:city) do |city|
63
- render city.label_tag
64
- render city.input_tag
65
- end
66
- end
79
+ ```ruby
80
+ # For a has_one relationship
81
+ namespace.nest_one(:profile) do |profile|
82
+ first_name = profile.field(:first_name)
83
+ # DOM properties: id="user_profile_first_name", name="user[profile][first_name]"
84
+ end
67
85
 
68
- render submit_button
86
+ # For a has_many relationship
87
+ namespace.nest_many(:addresses) do |address_builder|
88
+ street = address_builder.field(:street)
89
+ # DOM properties: id="user_addresses_street", name="user[addresses][][street]"
69
90
  end
70
91
  ```
71
92
 
72
- > **Important**
73
- >
74
- > If you are rendering your form inline e.g.
75
- > ```ruby
76
- > render Phlexi::Form(User.new) {
77
- > render field(:name).label_tag
78
- > render field(:name).input_tag
79
- > }
80
- > ```
81
- >
82
- > Make sure you use `{...}` in defining your block instead of `do...end`
83
- > This might be fixed in a future version.
93
+ ### Field Builders and Components
94
+
95
+ Phlexi::Field separates field creation from rendering:
96
+
97
+ - `Builder` classes handle field creation, value access, and configuration
98
+ - `Component` classes handle presentation and rendering
99
+
100
+ This separation allows the same field structure to be rendered differently in various contexts.
101
+
102
+ ## Field Configuration Options
103
+
104
+ Phlexi::Field provides a rich set of options for configuring how fields are displayed and behave:
105
+
106
+ ### Labels
107
+
108
+ Labels can be explicitly set or automatically generated from the object's attribute name:
109
+
110
+ ```ruby
111
+ # Custom label
112
+ field = namespace.field(:email, label: "Email Address")
113
+
114
+ # Get the current label (automatically falls back to humanized attribute name)
115
+ field.label # => "Email Address" or "Email" if not explicitly set
116
+
117
+ # Rails integration: Uses human_attribute_name if available
118
+ # user.class.human_attribute_name(:email) => "Email Address"
119
+ ```
120
+
121
+ ### Hints and Descriptions
84
122
 
85
- ### Inherit form
123
+ Hints provide contextual help for users, while descriptions offer more detailed explanations:
86
124
 
87
125
  ```ruby
88
- class UserForm < Phlexi::Form::Base
89
- def form_template
90
- field(:name) do |name|
91
- render name.label_tag
92
- render name.input_tag
93
- end
126
+ # Setting a hint for a field
127
+ field = namespace.field(:password, hint: "Must be at least 8 characters")
128
+ field.hint # => "Must be at least 8 characters"
129
+ field.has_hint? # => true
130
+
131
+ # Setting a description
132
+ field = namespace.field(:terms_accepted,
133
+ description: "By accepting our terms, you agree to our privacy policy.")
134
+ field.description # => "By accepting our terms, you agree to our privacy policy."
135
+ field.has_description? # => true
136
+ ```
94
137
 
95
- field(:email) do |email|
96
- render email.label_tag
97
- render email.input_tag
98
- end
138
+ ### Placeholders
99
139
 
100
- nest_one(:address) do |address|
101
- address.field(:street) do |street|
102
- render street.label_tag
103
- render street.input_tag
104
- end
140
+ Text placeholders can be set for input fields:
105
141
 
106
- address.field(:city) do |city|
107
- render city.label_tag
108
- render city.input_tag
109
- end
110
- end
142
+ ```ruby
143
+ field = namespace.field(:email, placeholder: "john@example.com")
144
+ field.placeholder # => "john@example.com"
145
+ ```
111
146
 
112
- render submit_button
113
- end
114
- end
147
+ ### Type Inference
148
+
149
+ Phlexi::Field automatically infers the appropriate field type based on multiple signals:
150
+
151
+ ```ruby
152
+ # Based on attribute/column type in ActiveRecord/ActiveModel
153
+ # email_field.inferred_field_type # => :string, :integer, :boolean, etc.
154
+
155
+ # For string fields, it can also infer more specific types
156
+ # email_field.inferred_string_field_type # => :email, :password, :url, etc.
157
+ ```
158
+
159
+ The type inference algorithm considers:
160
+ 1. ActiveRecord column types
161
+ 2. ActiveModel attribute types
162
+ 3. Value type inspection
163
+ 4. Field name conventions (email, password, url, etc.)
164
+ 5. Validation rules (email format, numericality, etc.)
165
+
166
+ ### Validators
167
+
168
+ When using ActiveModel validations, Phlexi::Field can access them:
169
+
170
+ ```ruby
171
+ # Check if a field is required based on presence validators
172
+ field.required? # => true/false based on presence validators
115
173
 
174
+ # Access the validation errors
175
+ field.errors # => ["can't be blank", etc.]
176
+ ```
116
177
 
117
- # In your view or controller
118
- form = UserForm.new(User.new)
178
+ ### Multiple Options
119
179
 
120
- # Render the form
121
- render form
180
+ For fields that support multiple selections:
122
181
 
123
- # Extract params
124
- form.extract_input({
125
- name: "Brad Pitt",
126
- email: "brad@pitt.com",
127
- address: {
128
- street: "Plumbago",
129
- city: "Accra",
130
- }
131
- })
182
+ ```ruby
183
+ field = namespace.field(:categories, multiple: true)
184
+ field.multiple? # => true
132
185
  ```
133
186
 
134
- ## Advanced Usage
187
+ ## Usage
188
+
189
+ Phlexi::Field provides a foundation for building components that handle object fields such as forms and other UI components. It handles DOM structure, field values, configuration of labels, descriptions, hints, and more.
135
190
 
136
- ### Custom Components
191
+ It serves as a foundation for [Phlexi::Form](https://github.com/radioactive-labs/phlexi-form), [Phlexi::Display](https://github.com/radioactive-labs/phlexi-display) and [Phlexi::Table](https://github.com/radioactive-labs/phlexi-table).
137
192
 
138
- You can create custom form components by inheriting from `Phlexi::Form::Components::Base`:
193
+ ### Basic Example
139
194
 
140
195
  ```ruby
141
- class CustomInput < Phlexi::Form::Components::Base
196
+ class UserForm < Phlex::HTML
197
+ def initialize(user)
198
+ super()
199
+ @user = user
200
+ end
201
+
142
202
  def view_template
143
- div(class: "custom-input") do
144
- input(**attributes)
145
- span(class: "custom-icon")
203
+ namespace = Phlexi::Field::Structure::Namespace.new(
204
+ :user,
205
+ parent: nil,
206
+ builder_klass: Phlexi::Field::Builder,
207
+ object: @user
208
+ )
209
+
210
+ form(action: "/users", method: "post") do
211
+ # Create a name field
212
+ name_field = namespace.field(:name)
213
+ label(for: name_field.dom.id) { name_field.label }
214
+ input(
215
+ id: name_field.dom.id,
216
+ name: name_field.dom.name,
217
+ value: name_field.value,
218
+ type: "text"
219
+ )
220
+
221
+ # Create an email field
222
+ email_field = namespace.field(:email)
223
+ label(for: email_field.dom.id) { email_field.label }
224
+ input(
225
+ id: email_field.dom.id,
226
+ name: email_field.dom.name,
227
+ value: email_field.value,
228
+ type: "email"
229
+ )
230
+
231
+ # Submit button
232
+ button(type: "submit") { "Submit" }
146
233
  end
147
234
  end
148
235
  end
149
236
 
150
- # Usage in your form
151
- field(:custom_field) do |field|
152
- render CustomInput.new(field)
237
+ # Usage in a controller
238
+ def new
239
+ user = User.new
240
+ render UserForm.new(user)
153
241
  end
154
242
  ```
155
243
 
156
- ### Theming
244
+ ### Nested Forms
157
245
 
158
- Phlexi::Form supports theming through a flexible theming system:
246
+ Phlexi::Field supports nested forms for complex data structures:
159
247
 
160
248
  ```ruby
161
- class ThemedForm < Phlexi::Form::Base
162
- class FieldBuilder < FieldBuilder
163
- private
164
-
165
- def default_theme
166
- {
167
- input: "border rounded px-2 py-1",
168
- label: "font-bold text-gray-700",
169
- # Add more theme options here
170
- }
171
- end
172
- end
249
+ # For a has_one relationship
250
+ namespace.nest_one(:profile) do |profile|
251
+ profile.field(:first_name) # => Creates a field for user[profile][first_name]
173
252
  end
253
+
254
+ # For a has_many relationship
255
+ namespace.nest_many(:addresses) do |address_builder|
256
+ address_field = address_builder.field(:id)
257
+ # Creates fields like user[addresses][][id]
258
+ end
259
+ ```
260
+
261
+ ### Field Options
262
+
263
+ Fields support various options for customization:
264
+
265
+ ```ruby
266
+ # Custom label
267
+ field = namespace.field(:email, label: "Email Address")
268
+
269
+ # Required fields
270
+ field = namespace.field(:email, required: true)
271
+
272
+ # Placeholder text
273
+ field = namespace.field(:email, placeholder: "Enter your email")
274
+
275
+ # Description/hint text
276
+ field = namespace.field(:password, hint: "Must be at least 8 characters")
174
277
  ```
175
278
 
176
- <!-- ## Configuration
279
+ ## Components
177
280
 
178
- You can configure Phlexi::Form globally by creating an initializer:
281
+ Phlexi::Field comes with a base component architecture that you can extend:
179
282
 
180
283
  ```ruby
181
- # config/initializers/phlexi_form.rb
182
- Phlexi::Form.configure do |config|
183
- config.default_theme = {
184
- # Your default theme options
185
- }
186
- # Add more configuration options here
284
+ class MyInputComponent < Phlexi::Field::Components::Base
285
+ def view_template
286
+ input(
287
+ id: field.dom.id,
288
+ name: field.dom.name,
289
+ value: field.value,
290
+ type: "text",
291
+ **attributes
292
+ )
293
+ end
187
294
  end
188
- ``` -->
295
+
296
+ # Usage
297
+ my_component = MyInputComponent.new(field, class: "custom-input")
298
+ render my_component
299
+ ```
300
+
301
+ ## Comparison with Other Libraries
302
+
303
+ Phlexi::Field differs from traditional Rails form helpers and gems like Simple Form in several key ways:
304
+
305
+ 1. **Unified Field API**: Creates a consistent interface for fields across forms, displays, and tables
306
+ 2. **Component-Based**: Built on Phlex's component system rather than Rails' helper-based approach
307
+ 3. **Separation of Concerns**: Cleanly separates field structure (namespace), data handling (builder), and presentation (components)
308
+ 4. **True Object Orientation**: Works with complete objects and their relationships rather than flat attribute lists
309
+
310
+ ## Integration with Rails
311
+
312
+ While Phlexi::Field works standalone with Phlex, it has first-class support for Rails:
313
+
314
+ ```ruby
315
+ # In your Gemfile
316
+ gem 'phlexi-field'
317
+ gem 'phlex-rails'
318
+ ```
319
+
320
+ The library automatically uses Rails conventions when available:
321
+ - Humanized attribute names via `human_attribute_name`
322
+ - Association detection via `reflect_on_association`
323
+ - Primary key handling
324
+
325
+ ## Development
326
+
327
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
189
328
 
190
329
  ## Contributing
191
330
 
192
- Bug reports and pull requests are welcome on GitHub at https://github.com/radioactive-labs/phlexi-form.
331
+ Bug reports and pull requests are welcome on GitHub at https://github.com/radioactive-labs/phlexi-field.
193
332
 
194
333
  ## License
195
334
 
data/Rakefile CHANGED
@@ -3,6 +3,7 @@
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
5
  require "standard/rake"
6
+ require "appraisal"
6
7
 
7
8
  task default: %i[test standard]
8
9
 
@@ -12,3 +13,31 @@ Rake::TestTask.new do |t|
12
13
  t.test_files = FileList["test/**/*_test.rb"]
13
14
  t.verbose = true
14
15
  end
16
+
17
+ Rake::TestTask.new(:test) do |t|
18
+ t.libs << "test"
19
+ t.libs << "lib"
20
+ t.test_files = FileList["test/**/*_test.rb"]
21
+ end
22
+
23
+ namespace :test do
24
+ desc "Run core tests that don't depend on Rails"
25
+ Rake::TestTask.new(:core) do |t|
26
+ t.libs << "test"
27
+ t.libs << "lib"
28
+ t.test_files = FileList["test/phlexi/field/**/*_test.rb"].exclude("test/phlexi/rails/**/*_test.rb")
29
+ end
30
+
31
+ desc "Run Rails integration tests"
32
+ Rake::TestTask.new(:rails) do |t|
33
+ t.libs << "test"
34
+ t.libs << "lib"
35
+ t.test_files = FileList["test/phlexi/rails/**/*_test.rb"]
36
+ end
37
+ end
38
+
39
+ desc "Run tests across all appraisals"
40
+ task :test_all do
41
+ sh "bundle exec appraisal install"
42
+ sh "bundle exec appraisal test"
43
+ end
@@ -2,4 +2,11 @@
2
2
 
3
3
  source "https://rubygems.org"
4
4
 
5
+ gem "rails", "~> 7.1"
6
+ gem "sqlite3", "~> 1.4"
7
+
8
+ platforms :ruby, :x86_64_linux, :arm64_darwin do
9
+
10
+ end
11
+
5
12
  gemspec path: "../"