How to use Sequel to select one field from database - ruby

I am using Sinatra and Sequel with PostgreSQL.
After authentication, I want to welcome the user by printing their name but I cannot get only the value of the user's name from the database, it comes out as a hash.
The query is:
current_user = DB[:users].select(:username).where('password = ?', password).first
and the resulting piece of data is:
Welcome, {:username=>"Rich"}
which looks rather weird, I would prefer it to read "Welcome, Rich".
What am I doing wrong here? I tried the same query without 'first" at the end and that does not work either.

You can either pull the (single) column you selected out of the Hash you are given:
current_user = DB[:users].select(:username).where('password=?', password).first[:username]
Or you can map your results to an array of usernames and pull the first:
# Using a hash in the filter method is simpler than SQL placeholders.
current_user = DB[:users].filter(password:password).select_map(:username).first
But the best way is to get only the user you care about, and then get the name:
# Using [] on a dataset returns the first row matching the criteria
current_user = DB[:users][password:password][:username]

Try Sequel::Dataset#get. Also, as Phrogz points out, Sequel::Dataset#where can take a hash (it will securely escape values to prevent injection attacks).
current_username = DB[:users].where(password: password).get(:username)
There's also Sequel::Dataset#where_single_value, which is optimized for this exact situation:
current_username = DB[:users].select(:username).where_single_value(password: password)

Related

How to get table name for a simple Sequel Dataset object?

Ie, given a dataset object ds = DB[:transactions].where{updated_at > 1.day.ago} - no funny joins and stuff going on - how could I fetch the table name (:transactions) ?
If you want the first table in the dataset, you can use ds.first_source.
If you want it as a string you can do:
ds.first_source_table.to_s
If you want a symbol, just omit .to_s
Based on the example provided, I would do something like this.
ds.klass.name
That will return a string with the name of your table.

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.

How to create an value array for a specific key in ruby hash?

There are user and user_level models in our rails app. In user_level there is a field called user_group_id. The relationship is userhas_manyuser_levels. We would like to generate an array of the user_group_id (in user_level) for a given user_id.
For a given user_id, its user_levels could be retrieved as :
u = User.find(user_id)
ul = u.user_levels
There may be multiple user_levels for a user. How to create an array of user_group_id from ul with ruby map (or some other ruby method(preferable ruby))? thanks.
Try this,
user = User.find(user_id)
user.user_levels.pluck(:user_group_id)
or this,
user.user_levels.map(&:user_group_id)
The first makes a separate database query selecting just the :user_group_id. For example, in MySQL it would call SELECT user_levels.user_group_id ....
The second collects the :user_group_id from the fetched user_levels.
You are indeed looking for map
user_group_ids = ul.map{|x| x.user_group_id}
Or with the shorthand:
user_group_ids = ul.map(&:user_group_id)
You might also want to have only different ids and no nils
user_group_ids = ul.map(&:user_group_id).uniq.compact

how to say 'is not in' in an active record query

Hey I hope you can help me,
#courses = Course.where(:id != current_user.courses)
I want to get the courses, where the current user is not registered to
#courses = Course.where(:id => current_user.courses) gives me the courses where the user has registered to. But I want the opposite
Associations are just fine. I can use current_user.courses or current_user.assignments.
You probably need something like this:
#courses = Course.where("id NOT IN (?)", current_user.courses)
The reason you can't use a pure array or hash condition is because you're negating ("NOT IN") the query. As far as I know, this can't be done without using the string based syntax. If you just wanted to find matching ("IN") items, you could use the hash form:
#courses = Course.where(:id => current_user.courses)
It's the negating ("NOT IN") part that makes it tricky.
More information can be found in the Active Record Query Interface Guide.
If you wish to avoid using raw strings, you could use ARel
#courses = Course.where(Course.arel_table[:id].not_in(current_users.courses))
Unfortunately, ARel is not documented very well. I use this as a reference.

What is the best way pre filter user access for sqlalchemy queries?

I have been looking at the sqlalchemy recipes on their wiki, but don't know which one is best to implement what I am trying to do.
Every row on in my tables have an user_id associated with it. Right now, for every query, I queried by the id of the user that's currently logged in, then query by the criteria I am interested in. My concern is that the developers might forget to add this filter to the query (a huge security risk). Therefore, I would like to set a global filter based on the current user's admin rights to filter what the logged in user could see.
Appreciate your help. Thanks.
Below is simplified redefined query constructor to filter all model queries (including relations). You can pass it to as query_cls parameter to sessionmaker. User ID parameter don't need to be global as far as session is constructed when it's already available.
class HackedQuery(Query):
def get(self, ident):
# Use default implementation when there is no condition
if not self._criterion:
return Query.get(self, ident)
# Copied from Query implementation with some changes.
if hasattr(ident, '__composite_values__'):
ident = ident.__composite_values__()
mapper = self._only_mapper_zero(
"get() can only be used against a single mapped class.")
key = mapper.identity_key_from_primary_key(ident)
if ident is None:
if key is not None:
ident = key[1]
else:
from sqlalchemy import util
ident = util.to_list(ident)
if ident is not None:
columns = list(mapper.primary_key)
if len(columns)!=len(ident):
raise TypeError("Number of values doen't match number "
'of columns in primary key')
params = {}
for column, value in zip(columns, ident):
params[column.key] = value
return self.filter_by(**params).first()
def QueryPublic(entities, session=None):
# It's not directly related to the problem, but is useful too.
query = HackedQuery(entities, session).with_polymorphic('*')
# Version for several entities needs thorough testing, so we
# don't use it yet.
assert len(entities)==1, entities
cls = _class_to_mapper(entities[0]).class_
public_condition = getattr(cls, 'public_condition', None)
if public_condition is not None:
query = query.filter(public_condition)
return query
It works for single model queries only, and there is a lot of work to make it suitable for other cases. I'd like to see an elaborated version since it's MUST HAVE functionality for most web applications. It uses fixed condition stored in each model class, so you have to modify it to your needs.
Here is a very naive implementation that assumes there is the attribute/property self.current_user logged in user has stored.
class YourBaseRequestHandler(object):
#property
def current_user(self):
"""The current user logged in."""
pass
def query(self, session, entities):
"""Use this method instead of :method:`Session.query()
<sqlalchemy.orm.session.Session.query>`.
"""
return session.query(entities).filter_by(user_id=self.current_user.id)
I wrote an SQLAlchemy extension that I think does what you are describing: https://github.com/mwhite/multialchemy
It does this by proxying changes to the Query._from_obj and QueryContext._froms properties, which is where the tables to select from ultimately get set.

Resources