Rails and postgresql not dialoguing properly on record creation - ruby-on-rails

The following record creation is failing as the data is being misinterpreted by Postgresql
SQL (1.3ms) INSERT INTO "gruppomerceologicos" ("nome", "reparto_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4) RETURNING "id" [["nome", "Salumeria"], ["reparto_id", 1], ["created_at", "2020-03-13 09:24:15.529470"], ["updated_at", "2020-03-13 09:24:15.529470"]]
(0.2ms) ROLLBACK
Completed 500 Internal Server Error in 11ms (ActiveRecord: 2.5ms)
PG::NotNullViolation - ERROR: null value in column "id" violates not-null constraint
DETAIL: Failing row contains (null, Salumeria, 2020-03-13 09:24:15.52947, 2020-03-13 09:24:15.52947, 1).
The row creation is in effect incorrect as PostgreSQL is attempting to handle a record with a nullvalue for id
The controller is standard
#gruppomerceologico = Gruppomerceologico.new(gruppomerceologico_params)
authorize #gruppomerceologico
#gruppomerceologico.save
respond_with(#gruppomerceologico)
The rails debugger does however show that it is attempting to handle a record without an ID at point where error is occuring
#gruppomerceologico
#<Gruppomerceologico id: nil, nome: "Salumeria", created_at: "2020-03-13 08:24:15", updated_at: "2020-03-13 08:24:15", reparto_id: 1>
Postgre has the following data structure
Table "public.gruppomerceologicos"
Column | Type | Modifiers
------------+-----------------------------+-----------
id | integer | not null
nome | character varying |
created_at | timestamp without time zone | not null
updated_at | timestamp without time zone | not null
reparto_id | integer |
Indexes:
"gruppomerceologicos_pkey" PRIMARY KEY, btree (id)
"index_gruppomerceologicos_on_reparto_id" btree (reparto_id) Foreign-key constraints:
"fk_rails_d8a087054a" FOREIGN KEY (reparto_id) REFERENCES repartos(id) Referenced by:
TABLE "materiagrezzas" CONSTRAINT "fk_rails_2b6ca167a0" FOREIGN KEY (gruppomerceologico_id) REFERENCES gruppomerceologicos(id)
Normally Postgre knows it is creating a new record and will thus assign the sequential ID and create the record. But this is not happening. Where is the gap between rails and Postgresql?
The initial migration:
class CreateGruppomerceologicos < ActiveRecord::Migration
def change
create_table :gruppomerceologicos do |t|
t.string :nome
t.timestamps null: false
end
end
end
followed by an update in migration
class AddRepartoToGruppomerceologicos < ActiveRecord::Migration
def change
add_reference :gruppomerceologicos, :reparto, index: true, foreign_key: true
end
end
Update
Double checking with a development server, the \d query for the table returns something completely different:
Table "public.gruppomerceologicos"
Column | Type | Collation | Nullable | Default
------------+-----------------------------+-----------+----------+-------------------------------------------------
id | integer | | not null | nextval('gruppomerceologicos_id_seq'::regclass)
nome | character varying | | |
created_at | timestamp without time zone | | not null |
updated_at | timestamp without time zone | | not null |
where the nextval is obviously in proper play. The two databases were somehow out of synch. As suggested below by #sebastianPalma select nextval('gruppomerceologicos_id_seq'::regclass); will directly reveal the discrepancy.

Related

Rails: Build Association when id Method is Non-standard

I'm trying to build a JobReport model that holds the return value from GoodJob jobs. The two fields I could build an association on, id and active_job_id, are problematic. The id field is set to return the active_job_id in the Job class:
# from good_job-3.12.1/app/models/good_job/job.rb
def id
active_job_id
end
The good_jobs.active_job_id field has no uniqueness constraint, and setting it as a foreign key throws a postgres error.
How can I link these two tables?
Here's the migration I'm using to create the job_reports table:
class CreateJobReports < ActiveRecord::Migration[7.0]
def change
create_table :job_reports do |t|
t.text :report
t.uuid :good_job_id
t.timestamps
end
add_foreign_key :job_reports, :good_jobs, column: :good_job_id, primary_key: :id
end
end
My JobReport model:
class JobReport < ApplicationRecord
belongs_to :good_job, class_name: 'GoodJob::Job', foreign_key: 'id'
end
And my good_job.rb initializer contains:
GoodJob::Job.class_eval do
has_one :job_report, dependent: :destroy
end
When I create a JobReport, tie it to a Job, and save it, postgres complains that the id doesn't exist in good_jobs, because it's trying to use the active_job_id:
irb(main):001:0> jr = JobReport.new; gj = GoodJob::Job.last
=>
#<GoodJob::Job:0x00007ff6950cda30
...
irb(main):002:0> jr.good_job = gj
=>
#<GoodJob::Job:0x00007ff6950cda30
...
irb(main):003:0> jr.save
/usr/local/bundle/gems/activerecord-7.0.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': PG::ForeignKeyViolation: ERROR: insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" (ActiveRecord::InvalidForeignKey)
DETAIL: Key (good_job_id)=(fdc02e75-a06a-4727-b790-9a846f61ed7d) is not present in table "good_jobs".
/usr/local/bundle/gems/activerecord-7.0.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': ERROR: insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" (PG::ForeignKeyViolation)
DETAIL: Key (good_job_id)=(fdc02e75-a06a-4727-b790-9a846f61ed7d) is not present in table "good_jobs".
irb(main):004:0> gj.id
=> "fdc02e75-a06a-4727-b790-9a846f61ed7d"
irb(main):005:0> gj.active_job_id
=> "fdc02e75-a06a-4727-b790-9a846f61ed7d"
irb(main):006:0> gj.attributes["id"]
=> "edc27b66-975d-4017-a09f-2d0cec332a0c"
As I mentioned before, if I give up on the ID column and switch to the active_job_id column, postgres says I can't use it as a foreign key b/c there's no uniqueness constraint. Sure, I could edit the GoodJob tables, but I'd prefer to use the drop-in form of the gem without tampering with it for upgrades and whatnot down the road.
Edit: I implemented Max's suggestion, but it's still trying to use the active_job_id column of the good_jobs table instead of the id column.
class JobReport < ApplicationRecord
belongs_to :good_job, class_name: 'GoodJob::Job', foreign_key: 'good_job_id', primary_key: 'id'
end
irb(main):010:0> jr = JobReport.new; gj = GoodJob::Job.last
=>
#<GoodJob::Job:0x00007f70ec430918
...
irb(main):011:0> jr.good_job = gj
=>
#<GoodJob::Job:0x00007f70ec430918
...
irb(main):012:0> jr.save
/usr/local/bundle/gems/activerecord-7.0.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': PG::ForeignKeyViolation: ERROR: insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" (ActiveRecord::InvalidForeignKey)
DETAIL: Key (good_job_id)=(fdc02e75-a06a-4727-b790-9a846f61ed7d) is not present in table "good_jobs".
/usr/local/bundle/gems/activerecord-7.0.4.1/lib/active_record/connection_adapters/postgresql_adapter.rb:768:in `exec_params': ERROR: insert or update on table "job_reports" violates foreign key constraint "fk_rails_6135bfd69e" (PG::ForeignKeyViolation)
DETAIL: Key (good_job_id)=(fdc02e75-a06a-4727-b790-9a846f61ed7d) is not present in table "good_jobs".
irb(main):013:0> gj.id
=> "fdc02e75-a06a-4727-b790-9a846f61ed7d"
irb(main):014:0> gj.active_job_id
=> "fdc02e75-a06a-4727-b790-9a846f61ed7d"
irb(main):015:0> gj.attributes['id']
=> "edc27b66-975d-4017-a09f-2d0cec332a0c"
Here's the schema of the two tables:
development=# \d good_jobs
Table "public.good_jobs"
Column | Type | Collation | Nullable | Default
---------------------+--------------------------------+-----------+----------+-------------------
id | uuid | | not null | gen_random_uuid()
queue_name | text | | |
priority | integer | | |
serialized_params | jsonb | | |
scheduled_at | timestamp(6) without time zone | | |
performed_at | timestamp(6) without time zone | | |
finished_at | timestamp(6) without time zone | | |
error | text | | |
created_at | timestamp(6) without time zone | | not null |
updated_at | timestamp(6) without time zone | | not null |
active_job_id | uuid | | |
concurrency_key | text | | |
cron_key | text | | |
retried_good_job_id | uuid | | |
cron_at | timestamp(6) without time zone | | |
batch_id | uuid | | |
batch_callback_id | uuid | | |
Indexes:
"good_jobs_pkey" PRIMARY KEY, btree (id)
"index_good_jobs_on_cron_key_and_cron_at" UNIQUE, btree (cron_key, cron_at)
"index_good_jobs_jobs_on_finished_at" btree (finished_at) WHERE retried_good_job_id IS NULL AND finished_at IS NOT NULL
"index_good_jobs_jobs_on_priority_created_at_when_unfinished" btree (priority DESC NULLS LAST, created_at) WHERE finished_at IS NULL
"index_good_jobs_on_active_job_id" btree (active_job_id)
"index_good_jobs_on_active_job_id_and_created_at" btree (active_job_id, created_at)
"index_good_jobs_on_batch_callback_id" btree (batch_callback_id) WHERE batch_callback_id IS NOT NULL
"index_good_jobs_on_batch_id" btree (batch_id) WHERE batch_id IS NOT NULL
"index_good_jobs_on_concurrency_key_when_unfinished" btree (concurrency_key) WHERE finished_at IS NULL
"index_good_jobs_on_cron_key_and_created_at" btree (cron_key, created_at)
"index_good_jobs_on_queue_name_and_scheduled_at" btree (queue_name, scheduled_at) WHERE finished_at IS NULL
"index_good_jobs_on_scheduled_at" btree (scheduled_at) WHERE finished_at IS NULL
Referenced by:
TABLE "job_reports" CONSTRAINT "fk_rails_6135bfd69e" FOREIGN KEY (good_job_id) REFERENCES good_jobs(id)
development=# \d job_reports
Table "public.job_reports"
Column | Type | Collation | Nullable | Default
-------------+--------------------------------+-----------+----------+-----------------------------------------
id | bigint | | not null | nextval('job_reports_id_seq'::regclass)
report | text | | |
good_job_id | uuid | | |
created_at | timestamp(6) without time zone | | not null |
updated_at | timestamp(6) without time zone | | not null |
Indexes:
"job_reports_pkey" PRIMARY KEY, btree (id)
Foreign-key constraints:
"fk_rails_6135bfd69e" FOREIGN KEY (good_job_id) REFERENCES good_jobs(id)

Why am I unable to sort records by a column with a camelCase name

My Cardset table looks like:
create_table "cardsets", force: :cascade do |t|
t.string "name", null: false
t.string "code", null: false
t.integer "setOrder", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["code"], name: "index_cardsets_on_code", unique: true
end
When I run the command:
Cardset.all.order('setOrder ASC')
it responds with:
Traceback (most recent call last):
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column
"setorder" does not exist) LINE 1: SELECT "cardsets".* FROM "cardsets" ORDER
BY setOrder ASC L...
HINT: Perhaps you meant to reference the column "cardsets.setOrder".
: SELECT "cardsets".* FROM "cardsets" ORDER BY setOrder ASC LIMIT $1
Running the query again, sorting by any other field works normally as it should.
I have a Card table which looks exactly the same as the Cardset table, with a cardOrder field and the same problem arises when running a Order query on that table too.
The only way I can get this to work is by encapsulating the column name with "columnName" qoutation marks like so:
Cardset.all.where('"setOrder" ASC')
Can anyone explain what is going on behind the scenes here.
I believe the issue here comes down to your column name, setOrder. If it was lower-cased instead of camel-cased, the issue would go away.
When you created your table, it will look like this in Postgres (excluding a few columns):
database=# \d cardsets
Table "public.cardsets"
Column | Type | Collation | Nullable | Default
----------+-------------------+-----------+----------+--------------------------------------
id | integer | | not null | nextval('cardsets_id_seq'::regclass)
name | character varying | | not null |
code | character varying | | not null |
setOrder | integer | | not null |
Indexes:
"cardsets_pkey" PRIMARY KEY, btree (id)
When ActiveRecord turns your order string argument into a SQL query, without the double quotes you describe, the column name is folded to lower case. See this answer or this answer for more info. setOrder becomes setorder, which doesn't exist on the table, hence the error.
I think you have two options here: change the column name to set_order which is more conventional PostgreSQL, or setorder, both of which will work from ActiveRecord without double quotes, or continue to use double quotes anytime you need ActiveRecord to query that column. If possible I'd recommend the first approach.

How to generate model with the table named "document" with the column of id:int, file:blob, status:enum, exp_date:datetime?

I am using Rails 6 with ActiveStorage.
I have a table of User, Signee, Signature, Signature_template, Position and Document.
I have a table also named "signature_template" with the column of user_id:int, file:blob(this is my question with document), created_at:datetime and updated_at:datetime.
You can create from your terminal
rails generate model Document
And then add migration file as follow
create_table :documents do |t|
t.binary :file
t.integer :status, default: 0
t.datetime :exp_date
end
Rails will automatically create id so you don't need to create it
For binary type it depend with your your database system, here is mapping.
For some famous database system
Database System Mysql will be created as blob,
Postgres -> bytea
SQLite -> blob
Oracle -> blob
For enum you can create as integer, but then in your Document model
you should provide information as follow (change the status to follow your needs)
class Document < ApplicationRecord
enum status: %i(draft verified published)
...
end
And here is some detail for column type in rails for your documentation type
+-------------+---------------------------------------------------------------+
| column type | Description |
+-------------+---------------------------------------------------------------+
| :string | Limited to 255 characters by default, Might be case-sensitive |
| :text | Generally unlimited length depending on database |
| :integer | Whole number, in contrast to :decimal or :float. |
| :decimal | Stored with specified precision. Use for math accuracy. |
| :float | Floating-point decimal number with fixed precision |
| :boolean | True or false. |
| :binary | Raw chunks of data saved in database-specific way. |
| :date | Year, month and day (no time). |
| :time | Hours, minutes, seconds (no date). |
| :datetime | Date and time stored together. |
| :timestamp | Exactly the same as :datetime on Rails. |
+-------------+---------------------------------------------------------------+

I'm using Rails 4.1, How do I set a new models id type as bigint Postgres

I can't use bigint in my Model's id as you normally would if using Rails 5, as I am using rails 4.1
I want my model to have an auto incrementing id as normal, but I want it to be a bigint not a regular integer.
The transaction table is going to contain millions of records and migrating ids later will be a headache, requiring downtime.
I tried first to use a rails generator (like a good lazy dev)
Which gave me
class CreateTransactions < ActiveRecord::Migration
def change
create_table :transactions do |t|
t.bigint :id
#more stuff omitted
end
end
end
Which fails to migrate with
undefined method `bigint' for #<ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::TableDefinition:0x00007fe8df5c9b88>
I already tried modifying that to be
create_table :transactions, id: false do |t|
t.bigint :id
end
This works. The migration runs, but then it fails to auto-increment the id. Meaning, I can't do Transaction.create
or it shouts at me from the db layer about a null constraint on the id.
Apparently you can create a table and then modify it with change_column but that had no reflection in the schema.rb so that worries me.
Also apparently, that ^^ (change_column on id) is an irreversible migration so I want to avoid that.
I know there must be an easy / railsy, nice-nice way to make this work.
EXPECTED RESULT:
Transaction.create
Gives me a new transaction where the id sets itself as normal but it is a big int 8 bits not a normal int 4 bits
I just figured it out from reading postgres' supported data types and guessing how rails would handle it.
https://www.postgresql.org/docs/10/datatype-numeric.html
create_table :transactions, id: :bigserial do |t|
#other model stuff here
end
Verified by connecting directly to postgres
Table "public.transactions"
Column | Type | Collation | Nullable | Default | Storage | Stats target | Description
---------------+-----------------------------+-----------+----------+-----------------------------------------------------+----------+--------------+-------------
id | bigint | | not null | nextval('ghost_card_transactions_id_seq'::regclass) | plain | |

Rails: attempting save an existiing row but rails generates sql insert instead of update

The error
When I have a new record, I don't have a problem.
When I have an existing record I'm trying to update, an exception (see below) is thrown.
The following two variants of Rails code both throw the same exception when I have an existing row in my email_addresses table I wish to update:
ret = #email_address.update(email_address_id: #email_address.email_address_id)
I have also tried
ret = #email_address.save
and get the same exception.
The exception
When I have an existing record I wish to update, the error I'm getting is
#<ActiveRecord::RecordNotUnique: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "email_addresses_pkey"
DETAIL: Key (email_address_id)=(2651) already exists.
: INSERT INTO "email_addresses" ("email_address_id", "email_address", "zipcode", "confirm_token") VALUES ($1, $2, $3, $4) RETURNING "email_address_id">
Where I think the problem is
I think the problem may be related to my not having an id column.
email_address_id serves the same purpose as an id column.
Background
Using psql's \d+ command I get
development=# \d+ email_addresses
Table "public.email_addresses"
Column | Type | Modifiers | Storage | Stats target | Description
--------------------+-------------------+----------------------------------------------------------------------------+----------+--------------+-----------------------------------
email_address_id | integer | not null default nextval('email_addresses_email_address_id_seq'::regclass) | plain | |
email_address | citext | not null | extended | |
unsubscribe_reason | text | not null default ''::text | extended | |
zipcode | text | | extended | | If non-NULL then check confirmed.
email_confirmed | boolean | default false | plain | |
confirm_token | character varying | | extended | |
Indexes:
"email_addresses_pkey" PRIMARY KEY, btree (email_address_id)
"email_address_idx" UNIQUE, btree (email_address)
Referenced by:
TABLE "xref__email_addresses__realtor_organizations" CONSTRAINT "email_address_id_fkey" FOREIGN KEY (email_address_id) REFERENCES email_addresses(email_address_id) ON UPDATE CASCADE ON DELETE RESTRICT
Model
class EmailAddress < ApplicationRecord
validates_presence_of :email_address
validates_format_of :email_address, with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, on: :create
validates_format_of :email_address, with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, on: :save
validates_format_of :email_address, with: /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, on: :update
def initialize(email_address_params)
super(email_address_params)
confirmation_token()
end
# zipcodes can be nil/null in the database but we should never allow a human user to create such a record.
validates_presence_of :zipcode
validates_format_of :zipcode, with: /\A\d{5}(-\d{4})?\z/, message: "zipcode should be in the form 12345", on: :create
def email_activate
self.email_confirmed = true
self.confirm_token = nil
save!(:validate => false)
end
private
def confirmation_token
if self.confirm_token.blank?
self.confirm_token = SecureRandom.urlsafe_base64.to_s
end
end
end
Generated sql code:
Rails generates:
INSERT INTO "email_addresses" ("email_address_id", "email_address", "zipcode", "confirm_token") VALUES ($1, $2, $3, $4) RETURNING "email_address_id" [["email_address_id", 2651], ["email_address", "ralphs#dos32.com"], ["zipcode", "80503"], ["confirm_token", "r-UX4zOdOmHC7lO7Ta2pBg"]]
This will, obviously, fail if email_address_id already exists in the email_addresses table because of the
"email_addresses_pkey" PRIMARY KEY, btree (email_address_id)
implicit constraint on primary keys.
I can probably get this to work by writing SQL code directly in Rails but I'd like to keep with Rails code as well as understanding what I'm doing wrong.
I have searched the web for several hours without success. I've tried tracing through the rails code but it got so complicated with metaprogramming code that I got lost.
Questions
How does Rails know when to do an SQL UPDATE rather than an SQL INSERT?
How do I do I update an existing record given my existing email_addresses table?
Since you are using a non standard id, you need to tell rails the column to use as a primary_key, this way it can evaluate whether a save should insert or update.
To do so you need to add self.primary_key = 'email_address_id' in your model.
Remember Rails favors convention over configuration, so anytime you break convention you need to tell Rails.
P.S.: it seems redundant having the pkey for email_addresses named email_address_id; usually the format of resource_id is conventionally used for fkeys, while for pkeys you can just define the column name.

Resources