Getting "Not In" to Work in Rails MySQL Query - ruby-on-rails

I have three models - User. Game, and Activity. I have a view that is supposed to show games where the user has no Activity records. I'm having trouble figuring out how to write the query that excludes Games that already have activity.
When I do these two versions (in the Controller), it does not work (I receive the error undefined method 'game_id' for #<ActiveRecord::Relation:0x69eab40>)
version 1
def index
if current_user
#activities = current_user.activities.game_id
#games = Game.where("id NOT IN (?)", #activities )
end
end
version 2
def index
if current_user
#activities = current_user.activities
#activity_ids = #activities.game_id
#games = Game.where("id NOT IN (?)", #activity_ids )
end
end
However, when I do this it works:
The controller:
def index
if current_user
#activities = current_user.activities
#games = Game.where("id NOT IN (?)", #activities.collect {|p| p.game_id} )
end
end
I'm worried about doing it this way because I'm not sure how scalable it is. I got the idea to do it from this [question], but in along with the answers people said that this solution was not scalable (which was not an issue for that question asker).1

your error is not in that line. what's causing your error is this
#activities = current_user.activities.game_id
current_user.activities is an ActiveRecord::Relation object so calling game_id on it raises an exception. Your solution that works is fine since you're going to use #activities (you're going to, right?). If you want a more sql approach, try
def index
if current_user
#activities = current_user.activities
#games = Game.where("id NOT IN (?)", #activities.uniq.pluck(:game_id))
end
end

Related

how to check if there are any results in a query and add an if/else condition

in my home_controller, I have to show several lists.
I have this:
def subscriptions
#movies = current_user.followed_movies
.limit(12)
.order('movies.last_news DESC NULLS LAST').decorate
end
def watched
#movies = current_user
.watched_movies
.order_by_watched_date
.limit(12).decorate
end
I want to add an if condition in the def subscriptions.
For example
def subscriptions
#movies = if this query has no results... current_user.followed_movies
.limit(12)
.order('movies.last_news DESC NULLS LAST').decorate
else
to show the movies in the def watched
end
end
How to do?
it's not clear exactly what you're looking for, but I think you mean:
"if the subscriptions query is empty, use the watched query instead".
I'd probably do that like this:
def set_movies
#movies = subscriptions
#movies = watched if subscriptions.empty?
#movies = #movies.limit(12).decorate
end
def subscriptions
current_user.followed_movies.order_by_last_news
end
def watched
current_user.watched_movies.order_by_watched_date
end
and then in user.rb I might add:
scope :order_by_last_news, -> { order('movies.last_news DESC NULLS LAST') }
We can create scopes but for the simplicity, two separate methods can be created as below:
def movies_followed
current_user.followed_movies
end
def movies_watched
current_user.watched_movies
end
And then we can use those two methods in the below def subscriptions as below:
def subscriptions
#movies =
if movies_followed
movies_followed.limit(12).order('movies.last_news DESC NULLS LAST').decorate
else
movies_watched.order_by_watched_date.limit(12).decorate
end
end
Hope, it suites your requirement...

How to use math in controller variables

I simply need to be able to use math to set the end point of a query in my controller.
class WelcomeController < ApplicationController
def index
#video = Video.last
#videos = Video.last(7).reverse!.drop(1)
end
def show
#video = Video.find(params[:id])
#videos = Video.where(:id => start..stop)
end
end
This line: #videos = Video.where(:id => start..stop) should be something like #videos = Video.where(:id => params[:id]..params[:id]-7) because that array is supposed to be the next seven database entries after #video.
I'm also certain there's a better way to accomplish what I'm trying to do but I have no idea what it is.
One way to solve this is using a gem called will_paginate, which will automatically do what you are trying to solve
Example would be
#videos = Video.paginate(page: params[:page] || 1, per_page: 5)
you can get the page from the params, so your web page has to request something like this /videos?page=2
Other gem in this category is Kaminari

Slimming up scoped filters in rails controller

