Ruby on Rails: вложенные атрибуты, отношение own_to

У меня есть объект «Пользователь», в котором есть поле «Текущее местоположение» (город и страна). Чтобы хранить эту информацию, я создал объект под названием Location, который имеет_много пользователей.

Я не совсем уверен, должен ли я указать модель пользователя «has_one» или «belongs_to», но для того, что я прочитал, если бы я хотел, чтобы у него был внешний ключ местоположения, я должен указать «belongs_to». Я также хочу иметь возможность редактировать текущее местоположение пользователя при редактировании файла User. поэтому я использую вложенные атрибуты. Но когда я редактирую пользователя, я каждый раз добавляю новое местоположение, даже не связывая его с пользователем, который был отредактирован. Можете ли вы мне помочь?

Мой код следующий:

#User Model
class User < ActiveRecord::Base
  ## Relationships
  belongs_to :current_location, :class_name => 'Location'
  accepts_nested_attributes_for :current_location
end

#Location Model
class Location < ActiveRecord::Base
  #Relationship
  has_many :users
end

# part of the _form_edit.haml
- form_edit.fields_for :current_location do |location_form|
  = location_form.label :location, "Current Location"
  = location_form.text_field :location

#Application Helper
#nested attributes for user and location
def setup_user(user)
  returning(user) do |u|
    u.build_current_location if u.current_location.nil?
  end
end

#in the user controller (added after edit)
def update
    @user = @current_user
    if @user.update_attributes(params[:user])
      flash[:notice] = "Account updated!"
      redirect_to account_url
    else
      render :action => :edit
    end
  end

person simaob    schedule 20.10.2009    source источник
comment
А в контроллере, который сохраняет данные, что у вас есть?   -  person Damien MATHIEU    schedule 20.10.2009
comment
У меня есть: def update @user = @current_user if @user.update_attributes(params[:user]) flash[:notice] = Аккаунт обновлен! redirect_to account_url else render :action =› :edit end end   -  person simaob    schedule 20.10.2009


Ответы (4)


Точная проблема, с которой вы столкнулись, как указывали другие, заключается в том, что ваш контроллер не получает идентификатор местоположения, как должен. Мне кажется, что идентификатор местоположения передается через неправильный параметр. К сожалению, идентификатор местоположения не существует в новой записи, поэтому это невозможно в форме.

Ваша проблема связана с использованием accepts_nested_attributes_for в отношениях принадлежности_к. Поведение четко не определено. Похоже, это задокументированная ошибка. Таким образом, accepts_nested_attributes_for должен иметь одну или несколько сторон отношения.

Вот несколько возможных решений:

  1. Переместите accept_nested_attributes_for в модель Location и создайте свои формы наоборот.

    -form_for @location do |location_form|
     ...
     =location_form.fields_for @user do |user_form|
       ....
    

    К сожалению, это не позволяет логически представить информацию. И затрудняет редактирование нужного пользователя.

  2. Используйте модель соединения и создайте отношение has one :through.

    Честно говоря, я не уверен, насколько хорошо accept_nested_attributes_for работает с отношением :through, но это определенно решит вашу проблему со связыванием записей.

  3. Игнорируйте accepts_nested_attributes_for и обрабатывайте ассоциацию в вашем контроллере по старинке.

    На самом деле сохраните файл accepts_nested_attributes_for. Он предоставляет несколько удобных методов, просто не позволяйте ему переходить к оператору update_attributes/create.

    def update 
      @user = @current_user 
      completed = false
      location_params = params[:user].delete(:current_location_attributes)
    
      User.transaction do
        @location = Location.find_or_create_by_id(location_params)
        @user.update_attributes(params[:user]) 
        @user.current_location = @location
        @user.save!
        completed = true
      end
      if completed
        flash[:notice] = "Account updated!" redirect_to account_url 
      else 
        render :action => :edit 
      end
    end
    

Поля для будут автоматически заполнять поле идентификатора в хэше current_location_attributes, если не создается новое местоположение. Однако для работы find_or_create_by_id требуется запись :id в хеше. Он будет создан с правильно автоматически увеличивающимся идентификатором, если идентификатор отсутствует в базе данных. Если вы создаете новое местоположение, вам нужно будет добавить его. Проще всего добавить его в форму с помощью =location_form.hidden_field :id, 0 unless current\_location.new\_record?.

