How can I associate an existing nested resource on create? - ruby-on-rails

I have this model:
class Product < ActiveRecord::Base
has_many :product_images, dependent: :destroy, autosave: true
accepts_nested_attributes_for :product_images, allow_destroy: true
end
I create a bunch of ProductImage models via ajax before saving a new Product. The ajax creates form inputs for the ProductImage :id, :featured and :_destroy attributes on product_images_attributes and I correctly see these params in my log:
"product" => {"name" => "Test", "product_images_attributes"=>{"0"=>{"id"=>"112", "featured" => "true", "_destroy"=>""}}}
In my controller I'm doing this in #create:
#product = Product.new(params.require(:product).permit!)
#product.save
When passing in those params I get this error when I try to assign the params:
ActiveRecord::RecordNotFound (Couldn't find ProductImage with ID=112 for Product with ID=)
The database shows that ProductImage with ID=112 exists with product_id=null as expected.
It all works when updating an existing Product of course.
How do I associate existing ProductImages with a new record on create using standard rails methods?

nested_attributes is not the way I recommend. It's very contreniant. I suggest to use form objects.
You can do something like this :
class ProductCreation
include ActiveModel::Model
attr_accessor :product_name, :product_image_ids
def save
images = product_image_ids.map {|id| Image.find(id) }
product = Product.new(name: product_name)
product.images = images
product.save
end
end
And use ProductCreation in your controllers and your views.

Related

How do i create a parent and child element at the same time rails

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
...

Update if exists, destroy if empty, create if doesn't exist on array form submit

I am attempting to update multiple records at once but coming to an issue with my validation for uniqueness scope.
When I create a record on a per-record-basis, it does what the title asks. But with the array, it doesn't.
Why?
Because the params don't pass the ID of the nested attribute to update.
Example of param difference of updating on a per record vs array:
Mutli update:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"==", "shop_product_ids"=>["42"], "shop_product"=>{"shop_product_print_files_attributes"=>{"0"=>{"print_location_id"=>"1", "image_file_id"=>"2"}, "1"=>{"print_location_id"=>"2", "image_file_id"=>"2"}, "2"=>{"print_location_id"=>"3", "image_file_id"=>""}, "3"=>{"print_location_id"=>"4", "image_file_id"=>""}, "4"=>{"print_location_id"=>"5", "image_file_id"=>""}, "5"=>{"print_location_id"=>"6", "image_file_id"=>""}}}, "commit"=>"Edit Checked"}
1 Record Update:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"==", "shop_product"=>{... "shop_product_print_files_attributes"=>{"0"=>{"print_location_id"=>"1", "image_file_id"=>"1", "id"=>"145"}, "1"=>{"print_location_id"=>"2", "image_file_id"=>"", "id"=>"151"}, "2"=>{"print_location_id"=>"3", "image_file_id"=>""}, "3"=>{"print_location_id"=>"4", "image_file_id"=>""}, "4"=>{"print_location_id"=>"5", "image_file_id"=>""}, "5"=>{"print_location_id"=>"6", "image_file_id"=>""}},"id"=>"42"}, "commit"=>"Sync", "id"=>"42"}
On the one record update, it passes the shop_product_print_file ID in order to update. On the multi, it doesn't and shouldn't.
Goal: To update if exists, create if it doesn't destroy if empty. I need to somehow do what I do in the model reject_if in my controller but I am boggled on how to.
Models:
class PrintLocation < ApplicationRecord
has_many :shop_products, through: :shop_product_print_files
has_many :shop_product_print_files
end
class ShopProductPrintFile < ApplicationRecord
validates :shop_product, uniqueness: { scope: :print_location }
belongs_to :shop_product
belongs_to :print_location
belongs_to :image_file
end
class ShopProduct < ApplicationRecord
has_many :shop_product_print_files
has_many :print_locations, through: :shop_product_print_files
accepts_nested_attributes_for :shop_product_print_files
...
accepts_nested_attributes_for :shop_product_print_files, reject_if: :reject_file, :allow_destroy => true
def reject_file(attributes)
if attributes[:image_file_id].blank?
if attributes[:id].present?
attributes.merge!({:_destroy => 1}) && false
else
true
end
end
end
end
class ImageFile < ApplicationRecord
# this is where users upload files to
belongs_to :user
has_many :shop_product_print_files
end
Controller methods handling this:
def edit_multiple
#image_files = ImageFile.where(user_id: current_user.id)
#shop_products = ShopProduct.find(params[:shop_product_ids])
#shop_product = ShopProduct.new
#shop_product.shop_product_print_files.build PrintLocation.all.map { |pl| { print_location: pl } }
end
def update_multiple
#shop_products = ShopProduct.find(params[:shop_product_ids])
#image_files = ImageFile.where(user_id: current_user.id)
#shop_products.reject! do |shop_product|
shop_product.update_attributes!(params[:shop_product].permit(:shop_product, shop_product_print_files_attributes: [ :id, :print_file, :print_location_id, :shop_product_id, :image_file_id ]).reject { |k,v| v.blank? })
end
if #shop_products.empty?
redirect_to '/'
else
#shop_product = ShopProduct.new(params[:shop_product])
render "edit_multiple"
end
end
When updating on a record-by-record basis, the :reject_file method handles my goal with the help of the SPPF passing through. But When doing this with the array, no SPPF ID passes through which gives me the error:
> ActiveRecord::RecordInvalid (Validation failed: Shop product print
> files shop product has already been taken):
Does anyone have any conclusions on how I can accomplish this?
I am omitting the form to update array to keep this short as the parameters should explain it enough since what I need is most likely either a controller conditional statement to achieve my goal .

