How to clone models in Ruby on Rails/ActiveRecord? - ruby-on-rails

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

Related

Rails creates entry in table after failing to save because of associations

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

Rspec's expect change count not working

Here I'm testing the changes in current_user.messages.count after the current user sends a valid message. Here's my code:
spec
scenario 'adds to their messages', js: true do
expect { find('#message_content').send_keys(:enter) }.to \
change(current_user.messages, :count).by(1)
end
test.log
# ...
ConversationChannel is transmitting the subscription confirmation
ConversationChannel is streaming from conversation_channel_1
(0.6ms) SELECT COUNT(*) FROM "messages" WHERE "messages"."user_id" = $1 [["user_id", 1]]
ConversationChannel#send_message({"content"=>"foobar\n", "conversation_id"=>"1"})
(0.3ms) BEGIN
(0.9ms) SELECT COUNT(*) FROM "messages" WHERE "messages"."user_id" = $1 [["user_id", 1]]
Conversation Load (1.6ms) SELECT "conversations".* FROM "conversations" WHERE "conversations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.7ms) SELECT "users"."id" FROM "users" INNER JOIN "user_conversations" ON "users"."id" = "user_conversations"."user_id" WHERE "user_conversations"."conversation_id" = $1 [["conversation_id", 1]]
SQL (1.0ms) INSERT INTO "messages" ("content", "user_id", "conversation_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["content", "foobar\n"], ["user_id", 1], ["conversation_id", 1], ["created_at", "2018-01-29 11:27:13.095277"], ["updated_at", "2018-01-29 11:27:13.095277"]]
Finished "/cable/" [WebSocket] for 127.0.0.1 at 2018-01-29 19:27:13 +0800
ConversationChannel stopped streaming from conversation_channel_1
(0.2ms) BEGIN
(58.8ms) COMMIT
(16.7ms) ALTER TABLE "schema_migrations" DISABLE TRIGGER ALL;ALTER TABLE "ar_internal_metadata" DISABLE TRIGGER ALL;ALTER TABLE "conversations" DISABLE TRIGGER ALL;ALTER TABLE "messages" DISABLE TRIGGER ALL;ALTER TABLE "user_conversations" DISABLE TRIGGER ALL;ALTER TABLE "users" DISABLE TRIGGER ALL
Rendered messages/_message.html.erb (0.6ms)
[ActionCable] Broadcasting to conversation_channel_1: {:message=>"<p>User 1: foobar\n</p>\n"}
# ...
The spec fails expected #count to have changed by 1, but was changed by 0 even though in the log shows INSERT INTO actually happen.
This doesn't work because you're not waiting long enough for the message addition to actually occur. send_keys returns as soon as the browser has been sent the key event, but knows nothing at all about any request/action triggered by that key press in the browser. This is why direct DB access tests are generally a bad idea in feature/system tests (which should generally just test user visible changes/interactions) and make more sense as request or controller.
That being said you could fix this by just sleeping after sending the key, but a better solution is to use one of the Capybara provided matchers (have waiting/retrying behavior) to synchronize the test.
scenario 'adds to their messages', js: true do
expect do
find('#message_content').send_keys(:enter) }
expect(page).to have_css(...) # check for whatever visible change on the page indicates the action triggered by send_keys has completed
end.to change { current_user.reload.messages.count }.by(1)
end
Note: This test is also very simple for a feature test. It's okay to have multiple expectations in a feature test since it's really meant to test a whole user interaction with a specific feature of your app. You might want to look at combining this test with other tests of the same part of your app.
Try to write :
change{current_user.messages, :count}.by(1)
with {}

Rails: Issue assocating a record with a user who did not create that record

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.

