Rails Migration - Constraints not working - ruby-on-rails

I'm a new to Ruby on Rails (switched from Laravel) and don't understand how the migration constraints work.
In my migration file I have:
t.string :username, null: false, limit: 20
t.index :username, unique: true
But when I try to create a user with a username length that exceed the limit of 20 characters (or with no value), it works, only the unique constraint works and give me a warning if I try to create a second user with the same username.
I use sqlite for development. When I look into development.sqlite3 file, everything seems OK:
"username" varchar(20) NOT NULL
If someone could help me, it would be much appreciated :)
Thanks

Related

rails string limit does not prevent too long strings from being inserted in db

I wrote a migration for a Rails 4 project to add a character limit to a string column. I used the following code:
change_column :articles, :name, :string, :limit => 40
The migration with rake db:migrate went fine, resulting in the following line in db/schema.rb:
t.string "name", limit: 40, null: false
But when I enter a string with more than 40 characters in the form (generated by a scaffold), there isn't any error message and the too long string is inserted in the database.
What am I doing wrong ?
Depending on the RDBMS you're using, the field limit won't be validated at the database level. To ensure you're getting great error messages when your constraints are being violated, use ActiveRecord validations.
Try:
validates_length_of :name, maximum: 40
In your model class.

shema_plus with rails migration: make combined columns unique

I'm using the shema_plus gem in rails, and I want to make it so I am unable to duplicate data into my database if the combination of two columns is the same.
shouldn't this work?:
t.string :name, null: false, index: { with: :deleted_at, unique: true }
':deleted_at' is definitely a field in my migration.
but this allows me to enter in the same name twice on the mysql side.
also, here is some info that mysql gives me:
Index: index_plans_on_name_and_deleted_at
Definition:
Type BTREE
Unique Yes
Columns name
deleted_at
EDIT_____________________________________________________
It looks like I needed a default value for deleted_at, like :
t.datetime :deleted_at, default: '2000-01-01'
Not sure about the gem, but you can directly achieve this in ActiveRecord using:
validates :name, :uniqueness => {:scope => :deleted_at}
assuming you want the combination of name and deleted_at to be unique.

Rails & SQLite: migration to add unique index and allow null values

