diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 45dfeddba8d47..3521e3a9abc9a 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,3 +1,10 @@ +* Fixed `update_column`, `update_columns`, and `update_all` to correctly serialize + values for `array`, `hstore` and `json` column types in `PostgreSQL`. + + Fixes #12261. + + *Tadas Tamosauskas* + * Do not consider PostgreSQL array columns as number or text columns. The code uses these checks in several places to know what to do with a diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb index cab8fd745a966..bc78c3858aee2 100644 --- a/activerecord/lib/active_record/sanitization.rb +++ b/activerecord/lib/active_record/sanitization.rb @@ -101,7 +101,7 @@ def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name # # => "status = NULL , group_id = 1" def sanitize_sql_hash_for_assignment(attrs, table) attrs.map do |attr, value| - "#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value)}" + "#{connection.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, connection, columns_hash[attr.to_s])}" end.join(', ') end @@ -152,15 +152,16 @@ def replace_named_bind_variables(statement, bind_vars) #:nodoc: end end - def quote_bound_value(value, c = connection) #:nodoc: - if value.respond_to?(:map) && !value.acts_like?(:string) + def quote_bound_value(value, c = connection, column = nil) #:nodoc: + db_native_type = column && (column.respond_to?(:array) && column.array || [:json, :hstore].include?(column.type) ) + if value.respond_to?(:map) && !value.acts_like?(:string) && !db_native_type if value.respond_to?(:empty?) && value.empty? c.quote(nil) else value.map { |v| c.quote(v) }.join(',') end else - c.quote(value) + c.quote(value, column) end end diff --git a/activerecord/test/cases/adapters/postgresql/array_test.rb b/activerecord/test/cases/adapters/postgresql/array_test.rb index b0870e22567f5..cf059d20e285c 100644 --- a/activerecord/test/cases/adapters/postgresql/array_test.rb +++ b/activerecord/test/cases/adapters/postgresql/array_test.rb @@ -128,6 +128,12 @@ def test_attribute_for_inspect_for_array_field assert_equal("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ...]", record.attribute_for_inspect(:ratings)) end + def test_update_all + PgArray.create! tags: ["one", "two", "three"] + PgArray.update_all tags: ["four", "five"] + assert_equal ["four", "five"], PgArray.first.tags + end + private def assert_cycle field, array # test creation diff --git a/activerecord/test/cases/adapters/postgresql/hstore_test.rb b/activerecord/test/cases/adapters/postgresql/hstore_test.rb index 2845413575e60..730020b5c8f34 100644 --- a/activerecord/test/cases/adapters/postgresql/hstore_test.rb +++ b/activerecord/test/cases/adapters/postgresql/hstore_test.rb @@ -206,6 +206,12 @@ def test_quoting_special_characters def test_multiline assert_cycle("a\nb" => "c\nd") end + + def test_update_all + Hstore.create! tags: { "one" => "two" } + Hstore.update_all tags: { "three" => "four" } + assert_equal({ "three" => "four" }, Hstore.first.tags) + end end private diff --git a/activerecord/test/cases/adapters/postgresql/json_test.rb b/activerecord/test/cases/adapters/postgresql/json_test.rb index c33c7ef968b5d..10efa8b8028d1 100644 --- a/activerecord/test/cases/adapters/postgresql/json_test.rb +++ b/activerecord/test/cases/adapters/postgresql/json_test.rb @@ -121,4 +121,10 @@ def test_with_store_accessors x = JsonDataType.first assert_equal "640×1136", x.resolution end + + def test_update_all + JsonDataType.create! payload: { "one" => "two" } + JsonDataType.update_all payload: { "three" => "four" } + assert_equal({ "three" => "four" }, JsonDataType.first.payload) + end end