I have 3 models in Rails, Category (grandparent) has_many
Domain (parents), which has_many
Url (children). Here, users can create/edit Url but not the other two; Domain model is automatically created/modified, according to Url, and is automatically associated to Url.
In the new/edit SimpleForm (simple_form) screen for Url, I provide a form field for Category so that the user can select/edit the Category the Url belongs to through Domain; namely, the user can actually update the content of the parent Domain#category_id
(under the hood). Since Category is not the direct parent of Url, a simple f.association
does not work out of the box.
So I write the model Url as follows
class Url < ApplicationRecord
belongs_to :domain
has_one :category, through: :domain
def category_id
category ? category.id : nil
end
attr_writer :category_id
and the view _form.html.erb
<%= f.input :category_id, collection: Category.all %>
Although the form looks OK on the browser, showing the currently associated Category of Url as expected, and although params returns the expected Category-ID value and I can set it to @url
instance, the Url controller still fails to update the assocition (of parent Domain) to Category. How can I make it work?
accepts_nested_attributes_for
handles this:
class Category < ApplicationRecord
end
class Domain < ApplicationRecord
belongs_to :category
end
class Url < ApplicationRecord
belongs_to :domain
accepts_nested_attributes_for :domain
end
<% @url.build_domain unless @url.domain %>
<%= simple_form_for @url do |f| %>
<%= f.simple_fields_for :domain do |ff| %>
<%= ff.association :category, label_method: :name %>
<% end %>
<%= f.submit %>
<% end %>
Permit nested fields in controller:
params.expect(url: [domain_attributes: [:category_id, :id]])
If you don't want nested fields, you can delegate
to domain
:
class Category < ApplicationRecord
end
class Domain < ApplicationRecord
belongs_to :category
end
class Url < ApplicationRecord
belongs_to :domain, autosave: true # autosave - to save domain on url save
delegate :category_id, :category_id=, to: :domain
def domain
super || build_domain # always have domain to delegate to
end
end
<%= simple_form_for @url do |f| %>
<%= f.input :category_id, collection: Category.all, label_method: :name %>
<%= f.submit %>
<% end %>
Permit fields in controller:
params.expect(url: [:category_id])