Rails 3. How to perform a "where" query by a virtual attribute? - ruby-on-rails

I have two models: ScheduledCourse and ScheduledSession.
scheduled_course has_many scheduled_sessions
scheduled_session belongs_to scheduled_course
ScheduledCourse has a virtual attribute...
def start_at
s = ScheduledSession.where("scheduled_course_id = ?", self.id).order("happening_at ASC").limit(1)
s[0].happening_at
end
... the start_at virtual attribute checks all the ScheduledSessions that belongs to the ScheduledCourse and it picks the earliest one. So start_at is the date when the first session happens.
Now I need to write in the controller so get only the records that start today and go into the future. Also I need to write another query that gets only past courses.
I can't do the following because start_at is a virtual attribute
#scheduled_courses = ScheduledCourse.where('start_at >= ?', Date.today).page(params[:page])
#scheduled_courses = ScheduledCourse.where('start_at <= ?', Date.today)
SQLite3::SQLException: no such column: start_at: SELECT "scheduled_courses".* FROM "scheduled_courses" WHERE (start_at >= '2012-03-13') LIMIT 25 OFFSET 0

You can't perform SQL queries on columns that aren't in the database. You should consider making this a real database column if you intend to do queries on it instead of a fake column; but if you want to select items from this collection, you can still do so. You just have to do it in Ruby.
ScheduledCourse.page(params).find_all {|s| s.start_at >= Date.today}

Veraticus is right; You cannot use virtual attributes in queries.
However, I think you could just do:
ScheduledCourse.joins(:scheduled_sessions).where('scheduled_courses.happening_at >= ?', Date.today)
It will join the tables together by matching ids, and then you can look at the 'happening_at' column, which is what your 'start_at' attribute really is.
Disclaimer: Untested, but should work.

I wonder if this would be solved by a subquery ( the subquery being to find the earliest date first). If so, perhaps the solution here might help point in a useful direction...

Related

How to query a ActiveRecord Relation for created_by field

So i'm currently using the following command to join and query my tables - looking for an OrderItem amongst my Orders where the orderable_id = applicable_product_item_id the total_price = 0 and the buyer_id = current_user
Order.joins(:items)
.where(order_items: {id: OrderItem.where(orderable_id: applicable_product_item_id)})
.where(total_price: 0)
.where(buyer_id: current_user)
This all works fine, but now i want to query further and i want to know if the order that it has found has a created_at date > searchable_created_by_date
i've tried using another .where in the query as well as selecting the .first in the array and further querying that i.e. query = above_query.first
then
query.where("created_at > ?", searchable_created_by_date)
but i get
Undefined method where for #<Order:0x007fbc8d8edf90>
furman87's comment sounds right to me:
You'll have to specify the table in your where clause -- .where("orders.created_at > ?", searchable_created_by_date)
You might also try:
Order.
where(total_price: 0).
where(buyer_id: current_user).
where("created_at > ?", searchable_created_by_date).
joins(:order_items).
where(order_items: {id: OrderItem.where(orderable_id: applicable_product_item_id)})
I think putting the created_at statement before the joins statement will disambiguate the query - but I'm not 100% sure.
Also, I would have thought that you would have done joins(:order_items). But, I suppose that depends on how you have your associations set up. If joins(:items) works for you, then more power to you! (And ignore the comment.)

Rails: will index help for range search?

In my Rails App, I did a alot of range search to group objects, like
scope :best_of_the_week, ->(time) do
start_time = time.beginning_of_week
end_time = time.end_of_week
where("created_at > ? AND created_at < ?", start_time, end_time).where('votes_count > ?', 300).order('votes_count DESC').first(8)
end
In this case, do I need to add index to created_at? and what about votes_count?
Addtionally, how can I elegantly combine the first two where searches? Or does combining them make any difference?
If you want max performance to this query, create an index for both. If you don't want to create too many indexes, you should index created_at, date seems do have a bigger range as the time goes (and size of database).
I like to use the find_by_sql and make SELECT retrieve just the essential data to improve performance, if you have too many var chars fields this will have a nice impact.
Just for sintax sugar
where("between ? and ?", start_time, end_time).(other stuff)

How to query the result of a method in Ruby (rails)

