Using Delegation and Nested Attributes Approaches

In Rails, modeling indirect associations is easy, but editing them through forms can be tricky. Let’s walk through two clean ways to let users select a Category (grandparent) for a Url (child), where the association runs through an intermediate Domain (parent).

We’re working with the following model setup:

  • Category has many Domains
  • Domain has many Urls and belongs to a Category
  • Url belongs to a Domain
class Category < ApplicationRecord
  has_many :domains
end

class Domain < ApplicationRecord
  belongs_to :category
  has_many :urls
end

class Url < ApplicationRecord
  belongs_to :domain
  has_one :category, through: :domain
end

Our Goal

In the Url form, allow the user to select a Category, and update the associated Domain’s category_id accordingly.

Solution 1: Delegate category_id to Domain

Step 1: Delegate in Url

class Url < ApplicationRecord
  belongs_to :domain
  has_one :category, through: :domain

  delegate :category_id, :category_id=, to: :domain, allow_nil: true
end

Step 2: Build domain if missing (in controller)

def new
  @url = Url.new
  @url.build_domain unless @url.domain
end

Step 3: Update the form

<%= simple_form_for @url do |f| %>
  <%= f.input :name %>
  <%= f.input :category_id, collection: Category.all, label: "Category" %>
  <%= f.button :submit %>
<% end %>

Step 4: Permit category_id in the controller

params.require(:url).permit(:name, :category_id)

Solution 2: Use Nested Attributes for Domain

Step 1: Accept nested attributes in Url

class Url < ApplicationRecord
  belongs_to :domain
  accepts_nested_attributes_for :domain
end

Step 2: Form with nested fields

<%= simple_form_for @url do |f| %>
  <%= f.input :name %>

  <%= f.simple_fields_for :domain do |df| %>
    <%= df.input :category_id, collection: Category.all, label: "Category" %>
  <% end %>

  <%= f.button :submit %>
<% end %>

Step 3: Permit nested params in controller

params.require(:url).permit(:name, domain_attributes: [:category_id])

Step 4: Ensure domain is built if missing

def new
  @url = Url.new
  @url.build_domain
end

Need Help With Ruby On Rails Development?

Work with our skilled Ruby on Rails developers to accelerate your project and boost its performance.

Hire Ruby on Rails Developer

Support On Demand!

Related Q&A