Model creation failing with has_many through validation - ruby-on-rails

I have 2 models, order and owner, which are joined together by an order_owner model using a has_many through association. Each order must have at least 1 owner associated to it but since each order or owner can have many of the other, a many-to-many relationship is required.
I've set up my order model with validates :owners, presence: true which works great if I attempt to create an order without associating any owners to it. However, the issue I'm encountering is that the validation still fails regardless of associating an owner at the time of creation via nested attributes and I don't know how to fix it.
Models - Rails 5.0.2
##order.rb
has_many :order_owners, inverse_of: :order, dependent: :destroy
has_many :owners, through: :order_owners
accepts_nested_attributes_for :owners, reject_if: :all_blank, allow_destroy: true
accepts_nested_attributes_for :order_owners, reject_if: :all_blank, allow_destroy: true
validates :owners, presence: true
##owner.rb
has_many :order_owners, inverse_of: :owner, dependent: :destroy
has_many :orders, through: :order_owners
##order_owner.rb
belongs_to :owner
belongs_to :order
accepts_nested_attributes_for :owner
I've tried using custom validation methods like
validate :must_have_owner
...
def must_have_owner
if owners.empty? or owners.all? { |owner| owner.marked_for_destruction? }
errors.add(:base, 'Order must have at least one owner')
end
end
But the same thing occurs.
The way that I'm associating owners to an order upon creation is via nested attributes and using the Cocoon gem. So a user fills out the order form, then adds the appropriate owners (either via a Owners.all select or adding a new owner to be created), then submits the form. Without the aforementioned validation the whole process works fine but it also allows an order to be created without an owner which defeats the purpose.
Any ideas would be helpful, thanks!

Related

Trouble with validation check for has_many :through association; how to create?

Rails newbie here. I am having trouble with ensuring the presence of at least one "has_many :through" relationship in my users model.
I have:
class User < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :companies, through: :company_users
#validates :company_users, presence: true
end
class Company < ApplicationRecord
has_many :company_users, dependent: :destroy
has_many :users, through: :company_users
end
class CompanyUser < ApplicationRecord
belongs_to :user
belongs_to :company
end
I got the commented validates line from the excellent answers at:
Validate that an object has one or more associated objects
Rails 3: validate presence of at least one has many through association item
However when I implement this line, then doing the following in rails console:
c = Company.create
c.users.create!(email: "test#example.com")
gives
`raise_validation_error': Validation failed: Company users can't be blank (ActiveRecord::RecordInvalid)
From my newbie perspective, it seems like the HMT entry isn't created until after the user is created, which creates a validation error preventing the user in the first place! It's probably a very simple error, but how do I get the above behaviour to work with that validation in place?
I have tried setting inverse_of in a couple place, without any success.
And before it's mentioned, I'm purposefully using HMT instead of HABTM because I have additional attributes on the company_users model that will be set.
One option would be to change the validation to
validates :companies, presence: true
and then create the user with
User.create(companies: [Company.create])
Edit:
Adding inverse_of to the CompanyUser model should also work. The validation would be left as validate :company_users, presence: true:
class CompanyUser
belongs_to :user, inverse_of: :company_users
belongs_to :company
end

How to prevent nested create failing with scoped has_many in ActiveRecord?

A report_template has_many report_template_columns, which each have a
name and an index attribute.
class ReportTemplateColumn < ApplicationRecord
belongs_to :report_template
validates :name, presence: true
end
class ReportTemplate < ApplicationRecord
has_many :report_template_columns, -> { order(index: :asc) }, dependent: :destroy
accepts_nested_attributes_for :report_template_columns, allow_destroy: true
end
The report_template_columns need to be ordered by the index column. I'm applying this with a scope on the has_many association, however doing so causes the following error:
> ReportTemplate.create!(report_template_columns: [ReportTemplateColumn.new(name: 'id', index: '1')])
ActiveRecord::RecordInvalid: Validation failed: Report template columns report template must exist
from /usr/local/bundle/gems/activerecord-5.1.4/lib/active_record/validations.rb:78:in `raise_validation_error'
If I remove the scope the same command succeeds.
If I replace the order scope with where scope that command fails in the same way, so it seems to be the presence of the scope rather than the use of order specifically.
How can I apply a scope to the has_many without breaking the nested creation?
I believe you need the :inverse_of option added to the has_many association.
class ReportTemplate < ApplicationRecord
has_many :report_template_columns, -> { order(index: :asc) },
dependent: :destroy, inverse_of: :report_template
end
The api states that :inverse_of:
Specifies the name of the belongs_to association on the associated object that is the inverse of this has_many association. Does not work in combination with :through or :as options. See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
I also like how the cocoon gem words their reason for using it:
Rails 5 Note: since rails 5 a belongs_to relation is by default required. While this absolutely makes sense, this also means associations have to be declared more explicitly. When saving nested items, theoretically the parent is not yet saved on validation, so rails needs help to know the link between relations. There are two ways: either declare the belongs_to as optional: false, but the cleanest way is to specify the inverse_of: on the has_many. That is why we write: has_many :tasks, inverse_of: :project

