Check out my consultancy offering at RailsReviews.com!

DynamicFormReflex

Create and manage complex forms with page morphs

How?

  • A model (Address) has associations to two interdependent other models (Country => State)
  • Via a DynamicFormReflex#refresh action, manage the state_id select box, which depends on the country

Caveat To use this with unpersisted records, you will need to adapt the def resource method slightly:

def resource
  @resource ||= if element.dataset.sgid.present?
    element.signed[:sgid] 
  else
    element.dataset.resource_name.classify.new
  end
end

Variations

  • You can also use this with has_many associations:
class Address < ApplicationRecord
  belongs_to :tenant
  has_many :inhabitants
  
  accepts_nested_attributes_for :inhabitants
class Controller
  def edit
    @address = Address.find(params[:id])
    
    @address.inhabitants.build
  end
end
<%= form_with model: @address do |form| %>
  <%= form.label :tenant_id %>
  <%= form.collection_select :tenant_id, Tenant.all, :id, :name, {include_blank: true}, 
    data: {reflex: "change->DynamicForm#refresh", sgid: @address.to_sgid.to_s, 
    resource_name: "address", association: "tenant"} %>
  
  <%= form.fields_for :inhabitants do |inhabitant_fields| %>
    <%= inhabitant_fields.label :inhabitant_id %>
    <%= inhabitant_fields.collection_select :inhabitant_id, @address.tenant&.members || [], :id, :name %>
  <% end %>
<% end %>

app/reflexes/dynamic_form_reflex.rb

class DynamicFormReflex < ApplicationReflex
  def refresh
    resource.send("#{element.dataset.association}=", element.dataset.association.classify.safe_constantize.find(element.value))

    instance_variable_set "@#{element.dataset.resource_name}", resource
  end

  def resource
    @resource ||= element.signed[:sgid]
  end
end

app/controllers/addresses_controller.rb

class AddressesController < ApplicationController
  def edit
    @address = Address.find(params[:id])
  end
end

app/models/address.rb

class Address < ApplicationRecord
  belongs_to :country
  belongs_to :state
end

app/models/country.rb

class Country < ApplicationRecord
  has_many :states
end

app/models/state.rb

class State < ApplicationRecord
  belongs_to :country
end

app/views/addresses/edit.html.erb

<%= form_with model: @address do |form| %>  
  <%= form.label :country_id %>
  <%= form.collection_select :country_id, Countries.all, :id, :name, 
    {include_blank: true}, data: {reflex: "change->DynamicForm#refresh", 
    sgid: @address.to_sgid.to_s, resource_name: "address", association: "country"} %>

  <%= form.label :state_id %>
  <%= form.collection_select :state_id, @address.country&.states || [], :id, :name %>

  <%= form.submit %>
<% end %>