In my Rails 4 app I have a goal to see all contacts, where field visible_to in contacts table equal to 1. My visible_to is :integer, array: true.
However, I get the following exception:
PG::UndefinedFunction: ERROR: operator does not exist: integer[] = integer
LINE 1: ....* FROM "contacts" WHERE "contacts"."visible_to" IN (1) OR...
^
HINT: No operator matches the given name and argument type(s). You might need to add explicit type casts.: SELECT "contacts".* FROM "contacts" WHERE "contacts"."visible_to" IN (1) ORDER BY created_at DESC
I searched for answers and as far as I see there is an issue with a type of visible_to. However, I couldn't find the solution. I also tried to get benefit from casts hint, but in vain.
My migration:
class AddVisibleToToContacts < ActiveRecord::Migration
def change
add_column :contacts, :visible_to, :integer, array: true, default: [], using: 'gin'
end
end
Relevant variable from Controller:
#contacts_all_user_sorted = Contact.all.where(visible_to: [1]).order("created_at DESC")
From these two websites:
http://blog.relatabase.com/rails-postgres-arrays
http://adamsanderson.github.io/railsconf_2013/#11
It seems that this syntax should work:
#contacts_all_user_sorted = Contact.all.where("visible_to #> ARRAY[?]", [1])
Does it work?
P.S: As #Quertie pointed out in the comments, you may want to cast the value in the case of a String array, by replacing ARRAY[?] by ARRAY[?]::varchar[]
your migration seems pretty straight forward and correct.
can you please try this:
Contact.where('visible_to IN ?', ['1', '2'])
Related
Newbie here!
ruby 2.7.1, gem --version 3.1.4, Rails 6.0.3.4
I'm getting errors on my query
def entry_uniqueness
if Entry.where("lower(from) = ? AND lower(to) = ?",
from.downcase, to.downcase).exists?
errors.add(:base, 'Identical combination already exists. YUP.')
end
end
Error:
PG::SyntaxError: ERROR: syntax error at or near "from" LINE 1: SELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here' A... ^
Full error
app/models/entry.rb:35:in entry_uniqueness' app/controllers/entries_controller.rb:9:in create' Started POST
"/entries" for ::1 at 2021-02-13 16:17:47 +0800 Processing by
EntriesController#create as HTML Parameters:
{"authenticity_token"=>"98sjupNso6NW5xUonE/414I7ZvJQETMPBNWS+jcN+PffHaAJ3K0pdQofVPgnrBfflYn2SDXMlB17Q2G/gzideA==",
"entry"=>{"translation"=>"1", "from"=>"here", "to"=>"there"},
"commit"=>"Post Translation"} [1m[36mEntry Exists? (10.2ms)[0m
[1m[34mSELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here'
AND lower(to) = 'there') LIMIT $1[0m [["LIMIT", 1]] ↳
app/models/entry.rb:35:in `entry_uniqueness' Completed 500 Internal
Server Error in 38ms (ActiveRecord: 10.2ms | Allocations: 2140)
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: syntax
error at or near "from" LINE 1: SELECT 1 AS one FROM "entries" WHERE
(lower(from) = 'here' A...
^ ): app/models/entry.rb:35:in entry_uniqueness' app/controllers/entries_controller.rb:9:in create'
What I'm trying to achieve is:
| from | to |
-----------------
| Here | there |
=> validation should prevent the user from adding heRE | THERE but can add THERE | heRE
The columns :from and :to are in the table entries.
Note: I have tried scopes, they work on uniqueness but they fail on case-insensitivity 👇
validates_uniqueness_of :from, scope: :to
Also tried
validates_uniqueness_of :from, :scope => :to, :case_sensitive => false
Also tried #r4cc00n 's implementation but it doesn't work
scope :get_entries_by_from_and_to, ->(from, to) { where(arel_table[:from].lower.eq(from)).where(arel_table[:to].lower.eq(to))}
validates_uniqueness_of :from, if: :entry_uniqueness?
def entry_uniqueness?
Entry.get_entries_by_from_and_to('from','to').nil?
end
from is a reserved name in PostgreSQL. This makes a lot of sense because read queries usually contain this word (think of SELECT value FROM table). Therefore the query that Rails builds from your condition
SELECT 1 AS one FROM "entries" WHERE (lower(from) = 'here' A...
^^^^ ^^^^
confused the database. And btw to is a reserved name too.
One way to avoid running into this issue like this you should consider renaming the column.
Another easy workaround is to just wrap the column name in double-quotes. Just change your condition to this:
Entry.exists?('lower("from") = ? AND lower("to") = ?', from.downcase, to.downcase)
Note that I used double-quotes around the column names and single-quotes around the whole condition.
Besides the column naming issues mentioned by #Spickermann you should consider using a citext (case insentive) type column. It has a number of advantages over your approach:
You don't have to use a verbose query. WHERE foo = ? works regardless of case.
You can declare a multi-column unique index to ensure uniqueness on the database level - which is case insensitive. This prevents potential duplicates due to race conditions.
The drawback is that your application will be less portable if you have to switch to another database like MySQL or Oracle which may not be that much of a real concern.
This of course requires you to be able to enable extensions in your Postgres database and you also need to ensure that you are using the same database in testing, dev and production (which is a good idea anyways).
class EnableCitext < ActiveRecord::Migration
def change
enable_extension("citext")
end
end
class ChangeFoosToCitext < ActiveRecord::Migration
def change
change_column :foos, :bar, :citext
change_column :foos, :baz, :citext
add_index :foos, [:bar, :baz], unique: true
end
end
This will let you use a simple validation with a scope:
validates :bar, uniqueness: { scope: :baz }
You don't need to use the case_sentive: false option which generates an ineffective query.
Here is how I think you can fix your query:
You can define a scope within your Entry model and then use that scope everywhere, something like below:
scope :get_entries_by_from_and_to, ->(from, to) { where(arel_table[:from].lower.eq(from)).where(arel_table[:to].lower.eq(to))}
Then you can use it: Entry.get_entries_by_from_and_to('your_from','your_to') and if that returns nil then there is no records in your DB that matches your condition.
With that said if you want to combine that with what you have and with the validation scope you can do it like below:
def entry_uniqueness?
Entry.get_entries_by_from_and_to('your_from','your_to').nil?
end
validates_uniqueness_of :from, if: :entry_uniqueness?
Be aware that the validates_uniqueness_of is not thread/concurrency safe this means that in a really odd case you can run into some scenarios where you will have not unique data in your DB, to avoid this you should always create a unique index within your DB so the DB will avoid those 'duplicated' scenarios for you.
Hope this helps! 👍
I have two tables with schemas:
create_table "posts"
end
create_table "comments" do |t|
t.string post_id
end
My problem is that Post.includes(:comments) results in a postgres error:
ERROR: operator does not exist: character varying = integer
^
HINT: No operator matches the given name and argument type(s).
You might need to add explicit type casts
It's because Post.id is an integer and Comments.post_id is a string and postgres won't auto-cast INTEGER to VARCHAR.
The obvious solution is to make Post.id and Comment.post_id the same type, but I don't want to do that because Comments comes from a separate gem (ActiveAdmin) and I don't want to change my Post.id to a string.
In rails 3, I could use has_many :finder_sql to make this work via an explicit cast.
With finder_sql deprecated in Rails 4, is there any way to make this work?
I'd like to change all non-date formatted strings to date nil, then change a column type. I had planned to use the below:
class SetSubmitDateForAllMyTables < ActiveRecord::Migration
def self.up
MyTable.connection.execute("UPDATE my_table SET submit_date = NULL WHERE NOT ISDATE(submit_date)")
MyTable.connection.execute("UPDATE my_table SET submit_date=created_at WHERE submit_date IS NULL")
change_column :my_table, :submit_date, :date
end
def self.down
end
end
However, the ISDATE function doesn't seem to be valid in POSTGRES. How can I accomplish this?
I am open to using:
MyTable.all.each do |list|
Postgresql can coerce strings to date, but it will error on invalid strings:
dev_db=> select '1/2/2015'::date;
date
------------
2015-01-02
dev_db=> select 'asdf'::date;
ERROR: invalid input syntax for type date: "asdf"
LINE 1: select 'asdf'::date;
^
To get around this, you could create a Postgresql function to check if a string is a valid date, but really it would be much easier to just handle the conversion on each record in your migration.
I already google'd aroung a little bit and seems there's no satisfying answer for my problem.
I have a table with column of type string.
I'd like to run following migration:
class ChangeColumnToBoolean < ActiveRecord::Migration
def up
change_column :users, :smoking, :boolean
end
end
When I run this I get following error
PG::Error: ERROR: column "smoking" cannot be cast automatically to type boolean
HINT: Specify a USING expression to perform the conversion.
: ALTER TABLE "users" ALTER COLUMN "smoking" TYPE boolean
I know I can perform this migration using pure SQL but still it would be nicer if I could do it with Rails. I went through Rails code and seems theres no such possibility, but maybe someone knows a way?
I'm not interested in:
- pure SQL
- dropping the column
- creating another column, converting data, dropping original and then renaming
If your strings in smoking column are already valid boolean values, the following statement will change the column type without losing data:
change_column :users, :smoking, 'boolean USING CAST(smoking AS boolean)'
Similarly, you can use this statement to cast columns to integer:
change_column :table_name, :column_name, 'integer USING CAST(column_name AS integer)'
I am using Postgres. Not sure whether this solution works for other databases.
Not all databases allow changing of column type, the generally taken approach is to add a new column of the desired type, bring any data across, remove the old column and rename the new one.
add_column :users, :smoking_tmp, :boolean
User.reset_column_information # make the new column available to model methods
User.all.each do |user|
user.smoking_tmp = user.smoking == 1 ? true : false # If smoking was an int, for example
user.save
end
# OR as an update_all call, set a default of false on the new column then update all to true if appropriate.
User.where(smoking: 1).update_all(smoking_tmp: true)
remove_column :users, :smoking
rename_column :users, :smoking_tmp, :smoking
So right for boolean in postgres:
change_column :table_name, :field,'boolean USING (CASE field WHEN \'your any string as true\' THEN \'t\'::boolean ELSE \'f\'::boolean END)'
and you may add some more WHEN - THEN condition in your expression
For other database servers, the expression will be constructed based on the syntax for your database server, but the principle is the same. Only manual conversion algorithm, entirely without SQL there is not enough unfortunately.
The syntax change_column :table, :field, 'boolean USING CAST(field AS boolean)' is suitable only if the contents of the field something like: true / false / null
Since I'm using Postgres, I went with SQL solution for now.
Query used:
execute 'ALTER TABLE "users" ALTER COLUMN "smoking" TYPE boolean USING CASE WHEN "flatshare"=\'true\' THEN \'t\'::boolean ELSE \'f\'::boolean END'
It works only if one has a field filled with true/false strings (such as default radio button collection helper with forced boolean type would generate)
I'm trying to perform the following up migration to change the column "number" in the "tweet" model's table
class ChangeDataTypeForTweetsNumber < ActiveRecord::Migration
def up
change_column :tweets do |t|
t.change :number, :integer
end
end
def down
change_table :tweets do |t|
t.change :number, :string
end
end
end
Upon performing the the following up migration to heroku....
heroku rake db:migrate:up VERSION=20120925211232
I get the following error
PG::Error: ERROR: column "number" cannot be cast to type integer
: ALTER TABLE "tweets" ALTER COLUMN "number" TYPE integer
Any thoughts you have would be very much appreciated.
Thanks everyone.
Same as above but a little bit more concise:
change_column :yourtable, :column_to_change, 'integer USING CAST("column_to_change" AS integer)'
From the fine manual:
[ALTER TABLE ... ALTER COLUMN ...]
The optional USING clause specifies how to compute the new column value from the old; if omitted, the default conversion is the same as an assignment cast from old data type to new. A USING clause must be provided if there is no implicit or assignment cast from old to new type.
There is no implicit conversion from varchar to int in PostgreSQL so it complains that column "number" cannot be cast to type integer and the ALTER TABLE fails. You need to tell PostgreSQL how to convert the old strings to numbers to match the new column type and that means that you need to get a USING clause into your ALTER TABLE. I don't know of any way to make Rails do that for you but you can do it by hand easily enough:
def up
connection.execute(%q{
alter table tweets
alter column number
type integer using cast(number as integer)
})
end
You'll want to watch out for values that can't be cast to integers, PostgreSQL will let you know if there are problems and you'll have to fix them before the migration will succeed.
Your existing down-migration should be fine, converting integer to varchar should be handled automatically.