accepts_nested_attributes_for causing infinite loop in rails_admin

I have two models that both accept_nested_attributes_for each other. I know this is causing issues with rails_admin but can't seem to figure out the appropriate command to exclude nested attributes in my configuration. Ideally I would like to exclude nested attributes for all models so that this problem does not happen again.
Branden,
You need to inform the bi-direction association using inverse_of:
Ex:
class User < ActiveRecord::Base
belongs_to :company, :inverse_of => :users
accepts_nested_attributes_for :company
end
For both sides of association:
class Company < ActiveRecord::Base
has_many :users, inverse_of: :company
accepts_nested_attributes_for :users, :allow_destroy => true
end
More information in the docs: http://guides.rubyonrails.org/association_basics.html#bi-directional-associations

Why aren't my nested attributes being destroyed along with the parent?

I have a Resume Model that has_many :skills, and my skills model that belongs_to :resume.
I am nesting the skills form inside the resume form and it's creating the record and relationship perfectly. But when I try to destroy the resume, the associated Skill is not being destroyed along with it.
Here are my models:
# Resume.rb
class Resume < ActiveRecord::Base
has_many :skills
belongs_to :user
accepts_nested_attributes_for :skills, allow_destroy: true
end
# Skill.rb
class Skill < ActiveRecord::Base
belongs_to :resume
end
Here's the strong params in the resume_controller.rb
def resume_params
params.require(:resume).permit(:user_id, :title, :summary, :job_title, skills_attributes [:skill_name, :_destroy])
end
As far as I can tell I am passing the _destroy key properly. I noticed some people had _destroy checkboxes in the form. I want the Skills to be deleted when I Destroy the entire resume. Thanks!
All you have specified is that you can destroy skills on a resume like with the checkboxes you mention seeing in some examples. If you want it to destroy all skills associated when a resume is destroyed you have adjust your has_many declaration.
has_many :skills, dependent: :destroy
Add :dependent => :destroy in your Resume model like below:
class Resume < ActiveRecord::Base
has_many :skills, :dependent => :destroy
belongs_to :user
accepts_nested_attributes_for :skills, allow_destroy: true
end
:dependent Controls what happens to the associated objects when their owner is destroyed:
:destroy causes all the associated objects to also be destroyed
:delete_all causes all the associated objects to be deleted directly from the database (so callbacks will not execute)
:nullify causes the foreign keys to be set to NULL. Callbacks are not executed.
:restrict_with_exception causes an exception to be raised if there are any associated records
:restrict_with_error causes an error to be added to the owner if there are any associated objects

Rails Admin accepts_nested_attributes with belongs_to hides search select

I'm using model with two belongs_to associations with Rails Admin.
belongs_to :product, inverse_of: :services
belongs_to :user, inverse_of: :services
accepts_nested_attributes_for :product, allow_destroy: true
accepts_nested_attributes_for :user, allow_destroy: true
Adding accepts_nested_attributes_for makes generated form look like this without select for search in existing records.
Without accepts_nested_attributes_for it looks exacly as I want it to
Is there a way to force Rails Admin use search select?
field :user do
nested_form false
end
doesn't work as I expected.
Well
field :user do
nested_form false
end
actually works fine. I was just using it in list block instead of edit... sigh

Resources