Skip to content

Postgres default function values do not get loaded on create #34237

@davegson

Description

@davegson

Steps to reproduce

1. Create a model

class Order < ApplicationRecord
end

2. Create a migration with a DB function as default.

class CreateOrders < ActiveRecord::Migration[5.2]
  def change
    create_table :orders do |t|
      t.string :uuid, default: -> { "gen_random_uuid()" }
      t.references :user
      t.references :product
      t.text :comment

      t.timestamps
    end
  end
end

rails db:migrate

3. Create a new order

rails console

order = Order.create(user_id: 1, product_id: 1)

Expected behavior

I expect create to set uuid (by the postgres default) and for it to set the new uuid value into the order AR instance, as it does with the id.

> order
# =>
# +----+--------------------------------------+---------+------------+---------+----------------+----------------+
# | id | uuid                                 | user_id | product_id | comment | created_at     | updated_at     |
# +----+--------------------------------------+---------+------------+---------+----------------+----------------+
# | 1  | bec94fe0-f7c3-4ede-8b25-c4bce702ec00 | 1       | 1          |         | 2018-10-17 ... | 2018-10-17 ... |
# +----+--------------------------------------+---------+------------+---------+----------------+----------------+

Actual behavior

The uuid does get set in the db, but it is not returned into the order instance.

> order
# =>
# +----+------+---------+------------+---------+----------------+----------------+
# | id | uuid | user_id | product_id | comment | created_at     | updated_at     |
# +----+------+---------+------------+---------+----------------+----------------+
# | 1  |      | 1       | 1          |         | 2018-10-17 ... | 2018-10-17 ... |
# +----+------+---------+------------+---------+----------------+----------------+

Current workaround

Calling order.reload does then retrieve the values set by postgres. Other workarounds are described in this issue

> order
# =>
# +----+--------------------------------------+---------+------------+---------+----------------+----------------+
# | id | uuid                                 | user_id | product_id | comment | created_at     | updated_at     |
# +----+--------------------------------------+---------+------------+---------+----------------+----------------+
# | 1  | bec94fe0-f7c3-4ede-8b25-c4bce702ec00 | 1       | 1          |         | 2018-10-17 ... | 2018-10-17 ... |
# +----+--------------------------------------+---------+------------+---------+----------------+----------------+

Background

This issue already describes this bug, but the issue was initially a request to support functional defaults, which has since been implemented.

This quote from the discussion summarizes it pretty well:

@kenaniah Sure, but it makes sense why the full row shouldn't be returned by default: there could be too much data, and ActiveRecord already knows both default and non-default values. (Or at least it thinks so.) But if the default value is generated by a function in PG then ActiveRecord doesn't know. Turns out that is not enough reason to make RETURNING * the default, and I'm ok with that. I think what we need is another parameter to tell ActiveRecord, that for a particular model, it should use RETURNING * instead.

This blog post describes the issue wonderfully.

Next steps

So a solution could be an option to tell AR to use RETURNING * in some instances. But I myself am not familiar with the ActiveRecord internals so I feel this should be further discussed.

System configuration

Rails version: 5.2.1

Ruby version: 2.4.1

Postgres version: 9.6.3

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions