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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df7bfe8f520d385db578e47e05b9a08397eaed01ac108828b9d92a59b8aeca29
4
- data.tar.gz: 12975038ffe2a8dc51253383fa75a9e11e31c0e5c9911bf30ef605549ee8ea31
3
+ metadata.gz: '06090477510745144a75c5edcf7d2dfe6c7941a9bc64d5b73b1a1269cfb6db70'
4
+ data.tar.gz: f0ea4453cb4cda4a9c1f4cb33aa787c2b457c3724f49d919859f82d453498253
5
5
  SHA512:
6
- metadata.gz: ca388061f33ff239feb1cb9579181ecc469459db8ab364c4842fc8c6025bdb0140208cf97c7a82716e305a4ce098493d5696cb48af438fa9782d37cc9858f538
7
- data.tar.gz: 7121b04f4921390f4ca4285abff657cf4885f84a679af0bc3672d9dc37e7637d8f5040d7e5413075c46c18a88acc40a4565d4b6d18d7c306f36ada6d76574ef0
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 UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
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 UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
18
+ gem install model_form_facade
21
19
  ```
22
20
 
23
21
  ## Usage
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ModelFormFacade
4
- VERSION = "0.1.0"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -1,9 +1,9 @@
1
1
  require_relative "model_form_facade/version"
2
- require "activesupport"
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
- self.attributes = params
67
+ assign_attributes(params)
67
68
  end
68
69
  alias_method :fields=, :set_fields
69
70
 
70
- def attributes=(hash)
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 object.class.name&.split("::")&.last&.downcase&.to_sym
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
- field_methods_module.define_method(:"#{name}=") { |value| object.send(write, value) } unless write == false
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 ModelForm })
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_form_facade
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joshua Paine