• Stars
    star
    352
  • Rank 120,622 (Top 3 %)
  • Language
    Ruby
  • License
    MIT License
  • Created over 4 years ago
  • Updated 27 days ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Easing the form object pattern in Rails applications

YAAF

YAAF (Yet Another Active Form) is a gem that let you create form objects in an easy and Rails friendly way. It makes use of ActiveRecord and ActiveModel features in order to provide you with a form object that behaves pretty much like a Rails model, and still be completely configurable.

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 Maintainability Test Coverage

Table of Contents

Motivation

Form Objects is a design pattern that allows us to:

  1. Keep views, models and controllers clean
  2. Create/update multiple models at the same time
  3. Keep business logic validations out of models

There are some other form objects gems but we felt none of them provided us all the features that we expected:

  1. Form objects that behave like Rails models
  2. Simple to use and to understand the implementation (no magic)
  3. Easy to customize
  4. Gem is well tested and maintained

For this reason we decided to build our own Form Object implementation. After several months in production without issues we decided to extract it into a gem to share it with the community.

If you want to learn more about Form Objects you can check out these great articles.

Why YAAF?

  • It is 71 lines long. As you can imagine, we did no magic in such a few lines of code, we just leveraged Rails modules in order to provide our form objects with a Rails-like behavior. You can review the code, it's easy to understand.

  • It provides a similar API to ActiveModel models so you can treat them interchangeably.

  • You can customize it 100%. We encourage you to have your own ApplicationForm which inherits from YAAF::Form and make the customizations you'd like for your app.

  • It helps decoupling the frontend from the database. This is particularly important when using Rails as a JSON API with a frontend in React/Ember/Vue/Angular/you name it. If you were to use accepts_nested_attributes_for your frontend would need to know your database structure in order to build the request. With YAAF you can provide a the interface you think it's best.

  • It easily supports nested models, collection of models and associated models. You have full control on their creation.

  • It helps you keep your models, views and controllers thin by providing a better place where to put business logic. In the end, this will improve the quality of your codebase and make it easier to maintain and extend.

  • It is an abstraction from production code. It has been working well for us, I'm confident it will work well for you too :)

Installation

Add this line to your application's Gemfile:

gem 'yaaf'

And then execute:

$ bundle install

Or install it yourself as:

$ gem install yaaf

Usage

In the following sections we explain some basic usage and the API provided by the gem. You can also find some recipes here.

Setting up a form object

In order to use a YAAF form object, you need to inherit from YAAF::Form and define the @models of the form, for example:

# app/forms/registration_form.rb

class RegistrationForm < YAAF::Form
  attr_accessor :user_attributes

  def initialize(attributes)
    super(attributes)
    @models = [user]
  end

  def user
    @user ||= User.new(user_attributes)
  end
end

By doing that you can work with your form object in your controller such as you'd do with a model.

# app/controllers/registrations_controller.rb

class RegistrationsController < ApplicationController
  def create
    registration_form = RegistrationForm.new(user_attributes: user_params)

    if registration_form.save
      redirect_to registration_form.user
    else
      render :new
    end
  end

  private

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

Form objects supports calls to valid?, invalid?, errors, save, save!, such as any ActiveModel model. The return values match the corresponding ActiveModel methods.

When saving or validating a form object, it will automatically validate all its models and promote the error to the form object itself, so they are accessible to you directly from the form object.

Form objects can also define validations like:

# app/forms/registration_form.rb

class RegistrationForm < YAAF::Form
  validates :phone, presence: true
  validate :a_custom_validation

  # ...

  def a_custom_validation
    # ...
  end
end

Validations can be skipped the same way as for ActiveModel models:

# app/controllers/registrations_controller.rb

class RegistrationsController < ApplicationController
  def create
    registration_form = RegistrationForm.new(user_attributes: user_params)

    registration_form.save!(validate: false)
  end

  private

  def user_params
    params.require(:user).permit(:email, :password, :password_confirmation)
  end
end

Form objects support the saving of multiple models at the same time, to prevent leaving the system in a bad state all the models are saved within a DB transaction.

A good practice would be to create an empty ApplicationForm and make your form objects inherit from it. This way you have a centralized place to customize any YAAF default behavior you would like.

class ApplicationForm < YAAF::Form
  # Customized behavior
end

#initialize

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_accessors, otherwise ActiveModel will fail.

#valid?

The #valid? method will perform both the form object validations and the models validations. It will return true or false and store the errors in the form object.

By default YAAF form objects will store model errors in the form object under the same key. For example if a model has an email attribute that had an error, the form object will provide an error under the email key (e.g. form_object.errors[:email]).

#invalid?

The #invalid? method is exactly the same as the .valid? method but will return the opposite boolean value.

#errors

The #errors method will return an ActiveModel::Errors object such as any other ActiveModel model.

#save

