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