I want to know that is it possible to make a query on fetched data from a model, without creating a database query.
Let there be a User model having type A and B
#users = User.where('created_at > ?', Time.now - 1.day)
type_a = #users.where(:type => A).limit(100)
type_b = #users.where(:type => B).limit(10)
Currently, it is executing 2 queries. But I want to run a single query to fetch data, and then extract type_a and type_b from it.
Is there any other method to do that.
Possible by select method
#users = User.where('created_at > ?', Time.now - 1.day)
type_a = #users.select {|u| u.type == 'A'}.first(100)
type_b = #users.select {|u| u.type == 'B'}.first(10)
Related
Lets say there is a model called Event. I want to display important and current events first, so i have the attributes important (boolean) and enddate (date).
Now I want to get all events where important == true and where enddate >= today first, all others should be ordered by created_at.
I want to avoid doing events = important_events + not_important_events as this would return an array insted of an activerecord. Does anyone knows an elegant way to order a model in rails?
Try (for ActiveRecord 5+):
#events = Event.where(:important => true, 'enddate >= ?', Date.today).or(Event.where.not(:important => true, 'enddate >= ?', Date.today).order(:created_at => :desc))
Try this, It will return newest record first and fulfil your conditions.
important == true
enddate >= today
#events1 = Event.where(:important => true).where('enddate >= ?', Date.today).order(:created_at => :desc)
#events2 = Event.where(:important => false).where('enddate <= ?', Date.today).order(:created_at => :desc)
#events = #events1.or(#events2)
"OR" works only in ActiveRecod(5+).
I would like to append AND conditions depend on condition like this:
#flag = true || false;
#results = Model.where(conditions).where(conditions_depend_on_flag);
// The simple way:
if (#flag) {
#results = Model.where(conditions);
} else {
#results = Model.where(conditions).where(conditions_depend_on_flag);
}
Example for my expected:
#results = Model.where(conditions).where(conditions_depend_on_flag, #flag == true);
I don't know is it possible or not.
Could you give me some suggestion?
#results = Model.where(conditions)
#results = #results.where(conditions_depend_on_flag) if #flag
We can combine 2 conditions into 1 where statement:
To make it easy I assume:
conditions = created_at < 1.day.ago
conditions_depend_on_flag = updated_at > 1.day.ago
So the query will be:
Model.where(
'created_at < ? AND (? OR updated_at > ?)', 1.day.ago, !#flag, 1.day.ago
)
Beautiful SQL :)
use scopes for sql conditions
in model
scope :conditions_depend_on_flag, ->(flag) { where(....) if flag }
anywhere
#results = Model.where(conditions).conditions_depend_on_flag(#flag)
Class Order < ActiveRecord::Base
scope :ordered, lambda { |user| where("orders.user_id = ?", user.id) }
scope :delivered, lambda { |user| where("orders.user_id = ? and orders.delivered = ?", user.id, true) }
def self.delivered_percentage (user)
((self.ordered(user).count/self.delivered(user).count) * 100)
end
end
I have an Order model, and I need to find out
The count of ordered items
The count of delivered items
The percentage of delivered items.
The ordered and delivered methods return an array. How do I have them return a count, and compute the percentage of delivered items.
In my controller I would like to
Order(current_user).delivered_percentage
Thanks!
ordered and delivered return ActiveRecord::Relation. If you call count on it, result of SQL count query will be returned. So your approach basically should work - except you have to convert your counts into floats to make it work:
def self.delivered_percentage(user)
(ordered(user).count.to_f / delivered(user).count.to_f) * 100
end
and you call it with:
Order.delivered_percentage(current_user)
Order.group('delivered').count
That will return a hash: {true => 10, false => 33} with a single SQL query.
So you should be able to do this:
def self.delivered_percent(user)
delivered_counts ||= Order.group('delivered').where(user: user).count
delivered_counts[true]/delivered_counts.values.sum.to_f
end
In my Rails 4 app, I defined functions in my model than get the (nth) next or previous row in de database, wrapping around the entire database, so that Item.last.next will refer to Item.first:
def next(n=0)
following = Item.where("id > ?", self.id).order("id asc") + Item.where("id < ?", self.id).order("id asc")
following[n % following.length]
end
def prev(n=0)
n = n % Item.count-1
previous = Item.where("id < ?", self.id).order("id desc") + Item.where("id > ?", self.id).order("id desc")
previous[n % previous.length]
end
This results in three database queries per method call, and I've learned to keep database queries to a minimum, so I wonder if there is a way do get this result with only one query.
What you are looking for seems a bit high level. So let's prepare the basic API at first.
def next(n=1)
self.class.where('id > ?', id).limit(n).order('id ASC')
end
def previous(n=1)
self.class.where('id > ?', id).limit(n).order('id DESC')
end
Then higher level methods
def next_recycle(n=1)
klass = self.class
return klass.first if (n = 1 && self == klass.last)
next(n)
end
def previous_recycle(n=1)
klass = self.class
return klass.last if (n == 1 && self == klass.first)
previous(n)
end
You can pick methods according to needs.
I have an Array of Contact objects...
[#<Contact id: 371, full_name: "Don Hofton", birthday: "2013-11-07">,...]
And I need to order them by birthdays nearest to the current time AND remove objects from the array that have birthdays greater than 4 months away. Here is what I've got so far, but it's not working....
#contacts_with_birthday_data = Contact.where(:user_id => current_user.id).where("birthday IS NOT NULL")
#current_time = Time.now
#contacts_with_birthday_data.each do |c|
c.birthday = c.birthday[0..4]
c.birthday = Date.parse(c.birthday)
end
#contacts_with_birthday_data = #contacts_with_birthday_data.sort! { |a,b| b[:birthday] <=> a[:birthday] }
#contacts_with_birthday_data = #contacts_with_birthday_data.sort! { |a| a.birthday < DateTime.now }
I think you can do this all with one query:
Contact \
.where(:user_id => current_user.id)
.where("birthday > ?", 4.months.ago)
.order("birthday desc")
If 4.months.ago is used in a scope, make sure to wrap it in a lambda or Proc, or it will be calculated when the class is loaded and not on subsequent calls. This has bit me more than once!
Alternatively, in a non-Rails world, you could use the reject and sort_by methods on Enumerable:
contacts = [#your array of contacts]
contacts.reject { |c| c.birthday < 4.months.ago }.sort_by(&:birthday).reverse
If you haven't seen the syntax used in sort_by, that's actually equivalent to sort_by { |c| c.birthday }. That syntax tells Ruby to convert the birthday method to a Proc object, then call the Proc against each instance in your array.