I've recently picked up maintenance of a couple of Rails 5.2 apps with a PostgreSQL back end. I'm new to Rails, but I've got a fair bit of experience on the various Microsoft platforms.
I'm trying to add API calls to an existing model. When I attempt to create a new instance, I am not getting the database-generated ID back:
POST /invoices
{ "amount": 12.34 }
Invoice Create (4.0ms)
INSERT INTO "invoices" ("amount", "created_at", "updated_at")
VALUES ($1, $2, $3)
[["amount", 12.34], ["created_at", "..."], ["updated_at", "..."]]
201 Created
{ "id": null, "amount": 12.34 }
Checking the database, the new row is present, with a unique ID.
A different model in the same app generates different SQL and works as expected:
POST /customer
{ "name": "ACME" }
Customer Create (1.4ms)
INSERT INTO "customers" ("name", "created_at", "updated_at")
VALUES ($1, $2, $3)
** RETURNING "id" **
[["name", "ACME"], ["created_at", "..."], ["updated_at", "..."]]
201 Created
{ "id": 111, "name": "ACME" }
I can't see any differences in the two models that explain this behavior. I've checked everything I can think of:
- routes (via :resources)
- controller
- before/after filters
- strong parameters
- code in
create
- model
- neither contains any code
- schema
- column definitions are comparable in schema.rb and information_schema.columns
Here's the model and controller for the misbehaving type:
class Invoice < ActiveRecord::Base
end
class InvoiceController < ApplicationController
def create
invoice = Invoice.new(invoice_params)
if invoice.save
# invoice.id.nil? => true
render json: invoice, status: :created
end
end
def invoice_params
params.permit(:amount)
end
end
# schema.rb
create_table "invoices", id: false, force: :cascade do |t|
t.serial "id", null: false
t.float "amount"
t.datetime "created_at"
t.datetime "updated_at"
end
And the one that works as expected:
class Customer < ActiveRecord::Base
end
class CustomerController < ApplicationController
def create
customer = Customer.new(customer_params)
if customer.save
# customer.id.nil? => false
render json: customer, status: :created
end
end
def customer_params
params.permit(:name)
end
end
# schema.rb
create_table "customers", id: :serial, force: :cascade do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
Replacing new
/save
with create
or create!
doesn't change the behavior, so I'm convinced that the problem is somewhere in the model definition or metadata.
Creating the models from rails console
has the same result as shown below:
irb(main):001:0> Invoice.create(amount:12.34)
(0.8ms) BEGIN
Invoice Create (1.1ms) INSERT INTO "invoices" ("amount", "created_at", "updated_at") VALUES ($1, $2, $3) [["amount", 12.34], ["created_at", "2021-11-19 09:10:33.490117"], ["updated_at", "2021-11-19 09:10:33.490117"]]
(5.8ms) COMMIT
=> #<Invoice id: nil, amount: 12.34, created_at: "2021-11-19 09:10:33", updated_at: "2021-11-19 09:10:33">
irb(main):002:0> Customer.create(name: "ACME")
(0.9ms) BEGIN
Customer Create (1.5ms) INSERT INTO "customers" ("name", "created_at", "updated_at") VALUES ($1, $2, $3) RETURNING "id" [["name", "ACME"], ["created_at", "2021-11-19 09:12:50.492927"], ["updated_at", "2021-11-19 09:12:50.492927"]]
(13.3ms) COMMIT
=> #<Customer id: 24, name: "ACME", created_at: "2021-11-19 09:12:50", updated_at: "2021-11-19 09:12:50">
Can anyone point me in the right direction?