I'm not claiming that there are other ways to do it. I just had <30mins to get the app working in production. Here's how I did it:
1st we monkey patch Struct with a to_json() method
class Struct def to_map map = Hash.new self.members.each { |m| map[m] = self[m] } map end def to_json(*a) to_map.to_json(*a) end end
We're going to respond with a bit of JSON, so we ensure there's a handy struct.
payload = Struct.new(:status, :messages, :value)
We make our connection:
DB = Sequel.mysql( :database => 'your_db_name', :user => 'user', :password => 'password', :host => '192.168.1.1', :port => 3306, :encoding => 'utf8' )
Now here is some nasty SQL:
nasty_sql = " select t1.field1, t2.field2, t3.field3, t4.field4, t5.field5 from db1.table1 as t1 left join db1.table2 as t2 on t2.someId = t1.someId left join db1.table3 as t3 on t3.someId = t2.someId left join db1.table4 as t4 on t4.someId = t3.someId left join db2.table5 as t5 on t5.someId = t4.someId left join db2.table6 as t6 on t6.someId = t5.someId where t1.someField = ? and t2.someField is not null and t2.someField = 1 and t2.someField in ('Gonzo','Journalist','Greatest Articles') and t3.someField is not null and t4.someField = 1 and t5.someField = ? order by t1.someField, t2.someField"
Ignore the obvious 'why not a stored proc?' and 'crap! change the tables!' responses. Imagine you don't have a choice. And you're running out of time and your manager is staring pointedly at you. You're patching production and you'll be fired if it doesn't work RIGHT NOW.
Hypothetical anyway, as it's an article about how to do it, not why the hell you should do it in the first place.
Sooooo.... Anyway... We have a route:
get '/some/:f1/:f2' do begin result = payload.new(true, [], []) DB[nasty_sql, params[:f1], params[:f2]].each do |row| result.value << row end JSON.pretty_generate(result) rescue Sequel::DatabaseError => e # log it end end
When we do this, we get a nasty bunch of errors based around the fact that although the underlying connection is UTF-8, we get back blobs or strings as binary data so their encoding is ASCII-8BIT.
So here's how you solve it. First ensure you actually fix the encoding issue on a per-row basis:
get '/some/:f1/:f2' do begin result = payload.new(true, [], []) DB[nasty_sql, params[:f1], params[:f2]].each do |row| result.value << fix_encoding_issue(row) end JSON.pretty_generate(result) rescue Sequel::DatabaseError => e # log it end end
And add the method:
# This is required because when using raw SQL, the data comes back as a blob. # The actual data is UTF-8 but because the string is marked as binary it is treated as ASCII-8BIT. # Thus a row must have all strings coerced to ther actual encoding.
# Also the keys... Just in case... def fix_encoding_issue(row) item = Hash.new row.each_pair do |k, v| if v.kind_of? String item[k.to_s.force_encoding('UTF-8')] = v.force_encoding('UTF-8') else item[k.to_s.force_encoding('UTF-8')] = v end end item end
And your encoding issues are ameliorated.
No comments:
Post a Comment