Rails: How to use includes with conditions? - ruby-on-rails

I have a ressource Room that has_many reservations. Likewise the ressource Reservation belongs_to a room.
My reservation model has a starts_at and an ends_at attribute.
In my index action for the Room controller I want to get all the Rooms and include all the reservations for a certain date(the date is given as a parameter).
I have tried this code, but it doesn't seem to work as intended.
#rooms = Room.includes(:reservations).where("reservations.starts_at" => params[:date])
I am aware that this code does not take into account that a room could be reserved for multiple days, but I had to start somewhere.
SUM UP
Based on a date the action should return all the rooms, but only include the reservations that is relevant for that date.
EDIT
This is what I ended up doing.
controllers/rooms_controller.rb
def index
#rooms = Room.includes(:reservations)
#date = params[:date] ||= Date.today
end
views/rooms/index.html.haml
- #rooms.each do |room|
- room.reservations.relevant(#date).each do |reservation|
= reservation.id
models/reservation.rb
def self.relevant(date = Date.today)
if date.blank?
date = Date.today
end
where(
'(starts_at BETWEEN ? AND ?) OR (ends_at BETWEEN ? AND ?)',
date.to_date.beginning_of_day, date.to_date.end_of_day,
date.to_date.beginning_of_day, date.to_date.end_of_day
)
end
It works alright, but the view is talking to the model I think?

If your where conditions refer to another table then you also need to need to specify references as well as includes. e.g.
#rooms = Room.includes(:reservations).
where("reservations.starts_at" => params[:date]).
references(:reservations)
See the API documentation here.

Related

Rails query with condition in count

I'm having a little trouble with a query in Rails.
Actually my problem is:
I want to select all users which do not have any user_plans AND his role.name is equals to default... OR has user_plans and all user_plans.expire_date are lower than today
user has_many roles
user has_many user_plans
users = User.where(gym_id: current_user.id).order(:id)
#users = []
for u in users
if u.roles.pluck(:name).include?('default')
add = true
for up in u.user_plans
if up.end_date > DateTime.now.to_date
add = false
end
end
if add
#users << u
end
end
end
This code up here, is doing exactly what I need, but with multiple queries.
I don't know how to build this in just one query.
I was wondering if it is possible to do something like
where COUNT(user_plans.expire_date < TODAY) == 0
User.joins(:user_plans, :roles).where("roles.name = 'default' OR user_plans.expire_date < ?", Date.today)
Should work, not tested, but should give you some idea you can play with (calling .distinct at the end may be necessary)
There is also where OR in Rails 5:
User.joins(:user_plans, :roles).where(roles: { name: 'default' }).or(
User.joins(:user_plans).where('user_plans.expire_date < ?', Date.today)
)
FYI: Calling .joins on User will only fetch those users who have at least one user_plan (in other words: will not fetch those who have no plans)

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

Ruby on Rails search 2 models

Right... I've spent 3 days trying to do this myself to no a vale.
I have 2 models called Film and Screenings. Screenings belongs_to Film, Film has_many Screenings.
The Film has certain attributes(:title, :date_of_release, :description, :genre).
The Screening has the attributes(:start_time, :date_being_screened, :film_id(foreign key of Film)).
What I am trying to do is create a Search against both of these models.
I want to do something like this...
#films = Film.advanced_search(params[:genre], params[:title], params[:start_time], params[:date_showing])
And then in the Film model...
def self.advanced_search(genre, title, start_time, date)
search_string = "%" + title + "%"
self.find(:all, :conditions => ["title LIKE ? OR genre = ? OR start_time LIKE ? OR date_showing = ?", title, genre, start_time, date], order: 'title')
end
end
I don't think this could ever work quite like this, but I'm hoping my explanation is detailed enough for anyone to understand what im TRYING to do?? :-/
Thanks for any help guys
I would extract the search capability into a separate (non-ActiveRecord) class, such as AdvancedSearch as it doesn't neatly fit into either the Film or Screening class.
Rather than writing a complex SQL query, you could just search the films, then the screenings, and combine the results, for example:
class AdvancedSearch
def self.search
film_matches = Film.advanced_search(...) # return an Array of Film objects
screening_matches = Screening.advanced_search(...) # return an Array of Screening objects
# combine the results
results = film_matches + screening_matches.map(&:film)
results.uniq # may be necessary to remove duplicates
end
end
Update
Let's say your advanced search form has two fields - Genre and Location. So when you submit the form, the params sent are:
{ :genre => 'Comedy', :location => 'London' }
Your controller would then something like:
def advanced_search(params)
film_matches = Film.advanced_search(:genre => params[:genre])
screening_matches = Screening.advanced_search(:location => params[:location])
# remaining code as above
end
i.e. you're splitting the params, sending each to a different model to run a search, and then combining the results.
This is essentially an OR match - it would return films that match the genre or are being screened at that specified venue. (If you wanted and AND match you would need to the work out the array intersection).
I wanted to write something but this cast says all http://railscasts.com/episodes/111-advanced-search-form
Almost the same case as yours.

Sort posts by number of votes within certain time frame

I currently have a voting system implemented in my app and I'm sorting the posts by number of votes with this code in my view:
<%= render #posts.sort_by { |post| post.votes.count }.reverse %>
I want to sort by number of votes for each post by also don't want the post to be any more than lets say 5 days old. How can I sort the posts by both number of votes and date simultaneously.
This is wrong. You should do all sorting operation on the database side.
For this example consider using Arel for creating complex queries or consider create counter cache column.
You could just add a scope to your posts model, something like:
scope :five_days_ago, lambda { where("created_at >= :date", :date => 5.days.ago) }
Then just adjust your render method to the following:
<%= render #posts.five_days_ago.sort_by { |post| post.votes.count }.reverse %>
This assumes you want to keep the structure you are using. Obviously, as other suggested, doing it all in the database is the best course of action.
luacassus is right. It's better do delegate the sorting to the database for at least two reasons:
Performance
You can chain more query methods onto it (necessary for pagination, for example).
The counter cache is probably the best idea, but for the complex query, let me give it a shot. In your Post model:
class << self
def votes_descending
select('posts.*, count(votes.id) as vote_count').joins('LEFT OUTER JOIN votes on votes.post_id = posts.id').group_by('posts.id').order('votes_count desc')
end
def since(date)
where('created_at >= ?', date)
end
end
So...
#posts = Post.votes_descending.since(5.days.ago)
Indeed it will be better to let the db do the sorting. I would do something like
class Post < ActiveRecord::Base
default_scope :order => 'created_at DESC'
end
then you will always have your posts sorted and if I'm not mistaken the last one should be the first you get, so that substitutes your 'reverse' call. Then you can use the scope posted above to get only 5 days old posts. Also check that there is an index on the created_at column in the db. You can do this with
SHOW INDEX FROM posts
The db will do this much faster than ruby.
I figured out another way to do it although I appreciate your help it may not be the cleanest way but I did
def most
range = "created_at #{(7.days.ago.utc...Time.now.utc).to_s(:db)}"
#posts = Post.all(:conditions => range)
#title = "All Posts"
#vote = Vote.new(params[:vote])
respond_to do |format|
format.html
format.json { render :json => #users }
end
end
for my controller
created a route of /most :to => 'posts#most'
and made a view with the original code I had in my view.
I know its not the best way but I am still new to coding so its the best way I could figure out how.

Find and display nearest date in RoR

I am new to ruby on rails and I'm not sure where to start with this. I have a model for users, and one for projects. Users have many projects, and projects have one user. There is an end_date column in the projects table (as well as a name column).
What I want to do is find the project with the nearest end_date and display it's name and end date on the user's show page.
I tried putting this code in the projects controller, but I do not know if it is working, because I don't know how to access it and display the project name in the view.
def next_deadline(after = DateTime.now, limit = 1)
find(:all, :conditions => ['end_date > ?', after], :limit => limit)
end
Any help would be appreciated. Let me know if more information is needed.
As #Dan mentioned, you do need the :order clause to get the first one, but you should add it to your query and not replace the :conditions (otherwise you'll get the project with the earliest end_date irrespective of your after argument). The way you're defining this method is a bit off though. It should be defined in your Project model (and definitely not the controller) as a class method, or, what I think is a better approach, as a scope. In Rails < 3 (which it seems that you're using):
class Project < ActiveRecord::Base
named_scope :next_deadline, Proc.new { |after = DateTime.now, limit = 1| {:conditions => ['end_date > ?', after], :order => "end_date ASC", :limit => limit} }
...
end
Or in Rails >= 3:
class Project < ActiveRecord::Base
scope :next_deadline, Proc.new { |after = DateTime.now, limit = 1| where('end_date > ?', after).order("end_date ASC").limit(limit) }
...
end
Also, you can always test this kind of code using the Rails console: script/console in Rails < 3, rails c in Rails >= 3.
#projects = Project.find_by_sql("SELECT projects.* FROM projects
JOIN users ON users.id = projects.user_id AND projects.user_id = " + #user.id.to_s + "
WHERE projects.end_date > now()
ORDER BY projects.end_date ASC
LIMIT " + limit)
or
#projects = Project.where(:user_id => #user.id)
.where("end_date > ?", DateTime.now)
.order("end_date ASC")
You want to use :order, not :conditions.
Model.find(:all , :order => "end_date ASC")
Then the first result will be the item with the closest end_date
As Dan said, the condition you wrote won't get the nearest end date, but the dates that are greater than today, or the date passed in as a parameter.
In your User model you could write
def next_deadline_project
self.projects.first
end
as long as you give projects a default scope that orders records by end_date
In order to show information on the view you must set it in an instance variable in the User's controller show method. Instance variables are passed to views and you can access them to display the data.
#project = next_deadline_project
And in your show.html.erb you can use something like:
<%= #project.name %> - <%= #project.end_date %>

Resources