Rails database specific queries for Heroku - mysql

I've been deploying some apps to Heroku recently. I run MySQL on my local dev machine and have spent a little while updating some of my scopes to work in PostgreSQL. However one i have received an error on is proving difficult to change.
For the time being i've got a database specific case statement in my model. I understand why the error regarding the MySQL date functions is occurring, but im not sure if this is the most efficient solution. Does anyone have a better way of implementing a fix that will work with both MySQL and PostgreSQL?
case ActiveRecord::Base.connection.adapter_name
when 'PostgreSQL'
named_scope :by_year, lambda { |*args| {:conditions => ["published = ? AND (date_part('year', created_at) = ?)", true, (args.first)], :order => "created_at DESC"} }
named_scope :by_month, lambda { |*args| {:conditions => ["published = ? AND (date_part('month', created_at) = ?)", true, (args.first)], :order => "created_at DESC"} }
named_scope :by_day, lambda { |*args| {:conditions => ["published = ? AND (date_part('day', created_at) = ?)", true, (args.first)], :order => "created_at DESC"} }
else
named_scope :by_year, lambda { |*args| {:conditions => ["published = ? AND (YEAR(created_at) = ?)", true, (args.first)], :order => "created_at DESC"} }
named_scope :by_month, lambda { |*args| {:conditions => ["published = ? AND (MONTH(created_at) = ?)", true, (args.first)], :order => "created_at DESC"} }
named_scope :by_day, lambda { |*args| {:conditions => ["published = ? AND (DAY(created_at) = ?)", true, (args.first)], :order => "created_at DESC"} }
end
FYI, this is the PostgreSQL error that i am getting:
PGError: ERROR: function month(timestamp without time zone) does not exist LINE 1: ...T * FROM "articles" WHERE (((published = 't' AND (MONTH(crea... ^ HINT: No function matches the given name and argument types. You might need to add explicit type casts. : SELECT * FROM "articles" WHERE (((published = 't' AND (MONTH(created_at) = '11')) AND (published = 't' AND (YEAR(created_at) = '2010'))) AND ("articles"."published" = 't')) ORDER BY created_at DESC LIMIT 5 OFFSET 0
Thanks in advance for any input anyone has.

You should be using the standard EXTRACT function:
named_scope :by_year, lambda { |*args| {:conditions => ["published = ? AND (extract(year from created_at) = ?)", true, (args.first)], :order => "created_at DESC"} }
Both PostgresSQL and MySQL support it.

Unfortunately this happens alot, however you have the general right idea.
Your first method of attack is to see if there is a function that exists both in MySQL and Postres, however this isn't possible in this case.
The one suggestion I would make is that there is a lot of code duplication in this solution. Considering the condition statement is the only compatible issue here, I would factor out the compatiablity check only for the condition:
Example (Semi-Psuedo Code):
named_scope :by_year, lambda { |*args| {:conditions => ["published = ? AND (#{by_year_condition} = ?)", true, (args.first)], :order => "created_at DESC"} }
#...code...
def by_year_condition
if postgres
"date_part('year', created_at)"
else
"YEAR(created_at)"
end

Another option would be to create computed columns for each of your date parts (day, month, and year) and to query directly against those. You could keep them up to date with your model code or with triggers. You'll also get the benefit of being able to index on various combinations on your year, month, and day columns. Databases are notoriously bad at correctly using indexes when you use a function in the where clause, especially when that function is pulling out a portion of data from the middle of the column.
The upside of having three separate columns is that your query will no longer rely on any vendor's implementations of SQL.

Related

Retrieving array from MySQL

I'm not using sqlite3 gem
I'm using mysql2 gem
I'm retrieving data from MySQL database given that it meets the condition of a certain event type and severity. However, it returns only one row instead of an array of results. It really puzzles me. Shouldnt .map return an array?
result = connect.query("SELECT * FROM data WHERE event_type = 'ALARM_OPENED' AND severity = '2'")
equipments = result.map do |record|
[
record['sourcetime'].strftime('%H:%M:%S'),
record['equipment_id'],
record['description']
]
end
p equipments
I had misread your question...I think what you are looking for is in here.
UPDATE
You can use each instead, like this:
#!/usr/bin/env ruby
require 'mysql2'
connect= Mysql2::Client.new(:host => '', :username => '', :password => '', :database => '')
equipments = []
result = connect.query("SELECT * FROM data WHERE event_type = 'ALARM_OPENED' AND severity = '2'", :symbolize_keys => true).each do |row|
equipments << [
row[:sourcetime].strftime('%H:%M:%S'),
row[:equipment_id],
row[:description]
]
end
puts "#equipments {equipments}"
EDITED:
I forgot to add .each at the end of the query. So it was returning the initialized empty array instead.
You must need to change your sql statement :
result = connect.query("SELECT * FROM data WHERE event_type = 'ALARM_OPENED' AND severity = '2'", :as => :array)

Thinking Sphinx : Multiple indices search

I want to create two indices for the same model and search separately
I am using
gem 'thinking-sphinx', '3.2.0'
gem 'riddle', '1.5.11'
ThinkingSphinx::Index.define :product, :with => :active_record, :delta => ThinkingSphinx::Deltas::DelayedDelta do
indexes :field_a
end
ThinkingSphinx::Index.define :product, :name => "active_product", :with => :active_record, :delta => ThinkingSphinx::Deltas::DelayedDelta do
indexes :field_a
where "(active = 1)"
end
when i tried to search this way to get only the active products
Product.search_for_ids "", :match_mode => :extended, :index => "active_product_core, active_product_delta", :page => params[:page], :per_page => 50, :sort_mode => :extended, :order => "field_a desc"
But it is running query like this and listing all products
SELECT * FROM `product_core`, `product_delta` WHERE `sphinx_deleted` = 0 ORDER BY `field_a` desc LIMIT 0, 50 OPTION max_matches=50000
How can i get only the active products or to make sure query runs like this?
SELECT * FROM `active_product_core`, `active_product_delta` WHERE `sphinx_deleted` = 0 ORDER BY `field_a` desc LIMIT 0, 50 OPTION max_matches=50000
Note: Above feature was working fine in Thinking sphinx version 2
gem 'thinking-sphinx', '2.0.14'
gem 'riddle', '1.5.3'
In TS v3, the search option is now :indices rather than :index, and expects an array of index names. So, try the following:
Product.search_for_ids(
:indices => ["active_product_core", "active_product_delta"],
:page => params[:page],
:per_page => 50,
:order => "field_a desc"
)
I've removed :sort_mode and :match_mode from the options you were using - the extended approaches are the only approaches available with Sphinx's SphinxQL protocol (and that's what TS v3 uses), so you don't need to specify them.

2 Find Conditions with one variable

Right now I have only one condition in my Projects.paginate
Code is below
def list
#projects = Project.paginate(:page => params[:page], :per_page => 100, :order => (sort_column + ' ' + arrow), :conditions => ["description LIKE ?", "%#{query}%"])
I want to put another condition here but its is proving to be difficult. I'v tried
#projects = Project.paginate(:page => params[:page], :per_page => 100, :order => (sort_column + ' ' + arrow), :conditions => ["description OR name LIKE ?", "%#{query}%"])
but im getting a bind error from the SQL controller. Any ideas? I cant use the = sign either.
You need to have two bind variables in your conditions array:
qt = "%#{query}%"
#projects = Project.paginate(:conditions =>
["description LIKE ? OR name LIKE ?", qt, qt], ..)

MySQL / Ruby on Rails - How to "SUM" in a :has_many case

I have the following tables:
User :has_many Purchases
Item :has_many Purchases
Item has a column "amount" (can be + or -) and I need to find all Users that have a positive SUM of "Item.amounts" (over all Purchases each one has made).
How does this query look like? (I'm not sure how to handle "SUM" correctly, in this case.)
I started out with the following, but obviously, it's wrong... (it wouldn't "include" Purchases that have an Item with a negative Item.amount...)
#users = User.find(:all,
:include => {:purchases => :item},
:select => "SUM(item.amount)",
:order => "...",
:conditions => "...",
:group => "users.id",
:having => "SUM(item.amount) > 0"
)
Thanks for your help with this!
Tom
Try this:
User.all(:joins => items, :group => "users.id",
:having => "SUM(items.amount) > 0")
It sounds like this is a good case for some model methods.
I didn't test this but I think you want to do something similar to the following:
class User < ActiveRecord::Base
has_many :purchases
has_many :items, :through => :purchases
def items_total
#get all the items iterate over them to get the amount,
#compact to get rid of nils
#and reduce with a sum function to total and return
items.all.each{|item| item.amount}.compact.reduce(:+)
end
then
User.items_total

Rails: Using named_scope which triggers a MySQL "in"

PROBLEM:
I want to run a query which would trigger something like
select * from users where code in (1,2,4);
using a named_scope.
WHAT I TRIED:
This is for a single code:
named_scope :of_code, lambda {|code| {:conditions => ["code = ?", code]}}
I tried something like
named_scope :of_codes, lambda {|codes| {:conditions => ["code in ?", codes]}}
and sent
user.of_codes('(1,2,4)')
it triggers
select * from users where code in '(1,2,4)' which raises a MySQL error because of the extra quotes.
PS: Ideally I would like to send user.of_codes([1,2,4])
This will work just find and not expose you to the SQL injection attack:
named_scope :of_codes, lambda { |codes|
{ :conditions => ['code in (?)', codes] }
}
User.of_codes([1, 2, 3])
# executes "select * from users where code in (1,2,3)"
If you want to be a little more slick, you can do this:
named_scope :of_codes, lambda { |*codes|
{ :conditions => ['code in (?)', [*codes]] }
}
Then you can call it either with an Array (as above): User.of_codes([1, 2, 3]), or with a list of code arguments: User.of_codes(1, 2, 3).
The simplest approach would be to use a hash for conditions instead of an array:
named_scope :of_codes, lambda { |*codes| { :conditions => { :code => codes } } }
This will work as expected.
User.of_codes(1, 2, 3) # => SELECT ... code IN (1,2,3)
User.of_codes(1) # => SELECT ... code IN (1)
you can try follwing
named_scope :of_codes, lambda {|codes| {:conditions => ["code in "+codes]}}
and
user.of_codes('(1,2,4)')
EDITED For SQL INJECTION PROBLEM USE
named_scope :of_codes, lambda {|codes| {:conditions => ["code in (?) ", codes]}}
and
user.of_codes([1,2,4])