Why do I get a 500 error (undefined method 'to_model' on save of duplicated Ruby on Rails Model

I'm new to rails and still fuzzy on some things but here goes.
I am sending json post request to /microposts/10/duplicate.json to duplicate a micropost.
The route goes to microposts#duplicate
The Controller action is:
def duplicate
micropost = current_user.microposts.find(params[:id])
new_micropost = micropost.dup
respond_with new_micropost.save
end
The server logs for the request is:
Started POST "/microposts/15/duplicate.json" for 180.181.247.76 at 2015-09-06 11:00:38 +0000
Processing by MicropostsController#duplicate as JSON
Parameters: {"_json"=>15, "id"=>"15", "micropost"=>{}}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 2]]
Micropost Load (0.3ms) SELECT "microposts".* FROM "microposts" WHERE "microposts"."user_id" = ? AND "microposts"."id" = ? ORDER BY "microposts"."created_at" DESC LIMIT 1 [["user_id", 2], ["id", 15]]
(0.1ms) begin transaction
SQL (0.4ms) INSERT INTO "microposts" ("content", "user_id", "title", "link", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?) [["content", "RLCPYIIG"], ["user_id", 2], ["title", "herp"], ["link", "snerp"], ["created_at", "2015-09-06 11:00:38.576553"], ["updated_at", "2015-09-06 11:00:38.576553"]]
(11.7ms) commit transaction
Completed 500 Internal Server Error in 28ms (ActiveRecord: 12.8ms)
NoMethodError (undefined method `to_model' for true:TrueClass):
app/controllers/microposts_controller.rb:23:in `duplicate'
So the micropost is saved but I don't know why I get the 500 error.
Also, should I be using .require and .permit here? The only thing in params from the front end should be the id.
Any help would be appreciated.
I was recently getting a similar error when using respond_with.
Turned out I was just missing the corresponding view for my action when requesting a HTML response.
Unfortunately the error was rather cryptic with..
undefined method `to_model' for #<Array:0x007fc7c59ada50> Did you mean? to_xml
TLDR: Check you have a corresponding view.
Hope this helps someone.
Figured it out. I should be using PUT not POST here.

action update only last object

From last two days i try expect whats going on in my app. This is my problem:
My model:
class User
has_many :orders
---
class Order
belongs_to :user
has_and_belongs_to_many :contributors, class_name: 'User'
Situation:
User can create order, next for this order can add contributors(other users) with
<%= collection_check_boxes(:order, :contributor_ids, #prac, :id, :to_s) %>
After that in my custom action in OrdersController i have something like this:
#order.contributors.each do |u|
u.orders << #order
end
Here i want add this order to list of contributors(users) orders. Unfortunately this work only for last contributor(user). Below log from my console:
Started PATCH "/orders/282/zatwierdz" for 127.0.0.1 at 2014-07-01 08:48:59 +0200
Processing by OrdersController#zatwierdz as HTML
Parameters: {"authenticity_token"=>"hxWUnazKLoS9t8bVmOAMeIxdo/KYuO33bUeuQZYgeV
k=", "id"=>"282"}
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 32 OR
DER BY "users"."id" ASC LIMIT 1
Role Load (0.0ms) SELECT "roles".* FROM "roles" WHERE "roles"."id" = ? LIMI
T 1 [["id", 4]]
Order Load (0.0ms) SELECT "orders".* FROM "orders" WHERE "orders"."id" = ?
ORDER BY created_at DESC LIMIT 1 [["id", 282]]
OrderItem Exists (0.0ms) SELECT 1 AS one FROM "order_items" WHERE "order_it
ems"."order_id" = ? LIMIT 1 [["order_id", 282]]
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "orders" SET "status" = ?, "updated_at" = ? WHERE "orders"
."id" = 282 [["status", 1], ["updated_at", "2014-07-01 06:48:59.486472"]]
(62.5ms) commit transaction
User Load (0.0ms) SELECT "users".* FROM "users" INNER JOIN "orders_users" ON
"users"."id" = "orders_users"."user_id" WHERE "orders_users"."order_id" = ? [["
order_id", 282]]
(0.0ms) begin transaction
(0.0ms) commit transaction
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "orders" SET "updated_at" = ?, "user_id" = ? WHERE "orders
"."id" = 282 [["updated_at", "2014-07-01 06:48:59.564595"], ["user_id", 33]]
(78.1ms) commit transaction
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "orders" SET "updated_at" = ?, "user_id" = ? WHERE "orders
"."id" = 282 [["updated_at", "2014-07-01 06:48:59.642719"], ["user_id", 35]]
(78.1ms) commit transaction
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "orders" SET "updated_at" = ?, "user_id" = ? WHERE "orders
"."id" = 282 [["updated_at", "2014-07-01 06:48:59.720842"], ["user_id", 36]]
(109.4ms) commit transaction
(0.0ms) begin transaction
SQL (0.0ms) UPDATE "orders" SET "updated_at" = ?, "user_id" = ? WHERE "orders
"."id" = 282 [["updated_at", "2014-07-01 06:48:59.830215"], ["user_id", 37]]
(78.1ms) commit transaction
Redirected to http://localhost:3000/orders/282
Completed 302 Found in 437ms (ActiveRecord: 406.2ms)
I try use this action with callbacks and from model methods but nothing happened. Maybe better for this aprouch is to use other associations? Anny sugestions welcome.
Thx for help.
By doing:
#order.contributors.each do |u|
u.orders << #order
end
You are adding your order to the user.orders relationship, which you define as a many (Order) to one (User). The "has_many" orders on the User class is just the other side of the "belongs_to" user on the Order class. So yes, as per your model, an Order has one and only one user.
Now, if you want to be able to navigate your has_and_belongs_to_many relation from the User side, you need to define it there too.

Resources