I'm struggling with a particular feature.
In my users model I have a method to work out their age based on the current date less their first order date.
I'd like to be able to find all users who are older than X days. I can find active users by querying a column called state for 'active' users. But I'm unsure how to query the result of the age method to find users older than X.
Does anyone have any ideas?
Many thanks and seasons greetings.
**Edit
In postgresql I would write;
WITH
firstbill as (
SELECT
DISTINCT(user_id) as customer,
DATE(MIN(billed_at)) as first_order
FROM orders
WHERE state = 'shipped'
GROUP BY 1
ORDER BY 1)
SELECT count*
FROM
(SELECT *, (current_date - first_order) as age
FROM firstbill
JOIN users on users.id = customer) as t2
WHERE age >= 21
I have tried using User.find_by_sql["above query"] but that returns an array not activerecord relation which makes any further joins a little harder
You cannot really query for the return value of a method. Because to do so, you need to load all users and then call that method on every user, like this User.all.select(&:your_method?). That will be very slow if you have many users.
But for your particular example you can write something like this to let the database return the correct users (assuming you have a first_ordercolumn on your user):
User.where('first_order <= ?', 90.days.ago)
or
User.where('first_order <= ?', 1.month.ago)
I think the following startment should return the same users than your Postgresql example:
User.
select('users.*, MIN(DATE(orders.billed_at)) AS first_order_on').
joins('orders ON orders.user_id = users.id'). # just `(:orders)` with `has_many :order` on User
where('orders.state = ?', 'shipped').
group('users.id').
having('first_order_on <= ?', 21.days.ago.to_date)
Solved by using
scope :acquired, User.joins(:orders).where("orders.state = ?", "shipped")
scope :older_than_age, ->(age) {
acquired.group("users.id").having("(current_date - date(min(orders.shipped_at))) >= ?", age)
}

Find overlapping seasons where seasons have_many date_ranges

I have the following setup:
class Season < AR::Base
has_many :date_ranges
end
class DateRange < AR::Base
# has a :starts_at & :ends_at
end
How would I find all overlapping seasons from a season instance? I have already tried with a couple of different queries (below). But the problem I keep hitting is the fact that the season im checking for also possible has multiple date_ranges. I could solve it with a loop but i'd rather only use a query.
This query looks up all the seasons that overlap but it only does that for 1 input date_range
Season.joins(:date_ranges).where("starts_at <= ? AND ends_at >= ?", ends_at, starts_at)
Maybe I need something to chain a couple of OR's together for each date_range on the instance but where() only uses AND.
So in short, finding the overlap is not the problem, but how do I find overlap of multiple date_ranges to the entire database?
The easiest way to do this is through straight SQL. Something like this:
DateRange.find_by_sql(%q{
select a.*
from date_ranges a
join date_ranges b on
a.id < b.id
and (
(a.ends_at >= b.starts_at and a.ends_at <= b.ends_at)
or (a.starts_at >= b.starts_at and a.starts_at <= b.ends_at)
or (a.starts_at <= b.starts_at and a.ends_at >= b.ends_at)
)
where season_id = ?
}, season_id)
The basic idea is to join the table to itself so that you can easily compare the ranges. The a.id < b.id is there to get unique results and filter out "ranges matches itself" cases. The inner or conditions check for both types of overlaps:
[as-----ae] [as-----ae]
[bs-----be] [bs-----be]
and
[as--------------ae] [as----ae]
[bs----be] [bs--------------be]
You might want to think about the end points though, that query considers two intervals to overlap if they only match at an endpoint and that might not be what you want.
Presumably you already have a unique constraint on the (season_id, starts_at, ends_at) triples and presumably you're already ensuring that starts_at <= ends_at.

Ruby on Rails: how do I sort with two columns using ActiveRecord?

I want to sort by two columns, one is a DateTime (updated_at), and the other is a Decimal (Price)
I would like to be able to sort first by updated_at, then, if multiple items occur on the same day, sort by Price.
In Rails 4 you can do something similar to:
Model.order(foo: :asc, bar: :desc)
foo and bar are columns in the db.
Assuming you're using MySQL,
Model.all(:order => 'DATE(updated_at), price')
Note the distinction from the other answers. The updated_at column will be a full timestamp, so if you want to sort based on the day it was updated, you need to use a function to get just the date part from the timestamp. In MySQL, that is DATE().
Thing.find(:all, :order => "updated_at desc, price asc")
will do the trick.
Update:
Thing.all.order("updated_at DESC, price ASC")
is the Rails 3 way to go. (Thanks #cpursley)
Active Record Query Interface lets you specify as many attributes as you want to order your query:
models = Model.order(:date, :hour, price: :desc)
or if you want to get more specific (thanks #zw963 ):
models = Model.order(price: :desc, date: :desc, price: :asc)
Bonus: After the first query, you can chain other queries:
models = models.where('date >= :date', date: Time.current.to_date)
Actually there are many ways to do it using Active Record. One that has not been mentioned above would be (in various formats, all valid):
Model.order(foo: :asc).order(:bar => :desc).order(:etc)
Maybe it's more verbose, but personally I find it easier to manage.
SQL gets produced in one step only:
SELECT "models".* FROM "models" ORDER BY "models"."etc" ASC, "models"."bar" DESC, "models"."foo" ASC
Thusly, for the original question:
Model.order(:updated_at).order(:price)
You need not declare data type, ActiveRecord does this smoothly, and so does your DB Engine
Model.all(:order => 'updated_at, price')
None of these worked for me!
After exactly 2 days of looking top and bottom over the internet, I found a solution!!
lets say you have many columns in the products table including: special_price and msrp. These are the two columns we are trying to sort with.
Okay, First in your Model
add this line:
named_scope :sorted_by_special_price_asc_msrp_asc, { :order => 'special_price asc,msrp asc' }
Second, in the Product Controller, add where you need to perform the search:
#search = Product.sorted_by_special_price_asc_msrp_asc.search(search_params)

Resources