Why does `validates_associated` exist? - ruby-on-rails

This section of railsguide says:
You should use this helper when your model has associations with other models and they also need to be validated.
So I thought validation of associated models wouldn't be run without validates_associated.
But actually, It was run without it.
There are two models, School and Student.
class School < ActiveRecord::Base
has_many :students
validates :name, presence: true
end
class Student < ActiveRecord::Base
belongs_to :school
validates :name, presence: true
end
On rails console,
school = School.new
=> #<School id: nil, name: nil, created_at: nil, updated_at: nil>
school.students << Student.new
=> #<ActiveRecord::Associations::CollectionProxy [#<Student id: nil, name: nil, school_id: nil, created_at: nil, updated_at: nil>]>
school.name = "test shcool"
=> "test shcool"
school.save
(0.1ms) begin transaction
(0.1ms) rollback transaction
=> false
school.errors.full_messages
=> ["Students is invalid"]
If with validates_associated like below:
class School < ActiveRecord::Base
has_many :students
validates :name, presence: true
validates_associated :students
end
class Student < ActiveRecord::Base
belongs_to :school
validates :name, presence: true
end
On rails console, I ran the exact same commands as above. But the last command school.errors.full_messages returned different result. (It is strange that there are duplicate error messages.)
school.errors.full_messages
=> ["Students is invalid", "Students is invalid"]
My questions are
Is this a RailsGuide's mistake?
Why does validates_associated exist?
Or do I have any mistaken idea?
My environment is
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin14.0]
Rails 4.2.0

it checks to see if the associated objects are valid before saving

Related

How do i select an association record that has yet to be saved in Rails?

I have the following models and relationships. I'm building a form and am wanting to initialize terms of the proposal for the form. How can I select a specific ProposalTerm by it's term_type_id to pass on to my fields_for block?
Proposal
class Proposal < ApplicationRecord
after_initialize :add_terms
has_many :terms, class_name: "ProposalTerm", dependent: :destroy
accepts_nested_attributes_for :terms
def add_terms
terms << ProposalTerm.first_or_initialize(type: TermType.signing_bonus)
end
end
ProposalTerm
class ProposalTerm < ApplicationRecord
include DisableInheritance
belongs_to :proposal
belongs_to :type, class_name: "TermType", foreign_key: "term_type_id"
def self.signing_bonus
find_by(type: TermType.signing_bonus)
end
end
My Attempt
>> #proposal.terms
=> #<ActiveRecord::Associations::CollectionProxy [#<ProposalTerm id: nil, season: nil, value: nil, is_guaranteed: false, term_type_id: 2, proposal_id: nil, created_at: nil, updated_at: nil>]>
>> #proposal.terms.where(term_type_id: 2)
=> #<ActiveRecord::AssociationRelation []>
I was able to figure out an answer. I had tried "select" but I was doing it incorrectly.
I had tried the following,
#proposal.terms.select(term_type_id: 2)
but that wasn't returning anything. I then did the following...
#proposal.terms.select { |t| t.term_type_id = 2 }
If you want to return just the first instance use "detect" ...
#proposal.terms.detect { |t| t.term_type_id = 2 } }

Rails 5.1 Multiple belongs_to associations with optional: true, rollback - 'required association'

I want to use belongs_to parent_model, optional: true assocation for 1 child - and 2 parents. Found this answer: Model belongs_to eiher/or more than one models. Tried that. But with 2 associated parent models one of them causes rollback when saving the child model.
Is there any limitation on belongs_to with multiple parent models and optional: true? Or am I missing some other presence validation?
Important to say: I would rather avoid using polymorphic associations in this case.
PrivateAttachment has a file attached using Paperclip.
private_attachment.rb
Class PrivateAttachment < ApplicationRecord
belongs_to :first_class, optional: true
belongs_to :second_class, optional: true
has_attached_file :certificate
validates_attachment_presence :certificate
validates_attachment_size :certificate, :less_than => 10.megabytes
validates_attachment_content_type :certificate,
:content_type => [
"image/jpg",
"image/jpeg",
"image/png",
"application/pdf",
"file/txt",
"text/plain",
"application/doc",
"application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"application/vnd.oasis.opendocument.text",
]
end
first_class.rb
Class FirstClass < ApplicationRecord
validates_length_of :message, :maximum => 2000, allow_blank: true
has_many :private_attachments, dependent: :destroy
has_many :approvals
accepts_nested_attributes_for :approvals
end
second_class.rb
Class SecondClass < ApplicationRecord
has_many :private_attachments, dependent: :destroy
end
When creating PrivateAttachment:
second_class.private_attachments.new(certificate: file)
there's a Rollback on save with a message, despite optional attribute is set to true.
Full error
ActiveModel::Errors:0x00007f8bbe03a380 #base=#PrivateAttachment id:
nil, first_class_id: nil, company_id: nil, user_id: nil, doc_type:
nil, url: nil, active: nil, blind: nil, permit_period: nil, views:
nil, downloads: nil, created_at: nil, updated_at: nil,
certificate_file_name: "test.pdf", certificate_content_type:
"application/pdf", certificate_file_size: 443632,
certificate_updated_at: "2018-07-24 20:18:20", second_class_id: 23>,
#messages={:first_class=>["First class required."]},
**#details={:first_class=>[{:error=>:blank}]}>
When creating Attachment with a first_class, everything works just fine, no error.
UPDATE 1---------------------------------------------------------------------
I just realized, that first_class has a child model Approval which has own validation. But at this point I don't understand why should be this deep assocation being taken into consideration when using optional: true ?
approval.rb
class Approval < ApplicationRecord
belongs_to :job_application
validates_acceptance_of :operator_approval, :allow_nil => false,
end
Rails 5.1.6.
PG DB

