rails joins query over has_many objects independently - ruby-on-rails

I have parent model Project & children model ToDo.
A project has many ToDos with column starts_at & ends_at.
I would like to search for projects that have 'any' todos in the time range.
Here I wrote some codes, however, it is not as expected.
class Project
has_many :todos
scope :active, -> {joins(:todos).where("todos.starts_at < '#{Time.now}' AND todos.ends_at > '#{Time.now}'").distinct}
scope :waiting, -> {joins(:todos).where.not("todos.starts_at < '#{Time.now}' AND todos.ends_at > '#{Time.now}'").distinct}
scope :done, -> {where("project_due > ?", Time.now)}
end
Active one seems right, but waiting scope also contains the projects that have more than one todos.
I wonder if there any solution to compare starts_at & ends_at per each todo. Not like above.
Thanks ahead.
*# Update *
This is what I wanna achieve. but in one query.
scope :waiting, -> { joins(:todos).where.not(id: active.ids).where('finishing > ?', Time.now).distinct }

Try to the following
Update
For waiting you mean starts_at greater than NOW right? then it will be
scope :waiting, -> {joins(:todos).where("todos.starts_at >= ?", Time.now).distinct}
If the match with the first condition then you don't need to match the second condition, you can write with the second condition like this
scope :waiting, -> {joins(:todos).where("todos.starts_at > '#{Time.now}' AND todos.ends_at > '#{Time.now}'").distinct}
but don't need.
Update 2
Remove the not from where, here not means active
scope :waiting, -> {joins(:todos).where("todos.starts_at >= ?", Time.now).distinct}
Update 3 after Update 2 worked
scope :waiting, -> {joins(:todos).where("todos.starts_at >= ?", Time.now).distinct}
scope :finished, -> {where("finishing > ?", Time.now).distinct}
scope :waiting_n_progress, -> {where.not(id: active.ids).finished.waiting}
the waiting_n_progress scope, I think you will get your goal, remember that's not tested.
Hope it should work.

Your query for waiting basically translate from !(start < now && end > now) to this start >= now || end <= now which would most likely return a lot more projects than you wanted. See if that is the logic that you wanted.
Also, for best Rails practices, you should write query like the user fool-dev suggested in the other answer using question mark. This is to prevent SQL injection as explained in more details here
EDIT
So I think you mean waiting means projects that does not have any Todo or those that have awaiting Todo (start_at > now). I think this should work:
scope(:waiting) { includes(:todos).where('todos.project_id IS NULL OR todos.start_at > ?', Time.now).distinct }
The first part is to select projects that doesn't have any Todo and the second part is self explanatory.

Related

get value within rails scope

I want to create a scope, I want to get all Orders in which has passed more than 24hrs since the last time they were updated.
I want to get all those Orders for then loop in them:
Order.more_than_24_updated.find_each do |order|
#do some stuff with each order
end
I do not know how to add 24hrs to the updated_at attribute of orders in the scope:
scope :more_than_24_updated, -> { where(status: "pago_pendiente").where('#{Time.now} >= ?', updated_at + 24.hours) }
Well I know the above does not work, but well... how to do this?
You can try something like this:
scope :more_than_24_updated, -> {where('status = ? and updated_at <= ?', :pago_pendiente, 24.hours.ago)}
You can try this one
scope :more_than_24_updated, -> { where(status: "pago_pendiente").where("NOW() - '24 hours'::INTERVAL >= updated_at") }

Rails scope filter by date range

There are many questions relate to rails date range problem but mine is a little more complicated.
I have two models: house and booking. A House has_many bookings. A Booking has two attributes in date format: check_in and check_out.
What I want to achieve: Giving a valid date range, show all houses that are available during this range. In detail:
The start date of the range should not be in any booking.
The end date of the range should not be in any booking.
There should not be any booking between the start and the end.
Can this be done using the rails scope?
UPDATE:
I found the code below that can check scope date interval that overlaps.
named_scope :overlapping, lambda { |interval| {
:conditions => ["id <> ? AND (DATEDIFF(start_date, ?) * DATEDIFF(?, end_date)) >= 0", interval.id, interval.end_date, interval.start_date]
}}
How can I transfer this to my problem?
scope :overlapping, (lambda do |start_date, end_date|
House.includes(:bookings).where("bookings.check_in < ? AND bookings.check_out > ?",
start_date, end_date).references(:bookings).uniq
end)
I went ahead and deleted the >= and <= operators in favor of > and < to explicitly show these bookings being outside of the given range, but you can adjust them per your needs!
Update
Changed query to use #includes instead of #joins, since we're querying the attached table.
Yes it is possible to have this query through scope. Put this scope in house model.
scope :overlapping, -> (start_date, end_date) {
includes(:bookings).where('bookings.check_in < ? AND bookings.check_out > ?',
start_date.to_date, end_date.to_date)
}
And call as House.overlapping('2015-07-01', '2015-07-09')

Rails 4 scope add filtered out record

