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])
Related
I'm trying to use eloquent to get me a grouped by response and at the same time give me a Pagination response (The one that gives me the link to the second page).
I'm trying to do this:
App\Eating::Where('student_id', 2)->orderBy('created_at', 'DESC')->groupBy(function ($row) {
return Carbon\Carbon::parse($row->created_at)->format('Y-m-d');
})->paginate(25);
But, I'm getting this error when running it in the Tinker:
PHP warning: strtolower() expects parameter 1 to be string, object given in D:\Folder\vendor\laravel\framework\src\Illuminate\Database\Grammar.php on line 58
without the groupBy, I'm getting the correct result:
>>> App\Eating::Where('student_id', 2)->orderBy('created_at', 'DESC')->paginate(25)->toArray();
=> [
"total" => 1,
"per_page" => 25,
"current_page" => 1,
"last_page" => 1,
"next_page_url" => null,
"prev_page_url" => null,
"from" => 1,
"to" => 3,
"data" => [
[
"id" => 5,
"status" => "Comeu Bem",
"created_at" => "2017-07-05 13:55:25",
"updated_at" => "2017-07-05 13:55:25",
],
],
]
BUT, when I remove the pagination, I do get the error but only because I added the get():
>>> App\Eating::Where('student_id', 2)->orderBy('created_at', 'DESC')->groupBy(function ($row) {
... return Carbon\Carbon::parse($row->created_at)->format('Y-m-d');
... })->get();
PHP warning: strtolower() expects parameter 1 to be string, object given in D:\Joao\git\F1Softwares\Code\Server\F1Softwares\vendor\laravel\framework\src\Illuminate\Database\Grammar.php on line 58
>>>
>>>
>>> App\Eating::Where('student_id', 2)->orderBy('created_at', 'DESC')->groupBy(function ($row) {
... return Carbon\Carbon::parse($row->created_at)->format('Y-m-d');
... });
=> Illuminate\Database\Eloquent\Builder {#855}
Any idea what I could be doing wrong? I do need to have the orderBy AND the pagination, to make it easier for the app to show the results(It is a RestFul call).
Thanks,
João
You must call the groupBy() method on a collection, but it seems this won't work with paginate(). You could try using the forPage() method on the collection:
App\Eating::where('student_id', 2)->orderBy('created_at', 'DESC')
->get()->groupBy(function ($eating) {
return $eating->created_at->format('Y-m-d');
})->forPage(1, 25);
Also, just a note, you don't need to use Carbon to parse the date, Eloquent does this for you.
Alternatively, you could try to manually create your paginator once you have the collection grouped using Illuminate\Pagination\LengthAwarePaginator.
$eatings = App\Eating::where('student_id', 2)->orderBy('created_at', 'DESC')
->get()->groupBy(function ($eating) {
return $eating->created_at->format('Y-m-d');
});
$paginatedEatings = new LengthAwarePaginator($eatings, $eatings->count(), 25);
return $paginatedEatings->toArray();
I’m using Rails 4.2.3 and MySQL 5.5.37. I want to write a finder method for my model, so I have written this (./app/models/user_object.rb):
class UserObject < ActiveRecord::Base
validates :day, :presence => true
validates_numericality_of :total
validates :object, :presence => true
def find_total_by_user_object_and_year
UserObject.sum(:total, :conditions => ['user_id = ?', params[:user_id], 'object = ?', params[:object], 'year(day) = ?', params[:year]])
end
end
However, when I attempt to invoke the method within a controller like so
#my_total = UserObject.find_total_by_user_object_and_year(session["user_id"], 3, #year)
I get the following error
undefined method `find_total_by_user_object_and_year' for #<Class:0x007fb7551514e0>
What is the right way to define my finder method?
Use self.method to define class method:
def self.find_total_by_user_object_and_year
sum(:total, :conditions => ['user_id = ?', params[:user_id], 'object = ?', params[:object], 'year(day) = ?', params[:year]])
end
In this case UserObject inside class method definition is redundant, besause it is same as self. Now you can write:
UserObject.find_total_by_user_object_and_year(params)
I want to return about 90k items in a JSON document but I'm getting this error when I make the call:
Timeout::Error in ApisController#api_b
time's up!
Rails.root: /root/api_b
I am simply running "rails s" with the default rails server.
What's the way to make this work and return the document?
Thanks
#bs.each do |a|
puts "dentro do bs.each"
#final << { :Email => a['headers']['to'], :At => a['date'], :subject => a['headers']['subject'], :Type => a['headers']['status'], :Message_id => a['headers']['message_id'] }
end
Being #bs the BSON object from MongoDB. The timeout is in "#final << ..."
If you are experiencing timeouts from rails and it is possible to cache the data (e.g. the data changes infrequently), I would generate the response in the background using resque or delayed_job and than have Rails dump that to the client. Or if the data cannot be cached, use a lightweight Rack handler like Sinatra and Metal to generate the responses.
Edited to reflect sample data
I was able to run the following code in a Rails 3.0.9 instance against a high performance Mongo 1.8.4 instance. I was using Mongo 1.3.1, bson_ext 1.3.1, webrick 1.3.1 and Ruby 1.9.2p180 x64. It did not time out but it took some time to load. My sample Mongo DB has 100k records and contains no indexes.
before_filter :profile_start
after_filter :profile_end
def index
db = #conn['sample-dbs']
collection = db['email-test']
#final = []
#bs = collection.find({})
#bs.each do |a|
puts "dentro do bs.each"
#final << { :Email => a['headers']['to'], :At => a['date'], :subject => a['headers']['subject'], :Type => a['headers']['status'], :Message_id => a['headers']['message_id'] }
end
render :json => #final
end
private
def profile_start
RubyProf.start
end
def profile_end
RubyProf::FlatPrinter.new(RubyProf.stop).print
end
A more efficient way to dump out the records would be
#bs = collection.find({}, {:fields => ["headers", "date"]})
#final = #bs.map{|a| {:Email => a['headers']['to'], :At => a['date'], :subject => a['headers']['subject'], :Type => a['headers']['status'], :Message_id => a['headers']['message_id'] }}
render :json => #final
My data generator
100000.times do |i|
p i
#coll.insert({:date =>Time.now(),:headers => {"to"=>"me#foo.com", "subject"=>"meeeeeeeeee", "status" => "ffffffffffffffffff", "message_id" => "1234634673"}})
end
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.
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], ..)