How to reduce number of queries in this case on Rails - ruby-on-rails

I need to do a bunch of queries.
In this case I think I query a result by many times,
Not completed in one query.
How to make my search results can be done by one query ?
q = WeatherLog.nearby(100, longitude, latitude)
if start_time and end_time
#weather_logs = q.where(datetime: start_time..end_time)
elsif start_time
#weather_logs = q.where("datetime > ?", start_time)
elsif end_time
#weather_logs = q.where("datetime < ?", end_time)
end
#weather_logs = #weather_logs.order(datetime: :asc).first(2000)

The first thing to realize is that ActiveRecord does not execute a query until it aboultely has to (lazy loading). While there are a number of lines of code building up the query, the query is only executed on methods like .all, .each, .first etc. So from a performance standpoint your code is ok as your only executing one query to the database and not many.
However you can tweak the code to make it more human readable and maintainable:
class WeatherLog < ActiveRecord::Base
# ...
class << self
def between_times(times)
after_time(times[:start_time]).before_time(times[:end_time])
end
def after_time(time)
return self.all if time.nil?
where('datetime > ?', time)
end
def before_time(time)
return self.all if time.nil?
where('datetime < ?', time)
end
end
end
Using self.all effectively skips the query condition while still enabling query chaining. This makes it possible to remove of all the if/else logic. Then you can chain the queries (or create a helper method within WeatherLog):
WeatherLog.nearby(100, longitude, latitude).between_times(start_time: start_time, end_time: end_time)

Related

Rails 4 - How to get specific records based on where conditions

I have a Sale model with an :offer_end column with a date data type. I would like to display specific records of Sale where :offer_end >= Date.today. I tried to get this to work in the controller but im not sure what is the correct syntax to achieve this. This is what im currently doing which isnt working:
def index
#shops = Shop.all
#sales = Sale.where("offer_end >= Date.today", {offer_end: params[:offer_end]})
end
First of all you can't pass the Date.today as a string to the query, it will be passed to the database and it won't understand it.
The query should be something like this
#sale = Sale.where('offer_end > ?', Date.today)
The Date.today will be evaluated then passed as a value to the query.
You could replace the Date.today with any date object or date string, which in your case seems to be in theparams[:offer_end]
#sale = Sale.where('offer_end > ?', params[:offer_end])
You can use scope for these type of operations:
class Sale < ActiveRecord::Base
scope :get_products, ->(date){where(" sales.offer_end >= ? ", date)}
end
In controller you can use this scope as below:
#sales = Sale.get_products(params[:offer_end])
or you can use it directly in controller:
#sales = Sale.where("offer_end >= ?", Date.today)
and you can use params[:offer_end] instead of Date.today

Display all the records when due date is less than current date in Rails

