EpixRu
EpixRu

Reputation: 211

Dynamically generate values and create multiple records based on user input with Ruby on Rails

In my Ruby on Rails application I am trying to dynamically create n number of database records based on a number that the user enters on a form. To keep things simple,I will use the analogy of a book and its chapters, to illustrate what I am trying to do. Below is my model, attributes, and form to create a book.

Models:

class Book < ActiveRecord::Base
  has_many :chapters
end

class Chapter < ActiveRecord::Base
  belongs_to :book
end

Book Attributes:
id, book_title, number_of_chapters , created_at, updated_at

Chapter Attributes:
id, chapter_title, contents, book_id, created_at, updated_at

Form to create new Book:

<%= form_for(@book) do |f| %>
    <%= f.text_field :book_title %>
    <%= f.text_field :number_of_chapters %>

    <%= f.submit %>
<% end %> 

When a user creates a book, they can enter in the book's title and the number of chapters the book has. If the user enters in 5 chapters, we then need to be able to dynamically insert 5 records into the Chapter table each with a unique generated chapter title name.

The generated chapter titles could be as simple as:

 chapter1   
 chapter2   
 chapter3  
 chapter4  
 chapter5  

The closest solution I have been able to find in my research is to create a nested model form. But a nested model form is not the ideal solution, for a couple of reasons. I cannot predict the number of records that will need to be inserted into the second model, it could be a few or as high as 10,000 records. Plus manually data entering field values for that many records is not practical.

I have a few bits and pieces of what I think needs to be done, but I need help tying it all together.

My first thought is to use an after_save callback to trigger a method that will create the chapters. I would add the following to the Book model.

after_save Chapter.generate_chapter_titles

But I am questioning if after_save is the best way to do this, from a performance standpoint, since we could potentially be creating several thousand records at a time.

On the Chapter model I was thinking of using the following method to create the chapter_titles values based on the number_of_chapters.

def generate_chapter_titles
    1.upto(book.number_of_chapters) do |chapter|
    print "chapter%d" % chapter
end 

But now I am stuck, I haven't quite figured how to take the output from generate_chapter_titles and create the individual Chapter db records. While making sure that the newly generated chapters are associated with the book it was created from.

Any help on getting this functionality working is greatly appreciated. I have tried to research this before posting here, but answer still eludes me. I am a programming and Ruby noob, so I am willing to admit that some of the information on the Ruby and Ruby on Rails API documentation goes over my head. I would greatly appreciate any help on breaking down and understanding the added functionally that I need to make this work.

Update - Final Solution Thanks to @margo, all I had to do was update the create method in the books_controller.rb with the code below. Now the chapters are generated automatically based on the value entered in for number_of_chapters.

class BooksController < ApplicationController

  [...]

  def create
    @book = Book.new(book_params)

    if @book.save
      ActiveRecord::Base.transaction do
        @book.number_of_chapters.times do |n|
          Chapter.create!(book_id: @book.id, title: "Chapter #{n+1}")
        end
      end
      redirect_to @book, notice: "Book created successfully."
    else
      render 'new'
    end
  end

  [...]

end

Upvotes: 0

Views: 1211

Answers (1)

margo
margo

Reputation: 2927

First of all, you will want to wrap the creation of the book's chapters in a transaction. Transactions will ensure that all the chapters get saved or rolls back any commits if there is an error before it finishes.

You can create all the chapters in the create method in the books_controller. Below is an example, you'll have to fill in the pieces.

In your books_controller

def create
  @book = Book.new(book_params)
  if @book.save 
    generate_chapters(@book)
  end
end

private
def generate_chapters(book)
  ActiveRecord::Base.transaction do
    book.number_of_chapters.times do |n|
      Chapter.create!(book_id: book.id, title: "Chapter #{n+1})"
    end
  end
end

I would suggest that you start with this approach and get it working. If you're going to be creating thousands of records at once, then you might want to consider moving this to a background job, but that's a separate question.

Upvotes: 0

Related Questions