The #save method will run validations. If it's invalid it will return false, otherwise it will save all the models within a DB transaction and return true.

Defined callbacks will be called in the following order:

  • before_validation
  • after_validation
  • before_save
  • after_save
  • after_commit/after_rollback

Options:

  • If validate: false is send as options to the save call, it will skip validations.

#save!

The #save! method is exactly the same as the .save method, just that if it is invalid it will raise an exception.

Validations

YAAF form objects support validations the same way as ActiveModel models. For example:

class RegistrationForm < YAAF::Form
  validates :email, presence: true
  validate :some_custom_validation

  # ...
end

Callbacks

YAAF form objects support callbacks the same way as ActiveModel models. For example:

class RegistrationForm < YAAF::Form
  before_validation :normalize_attributes
  after_commit :send_confirmation_email

  # ...
end

Available callbacks are (listed in execution order):

  • before_validation
  • after_validation
  • before_save
  • after_save
  • after_commit/after_rollback

Sample app

You can find a sample app making use of the gem here. Its code is also open source, and you can find it here.

Links

Development

After checking out the repo, run bin/setup to install dependencies. Then, run bundle exec rspec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/rootstrap/yaaf. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Code of Conduct

Everyone interacting in the YAAF project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

Credits

YAAF is maintained by Rootstrap with the help of our contributors.

YAAF

More Repositories

1

rails_api_base

API boilerplate project for Ruby on Rails 7
Ruby
346
star
2

ios-base

Boilerplate for new iOS projects using Swift 5. Provides a handful of functionalities.
Swift
263
star
3

active-storage-base64

Base64 support for ActiveStorage
Ruby
148
star
4

htmx-rails

The easiest way to work with HTMX in your Rails app
Ruby
112
star
5

node-ts-api-base

REST API boilerplate made with Express + NodeJS
TypeScript
106
star
6

rails-modular-monolith-with-ddd

Full Modular Monolith Rails application with Domain-Driven Design approach. Inspired by https://github.com/kgrzybek/modular-monolith-with-ddd
96
star
7

activeadmin-chat

ActiveAdmin chat plugin
Ruby
89
star
8

apple_auth

Complete Ruby gem for Sign in with Apple. Actively maintained by rootstrap.com
Ruby
88
star
9

swift-ui-base

SwiftUI base is a boilerplate project created by Rootstrap for new projects using SwiftUI. The main objective is helping any new projects jump start into feature development by providing a handful of functionalities.
Swift
86
star
10

RSFormView

A Cocoapods library designed to easily create forms with multiple data entry fields
Swift
86
star
11

exception_hunter

Crash reporting engine to hunt down bugs 🐞
Ruby
81
star
12

rails_hotwire_base

Rails + Hotwire base app
Ruby
76
star
13

PagedLists

Paginated UITableView and UICollectionViews for iOS.
Swift
71
star
14

react-native-use-styles

A classy approach to manage your react native styles.
JavaScript
65
star
15

tech-guides

Guidelines that document processes and standards followed by our entire organization
64
star
16

react-native-base

React Native-Redux Boilerplate
TypeScript
53
star
17

django-drip-campaigns

💧 Use Django admin to manage drip campaign emails using querysets on Django's User model.
Python
51
star
18

arqo

Easing the query object pattern in Rails applications
Ruby
50
star
19

i18n_linter

Rails i18n Linter Gem
Ruby
44
star
20

NeatTipView

A swift library to easily create and present tips for you user in your iOS app
Swift
43
star
21

ai-job-title-area-classification

Classification of job titles into categories, using different ML techniques
Python
43
star
22

react-native-use-animate

Animations in React native made simple
JavaScript
41
star
23

android-base

Rootstrap Android Base project
Kotlin
39
star
24

validate

An extension to the popular library validate.js that adds some useful custom validations out of the box. Also, a hub for all custom validations, that we have created, so you can easily add them to your own project.
JavaScript
31
star
25

mobx-session

mobx react session managment using localforage
JavaScript
29
star
26

active_outbox

A Transactional Outbox implementation for Rails and ActiveRecord
Ruby
27
star
27

RSFontSizes

RSFontSizes pod repository. Allows you to customize fonts and sizes in different screen sizes.
Swift
27
star
28

ai-job-title-level-classification

Python
24
star
29

rsgem

Rootstrap way ® to generate gems
Ruby
24
star
30

best_buy_ruby

Ruby library for the BestBuy API. https://www.bestbuy.com
Ruby
22
star
31

pull_requests_to_slack

Send Github pull request notifications to Slack
Ruby
21
star
32

SwiftGradients

Useful extensions for UIViews and CALayer classes to add beautiful color gradients.
Swift
18
star
33

redux-tools

Redux tools to speed up development.
JavaScript
16
star
34

airflow-examples

Python
14
star
35

chat-gpt-nodejs

