Trapping constraint errors in Sequel ORM before the server - ruby

I have a Ruby/Padrino project using Sequel ORM to access a legacy database in MySQL. The problem I am having is that several of the tables have a user editable column that is also a Primary Key.
I am trying to set up Sequel to catch any duplication errors within the application itself before MySQL can throw an error. I assumed that the validation helpers in Sequel could help me do that, but it appears they do not kick in before the data is saved.
My SQL table is defined as:
CREATE TABLE `dblocations` (
`code` varchar(3) NOT NULL,
`description` varchar(100) NOT NULL,
PRIMARY KEY (`code`)
)
and my Sequel models are defined as follows:
class Location < Sequel::Model(:dblocations)
plugins :validation_helpers
def validate
super
validates_presence [:code, :description]
validates_unique :code
end
end
the problem is now, when I try the following code to insert Location Code 'ABC' into the database (when there is already another row in there with the Code 'ABC'), Ruby throws the following error:
Location.insert(:code => "ABC", :description => "Test Location")
ERROR - Mysql2::Error: Duplicate entry 'ABC' for key 'PRIMARY': INSERT INTO `dblocations` (`code`, `description`) VALUES ('ABC', 'Test Location')
Sequel::UniqueConstraintViolation: Mysql2::Error: Duplicate entry 'ABC' for key 'PRIMARY'
Have I misconfigured my validations or is there another plugin I need to invoke?

insert is a dataset-level method, validations are model level. You probably want to use create instead of insert.

Related

How to get auto-generated row primary key via Ruby PostgreSQL gem?

My table in PostgreSQL looks like this:
CREATE TABLE user (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL
)
I'm using Ruby pg gem to add a record to the table:
c = PG.connect(dbname: 'foo')
id = c.exec_params('INSERT INTO user (name) VALUES ($1)', ['Jeff']).oid_value
I'm getting nil back, instead of the auto-generated id of the new record. What is the right way to get it back?
According to the docs, if the object id you're retrieving isn't set, thus nil.
You either have to set the object id yourself, or use returning like this:
res = conn.exec("INSERT INTO users (name) VALUES ('john') returning id")
res[0]['id']
#=> 1

cakebake not working with prefix in cakephp3

I am using cakephp 3.4.9. When I am using a table with prefix n field its working properly after baking but if I use prefix in table fields its not working.
Like when I am using post with following fields like
id,
post,
date
it's working fine but if I use following fields its not working
p_id,
p_post,
p_date
it is adding extra codes in model
$this->belongsTo('Ps', [
'foreignKey' => 'p_id',
'joinType' => 'INNER'
]);
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['p_id'], 'Ps'));
return $rules;
}
why ps is adding here? If I use articales table like same its become As.
Please help.
I would like to suggest you, read this article.
CakePHP naming convention documentation
In cakePHP framework everything you have to keep in mind while creating the table is the CakePHP naming conventions. In your case, This is happening because cakePHP expects the primary column of any table will be only 'id', and the foreign key for the table will be the Related table name with an underscore id
(ex: If product table BelogsTO categories you have to make a column in your product table as category_id)
In your case cakePHP considering p_id as a foreign key for the table P. And by default cakePHP has a validation for the forein key that the existsIn which means that while saving that p_id, it will check for the existance of id in P table.
In one sentense this is because of the naming convention issue. You can change only p_id to id and keeping other things same will work for you.
HAPPY CODING :)

Rails 3 find_or_create_by_slug sometimes creates duplicate records

I am using Rails 3.2.14 with Ruby 2.1.0. I have a method which takes care of creating a user in DB by checking if the email id exists or not.
I use devise and SAML for user authentication and session creation.
I use the following code to do so:
#user = User.find_or_create_by_email(:email=>"xyz#gmail.com", firstname: 'XY', lastname: 'Z', code: 'ABC')
This works in most of the cases, but rest of the time I keep getting error notification emails in my account as the above code tries to create one more user with the same email:
An ActiveRecord::RecordNotUnique occurred in sessions#consume:
Mysql2::Error: Duplicate entry 'xyz#gmail.com' for key 'email'
How I can avoid this error?
You need to use something like Upsert
find_or_create will first run SELECT and then INSERT. Another instance could have run the INSERT in between. You could also maybe use a transaction:
User.transaction do
#user = User.find_or_create_by_email(:email=>"xyz#gmail.com", firstname: 'XY', lastname: 'Z', code: 'ABC')
end

writing basic Ruby code to register user in SQLite database

