Полиморфная ассоциация Rails 4 RC1 создает новый адрес, а не обновляет существующий адрес при обновлении

Это продолжение другого полиморфного вопроса, который я задал пару дней назад. Я создаю полиморфную ассоциацию для адреса. В этом случае я просто хотел посмотреть, будет ли это работать в простой модели, поэтому я добавил «адрес» к «статье» в существующем тестовом блоге, который я сделал. Моя проблема в том, что теперь я могу создать адрес с новой «статьей» (зная, что статья будет бизнесом, пользователем, клиентом и т. д. в реальном приложении) и видеть ее, когда я иду редактировать этот бизнес. Но если я редактирую адрес, addressable_id для существующего адреса устанавливается равным нулю, и создается новый адрес, оставляя старый и обновляя addressable_id для нового. Я не могу представить, что это правильное поведение, хотя, возможно, я каким-то образом делаю это с собой.

Вот код.

Статья Модель

class Article < ActiveRecord::Base
  has_one :address, as: :addressable
  accepts_nested_attributes_for :address
end

Модель адреса

class Address < ActiveRecord::Base
  belongs_to :addressable, polymorphic: true
end

Контроллер статей

class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]


  # GET /articles
  # GET /articles.json
  def index
    @articles = Article.all
  end

  # GET /articles/1
  # GET /articles/1.json
  def show
  end

  # GET /articles/new
  def new
    @article = Article.new
    @address = @article.build_address(params[:address])
  end

  # GET /articles/1/edit
  def edit
    @address = @article.address ||= @article.build_address(params[:address])  
  end

  # POST /articles
  # POST /articles.json
  def create
    @article = Article.new(article_params)

    respond_to do |format|
      if @article.save
        format.html { redirect_to @article, notice: 'Article was successfully created.' }
        format.json { render action: 'show', status: :created, location: @article }
      else
        format.html { render action: 'new' }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /articles/1
  # PATCH/PUT /articles/1.json
  def update
    @address = 
    respond_to do |format|
      if @article.update(article_params)
        format.html { redirect_to @article, notice: 'Article was successfully updated.' }
        format.json { head :no_content }
      else
        format.html { render action: 'edit' }
        format.json { render json: @article.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /articles/1
  # DELETE /articles/1.json
  def destroy
    @article.destroy
    respond_to do |format|
      format.html { redirect_to articles_url }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_article
      @article = Article.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def article_params
      params.require(:article).permit(:name, :content, :address_attributes => [:line1, :line2, :city, :state, :zip])
    end
end

Мой файл миграции БД

class CreateAddresses < ActiveRecord::Migration
  def change
    create_table :addresses do |t|
      t.string :line1
      t.string :line2
      t.string :city
      t.string :state, limit: 2
      t.integer :zip, limit: 5
      t.references :addressable, polymorphic: true

      t.timestamps
    end
     add_index :addresses, [:addressable_type, :addressable_id], unique: true
  end
end

Вид стандартный

<%= f.fields_for :address do |address| %>
  Fields n stuff.....
<% end %>

Как я уже упоминал, на этом уровне все работает. У меня все еще есть проблема с моим первоначальным вопросом по этому поводу, и я предполагаю, что это как-то связано с вложением, поэтому я поработаю над этим. Я просто хочу убедиться, что знаю, что это правильно, прежде чем добавлять это.

Когда вы редактируете существующий адрес, привязанный к «статье» — в данном случае — должен ли он оставить старый адрес и создать новый или обновить существующий? Есть что-то простое, что мне не хватает?


person Art    schedule 16.05.2013    source источник
comment
Я, возможно, заставил это работать. Я добавил :id к: def article_params params.require(:article).permit(:name, :content, :address_attributes => [:line1, :line2, :city, :state, :zip]) end В методе обновления контроллера я поставил @article.address.update(params[:address]). Теперь при обновлении обновляю саму запись. Если я самостоятельно удалю связанный адрес, метод обновления не сможет создать его в будущем, так как я получаю ошибку отсутствия метода при обновлении, когда @article.address содержит nil. Хотя работа, я надеюсь, что есть что-то, что я мог бы улучшить.   -  person Art    schedule 17.05.2013


Ответы (1)


У меня в основном все работает.

Не уверен, что это правильно, но, похоже, это работает. Это долго, но:

Что-то подсказывает мне, что я в основном идиот из-за того, что мне так трудно заставить полиморфные ассоциации работать в приложении Rails 4.0 RC1, над которым я работаю. Будучи идиотом в стороне, я ДЕЙСТВИТЕЛЬНО попросил Stack Overflow помочь с проблемами, с которыми я столкнулся, и перепостил вопрос Ruby on Rails в Google Group. Ответов не последовало, поэтому этот вопрос стал первым, в котором я эффективно разобрался самостоятельно.

Так это хорошо, верно?

Во всяком случае, моя проблема была проста. Я хочу, чтобы уличные/почтовые/платежные адреса были общими для предприятий, клиентов, владельцев и т. д. Я не хочу кодировать три разные таблицы, выполняющие одно и то же. Полиморфная ассоциация кажется единственным выходом из того, что я понял из таких ассоциаций. Я ковыряюсь в поисках руководства по этому поводу, и у меня есть кое-что для моделирования.

http://kconrails.com/2010/10/19/common-addresses-using-polymorphism-and-nested-attributes-in-rails/> Этот сайт был полезен, хотя и устарел. Я использую Rails 4. Я думаю, это было написано в конце Rails 2. Это помогло мне сократить свое время, потому что я никогда раньше не делал ассоциацию has_one, и моя первоначальная проблема была в консоли Rails, я не мог сделать что-то вроде:

b = Owner.first.businesses.first
b.address.create!(params n' stuff)

Я продолжал получать ошибку no_method для создания. Именно тогда я действительно сосредоточился на статье выше и понял, что он тоже не использует create. Он использовал:

b = Owner.first.businesses.first
b.build_address(params n' stuff) -- he actually wrote @customer.build_address

build_association — это has_one. Я знаю это теперь очень хорошо. Конечно, я мог бы прочитать документы заранее, но я прочитал их во время этого. Вы можете подробно прочитать об этом по адресу http://guides.rubyonrails.org/association_basics.html#has_one-association-reference, если вам интересно.

Я внимательно следую этому документу, но он не совсем правильный. Элемент «build_address» в новом методе моего бизнес-контроллера DID позволяет отображать fields_for в моей форме. Но в базу данных ничего не записывалось. Пустая запись вставлялась для адреса со всеми нулевыми значениями и вообще не привязывалась к бизнесу, для которого она была добавлена. И, очевидно, если бы бизнес действительно «редактировался», а не создавался новый, вы бы вообще не увидели форму адреса, поэтому я решил, что нам понадобится этот метод build_address, по крайней мере, в поле редактирования.

Я был прав в этом, но все равно ничего не спасло.

Как всегда, я иду на Railscasts, чтобы посмотреть, что есть у Райана. И, http://http://railscasts.com/episodes/154-polymorphic-association-revised>у него есть кое-что полиморфное, что будет здорово, когда я добавлю комментарии для разных моделей. Но это не было напрямую полезно для того, что я хотел сделать здесь. Я много читал о переполнении стека. Проверил список проблем в репозитории Rails на Github. Читайте группу Rails в Google. Ничто действительно не изложило то, что я видел.

Во многом потому, что я видел проблему с Rails 4.

Строгие параметры встроены в Rails 4. Поэтому я добавил следующее в адресный контроллер, чтобы учесть это:

# Never trust parameters from the scary internet, only allow the white list through.
def address_params
  params.require(:address).permit(:line1, :line2, :city, :state, :zip)
end

Это то, что скаффолд естественно сделал бы для вас в Rails 4, если бы вы использовали для этого скаффолд rails g. Сильные параметры заменяют attr_accessible в модели из предыдущих версий Rails. Таким образом, модель — это просто ссылка на полиморфную ассоциацию для адреса и адресата, а бизнес добавил has_one :address, as: addressable, а также accepts_nested_attributes_for :address. Это все хорошо, но все равно ничего не сохраняется. Я даже добавил хеш address_params в appliction_controller.rb.

Я понимаю, что сейчас я веду себя глупо. Добавление адреса происходит в бизнес-контроллере и НИКОГДА не увидит этот хэш параметров. Даже в application_controller.rb ничего не изменилось. Так что этого не могло быть.

Я вижу параметры, которые передаются в журнале, и попытка вставки, но неразрешенная ссылка параметров на «адрес» продолжает отображаться.

Я прошу помощи в этот момент и пытаюсь настроить его.

Я вижу https://github.com/rails/strong_parameters#nested-parameters> этот раздел на github в репозитории сильных параметров. Я не понимаю, почему accepts_nested_attributes_for не сделал бы это для меня, но я захожу в бизнес-контроллер и настраиваю там хеш параметров, чтобы он выглядел так:

# Never trust parameters from the scary internet, only allow the white list through.
def business_params
  params.require(:business).permit(:name, :description, :address_attributes => [:line1, :line2, :city, :state, :zip])
end

В одной из статей, которые я читал, говорилось, что ассоциация has_one передает :address_attributes, поэтому я поместил это туда. Теперь все по-другому. Данные по-прежнему не сохраняются, но я не получаю неразрешенных ошибок. И до сих пор нет ответов на сайтах помощи.

Я решаю настроить свои новые, отредактировать, создать и обновить методы.

В новом и редактировании у меня было:

@business.build_address

Я изменил это в новом и отредактировал и добавил для создания и обновления следующее:

@business.build_address(params[:address]) -- NOTE, :address_attributes also appears to work there.

Теперь данные сохранялись. Все поля входили в БД. Проблема в том, что ДВА были. Один был пуст, а другой был заполнен при каждом новом и каждом обновлении. Итак, я удаляю приведенный выше код из методов создания и обновления в бизнес-контроллере. Теперь у меня создан ОДИН адрес. ПРАВИЛЬНО. Чувствуя себя хорошо, я открываю бизнес, чтобы изменить адрес, и получаю пустую форму адреса. Ничто не передается обратно в представление формы.

Итак, я изменяю свой метод редактирования в бизнес-контроллере, чтобы он выглядел так:

@business.address ||= @business.build_address(params[:address])

В основном это говорит о том, что если есть @business.address, покажите его, иначе @business.address будет создан.

Сейчас катаюсь. Теперь я вижу сохраненный адрес, когда редактирую форму. Я редактирую адрес. Редактирование сохраняется и отображается снова. Но связанный с этим СТАРЫЙ адрес остается. Addressable_id просто изменяется на nil и записывается новая адресная запись. ЭТО не то, чего я хочу. По-прежнему никакой помощи от справочных сайтов. В логе вообще ничего.

Я думаю, что здесь следует использовать метод обновления. Поэтому я добавляю следующую строку в метод обновления бизнес-контроллера:

@business.address.update (параметры [: адрес])

Никаких изменений в поведении, но теперь журнал показывает новый неразрешенный параметр.

Он показывает «ID» как неразрешенный. Итак, я меняю хеш параметров бизнес-контроллера на:

# Never trust parameters from the scary internet, only allow the white list through.
def address_params
  params.require(:address).permit(:id, :line1, :line2, :city, :state, :zip)
end

И на этом все работает. Я могу создать бизнес с адресом. На самом деле, даже если я ничего не заполняю для адреса, адрес создается (без проверки на этом этапе). Я могу обновить этот адрес, полный или пустой, и он обновит существующую запись. Оно работает. Я не уверен, что это лучший или самый чистый способ, но я буду продолжать искать.

Одна проблема заключается в том, что если я вручную удалю созданный адрес, связанный с бизнесом, а ЗАТЕМ попытаюсь отредактировать этот бизнес, метод обновления, который я добавил выше, выдаст ошибку, поскольку нет записи для обновления. Я, наверное, могу жить без этого. Я ДУМАЮ, что могу создать запись для каждого бизнеса (и, в конечном итоге, пользователя, владельца, клиента и т. д.), даже если она пуста, а затем разрешить удаление только через ассоциацию, а не напрямую.

Но я могу изменить код, чтобы найти способ ТОЛЬКО создать полный адрес, а не писать его пустым.

В любом случае, сейчас все работает.

Я хотел бы услышать, если есть что-то, что я могу улучшить или изменить.

А пока я перехожу к следующему.

person Art    schedule 17.05.2013