Does Rails allow conditional validations for validates_associated?

Does Rails allow conditional validations for validates_associated? I'm seeing the following on Rails 4.2.0. Am I trying to do it incorrectly?
Models:
class User < ActiveRecord::Base
has_many :books
validates_associated :books, if: :should_validate_book?
def should_validate_book?
return false
end
end
class Book < ActiveRecord::Base
belongs_to :user
validates_presence_of :title
end
The presence validation on Book's title attribute still runs (Rails console):
> u = User.create!
=> #<User id: 2, created_at: "2015-02-24 19:34:51", updated_at: "2015-02-24 19:34:51">
> u.books.build
=> #<Book id: nil, user_id: 3, title: nil, created_at: nil, updated_at: nil>
> u.valid?
=> false
> u.books.first.errors
=> #<ActiveModel::Errors:0x007fa256b210d8 #base=#<Book id: nil, user_id: 3, title: nil, created_at: nil, updated_at: nil>, #messages={:title=>["can't be blank"]}>
It turns out that validates_associated is ON by default for has_many relationships. To make it conditionally, you'd need to add validate: false to the has_many declaration:
has_many :books, validate: false
In Rails since time immemorial validates_associated has only taken a list of attributes. Besides that, you'd kind of be mixing up behavior between your models from what I gather from the criteria you've pasted. A better approach would be to adjust your validations in the Book model to account for the variation and let Book decide for itself whether an object should be validated or not.

Why is accepts_nested_attributes_for not working as expected?

I am trying to figure out why my create is not working. If I have the following two models below
class Product < ActiveRecord::Base
belongs_to :product_template
has_many :presentations, through: :product_presentations
has_many :product_presentations
accepts_nested_attributes_for :product_presentations
validates :start_date, :product_template_id, presence: true
validates :start_date, uniqueness: true
end
class ProductPresentation < ActiveRecord::Base
belongs_to :product
belongs_to :course
belongs_to :presentation
validates_presence_of :product_id, :course_id, :presentation_id
validates_uniqueness_of :presentation_id, :scope => :product_id
end
And I enter the following into my console.
product = Product.new(
{"start_date"=>"Sat, 06 Sep 2014 00:00:00 +0200",
"product_template_id"=>"5", "product_presentations_attributes"=>{
"0"=>{"course_id"=>"1", "presentation_id"=>"1"},
"1"=>{"course_id"=>"2", "presentation_id"=>"2"}}})
It should save, but I get the error below
#<ActiveModel::Errors:0x007f9eaeb9d698 #base=#<Product id: nil, product_template_id: 5, start_date: "2014-09-05 22:00:00", created_at: nil, updated_at: nil>, #messages={:"product_presentations.product_id"=>["can't be blank"]}>
Now I understand that there is not product_id in the product_presentations_attributes hash, but I thought this would be rails automigically populated because it's being created via the product.
I had this working and now I can't figure out how I messed it up. Any help appreciated.
Cool ... after posting this, reading over my own post I tried to remove the validation for the product_id presence.
This seems to fix the error.
I am guessing that this is a limitation of accepts_nested_attributes_for in rails - the parent object cannot have it's foreign key validated in the model.

Mass Assignment warning when using association methods

I have a very odd mass assignment error that shows up when I use association methods to create new objects.
I have a user model that looks like this:
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
end
I also have a posts model that looks like this:
class Post < ActiveRecord::Base
belongs_to :user
attr_accessible :body, :title
end
If I do the following in console, I get a mass assignment warning:
> user = User.create(:name => "Daniel");
> user.posts.create(:title => "Hello World")
=> #<Post id: 1, body: nil, title: "Hello World", created_at: "2011-11-03
18:24:06", updated_at "2011-11-03 18:24:06", user_id = 1>
> user.posts
=> WARNING: Can't mass-assign attributes: created_at, updated_at, user_id
When I run user.posts again, however, I get:
> user.posts
=> [#<Post id: 1, body: nil, title: "Hello World", created_at: "2011-11-03
18:24:06", updated_at "2011-11-03 18:24:06", user_id = 1>]
There are a couple of other tricks I can do to avoid the mass assignment error, such as calling user.posts before I do users.posts.create.
Why is this happening and how can I prevent it?
I'm using Rails 3.0.7.
How about changing your user model to include attr_accessible for posts association
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
attr_accessible :posts
end

Resources