-
Notifications
You must be signed in to change notification settings - Fork 4
Step by step : Manual configuration
Welcome to this tutorial !
Let's get you through the manual configuration for the abyme
gem.
Let's consider a to-do application with Projects having many Taks, themselves having many Comments.
# models/project.rb
class Project < ApplicationRecord
has_many :tasks
validates :title, :description, presence: true
end
# models/task.rb
class Task < ApplicationRecord
belongs_to :project
has_many :comments
validates :title, :description, presence: true
end
# models/comment.rb
class Comment < ApplicationRecord
belongs_to :task
validates :content, presence: true
end
The end-goal here is to be able to create a project along with different tasks, and immediately add comments to some of these tasks ; all within a single form.
What we'll have is a 2-level nested form. Thus, we'll need to configure our Project
and Task
models like so :
# models/project.rb
class Project < ApplicationRecord
include Abyme::Model
has_many :tasks, inverse_of: :project
# ...
abymize :tasks
end
# models/task.rb
class Task < ApplicationRecord
include Abyme::Model
has_many :comments, inverse_of: :task
# ...
abymize :comments
end
Note the use of the inverse_of
option. It is needed for Rails to effectively associate children to their yet unsaved parent. Have a peek to the bottom of this page for more info.
Dealing with nested attributes means you'll generally have to handle a few things inside your form:
- Display fields for the persisted records (here, pre-existing
:tasks
) - Display fields for the new records (future
:tasks
not yet persisted) - A button to trigger the addition of fields for a new resource (an
Add a new task
button) - A button to remove fields for a given resource (
Remove task
)
abyme provides helper methods for all these. Here's how our form for Project
looks like when using default values and simple_form
(abyme
is agnostic and should work with any FormBuilder
):
# views/projects/_form.html.erb
<%= simple_form_for @project do |f| %>
<%= f.input :title %>
<%= f.input :description %>
<%= f.submit 'Save' %>
<%= f.abyme_for(:tasks) do |abyme| %>
<%= abyme.records %>
<%= abyme.new_records %>
<%= add_associated_record %>
<% end %>
<% end %>
abyme.records
will contain the persisted associations fields, while abyme.new_records
will contain fields for the new associations. add_associated_record
will by default generate a button with a text of type "Add resource_name
". To work properly, this method has to be called inside the block passed to the abyme_for
method.
Now where's the code for these fields ? abyme will assume a partial to be present in the directory /views/abyme
with a name respecting this naming convention (just like with cocoon): _singular_association_name_fields.html.erb
.
This partial might look like this:
# views/abyme/_task_fields.html.erb
<%= f.input :title %>
<%= f.input :description %>
<%= f.hidden_field :_destroy %>
<%= remove_associated_record(tag: :div) do %>
<i class="fas fa-trash"></i>
<% end %>
Note the presence of the remove_associated_record
button. Here, we pass it an option to make it a <div>
, as well as a block to customize its content. Don't forget the _destroy
attribute, needed to mark items for destruction.
Since we're dealing with one form, we're only concerned with one controller : the one the form routes to. In our example, this would be the ProjectsController
.
The only configuration needed here will concern our strong params. Nested attributes require a very specific syntax to white-list the permitted attributes. It looks like this :
def project_params
params.require(:project).permit(
:title, :description, tasks_attributes: [
:id, :title, :description, :_destroy, comments_attributes: [
:id, :content, :_destroy
]
]
)
end
A few explanations here.
- To permit a nested model attributes in your params, you'll need to pass the
association_attributes: [...]
hash at the end of your resource attributes. Key will always beassociation_name
followed by_attributes
, while the value will be an array of symbolized attributes, just like usual.
Note: if your association is a singular one (
has_one
orbelongs_to
) the association will be singular ; if a Projecthas_one :owner
, you would then need to passowner_attributes: [...]
)
- You may have remarked the presence of
id
and_destroy
among those params. These are necessary for edit actions : if you want to allow your users to destroy or update existing records, these are mandatory. Otherwise, Rails won't be able to recognize these records as existing ones, and will just create new ones. More info here.
Well, yes. This is the actual magical thing about nested_attributes
: once your model is aware of its acceptance of those for a given association, and your strong params are correctly configured, there's nothing else to do.
@project.create(project_params)
is all you'll need to save a project along with its descendants 👨👧👧
Let's now take care of our comments fields. We'll add these using our neat automatic mode: just stick this line at the end of the partial :
# views/abyme/_task_fields.html.erb
# ... rest of the partial above
<%= f.abyme_for(:comments) %>
The rest of the code ? If the default configuration you saw above in the form.html.erb
suits you, and the order in which the different resources appear feels right (persisted first, new fields second, and the 'Add' button last), then you can just spare the block, and it will be taken care of for you. We'll just write our comment_fields.html.erb
partial in the views/abyme
directory and we'll be all set.
If you want to know more about more advanced configuration option, get a peak into our advanced configuration wiki.