How to create association between unsaved records - ruby-on-rails

I created 3 models as below, and used cocoon nested form to create associations between them.
class Unit < ApplicationRecord
has_many :mapping_categories, -> { distinct }, dependent: :destroy, inverse_of: :unit
accepts_nested_attributes_for :mapping_categories,
allow_destroy: true,
reject_if: :all_blank
end
class MappingCategory < ApplicationRecord
belongs_to :unit
has_many :mapping_items, -> { distinct }, dependent: :destroy, inverse_of: :mapping_category
accepts_nested_attributes_for :mapping_items,
allow_destroy: true
end
class MappingItem < ApplicationRecord
belongs_to :mapping_category
has_many :mapping_item_links
has_many :linked_mapping_items, through: :mapping_item_links, dependent: :destroy
end
Each mapping_item can have many other mapping_items through a joint table. In every mapping_item section in Unit form, this association is displayed as a select input.
When creating or updating Unit, there are many mapping_categories tabs in the Unit form, and there are many mapping_items sections in each mapping_category section.
For example, I have Mapping Category A and Mapping Category B. I want to add Mapping Item 1 to Mapping Category A and Mapping Item 2 to Mapping Category B. The question is: How to create the association between Mapping Item 1 and Mapping Item 2, as these two items are not saved yet?
Thanks in advance.

YOU CAN DO IT
You have to write right code
user = User.new(name: 'Jons', email: 'jons#qq.ww')
bank_account = BankAccount.new(number: 'JJ123456', user: user)
bank_account.save
in this way will be saved both raws and user and bank_account
in your case:
unit = Unit.new(mapping_categories: [mapping_category])
mapping_category = MappingCategory.new(mapping_items: [mapping_item])
mapping_item = MappingItem.new
unit.save
and if you wanna use nested_attributes, you just have to build hash with attributes
params = { mapping_categories: [mapping_items: [{.....}]}] }
Unit.create(params)
but you have to figure out with right nesting

From my understanding of your question... You can't. These items don't yet have ids and there for can't be associated with another model.
> contact = Contact.new(full_name: "Steve", email:"example#asdf.com")
=> #<Contact id: nil, full_name: "Steve", email: "example#asdf.com", created_at: nil, updated_at: nil>
> invoice = Invoice.new(contact_id: contact.id, invoice_type: "Something")
=> #<Invoice id: nil, contact_id: nil, invoice_type: "Something" created_at: nil, updated_at: nil>
> invoice.save
=> false

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

self-joined model Comment that belongs to Posts

I have two models. I want them behave like post with tree-structured comments.
Post:
class Post < ApplicationRecord
has_many :comments
end
Comment:
class Comment < ApplicationRecord
belongs_to :post
belongs_to :parent, class_name: 'Comment', optional: true
has_many :children, class_name: 'Comment', foreign_key: 'parent_id'
end
When I create a comment in console through
post = Post.create(title: 'Title', content: 'text')
comment = post.comments.create(content: 'text')
child = comment.children.create(content: 'text')
pp child
this is what I get:
[22] pry(main)> child = comment.children.create(content: 'text')
(0.2ms) begin transaction
(0.2ms) rollback transaction
=> #<Comment:0x00007f16ec59cc20
id: nil,
content: "text",
post_id: nil,
parent_id: 5,
created_at: nil,
updated_at: nil>
I experimented a bit without much success and self-join guides did not help. What code is lacking in my models?
upd.
child is not saved into the database. Error: ["Post must exist"]. But post exists. Post id is nit set when I run comment.children.new(content: 'text') How do I create an association like children belongs_to :post, through: :parents (pseudocode)
You have to mention what post the parent comment belongs to.
comment.children.new(content: "text", post: comment.post)
Or you might also do this in a callback on your model.
in models/comment.rb
before_save :set_post_id
private
def set_post_id
self.post_id = post.id || parent.post.id
end

Rails 4 converting a polymorphic association to has_many through

