From b59b89bc532c82bff1eb50714a157d2c91807bcb Mon Sep 17 00:00:00 2001 From: Andres Garcia Date: Tue, 3 Feb 2026 15:30:50 -0300 Subject: [PATCH 1/5] Test attribute instead of attr_accessor --- .ruby-version | 1 - README.md | 3 +++ spec/support/forms/multiple_errors_form.rb | 3 ++- spec/support/forms/registration_form.rb | 3 ++- spec/support/forms/user_destroy_form.rb | 3 ++- spec/support/forms/with_commit_callbacks_form.rb | 5 ++++- spec/support/forms/with_multiple_callbacks_form.rb | 5 ++++- spec/support/forms/with_rollback_callbacks_form.rb | 5 ++++- spec/support/forms/with_save_callbacks_form.rb | 5 ++++- spec/support/forms/with_validation_callbacks_form.rb | 5 ++++- 10 files changed, 29 insertions(+), 9 deletions(-) delete mode 100644 .ruby-version diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 24ba9a3..0000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -2.7.0 diff --git a/README.md b/README.md index dbb91d7..215d04b 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,9 @@ Available callbacks are (listed in execution order): ```ruby class RegistrationForm < YAAF::Form + attribute :email, :string + attribute :name, :string + normalizes :email, with: ->(email) { email.strip.downcase } normalizes :name, with: ->(name) { name.strip.titleize } diff --git a/spec/support/forms/multiple_errors_form.rb b/spec/support/forms/multiple_errors_form.rb index 14c7783..e78cdaf 100644 --- a/spec/support/forms/multiple_errors_form.rb +++ b/spec/support/forms/multiple_errors_form.rb @@ -1,5 +1,6 @@ class MultipleErrorsForm < YAAF::Form - attr_accessor :email, :name + attribute :email, :string + attribute :name, :string validates :name, format: { with: /[a-zA-Z]+/ } validates :email, format: { with: /\S+@\S+\.\S+/ } diff --git a/spec/support/forms/registration_form.rb b/spec/support/forms/registration_form.rb index 0653ae3..ce3bb2b 100644 --- a/spec/support/forms/registration_form.rb +++ b/spec/support/forms/registration_form.rb @@ -1,5 +1,6 @@ class RegistrationForm < YAAF::Form - attr_accessor :email, :name + attribute :email, :string + attribute :name, :string validates :name, format: { with: /[a-zA-Z]+/ }, allow_blank: true diff --git a/spec/support/forms/user_destroy_form.rb b/spec/support/forms/user_destroy_form.rb index 561c192..1b15816 100644 --- a/spec/support/forms/user_destroy_form.rb +++ b/spec/support/forms/user_destroy_form.rb @@ -1,5 +1,6 @@ class UserDestroyForm < YAAF::Form - attr_accessor :email, :name + attribute :email, :string + attribute :name, :string before_save :mark_user_for_destruction diff --git a/spec/support/forms/with_commit_callbacks_form.rb b/spec/support/forms/with_commit_callbacks_form.rb index eb4bfa0..3f63ffe 100644 --- a/spec/support/forms/with_commit_callbacks_form.rb +++ b/spec/support/forms/with_commit_callbacks_form.rb @@ -1,5 +1,8 @@ class WithCommitCallbacksForm < YAAF::Form - attr_accessor :email, :name, :after_counter + attribute :email, :string + attribute :name, :string + + attr_accessor :after_counter validates :name, format: { with: /[a-zA-Z]+/ }, allow_blank: true after_commit { @after_counter += 1 } diff --git a/spec/support/forms/with_multiple_callbacks_form.rb b/spec/support/forms/with_multiple_callbacks_form.rb index 883b415..0360253 100644 --- a/spec/support/forms/with_multiple_callbacks_form.rb +++ b/spec/support/forms/with_multiple_callbacks_form.rb @@ -1,5 +1,8 @@ class WithMultipleCallbacksForm < YAAF::Form - attr_accessor :email, :name, :result + attribute :email, :string + attribute :name, :string + + attr_accessor :result validates :name, format: { with: /[a-zA-Z]+/ }, allow_blank: true after_validation :add_to_after_validation_counter, :add_again_to_after_validation_counter diff --git a/spec/support/forms/with_rollback_callbacks_form.rb b/spec/support/forms/with_rollback_callbacks_form.rb index acea024..8dad5a9 100644 --- a/spec/support/forms/with_rollback_callbacks_form.rb +++ b/spec/support/forms/with_rollback_callbacks_form.rb @@ -1,5 +1,8 @@ class WithRollbackCallbacksForm < YAAF::Form - attr_accessor :email, :name, :after_counter + attribute :email, :string + attribute :name, :string + + attr_accessor :after_counter validates :name, format: { with: /[a-zA-Z]+/ }, allow_blank: true after_rollback { @after_counter += 1 } diff --git a/spec/support/forms/with_save_callbacks_form.rb b/spec/support/forms/with_save_callbacks_form.rb index 8a0eadb..4adc3ef 100644 --- a/spec/support/forms/with_save_callbacks_form.rb +++ b/spec/support/forms/with_save_callbacks_form.rb @@ -1,5 +1,8 @@ class WithSaveCallbacksForm < YAAF::Form - attr_accessor :email, :name, :before_counter, :after_counter + attribute :email, :string + attribute :name, :string + + attr_accessor :before_counter, :after_counter validates :name, format: { with: /[a-zA-Z]+/ }, allow_blank: true before_save :add_to_before_counter diff --git a/spec/support/forms/with_validation_callbacks_form.rb b/spec/support/forms/with_validation_callbacks_form.rb index e361b43..9666b15 100644 --- a/spec/support/forms/with_validation_callbacks_form.rb +++ b/spec/support/forms/with_validation_callbacks_form.rb @@ -1,5 +1,8 @@ class WithValidationCallbacksForm < YAAF::Form - attr_accessor :email, :name, :before_counter, :after_counter + attribute :email, :string + attribute :name, :string + + attr_accessor :before_counter, :after_counter validates :name, format: { with: /[a-zA-Z]+/ }, allow_blank: true before_validation :add_to_before_counter From ffd95b5fc490000ea6a88de5ea6003e0aeca6e50 Mon Sep 17 00:00:00 2001 From: Andres Garcia Date: Tue, 3 Feb 2026 15:39:58 -0300 Subject: [PATCH 2/5] Test --- lib/yaaf/form.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/yaaf/form.rb b/lib/yaaf/form.rb index b3cf10e..9a43f19 100644 --- a/lib/yaaf/form.rb +++ b/lib/yaaf/form.rb @@ -6,9 +6,9 @@ class Form include ::ActiveModel::Model include ::ActiveModel::Validations::Callbacks include ::ActiveRecord::Transactions + include ::ActiveModel::Attributes if defined?(::ActiveModel::Attributes) if defined?(::ActiveModel::Attributes::Normalization) - include ::ActiveModel::Attributes include ::ActiveModel::Attributes::Normalization end From b4ff8cfcd29d95424e12175b40fbc59848855b32 Mon Sep 17 00:00:00 2001 From: Andres Garcia Date: Tue, 3 Feb 2026 16:03:26 -0300 Subject: [PATCH 3/5] Remove conditional --- lib/yaaf/form.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/yaaf/form.rb b/lib/yaaf/form.rb index 9a43f19..44e5d2f 100644 --- a/lib/yaaf/form.rb +++ b/lib/yaaf/form.rb @@ -6,7 +6,7 @@ class Form include ::ActiveModel::Model include ::ActiveModel::Validations::Callbacks include ::ActiveRecord::Transactions - include ::ActiveModel::Attributes if defined?(::ActiveModel::Attributes) + include ::ActiveModel::Attributes if defined?(::ActiveModel::Attributes::Normalization) include ::ActiveModel::Attributes::Normalization From ba46a21254daab8797a3857ee00a7136a04eb50f Mon Sep 17 00:00:00 2001 From: Andres Garcia Date: Tue, 3 Feb 2026 16:18:36 -0300 Subject: [PATCH 4/5] Update docs --- README.md | 2 +- docs/recipes/devise.md | 7 +++++-- docs/recipes/json_api.md | 5 +++-- docs/recipes/nested_forms.md | 3 ++- docs/recipes/simple_form.md | 3 ++- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 215d04b..e0b0216 100644 --- a/README.md +++ b/README.md @@ -181,7 +181,7 @@ The `.new` method should be called with the arguments that the form object needs When initializing a `YAAF` form object, there are two things to keep in mind 1. You need to define the `@models` instance variables to be an array of all the models that you want to be validated/saved within the form object. -2. To leverage `ActiveModel`'s features, you can call `super` to automatically make the attributes be stored in instance variables. If you use it, make sure to also add `attr_accessor`s, otherwise `ActiveModel` will fail. +2. To leverage `ActiveModel`'s features, you can call `super` to automatically make the attributes be stored in instance variables. If you use it, make sure to also add `attribute`s, otherwise `ActiveModel` will fail. ### #valid? diff --git a/docs/recipes/devise.md b/docs/recipes/devise.md index c8b4288..5ed2a5f 100644 --- a/docs/recipes/devise.md +++ b/docs/recipes/devise.md @@ -12,8 +12,11 @@ For example, a simple registration form will look like this: # app/forms/registration_form.rb class RegistrationForm < ApplicationForm - attr_accessor :first_name, :last_name, :email, :password, - :password_confirmation + attribute :first_name, :string + attribute :last_name, :string + attribute :email, :string + attribute :password, :string + attribute :password_confirmation, :string # To let Devise treat the form object as it were the actual user object delegate_missing_to :user diff --git a/docs/recipes/json_api.md b/docs/recipes/json_api.md index f892afb..298b52e 100644 --- a/docs/recipes/json_api.md +++ b/docs/recipes/json_api.md @@ -1,4 +1,4 @@ -# Using YAAF with nested forms +# Using YAAF with JSON API When you are using Rails as a JSON API, you can still use `YAAF` to build your models. @@ -45,7 +45,8 @@ end module Api module V1 class InviteForm < ApplicationForm - attr_accessor :email + attribute :email, :string + validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } def initialize(args = {}) diff --git a/docs/recipes/nested_forms.md b/docs/recipes/nested_forms.md index 505b590..430511a 100644 --- a/docs/recipes/nested_forms.md +++ b/docs/recipes/nested_forms.md @@ -49,7 +49,8 @@ end # app/forms/invite_form.rb class InviteForm < ApplicationForm - attr_accessor :email + attribute :email, :string + validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } def initialize(args = {}) diff --git a/docs/recipes/simple_form.md b/docs/recipes/simple_form.md index a15d109..425d4d4 100644 --- a/docs/recipes/simple_form.md +++ b/docs/recipes/simple_form.md @@ -12,7 +12,8 @@ For example, in a library management system (a pretty minimal one), the form to # app/forms/book_form.rb class BookForm < YAAF::Form - attr_accessor :name, :isbn + attribute :name, :string + attribute :isbn, :string def initialize(attributes) super(attributes) From 8663671476801e642717cf4e97144544abaec6cf Mon Sep 17 00:00:00 2001 From: Andres Garcia Date: Tue, 10 Feb 2026 11:15:32 -0300 Subject: [PATCH 5/5] Test form with attributes --- spec/support/forms.rb | 1 + spec/support/forms/with_attributes_form.rb | 13 ++++ spec/yaaf/attributes_spec.rb | 76 ++++++++++++++++++++++ 3 files changed, 90 insertions(+) create mode 100644 spec/support/forms/with_attributes_form.rb create mode 100644 spec/yaaf/attributes_spec.rb diff --git a/spec/support/forms.rb b/spec/support/forms.rb index e8bf235..e2c78f5 100644 --- a/spec/support/forms.rb +++ b/spec/support/forms.rb @@ -9,3 +9,4 @@ require_relative 'forms/custom_transaction_form' require_relative 'forms/user_destroy_form' require_relative 'forms/with_normalize_form' +require_relative 'forms/with_attributes_form' diff --git a/spec/support/forms/with_attributes_form.rb b/spec/support/forms/with_attributes_form.rb new file mode 100644 index 0000000..d9e45a2 --- /dev/null +++ b/spec/support/forms/with_attributes_form.rb @@ -0,0 +1,13 @@ +class WithAttributesForm < YAAF::Form + attr_accessor :attributes + + def initialize(attributes) + @attributes = attributes + + @models = [user] + end + + def user + @user ||= User.new(attributes) + end +end diff --git a/spec/yaaf/attributes_spec.rb b/spec/yaaf/attributes_spec.rb new file mode 100644 index 0000000..cae7fb5 --- /dev/null +++ b/spec/yaaf/attributes_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +RSpec.describe 'Form with attributes' do + let(:options) { {} } + let(:registration_form) { WithAttributesForm.new(attributes) } + let(:attributes) { { email: 'test@example.com', name: 'John' } } + + before { expect(registration_form).to be_valid } + + describe '#save' do + subject { registration_form.save(options) } + + it 'returns true' do + expect(subject).to eq true + end + + it 'saves the user' do + expect { subject }.to change { User.count }.by 1 + end + + it 'saves with correct information' do + expect { subject }.to change { + User.last&.email + }.to('test@example.com').and change { + User.last&.name + }.to('John') + end + end + + describe '#save!' do + subject { registration_form.save!(options) } + + it 'returns true' do + expect(subject).to eq true + end + + it 'saves the user' do + expect { subject }.to change { User.count }.by 1 + end + + it 'saves with correct information' do + expect { subject }.to change { + User.last&.email + }.to('test@example.com').and change { + User.last&.name + }.to('John') + end + end + + describe '#valid?' do + subject { registration_form.valid? } + + it { is_expected.to be true } + end + + describe '#invalid?' do + subject { registration_form.invalid? } + + it { is_expected.to be false } + end + + describe '#errors' do + subject do + registration_form.valid? + registration_form.errors + end + + it 'returns the correct class' do + expect(subject.class).to eq(ActiveModel::Errors) + end + + it 'is empty' do + expect(subject.messages).to be_empty + end + end +end