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
Related
My BookingGroup has_many Booking. Booking contains column category where the data can be "adult" or "child_infant" or child_normal.
Now I want to count all total %child% and display it in my index view table
I was'nt sure whether this could be done in one line or I have to use a scope, this is where I stucked.
BookingGroup model
def search_by_category
bookings.visible.map(&:category).inject(:+)
end
Assuming category is a string column, you should be able to count it like that :
bookings.visible.where("category LIKE ?", "child%").count
bookings.visible.where(category: ["child_infant", "child_normal"]).count
We can use LIKE just as in SQL with active record
In your BookingGroup model
def search_by_category
bookings.visible.where('category LIKE ?', '%child%').size
end
But, if you do so for many booking_groups, your code will have N+1 queries issue. You can use eager load in your controller
#booking_groups = BookingGroup.joins(:bookings).select('booking_groups.*', 'count(*) as total_bookings').where('bookings.category LIKE ?', '%child%').group(:id)
Then you can
#booking_groups.first.total_bookings
I'm combining the two .where like this:
#questions = (FirstQuestion.where(user_id: current_user) + SecondQuestion.where(user_id: current_user)).sort_by(&:created_at).reverse
Both .where searches are with one attribute... the user_id. But now I want to search with two attributes, the user_id and this created_at >= ?", Date.today + 60.days. So basically I want to find the object with a user_id: current_user and the objects that where created less then or equal to 60 days.
Any idea on how to implement this?
Please see my comment as well... because it is kind of a code smell when you have models names FirstQuestion, SecondQuestion. There's really no reason for having separate models. You could probably easily model the logic via an attribute question_depth or something (I don't know what you are trying to achieve exactly).
With regard to your question: ActiveRecord is quite a nice class, that allows for very customizable queries. In your case, you could easily write both conditions each in a separate where, or create a single where. That's totally up to you:
Question.where(user: current_user).where('created_at <= ?', 60.days.from_now)
Or in a single where
Question.where('user_id = ? AND created_at <= ?', current_user.id, 60.days.from_now)
Also, consider using scopes on your Question model for readability and reusability:
class Question < AppModel
scope :by_user, -> (user) { where(user: user) }
scope :min_age, -> (date) { where('created_at <= ?', date) }
end
And use it like:
Question.by_user(current_user).min_age(60.days.from_now)
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
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)
Let's say you have an assocation in one of your models like this:
class User
has_many :articles
end
Now assume you need to get 3 arrays, one for the articles written yesterday, one of for the articles written in the last 7 days, and one of for the articles written in the last 30 days.
Of course you might do this:
articles_yesterday = user.articles.where("posted_at >= ?", Date.yesterday)
articles_last7d = user.articles.where("posted_at >= ?", 7.days.ago.to_date)
articles_last30d = user.articles.where("posted_at >= ?", 30.days.ago.to_date)
However, this will run 3 separate database queries. More efficiently, you could do this:
articles_last30d = user.articles.where("posted_at >= ?", 30.days.ago.to_date)
articles_yesterday = articles_last30d.select { |article|
article.posted_at >= Date.yesterday
}
articles_last7d = articles_last30d.select { |article|
article.posted_at >= 7.days.ago.to_date
}
Now of course this is a contrived example and there is no guarantee that the array select will actually be faster than a database query, but let's just assume that it is.
My question is: Is there any way (e.g. some gem) to write this code in a way which eliminates this problem by making sure that you simply specify the association conditions, and the application itself will decide whether it needs to perform another database query or not?
ActiveRecord itself does not seem to cover this problem appropriately. You are forced to decide between querying the database every time or treating the association as an array.
There are a couple of ways to handle this:
You can create separate associations for each level that you want by specifying a conditions hash on the association definition. Then you can simply eager load these associations for your User query, and you will be hitting the db 3x for the entire operation instead of 3x for each user.
class User
has_many articles_yesterday, class_name: Article, conditions: ['posted_at >= ?', Date.yesterday]
# other associations the same way
end
User.where(...).includes(:articles_yesterday, :articles_7days, :articles_30days)
You could do a group by.
What it comes down to is you need to profile your code and determine what's going to be fastest for your app (or if you should even bother with it at all)
You can get rid of the necessity of checking the query with something like the code below.
class User
has_many :articles
def article_30d
#articles_last30d ||= user.articles.where("posted_at >= ?", 30.days.ago.to_date)
end
def articles_last7d
#articles_last7d ||= articles_last30d.select { |article| article.posted_at >= 7.days.ago.to_date }
end
def articles_yesterday
#articles_yesterday ||= articles_last30d.select { |article| article.posted_at >= Date.yesterday }
end
end
What it does:
Makes only one query maximum, if any of the three is used
Calculates only the used array, and the 30d version in any case, but only once
It does not however simplifies the initial 30d query even if you do not use it. Is it enough, or you need something more?