Skip to main content

Using find_or_create_by with accepts_nested_attributes_for

Rails • Asked by Nino Rosella

Hi gang...

I have a Book which has_many :authors, through: :book_authors

Instead of creating a new Author each time I create a book I'd like to use find_or_create_by on the author's name attribute. Really struggling to work out where this code should go. Using cocoon gem for the form.

Here's what I currently have that works.

class Book < ApplicationRecord

has_many :book_authors
has_many :authors, through: :book_authors
belongs_to :user

accepts_nested_attributes_for :authors, allow_destroy: true

class Author < ApplicationRecord
has_many :book_authors
has_many :books, through: :book_authors

class BookAuthor < ApplicationRecord
belongs_to :book
belongs_to :author

class BooksController < ApplicationController
before_action :authenticate_user!

def new
@book =

def create
@book = current_user.books.create(book_params)
@book.authors.each {|author| author.user_id =}

    redirect_to book_path(@book)
    render :new


def book_params
params.require(:book).permit(:title, authors_attributes: [:id, :name, :_destroy])

<%= simple_form_for @book do |f| %>
<%= f.input :title %>
<div id='authors'>
<%= f.simple_fields_for :authors do |author| %>
<%= render 'author_fields', :f => author %>
<% end %>
<div class='links'>
<%= link_to_add_association 'Add another author', f, :authors %>
<% end %>

<div class="nested-fields">
<%= f.input :name, label: "Author(s)", collection: @authors, value_method: :name, input_html: {value: @authors, class: 'new-author'} %>
<%= link_to_remove_association "Remove this author", f %>

Don't suppose anyone could lend a hand before this drives me insane..? :s

Hey Nino, 

I haven't tested this, but I believe this should work for you on your create method:

@book = current_user.books.create(book_params)

author = Author.find_or_create_by(name: "foo") do |author|
  # do stuff if author is new


find_or_create_by will either create and return a new object or return the found object. So once you have the author object, just create the @book.book_authors record directly.

Hi Jacob,

That's been a massive help, so thanks. I just have one small problem now.

When I save the Book and then do


I have two records saved. One is the correct record that was retrieved by the find_or_create_by method. The second record is new and I believe this is being created by the first line of code:

@book = current_user.books.create(book_params)

Is there a way to skip the saving of the new record?

Oh, duh I didn't pay attention to your book_params, it includes authors_attirbutes.

If this were my project, right or wrong, I'd make two sets of params, one for author and one for book. So something like:

def book_params

def author_params
  params.require(:book).permit(authors_attributes: [:id, :name, :_destroy])

This way you can create your book without the rails magic also creating the association when you create the book when passing book_params.

There could be a better way to handle this scenario that rails provides, but I haven't found it so I usually resort to this sort of setup to get the job done.

Jacob, you nailed it.

Thanks so much for your help!

Woohoo, glad that worked for you!

Good luck!

Login or Create An Account to join the conversation.

Subscribe to the newsletter

Join 20,000+ developers who get early access to new screencasts, articles, guides, updates, and more.

    By clicking this button, you agree to the GoRails Terms of Service and Privacy Policy.

    More of a social being? We're also on Twitter and YouTube.