Ruby and mysql2- iterating a subset of rows in result set - mysql

I'm writing a Ruby script that accesses a MySQL database using the Mysql2 gem.
After I get a set of results from a query, I want to examine a subset of the rows of the result set (e.g. rows 5 to 8) rather than the whole set.
I know I can do this with a while loop, something like this:
db = Mysql2::Client.new(:host => "myserver", :username => "user", :password => "pass", :database => "books")
rs = db.query "select * from bookslist"
i = 5
while i <= 8
puts rs.entries[i]
i += 1
end
db.close
But I'm aware this is probably not the best way to write this in Ruby. How can I make this more "idiomatic" Ruby code?
(I know my other option is to modify the query to return only the data I want. I still want to know how to do this in Ruby)

Ruby provides range operator in for loop, Probably you can use following code
for i in 5..8
puts rs.entries[i]
end

Related

Number Stored as Text when Writing Query to a Worksheet

I am creating a report using the following gems:
require "mysql2"
require "watir"
require "io/console"
require "writeexcel"
After I query a database with mysql2 and convert the query into a multidimensional array like so:
Mysql2::Client.default_query_options.merge!(:as => :array)
mysql = Mysql2::Client.new(:host => "01.02.03.405", :username => "user", :password => "pass123", :database => "db")
report = mysql.query("SELECT ... ASC;")
arr = []
report.each {|row| arr << row}
and then finally write the data to an Excel spreadsheet like so:
workbook = WriteExcel.new("File.xls")
worksheet = workbook.add_worksheet(sheetname = "Report")
header = ["Column A Title", ... , "Column N Title"]
worksheet.write_row("A1", header)
worksheet.write_col("A2", arr)
workbook.close
when I open the file in the latest edition of Excel for OSX (Office 365) I get the following error for every cell containing mostly numerals:
This report has a target audience that may become distracted with such an error.
I have attempted all the .set_num_format enumerable methods found in the documentation for writeexcel here.
How can I create a report with columns that contain special characters and numerals, such as currency, with write excel?
Should I look into utilizing another gem entirely?
Define the format after you create the worksheet.
format01 = workbook.add_format
format01.set_num_format('#,##0.00')
then write the column with the format.
worksheet.write_col("A2", arr, format01)
Since I'm not a Ruby user, this is just a S.W.A.G.

Is it possible to implement SUBSTRING_INDEX logic using Ruby Sequel to create a column alias?

I have a client who has a database of images/media that uses a file naming convention that contains a page number for each image in the filename itself.
The images are scans of books and page 1 is often simply the cover image and the actual “page 1” of the book is scanned on something like scan number 3. With that in mind the filenames would look like this in the database field filename:
great_book_001.jpg
great_book_002.jpg
great_book_003_0001.jpg
great_book_004_0002.jpg
great_book_005_0003.jpg
With that in mind, I would like to extract that page number from the filename using MySQL’s SUBSTRING_INDEX. And using pure MySQL it took me about 5 minutes to come up with this raw query which works great:
SELECT `id`, `filename`, SUBSTRING_INDEX(SUBSTRING_INDEX(`filename`, '.', 1), '_', -1) as `page`
FROM `media_files`
WHERE CHAR_LENGTH(SUBSTRING_INDEX(SUBSTRING_INDEX(`filename`, '.', 1), '_', -1)) = 4
ORDER BY `page` ASC
;
The issue is I am trying to understand if it’s possible to implement column aliasing using SUBSTRING_INDEX while using the Sequel Gem for Ruby?
So far I don’t seem to be able to do this with the initial creation of a dataset like this:
# Fetch a dataset of media files.
one_to_many :media_files, :class => MediaFiles,
:key => :id, :order => :rank
Since the returned dataset is an array, I am doing is using the Ruby map method to roll through the fetched dataset & then doing some string processing before inserting a page into the dataset using the Ruby merge:
# Roll through the dataset & set a page value for files that match the page pattern.
def media_files_final
media_files.map{ |m|
split_value = m[:filename].split(/_/, -1).last.split(/ *\. */, 2).first
if split_value != nil && split_value.length == 4
m.values.merge({ :page => split_value })
else
m.values.merge({ :page => nil })
end
}
end
That works fine. But this seems clumsy to me when compared to a simple MySQL query which can do it all in one fell swoop. So the question is, is there any way I can achieve the same results using the Sequel Gem for Ruby?
I gather that perhaps SUBSTRING_INDEX is not easily supported within the Sequel framework. But if not, is there any chance I can insert raw MySQL instead of using Sequel methods to achieve this goal?
If you want your association to use that additional selected column and that filter, just use the :select and :conditions options:
substring_index = Sequel.expr{SUBSTRING_INDEX(SUBSTRING_INDEX(:filename, '.', 1), '_', -1)}
one_to_many :media_files, :class => MediaFiles,
:key => :id, :order => :page,
:select=>[:id, :filename, substring_index.as(:page)],
:conditions => {Sequel.function(:CHAR_LENGTH, substring_index) => 4}

Rails custom query based on params

