diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b6e8e60..11f9c81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: strategy: fail-fast: false matrix: - gemfile: [rails_5_2.gemfile, rails_6_0.gemfile, rails_6_1.gemfile, rails_7_0.gemfile, rails_7_1.gemfile, rails_7_2.gemfile, rails_8_0.gemfile, rails_main.gemfile] + gemfile: [rails_5_2.gemfile, rails_6_0.gemfile, rails_6_1.gemfile, rails_7_0.gemfile, rails_7_1.gemfile, rails_7_2.gemfile, rails_8_0.gemfile, rails_8_1.gemfile, rails_main.gemfile] ruby_version: ['2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4'] exclude: - gemfile: rails_main.gemfile @@ -29,6 +29,22 @@ jobs: ruby_version: '3.0' - gemfile: rails_main.gemfile ruby_version: '3.1' + - gemfile: rails_main.gemfile + ruby_version: '3.2' + - gemfile: rails_8_1.gemfile + ruby_version: '2.3' + - gemfile: rails_8_1.gemfile + ruby_version: '2.4' + - gemfile: rails_8_1.gemfile + ruby_version: '2.5' + - gemfile: rails_8_1.gemfile + ruby_version: '2.6' + - gemfile: rails_8_1.gemfile + ruby_version: '2.7' + - gemfile: rails_8_1.gemfile + ruby_version: '3.0' + - gemfile: rails_8_1.gemfile + ruby_version: '3.1' - gemfile: rails_8_0.gemfile ruby_version: '2.3' - gemfile: rails_8_0.gemfile @@ -117,22 +133,9 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby_version }} - - name: Before build - run: | - sudo apt-get install libsqlite3-dev - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build - env: - CC_TEST_REPORTER_ID: aff2c7b9e07e54d5fc9e5588d2e2a8bab4f69950d35000edc2b6250bbaba477d - name: Run test run: | bundle update bundle install --gemfile spec/gemfiles/${{ matrix.gemfile }} --jobs 4 --retry 3 bundle exec rake code_analysis bundle exec rspec - - name: Report to CodeClimate - run: | - ./cc-test-reporter after-build --exit-code 0 - env: - CC_TEST_REPORTER_ID: aff2c7b9e07e54d5fc9e5588d2e2a8bab4f69950d35000edc2b6250bbaba477d diff --git a/README.md b/README.md index 95806e0..dbb91d7 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,6 @@ YAAF (Yet Another Active Form) is a gem that let you create form objects in an e We were going to name this gem `ActiveForm` to follow Rails naming conventions but given there are a lot of form object gems named like that we preferred to go with `YAAF`. ![CI](https://github.com/rootstrap/yaaf/workflows/CI/badge.svg) -[![Maintainability](https://api.codeclimate.com/v1/badges/c3dea064e1003b700260/maintainability)](https://codeclimate.com/github/rootstrap/yaaf/maintainability) -[![Test Coverage](https://api.codeclimate.com/v1/badges/c3dea064e1003b700260/test_coverage)](https://codeclimate.com/github/rootstrap/yaaf/test_coverage) ## Table of Contents @@ -23,6 +21,7 @@ We were going to name this gem `ActiveForm` to follow Rails naming conventions b - [#save!](#save!) - [Validations](#validations) - [Callbacks](#callbacks) + - [Normalizes](#normalizes) - [Sample app](#sample-app) - [Links](#links) - [Development](#development) @@ -249,6 +248,19 @@ Available callbacks are (listed in execution order): - `after_save` - `after_commit/after_rollback` +### Normalizes (Rails 8.1+) + +`YAAF` form objects support `normalizes` the same way as `ActiveModel` models. For example: + +```ruby +class RegistrationForm < YAAF::Form + normalizes :email, with: ->(email) { email.strip.downcase } + normalizes :name, with: ->(name) { name.strip.titleize } + + # ... +end +``` + ## Sample app You can find a sample app making use of the gem [here](https://yaaf-examples.herokuapp.com). Its code is also open source, and you can find it [here](https://github.com/rootstrap/yaaf-examples). diff --git a/Rakefile b/Rakefile index 07fa488..981c803 100644 --- a/Rakefile +++ b/Rakefile @@ -2,5 +2,4 @@ task :code_analysis do sh 'bundle exec rubocop lib spec' - sh 'bundle exec reek lib' end diff --git a/lib/yaaf/form.rb b/lib/yaaf/form.rb index 8e0417d..b3cf10e 100644 --- a/lib/yaaf/form.rb +++ b/lib/yaaf/form.rb @@ -6,6 +6,12 @@ class Form include ::ActiveModel::Model include ::ActiveModel::Validations::Callbacks include ::ActiveRecord::Transactions + + if defined?(::ActiveModel::Attributes::Normalization) + include ::ActiveModel::Attributes + include ::ActiveModel::Attributes::Normalization + end + define_model_callbacks :save delegate :transaction, to: ::ActiveRecord::Base diff --git a/spec/gemfiles/rails_8_1.gemfile b/spec/gemfiles/rails_8_1.gemfile new file mode 100644 index 0000000..63366c8 --- /dev/null +++ b/spec/gemfiles/rails_8_1.gemfile @@ -0,0 +1,5 @@ +source 'https://rubygems.org' + +gemspec path: '../..' + +gem 'rails', '~> 8.1.0' diff --git a/spec/support/forms.rb b/spec/support/forms.rb index 42d178c..e8bf235 100644 --- a/spec/support/forms.rb +++ b/spec/support/forms.rb @@ -8,3 +8,4 @@ require_relative 'forms/with_callback_exception_raising' require_relative 'forms/custom_transaction_form' require_relative 'forms/user_destroy_form' +require_relative 'forms/with_normalize_form' diff --git a/spec/support/forms/with_normalize_form.rb b/spec/support/forms/with_normalize_form.rb new file mode 100644 index 0000000..42b8658 --- /dev/null +++ b/spec/support/forms/with_normalize_form.rb @@ -0,0 +1,19 @@ +class WithNormalizeForm < YAAF::Form + if defined?(ActiveModel::Attributes::Normalization) + attribute :email, :string + attribute :name, :string + + normalizes :email, with: ->(email) { email.strip.downcase } + normalizes :name, with: ->(name) { name.strip.titleize } + + def initialize(args) + super(args) + + @models = [user] + end + + def user + @user ||= User.new(email: email, name: name) + end + end +end diff --git a/spec/yaaf/normalization_spec.rb b/spec/yaaf/normalization_spec.rb new file mode 100644 index 0000000..c1e5b49 --- /dev/null +++ b/spec/yaaf/normalization_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +RSpec.describe 'Form with normalization' do + if defined?(ActiveModel::Attributes::Normalization) + let(:form) { WithNormalizeForm.new(args) } + let(:args) { { email: ' TEST@Example.com ', name: ' john doe ' } } + + describe 'attribute normalization' do + it 'normalizes email on assignment' do + expect(form.email).to eq('test@example.com') + end + + it 'normalizes name on assignment' do + expect(form.name).to eq('John Doe') + end + end + + describe '#save' do + subject { form.save } + + it 'saves with normalized values' do + expect(subject).to be true + expect(User.last.email).to eq('test@example.com') + expect(User.last.name).to eq('John Doe') + end + end + + describe '#valid?' do + it 'validates with normalized values' do + expect(form.valid?).to be true + end + end + + context 'when updating attributes' do + it 'normalizes new values' do + form.email = ' test@example.com ' + expect(form.email).to eq('test@example.com') + end + end + + context 'with nil values' do + let(:args) { { email: nil, name: nil } } + + it 'does not normalize nil by default' do + expect(form.email).to be_nil + expect(form.name).to be_nil + end + end + end +end diff --git a/yaaf.gemspec b/yaaf.gemspec index 91ee8c6..bdbaea7 100644 --- a/yaaf.gemspec +++ b/yaaf.gemspec @@ -26,7 +26,6 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'database_cleaner-active_record', '~> 2.1.0' spec.add_development_dependency 'rake', '~> 13.0.1' - spec.add_development_dependency 'reek', '~> 5.6.0' spec.add_development_dependency 'rspec', '~> 3.9.0' spec.add_development_dependency 'rubocop', '~> 0.80.0' spec.add_development_dependency 'simplecov', '~> 0.17.1'