I have a rails application with 3 models (Job, Order, Fulfillment). The relationship between the models is:
Order has one Job
Fulfillment has many Jobs
The Jobs table has the following Schema:
create_table "jobs", force: :cascade do |t|
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "order_id"
t.integer "fulfillment_id"
end
Lets say I have an Order o and a Fulfillment f
o.job = Job.create!(:order_id => o.id, :fulfillment_id => f.id)
This creates a job associated to o and f.
But if I try to execute the same statement again, I get an ActiveRecord::RecordNotSaved error but there exists a new Job entry in the table.
The same error is seen if I try to create the Job via the following method:
o.create_job!(:fulfillment_id => f.id)
Stack Trace:
(0.1ms) begin transaction
Order Load (0.2ms) SELECT "orders".* FROM "orders" WHERE "orders"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]]
Fulfillment Load (0.2ms) SELECT "fulfillments".* FROM "fulfillments" WHERE "fulfillments"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]]
Job Create (0.4ms) INSERT INTO "jobs" ("created_at", "updated_at", "order_id", "fulfillment_id") VALUES (?, ?, ?, ?) [["created_at", "2018-07-23 06:27:33.519957"], ["updated_at", "2018-07-23 06:27:33.519957"], ["order_id", 4], ["fulfillment_id", 1]]
(0.8ms) commit transaction
(0.1ms) begin transaction
(0.7ms) rollback transaction
Traceback (most recent call last):
1: from (irb):82
ActiveRecord::RecordNotSaved (Failed to remove the existing associated job. The record failed to save after its foreign key was set to nil.)
Why is this possible? If the association is being breached, and the transaction has been rolled-back, the table entry should have been deleted?
When exactly is Rails checking for failing association? How do I rescue from this error without having to manually delete the incorrect entry in the table?
It's happen due to your has_one associations.
will fail because it is trying to delete the original order, but that one can't
be deleted because there is a validation on job_id.
This is because prior to deleting, the foreign key of the target association is set to nil and a save operation is performed on the target.
Please refer https://github.com/rails/rails/issues/17325
Related
I seem to run into a some kind of circular relationships that the two solutions in the gem's documentation won't solve for me. See the example below. Is this meant to be done differently?
One would argue that because one object could not really be persisted without the other they ought to just be one model. I think it's better to extract all the logic regarding authentication to it's seperate model in order not to bloat the user. Most of the time credential stuff is only used when creating sessions, whereas the user is used all the time.
create_table "credentials", force: :cascade do |t|
t.bigint "user_id", null: false
...
t.index ["user_id"], name: "index_credentials_on_user_id"
end
add_foreign_key "credentials", "users"
class Credential < ApplicationRecord
belongs_to :user, inverse_of: :credential
end
class User < ApplicationRecord
has_one :credential, inverse_of: :user
validates :credential, presence: true
end
Fabricator(:user_base, class_name: :user)
Fabricator(:user, from: :user_base) do
credential
end
Fabricator(:credential) do
user(fabricator: :user_base)
end
irb(main):001:0> Fabricate(:user)
TRANSACTION (0.1ms) BEGIN
TRANSACTION (0.1ms) ROLLBACK
Traceback (most recent call last):
1: from (irb):1:in `<main>'
ActiveRecord::RecordInvalid (Validation failed: Credential can't be blank)
irb(main):002:0> Fabricate(:credential)
Traceback (most recent call last):
2: from (irb):1:in `<main>'
1: from (irb):2:in `rescue in <main>'
ActiveRecord::RecordInvalid (Validation failed: Credential can't be blank)
irb(main):003:0> Fabricate.build(:user).save
TRANSACTION (0.2ms) BEGIN
User Create (0.8ms) INSERT INTO "users" ("email", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["email", "fake#mail.com"], ["created_at", "2021-05-29 18:19:09.312429"], ["updated_at", "2021-05-29 18:19:09.312429"]]
Credential Create (0.9ms) INSERT INTO "credentials" ("user_id", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["user_id", 19], ["created_at", "2021-05-29 18:19:09.319411"], ["updated_at", "2021-05-29 18:19:09.319411"]]
TRANSACTION (41.2ms) COMMIT
=> true
The way you're solving this would certainly work. The way I normally recommend people solve this is by overriding the inverse relationships. ActiveRecord will do the right thing in this case.
Fabricator(:user) do
credential { Fabricate.build(:credential, user: nil) }
end
Fabricator(:credential) do
user { Fabricate.build(:user, credential: nil) }
end
The model that has the foreign key, in this case Credential, is the one that is required to have a user_id value to be persisted. This means that there needs to be a user (either in memory or in the database) before creating a credential. This is the reason why using build works for you.
If the user exists in memory, rails will be smart enough to create that one first before creating the credential. It seems to me that when you use build with Fabricate it’s initializing a user and a credential so when the user is saved, it saves the credential with the newly created user.
Note that the docs use this syntax for belongs_to, not has_one. It seems that you may need to refer to the callbacks section of the documentation to fix this issue.
I am trying to assign parent as superadmin. when i run the command parent.toggle!(superadmin) it doesnt assign the parent as superadmin.
I am not able to locate the error
irb(main):004:0> parent.toggle!(:superadmin)
(0.2ms) begin transaction
Parent Update (0.6ms) UPDATE "parents" SET "updated_at" = ?, "superadmin" = ? WHERE "parents"."id" = ? [["updated_at", "2018-06-20 17:50:31.273816"], ["superadmin", 1], ["id", 1]]
Parent Update (0.2ms) UPDATE "parents" SET "updated_at" = ?, "superadmin" = ? WHERE "parents"."id" = ? [["updated_at", "2018-06-20 17:50:31.280343"], ["superadmin", 0], ["id", 1]]
(1.8ms) commit transaction
=> true
it seems that the transaction is beign done, remember that toggle doesn't assign true, it changes the false to true a viceversa, depending on the current value that it has.
on the example you are sending, it's changed to true at first but then again to false. you are doing it right talking about code. but maybe there is a callback that changes again the value to false before saving it. Check on your model if there are callbacks or if you have some rules for this.
On my test I'm seeing that after_update is been called after calling FactoryBot.create(:object). Is it normal? As far as I know, it should be called only when a record gets updated, no?
I can see someone reporting this as a bug, with a good explanation here.
To take the essentials from this, if your factory is adding an association (this is an assumption at this stage - if you could add a little more to your question, that'd be great), the code runs as follows:
Example factory
FactoryGirl.create(
:user,
:account => FactoryGirl.create(:account)
)
How this is invoked:
account = Account.new
account.save! # Since this is Ruby, it'll evaluate this line as part of the hash first, before creating the user
user = User.new
user.account = account
user.save! # The hash has been evaluated and we're assigning the account created from the hash
So, if you have an association in there, the account, in this case, would be created, then updated as the association is saved.
To setup your factory to overcome this, you can use the following:
factory :user do
factory :user_with_account do
after_create do |user|
FactoryGirl.create(:account, :user => user)
end
end
end
factory :account do
user
end
How does that apply to your setup? Have a shot and see if it provides a solution - let me know how you get on :)
after_update will only be called when the object is updated, however if your factory has associations or after_create actions, these will often cause the model to be updated, causing after_update to be triggered.
An example, using ActiveRecord 5:
class Client < ApplicationRecord
after_create :ping
after_update :pong
def ping
logger.info("---> after_create HOOK CALLED")
end
def pong
logger.info("---> after_update HOOK CALLED")
end
end
Creating and updating the object act as expected:
c = Client.create!(name: "test")
# (0.4ms) BEGIN
# Client Create (1.4ms) INSERT INTO "clients" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "test"], ["created_at", "2018-05-24 17:06:24.076085"], ["updated_at", "2018-05-24 17:06:24.076085"]]
# ---> after_create HOOK CALLED
# (4.0ms) COMMIT
c.update! name: "test2"
# (0.8ms) BEGIN
# Client Update (2.3ms) UPDATE "clients" SET "name" = $1, "updated_at" = $2 WHERE "clients"."id" = $3 [["name", "test2"], ["updated_at", "2018-05-24 17:06:36.525448"], ["id", "a3d49153-2f25-48c3-8319-61c2fb6ea173"]]
# ---> after_update HOOK CALLED
# (0.9ms) COMMIT
]
And FactoryBot behaves the same:
FactoryBot.create(:client)
# (1.2ms) BEGIN
# Client Create (0.9ms) INSERT INTO "clients" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "Montana tigers"], ["created_at", "2018-05-24 17:11:57.138995"], ["updated_at", "2018-05-24 17:11:57.138995"]]
# ---> after_create HOOK CALLED
# (1.1ms) COMMIT
I have a single Users table with roles defined using enums in user.rb:
enum role: { staff: 0, clinician: 1 }
A staff user can create a patient record. That staff user who creates the patient record may be that patient's staff clinician, or they may not be, in which case I have a dropdown form that gives options for select of all stuff users. (The clinician user role is for outside clinicians - they are not involved)
I have a patients table in which I have user.id, which I intend to use to store the staff user id who created the patient, and staff_clinician_id, which I
intend to use to store the id of the patient's doctor (who will also be a staff user - confusing I know). Here's my patients schema:
create_table "patients", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "age"
t.integer "staff_clinician_id"
t.integer "user_id"
t.index ["staff_clinician_id"], name: "index_patients_on_staff_clinician_id"
t.index ["user_id"], name: "index_patients_on_user_id"
Then in my patients controller I've permitted staff_clinician_id and user_id:
def patient_params
params.require(:patient).permit(:age, :staff_clinician_id, :user_id, insurance_ids: [], gender_ids: [], concern_ids: [], race_ids: [])
end
and in the Patient model I've created this relationship:
has_one :staff_clinician, through: :users
Here is my form:
<%= select_tag "staff_clinician_id", options_from_collection_for_select(User.where(role:"staff"), "id", "name"), prompt: "Select this patient's clinician" %>
When I submit a new patient, the server says:
Started POST "/patients" for ::1 at 2017-09-25 14:16:44 -0400
Processing by PatientsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"[FILTERED]", "patient"=>{"gender_ids"=>["1"], "race_ids"=>["1"], "insurance_ids"=>["1"], "concern_ids"=>["31"], "age"=>"243"}, "staff_clinician_id"=>"5", "commit"=>"Post"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = ? ORDER BY "users"."id" ASC LIMIT ? [["remember_token", "3e607ec61e623710c58c42a0d313395439f82a82"], ["LIMIT", 1]]
Insurance Load (0.2ms) SELECT "insurances".* FROM "insurances" WHERE "insurances"."id" = 1
Gender Load (0.1ms) SELECT "genders".* FROM "genders" WHERE "genders"."id" = 1
Concern Load (0.2ms) SELECT "concerns".* FROM "concerns" WHERE "concerns"."id" = 31
Race Load (0.1ms) SELECT "races".* FROM "races" WHERE "races"."id" = 1
(0.0ms) begin transaction
Gender Exists (0.2ms) SELECT 1 AS one FROM "genders" WHERE "genders"."name" = ? AND ("genders"."id" != ?) LIMIT ? [["name", "Female"], ["id", 1], ["LIMIT", 1]]
Race Exists (0.1ms) SELECT 1 AS one FROM "races" WHERE "races"."name" = ? AND ("races"."id" != ?) LIMIT ? [["name", "American Indian or Alaska Native"], ["id", 1], ["LIMIT", 1]]
SQL (0.3ms) INSERT INTO "patients" ("created_at", "updated_at", "age", "user_id") VALUES (?, ?, ?, ?) [["created_at", 2017-09-25 18:16:44 UTC], ["updated_at", 2017-09-25 18:16:44 UTC], ["age", 243], ["user_id", 21]]
SQL (0.1ms) INSERT INTO "genders_patients" ("gender_id", "patient_id") VALUES (?, ?) [["gender_id", 1], ["patient_id", 7]]
Gender Exists (0.1ms) SELECT 1 AS one FROM "genders" WHERE "genders"."name" = ? AND ("genders"."id" != ?) LIMIT ? [["name", "Female"], ["id", 1], ["LIMIT", 1]]
SQL (0.1ms) INSERT INTO "concerns_patients" ("concern_id", "patient_id") VALUES (?, ?) [["concern_id", 31], ["patient_id", 7]]
SQL (0.1ms) INSERT INTO "insurances_patients" ("insurance_id", "patient_id") VALUES (?, ?) [["insurance_id", 1], ["patient_id", 7]]
SQL (0.2ms) INSERT INTO "patients_races" ("race_id", "patient_id") VALUES (?, ?) [["race_id", 1], ["patient_id", 7]]
Race Exists (0.1ms) SELECT 1 AS one FROM "races" WHERE "races"."name" = ? AND ("races"."id" != ?) LIMIT ? [["name", "American Indian or Alaska Native"], ["id", 1], ["LIMIT", 1]]
(10.5ms) commit transaction
Redirected to http://localhost:3000/referral_requests/new?patient_id=7
Completed 302 Found in 172ms (ActiveRecord: 17.5ms)
but when I do Patient.last in console, it hasn't saved the staff_clinician_id. it is nil
What am I doing wrong? Any help appreciated!
Your select tag should be named patient[staff_clinician_id], not staff_clinician_id.
<%= select_tag "patient[staff_clinician_id]", options_from_collection_for_select(User.where(role:"staff"), "id", "name"), prompt: "Select this patient's clinician" %>
If you use the object-based form builder, you can use the shorthand:
<% form_for #patient do |f| %>
...
<%= f.select :staff_clinician_id ... %>
...
<% end %>
select and select_tag are used in very different contexts.
I'm trying to create many copies of a message to users:
users.each do |user|
new_message = message.clone
new_message.to = user
new_message.save!
end
However, this doesn't work after the 1st user. Here's part of the error message:
SQL (0.7ms) INSERT INTO "messages" ("content", "to_id") VALUES (?, ?) [["content", "abc"], ["to_id", 1]]
(2.5ms) commit transaction
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 LIMIT 1
(0.0ms) begin transaction
SQL (0.5ms) INSERT INTO "messages" ("content", "id", "to_id") VALUES (?, ?, ?) [["content", "abc"], ["id", 6], ["to_id", 2]]
SQLite3::ConstraintException: PRIMARY KEY must be unique: INSERT INTO "messages" ("content", "id", "to_id") VALUES (?, ?, ?)
(0.0ms) rollback transaction
Completed 500 Internal Server Error in 130.1ms
ActiveRecord::StatementInvalid (SQLite3::ConstraintException: PRIMARY KEY must be unique: INSERT INTO "messages" ("content", "id", "to_id") VALUES (?, ?, ?)):
As you can see, the problem is that the 2nd time the loop runs, message.clone must contain the id of the previous new_message.
What is the proper way to clone message records for many users? I can, of course, manually copy each attribute (I have a lot more than just content and to_id) into a fresh new Message object. But I'd like to know if there's a better way.
You can use dup, with which the id, created_at and updated_at are set to nil. This will ensure that Primary Key exception is not thrown, however if there are other unique validations, they will fail. You'd have to handle them separately.
users.each do |user|
new_message = message.dup
new_message.to = user
new_message.save!
end