I am having a Quote model which consists of due_date and other fields.
I need to write a condition where we have to display all the records where due_date (which is date data type and stored in database as yyyy-mm-dd format) is less than current date.
For example, current_date is today's date, so I need to display all the records till yesterday.
I wrote a condition but didn't work, can you tell me where I went wrong?
def past_quotes
#items = []
current_date = Time.now.strftime("%Y-%m-%d")
daily_items = Quote.by_account(#account).active.includes(:company).where('due_date' < current_date)
#items << Item.new(items: daily_items) unless daily_items.empty?
end
But still displaying all the records, I think condition is wrong.
You shouldn't use a string when comparing the dates. ActiveRecord should be able to handle dates directly
daily_items = Quote.by_account(#account).active.includes(:company).where('due_date < ?', Date.today)
Can you try following query:
def past_quotes
#items = []
daily_items = Quote.by_account(#account).active.includes(:company).where('due_date < DATE(?)', Time.now)
#items << Item.new(items: daily_items) unless daily_items.empty?
end

Rails 3 Counting Records by Date

I am trying to build an array that looks like this via a model method:
[['3/25/13', 2], ['3/26/13', 1], ['3/27/13', 2]]
Where, the dates are strings and the numbers after them are the count of an table/object.
I have the following model method right now:
def self.weekly_count_array
counts = count(group: "date(#{table_name}.created_at)", conditions: { created_at: 1.month.ago.to_date..Date.today }, order: "date(#{table_name}.created_at) DESC")
(1.week.ago.to_date).upto(Date.today) do |x|
counts[x.to_s] ||= 0
end
counts.sort
end
However, it doesn't return the count accurately (all values are zero). There seem to be some similar questions on SO that I've checked out, but can't seem to get them to work either.
Can someone help (1) let me know if this is the best way to do it, and (2) provide some guidance in terms of what the problem might be with the above code, if so? Thanks!
Use this as a template if you wish
def self.period_count_array(from = (Date.today-1.month).beginning_of_day,to = Date.today.end_of_day)
where(created_at: from..to).group('date(created_at)').count
end
This will return you a hash with dates as key and the count as value. (Rails 3.2.x)
maybe this is what you are trying to do?
class YourActiveRecordModel < ActiveRecord::Base
def.self weekly_count_array
records = self.select("COUNT(id) AS record_count, DATE(created_at) AS created")
.group("DATE(created_at)")
.where("created_at >= ?", 1.month.ago.to_date)
.where("created_at <= ?", Date.current)
records.each do |x|
puts x.record_count
puts x.created # 2013-03-14
# use I18n.localize(x.created, format: :your_format)
# where :your_format is defined in config/locales/en.yml (or other .yml)
end
end
end
Fantastic answer by #Aditya Sanghi.
If you have the exact requirement, you can opt:
def self.weekly_count_array
records = select('DATE(created_at) created_at, count(id) as id').group('created_at')
1.week.ago.to_date.upto(Date.today).map do |d|
[d, records.where('DATE(created_at) = ?', d.to_date).first.try(:id) || 0]
end
end
You do not need a process to perform the count. Simply perform a query for this.
def self.weekly_count_array
select("created_at, COUNT(created_at) AS count")
where(created_at: 1.month.ago.to_date..Date.today)
group("created_at")
order("created_at DESC")
end
Built on #kiddorails Answer,
so not to make a lot of Requests to the DataBase, Created a Hash from the ActiveRecord
& changed the group from .group('created_at') to .group('DATE(created_at)') to base it on date
def self.weekly_count_array
# records = select('DATE(created_at) created_at, count(id) as id').group('created_at')
records_hash = Hash[Download.select('DATE(created_at) created_at, count(id) as id').group('DATE(created_at)').map{|d|[d.created_at, d.id] }]
1.month.ago.to_date.upto(Date.today).map do |d|
[ d, records_hash[d.to_date] || 0 ]
end
end

class method on active records query

Given I have a model/table with appointments, saved as a date. I want to create a method which returns me only active records. One for all records, one on an object. Is my approach improvable/combinable? Thx for advise!
def self.actives
where("start_time >= ?", Date.today)
end
def is_active
where("start_time >= ?", Date.today)
end
Scopes are the proper way to handle your first filter. See doc: http://api.rubyonrails.org/classes/ActiveRecord/NamedScope/ClassMethods.html
Your second filter won't work, replace with:
def is_active?
  start_time >= Date.today
end
I can't see why you want to combine both methods.
Be aware that:
Model.actives will provide you with an ActiveRecord Relation. You have to append .all to trigger the call. Then you'll have an array to iterate.
instance.is_active? will provide a boolean

How do I combine results from two queries on the same model?

I need to return exactly ten records for use in a view. I have a highly restrictive query I'd like to use, but I want a less restrictive query in place to fill in the results in case the first query doesn't yield ten results.
Just playing around for a few minutes, and this is what I came up with, but it doesn't work. I think it doesn't work because merge is meant for combining queries on different models, but I could be wrong.
class Article < ActiveRecord::Base
...
def self.listed_articles
Article.published.order('created_at DESC').limit(25).where('listed = ?', true)
end
def self.rescue_articles
Article.published.order('created_at DESC').where('listed != ?', true).limit(10)
end
def self.current
Article.rescue_articles.merge(Article.listed_articles).limit(10)
end
...
end
Looking in console, this forces the restrictions in listed_articles on the query in rescue_articles, showing something like:
Article Load (0.2ms) SELECT `articles`.* FROM `articles` WHERE (published = 1) AND (listed = 1) AND (listed != 1) ORDER BY created_at DESC LIMIT 4
Article Load (0.2ms) SELECT `articles`.* FROM `articles` WHERE (published = 1) AND (listed = 1) AND (listed != 1) ORDER BY created_at DESC LIMIT 6 OFFSET 4
I'm sure there's some ridiculously easy method I'm missing in the documentation, but I haven't found it yet.
EDIT:
What I want to do is return all the articles where listed is true out of the twenty-five most recent articles. If that doesn't get me ten articles, I'd like to add enough articles from the most recent articles where listed is not true to get my full ten articles.
EDIT #2:
In other words, the merge method seems to string the queries together to make one long query instead of merging the results. I need the top ten results of the two queries (prioritizing listed articles), not one long query.
with your initial code:
You can join two arrays using + then get first 10 results:
def self.current
(Article.listed_articles + Article.rescue_articles)[0..9]
end
I suppose a really dirty way of doing it would be:
def self.current
oldest_accepted = Article.published.order('created_at DESC').limit(25).last
Artcile.published.where(['created_at > ?', oldest_accepted.created_at]).order('listed DESC').limit(10)
end
If you want an ActiveRecord::Relation object instead of an Array, you can use:
ActiveRecordUnion gem.
Install gem: gem install active_record_union and use:
def self.current
Article.rescue_articles.union(Article.listed_articles).limit(10)
end
UnionScope module.
Create module UnionScope (lib/active_record/union_scope.rb).
module ActiveRecord::UnionScope
def self.included(base)
base.send :extend, ClassMethods
end
module ClassMethods
def union_scope(*scopes)
id_column = "#{table_name}.id"
if (sub_query = scopes.reject { |sc| sc.count == 0 }.map { |s| "(#{s.select(id_column).to_sql})" }.join(" UNION ")).present?
where "#{id_column} IN (#{sub_query})"
else
none
end
end
end
end
Then call it in your Article model.
class Article < ActiveRecord::Base
include ActiveRecord::UnionScope
...
def self.current
union_scope(Article.rescue_articles, Article.listed_articles).limit(10)
end
...
end
All you need to do is sum the queries:
result1 = Model.where(condition)
result2 = Model.where(another_condition)
# your final result
result = result1 + result2
I think you can do all of this in one query:
Article.published.order('listed ASC, created_at DESC').limit(10)
I may have the sort order wrong on the listed column, but in essence this should work. You'll get any listed items first, sorted by created_at DESC, then non-listed items.

Resources