I'm getting this error when I try to mass-assign a HABTM relationship in Rails 5:
*** ActiveRecord::RecordNotFound Exception: Couldn't find Job with ID=6 for Profile with ID=
class Profile < ApplicationRecord
has_and_belongs_to_many :jobs
accepts_nested_attributes_for :jobs
end
class Job < ApplicationRecord
has_and_belongs_to_many :profiles
end
class ProfilesController < ApplicationController
def create
#profile = Profile.new(profile_params)
end
private
def profile_params
params.require(:profile).permit(
:name,
:email,
:jobs_attributes: [:id]
)
end
end
=form_for #profile do |f|
=f.fields_for :jobs do |j|
=j.select :id, options_for_select([[1, "Job 1", ...]])
The problem is that accepts_nested_attributes_for is a way to update attributes on an associated object (either already linked or one that you're creating). You can think of it doing something like:
params[:jobs_attributes].each do |job_attributes|
#profile.jobs.find(job_attributes[:id]).attributes = job_attributes
end
Except that the job is then saved with the new attributes in an after-save of the parent object.
This isn't what you want. In fact, you don't need accepts_nested_attributes at all.
Instead, change the attribute in your view to job_ids as if it's an attribute of the profile, something like the following:
=form_for #profile do |f|
=j.select :job_ids, options_for_select([[1, "Job 1", ...]])
This will effectively call profile.job_ids = [1,2,3] which will have the effect that you're looking for.
Related
I want to create an Invoice and the regarding InvoiceItems at the same time. While Invoice has_many :invoice_items and an InvoiceItem belongs_to :invoice. How do I perform such action in Rails 7 so that a User can add multiple invoiceItems to their invoice via Turbo? I dont need to know how TurboStreams and stuff work, since I am familiar, but I just cant get the InvoiceItems to be created at the same time as the Invoice.
I already found this post, but could not get any useful information out of it.
Models
Invoice.rb
class Invoice < ApplicationRecord
belongs_to :project
has_many :invoice_items, foreign_key: :invoice_id # not sure if this foreign_key is necessary
accepts_nested_attributes_for :invoice_items
end
invoice_item.rb
class InvoiceItem < ApplicationRecord
belongs_to :invoice
end
Controllers
Invoice_controller.rb
def create
#project = Project.find(params[:project_id])
#client = Client.find(params[:client_id])
#invoice = #project.invoices.new(invoice_params)
#invoice_item = #invoice.invoice_items.new
#invoice.invoice_items_attributes = [:invoice_id, :amount]
#invoice.client_id = #client.id
respond_to do |format|
if #invoice.save
....
def invoice_params
params.require(:invoice).permit(... :invoice_item, invoice_item_attributes: [:id, :invoice_id, :amount, ...])
end
Currently I try using a form_for inside of the Invoice form like:
<%= form.fields_for #invoice.invoice_items.build do |lorem| %>
Which gives me following error in the console (but saves the invoice as expected:
Unpermitted parameter: :invoice_item. Context: { controller: InvoicesController, action: create, request: #<ActionDispatch::Request:0x000000010a0c8d88>, params: {"authenticity_token"=>"[FILTERED]", "invoice"=>{..., "invoice_item"=>{"invoice_id"=>"", "amount"=>"3"}}, "button"=>"", "controller"=>"invoices", "action"=>"create", "user_id"=>"1", "client_id"=>"1", "project_id"=>"1"} }
notice that the invoice_id is not passed to the invoice_item.
Via console something like
#invoice = Invoice.new
#invoice.invoice_items.new(amount: "3", ...)
#invoice.save!
Does work weirdly but it does not translate to my code.
What am I doing wrong here?
# invoice_item_attributes is wrong
def invoice_params
params.require(:invoice).permit(... :invoice_item, invoice_item_attributes: [:id, :invoice_id, :amount, ...])
end
Should be
# invoice_items_attributes is right
def invoice_params
params.require(:invoice).permit(... :invoice_item, invoice_items_attributes: [:id, :invoice_id, :amount, ...])
end
Notice the missing 's'.
https://www.ombulabs.com/blog/learning/rails/nested-forms.html
After following the GoRails screencast on how to properly set nested form attributes in rails, I still came across errors. I eventually could trace them and found this neat post which game the hint to use inverse_of and autosave: true. I am not 100% sure what those do, even though I will read now to find out, but my stuff is working properly now :)
Modified Model
class Invoice < ApplicationRecord
belongs_to :project
has_many :invoice_items, inverse_of: :invoice, autosave: true
accepts_nested_attributes_for :invoice_items
...
I had this working very similar to another controller however i needed to change this relation to another controller called agreements_controller. I want to create a has one model. review has one and belongs to agreements.
Why isn't the row being created properly?
reviews_controller:
class ReviewsController < ApplicationController
def create
#review = Reviews.create(review_params)
end
private
def review_params
params.require(:review).permit(:comment, :star, :agreement_id, :user_id, :reviser_user_id)
end
end
_form.html.erb
<%= form_for([agreement, agreement.build_review] ) do |f| %>
<% end %>
agreement.rb
class Agreement < ActiveRecord::Base
has_one :review, :dependent => :destroy
end
review.rb
class Review < ActiveRecord::Base
belongs_to :agreement
belongs_to :reviser_user
belongs_to :user
end
I've tried to find similar examples online, but all I could find was nested forms... I don't need a nested form I just want the review to create as a has one.
Models are Singular. Use
Review.create(review_params)
I have a model named Profile which has belongs_to relation with Address
class Profile < ActiveRecord::Base
belongs_to :address, dependent: :destroy
accepts_nested_attributes_for :address, allow_destroy: true
here is the code in controller
def create
#profile = Profile.new(profile_signup_params)
#profile.save
respond_to
..... etc.....
end
for params
def profile_signup_params
params.require(:profile).permit( { address_attributes: [:country]
end
but #profile.save
i get this object
#<MemberProfile:0x0000000af135b0
id: 28,
address_id: nil,
birth_date: nil,
country_code: nil,
phone: nil,
stripe_customer_id: "123",
created_at: some time,
updated_at: some time>
as you cane see this address_id is nil
Profile is created
Address is created
but Address is not assigned to Profile
Please help me, what thing i am doing wrong
I think You made a Wrong association between Address and Profile
Profile which has belongs_to relation with Address instead It should be Profile has has_one association with respect to Address.
As mention in official Documentation Active Record Nested Attributes
class Profile < ActiveRecord::Base
has_one :address, dependent: :destroy
accepts_nested_attributes_for :address, allow_destroy: true
...
end
class Address < ActiveRecord::Base
belongs_to :profile
end
Rest Controller and model code would be same in your case except you need to a primary-foreign key relation between Address and Profile; need to create profile_id column in address table.
Note: Make sure if there is a uniqueness you need to follow Uniqueness Gotcha!!! in ActiveRecord NestedAttributes.
Original Blog Uniqueness Gotcha!!! Problem and Solution
Hope this This Help you!!!
To add to Vinay's answer (which is correct IMO), you'd want to make sure you're passing the right data through your controller.
Whilst the belongs_to association should allow you to set the nested parameters you require, it would be prudent to mention what Vinay said -- if you're creating an Address for a Profile, surely it would be the address that would belong to the Profile?
You can see about the has_one association here:
You'd handle it in a very similar way:
#app/controllers/profiles_controller.rb
class ProfilesController < ApplicationController
def new
#profile = Profile.new
#profile.build_address
end
def create
#profile = Profile.new profile_params
#profile.save
end
private
def profile_params
params.require(:profile).permit(address_attributes: [:country])
end
end
#app/views/profiles/new.html.erb
<%= form_for #profile do |f| %>
<%= f.fields_for :address do |a| %>
<%= a.text_field :country %>
<% end %>
<% end %>
This, with the model code from Vinay should get it working properly.
I have 3 models: Employers, Partners and Collaborations.
As an Employer, I want to add a record to my Partner model and to my Collaboration model to be able to indicate a collaboration between a Partner and a Employer. I therefore have the following columns in my database/tabels.
Models
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :partners, :through => :collaborations
end
class Partner < ActiveRecord::Base
has_many :collaborations
has_many :employers, :through => :collaborations
accepts_nested_attributes_for :collaborations
end
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
end
Tables
Collaborations
employer_id:integer
partner_id:integer
tarive:string
Partners
added_by:integer
name:string
Because I want to be able to add a Partner/Collaboration within 1 form, I use nested forms. So I can add a partner (name, etc) and a collaboration (tarive, etc) in one go.
My (simple_form) form looks like this (I have named_space resource).
Te reduce clutter, I removed as much HTML mark_up as I could, this is not the issue.
Form
/views/employer/partners/_form
= simple_form_for [:employer, #partner], html: { multipart: true } do |f|
Partner
= f.input :name, input_html: { class: 'form-control' }
= f.simple_fields_for :collaborations do |ff|
Tarive
= ff.input :tarive, input_html: { class: 'form-control' }
= f.button :submit, "Save"
My controller looks like
class Employer::PartnersController < ActionController::Base
def new
#partner = Partner.new
#partner.collaborations.build
end
def create
#partner = Partner.new(partner_params)
#partner.collaborations.build
#partner.added_by = current_employer.id
#partner.collaborations.employer_id = current_employer.employer_id
#partner.collaborations.partner_id = #partner.id
#partner.collaborations.added_by = current_employer.id
if #partner.save
redirect_to employer_partner_path(#partner), notice: "Succes!"
else
render 'new'
end
end
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:id, :employer_id, :partner_id, :tarive])
end
end
Problem
The problem/question I have is this. The attributes are assigned nicely and added in the model. But I want to add a employer_id as well, which I have in current_employer.employer.id (Devise). I do not want to work with hidden forms, just to avoid this issue.
I assigned 'parent' models always like #partner.added_by = current_employer.id and that works beautifully.
When I use:
#partner.collaborations.employer_id = current_employer.employer_id
I get an error, saying #partner.collaborations.employer_id is empty.
Question
How can I assign a variable to the nested_form (Collaboration) in my controller#create?
Or more specifically: how can I assign current_employer.employer_id to #partner.collaborations.employer_id?
There are several ways:
Merge the params
Deal with objects, not foreign keys
Personally, I feel your create method looks really inefficient. Indeed, you should know about fat model skinny controller - most of your associative logic should be kept in the model.
It could be improved using the following:
#app/controllers/employers/partners_controller.rb
class Employers::PartnersController < ApplicationController
def new
#partner = current_employer.partners.new #-> this *should* build the associated collaborations object
end
def create
#partner = current_employer.partners.new partner_params
#partner.save ? redirect_to(employer_partner_path(#partner), notice: "Succes!") : render('new')
end
private
def partner_params
params.require(:partner).permit(:id, :name, collaborations_attributes: [:tarive]) #when dealing with objects, foreign keys are set automatically
end
end
This would allow you to use:
#app/views/employers/partners/new.html.erb
= simple_form_for #partner do |f| #-> #partner is built off the current_employer object
= f.input :name
= f.simple_fields_for :collaborations do |ff|
= ff.input :tarive
= f.submit
... and the models:
#app/models/partner.rb
class Partner < ActiveRecord::Base
belongs_to :employer, foreign_key: :added_by
has_many :collaborations
has_many :employers, through: :collaborations
accepts_nested_attributes_for :collaborations
end
#app/models/collaboration.rb
class Collaboration < ActiveRecord::Base
belongs_to :employer
belongs_to :partner
belongs_to :creator, foreign_key: :added_by
before_create :set_creator
private
def set_creator
self.creator = self.employer_id #-> will probably need to change
end
end
#app/models/employer.rb
class Employer < ActiveRecord::Base
has_many :collaborations
has_many :employers, through: :collaborations
end
This may not give you the ability to set tarive, however if you cut down the manual declarations in your model, we should be able to look at getting that sorted.
The main thing you need to do is slim down your code in the controller. You're being very specific, and as a consequence, you're encountering problems like that which you mentioned.
I have a new form that creates an Item (all the codes are obviously simplified):
<%= simple_form_for #item do |f| %>
<%= f.input :brand_name %>
<%= f.button :submit %>
<% end %>
The current user will create an item and link it to a new or to an existing brand.
This field doesn't exist in the database; it'll be used as a way to associate all models. Hence, I create its getter and setter.
def Item < ActiveRecord::Base
belongs_to :user
belongs_to :brand
attr_accessible :brand_name
def brand_name
brand.try :name
end
def brand_name=(name)
if name.present?
brand = user.brands.find_or_initialize_by_name(name)
brand if brand.save
end
end
end
class ItemsController < ApplicationController
def new
#item = current_user.items.build
end
def create
#item = current_user.items.build(params[:item])
if #item.save
...
end
end
end
The problem is that when the form is submitted, I get this error, which lies in the product_name=() method. I've done some debugging through Rails' console and it goes all fine, but in the browser the setter method is called before the create action. That is, the record doesn't even have a user associated to it. I tried leaving the create method empty, for example, but nothing different happens.
undefined method `brands' for nil:NilClass
What is really weird is that this was working a couple of weeks ago (I've checked my git commits and the code is identical).
I though about calling the before_create callback, but there's no way to know which user should be linked.
UPDATE
I'm using Sorcery as the authentication handler. Everything always works fine, except for this create action.
class User < ActiveRecord::Base
authenticates_with_sorcery!
belongs_to :company
has_many :items
end
class Company < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :brands, dependent: :destroy
end