I have this scope to show recent posts, but I want outdated sticky posts to be shown as well. That is, the sticky post is created 3 days ago and the first condition is picked that post out, however, I also need the post which has a "sticky: true" boolean value to be included in the :to_show scope.
I tried this, but not working.
scope :to_show, -> { where("created_at > ?", Time.now - 1.day).except(sticky: true).order("created_at desc") }
Please advise.
It sounds like you're trying to do the following:
Include all posts that are 'sticky'
Include all posts that have been created in the last day
Order those posts from newest to oldest (do you want sticky posts to be ordered first?)
In that case what you really want is an 'or' condition in your where clause. Something like
scope :to_show, -> { where("(sticky = 1) OR (created_at > ?)", Time.now - 1.day).order("created_at desc") }
If you want to put the sticky items at the top, then the order clause would change to
scope :to_show, -> { where("(sticky = 1) OR (created_at > ?)", Time.now - 1.day).order("sticky desc, created_at desc") }

How to get records created at the current month?

I have a candidate which has_many votes.
I am trying to get the votes of a candidate that were created in the current month?
#candidate.votes.from_this_month
scope :from_this_month, where("created_at > ? AND created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
That gives me a PG error:
PG::Error: ERROR: column reference \"created_at\" is ambiguous
If I try
scope :from_this_month, where("vote.created_at > ? AND vote.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
I get the following error
PG::Error: ERROR: missing FROM-clause entry for table "vote"
Correct scope
scope :from_this_month, lambda {where("votes.created_at > ? AND votes.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)}
This is because in rails the model names are singular(i.e Vote) and tables created are pural (e.g. votes) by convection
EDIT
This can be written simpler with lambda {where(created_at: Time.now.beginning_of_month..(Time.now.end_of_month))} and we need to use lambda due to the reason given in below comments.
Thanx BroiSatse for reminding :D
You need to enclose the where in a lamda as well.
scope :from_this_month, lambda { where("votes.created_at > ? AND votes.created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month) }
Otherwise it may appear to work and your tests will all pass, but if your app runs for more than a month you will start to get incorrect results because Time.now is evaluated when the class loads, not when the method is called.
From Rails 5 you have a much cleaner syntax. On your votes model add a scope this_month
scope :this_month, -> { where(created_at: Date.today.all_month) }
You can now query #candidates.votes.this_month
scope :this_month, -> { where(created_at: Time.zone.now.beginning_of_month..Time.zone.now.end_of_month) }
and you can call the scope:
Model.this_month
You could use an ActiveRecord Association Extension:
#app/models/Candidate.rb
Class Candidate < ActiveRecord::Base
has_many :votes do
def from_this_month
where("created_at > ? AND created_at < ?", Time.now.beginning_of_month, Time.now.end_of_month)
end
end
end
This should allow you to call #candidate.votes.from_this_month to return the required conditional data

How can I find records by "count" of association using rails and mongoid?

With these models:
class Week
has_many :proofs
end
class Proof
belongs_to :week
end
I want to do something like:
Week.where(:proof.count.gt => 0)
To find only weeks that have multiple proofs.
There is one answer that seems to address this:
Can rails scopes filter on the number of associated classes for a given field
But in this example, there is no such attribute as proof_ids in Week since the ids are stored with the proofs. This does not work for example:
Week.where(:proof_ids.gt => 0)
How is this query possible? Conceptually simple but I can't figure out how to do this with mongo or mongoid.
Similarly, I'd like to order by the number of proofs for example like:
Week.desc(:proofs.size)
But this also does not work.
I do realize that a counter-cache is an option to both my specific questions but I'd also like to be able to do the query.
Thanks in advance for any help.
With rails (and without counter_cache), you could do:
class Week < ActiveRecord::Base
has_many :proofs
def self.by_proofs_size
sort_by { |week| week.proofs.size }
end
def self.with_at_least_n_proofs(n = 1)
select { |week| week.proofs.size >= n }
end
end
Even though each of those operations produces 2 queries, this is far from ideal.
The pair of queries is repeated (=> 4 queries for each operation) with scopes (bug?):
scope :with_at_least_n_proofs, -> (n = 1) { select { |w| w.proofs.size >= n } }
scope :by_proofs_size, -> { sort_by { |w| w.proofs.size } }
The ideal is probably to use counter_cache
scope :with_at_least_n_proofs, -> (n = 1) { where('proofs_count >= ?', n) }
scope :by_proofs_size, -> { order(proofs_count: :desc) }
I don't know if this is the best solution, as it maps it through a array, but this does the job: (the other solutions mentioned here gives me exceptions)
class Week < ActiveRecord::Base
scope :has_proofs, -> { any_in(:_id => includes(:proofs).select{ |w| w.proofs.size > 0 }.map{ |r| r.id }) }
end
Pardon me if I'm way off - but would you be able to use a simple counter_cache in the weeks table? Then you could do something like week.proofs_count.

Resources