rails nested attributes creating, but not updating and not deleting - ruby-on-rails

I have following models:
class Company < ActiveRecord::Base
has_and_belongs_to_many :people
has_many :companies_people
accepts_nested_attributes_for :companies_people, allow_destroy: true, reject_if: :all_blank
end
class CompaniesPerson < ActiveRecord::Base
belongs_to :company
belongs_to :person
belongs_to :company_role
end
class Person < ActiveRecord::Base
end
class CompanyRole < ActiveRecord::Base
end
and I'm trying along with Company object to update it's companies_people associated objects. The issue I'm facing is that I can create new companies_people objects but not update or remove existing ones. And what is the most thrilling is that it's not another question about not permitted or missing :id and :_destroy params - I have those set up for sure, but still can't nor update nor delete an existing association.
Eg. this call which has a purpose of updating company_role_id from 1 to 2 is being totally ignored:
Company.first.update_attributes(companies_people_attributes: [{id: 1, person_id: 1, company_role_id: 2}])
ps. tested with Rails 4.2.4

It appeared that it happens due to this association declaration:
has_and_belongs_to_many :people
I had this relation defined as HABTM initially, but later as it often happens I needed to get access to the join table and created a corresponding model CompaniesPerson, but didn't update :people association to work via has_many through. And now I discovered that changing the above statement to
has_many :people, through: :companies_people
or just completely commenting it out fixes the issue with nested attributes not updating and not deleting. Wow, pretty unexpected.

I have had this problem. The cause is rails doesn't know that your record is already exists.
You just need to add :ID in your parameters and it works.

Related

Rails has_many through across workspaces ignores class_name

I'm working with a simple has_many through relationship between 2 models.
The Achievement model describes an achievement that can be conquered by a Professional::Company, through the ConqueredAchievement table, that holds when it was conquered.
As I am working acrosss namespaces, I know I have to supply the full class name when creating the relationship, but it doesn't seem to have any effect, as it throws the exact same error with or without the value passed to class_name.
Here's the code for the models:
class Achievement < ApplicationRecord
has_many :conquered_achievements
end
class Professional::Company < ApplicationRecord
has_many :conquered_achievements
has_many :achievements, through: :conquered_achievements
end
class ConqueredAchievement < ApplicationRecord
belongs_to :achievement
belongs_to :professional_company, class_name: 'Professional::Company'
end
When I try to create an association, by using:
c.conquered_achievements.create!(achievement: a)
I get the error (same error with and without the class_name on the ConqueredAchievement model):
ActiveModel::UnknownAttributeError (unknown attribute 'company_id' for ConqueredAchievement.)
On the generated table, the row is actually named professional_company_id.
I've seen a lot of complaints about has_many through not working properly across namespaces. Am I doing something wrong, or is this actually a bug?
Try:
class Professional::Company < ApplicationRecord
has_many :conquered_achievements, foreign_key: :professional_company_id
has_many :achievements, through: :conquered_achievements
end
The clue is in the error:
ActiveModel::UnknownAttributeError (unknown attribute 'company_id' for ConqueredAchievement.)
The association is trying to use an attribute named company_id on ConqueredAchievement to find the Professional::Company. But, as you say, that attribute doesn't exist.
Instead of letting rails try to infer the foreign key, you can stipulate the foreign key using the foreign_key: option as described in the docs, section 4.3.2.6.

Error on updating nested attributes in rails

Three models:
class Order < ActiveRecord::Base
belongs_to :product
belongs_to :sale
end
class Sale < ActiveRecord::Base
has_many :orders
accepts_nested_attributes_for :orders, :reject_if => :all_blank
end
class Product < ActiveRecord::Base
belongs_to :greenhouse
has_many :orders
end
First a product is created. Then, an order can be made with one Product. Then, what I expect to be is, the Sale is filled with many Orders. But, when saving the Sale, it only ignores the Orders attached.
I've only found examples when the Sale creates the Order, or the parent object creates the Child object, but in this case, the child object, or the Order is already created, and only needs to be assigned or related with the new Sale.
How can I relate the child object with a new parent?
Just be sure that you have permit params at your SalesController something like this:
params.require(:sale).permit(:each, :sale, :field, :as, :symbol, :and, orders_attributes: [:each, :order, :field, :as, :symbol])
This is the most common issue which gives the described behavior. If not, we need some more information.

.build method not creating association in the join table

I have three Models setup with the following associations
class User < ActiveRecord::Base
has_many :faculties
has_many :schools, :through => :faculties
end
class School < ActiveRecord::Base
has_many :faculties
has_many :users, :through => :faculties
end
class Faculty < ActiveRecord::Base
belongs_to :user
belongs_to :school
end
and in my controller i go to create a school and assign the user
class SchoolsController < ApplicationController
def create
#school = current_user.schools.build(params[:school])
...
end
end
When I login and submit the form the flash displays success, but the association doesn't build on the join table.
I tried it inside the apps console and it builds the association just fine.
I've been stuck on this for a couple days now and I just cannot figure out what I am missing. Thank in advance for any and all advice
The build method does not save the object. You need to explicitly call #school.save.
Two things: If the schools association is :through a has_many association, you will have to select which parent the School exists through.
So, for instance, if you were to nest School resources under users as in /users/:id/faculties/:id you could create a school via current_user.faculties.find(params[:faculty_id]).schools.build(params[:school]).save
Based on the example code, it looks like the fundamental problem is that the has_many xxx, :through syntax is being used without specifying the id of the faculties record. Remember two things: 1) ActiveRecord doesn't natively support composite primary keys, and 2) you must call #save on associated records created using #build. If you remember these, you should be fine.