I want to force e-mails to be unique at the database level, but I also want to allow for null e-mail values. I get the following error with the migration.
Current Schema
create_table "users", force: true do |t|
t.string "email", default: ""
...
end
add_index "users", ["email"], name: "index_users_on_email"
Attempted Migration
remove_index :users, [:email]
change_column :users, :email, :string, :null => true
add_index :users, [:email], unique: true
Error with migration
== RemoveUniqueEmailFromUsers: reverting =====================================
-- remove_index(:users, [:email])
-> 0.0437s
-- change_column(:users, :email, :string, {:null=>true})
-> 0.2664s
-- add_index(:users, [:email], {:unique=>true})
rake aborted!
An error has occurred, this and all later migrations canceled:
SQLite3::ConstraintException: indexed columns are not unique: CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email")
Try to remove the default: "". I think you have contradicting configs.
Empty string is not nil.
The problem was that the last user I created had the email of ''. After destroying this user the migration proceeded.
If you see the SQLite::ConstraintException after you've run $ rake db:migrate:
SQLite::ConstraintException : indexed columns are not unique CREATE UNIQUE INDEX "index_users_on_email" ON "users"
... and you're working in the dev environment, and you do not need the data in the development database, you can fix this rather quickly.
You can find out if you have existing users in your database and delete them all like this:
$ rails console
> User.count
=> 4
> User.destroy_all
> ...
> User.count
=> 0
Then you can run $ rake db:migrate again, and the migrations should run successfully.
If you're not sure, you can run $ rake db:migrate:status. If all migrations are "up," then your migrations have run successfully.
Here's how to understand the problem:
You have existing users without email values (users table has no email attribute).
When you run the migration the first part adds the email column.
Now, all of your users have an email attribute, but with a null value in the database.
The last part of the migration tries to add an index, with the condition that the values are unique (that's what unique: true specifies).
The unique constraint is impossible to add to the database because you now have an email column for the users table, which all have null values, making them non-unique values. SQLite is throwing an error, stating that you cannot add a unique constraint to a column that has non-unique values.
This can be a good thing if you're trying to add this constraint to a database column in production mode! Thanks for protecting us, SQLite!
Once you understand the problem, you can see that there are other possible solutions, but if you're in the dev environment on a new application, this will probably fit your needs.

Rails :uniqueness validation not finding previous records

I'm having a bizzarre glitch, where Rails does not validate the uniqueness of an attribute on a model, despite the attribute being saved perfectly, and despite the validation being written correctly.
I added a validation to ensure the uniqueness of a value on one of my Rails models, Spark, with this code:
validates :content_hash, :presence => true, :uniqueness => true
The content_hash is an attribute created from the model's other attributes in a method called using a before_validation callback. Using the Rails console, I've confirmed that this hash is actually being created before the validation, so that is not the issue.
When I call in the Rails console spark.valid? on a spark for which I know a collision exists on its content_hash, the console tells me that it has run this query:
Spark Exists (0.2ms) SELECT 1 AS one FROM "sparks" WHERE "sparks"."content_hash" = '443524b1c8e14d627a3fadfbdca50118c6dd7a7f' LIMIT 1
And the method returns that the object is valid. It seems that the validator is working perfectly fine, and is running the correct query to check the uniqueness of the content_hash, the problem is instead on the database end (I'm using sqlite3). I know this because I decided to check on the database myself to see if a collision really exists using this query:
SELECT "sparks".* FROM "sparks" WHERE "sparks"."content_hash" = '443524b1c8e14d627a3fadfbdca50118c6dd7a7f'
Bizarrely, this query returns nothing from the database, despite the fact that I can see with my own eyes that other records with this content_hash exist on the table.
For some reason, this is an issue that exists exclusively with the content_hash attribute of the sparks table, because when I run similar queries for the other attributes of the table, the output is correct.
The content_hash column is no different from the others which work as expected, as seen in this relevant part of my schema.rb file:
create_table "sparks", :force => true do |t|
t.string "spark_type"
t.string "content_type"
t.text "content"
t.text "content_hash"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
Any help on this problem would be much appreciated; I'm about ready to tear my hair out over this thing.
Okay, I managed to fix the problem. I think it was an sqlite3 issue, because everything worked perfectly once I changed the type of content_hash from a text column to a string column. Weird.

Why is ActiveRecord/PostgreSQL not finding my data in the database?

I'm sure this is something simple I'm overlooking but since I've been dealing with this strange issue for a few days now I'm asking for help.
Here is my apps setup and the issue:
A Rails 3.2.13 app with the pg gem and the following db scheme:
create_table "clients", :force => true do |t|
t.string "uuid"
t.string "private_key"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "clients", ["private_key"], :name => "index_clients_on_private_key"
add_index "clients", ["uuid"], :name => "index_clients_on_uuid"
I currently have two rows in my "clients" database. They pictured below:
The issue
When I perform a simple "SELECT * FROM clients;" using the RubyMine database console I get the rows pictured in the screen shot above. When I perform a "SELECT * FROM clients WHERE id = 11;" I get that row, all is well there. However, the issue is that when I try to perform a "SELECT * FROM clients WHERE private_key = "MkRBNUZBQzUtMzVDRi00NzQ3LThFNjEtNjI4OThERUQzQkRF";" it fails and shows the following in the console output...
sql> SELECT * FROM clients WHERE private_key = "MkRBNUZBQzUtMzVDRi00NzQ3LThFNjEtNjI4OThERUQzQkRF"
[2013-04-17 20:09:51] [42703] ERROR: column "MkRBNUZBQzUtMzVDRi00NzQ3LThFNjEtNjI4OThERUQzQkRF" does not exist
Position: 43
I've also tried pulling this row out of the db by utilizing the active record helper methods...
Client.exists?(:private_key => token)
Client.find_by_private_key(token)
However, nothing works, it never finds the record that does exist in the db. Does anyone have any idea what is going on here? All I need to be able to do is utilize the active record helpers, specifically the "Client.exists?(:private_key => token)" to be able to check and see if the supplied token exists in the db.
Thanks for your help!
Update 4/18/13 # 9:30am
I just tried Client.find(12) just to see if it would find the record by id, and this works. This doesn't help me understand why I still can't use Client.exists?(:private_key => token) or Client.find_by_private_key(token).
Update 4/19/13 # 9:15am
The answer to this problem was that the code generating my private_key value was adding a return/white space to the end of the value. So when trying to query the db for row based on a private_key it was always failing to find anything.
To fix the problem I added a gsub to the end of the code that generates the private_key, like this...
private_key = SecurityHelper.encrypt_private_key(client_uuid).gsub(/\s+/, "")
This strips all white space from the generated private key and solved the problem.
You're using the wrong quoting style. ANSI SQL quoting as used in PostgreSQL is:
"SELECT * FROM clients WHERE private_key = 'MkRBNUZBQzUtMzVDRi00NzQ3LThFNjEtNjI4OThERUQzQkRF';"
Note the single quotes.
It isn't clear how the first statement even got parsed by Ruby, since the inner double quotes would've ended the quoted string, leading to:
"SELECT * FROM clients WHERE private_key = "MkRBNUZBQzUtMzVDRi00NzQ3LThFNjEtNjI4OThERUQzQkRF";"
^^^^
should be Ruby syntax error here
I don't really do Rails so I can't speak for the correct Rails syntax to do this check via ActiveRecord, though.
Have you tried
Client.exists?(:private_key => "MkRBNUZBQzUtMzVDRi00NzQ3LThFNjEtNjI4OThERUQzQkRF")

Resources