Rails Dropdown has_many through

I can't save the id's in the join table (document_configuration).
I have tree models:
document.rb
belongs_to :languages
has_many :document_configurations
has_many :document_catalogs, through: :document_configurations
accepts_nested_attributes_for :document_catalogs
accepts_nested_attributes_for :document_configurations
document_catalog.rb
has_many :document_configurations
has_many :documents, through: :document_configurations
document_configuration.rb
belongs_to :document
belongs_to :document_catalog
So, I want to get a list of all document_catalog in my document_form So, when I create a new document I can include the corresponding catalog.
This is my form:
<div class="form-group">
<%= f.select :document_catalog_ids, DocumentCatalog.all.collect {|x| [x.name, x.id]}, {}%>
</div>
Is listing the catalogs as I want.
This is my controller:
def new
#document = Document.new
#document.document_catalogs.build
end
def document_params
params.require(:document).permit(:name, :description, :document_file,
:language_id, {:document_catalog_ids=>[]}) #I tried this too: :document_catalog_ids=>[] without the {}
end
I'm just getting this error: Unpermitted parameter: document_catalog_ids and I really need to save the document_id and document_catalog_id in document_configuration model.
Another thing is: I need to add anything else in my create, update and destroy methods?
Handling of Unpermitted Keys
By default parameter keys that are not explicitly permitted will be logged in the development and test environment. In other environments these parameters will simply be filtered out and ignored.
Additionally, this behaviour can be changed by changing the config.action_controller.action_on_unpermitted_parameters property in your environment files. If set to :log the unpermitted attributes will be logged, if set to :raise an exception will be raised.
Found this in a documentation from GitHub : https://github.com/rails/strong_parameters#handling-of-unpermitted-keys

Filling out an inherited mongoid document using nested attributes

