Rails many-to-many self relationship failing validation - ruby-on-rails

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

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 implicit validation error on existence of association

I'm running Rails 6.0.2.1.
I have a fairly simple model between clients, invoices, and proposals. An invoice belongs to a proposal (but this is optional - an invoice doesn't have to be based upon a proposal). Also, an invoice belongs to a client.
class Invoice < ActiveRecord::Base
belongs_to :client
belongs_to :proposal, foreign_key: "prop_id"
...
class Proposal < ActiveRecord::Base
belongs_to :client
has_one :invoice, foreign_key: "prop_id", dependent: :destroy
...
class Client < ActiveRecord::Base
has_many :proposals, dependent: :destroy
has_many :invoices, dependent: :destroy
...
These models have no validations between one another. That is, I do not have a validation indicating that an invoice must have a proposal or even a client. However, Rails is giving me validation errors on their existence if I check the validity of any field in the invoice:
> inv = Invoice.new
=> #<Invoice id: nil, client_id: nil, prop_id: nil, tocb_id: nil, fromcb_id: nil,
date_invoice: "2020-02-10", written_by: nil, terms: nil, date_due: nil,
status: "Pending", shipping: nil, amount: 0.0, amt_due: 0.0, deposit: nil,
tax_rate: nil, comments: nil>
> inv.errors.count
=> 0
> inv.valid? :amount
=> false
> inv.errors.count
=> 2
> inv.errors
=> #<ActiveModel::Errors:0x000056466dac7a38 #base=#<Invoice id: nil, client_id: nil,
prop_id: nil, ... , #messages={:client=>["must exist"], :proposal=>["must exist"]},
#details={:client=>[{:error=>:blank}], :proposal=>[{:error=>:blank}]}>
Why is it flagging missing client and missing proposal as existence errors?
You are getting the error because in Rails 5 and above, whenever we define a belongs_to association, it is required to have the associated record present by default.
So you need to link a client and a proposal to invoice, only then would you be able to create an invoice object. Which means you need to do this -
client = Client.create
proposal = Proposal.create
inv = Invoice.new(client: client, proposal: proposal)
You can also mention the belongs_to relationship as optional, then the presence of the associated object won't be validated
class Invoice < ActiveRecord::Base
belongs_to :client, optional: true
belongs_to :proposal, foreign_key: "prop_id", optional: true
...
With optional: true
inv = Invoice.new
will not give any errors

Validation Failed using Cocoon for Polymorphic Association

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

Rails Find based on a set existing in through a relation

I am trying to select an instance based on a relation of that instance containing a set. A simplified example follows:
class Product::Variation < ActiveRecord::Base
attr_accessible :name, :product_id, :quantity
belongs_to :product
has_many :bids, :foreign_key => :product_variation_id
has_many :product_variation_property_values, :class_name => 'Product::Variation::PropertyValue'
has_many :property_values, :through => :product_variation_property_values, :class_name => 'Property::Value'
end
class Product::Variation::PropertyValue < ActiveRecord::Base
attr_accessible :property_value_id, :variation_id, :property_id
belongs_to :variation
belongs_to :property_value, :class_name => 'Property::Value'
end
class Property < ActiveRecord::Base
attr_accessible :name
has_many :values, :class_name => 'Property::Value'
end
class Property::Value < ActiveRecord::Base
attr_accessible :content
belongs_to :property
belongs_to :partner
end
So now I want to do something like the following (in psuedo code):
Variation.where(:property_values includes [Property::Value.find(1), Property::Value.find(2)])
Is there a way to do this using ActiveRecord?
Thanks and let me know if you need more info.
More Info
I tried the following:
Product::Variation.joins(:property_values).where('property_values.id' => [Property::Value.find(1).id, Property::Value.find(2).id]).first
...which is the following SQL...
SELECT "product_variations".* FROM "product_variations" INNER JOIN "product_variation_property_values" ON "product_variation_property_values"."variation_id" = "product_variations"."id" INNER JOIN "property_values" ON "property_values"."id" = "product_variation_property_values"."property_value_id" WHERE "property_values"."id" IN (1, 2)
...and this returns...
#<Product::Variation id: 25, product_id: 1, quantity: 39, created_at: "2013-11-18 00:18:45", updated_at: "2013-11-18 00:18:45">
But if I do:
Product::Variation.find(25).property_values.inspect
...I get...
[#<Property::Value id: 1, property_id: 1, content: "XS", created_at: "2013-11-18 00:18:45", updated_at: "2013-11-18 00:18:45", color: nil, color_texture: nil, secondary_color: nil>, #<Property::Value id: 6, property_id: 2, content: "Dark Wood", created_at: "2013-11-18 00:18:45", updated_at: "2013-11-18 00:18:45", color: "#855E42", color_texture: "striped", secondary_color: "#FFB90F">]
But I'm looking for the Product::Variation that contains both Property::Value 1 and 2. This is returning those that contain 1 or 2.
This should be doable with a join query. Hard to write form heart, but should be something like this:
Variation.joins(propert_values: :values).where('values.id' => [Property::Value.find(1).id, Property::Value.find(2).id]).first

rails nested forms unknown attribute

I've read a couple of other posts for this error here, but no one really fits my problem.
The produced error is
ActiveRecord::UnknownAttributeError: unknown attribute: practice_id
It's happening while I try to build the uebung_maps in the rails console:
irb(main):003:0> #p = Practice.new
=> # Practice id: nil, datum: nil, start: nil, end: nil, group: nil, topic: nil, theoab: nil, pracab: nil, action: nil, water: nil, tools: nil, broken: nil, toolkeeper: nil, atw: nil, atfinfo: nil, created_at: nil, updated_at: nil>
irb(main):004:0> #p.uebung_maps.build
ActiveRecord::UnknownAttributeError: unknown attribute: practice_id
from /home/basti/.rvm/gems/ruby-1.9.3-p392/gems/activerecord-3.2.13/lib/active_record/attribute_assignment.rb:88:in `block in assign_attributes'
my models and so on looks like following
Practice Model
class Practice < ActiveRecord::Base
has_many :uebung_maps
has_many :persons
accepts_nested_attributes_for :uebung_maps
attr_accessible :uebung_map_attributes, :action, :atfinfo, :atw, :broken, :datum, :end, :group, :pracab, :start, :theoab, :toolkeeper, :tools, :topic, :water
end
Uebung_map Model
class UebungMap < ActiveRecord::Base
belongs_to :person
belongs_to :role
belongs_to :practice
belongs_to :vehicle
attr_accessible :person_id, :role_id, :uebung_id, :vehicle_id
end
Try adding :practice_id to your attr_accessible, like so:
attr_accessible :person_id, :role_id, :uebung_id, :vehicle_id, :practice_id
I've finally found the problem. The column in uebung_map wich should point to Pratice was named uebung_id in stead of practice_id.. now it's working

Resources