I'm having problems with AR trying to build associations of models that inherit from others. The problem is the associated models are being saved to the database before the call do the save method.
I found more information in this page http://techspry.com/ruby_and_rails/active-records-or-push-or-concat-method/
That's really weird, why would AR automatically save models appended to the association (with << method) ? One would obviously expect that the save method must called, even if the parent already exists. We can prevent this calling
@user.reviews.build(good_params)
but this would be a problem in a context where the association have an hierarchy, for example: if a Hunter has_many :animals, and Dog and Cat inherit from Animal, we can't do
@hunter.dogs.build
@hunter.cats.build
instead we are stuck with
@hunter.animals << Cat.new
@hunter.animals << Dog.new
and if the Cat/Dog class has no validations, the object will be saved automatically to the database. How can I prevent this behaviour ?
I found out that Rails 3 doesn't fully support associations with STI, and usually hacks are needed. Read more on this post http://simple10.com/rails-3-sti/. As mentioned in one of the comments, this issue is referred in rails 4 https://github.com/rails/rails/commit/89b5b31cc4f8407f648a2447665ef23f9024e8a5 Rails sux so bad handling inheritance = (( Hope Rails 4 fixes this.
Meanwhile I'm using this ugly workaround:
animal = @hunter.animals.build type: 'Dog'
then replace the built object, this step may be necessary for reflection to workout (check Lucy's answer and comments)
hunter.animals[@hunter.animals.index(animal)] = animal.becomes(Dog)
this will behave correctly in this context, since
hunter.animals[@hunter.animals.index(animal)].is_a? Dog
will return true and no database calls will be made with the assignment
Based on Gus's answer I implemented a similar solution:
# instantiate a dog object
dog = Dog.new(name: 'fido')
# get the attributes from the dog, add the class (per Gus's answer)
dog_attributes = dog.attributes.merge(type: 'Dog')
# build a new dog using the correct attributes, including the type
hunter.animals.build(dog_attributes)
Note that the original dog object is just thrown away. Depending on how many attributes you need to set it might be easier to do:
hunter.animals.build(type: 'Dog', name: 'Fido')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With