0

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
5
  • What do you mean when you say it "doesn't work"? Commented Apr 23, 2015 at 17:39
  • 1
    Instead of writing raw SQL to talk to SQLite specifically, instead use an ORM like Sequel. It'll make your life a lot easier, especially once you need to move from SQLite to MySQL or PostgreSQL. Commented Apr 23, 2015 at 18:02
  • Don't use Sequel. As a beginner, you are on the right track. Stay on it. Good luck. Commented Apr 23, 2015 at 18:09
  • @7stud This thing is so full of holes it's scary. Please, do not tell people to do it raw when they're learning. A framework like ActiveRecord or Sequel makes composing queries much more forgiving, allowing you to focus on solving problems rather than flailing with syntax. It's much easier to understand models and relationships at a Ruby level and build down to SQL than to go the other way. Commented Apr 23, 2015 at 18:44
  • @smw Although the learning curve on Rails is a little steeper than plain-old Ruby, if you're building a web application it really is the way to go. You can use a module like Devise to do authentication out-of-the-box where it employs proper password having procedures, and has ties for Oauth (e.g. Google, Twitter or Facebook sign-on) without a lot of extra work. Storing plain-text passwords is extremely dangerous, so you need to be really careful when handling that kind of data. Commented Apr 23, 2015 at 18:49

2 Answers 2

3

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])
Sign up to request clarification or add additional context in comments.

Comments

0
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.