diff --git a/lib/administrate/field/associative.rb b/lib/administrate/field/associative.rb
index 989fb6c899414ec31a0ef316a252ae7a2f00e81f..73d0434e81f6a2754cfef79c3f8d592bed8c1184 100644
--- a/lib/administrate/field/associative.rb
+++ b/lib/administrate/field/associative.rb
@@ -7,6 +7,10 @@ module Administrate
         reflection(resource_class, attr).foreign_key
       end
 
+      def self.association_primary_key_for(resource_class, attr)
+        reflection(resource_class, attr).association_primary_key
+      end
+
       def self.associated_class(resource_class, attr)
         reflection(resource_class, attr).klass
       end
@@ -49,10 +53,16 @@ module Administrate
       end
 
       def primary_key
+        # Deprecated, renamed `association_primary_key`
+        Administrate.warn_of_deprecated_method(self.class, :primary_key)
+        association_primary_key
+      end
+
+      def association_primary_key
         if option_given?(:primary_key)
           deprecated_option(:primary_key)
         else
-          :id
+          self.class.association_primary_key_for(resource.class, attribute)
         end
       end
 
diff --git a/lib/administrate/field/belongs_to.rb b/lib/administrate/field/belongs_to.rb
index 95fdebbf0a30aabc5436f02d8080243e3cf1d7d1..a86762805855356ea0d7c28787b03b147fb4ac91 100644
--- a/lib/administrate/field/belongs_to.rb
+++ b/lib/administrate/field/belongs_to.rb
@@ -23,12 +23,15 @@ module Administrate
 
       def associated_resource_options
         candidate_resources.map do |resource|
-          [display_candidate_resource(resource), resource.send(primary_key)]
+          [
+            display_candidate_resource(resource),
+            resource.send(association_primary_key),
+          ]
         end
       end
 
       def selected_option
-        data && data.send(primary_key)
+        data&.send(association_primary_key)
       end
 
       def include_blank_option
diff --git a/lib/administrate/field/has_many.rb b/lib/administrate/field/has_many.rb
index 912cd3ce9f9bcde5b13363722e08f60d2db084bd..13ddebd9c7081b038a25fa3534b81346062f97ba 100644
--- a/lib/administrate/field/has_many.rb
+++ b/lib/administrate/field/has_many.rb
@@ -30,15 +30,18 @@ module Administrate
       end
 
       def associated_resource_options
-        candidate_resources.map do |resource|
-          [display_candidate_resource(resource), resource.send(primary_key)]
+        candidate_resources.map do |associated_resource|
+          [
+            display_candidate_resource(associated_resource),
+            associated_resource.send(association_primary_key),
+          ]
         end
       end
 
       def selected_options
         return if data.empty?
 
-        data.map { |object| object.send(primary_key) }
+        data.map { |object| object.send(association_primary_key) }
       end
 
       def limit
diff --git a/spec/features/edit_page_spec.rb b/spec/features/edit_page_spec.rb
index d988e11c852d68ef69b5be4317c202dd42ee30d1..9600d39424bae47ace6cd59c34a0ee71a6c42c9c 100644
--- a/spec/features/edit_page_spec.rb
+++ b/spec/features/edit_page_spec.rb
@@ -87,4 +87,15 @@ describe "customer edit page" do
     expect(page).to have_text(new_email)
     expect(page).to have_flash("Custom name was successfully updated.")
   end
+
+  it "handles complex associations" do
+    country = create(:country, code: "CO")
+    customer = create(:customer, territory: country)
+
+    visit edit_admin_customer_path(customer)
+    click_on "Update Customer"
+
+    customer.reload
+    expect(customer.territory).to eq(country)
+  end
 end
diff --git a/spec/lib/fields/belongs_to_spec.rb b/spec/lib/fields/belongs_to_spec.rb
index 9499e5b49a8e9125c245eaa5d7453e920e5147ae..14be213bbd7474e3b37a65cfbb1f0ef2af5f8310 100644
--- a/spec/lib/fields/belongs_to_spec.rb
+++ b/spec/lib/fields/belongs_to_spec.rb
@@ -196,7 +196,7 @@ describe Administrate::Field::BelongsTo do
       remove_constants :Foo, :FooDashboard
     end
 
-    it "determines what primary key is used on the relationship for the form" do
+    it "is the associated table key that matches our foreign key" do
       association =
         Administrate::Field::BelongsTo.with_options(
           primary_key: "uuid", class_name: "Foo",
diff --git a/spec/lib/fields/has_many_spec.rb b/spec/lib/fields/has_many_spec.rb
index 7fcdd056c45370bb5bd8bd1330d035b8b9774fde..027681f69cd48a9f83acdb2737bbc0d1b9e9594c 100644
--- a/spec/lib/fields/has_many_spec.rb
+++ b/spec/lib/fields/has_many_spec.rb
@@ -87,7 +87,7 @@ describe Administrate::Field::HasMany do
       remove_constants :Foo, :FooDashboard
     end
 
-    it "determines what primary key is used on the relationship for the form" do
+    it "is the key matching the associated foreign key" do
       association =
         Administrate::Field::HasMany.with_options(
           primary_key: "uuid", class_name: "Foo",
@@ -241,14 +241,42 @@ describe Administrate::Field::HasMany do
   end
 
   describe "#selected_options" do
-    it "returns a collection of primary keys" do
-      model = double("model", id: 123)
-      value = MockRelation.new([model])
+    it "returns a collection of keys to use for the association" do
+      associated_resource1 = double(
+        "AssociatedResource1",
+        associated_resource_key: "associated-1",
+      )
+      associated_resource2 = double(
+        "AssociatedResource2",
+        associated_resource_key: "associated-2",
+      )
+      attribute_value = MockRelation.new(
+        [
+          associated_resource1,
+          associated_resource2,
+        ],
+      )
+
+      primary_resource = double(
+        "Resource",
+        class: double(
+          "ResourceClass",
+          reflect_on_association: double(
+            "ResourceReflection",
+            association_primary_key: "associated_resource_key",
+          ),
+        ),
+      )
 
       association = Administrate::Field::HasMany
-      field = association.new(:customers, value, :show)
+      field = association.new(
+        :customers,
+        attribute_value,
+        :show,
+        resource: primary_resource,
+      )
 
-      expect(field.selected_options).to eq([123])
+      expect(field.selected_options).to eq(["associated-1", "associated-2"])
     end
 
     context "when there are no records" do