model_form_facade 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +2 -4
- data/lib/model_form_facade/version.rb +1 -1
- data/lib/model_form_facade.rb +47 -8
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '06090477510745144a75c5edcf7d2dfe6c7941a9bc64d5b73b1a1269cfb6db70'
|
|
4
|
+
data.tar.gz: f0ea4453cb4cda4a9c1f4cb33aa787c2b457c3724f49d919859f82d453498253
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 619871d42f8eaa331dab60444c9288e936bdb762c874e1b5a77e9fa2cf1ae687f6740218ea1effd46a48821a4c97914d41bb187b47c5237596d1eff6c7274dab
|
|
7
|
+
data.tar.gz: d68357db0bd0271959eba026742b5b22f7ae18328ae28526a50c036bf259c48e634d9735818b82da16a0eb73e130b5c147a4f075706446bf2abba452f44be0b5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-05-20
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Breaking:** Fields on forms backed by multi-word class names (e.g. `FooBar`) now generate correctly parameterized field names (`foo_bar[field]` instead of `foobar[field]`).
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- Array fields now accept both a plain array and a numeric-keyed hash (Rails strong params style), making it easier to handle form submissions without manual coercion.
|
|
12
|
+
|
|
13
|
+
## [0.2.0] - 2025-08-30
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Nested forms with custom `read:`, `write:`, and `attribute:` mappings now work correctly — nested object and array fields are routed through the inner form's write logic.
|
|
18
|
+
- `as_written(hash)` class method returns the attributes hash as they would be written to the backing object, without needing a real model instance.
|
|
19
|
+
- `#errors` now joins multiple validation messages with a readable separator.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- `Field type: :array` write method no longer errors on assignment.
|
|
24
|
+
|
|
3
25
|
## [0.1.0] - 2025-08-27
|
|
4
26
|
|
|
5
27
|
- Initial release
|
data/README.md
CHANGED
|
@@ -6,18 +6,16 @@ Welcome to your new gem! In this directory, you'll find the files you need to be
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
10
|
-
|
|
11
9
|
Install the gem and add to the application's Gemfile by executing:
|
|
12
10
|
|
|
13
11
|
```bash
|
|
14
|
-
bundle add
|
|
12
|
+
bundle add model_form_facade
|
|
15
13
|
```
|
|
16
14
|
|
|
17
15
|
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
18
16
|
|
|
19
17
|
```bash
|
|
20
|
-
gem install
|
|
18
|
+
gem install model_form_facade
|
|
21
19
|
```
|
|
22
20
|
|
|
23
21
|
## Usage
|
data/lib/model_form_facade.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require_relative "model_form_facade/version"
|
|
2
|
-
require "
|
|
2
|
+
require "active_support"
|
|
3
3
|
|
|
4
4
|
module ModelFormFacade
|
|
5
5
|
class Error < StandardError; end
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
extend ActiveSupport::Concern
|
|
8
8
|
|
|
9
9
|
included do
|
|
@@ -12,6 +12,7 @@ module ModelFormFacade
|
|
|
12
12
|
attr_reader :object
|
|
13
13
|
private attr_writer :object
|
|
14
14
|
private attr_accessor :component_props
|
|
15
|
+
delegate :model_name, :to_key, :to_model, to: :object
|
|
15
16
|
end
|
|
16
17
|
|
|
17
18
|
def initialize(model_object = nil, root: nil, **component_props)
|
|
@@ -42,7 +43,7 @@ module ModelFormFacade
|
|
|
42
43
|
errs = object_error_messages.reject { |k, v| k.to_s.include? "." } # Remove nested, we'll recurse
|
|
43
44
|
hash = self.class.fields.values.filter_map do |field|
|
|
44
45
|
err = case field.type
|
|
45
|
-
in :scalar then errs[field.attribute]&.map(&:capitalize)&.join(";").presence
|
|
46
|
+
in :scalar then errs[field.attribute]&.map(&:capitalize)&.join("; ").presence
|
|
46
47
|
in :object then field.form.new(send(field.name)).errors(root: false)
|
|
47
48
|
in :array then (send(field.name) || []).map { field.form.new(_1).errors(root: false) }
|
|
48
49
|
end
|
|
@@ -63,15 +64,16 @@ module ModelFormFacade
|
|
|
63
64
|
|
|
64
65
|
def set_fields(params, root: nil)
|
|
65
66
|
params = params.expect(expectation(root:)) if params.respond_to?(:expect) && !params.permitted?
|
|
66
|
-
|
|
67
|
+
assign_attributes(params)
|
|
67
68
|
end
|
|
68
69
|
alias_method :fields=, :set_fields
|
|
69
70
|
|
|
70
|
-
def
|
|
71
|
+
def assign_attributes(hash)
|
|
71
72
|
hash.each do |key, value|
|
|
72
73
|
send("#{key}=", value)
|
|
73
74
|
end
|
|
74
75
|
end
|
|
76
|
+
alias_method :attributes=, :assign_attributes
|
|
75
77
|
|
|
76
78
|
def attributes = as_json
|
|
77
79
|
|
|
@@ -85,7 +87,7 @@ module ModelFormFacade
|
|
|
85
87
|
|
|
86
88
|
private def _params_root(root: nil)
|
|
87
89
|
case root.nil? ? params_root : root
|
|
88
|
-
in true then
|
|
90
|
+
in true then model_name&.param_key&.to_sym
|
|
89
91
|
in false then nil
|
|
90
92
|
in Symbol => sym then sym
|
|
91
93
|
in String => str then str.to_sym
|
|
@@ -101,6 +103,21 @@ module ModelFormFacade
|
|
|
101
103
|
end
|
|
102
104
|
end
|
|
103
105
|
|
|
106
|
+
class MockObject
|
|
107
|
+
attr_accessor :attributes
|
|
108
|
+
|
|
109
|
+
def self.define_writer(write_method)
|
|
110
|
+
key = write_method.to_s.delete_suffix("=")
|
|
111
|
+
define_method(write_method) do |v|
|
|
112
|
+
attributes[key] = v
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def initialize
|
|
117
|
+
self.attributes = {}
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
104
121
|
module FieldMethods; end
|
|
105
122
|
|
|
106
123
|
class_methods do
|
|
@@ -124,6 +141,13 @@ module ModelFormFacade
|
|
|
124
141
|
{root => expected}
|
|
125
142
|
end
|
|
126
143
|
|
|
144
|
+
def as_written(hash)
|
|
145
|
+
obj = mock_object_class.new
|
|
146
|
+
instance = new(obj)
|
|
147
|
+
instance.assign_attributes(hash)
|
|
148
|
+
obj.attributes
|
|
149
|
+
end
|
|
150
|
+
|
|
127
151
|
private
|
|
128
152
|
|
|
129
153
|
def _relation(name, allow_destroy: false, form: nil, read: nil, write: nil, attribute: nil, type: nil, &block)
|
|
@@ -144,12 +168,23 @@ module ModelFormFacade
|
|
|
144
168
|
field = Field.new(name:, read:, write:, attribute:, type:, form:)
|
|
145
169
|
self.fields = {**fields, name.to_sym => field}
|
|
146
170
|
field_methods_module.define_method(name) { object&.send(read) } unless read == false
|
|
147
|
-
|
|
171
|
+
return if write == false
|
|
172
|
+
mock_object_class.define_writer(write)
|
|
173
|
+
write_method =
|
|
174
|
+
case field.type
|
|
175
|
+
in :object then ->(value) { object.send(write, field.form.as_written(value)) }
|
|
176
|
+
in :array then ->(value) {
|
|
177
|
+
items = value.respond_to?(:values) ? value.values : Array(value)
|
|
178
|
+
object.send(write, items.map { |v| field.form.as_written(v) })
|
|
179
|
+
}
|
|
180
|
+
in :scalar then ->(value) { object.send(write, value) }
|
|
181
|
+
end
|
|
182
|
+
field_methods_module.define_method(:"#{name}=", write_method)
|
|
148
183
|
end
|
|
149
184
|
|
|
150
185
|
def create_form_class_for(field_name)
|
|
151
186
|
class_name = "anonymous_#{field_name}_form".camelize
|
|
152
|
-
const_set(class_name, Class.new.tap { _1.include
|
|
187
|
+
const_set(class_name, Class.new.tap { _1.include ModelFormFacade })
|
|
153
188
|
end
|
|
154
189
|
|
|
155
190
|
def field_methods_module
|
|
@@ -160,5 +195,9 @@ module ModelFormFacade
|
|
|
160
195
|
include @field_methods_module
|
|
161
196
|
@field_methods_module
|
|
162
197
|
end
|
|
198
|
+
|
|
199
|
+
def mock_object_class
|
|
200
|
+
@mock_object_class ||= Class.new(MockObject)
|
|
201
|
+
end
|
|
163
202
|
end
|
|
164
203
|
end
|