I’m using Rails 6.1.3.2
and trying to make saving of nested attributes more efficient. The database used is MySQL.
Code basis
BoardSet.rb
class BoardSet < ApplicationRecord
has_many :boards, inverse_of: :board_set, foreign_key: 'board_set_id', dependent: :destroy
accepts_nested_attributes_for :boards
Board.rb
class Board < ApplicationRecord
has_many :cells, inverse_of: :board, foreign_key: 'board_id', dependent: :destroy
accepts_nested_attributes_for :cells
Cell.rb
class Cell < ApplicationRecord
belongs_to :board, inverse_of: :cells, foreign_key: 'board_id'
There is Grape::API endpoint like this:
params do
requires :name, type: String, desc: 'BoardSet name'
optional :boards, as: :boards_attributes, type: Array[JSON] do
# more params
optional :cells, as: :cells_attributes, type: Array[JSON] do
# more params
end
end
end
post do
board_set = BoardSet.new(declared(params, include_missing: false))
board_set.save!
end
Functionality
Summary of code basis: a BoardSet contains Boards which contain Cells. There is a Grape::API endpoint, where data can be passed to via PUT
, which is then saved within the database.
Problem
Saving with board_set.save!
is very inefficient. A BoardSet typically contains 40
Boards, where each contain maybe 20
Cells. So in total there are more than 40 * 20 = 800
SQL INSERT
statements (a single one for each Cell).
Solution attempts
I’ve tried these attempts for improvements:
- use the gem activerecord-import which seems to be able to do bulk inserts for such situations. However I think I would need something like
BoardSet.import [board_set], recursive: true
, butrecursive
is only supported for PostgeSQL, so not for MySQL, which I’m using. - first save
board_set
including boards with empty cells, then transferring the ID of the boards to the cells and save them in bulk. However I didn’t manage to remove the cells, I’ve tried to usecloned_board = board.dup
andcloned_board.cells = []
for saving empty copies, but it still saves all cells atcloned_board_set.save!
(wherecloned_board_set
contains cloned boards with emtpy cells). - I’ve tried to remove
accepts_nested_attributes_for :cells
fromBoard.rb
in order to preventboard_set.save!
from automatically saving all the cells. However this results in an exception inBoardSet.new(declared(params, include_missing: false))
since the parametercells_attributes
it not known.
3
Instead of saving all the objects with one line try these 3 steps:
- Save the board set with only its name i.e. without nested attributes.
- Save all boards without nested attributes
- Save all cells
After rails 6 we can do insert all
board_set = BoardSet.new(declared(params[:name], include_missing: false))
3