Given the following models:
class Company
include Mongoid::Document
has_many :workers, autosave: true
accepts_nested_attributes_for :workers
attr_accessible :workers_attributes
end
class Worker
include Mongoid::Document
field :hours
attr_accessible :hours
belongs_to :company
end
class Manager < Worker
field :order
has_many :contributors, :class_name => "Worker"
attr_accessible :order, :contributors
end
class Contributor < Worker
field :task
belongs_to :manager, :class_name => "Worker"
attr_accessible :task
end
How does one create a manager in a company in the controller and view using nested attributes?
Here's my guess:
def new
#company = Company.new
#company.workers = [Manager.new]
end
def create
#company = Company.new params[:user]
if #company.save
redirect_to root_url, :notice => "Company with manager created."
else
render :new
end
end
= semantic_form_for #company do |f|
= f.semantic_fields_for :workers do |worker_fields|
= worker_fields.inputs do
= worker_fields.input :hours
= worker_fields.input :order
problem is the order field which specifically belongs to the manager is not persisting after the create. Also when the data is improperly filled there is an error:
undefined method `order' for #<Worker:0x0000000646f018> (ActionView::Template::Error)
So is there a way for nested attributes to handle inheritance in the models from mongoid?
The question is related to Can nested attributes be used in combination with inheritance? except instead of active record using mongoid.
Honestly, this is a paraphrasing of my code... the real code is more complex situation although i believe these are all of the relevant parts. If you have more questions ask.
UPDATE:
I changed the view to the following:
= semantic_form_for #company do |f|
- #company.workers.each do |worker|
- if worker._type == "Manager"
= f.semantic_fields_for :workers, worker do |worker_fields|
= worker_fields.inputs do
= worker_fields.input :hours
= worker_fields.input :order
I do not get the error anymore, however the nested attributes do not update the company object properly. The params are the following:
{"company"=> {"workers_attributes"=>{"0"=>{"hours"=>"30", "order" => "fish", "id"=>"4e8aa6851d41c87a63000060"}}}}
Again edited for brevity. So the key part is that there is a hash between "0" => {data for manager}. The workers data seems to be held in a hash. I would expect the data to look more like the following:
params = { company => {
workers_attributes => [
{ hours => "30", "order" => "fish" }
]}}
This is different because the workers data is held in an array instead of a hash. Is there another step to get the nested attributes to save properly?
Thanks
what version of Mongoid are you using? Because I don't think the use of refereneces_many is encouraged -- Not that that's related to your problem here, just wanted to probe what version you're using. In the doc on the gorgeous Mongoid.org, get this, I had to learn it the hard way, they say for Updating your records, you need to the autossave set to true. That's NOT accurate. You need it for even creating
so:
class Company
include Mongoid::Document
has_many :workers, :autossave => true # your money shot
accepts_nested_attributes_for :workers
attr_accessible :workers_attributes
end
ADDED:
I was re-reading your code, I spotted the following that might be the problem: Your Company model is set to has_many :workers and is set to accept nested attribbutes for Worker when changes come in, correct? And there is a field named Order in your Manager model which is subclassed from Worker. Yet you're having a form whose nested fields part is pointed at Worker not at Manager, the model that actually has the Order field. And that's obviously not enough, because Company isn't having_many :managers yet, you may need to set it to has_many :managers in the Company model as well.

How to access nested params

I would like to get some nested params. I have an Order that has many Items and these Items each have a Type. i would like to get the type_id parameter from the controllers create method.
#order = Order.new(params[:order])
#order.items.each do |f|
f.item_type_id = Item_type.find_by_name(f.item_type_id).id
end
The reason is that i want the user to be able to create new item_types in the view. When they do that i use an AJAX call add them to the db. When they post the form i get names of the item_type in the item_type_id parameter and i want to find the correct item_type and set the id to that
To access the nested fields from params do the following:
params[:order][:items_attributes].values.each do |item|
item[:type_id]
end if params[:order] and params[:order][:items_attributes]
Above solution will work ONLY if you have declared the correct associations and accepts_nested_attributes_for.
class Order < ActiveRecord::Base
has_many :items
accepts_nested_attributes_for :items, :allow_destroy => true
end
class Item < ActiveRecord::Base
belongs_to :order
end

Resources