Тем не менее, вы можете сократить создание дубликатов местоположений и изменить строку Location.find_or_create_by_id на Location.find_or_create_by_location. Это также уменьшит количество ошибок из-за неудачных проверок уникальности.

person EmFi    schedule 20.10.2009
comment
привет EmFI спасибо за ваш ответ. Я не знал, что это известная ошибка... но я действительно пытался найти решение и ничего не нашел. По поводу 3-х вариантов. 1- Это действительно не лучший вариант для этой цели.. =/ Потому что я хочу изменить местоположение пользователя. 2- Я не знаю, как это сделать, чтобы сказать правду... 3- Это казалось довольно хорошим, и я попробовал это, но это не работает. =/ Сначала было сказано, что удаления нет! функция, но после удаления ! в нем не было ошибок. Он просто не связывает местоположение с пользователем :s Он просто создает его и все... :s Есть идеи? :/ - person simaob; 20.10.2009
comment
Да, я предполагал, что на Hash было деструктивное удаление. Также в спешке я забыл добавить строку, которая фактически связывает пользователя с местоположением. Я исправил ошибку. - person EmFi; 20.10.2009
comment
Я не знаю, что не так, но, к сожалению, это не помогает :( извините :s - person simaob; 20.10.2009
comment
Вы получаете какие-либо ошибки. Какие именно параметры получает контроллер? - person EmFi; 20.10.2009
comment
как я вижу, он отправляет это: current_location_attributes=›{location=›Порто, Португалия}, не уверен, должен ли он иметь :id местоположения... как это сделать? Я не получаю никакой ошибки сейчас. - person simaob; 21.10.2009
comment
Кажется, я упустил еще одну деталь: мой код удалял location_attributes вместо current_location_attributes из хэша параметров. Обновлено для корректности. - person EmFi; 21.10.2009
comment
Привет, я думаю, что в скрытом поле должно быть @user.current_location.new_record? правильно? В противном случае я получаю сообщение об ошибке для несуществующей переменной или метода с именем current_location. Тем не менее, он по-прежнему не отправляет никакого идентификатора и не работает :( Я схожу с ума от этого. Если это не сработает, я просто передам current_location пользователю. :s - person simaob; 21.10.2009

Вы не предоставляете идентификатор вложенного атрибута. Итак, рельсы думают, что это новый.

- form_edit.fields_for :current_location do |location_form|
    = location_form.label :location, "Current Location"
    = location_form.text_field :location
    = location_form.hidden_field :id unless location_form.new_record?
person Damien MATHIEU    schedule 20.10.2009
comment
Всем привет. Спасибо за быстрый ответ. Я добавил это поле, но то же самое продолжает происходить. Он добавляет новое местоположение в таблицу. И это не делает ассоциацию для пользователя. Таким образом, show.haml никогда не показывает current_location :( он всегда пуст. - person simaob; 20.10.2009

Не уверен, что предыдущий ответ действительно правильный. Вам нужно указать идентификатор пользователя для местоположения, а не само местоположение.

- form_edit.fields_for :current_location do |location_form|
  = location_form.label :location, "Current Location"
  = location_form.text_field :location
  = location_form.hidden_field :user_id
person alex.zherdev    schedule 20.10.2009
comment
Привет, спасибо за ваш ответ. Когда я добавляю это скрытое поле, я получаю следующее сообщение об ошибке: undefined method `user_id' for #‹Location:0x54982c0›. Разве он не должен иметь это из-за отношения has_many :users? - person simaob; 20.10.2009
comment
ой, извините, что-то напутал в вашей модели домена. Вы должны попробовать form_edit.hidden_field :location_id - person alex.zherdev; 20.10.2009
comment
Привет еще раз. попробовал еще раз hidden_field с :location_id и :current_location_id ... но, тем не менее, он продолжает работать так же: s не работает должным образом =( - person simaob; 20.10.2009

По умолчанию belongs_to :current_location, :class_name => 'Location' будет ожидать, что в таблице Users будет поле current_location_id. Как только вы это сделаете, вы сможете сделать что-то вроде:

@user = @current_user
@user.update_attributes(params[:user])

@location = @user.current_location or @user.build_current_location
@location.update_attributes(params[:location]) 

@user.current_location.save!
@user.save!
person Jerry Fernholz    schedule 20.10.2009