counter_cache not decrementing for has_many associations in ActiveReord

My Rails 3 app has 2 models and a third that's join table between them and their has_many relationships. Basically, User and Show are joined by SavedShow, allowing users to save a list of shows:
class Show < ActiveRecord::Base
has_many :saved_shows
has_many :users, :through => :saved_shows
end
class User < ActiveRecord::Base
has_many :saved_shows
has_many :shows, :through => :saved_shows
end
class SavedShow < ActiveRecord::Base
belongs_to :user, :counter_cache => :saved_shows_count
belongs_to :show
end
I've noticed that the counter_cache field (shows_saved_count) gets incremented automatically just fine, but not decremented. The core of the issue seems to be that removing shows from a user's list is done via delete, which does not trigger updating of the counter_cache:
current_user.shows.delete(#show)
However, I can't call the destroy method here, since that not only deleted the User/Show association in SavedShow, but also the Show object itself, which is not what I want.
Is a counter_cache in this kind of scenario not an appropriate use?
There appears to be a discussion about this as a bug back in 2009, and fixes were discussed, but I'm still seeing the issue in the latest Rails 3.0.
I would just write my own custom handling in the model, but there seems to be no after_delete callback that I can hook into (presumably this is the reason decrementing doesn't work in the first place). Right now, there's only one place in my own code where a delete of the association could occur, so I'll just manually make a call to update the counter, but this seems like like such a fundamental shortcoming or bug of ActiceRecord associations with counter_cache, that I'm wondering if I'm not just missing something.
If this is indeed a genuine problem with counter_caches, what would be the best workaround?
Faced a related issue in Rails 5 (with self referential counter cache through a join table) and fixed it as below:
class User < ActiveRecord::Base
has_many :saved_shows, :counter_cache => :saved_shows_count
has_many :shows, :through => :saved_shows
end
https://guides.rubyonrails.org/association_basics.html#options-for-has-many-counter-cache
[RAILS 6]
On a standard has_many through relation :
class Parent < ApplicationRecord
has_many :joins,
foreign_key: :parent_id,
dependent: :destroy,
counter_cache: :joins_count
has_many :children, through: :joins, source: 'child'
...
class Join < ApplicationRecord
belongs_to :parent, counter_cache: :joins_count
belongs_to :child
end
The counter cache has to be specified on both sides, otherwise it won't we decremented on relation deletion
Same issues here but on Rails 2.3.
Worth noticing that also adding a touch, like:
belongs_to :user, :counter_cache => :saved_shows_count, :touch => true
Won't update counter cache nor the related updated_at field on association.delete(object).
To workaround the issue usually we manipulate the join model, but that also have some drawbacks.
Patch is here: https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/2824-patch-has_many-through-doesnt-update-counter_cache-on-join-model-correctly#ticket-2824-18

Cascade delete in Ruby ActiveRecord models?

I was following the screencast on rubyonrails.org (creating the blog).
I have following models:
comment.rb
class Comment < ActiveRecord::Base
belongs_to :post
validates_presence_of :body # I added this
end
post.rb
class Post < ActiveRecord::Base
validates_presence_of :body, :title
has_many :comments
end
Relations between models work fine, except for one thing - when I delete a post record, I'd expect RoR to delete all related comment records. I understand that ActiveRecords is database independent, so there's no built-in way to create foreign key, relations, ON DELETE, ON UPDATE statements. So, is there any way to accomplish this (maybe RoR itself could take care of deleting related comments? )?
Yes. On a Rails' model association you can specify the :dependent option, which can take one of the following three forms:
:destroy/:destroy_all The associated objects are destroyed alongside this object by calling their destroy method
:delete/:delete_all All associated objects are destroyed immediately without calling their :destroy method
:nullify All associated objects' foreign keys are set to NULL without calling their save callbacks
Note that the :dependent option is ignored if you have a :has_many X, :through => Y association set up.
So for your example you might choose to have a post delete all its associated comments when the post itself is deleted, without calling each comment's destroy method. That would look like this:
class Post < ActiveRecord::Base
validates_presence_of :body, :title
has_many :comments, :dependent => :delete_all
end
Update for Rails 4:
In Rails 4, you should use :destroy instead of :destroy_all.
If you use :destroy_all, you'll get the exception:
The :dependent option must be one of [:destroy, :delete_all, :nullify,
:restrict_with_error, :restrict_with_exception]

Resources