Rails 4 - Polymorphic associations

2019-08-12 13:57发布

I am trying to make an app in Rails 4.

I have a profile model and an address model.

the Associations are:

profile.rb

has_many :addresses, as: :addressable

address.rb

 belongs_to :addressable, :polymorphic => true

Address is a polymorphic model.

I have an address form which has:

<%= simple_form_for(@address) do |f| %>
            <%= f.error_notification %>

            <div class="form-inputs">
                <div class="row">
                    <div class="col-xs-3">
                        <%= f.input :unit %>
                    </div>
                    <div class="col-xs-3 col-xs-offset-1">
                        <%= f.input :street_number %>
                    </div>
                    <div class="col-xs-3 col-xs-offset-1">
                        <%= f.input :street %>
                    </div>
                </div>

                <div class="row">
                    <div class="col-xs-3">
                        <%= f.input :building %>
                    </div>
                    <div class="col-xs-3 col-xs-offset-1">
                        <%= f.input :city %>
                    </div>
                    <div class="col-xs-3 col-xs-offset-1">
                        <%= f.input :region %>
                    </div>

                </div>

                <div class="row">
                    <div class="col-xs-3">
                        <%= f.input :zip %>
                    </div>
                    <div class="col-xs-3 col-xs-offset-1">
                        <%= f.input :country, priority: [ "Australia", "New Zealand", "United Kingdom" ] %>
                    </div>


                </div>

                <div class="row">
                    <div class="col-xs-3">
                        <%= f.input :main_address %>
                    </div>
                    <div class="col-xs-3 col-xs-offset-1">
                        <%= f.input :project_offsite %>
                    </div>


                </div>



            </div>

            <div class="form-actions">
                <%= f.button :submit, :class => "formsubmit" %>
            </div>
            <% end %>

I try to create an address and save it. I then check that it is in the rails console. Something is going wrong because the rails console shows this:

#<Address id: 1, unit: "", building: "", street_number: "34", street: "f", city: "sdfasdf", region: "asdf", zip: "2000", country: "GB", main_address: nil, project_offsite: nil, time_zone: nil, latitude: nil, longitude: nil, addressable_id: nil, addressable_type: nil, created_at: "2015-12-30 00:07:00", updated_at: "2015-12-30 00:17:00"> 

The addressable id and the addressable type are both nil.

They should have the references back to profile or wherever the related model is.

Is there a step missing in setting up this process?

This video (minute 3.40) says rails automatically handles filling out the type and id fields for polymorphic models. https://gorails.com/episodes/comments-with-polymorphic-associations?autoplay=1

When I try to create a new address from the rails console, I type:

Address.create(addressable: Address.first, profile_id: "1", country: "AU")

The console error is:

ActiveRecord::UnknownAttributeError: unknown attribute 'profile_id' for Address

My address table has an addressable_id key and my profile model has has_many :addresses, as: :addressable

I can't understand what causes the error

ANOTHER ATTEMPT

So, from what I gather from the suggestions below, I need to do something to let address know that it belongs to a profile.

I also found this article which sets out an example of what needs to happen in the profiles controller to get things working with polymorphic associations: http://6ftdan.com/allyourdev/2015/02/10/rails-polymorphic-models/

I change my new action in the profiles controller to (include addresses.build):

 def new
    @profile = Profile.new
    @profile.qualifications.build
    @profile.visions.build
    @profile.personalities.build
    @profile.addresses.build

    authorize @profile
  end

I add nested attributes to my address association in the profile model:

has_many :addresses, as: :addressable
    accepts_nested_attributes_for :addresses,  reject_if: :all_blank, allow_destroy: true

I change my address form: - from form_for to fields_for; - rename it _address_fields.html.erb; and - open the div tag with:

<div class="nested-fields">

In my profiles form, I add the link to the form partial:

              Your addresss
            </div>
            <%= f.simple_fields_for :addresses do |f| %>

              <%= render 'addresses/address_fields', f: f %>  
            <% end %>
            </div>
          <div class="row">
            <div class="col-md-6">

               <%= link_to_add_association 'Add an address', f, :addresses, partial: 'addresses/address_fields' %>

            </div>

All of this is as set out in Jeiwan's answer on this post: Rails - Cocoon gem - Nested Forms

Then, when I try all of this, I get this error:

ActiveRecord::RecordNotUnique in ProfilesController#update
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_addresses_on_addressable_type_and_addressable_id" DETAIL: Key (addressable_type, addressable_id)=(0, 1) already exists. : INSERT INTO "addresses" ("unit", "building", "street_number", "street", "city", "region", "zip", "country", "addressable_id", "addressable_type", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING "id"

The error points to the update method in the profiles controller:

 def update
    respond_to do |format|
      if @profile.update(profile_params)
        format.html { redirect_to @profile }
        format.json { render :show, status: :ok, location: @profile }
      else

I found this post which describes a user's similar problem, but before i change the database, I'd like to try and understand what has happened.

Rails auto-assigning id that already exists

Another odd thing is when I look at the last address created in the console, it shows this:

#<Address id: 6, unit: "", building: "", street_number: "34", street: "asdf", city: "asdf", region: "add", zip: "2111", country: "AU", main_address: nil, project_offsite: nil, time_zone: nil, latitude: nil, longitude: nil, addressable_id: 1, addressable_type: 0, created_at: "2016-01-01 22:01:31", updated_at: "2016-01-01 22:01:31">]> 

The record now has an addressable_id but does not have an addressable_type. Is there something extra required to populate this field?

2条回答
聊天终结者
2楼-- · 2019-08-12 14:26

You're getting confused with polymorphic associations:

With polymorphic associations, a model can belong to more than one other model, on a single association

This means that ActiveRecord will automatically assign the addressable_type & addressableid of the Address model, depending on which associated model created it.

For example...

#app/models/profile.rb
class Profile < ActiveRecord::Base
   has_many :addresses, as: :addressable
end

#app/models/address.rb
class Address < ActiveRecord::Base
   belongs_to :addressable, polymorphic: true
end

The above means that each time you create a profile, you have the ability to create or assign addresses for it. If you had another model (users), you could do the same. The addressable_id & addressable_type will be assigned automatically on the address record:

#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
   def create
       @profile = Profile.new profile_params
       @profile.save
   end

   private

   def profile_params
      params.require(:profile).permit(...)
   end
end

--

The main problem you have is that you're getting confused between which model has the polymorphic element of the association.

They should have the references back to profile or wherever the related model is.

@profile = Profile.find x
@address = @profile.addresses.new address_params ...

This will assign addressable_id and addressable_type for the profile model, allowing you to create addresses on behalf of the profile.

@address = current_user.addresses.new address_params

This exact same code would allow you to create an address for a user (same model, same associations etc).


To answer your question, you need to scope your object creation around the associated model:

@address = Address.create(country: "AU")

@profile = Profile.find 1
@profile.addresses << @address

You could also do:

@profile = Profile.find 1
@profile.addresses.create country: "AU"

--

If you wanted addresses to be "dependent" on others, you'd need to add a parent_id column, and use acts_as_tree or another hierarchy gem.

查看更多
你好瞎i
3楼-- · 2019-08-12 14:40

This form does not have addressable id/type fields so unless you explicitly set them or related object in controller getting nil is what is expected.

Defining the relation in model only states that objects can be related in this way, but does not set actual values. Actually - rails has a lot of magic in it, but it cannot guess which profile is the owner of which address

查看更多
登录 后发表回答