2017-02-08 3 views
0

У меня есть модель Книги и модель Авторы.рельсы: атрибуты вложенной формы, как избежать создания новых записей в базе данных?

Форма для добавления книг содержит вложенные возможности для добавления авторов. Это работает. Однако у меня есть функция автозаполнения в полях авторов, поэтому, когда форма отправляется в контроллер, автор (почти) наверняка существует в базе данных.

Я должен как-то сделать find_or_initialize_by на вложенных атрибутах.

Возможно, я смотрю на неправильное место, но я не могу найти это в направляющих. Я попробовал это (нашел на SO):

def create 

    @book = Book.new(params_book) 
    small_name = params[:book][:authors_attributes]["0"]["name"].downcase 
    aut_id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join 
    @book.authors = Author.find_or_initialize_by(id: aut_id) 

    if @book.save 
     redirect_to see_book_url(Book.last) 
    else 
     render 'new' 
    end 
end 

Это создает ошибку:

undefined method `each' for #<Author:0x007fac59c7e1a8> 

со ссылкой на линии @book.authors = Author.find_or_initialize_by(id: aut_id)

EDIT

После замечаний по этому вопросу , Я обновил код:

def create 

    book_params = params_book 
    small_name = params[:book][:authors_attributes]["0"]["name"].downcase 
    id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join 
    book_params["authors_attributes"]["0"]["id"] = id 

    @book = Book.new(book_params) 

    if @book.save 
      redirect_to see_book_url(Biblio.last) 
    else 
     .... 

В книге Params выглядеть следующим образом:

<ActionController::Parameters {"title"=>"Testus Testa", 
"authors_attributes"=><ActionController::Parameters { 
    "0"=><ActionController::Parameters {"name"=>"Vabien", "id"=>"22"} 
     permitted: true>} permitted: true>} permitted: true> 

Это выглядит хорошо для меня, но я получаю эту ошибку:

ActiveRecord::RecordNotFound in Administration::BooksController#create 
Couldn't find Author with ID=22 for Book with ID= 

ответ

0

решена, большое спасибо Хосе Кастелланоса и этот пост:

Adding existing has_many records to new record with accepts_nested_attributes_for

Код:

# the strong params isn't a Hash, so this is necessary 
# to manipulate data in params : 
book_params = params_book 

# All registrations in the DB are small case 
small_name = params[:book][:authors_attributes]["0"]["name"].downcase 

# the form sends the author's name, but I need to test against the id: 
id = Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join 
book_params["authors_attributes"]["0"]["name"] = params[:book][:authors_attributes]["0"]["name"].downcase 

# this author_ids is the line that I was missing! necessary to 
# test whether the author already exists and avoids adding a 
# new identical author to the DB. 
book_params["author_ids"] = id 
book_params["authors_attributes"]["0"]["id"] = id 

# the rest is pretty standard: 
@book = Book.new(book_params) 

if @book.save 
    redirect_to see_book_url(Book.last) 
else 
+0

Рад, что я мог бы помочь! Извините, я не знал, что у вас нет вложенных атрибутов, установленных в вашей модели, иначе я бы это сказал. Однако я все еще немного смущен вашим кодом. Кажется, вы только находите идентификатор первого автора и только первого автора. Кроме того, если у вас есть 2 автора с тем же именем, ваш код не будет работать, что, вероятно, является ошибкой, с которой вы сталкивались раньше. –

+0

Допустим, имя автора просто «bob», и у него есть идентификатор 1. Другой другой также известен как «bob» имеет идентификатор 2. Код 'id = Author.where (" \ "authors \". \ "name \" =: name ", {name: 'bob'}). pluck (: id) .join' возвращает '12', который не является идентификатором какого-либо боба или не может быть идентификатором другого. –

+1

Ознакомьтесь с моим последним ответом –

1

ИТАК самый простой способ получить то, что вы хотите, для изменения автозаполнения в форме из массива имен, таких как: ['author 1 name', 'author 2 name'], измените его на массив объектов, содержащий имя и идентификатор автора, например: [{label: 'author 1 name', value: 0}, {label: 'author 2 name', value: 1}], чтобы до тех пор, пока это поле формы для «id» вместо «name» «тогда в вашем контроллере все, что вам нужно сделать это:

def create 
    @book = Book.new(params_book) 
    if @book.save 
     redirect_to see_book_url(Book.last) 
    else 
     render 'new' 
    end 
end 

Потому что в качестве новых объектов будут созданы только атрибуты без идентификатора. Просто убедитесь, что вы установилиaccepts_nested_attributes_for :authorsв вашей книге.


ошибка вы получаете, потому что @book.authors это многие отношения так, что ожидает коллекция, когда вы устанавливаете его не индивидуальный автор. Чтобы добавить отдельного автора в коллекцию, вы делаете @book.authors << Author.find_or_initialize_by(id: aut_id) вместо @book.authors = Author.find_or_initialize_by(id: aut_id), хотя его избыточно, чтобы получить идентификатор, используя имя для инициализации с идентификатором. Идентификатор будет создан автоматически. Вместо этого используйте Author.find_or_initialize_by(name: small_name).

В вашем текущем коде несколько авторов созданы не только из-за отсутствия «id», но потому, что @book = Book.new(params_book) передает вложенные атрибуты инициализатору объекта, а затем после доступа к параметрам вложенных атрибутов и добавлению авторов еще раз. Кроме того, если у вас есть несколько авторов с тем же именем, то Author.where("\"authors\".\"name\" = :name",{name: small_name}).pluck(:id).join на самом деле сделает идентификатор из объединенного идентификатора всех авторов с этим именем.

Если вы хотите сделать это вручную, то удалить :authors_attributes с вашего разрешения в методе «params_book», поэтому он не будет передан Book.new затем выполните следующие действия:

def create 
    @book = Book.new(params_book) 
    params[:book][:author_attributes].each{|k,v| @book.authors << Author.find_or_initialize_by(name: v['name'])} 

    if @book.save 
     redirect_to see_book_url(Book.last) 
    else 
     render 'new' 
    end 
end 

Позвольте мне знать, если у вас есть проблемы !

После ответа от плаката

удалить :authors_attributes с вашего разрешения в методе "params_book" и попробовать это:

def create 
    @book = Book.new(params_book) 
    @book.authors_attributes = params[:book][:author_attributes].inject({}){|hash,(k,v)| hash[k] = Author.find_or_initialize_by(name: v['name']).attributes.merge(v) and hash} 

    if @book.save 
     redirect_to see_book_url(Book.last) 
    else 
     render 'new' 
    end 
end 
+0

Wow! Большое спасибо ! У меня есть небольшая проблема с этим. У меня был очень похожий метод в моем book_controller и причина, почему я этого не делал, потому что я не могу проверить параметры автора. Выполняя, как вы сказали, как я могу проверить, то есть отправить форму обратно, если ни один автор не выбран, т. Е. Генерировать сообщения об ошибках, которые связаны с другими отсутствующими полями, например заголовок не заполняется? – thiebo

+0

@thiebo предлагаемый способ, который я поставил наверху, должен проверять атрибуты автора. Если вы хотите сделать это так, как я показываю внизу, посмотрите, работает ли это. Вместо 'params [: book] [: author_attributes] .each {| k, v | @ book.authors << Author.find_or_initialize_by (name: v ['name'])} 'try '@book.authors_attributes = params [: book] [: author_attributes] .inject ({}) {| hash, (k, v) | hash [k] = Author.find_or_initialize_by (name: v ['name']). attributes.merge (v) и hash} ' –

+0

Я проверю это завтра. Большое спасибо за этот отличный ответ. Я опубликую, если проблемы (и если все в порядке;) – thiebo

Смежные вопросы