Reputation: 114
I'm using rails 5 and I have an ActiveRecord 'Report' with a one-to-many relationship called dosages.
If on a new report:
report.dosages.size # Returns 0
report.dosages.build
report.dosages.size # 1, correct
report.dosages.first.destroy
report.dosages.size # Still 1 !
I understand that it set dosage as 'destroyed' but it's possible to actually remove it from the list of relationship? (I can't save the report on db until later)
Upvotes: 2
Views: 6294
Reputation: 1701
TL;DR: this will do what you need:
report = Report.first
dosage = report.dosages.build
report.dosages.size // 1
to_delete_dosage = report.dosages.first
report.dosages.delete(to_delete_dosage)
report.dosages.size // 0
For more information, check the docs for ActiveRecord_Associations_CollectionProxy::delete
First, ActiveRecord::Relation#build
is an alias for ActiveRecord::Relation#new
:
New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved (pass a hash with key names matching the associated table column names). In both instances, valid attribute keys are determined by the column names of the associated table – hence you can’t have attributes that aren’t part of the table columns.
if you want the first element deleted you would have to persist it to the database, otherwise rails does exactly what you experienced, set the destroyed flag.
Try the following sequence:
report = Report.first
dosage = report.dosages.build
dosage.save
report.dosages.size # 1
report.dosages.first.destroy
report.dosages.size # 0
I set up the equivalent to your question and this is the output:
Your scenario:
2.4.0 :007 > r.dosages.build
=> #<Dosage id: nil, title: nil, created_at: nil, updated_at: nil, report_id: 1>
2.4.0 :008 > r.dosages.size
=> 1
2.4.0 :009 > r.dosages.first.destroy
(0.1ms) begin transaction
(0.0ms) commit transaction
=> #<Dosage id: nil, title: nil, created_at: nil, updated_at: nil, report_id: 1>
2.4.0 :010 > r.dosages.size
=> 1
My proposal:
2.4.0 :005 > report = Report.first
Report Load (0.1ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<Report id: 1, title: nil, date: nil, created_at: "2018-07-03 14:00:08", updated_at: "2018-07-03 14:00:08">
2.4.0 :006 > report.dosages.count
(0.1ms) SELECT COUNT(*) FROM "dosages" WHERE "dosages"."report_id" = ? [["report_id", 1]]
=> 0
2.4.0 :007 > dosage = report.dosages.build
=> #<Dosage id: nil, title: nil, created_at: nil, updated_at: nil, report_id: 1>
2.4.0 :008 > dosage.save
(0.1ms) begin transaction
SQL (0.7ms) INSERT INTO "dosages" ("created_at", "updated_at", "report_id") VALUES (?, ?, ?) [["created_at", "2018-07-03 14:06:08.709323"], ["updated_at", "2018-07-03 14:06:08.709323"], ["report_id", 1]]
(0.9ms) commit transaction
=> true
2.4.0 :009 > report.dosages.size
(0.2ms) SELECT COUNT(*) FROM "dosages" WHERE "dosages"."report_id" = ? [["report_id", 1]]
=> 1
2.4.0 :010 > report.dosages.first.destroy
Dosage Load (0.2ms) SELECT "dosages".* FROM "dosages" WHERE "dosages"."report_id" = ? ORDER BY "dosages"."id" ASC LIMIT ? [["report_id", 1], ["LIMIT", 1]]
(0.0ms) begin transaction
SQL (0.3ms) DELETE FROM "dosages" WHERE "dosages"."id" = ? [["id", 1]]
(0.8ms) commit transaction
=> #<Dosage id: 1, title: nil, created_at: "2018-07-03 14:06:08", updated_at: "2018-07-03 14:06:08", report_id: 1>
2.4.0 :011 > report.dosages.size
(0.2ms) SELECT COUNT(*) FROM "dosages" WHERE "dosages"."report_id" = ? [["report_id", 1]]
=> 0
I believe this is what you need. The relationships are set like:
dosage.rb
class Dosage < ApplicationRecord
belongs_to :report
end
report.rb
class Report < ApplicationRecord
has_many :dosages
end
And the following migrations:
class ManyDosagesToReport < ActiveRecord::Migration[5.0]
def change
add_column :dosages, :report_id, :integer
end
end
In fact, if you try to persist the dosage
object that you built but didn't persist, Rails will complain:
RuntimeError: can't modify frozen Hash
due to the destroyed
attribute.
Upvotes: 4
Reputation: 213
If I understand you correctly, you would like to remove the dosage from the db hence rendering the dosage as deleted
but would still like to keep the report object? If this is your use case then first you should know that report will stay regardless of the dosage being deleted since its a 1-* (one to many relationship) however you will need to take a look at the difference between delete
and destroy
which the stackoverflow community has helped in answering here Difference between Destroy and Delete
As a bonus, do take a look at the shebang(!) method in ruby and when to use it e.g. delete!
destroy!
Upvotes: 0