I have the following models in my schema:
class Notification < ApplicationRecord
has_many :impacts, as: :impactable
accepts_nested_attributes_for :impacts, reject_if: :all_blank, allow_destroy: true
end
class Impact < ApplicationRecord
#association
belongs_to :impactable, polymorphic: true
#enum
enum impact_type: [:full_school, :standard, :section]
end
Whenever I try to save a notification now - I get the an error - Validation failed: Impacts impactable must exist
I've tried to create impacts from notifications manually with Notification.last.impacts.create and they work fine.
What could be the problem here?
More info -
When I add a byebug to the #notification object before it saves in the controller - this is the output -
>> #notification
=> #<Notification id: nil, notification_type: "email", title: "Test", content: "Tomorrow is a holiday", created_at: nil, updated_at: nil>
And also checking for it's associations --
>> #notification.impacts
=> #<ActiveRecord::Associations::CollectionProxy [#<Impact id: nil, impact_type: "standard", standard: 2, created_at: nil, updated_at: nil, impactable_id: nil, impactable_type: "Notification", section: "">]>
You just need to add inverse_of: :impactable to your Notifications Model.
has_many :impacts, as: :impactable, inverse_of: :impactable
Related
I'm building a toy Rails app that will run tournaments. The thing with this type of tournament is though is that rounds can have both multiple input rounds and multiple output rounds (i.e., you can't model a tournament's rounds as a tree, but it's still a DAG).
I'm running into an issue where I believe I have my associations set up properly, but Rails is complaining about the models suddenly becoming invalid, and I can't figure out how to get more information.
I have these relevant ActiveModel classes:
class Round < ApplicationRecord
belongs_to :tournament
has_and_belongs_to_many :players
has_many :previous_round_edges, foreign_key: :end_id, class_name: "RoundEdge"
has_many :previous_rounds, through: :previous_round_edges, source: :start
has_many :next_round_edges, foreign_key: :start_id, class_name: "RoundEdge"
has_many :next_rounds, through: :next_round_edges, source: :end
end
class RoundEdge < ApplicationRecord
belongs_to :start, foreign_key: :start_id, class_name: "Round"
belongs_to :end, foreign_key: :end_id, class_name: "Round"
end
The code in question that triggers errors:
final_round = rounds.build()
players.each_slice(4) do |player_chunk|
round = rounds.build(players: player_chunk, position: round_position)
# Objects are valid right until we start associating them with each other
round.next_rounds << final_round
final_round.previous_rounds << round
end
I've also tried throwing in a debugger, and these are the results:
round.errors
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=next_round_edges, type=invalid, options={}>, #<ActiveModel::Error attribute=next_rounds, type=invalid, options={}>]>
final_round.errors
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=previous_round_edges, type=invalid, options={}>, #<ActiveModel::Error attribute=previous_rounds, type=invalid, options={}>]>
If I look at the relations though, they seem correct:
round
=> #<Round:0x000000010fc0e760 id: nil, position: 0, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>
final_round
=> #<Round:0x00000001104e2580 id: nil, position: nil, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>
round.previous_rounds
=> []
round.next_rounds
=> [#<Round:0x00000001104e2580 id: nil, position: nil, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>]
final_round.previous_rounds
=> [#<Round:0x000000010fc0e760 id: nil, position: 0, next_round_id: nil, tournament_id: nil, created_at: nil, updated_at: nil>]
final_round.next_rounds
=> []
So it looks like they point to each other fine, but I can't figure out what is going on.
Thanks!
I figured it out! I needed to manually specific the inverse relations. What was happening was that even though the Rounds were linked, the RoundEdges` were not being created properly.
The solution was to update the Round class to look as follows:
class Round < ApplicationRecord
belongs_to :tournament
has_and_belongs_to_many :players
has_many :previous_round_edges, foreign_key: :end_id, class_name: "RoundEdge", inverse_of: :end
has_many :previous_rounds, through: :previous_round_edges, source: :start, inverse_of: :next_rounds
has_many :next_round_edges, foreign_key: :start_id, class_name: "RoundEdge", inverse_of: :start
has_many :next_rounds, through: :next_round_edges, source: :end, inverse_of: :previous_rounds
end
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 } }
If I have a parent class like so:
class Component < ApplicationRecord
include Imageable
end
And that concern looks like so:
module Imageable
extend ActiveSupport::Concern
included do
has_one :image_attachment, as: :imageable, dependent: :destroy
end
end
And that class looks like this:
class ImageAttachment < ApplicationRecord
belongs_to :imageable, polymorphic: true, optional: true
has_attached_file :data
end
Why can't they find each other given these two instances?
ImageAttachment.last
=> <ImageAttachment id: 12, imageable_type: "component", imageable_id: 3, data_file_name: "tumblr_nb88njd2DF1sfwp0ho1_1280.jpg", data_content_type: "image/jpeg", data_file_size: 63794, data_updated_at: "2018-05-02 11:07:12", created_at: "2018-05-02 10:37:48", updated_at: "2018-05-02 11:07:13">
Component.find(3)
=> <Component id: 3, name: "Testing", body: "Test body", created_at: "2017-11-22 02:43:03", updated_at: "2018-05-01 23:50:01">
Component.find(3).image_attachment
=> nil
ImageAttachment.last.component
=> NoMethodError (undefined method `component' for #<ImageAttachment:0x00007fea291ac1e0>)
1) It seems that your imageable_type attribute is wrong, because it should be a class name e.g. 'Component' not 'component'. Rails would not do this by default, so I assume you tampered with that column manually and that is why it is not working
2) ImageAttachment.last.component -> this won't work because your association for ImageAttachment is belongs_to :imageable, so in order to get parent, you need to call ImageAttachment.last.imageable
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.
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