Skip to content

Step by step : Manual configuration

Louis Sommer edited this page Feb 27, 2021 · 1 revision

Welcome to this tutorial !

Let's get you through the manual configuration for the abyme gem.

Models

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.

On to the views

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.

Controller

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 be association_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 or belongs_to) the association will be singular ; if a Project has_one :owner, you would then need to pass owner_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.

Wait, that's it ?

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 👨‍👧‍👧

Auto mode

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.

Clone this wiki locally