I'm putting filtering functionality into an application (Rails 4.1beta) - I did this by creating scopes on the Item model, passing a scope through the request params and doing a case statement in the index action. It all works but there's a code smell I'm trying to get rid of in the index action of one of the controllers;
def index
case params[:scope]
when "recent"
#items = Item.recent
when "active"
#items = Item.active
when "inactive"
#items = Item.inactive
else
#items = Item.all
end
end
It all feels a little too rigid / verbose. I'd really like to just do something like this;
def index
#items = Item.send(params[:scope])
end
but then I leave the application wide open to people calling methods on the Item class. Whacking conditions in there kinda defeats the point of what I'm trying to achieve.
Is there some rails magic I'm missing that can help me here?
You can use different controllers to do each of these.
inactive_items_controller.rb
def index
#items = Item.inactive
end
recent_items_controller.rb
def index
#items = Item.recent
end
etc.
Or you can just move you logic you have above to the model
Item model
def self.custom_scope(scope)
case scope
when "recent"
Item.recent
when "active"
Item.active
when "inactive"
Item.inactive
else
Item.all
end
end
or
def self.custom_scope(scope)
recent if scope == 'recent'
active if scope == 'active'
inactive if scope == 'inactive'
scoped if scope.blank?
end
And then in your index
#items = Item.custom_scope params[:scope]
something like this:
if ['recent', 'active', 'inactive'].include?(params[:scope])
#items = Item.send(params[:scope])
else
#items = Item.all
end

Refactoring a similar controller's action

I used Pull Review for reviewing my app's code and it came back with this:
Consider refactoring, similar code detected.
Occurred at:
SkillsController # index
PagesController # index
So the app/controllers/skills_controller.rb index action code is:
def index
#skill = Skill.new
if params[:search]
#skills = Skill.search(params[:search]).order('created_at DESC')
else
#skills = Skill.all.order('created_at DESC')
end
end
and on app/controllers/pages_controller.rb is:
def index
#users = User.all
if params[:search]
#users = User.search(params[:search]).order('created_at DESC')
else
#users = User.all.order('created_at DESC')
end
end
Am I suppose to somehow refactor these two actions on these two controllers? Also, I am not sure how I refactor this. Do I extract the if params[:search] segment and replace the instance variables with another variable that will be used on both actions?
Thanks for your time.
I don't know where your method search comes from. It seems it comes from a custom module/gem for ActiveRecord.
If so, you can change the method to shorten code in controller
def self.search(args)
return self unless args
original_search_logic args
end
# As well as extract order to a scope
scope :by_time, -> { order('created_at DESC') }
Then in controller:
# Skill
def index
#skills = Skill.search(params[:search]).by_time
end
# User
def index
#users = User.search(params[:search]).by_time
end
These should be dry enough for now.
take a look at the has_scope and inherited_resources. You can extract the params[:search] part with has_scope. And use inherited_resources to extract how to get the collection and do the ordering.

what is better SQL optimization and DRY code?

Which is the best strategy for writing DRY code as well as SQL optimized?
Passing an optional parameter, subject_id, and passing #subject to the view only if this parameter exists as well as filtering "pages" that contain that subject_id in their record.
This:
def list
#pages = Page.order("pages.position ASC")
if (params.has_key?(:subject_id))
#subject = params[:subject_id]
#pages = #pages.where("subject_id = ?", params[:subject_id])
end
end
Or:
def list
if (params.has_key?(:subject_id))
#subject = params[:subject_id]
#pages = Page.order("pages.position ASC").where("subject_id = ?", params[:subject_id])
else
#pages = Page.order("pages.position ASC")
end
end
I'd probably go with a slight variation of the first one:
def list
#pages = Page.order("pages.position ASC")
if (params.has_key?(:subject_id))
#subject = params[:subject_id]
#pages = #pages.where(:subject_id => params[:subject_id])
end
end
In either case, #pages won't hit the database until you try to iterate over it so both versions will be the same as far as the database is concerned. Furthermore, these two should be identical (or as close to identical as to not matter) as far as Ruby is concerned:
x = M.order(...).where(...)
# and
x = M.order(...)
x = x.where(...)
so the above version of list is DRYer and should perform exactly the same as the more repetitive version.

Resources