How to type attributes with ruby and mysql ruby gem
(Wednesday, March 17, 2010, at 09:31 PM)
Tonight, I’m working on a new version of our TwitterGrab program. Essentially, we’re moving our program out of rails because it no longer makes sense to rely on that crutch just for it’s solid MySQL connection back to the objects we work with. In doing this, I realized that we had to start re-typing our variables when we pulled them out of the database; by default, everything is an integer, but if we want to automatically re-type them as they roll in, then we need to take another step:
1. Given a result from the database, fetch the details about the given field, then type the variable correctly according to those details
2. return this as an ordered hash, which is then instantiated as an object back in the actual Ruby program, abstracted from the MySQL database.
First, we make our connection to the database, and grab an overview of what it looks like so we know what we’re dealing with. This whole section is from this website :
require ‘rubygems’
require ‘mysql’
dbh = Mysql.real_connect(“localhost”, “testuser”, “testpass”, “test”)
stmt = “select * from table where id = 1”
res = dbh.query(stmt) puts “Statement: #{stmt}” if res.nil? then puts “Statement has no result set” printf “Number of rows affected: %d\n”, dbh.affected_rows else puts “Statement has a result set” printf “Number of rows: %d\n”, res.num_rows printf “Number of columns: %d\n”, res.num_fields res.fetch_fields.each_with_index do |info, i| printf “-Column %d (%s)-\n”, i, info.name printf “table: %s\n”, info.table printf “def: %s\n”, info.def printf “type: %s\n”, info.type printf “length: %s\n”, info.length printf “max_length: %s\n”, info.max_length printf “flags: %s\n”, info.flags printf “decimals: %s\n”, info.decimals end res.free end
This will hopefully produce something like this:
Statement has a result set Number of rows: 1 Number of columns: 14 --- Column 0 (id) --- table: scrapes def: type: 3 length: 11 max_length: 1 flags: 49667 decimals: 0 --- Column 1 (researcher_id) --- table: scrapes def: type: 3 length: 11 max_length: 1 flags: 32768 decimals: 0 --- Column 2 (term) --- table: scrapes def: type: 253 length: 255 max_length: 3 flags: 0 decimals: 0 --- Column 3 (length) --- table: scrapes def: type: 3 length: 11 max_length: 4 flags: 32768 decimals: 0 --- Column 4 (created_at) --- table: scrapes def: type: 12 length: 19 max_length: 19 flags: 128 decimals: 0 --- Column 5 (updated_at) --- table: scrapes def: type: 12 length: 19 max_length: 19 flags: 128 decimals: 0 --- Column 6 (finished) --- table: scrapes def: type: 1 length: 1 max_length: 1 flags: 32768 decimals: 0 --- Column 7 (notified) --- table: scrapes def: type: 1 length: 1 max_length: 1 flags: 32768 decimals: 0 --- Column 8 (sanitized_term) --- table: scrapes def: type: 253 length: 255 max_length: 3 flags: 0 decimals: 0 --- Column 9 (scrape_finished) --- table: scrapes def: type: 1 length: 1 max_length: 1 flags: 32768 decimals: 0 --- Column 10 (folder_name) --- table: scrapes def: type: 253 length: 255 max_length: 8 flags: 0 decimals: 0 --- Column 11 (lock) --- table: scrapes def: type: 253 length: 255 max_length: 0 flags: 0 decimals: 0 --- Column 12 (flagged) --- table: scrapes def: type: 1 length: 1 max_length: 1 flags: 32768 decimals: 0 --- Column 13 (scrape_type) --- table: scrapes def: type: 253 length: 255 max_length: 6 flags: 0 decimals: 0 => nil
So, you can clearly see that there doesn’t appear to be any straightforward way to figure out what the actual type is for the data; this is a problem. It’s ok though, because we can use type to do something else. Type, which you would expect to be some string like “INT”, “TINYINT”, “VARCHAR”, “TEXT”, “DATETIME”, etc is actually just some number; its not any number, however: it’s the number of bytes required to store any particular entry in that row. So, assuming we’re not going to do anything super crazy (figure out down to the byte what everything is worth, but even then this would work so long as two different things need to be typed differently but have the same byte value), we can just use this byte value as an equivalency to types: 1 is a boolean, 12 is a datetime, and anything 253 or over is a string! Let’s do it.
def self.type_attributes(row, query_result)
map_hash = {}
final_row = {}
debugger
type_map = query_result.fetch_fields.collect{|field| map_hash[field.name] = field.type}
row.each_pair do |k,v|
if map_hash[k] == 1
final_row[k] = true
elsif map_hash[k] == 3
final_row[k] = v.to_i
elsif map_hash[k] == 12
final_row[k] = Time.parse(v)
elsif map_hash[k] >= 253
final_row[k] = v
end
end
return final_row
end
Where the string method to_bool is an extension as such:
class String
def to_bool
if self == "1"
return true
elsif self == "0"
return false
else return nil
end
end
end
So, try that? It should work appropriately and re-type all variables accordingly.