A Manager has many contacts via polymorphic association
class Manager
has_many :contacts, as: :contactable
end
class Contact
belongs_to :contactable, polymorphic: true
end
The relation works fine but now a contact can be associated to many managers.
So, added a new model Contactable, a joins table 'contactables' and moved contactable_id and contactable_type fields from contacts table to contactables table.
class Contactable
belongs_to :contact
belongs_to :contactable, polymorphic: true
end
Now confused about the Manager and Contact relation that how it would be defined in models correctly to make it working. Tried the following but it doesn't work:
class Manager
has_many :contacts, through: :contactables, source: :contactable, source_type: "Contact"
end
So I checked this interesting topic and will tell what I know.
When you create objects as usual in has_many :through:
class Contact
has_many :contactables
has_many :managers, :through => :contactables
end
class Manager
has_many :contactables
has_many :contacts, :through => :contactables
end
class Client
has_many :contactables
has_many :contacts, :through => :contactables
end
class Contactable
belongs_to :contact
belongs_to :manager
belongs_to :client
end
You get to use foreign keys fro each referenced object. Polymorphic looks like a great solution. So:
class Contactable
belongs_to :contact
belongs_to :polymorphic_model, polymorphic: true
end
class Contact
has_many :contactables
has_many :managers, :through => :contactables, :source => :polymorphic_model, :source_type => 'Manager'
end
class Manager
has_many :contactables, :as => :polymorphic_model
has_many :contacts, :through => :contactables
end
Setting the :as option indicates that this is a polymorphic
association
:source => :polymorphic_model is used to tell Rails to get the related object from the subclass. :source means the same as :class_name. Without this option Rails would try to get associated Manager from the Contactables table, while it should be reached via virtual Polymorphic_model.
By adding belongs_to :polymorphic_model to Contactable you enable Contact (witch already sits there, because of belongs_to :contact) to be associated with a Manager or Client, because thats what Polymorphic association does - references two or more parent tables. And because Contact have_many Contactables, the same Contact object can be associated with many managers or clients. So after you understand it, it looks really simple - Joined model belongs to Contact and Joined model also holds references to Manager and Client through Polymorphic association. So in order for Contact to have many managers, you create another Contactable object that belongs to the same Contact, but different Manager. Doesn't look super efficient, but personally me, not knowing a better way..
Here is a tested proof:
Manager.create!(name: "Bravo")
=> #<Manager id: 1, created_at: "2017-04-12 12:17:41", updated_at: "2017-04-12 12:17:41", name: "Bravo">
Manager.create!(name: "Johnny")
=> #<Manager id: 2, created_at: "2017-04-12 12:18:24", updated_at: "2017-04-12 12:18:24", name: "Johnny">
Contact.create!(number:"123")
=> #<Contact id: 1, created_at: "2017-04-12 12:18:59", updated_at: "2017-04-12 12:18:59", number: 123>
c = Contactable.new
c.contact = Contact.first
c.unit = Manager.first
c
=> #<Contactable id: nil, unit_type: "Manager", created_at: nil, updated_at: nil, unit_id: 1, contact_id: 1>
Now to set another Manager to the same contact, we create a new Contactable:
cc = Contactable.new
cc.contact = Contact.first
cc.unit = Manager.last
cc
=> #<Contactable id: nil, unit_type: "Manager", created_at: nil, updated_at: nil, unit_id: 4, contact_id: 1>
And to get all associated:
Contact.first.managers
Contactable's database:
contact_id
unit_id
unit_type
And one interesting quote by #Bill Karwin:
The Polymorphic Associations design breaks rules of relational
database design. I don't recommend using it.
But he wrote this long time ago. Probably irrelevant now.
Why can you not have a foreign key in a polymorphic association?

Updating nested_attribute on a model, doesn't update the has_many through relationship

I have the following model relationship:
class Role < ActiveRecord::Base
has_many :permissions_roles, inverse_of: :role, dependent: :destroy
has_many :permissions, through: :permissions_roles
accepts_nested_attributes_for :permissions_roles, allow_destroy: true
end
class PermissionsRole < ActiveRecord::Base
belongs_to :permission
belongs_to :role
end
Permission class has id, name etc.
I am using Rails 4.2.4 and facing an error with update method of the role model. When I update the nested attribute permissions_roles, it doesn't update the has_many :through permissions attribute of the model. This is what I did to verify the error in rails console:
> role = Role.create(name: 'role', permissions_roles_attributes: [{permission_id: 1}])
# Checking permissions for the role
> role.permissions
[#<Permission:0x007ff3c3963160
id: 1,
name: "read">]
# Updating the nested attributes
> role.update(permissions_roles_attributes: [{permission_id: 10}])
# Checking nested attributed - Return as expected
> role.permissions_roles
=> [#<PermissionsRole:0x007ff3bbade740 id: 78, permission_id: 1, role_id: 11>, #<PermissionsRole:0x007ff3bc8fdee8 id: 79, permission_id: 10, role_id: 11>]
# Checking has_many through relationship. Stale :(
> role.permissions
=> [#<Permission:0x007ff3be1d29f0
id: 1,
name: "read">]
I have to manually call reload on the model or role.permissions attribute to make sure role.permissions is updated. Is there any way for permissions attribute to automatically update whenever I update permissions_roles ?
You have forgot to specify the id of the PermissionRole model, which you want to update:
role.update(permissions_roles_attributes: [{ id: 78, permission_id: 10}])

Resources