A wrapper to form objects in ActiveRecord. Forget accepts_nested_attributes_for
.
Installation
Add this line to your application's Gemfile:
gem 'linker'
And then execute:
$ bundle
Or install it yourself as:
$ gem install linker
Supported versions
- Ruby 2.0+
- Rails 3.1+ (including 4 and 5)
Usage
Given the below model:
class User < ActiveRecord::Base
belongs_to :company
belongs_to :family
has_one :address, dependent: :destroy
has_one :profile
has_many :dependent_users, dependent: :destroy
has_many :tasks, dependent: :destroy
end
Create a form class through rails g form whatever
or manually (it should include Linker and have main model set like below).
class UserForm
include Linker
# we infer the main model through the form's class name, but you can set manually like:
main_model User # or :user or 'User' ====> this line is optional
# Use relationship's name followed by "__" plus attribute's name
# to validate has_one and belongs_to associations
validates :name, :address__street, :company__name, presence: true
end
Now you can create a new form for existing user UserForm.new(User.find params[:id])
or to a new one UserForm.new
:
class UsersController < ApplicationController
def new
@user_form = UserForm.new # same as UserForm.new(User.new)
end
def create
@user_form = UserForm.new(User.new)
@user_form.params = users_form_params
if @user_form.save
redirect_to users_path, notice: 'User created successfully'
else
render :new
end
end
def edit
@user_form = UserForm.new(User.find(params[:id])) # you need to load the record being edited
end
end
By default, save
method will perform validations, you can disable them by passing validate: false
as parameter.
Finally, you can use fields_for
in order to create/edit associated fields and the suffix _list
(like profile_list
below) to choose an existing associated record.
<%= form_for @user_form do |f| %>
<%= f.text_field :name %>
<%= f.fields_for :tasks do |ta| %>
<%= ta.hidden_field :id %>
<%= ta.text_field :name %>
<%= f.fields_for :company do |co| %>
<%= co.hidden_field :id %>
<%= co.text_field :name %>
<%= co.text_field :website %>
<%= f.select :profile_list, Profile.all.map{|c| [c.profile_type, c.id]}, include_blank: true %>
<% end %>
How it works
Linker will create delegators for all attributes of main model and its associations (has_one
, has_many
and belongs_to
, except for polymorphic ones) and let it ready to use.
Internally, it will include ActiveModel::Model
if on Rails 4+ or ActiveModel::Validations
if on Rails < 4.
It will also create params=
and save
methods responsible to set new objects and save them. You can override these methods to get a custom behavior without worrying with delegations.
You can check out a demo project using Linker gem here. Click here to see it live.
Callbacks
There are some callbacks you can override to keep your controllers DRY:
-
after_init
: runs afterinitialize
method of form class. Can be used to set default field values or to prepare data to form. -
before_set_params(params)
: runs beforeparams=
method. Can be used to change params inside the form class, like string formatting. -
before_save
: runs before save method, except if some validation has failed. -
after_save
: runs after save method. You can enqueue some background job here for instance.
Example:
class CarsForm
include Linker
attr_accessor :before_save_checked
validates :name, presence: true
def before_set_params(params)
params['name'] = "#{params['name']} 2000"
end
def before_save
@before_save_checked = true
end
def after_save
HardWorker.perform_async('bob', 5)
end
end
Roadmap
- Create delegators only for specified associations (not all as now). For ie:
main_model :car, only_associations: [:customer]
- Rename this gem? Maybe.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create a new Pull Request
License
This projected is licensed under the terms of the MIT license.