Reputation: 89
In my Rails 4.2.5 application I have the following two models (Spell, Category) which have a many-to-many relation with association model (Spellcategory):
class Spell < ActiveRecord::Base
has_many :spellcategories, class_name: "Spellcategory", foreign_key: "spell_id", dependent: :destroy
has_many :categories, through: :spellcategories, source: :category
end
class Category < ActiveRecord::Base
has_many :spellcategories, class_name: "Spellcategory", foreign_key: "category_id", dependent: :destroy
has_many :spells, through: :spellcategories, source: :spell
end
class Spellcategory < ActiveRecord::Base
belongs_to :spell, class_name: "Spell"
belongs_to :category, class_name: "Category"
end
I have already an existing list of categories and want to import a list of spells. Because manual input is possible in the import process I want to first create everything, display it, and then save everything (spells + links to the categories) altogether. I store the objects in the activerecord-session_store from the first to the second step.
I load the object with the following code from a XML file in the first step (a little bit pseudo-code because I don't want to bore you with all the XML parsing):
spells = xml_doc.path(...).map do |xml_spell|
new_spell = @user.spells.build
xml_spell.path('cats/cat').each do |node|
new_spell.categories << Category.find_by(name: node.text)
end
new_spell
end
and save all the spells in the second step.
if spells.map(&:valid?).all?
spells.each(&:save!)
end
The last lines fail because validation fails with the error Spellcategories is invalid.
My guess is it has something to do with the unsaved child elements from the association table. I tried to use :autosave
but it didn't solve the problem.
What am I doing wrong which prevents me from saving all in one go?
Upvotes: 1
Views: 673
Reputation: 1759
If you don't need access to Spellcategory
model per se, you can use has_and_belongs_to_many
instead of two has_many
and it will simplify things quite a bit.
If you do need it, the reason for the problem was outlined above in comments to the question -- you don't have an ID for Spell
you are creating to be able to save relations with it. Here's what I would try -- two options:
Option 1. If you have just a few records:
Spell.transaction do
spells = xml_doc.path(...).map do |xml_spell|
new_spell = @user.spells.create
xml_spell.path('cats/cat').each do |node|
new_spell.categories << Category.find_by(name: node.text)
end
new_spell
end
end
Wrapping it into transaction guarantees the consistency. Replacing "build" with "create" initializes the Spell record before adding categories.
Option 2. If you have many records. I used this method for importing millions of records. Keep the join table class and then use bulk import plugin (like https://github.com/zdennis/activerecord-import) to load data.
Upvotes: 1