Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When updating has_many relation via attributes it fails with either ActiveRecord::RecordNotFound or ActiveRecord::RecordNotUnique #468

Closed
ibotty opened this issue Dec 11, 2018 · 11 comments

Comments

@ibotty
Copy link

ibotty commented Dec 11, 2018

record.update({"join_table_attributes" => {"0" => { "record_id" => 1, "other_record_id" => 2, "preference"=> "interest"}}})

it fails with

ActiveRecord::RecordNotUnique (Mysql2::Error: Duplicate entry '1-2' for key 'record_other_record_unique_ix': INSERT INTO `records_other_records` (`record_id`, `other_record_id`, `preference`) VALUES (1,2, 'interest))

When including a id key as generated by formtastic's nested forms, i.e.

record.update({"join_table_attributes" => {"0" => { "record_id" => 1, "other_record_id" => 2, "preference"=> "interest", "id" => "1 2"}}})

it fails with

ActiveRecord::RecordNotFound (Couldn't find RecordsOtherRecords with ID=1 2 for Record with ID=1

when using "id" => "1,2" or "id" => [1,2] I get RecordNotUnique again.

Am I missing something on how composite_primary_keys is supposed to be used?

@cfis
Copy link
Contributor

cfis commented Dec 14, 2018

Hard to tell. Did you setup the model correctly? What's the traceback?

@ibotty
Copy link
Author

ibotty commented Dec 14, 2018

The traceback is not very enlightening I guess.

I think I set up the model correctly? It does work when I do (paraphrased) RecordsOtherRecords.find([1,2]).

bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/nested_attributes.rb:596:in `raise_nested_attributes_record_not_found!'
bundle/ruby/2.5.0/gems/composite_primary_keys-11.1.0/lib/composite_primary_keys/nested_attributes.rb:79:in `block in assign_nested_attributes_for_collection_association'
bundle/ruby/2.5.0/gems/composite_primary_keys-11.1.0/lib/composite_primary_keys/nested_attributes.rb:54:in `each'
bundle/ruby/2.5.0/gems/composite_primary_keys-11.1.0/lib/composite_primary_keys/nested_attributes.rb:54:in `assign_nested_attributes_for_collection_association'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/nested_attributes.rb:373:in `organisation_preferences_attributes='
bundle/ruby/2.5.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:51:in `public_send'
bundle/ruby/2.5.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:51:in `_assign_attribute'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/attribute_assignment.rb:31:in `block in assign_nested_parameter_attributes'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/attribute_assignment.rb:31:in `each'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/attribute_assignment.rb:31:in `assign_nested_parameter_attributes'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/attribute_assignment.rb:25:in `_assign_attributes'
bundle/ruby/2.5.0/gems/activemodel-5.2.1/lib/active_model/attribute_assignment.rb:35:in `assign_attributes'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/persistence.rb:427:in `block in update'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/transactions.rb:387:in `block in with_transaction_returning_status'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:254:in `block in transaction'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/transaction.rb:239:in `block in within_new_transaction'
/opt/rh/rh-ruby25/root/usr/share/ruby/monitor.rb:226:in `mon_synchronize'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/transaction.rb:236:in `within_new_transaction'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/connection_adapters/abstract/database_statements.rb:254:in `transaction'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/transactions.rb:212:in `transaction'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/transactions.rb:385:in `with_transaction_returning_status'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/persistence.rb:426:in `update'
app/controllers/organisations_controller.rb:27:in `update'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/abstract_controller/base.rb:194:in `process_action'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_controller/metal/rendering.rb:30:in `process_action'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/abstract_controller/callbacks.rb:42:in `block in process_action'
bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/callbacks.rb:132:in `run_callbacks'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/abstract_controller/callbacks.rb:41:in `process_action'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_controller/metal/rescue.rb:22:in `process_action'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'
bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/notifications.rb:168:in `block in instrument'
bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/notifications/instrumenter.rb:23:in `instrument'
bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/notifications.rb:168:in `instrument'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_controller/metal/instrumentation.rb:32:in `process_action'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'
bundle/ruby/2.5.0/gems/activerecord-5.2.1/lib/active_record/railties/controller_runtime.rb:24:in `process_action'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/abstract_controller/base.rb:134:in `process'
bundle/ruby/2.5.0/gems/actionview-5.2.1/lib/action_view/rendering.rb:32:in `process'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_controller/metal.rb:191:in `dispatch'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_controller/metal.rb:252:in `dispatch'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/routing/route_set.rb:34:in `serve'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/journey/router.rb:52:in `block in serve'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/journey/router.rb:35:in `each'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/journey/router.rb:35:in `serve'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/routing/route_set.rb:840:in `call'
bundle/ruby/2.5.0/gems/remotipart-1.4.2/lib/remotipart/middleware.rb:32:in `call'
bundle/ruby/2.5.0/gems/warden-1.2.8/lib/warden/manager.rb:36:in `block in call'
bundle/ruby/2.5.0/gems/warden-1.2.8/lib/warden/manager.rb:34:in `catch'
bundle/ruby/2.5.0/gems/warden-1.2.8/lib/warden/manager.rb:34:in `call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/tempfile_reaper.rb:15:in `call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/etag.rb:25:in `call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/conditional_get.rb:38:in `call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/head.rb:12:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/http/content_security_policy.rb:18:in `call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:232:in `context'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/session/abstract/id.rb:226:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/cookies.rb:670:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'
bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/callbacks.rb:98:in `run_callbacks'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/callbacks.rb:26:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/executor.rb:14:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'
bundle/ruby/2.5.0/gems/rails_semantic_logger-4.3.2/lib/rails_semantic_logger/rack/logger.rb:43:in `call_app'
bundle/ruby/2.5.0/gems/rails_semantic_logger-4.3.2/lib/rails_semantic_logger/rack/logger.rb:28:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/request_id.rb:27:in `call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/method_override.rb:22:in `call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/runtime.rb:22:in `call'
bundle/ruby/2.5.0/gems/activesupport-5.2.1/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/executor.rb:14:in `call'
bundle/ruby/2.5.0/gems/actionpack-5.2.1/lib/action_dispatch/middleware/static.rb:127:in `call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/sendfile.rb:111:in `call'
bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/engine.rb:524:in `call'
bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/railtie.rb:190:in `public_send'
bundle/ruby/2.5.0/gems/railties-5.2.1/lib/rails/railtie.rb:190:in `method_missing'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:68:in `block in call'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `each'
bundle/ruby/2.5.0/gems/rack-2.0.6/lib/rack/urlmap.rb:53:in `call'
bundle/ruby/2.5.0/gems/puma-3.12.0/lib/puma/configuration.rb:225:in `call'
bundle/ruby/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:658:in `handle_request'
bundle/ruby/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:472:in `process_client'
bundle/ruby/2.5.0/gems/puma-3.12.0/lib/puma/server.rb:332:in `block in run'
bundle/ruby/2.5.0/gems/puma-3.12.0/lib/puma/thread_pool.rb:133:in `block in spawn_thread'

@jasonperrone
Copy link

jasonperrone commented Jan 19, 2019

Same issue for me. I have this model which does NOT have a composite primary key

class BillOfLading < ApplicationRecord
  self.table_name = 'bol_header'
  self.primary_key = 'bol_number'

  has_many :bol_customer_details, foreign_key: 'bol_id', primary_key: 'bol_id'
  accepts_nested_attributes_for :bol_customer_details
end

Its children do however.

class BolCustomerDetail < ApplicationRecord
  self.table_name = 'bol_detail_customer'
  self.primary_keys = :bol_id, :customer_order_number

  belongs_to :bill_of_lading, inverse_of: :bol_customer_details
end

I get the same error as in the first comment with the not found.

@jasonperrone
Copy link

jasonperrone commented Jan 19, 2019

Well, I managed to work around it. Here's how:

First of all, on the child model, put required: false on the belongs_to.

Secondly, and most importantly, Rails is simply not creating the hidden id attribute correctly. In other words, the nested parameters would normally pass the id of the child record to the controller so update_attributes on the parent would work. Rails is setting the value as "id1 id2" in the hidden. Like this:

<input type="hidden" id="whatever" name="whatever" value="id1 id2"/>

What I did was I changed my fields_for to no longer produce the id, with include_id: false

f.fields_for :bol_customer_details, include_id: false do |child_form|

and then construct it yourself, EXCEPT, instead of a space between the composite keys, use a comma, so:

<input type="hidden" id="whatever" name="whatever" value="id1,id2"/>

Composite keys now correctly uses the id parameter for the lookup.

Hope this helps someone else.

@cfis
Copy link
Contributor

cfis commented Mar 15, 2019

Thanks for digging, I actually saw something similar recently and had to fix a method in active view that generated that id field. In the end I reverted the change in the project I was working on. I guess this could be added to CPK, although then CPK needs a reference to ActiveView for a one line code change.

@codeodor
Copy link
Collaborator

Do you remember what method it's calling? I'd have assumed #to_param on the model, but I guess not?

@cfis
Copy link
Contributor

cfis commented May 9, 2021

Sorry, its been a long time at this point and I don't remember. I don't really know what to do about this. We could fix ActiveView but do we really want to pull that in as dependency? I guess we could have an overridden method but not actually load it, and then its up to the user to load it.

@codeodor - Since I'm not using Rails anymore I'll leave it up to you if you want to go down that route or close this a known issue but one that won't be fixed (would be good to document it though).

@bf4
Copy link
Contributor

bf4 commented Sep 11, 2022

Given the age of this issue, it's likely stale and should be closed without a test case that can make a current version fail

@cfis
Copy link
Contributor

cfis commented Oct 15, 2022

Yes, agreed.

@cfis cfis closed this as completed Oct 15, 2022
@dvergeylen
Copy link

I can confirm the issue is still present in 13.0.4 (not the latest version at this time of writing).

@bf4
Copy link
Contributor

bf4 commented Mar 1, 2023

@dvergeylen if you have a test case, seems worth opening a new issue and linking this one

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants