Cannot use column reference in DEFAULT expression rails migration - ruby-on-rails

Error : PG::FeatureNotSupported: ERROR: cannot use column reference in DEFAULT expression
LINE 1: ..._at" timestamp, "total" decimal DEFAULT (COALESCE(price, (0)...
class AddTotalToOrderLines < ActiveRecord::Migration[6.1]
def up
execute <<~SQL
ALTER TABLE order_lines
ADD COLUMN total numeric GENERATED ALWAYS AS (COALESCE(price, 0) *
COALESCE(quantity, 0)) STORED;
SQL
end

Maybe you have another migration that's actually generating the error. The one you're showing there isn't :)

Related

Create combined index for JSONB column in PostgreSQL

I need to set a combined index on two fields from a JSONB in my PostgreSQL DB. I can set an index for a single field like so (using ActiveRecord in my Rails 6 application):
add_index :my_table,
"(content->'reference')",
using: :gin,
name: 'index_my_table_on_content_reference'
This one works as expected. However, when I try to set a combined index for two fields, I get the following error:
add_index :my_table,
["(content->'reference')", "(content->'ext_id')"],
using: :gin,
name: 'index_my_table_on_content_ref_and_ext_id'
ActiveRecord::StatementInvalid: PG::UndefinedColumn: ERROR: column "(content->'reference')" does not exist
What am I doing wrong and how can I create a combined index for multiple fields in a JSONB column?
And before you ask: Yes, each JSONB blob has a key named reference.
Using: Ruby 2.6.5, Rails 6.0, PostgreSQL 11
The error PG::UndefinedColumn: ERROR: column "(content->'reference')" does not exist means it's treating "(content->'reference')" as a column name.
Reproducing from SQL:
create index my_table_json_idx on my_table using gin(
(content->'reference'),
"(content->'ext_id')"
);
Note the quotes around the second expression.
It seems there is a problem with your ActiveRecord library and it is escaping your jsonb expression when it isn't desirable.
Either use plain SQL or try to make your ActiveRecord library not escape your expression.
After some Googling I think changing the array to string "(content->'reference'), (content->'ext_id')" might work.
Ya Rails doesn't know how to dump your jsonb index using ruby. So SQL would be the way to go. This was a pretty good article for me when I started messing with jsonb Rails & jsonb
def change
reversible do |dir|
dir.up do
execute <<-SQL
CREATE INDEX IF NOT EXISTS idx_my_table_on_content_references on my_table using gin ((content->>'reference'));
SQL
end
dir.down {execute "DROP INDEX IF EXISTS idx_my_table_on_content_references;"}
end
end

Rails audited search

I'm trying to find every items that has been set to a specifique status last year.
I'm using Rail 5 with audited so I created a specific Audit Model and I try to write a scope to return my condition :
Audit = Audited.audit_class
class Audit
scope :followupRebus, -> { where(auditable_type: 'Followup')
.where(action: 'update')
.where("audited_changes LIKE '%step_id:[#{Step::REBUS}%'")
}
end
the content of the audited text field in postgres looks like this when I take it and show it with a .to_s
{"step_id"=>[9, 4], "active"=>[false, true]}
How can I get all audit with step_id = 9 ?
EDIT
Great thanks to DRSE, I finally found a working solution :
changing the default TEXT type of the Column with the migration sent by DRSE
Change the request like this :
class Audit
scope :followupRebus, -> { where(auditable_type: 'Followup')
.where(action: 'update')
.where("((audited_changes -> 'step_id')::json->>0)::int = :step_id", step_id: Step::REBUS)
}
end
You need to use Postgres JSON functions to query the JSON column audited_changes instead of the LIKE operator.
To find audits where the step_id was changed, you can use
.where("(audited_changes -> 'step_id')::jsonb ? :step_id", step_id: '9')
Note the use of the named bind variable :step_id, instead of using active record question mark (?) replacement because Postgres uses the question mark as a JSON query operator.
The clause above will find any audits where step_id = 9, whether that was the value set in the previous version or the updated version of your model.
To find audits where step_id = 9 in the the previous version:
# Check if the first value (indexed by 0) in 'step_id' array is '9'
.where("(audited_changes -> 'step_id')::jsonb->0 ? :step_id", step_id: '9')
To find audits with step_id = 9 set in the updated version:
# Check if the second value (indexed by 1) in 'step_id' array is 9
.where("(audited_changes -> 'step_id')::jsonb->1 ? :step_id", step_id: '9')
(Note: you should not directly string interpolate your conditions for your where clause because you are opening your app to a SQL injection vulnerability. Use rails style conditions with sanitized inputs instead.)
EDIT
Since you have indicated that your audited_changes column type is TEXT and not a JSON type, you will need to either run a migration to change the column type or else cast the column in the query.
To cast the column to JSON at query execution time, use audited_changes::json, so the example would be like this:
.where("(audited_changes::json -> 'step_id')::json ? :step_id", step_id: '9')
To change the column to JSON, start with rails g migration ChangeAuditedChangesColumnToJSONB. Then in your migration file (db/migrate/{timestamp}_change_audited_changes_column_to_jsonb.rb) write:
class ChangeAuditedChangesColumnToJSONB < ActiveRecord::Migration[5.1]
def up
change_column :audits, :audited_changes, :jsonb, using: 'audited_changes::JSONB'
end
def down
change_column :audits, :audited_changes, :text
end
end
Then run rails db:migrate and you should be good to go.
For a new project using Audited, you can add a param to the install step to specify the use of JSON or JSONB type for the audited_changes column.
rails generate audited:install --audited-changes-column-type jsonb # or json

saving a hash to serialize Hash column in mysql but getting error

I have a Rails 3.2.19 app which has the following class:
class Item < ActiveRecord::Base
serialize :mig_serializer_frag, Hash
I have an after_save callback block that looks like this:
after_save do
a={}
a[:name]="Jon"
a[:last_name]="Johnson"
self.update_column(:mig_serializer_frag, a)
end
looking at the sql, it produces:
SQL (0.4ms) UPDATE `items` SET `mig_serializer_frag` = '---\n- :name\n- Jon\n','---\n- :last_name\n- Johnson\n' WHERE `items`.`id` = 1
(0.7ms) ROLLBACK
ActiveRecord::StatementInvalid: Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''---\n- :last_name\n- Johnson\n' WHERE `items`.`id` = 1' at line 1: UPDATE `items` SET `mig_serializer_frag` = '---\n- :name\n- Jon\n','---\n- :last_name\n- Johnson\n' WHERE `items`.`id` = 1
from /Users/jt/.rvm/gems/ruby-2.1.2/gems/activerecord-3.2.19/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:245:in `query'
I clearly see the error but am I not fully understading what a serialize Hash should be. I thought it was a YAML representation which when brought out of db becomes a Hash. Why would rails generate this update statement and how do I fix it?
edit #1
I'm pretty sure that the problem is update_column rather than anything else in your code. update_column doesn't work with serialized attributes as documented in the rails issues.
If you need to set the value after_save you might have to convert to yaml yourself:
self.update_column(:mig_serializer_frag, a.to_yaml)
My guess is the column type is String for that attribute you're trying to serialize.
Change it to Text and rails will be able to perform the conversion to YAML and then serialize it.

I have a following mistake: PG::UndefinedColumn: ERROR: column students.year_course_id does not exist LINE 1

Change my code! I have a following mistake: PG::UndefinedColumn: ERROR: column students.year_course_id does not exist LINE 1: SELECT COUNT() FROM "students" WHERE "students"."year_cour... ^ : SELECT COUNT() FROM "students" WHERE "students"."year_course_id" = $1
PG::UndefinedColumn: ERROR: column students.year_course_id does not
exist LINE 1: SELECT COUNT() FROM "students" WHERE
"students"."year_cour... ^ : SELECT COUNT() FROM "students" WHERE
"students"."year_course_id" = $1
Error clearly states that year_course_id column does not exist in students table.
Create it to resolve the issue.
When you specify
class Student < ActiveRecord::Base
belongs_to :year_course
end
you would need to add year_course_id field in students table referencing year_courses table.
UPDATE
Here is how you can add year_course_id column in students table by generating a migration:
rails generate migration AddYearCourseRefToStudents year_course:references
After this run rake db:migrate
Kirti's answer is correct in that you definitely need to have an id column in the respective, referencing model, however, if you've already got that in place but still getting the error (like myself), here's how I resolved it:
I found saw that I was trying to delegate a different name to the column association in my model, which was tripping this up for some reason. Namely, I had:
has_many :document_doc_types, as: :doc_types
and Rails was barfing with a similar error to what you posted. After I removed the 'as: doc_types' portion, all was well again. So, this works as expected:
has_many :document_doc_types
Hope this helps.

Heroku error with PostgreSQL "could not identify an equality operator for type json"

I'm using JSON type in postgreSQL. I am able to solve the could not identify an equality operator for type json problem on my local machine by adding:
execute <<-SPROC
-- This creates a function named hashjson that transforms the
-- json to texts and generates a hash
CREATE OR REPLACE FUNCTION hashjson(
json
) RETURNS INTEGER LANGUAGE SQL STRICT IMMUTABLE AS $$
SELECT hashtext($1::text);
$$;
-- This creates a function named json_eq that checks equality (as text)
CREATE OR REPLACE FUNCTION json_eq(
json,
json
) RETURNS BOOLEAN LANGUAGE SQL STRICT IMMUTABLE AS $$
SELECT bttextcmp($1::text, $2::text) = 0;
$$;
-- This creates an operator from the equality function
CREATE OPERATOR = (
LEFTARG = json,
RIGHTARG = json,
PROCEDURE = json_eq
);
-- Finaly, this defines a new default JSON operator family with the
-- operators and functions we just defined.
CREATE OPERATOR CLASS json_ops
DEFAULT FOR TYPE json USING hash AS
OPERATOR 1 =,
FUNCTION 1 hashjson(json);
SPROC
However, I do not have the superuser privilege in Heroku. How should I solve the problem?
UPDATE
I am using JSON to store a nested hash, the record is like this:
Company has column properties. properties can be:
{websites:[{url:1,images:[1,2,3],description:1},{url:2,images:[1,2,3],description:2}], wikipedia:{url:1, description:2},facebook:{id:1}}
UPDATE 2
The error message:
PG::UndefinedFunction: ERROR: could not identify an equality operator for type json
LINE 1: SELECT DISTINCT "companies".* FROM "companies" INNER JOIN "reviews...
the error indicator arrow is on SELECT DISTINCT .
the sql is made by the this part in the view:
#review.companies
In model:
class Company < ActiveRecord::Base
store_accessor :properties, [:websites, :fb, :twitter, :linkedin,:country, :city, :street, :postcode]
In migration:
class AddPropertiesToCompany < ActiveRecord::Migration
def change
add_column :companies, :properties, :json
end
end
Thanks for help again.
One solution is to use hstore instead of json field type. The problem with this is casting your existing json to hstore.
Another solution is to use GROUP BY, instead of SELECT DISTINCT, in your queries.
But there are some cases where it would be easier to just change the field type to hstore, for example when using active admin.
I had a HABTM association in one of my models, and active admin would try to query:
db_dev=# SELECT DISTINCT "api_v1_test".* FROM "api_v1_test" INNER JOIN "api_v1_test_users" ON "api_v1_test"."id" = "api_v1_test_users"."test_id" WHERE "api_v1_test_users"."user_id" = 200;
ERROR: could not identify an equality operator for type json
LINE 1: SELECT DISTINCT "api_v1_test".* FROM "api_v1_test" INNER JOI...
Without wanting to spend much time fixing this and figuring out how to rewrite these queries I just converted the field to a hstore and everything is now working as it should.
Also, can you not enter a psql console from heroku to create your functions? or create a new migration for them?

Resources