I am having issues updating a record, using save. This is my code so far
def update
if current_user.customer?
question = JobQuestion.find(job_question_params[:job_id])
question.answer = job_question_params[:answer]
# Only save, if the current user is the owner of the job.
if current_user.id == Job.find(job_question_params['job_id']).customer.id && question.save
raise JobQuestion.last.inspect
render json: { status: 201 }
end
end
end
But for some reason it doesn't get updated, this is the output from my log.
SQL (85.6ms) UPDATE "job_questions" SET "answer" = $1, "job_id" = $2, "question" = $3, "updated_at" = $4 WHERE "job_questions"."id" = 2 [["answer", "safsf"], ["job_id", 2], ["question", "test test test"], ["updated_at", Wed, 08 Jan 2014 15:04:16 UTC +00:00]]
(11.5ms) COMMIT
JobQuestion Load (0.9ms) SELECT "job_questions".* FROM "job_questions" ORDER BY "job_questions"."id" DESC LIMIT 1
Completed 500 in 218ms
RuntimeError - #<JobQuestion id: 3, job_id: 2, company_id: 2, question: "test test test", answer: nil, created_at: "2014-01-08 11:47:34", updated_at: "2014-01-08 14:17:00">:
app/controllers/api/job_questions_controller.rb:27:in `update'
actionpack (4.0.0) lib/action_controller/metal/implicit_render.rb:4:in `send_action'
As you can see the raise is fetching the job question, but the answer is nil, even thought it is inserting something into it.
What am i doing wrong?
You are updating the JobQuestion with id == 2 and then fetching the one with id == 3
Try to use the same query to find the record.
JobQuestion.find(2).inspect #=> #<JobQuestion id: 2,...
JobQuestion.last.inspect #=> #<JobQuestion id: 3,...
Related
In the terminal (later I need to add this to the migration) I want to update total_campaign_codes_amount which represents all campaign codes for each credit campaign. This field is a counter_cache and to do so in terminal I've got this script:
CreditCampaign.all.each do |cc|
cc.update!(
total_campaign_codes_amount: cc.campaign_codes.count
)
end
I didn't received any errors in my sql, everything should worked but these values are not saved to - total_campaign_codes_amount for each CreditCampaign shows 0.
CreditCampaign Update (0.2ms) UPDATE "credit_campaigns" SET "updated_at" = $1 WHERE "credit_campaigns"."id" = $2 [["updated_at", "2019-10-14 15:26:54.010779"], ["id", 1]]
(0.4ms) COMMIT
(0.2ms) SELECT COUNT(*) FROM "campaign_codes" WHERE "campaign_codes"."credit_campaign_id" = $1 [["credit_campaign_id", 2]]
(0.1ms) BEGIN
CreditCampaign Update (0.2ms) UPDATE "credit_campaigns" SET "updated_at" = $1 WHERE "credit_campaigns"."id" = $2 [["updated_at", "2019-10-14 15:26:54.013239"], ["id", 2]]
(0.2ms) COMMIT
=> [#<CreditCampaign id: 1, amount: 0.25e3, interest_rate: 0.12e2, installment_amount: 0.1e3, duration_in_months: 24, start_date: "2019-10-10", end_date: "2019-11-10", created_at: "2019-10-10 14:54:27", updated_at: "2019-10-14 15:26:54", name: "Credit Campaign #1", total_campaign_codes_amount: 15, used_campaign_codes_amount: 6>, #<CreditCampaign id: 2, amount: 0.1e1, interest_rate: 0.1e1, installment_amount: 0.1e1, duration_in_months: 1, start_date: "2011-01-01", end_date: "2020-01-01", created_at: "2019-10-14 13:40:02", updated_at: "2019-10-14 15:26:54", name: "test", total_campaign_codes_amount: 10, used_campaign_codes_amount: 0>]
I was trying to use update_attributes! instead of update! and find_each instead of all.each but without any results.
You can not update counter cache column in Rails, because it is added to the containing model's list of read-only attributes through attr_readonly. Instead you should use update_counters or reset_counters.
I have a height_ranges table on my database, which stores numeric ranges.
migration file
class CreateHeightRanges < ActiveRecord::Migration
def change
create_table :height_ranges do |t|
t.references :classification, index: true, foreign_key: true
t.references :tree, index: true, foreign_key: true
t.numrange :h_range
t.integer :diameter
t.timestamps null: false
end
end
end
I can update table via raw sql query,
UPDATE height_ranges SET h_range = numrange(5.6, 9.8, '[]') WHERE id = 1;
But when it comes to ActiveRecord, it surprisely omits lower bound.
[10] pry(main)> hr = HeightRange.first;
HeightRange Load (0.5ms) SELECT "height_ranges".* FROM "height_ranges" ORDER BY "height_ranges"."id" ASC LIMIT 1
[11] pry(main)> hr.h_range = "numrange(6.2, 9.8, '[]')"
=> "numrange(6.2, 9.8, '[]')"
[12] pry(main)> hr.save
(0.2ms) BEGIN
SQL (1.9ms) UPDATE "height_ranges" SET "h_range" = $1, "updated_at" = $2 WHERE "height_ranges"."id" = $3 [["h_range", "[0.0,9.8)"], ["updated_at", "2017-10-09 10:18:03.178971"], ["id", 1]]
(13.6ms) COMMIT
=> true
[13] pry(main)>
another query,
[13] pry(main)> d = {"classification_id"=>"2", "tree_id"=>"4", "diameter"=>"16", "h_range"=>"numrange(5.3, 7.5, '[]')"}
=> {"classification_id"=>"2", "tree_id"=>"4", "diameter"=>"16", "h_range"=>"numrange(5.3, 7.5, '[]')"}
[14] pry(main)> hr.update_attributes(d)
(0.2ms) BEGIN
SQL (0.3ms) UPDATE "height_ranges" SET "h_range" = $1, "updated_at" = $2 WHERE "height_ranges"."id" = $3 [["h_range", "[0.0,7.5)"], ["updated_at", "2017-10-09 10:20:30.638967"], ["id", 1]]
(21.4ms) COMMIT
=> true
A semi AR query that works is,
HeightRange.where(id: hr.id).update_all("h_range = numrange(5.5, 9.4, '[]')")
As a last resort I can use the first (raw) and last (passing raw query to ActiveRecord method) but what is wrong with attribute setting approach?
My current implementation has those helper methods in the model:
def unusual_update dict
HeightRange.where(id: id).update_all(attrs_to_sql_set_stmt(dict))
end
def attrs_to_sql_set_stmt dct
dct.map{|k,v| "#{k} = #{v}" }.join(", ")
end
In the controller, I trigger the update as follows:
def update
r_min = params[:range_min].to_f
r_max = params[:range_max].to_f
h_range = "numrange(#{r_min}, #{r_max}, '[]')"
height_range_params = pre_params.merge({h_range: h_range})
if #height_range.unusual_update(height_range_params)
redirect_to admin_height_ranges_path, notice: 'Height range was successfully updated.'
else
render :edit
end
end
PostgreSQL ranges should be fully supported by Rails.
I manage to reproduce your situation as follows:
Migration:
#db/migrate/20171009152602_create_test_table.rb
class CreateTestTable < ActiveRecord::Migration[5.0]
def up
create_table :test_tables do |t|
t.numrange :h_range
end
end
def down
drop_table :test_tables
end
end
Model:
# app/models/test_table.rb
class TestTable < ActiveRecord::Base
end
The numrange has some challenges in terms of type support:
$ rails c
Loading development environment (Rails 5.0.5)
2.4.0 :001 > c = TestTable.new
=> #<TestTable id: nil, h_range: nil>
# Inserting Integer range
2.4.0 :002 > c.update(h_range:(3..5))
(0.3ms) BEGIN
SQL (0.5ms) INSERT INTO "test_tables" ("h_range") VALUES ($1) RETURNING "id" [["h_range", "[3,5]"]]
(16.7ms) COMMIT
=> true
2.4.0 :003 > c.h_range
=> 0.3e1..0.5e1
2.4.0 :004 > c.h_range.to_a
TypeError: can't iterate from BigDecimal
2.4.0 :005 > c.h_range.step
=> #<Enumerator: 0.3e1..0.5e1:step(1)>
2.4.0 :006 > c.h_range.step.to_a
=> [0.3e1, 0.4e1, 0.5e1]
# Inserting Float range
2.4.0 :007 > c.update(h_range:(3.4..5.8))
(0.3ms) BEGIN
SQL (0.8ms) UPDATE "test_tables" SET "h_range" = $1 WHERE "test_tables"."id" = $2 [["h_range", "[3.4,5.8]"], ["id", 1]]
(20.7ms) COMMIT
=> true
2.4.0 :008 > c.h_range.step.to_a
=> [0.34e1, 0.44e1, 0.54e1]
2.4.0 :009 > c.h_range.step(0.1).to_a
=> [3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0, 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9, 5.0, 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8]
I would suggest you to prefer ActiveRecord instance update method than raw sql in your model.
You can eventually add an instance method where you perform .step on your h_range field.
Update
After the code you shared via pastebin, in your controller you should be able to update the values as follows:
def update
hr_params = params.require(:height_range).permit(:classification_id, :tree_id, :diameter, :range_min, :range_max)
r_min = hr_params[:range_min].to_f
r_max = hr_params[:range_max].to_f
#hr = HeighRange.find(params[:id])
hr_options = {
classification_id: hr_params[:classification_id],
tree_id: hr_params[:tree_id],
diameter: hr_params[:diameter],
h_range: (r_min..r_max)
}
#hr.update(hr_options)
if #hr.valid?
#hr.save!
redirect_to admin_height_ranges_path, notice: 'Height range was successfully updated.
else
render action: :edit, params: { id: #hr.id }
end
end
I'm learning rails, And read about optimistic lock. I've added lock_version column of type integer into my articles table.
But now whenever I try to update a record for the first time, I get StaleObjectError exception.
Here's my migration:
class AddLockVersionToArticle < ActiveRecord::Migration
def change
add_column :articles, :lock_version, :integer
end
end
When I try updating an article through rails console:
article = Article.first
=> #<Article id: 1, title: "Ccccc", text: "dfdsfsdfsdf", created_at: "2015-02-20 21:58:45", updated_at: "2015-02-25 20:03:12", lock_version: 0>
And I do:
article.title = "new title"
article.save
I get this:
(0.3ms) begin transaction
(0.3ms) UPDATE "articles" SET "title" = 'dwdwd', "updated_at" = '2015-02-25 20:40:36.537876', "lock_version" = 1 WHERE ("articles"."id" = 1 AND "articles"."lock_version" = 0)
(0.1ms) rollback transaction
ActiveRecord::StaleObjectError: Attempted to update a stale object: Article
You have to initialize all the articles lock_version to 0.
Look at the query:
UPDATE "articles" SET "title" = 'dwdwd', "updated_at" = '2015-02-25 20:40:36.537876', "lock_version" = 1 WHERE ("articles"."id" = 1 AND "articles"."lock_version" = 0)
(0.1ms)
If the query returns 0 records updated, then the framework suppose that you have updated the version or deleted the object in another thread.
I want to let a user edit the field report.plan only if report.published = false. If report.published = true and they try to save a change, I want to throw an error.
I've written the following code to do this:
class Report < ActiveRecord::Base
validate :cannot_update_plan_after_published, on: :publish_plan!
def publish_plan!(plan)
self.plan = plan
self.published = true
self.save
end
private
def cannot_update_plan_after_published
if self.published?
errors.add(:plan, "You cannot change the plan once it has been published.")
end
end
end
However, this is not working. When I call publish_plan! on an already published report, it makes the save. For example:
> f = Report.last
=> #<Report id: 12, plan: "a", published: false>
> f.publish_plan!("b")
(0.1ms) begin transaction
(0.4ms) UPDATE "reports" SET "plan" = 'b', "updated_at" = '2014-09-18 18:43:47.459983' WHERE "reports"."id" = 12
(9.2ms) commit transaction
=> true
> f = Report.last
Report Load (0.2ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" DESC LIMIT 1
=> #<Report id: 12, plan: "b", published: true>
> f.publish_plan!("c")
(0.1ms) begin transaction
(0.4ms) UPDATE "reports" SET "plan" = 'c', "updated_at" = '2014-09-18 18:43:53.996191' WHERE "reports"."id" = 12
(8.7ms) commit transaction
=> true
> Report.last
Report Load (0.2ms) SELECT "reports".* FROM "reports" ORDER BY "reports"."id" DESC LIMIT 1
=> #<Report id: 12, plan: "c", published: true>
How do I get this field to become uneditable once report.published = true?
Try removing the on: :public_plan!. That way, the validation should be run every time the model is saved.
validate :cannot_update_plan_after_published
See here for more details: Adding a validation error with a before_save callback or custom validator?
Also, for the validation method itself, change it to the following:
def cannot_update_plan_after_published
if self.published? && self.published_changed? == false
errors.add(:plan, "You cannot change the plan once it has been published.")
end
end
This allows you to set it the first time publishing the plan.
I want to check if an ActiveRecord instance was changed database-wise. Something like:
p1 = Product.first
p1.name #=> "some name"
p2 = Product.first
p2.name = "other name"
p2.save #=> true
p1.database_changed? #=> true
I'm currently comparing the record's attributes to the persisted attributes:
class Product < ActiveRecord::Base
def database_changed?
Product.find(id).attributes != attributes
end
end
This seems to work, but I'm wondering if there is a built-in way to find database changes?
After Зелёный's comment I've reviewed ActiveModel::Dirty and realized that it almost does what I want. There's already an in-memory state (the record's attributes) and a database state (handled by ActiveModel::Dirty). I just need a method to update the database state, leaving the in-memory state unchanged:
def refresh
#changed_attributes = {}
fresh_object = self.class.unscoped { self.class.find(id) }
fresh_object.attributes.each do |attr, orig_value|
#changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, #attributes[attr])
end
self
end
#changed_attributes is ActiveModel::Dirty's hash to store changed values. We obviously have to reset it.
fresh_object is the same record, freshly fetched from the database (this line comes from reload, thanks emaillenin).
Within the loop, each (fresh) attribute is compared to the corresponding (in-memory) attribute. If they differ, it is added to the #changed_attributes hash. This line comes from ActiveRecord's dup method. (it's actually from a private method called by dup, and _field_changed is private, too. It might be better to use ActiveRecord's public API, but I was lazy)
Finally, refresh returns self for convenience, just like reload does.
Here's an example usage:
p1 = Product.first
p1.name #=> "some name"
p1.changed? #=> false
p2 = Product.first
p2.name = "other name"
p2.save #=> true
p1.refresh
p1.name #=> "some name"
p1.changed? #=> true
p1.name_changed? #=> true
p1.name_was #=> "other name"
p1.name = "other name"
p1.name_changed? #=> false
p1.changed? #=> true
p1.changes #=> {"updated_at"=> [Tue, 29 Jul 2014 21:58:57 CEST +02:00, Tue, 29 Jul 2014 15:49:54 CEST +02:00]}
def database_changed?
self.class.where(self.class.arel_table[:updated_at].gt(updated_at)).exists? self
end
The Rails way to to do this is to use reload method on the ActiveRecord object.
def database_changed?
attributes != reload.attributes
end
Terminal 1
2.1.2 :001 > c = Car.find(1)
Car Load (0.4ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> #<Car id: 1, name: "Audi", model: "R8", created_at: "2014-07-29 11:14:43", updated_at: "2014-07-29 11:14:43">
2.1.2 :002 > c.database_changed?
Car Load (0.1ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> false
2.1.2 :003 > c.database_changed?
Car Load (0.2ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> false
2.1.2 :004 > c.database_changed?
Car Load (0.2ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> true
Terminal 2
2.1.2 :001 > c = Car.find(1)
Car Load (0.2ms) SELECT "cars".* FROM "cars" WHERE "cars"."id" = ? LIMIT 1 [["id", 1]]
=> #<Car id: 1, name: "Audi", model: "R8", created_at: "2014-07-29 11:14:43", updated_at: "2014-07-29 11:14:43">
2.1.2 :002 > c.model = 'A5'
=> "A5"
2.1.2 :003 > c.save!
(0.2ms) begin transaction
SQL (0.3ms) UPDATE "cars" SET "model" = ?, "updated_at" = ? WHERE "cars"."id" = 1 [["model", "A5"], ["updated_at", "2014-07-29 11:15:32.845875"]]
(1.2ms) commit transaction
=> true
2.1.2 :004 >