I need some help writing basic Ruby code to register a user in a SQLite database. I'm very new to Ruby, I checked lots of good examples online but my code still doesn't work.
This is my 1st test project using Ruby, so appreciate any help and apologise for making any bad mistakes.
require 'sqlite3'
def register_user(l)
user = l[1]
pass = l[2]
db = SQLite3::Database.new "database.db"
db.execute("INSERT INTO users (user, pass)
VALUES (#{user}, #{pass})")
end
def cmd_register(l)
if register_user(#{#nick}, l[1])
sv_send 'NOTICE', 'REGISTER', ':*** User created'
else
sv_send 'NOTICE', 'REGISTER', ':*** User not created'
end
end
There are a few problems with your code. First, here:
db.execute("INSERT INTO users (user, pass)
VALUES (#{user}, #{pass})")
You're trying to generate a query that looks like this (supposing the variable user contains "Jordan" and pass contains "xyz"):
INSERT INTO users (user, pass) VALUES('Jordan', 'xyz')
...but your code generates a query that looks like this:
INSERT INTO users (user, pass) VALUES(Jordan, xyz)
Do you see the difference? Values in SQL queries need to be surrounded by quotation marks. Your query will fail because SQLite doesn't know what Jordan is; it only knows what 'Jordan' is.
You could just add quotation marks to your query, but then you would have another problem: SQL injection attacks. Because you're just blindly putting the values of user and pass into your query, an attacker could manipulate those values to perform a different query than you intended. Never use string interpolation (#{var}) or concatenation (+ or <<) when creating an SQL query. (For a brief description of how SQL injection attacks work, read the "How to get hacked" section on this page: http://ruby.bastardsbook.com/chapters/sql/.)
The correct way to use variables in a query is with prepared statements and parameter binding. It looks like this:
statement = db.prepare("INSERT INTO users (user, pass) VALUES (?, ?)")
statement.bind_params(user, pass)
result = statement.execute
What this does is automatically escapes the values of user and pass to make sure they don't do anything you don't expect, wraps them in quotation marks, and substitutes them for the question marks in the query. Another way to do the same thing is this:
result = db.execute("INSERT INTO users (user, pass) VALUES (?, ?)", user, pass)
The other obvious problem with your code is this:
if register_user(#{#nick}, l[1])
This is a syntax error. You can only use the #{var} syntax in a string, like "hello #{var}". In this case you just want to do this:
if register_user(#nick, l[1])
require "sqlite3"
my_db = SQLite3::Database.new "my_db1.db"
my_db.execute <<END_OF_CREATE #The <<END_OF_CREATE .... END_OF_CREATE thing is called HEREDOC syntax, which is one way to create a String that spans multiple lines
CREATE TABLE IF NOT EXISTS users( #A useful SQL command to be aware of.
name varchar(30),
password varchar(30)
);
END_OF_CREATE
def register_user(target_db, user_info)
user_name, user_pass = user_info #A neat trick for "unpacking" an Array
target_db.execute(
"INSERT INTO users (name, password)
VALUES (?, ?)", user_name, user_pass #For security reasons, inserts into a db should use this "question mark" format.
)
end
register_user(my_db, ['John', 'abc123'])
register_user(my_db, ['Jane', 'xyz456'])
my_db.execute("SELECT * FROM users") do |row|
p row #Use the 'p' method instead of puts to print an Array or Hash.
end
--output:--
["John", "abc123"]
["Jane", "xyz456"]
Also, don't ever name a variable l. You absolutely, no exceptions, have to use descriptive variable names. See the code above for an example.
Even though the code above unpacks the user_info array into separate variables, that is actually not required because execute() will take an
Array as an argument:
target_db.execute(
"INSERT INTO users (name, password)
VALUES (?, ?)", user_info
)
In other words, all the values for the question marks can be gathered into an Array and provided as the last argument for execute().
One problem you can run into when writing and testing database programs is when you change one of the column names in your table. The code above will cause an error: the table will not be re-created because the table already exists, but your new code will use the new column name, which won't exist in the table.
So, you might consider using this combination of sql statements:
my_db.execute <<END_OF_DROP
DROP TABLE IF EXISTS users
END_OF_DROP
my_db.execute <<END_OF_CREATE
CREATE TABLE users(
name varchar(30),
password varchar(30)
);
END_OF_CREATE
With those sql statements, if you change one of the column names (or add a column), then your new code won't throw an error because the table is destroyed and recreated with the new column names every time you run your program.

Sequel Migration update with a row's ID

How can you run a Sequel migration that updates a newly added column with a value from the row?
The Sequel documentation shows how you can update the column with a static value:
self[:artists].update(:location=>'Sacramento')
What I need to do is update the new column with the value of the ID column:
something like:
self[:artists].each |artist| do
artist.update(:location => artist[:id])
end
But the above doesn't work and I have been unable to figure out how to get it to go.
Thanks!
artist in your loop is a Hash, so you are calling Hash#update, which just updates the Hash instance, it doesn't modify the database. That's why your loop doesn't appear to do anything.
I could explain how to make the loop work (using all instead of each and updating a dataset restricted to the matching primary key value), but since you are just assigning the value of one column to the value of another column for all rows, you can just do:
self[:artists].update(:location=>:id)
if you need update all rows of a table, because it is a new column that need be populate
artists = DB[:artists]
artists.update(:column_name => 'new value')
or if you need, update only a unique row into your migration file you can:
artists = DB[:artists]
artists.where(:id => 1).update(:column_name1 => 'new value1', :column_name2 => "other")

Resources