Home > Back-end >  Rails 7 has_many through not loading associated records when editing record
Rails 7 has_many through not loading associated records when editing record

Time:11-11

I have the following models:

class Property < ApplicationRecord
  belongs_to :property_type
  belongs_to :landlord
  belongs_to :property_transaction
  has_many_attached :photos
  has_many :included_amenities, dependent: :destroy
  has_many :included_services, dependent: :destroy
  has_many :amenities, through: :included_amenities
  has_many :services, through: :included_services
end

class Amenity < ApplicationRecord
    has_many :included_amenities
    has_many :properties, through: :included_amenities
    validates_presence_of :name, :en_translation_key
    validates_uniqueness_of :name, :en_translation_key
end

class Service < ApplicationRecord
    has_many :included_services
    has_many :properties, through: :included_services
    validates_presence_of :name, :en_translation_key
    validates_uniqueness_of :name, :en_translation_key
end

class IncludedAmenity < ApplicationRecord
    belongs_to :amenity
    belongs_to :property
end

class IncludedService < ApplicationRecord
    belongs_to :service
    belongs_to :property
end

I can confirm that when I create a new record via UI, the associated models get saved successfully into the intermediate table. However, when I try to edit the record via UI, I can also see that Rails loads an empty hash if I call property.services at controller level, for example. What's also weird is that supposedly ActiveRecord is indeed calling the correct INNER JOINS:

Processing by PropertiesController#edit as HTML
  Parameters: {"id"=>"2"}
  Property Load (0.4ms)  SELECT "properties".* FROM "properties" WHERE "properties"."id" = $1 LIMIT $2  [["id", 2], ["LIMIT", 1]]
  ↳ app/controllers/properties_controller.rb:158:in `set_property'
  Service Load (0.3ms)  SELECT "services".* FROM "services" INNER JOIN "included_services" ON "services"."id" = "included_services"."service_id" WHERE "included_services"."property_id" = $1  [["property_id", 2]]  ↳ app/controllers/properties_controller.rb:73:in `edit'
  CACHE Service Load (0.0ms)  SELECT "services".* FROM "services" INNER JOIN "included_services" ON "services"."id" = "included_services"."service_id" WHERE "included_services"."property_id" = $1  [["property_id", 2]]
  ↳ app/controllers/properties_controller.rb:73:in `edit'

The query is correct as well. What am I missing? Just in case, here's the template for _form.html.erb view:

  <div >
    <%= form.label :services, 'Services', class: 'form-label' %>
    <%= form.collection_check_boxes(:services, @services, :id, :name) do |c| %>
      <div >
        <%= c.check_box(class: 'custom-control-input') %>
        <%= c.label(class: 'custom-control-label') %>
      </div>
    <% end %>
  </div>

Edit: Adding controller code. As far as I had understood, Rails matches whenever the form has a @property and its related attributes and ties it to the form, or am I missing something?

class PropertiesController < ApplicationController
  before_action :set_property, only: %i[show edit update destroy]
  before_action :set_proptypes, only: %i[index edit update new create]
  before_action :set_services, only: %i[edit update new create]
  
  # ...
  # GET /properties/1/edit
  def edit
    puts @property
  end

  def set_property
    @property = Property.find(params[:id])
  end

  def set_services
    @services = Service.select(:id, :name)
  end
end

CodePudding user response:

Not sure why I needed to do this, I'm guessing it's a workaround:

<div >
  <%= form.label :services, 'Services', class: 'form-label' %>
  <%= form.collection_check_boxes(:services, @services, :id, :name) do |c| %>
    <div >
      <%= c.check_box(checked: property.included_services.map(&:service_id).include?(c.value), class: 'custom-control-input') %>
      <%= c.label(class: 'custom-control-label') %>
    </div>
  <% end %>
</div>
  • Related