NodeJS REST-API to interact with OpenAI
TypeScript
14
star
36

dolphin-nft-marketplace-frontend

TypeScript
12
star
37

swift-lib-builder

Builder for Cocoapod, Carthage and Swift Package manager libraries in Swift.
Swift
11
star
38

FlowForms

Reactive and declarative Form management library for Kotlin projects
Kotlin
9
star
39

rs-code-review-metrics

Ruby
9
star
40

slack-gpt-base-bot-node

JavaScript
8
star
41

rails_hotwire_playground

Ruby
7
star
42

compress-s3-tinypng

Losslessly compresses and Optimizes PNG and JPG files. Uses TinyPNG API.
Python
6
star
43

gemini-nodejs

NodeJS REST-API to interact with Google Gemini
TypeScript
6
star
44

activeadmin-async_exporter

Async exporter for Active Admin using ActiveJob
Ruby
5
star
45

livy-base

Apache Livy is a service that enables easy interaction with a Spark cluster over a REST interface
Shell
5
star
46

rails_base

Configurable Rails backend generator
Ruby
5
star
47

flutter-base

Dart
5
star
48

web-a11y-demos

JavaScript
5
star
49

apple-sign-in-rails

App for testing apple sign in gem
Ruby
5
star
50

MRI-classifier

Jupyter Notebook
5
star
51

ctakes

cTAKES - instructions and example
Dockerfile
5
star
52

rs-gpt-review

TypeScript
5
star
53

blog

Repository for submitting and reviewing posts for Rootstrap blog
4
star
54

UnicodeEmoji

iOS Library that loads official Unicode Emoji repositories and make them accessible to your app.
Swift
3
star
55

ml-training

Machine Learning tutorials and examples
Jupyter Notebook
3
star
56

swift-unity-integration

Integration of Unity build into an iOS(Swift) project.
C#
3
star
57

biobert-test

Python
3
star
58

hotwire-workshop

Ruby
3
star
59

rubocop-rootstrap

To provide default configuration files for Rubocop and the ability to create custom cops
Ruby
3
star
60

node-ts-api-base-legacy

Base Project for NodeJs + TypeScript Backends
TypeScript
3
star
61

fastai-waste-classifier

Jupyter Notebook
3
star
62

htmx-rails_examples

Ruby
2
star
63

phoenix-target-api

Elixir
2
star
64

nest-target-api

Proof of concept of a nest api project. Trello: https://trello.com/b/4ZrvgNnq/nest-target-api
TypeScript
2
star
65

react-native-peeking-header

React native header that hides when scrolling down and shows when scrolling up.
JavaScript
2
star
66

react-ts-base

react-ts-base
TypeScript
2
star
67

create-rootstrap-react-native-app

TypeScript
2
star
68

yaaf-examples

Rails app with YAAF usage examples
Ruby
2
star
69

courier

A middleware gem for deep links that survive the install process
Ruby
2
star
70

typescript-workshop

2
star
71

django-base

Django boilerplate for rest-api backends
Python
2
star
72

rs-wordle

JavaScript
2
star
73

datasciene-ecommerce

A list of examples/experiments machine learning are provided
Jupyter Notebook
2
star
74

docker-workshop

Materials for internal Docker for Devs workshop
Shell
2
star
75

rails_base_extensions

Add features to your rails project in a simple way!
Ruby
2
star
76

android-base-compose

Kotlin
2
star
77

gangogh

Python
1
star
78

ios-bases-api

API for demo iOS projects.
Ruby
1
star
79

RSRoutingSwift

iOS Routing
Swift
1
star
80

medical-pipeline

Data pipeline for processing medical text records
Python
1
star
81

RSFormViewExample

A RSFormView example showcasing dark mode
Swift
1
star
82

blackmarket-remix-research

TypeScript
1
star
83

react-native-use-toast

React native hook for integrating an extensible/ultra-customizable toast 📬
1
star
84

vue-vs-react

Todo app example made in vuejs and in react
Vue
1
star
85

django-feature-flip

Minimal Django app for feature flipping
Python
1
star
86

coronavirus-analysis

HTML
1
star
87

RSSwiftNetworking

A Swift framework that provides a network communication layer API
Swift
1
star
88

sprinkler-iot

Elixir
1
star
89

rails_admin_s3_file

A rails admin plugin to direct upload assets to s3.
HTML
1
star
90

spinner-playground

Swift
1
star
91

neuro-backyardbrains

train a ML model using data from backyard brains device
Jupyter Notebook
1
star
92

openform-rbac-api

Ruby
1
star
93

rootstrap-ui

Rootstrap's UI Components & Styles
CSS
1
star
94

mmhuman3d-docker

Docker for mmhuman3d - open-mmlab exposing streamlit app
Python
1
star
95

cra-template-base

A template created to generate a new CRA based code base for React projects.
JavaScript
1
star