I have zero or many filter params being sent from a json request.
the params may contain:
params[:category_ids"]
params[:npo_ids"]
etc.
I am trying to retreive all Projects from my database with the selected ids. Here is what I have currently:
def index
if params[:category_ids].present? || params[:npo_ids].present?
#conditions = []
#ids = []
if params["category_ids"].present?
#conditions << '"category_id => ?"'
#ids << params["category_ids"].collect{|x| x.to_i}
end
if params["npo_ids"].present?
#conditions << '"npo_id => ?"'
#ids << params["npo_ids"].collect{|x| x.to_i}
end
#conditions = #ids.unshift(#conditions.join(" AND "))
#projects = Project.find(:all, :conditions => #conditions)
else ...
This really isn't working, but hopefully it gives you an idea of what I'm trying to do.
How do I filter down my activerecord query based on params that I'm unsure will be there.
Maybe I can do multiple queries and then join them... Or maybe I should put a filter_by_params method in the Model...?
What do you think is a good way to do this?
In rails 3 and above you build queries using ActiveRelation objects, no sql is executed until you try to access the results, i.e.
query = Project.where(is_active: true)
# no sql has been executed
query.each { |project| puts project.id }
# sql executed when the first item is accessed
The syntax you are using looks like rails 2 style; hopefully you are using 3 or above and if so you should be able to do something like
query = Project.order(:name)
query = query.where("category_id IN (?)", params[:category_ids]) if params[:category_ids].present?
query = query.where("npo_ids IN (?)", params[:npo_ids]) if params[:npo_ids].present?
#projects = query
I solved this. here's my code
def index
if params[:category_ids].present? || params[:npo_ids].present?
#conditions = {}
if params["category_ids"].present?
#conditions["categories"] = {:id => params["category_ids"].collect{|x| x.to_i}}
end
if params["npo_ids"].present?
#conditions["npo_id"] = params["npo_ids"].collect{|x| x.to_i}
end
#projects = Project.joins(:categories).where(#conditions)
else
basically it stored the .where conditions in #conditions, which looks something like this when there's both categories and npos:
{:categories => {:id => [1,2,3]}, :npo_id => [1,2,3]}
Then inserting this into
Project.joins(:categories).where(#conditions)
seems to work.
If you're filtering on a has_many relationship, you have to join. Then after joining, make sure to call the specific table you're referring to by doing something like this:
:categories => {:id => [1,2,3]}

Ruby, MySQL2: check if the result is empty

I am using MySQL2 in Ruby to query a database. What is a direct way to check whether the result of a query is empty?
The code looks like:
require 'mysql2'
client = Mysql2::Client.new(:host => "localhost", :username => "root")
results = client.query("SELECT * FROM users WHERE group='githubbers'")
The Mysql2 documentation is indeed very poor. But by inspecting the type of results you will notice that it is a Mysql2::Result which contains 3 methods. The one that you are interested in is count (or alias size) which will return the number of rows of the result.
From here you can easily check if it is 0:
(results.count == 0)
Alternatively you could open the Mysql2::Result class and add the method empty? yourself:
class Mysql2::Result
def empty?
(count == 0)
end
end
And then you can just do:
results.empty?
0 == results.size
will return true if results is empty. AFAIK there is no direct method (such as Array#empty?) , but you could monkey patch it.

How to obtain result as array of Hashes in Ruby (mysql2 gem)

I'm using Ruby's mysql2 gem found here:
https://github.com/brianmario/mysql2
I have the following code:
client = Mysql2::Client.new(
:host => dbhost,
:port => dbport, :database => dbname,
:username => dbuser,
:password => dbpass)
sql = "SELECT column1, column2, column3 FROM table WHERE id=#{id}"
res = client.query(sql, :as => :array)
p res # prints #<Mysql2::Result:0x007fa8e514b7d0>
Is it possible the above .query call to return array of hashes, each hesh in the res array to be in the format column => value. I can do this manually but from the docs I was left with the impression that I can get the results directly loaded in memory in the mentioned format. I need this, because after that I have to encode the result in json anyway, so there is no advantage for me to fetch the rows one by one. Also the amount of data is always very small.
Change
res = client.query(sql, :as => :array)
to:
res = client.query(sql, :as => :hash)
As #Tadman says, :as => :hash is the default, so actually you don't have to specify anything.
You can always fetch the results as JSON directly:
res = client.query(sql, :as => :json)
The default format, as far as I know, is an array of hashes. If you want symbol keys you need to ask for those. A lot of this is documented in the gem itself.
You should also be extremely cautious about inserting things into your query with string substitution. Whenever possible, use placeholders. These aren't supported by the mysql2 driver directly, so you should use an adapter layer like ActiveRecord or Sequel.
The source code for mysql2 implemented MySql2::Result to simply include Enumerable, so the obvious way to access the data is by using any method implemented in Enumerabledoc here.
For example, #each, #each_with_index, #collect and #to_a are all useful ways to access the Result's elements.
puts res.collect{ |row| "Then the next result was #{row}" }.join("\t")