multiple string queries on a single table - sql

making a site for game trailers and on the front page I organize the games in terms of their category, so I end up doing this (rails):
def index
#newGames = Game.order("created_at DESC").limit(3)
#casualGames = Game.where("category = 'casual'").limit(9)
#actionGames = Game.where("category = 'action'").limit(8)
#strategyGames = Game.where("category = 'strategy'").limit(9)
#adventureGames = Game.where("category = 'adventure'").limit(8)
#puzzleGames = Game.where("category = 'puzzle'").limit(9)
end
Is there a way to accomplish the same thing but without making 6 separate queries on the sable table?
Thanks

As your search parameters are different querying DB multiple times is unavoidable. However you can make your controller skinny. Create a class method in Game class and collect and return everything you need in a hash.
Game.rb
def self.for_index_page
games = {}
games.merge!(new: order("created_at DESC").limit(3))
games.merge!(casual: category_with_limit('casual', 9)
games.merge!(action: category_with_limit('action', 8)
...
end
def self.category_with_limit(category, limit)
where(category: category).limit(limit)
end
GamesController.rb
def index
#games = Game.for_index_page
end
index.erb
<%=#games[:new] %>
<%=#games[:casual] %>
...

Related

Understanding Full Join in Django

I have two models in my app:
# Create your models here.
class Melody(models.Model):
notes = models.JSONField()
bpm = models.IntegerField()
aimodel = models.CharField(max_length=200)
score = models.IntegerField(default=0)
person = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name="melodies")
date_created = models.DateTimeField(default=timezone.now)
def __str__(self):
return str(self.id)
class Vote(models.Model):
user_score = models.IntegerField(validators=[MaxValueValidator(1), MinValueValidator(-1)])
melody = models.ForeignKey(Melody, on_delete=models.CASCADE, related_name="scores")
person = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="voters")
def __str__(self):
return f"{self.person} - {self.melody} - {self.score}"
And I get the melodies of the current user by
# Get melodies of current user
melodies = Melody.objects.all().filter(person=person).order_by('-score')[start:end+1].values()
I would like to add to this results the vote of the user to each melody, if there is one, otherwise just null so I can loop over the melodies and retrieve the values:
melody.notes = ...
melody.bpm = ...
melody.user_score = This is the values I do not know still how to get, Null if user has not voted
I was reading about select_related but when I use it it always says
"Invalid field name(s) given in select_related: 'xxxx'. Choices are: (none)"
What am I missing?
EDIT
I solved it based on the answer of #Fnechz by making two queries and then looping over the elements so I can add the user_score to the melody:
# Get melodies of current user
melodies = Melody.objects.all().filter(person=person).order_by('-score')[start:end+1].values()
# Get votes of the user
votes = Vote.objects.all().filter(person=person)
for i, m_melody in enumerate(melodies):
for m_vote in votes:
if (m_vote.melody.id == m_melody['id']):
melodies[i]['user_score'] = m_vote.user_score
return JsonResponse({"melodies": list(melodies)})
Not sure if this is the best way to achieved it
I do not know if there is a direct method to accomplish what you want with a single query. But I guess concatenating the queryset results might work.
from itertools import chain
melodies = Melody.objects.all().filter(person=person).order_by('-score')[start:end+1].values()
votes = #query your vote model here to get the user_score
result_list = list(chain(melodies,votes))
If I have understood your question that might work

Rails: changing repetition into loops

I'm trying to reduce repetition in my code. I have in several places this code (or variants thereof):
#articles1 = Article.all_articles(1).reverse
#articles2 = Article.all_articles(2).reverse
#articles3 = Article.all_articles(3).reverse
Is a way to change it to something like:
3.times do |i|
#articles[i+1] = Article.all_articles(i+1).reverse
end
Cheers!
How about:
#articles = (1..3).to_a.map { |i| Article.all_articles(i).reverse }

In Rails 3 data save with out p()

In the below code retrive data from database arrange those & delete from database again insert
page_nos = [10,11,12,13,14,20,21,22,23,24,30,31,32,33,34,40,41,42,43,44]
page_nos.each do |page|
#page_data = HomePageSetting.where("page_no = '#{page}'").order("score desc")
p(#page_data)
HomePageSetting.where("page_no = #{page}").delete_all
#page_data.each do |data|
#home_page = HomePageSetting.new
#home_page.subject_id = data.subject_id
#home_page.subject_type = data.subject_type
#home_page.score = data.score
#home_page.flag = data.flag
#home_page.flag_detail = data.flag_detail
#home_page.page_no = data.page_no
#home_page.release_date = data.release_date
#home_page.item_created_at = data.item_created_at
#home_page.save
end
end
that is not working if i remove p(#page_data) line
I don't quite understand what you're trying to do, but I suspect I do know the reason that outputting the #page_data variable makes a difference.
#page_data is an ActiveRecord::Relation object - that is, a stored SQL query. Because executing a query and loading the resulting objects into memory takes time, Rails doesn't actually execute the query until it has to. When you output #page_data, its has to instantiate those objects to print the output. They then exist in memory, and can be referenced even after you delete their rows from the database in the next line.
If you don't print out that data, it stays uninstantiated on the next line, where you delete all those rows. Then, you call .each on it, and it gets instantiated at that point, and there are no objects to fetch, because you just deleted them all.
You can force Rails to instantiate the relation when you first define #page_data by calling .all, on it, thus:
#page_data = HomePageSetting.where("page_no = '#{page}'").order("score desc").all
It looks as if one problem at least is that you're calling .save inside the .new block -- you should save the record after.
Here's the changed code:
page_nos = [10,11,12,13,14,20,21,22,23,24,30,31,32,33,34,40,41,42,43,44]
page_nos.each do |page|
#page_data = HomePageSetting.where("page_no = '#{page}'").order("score desc")
p(#page_data)
HomePageSetting.where("page_no = #{page}").delete_all
#page_data.each do |data|
#home_page = HomePageSetting.new
#home_page.subject_id = data.subject_id
#home_page.subject_type = data.subject_type
#home_page.score = data.score
#home_page.flag = data.flag
#home_page.flag_detail = data.flag_detail
#home_page.page_no = data.page_no
#home_page.release_date = data.release_date
#home_page.item_created_at = data.item_created_at
end
#home_page.save
end
When I'm having problems like this I also sometimes will use .save! -- it will halt processing and throw an exception if the data doesn't save correctly. .save can fail silently and simply return false. To try that use #home_page.save! instead.
Good luck!

Creating a user actions stream in rails

I'm trying to create a stream of actions in my rails App. Here are the helper methods I use to generate a list of likes a user received, and a list of stories a user's friend wrote.
Later, I simply combine these into one array to display in a view. But, I want to sort my final array, by date, but when I use .map in the earlier methods, I can't figure out how to get a date object in there that I can sort by.
def get_stream(current_user)
stories = get_friends_stories(current_user)
likes = get_likes(current_user)
stream = [stories, likes]
stream.flatten
end
def get_likes(user)
stories = get_stories(user)
likes = Like.find_all_by_story_id(stories)
hash = likes.map {|like| "#{like.user.display_name} liked your story #{like.story.title}" }
end
def get_friends_stories(user)
friends = get_friends(user)
friend_ids = friends.map {|f| f.friend_id }
stories = Story.find_all_by_user_id(friend_ids)
hash = stories.map {|story| "#{story.user.display_name} wrote a story called #{story.title}" }
end
Just a simple change seems to work:
def get_stream(current_user)
stories = get_friends_stories(current_user)
likes = get_likes(current_user).sort_by {|l| l.created_at }
streams = stories + likes
streams.sort_by(&:created_at)
end

Performing PostgreSQL LEFT OUTER JOINS and CASEs in Django

So I have a fairly involved sql query here.
SELECT links_link.id, links_link.created, links_link.url, links_link.title, links_category.title, SUM(links_vote.karma_delta) AS karma, SUM(CASE WHEN links_vote.user_id = 1 THEN links_vote.karma_delta ELSE 0 END) AS user_vote
FROM links_link
LEFT OUTER JOIN auth_user ON (links_link.user_id = auth_user.id)
LEFT OUTER JOIN links_category ON (links_link.category_id = links_category.id)
LEFT OUTER JOIN links_vote ON (links_vote.link_id = links_link.id)
WHERE (links_link.id = links_vote.link_id)
GROUP BY links_link.id, links_link.created, links_link.url, links_link.title, links_category.title
ORDER BY links_link.created DESC
LIMIT 20
All my relations are good (I think) and this query works perfectly when I run it in my navicat for postgresql but turning it into something Django can use has been quite the challenge. I am using the pre-alpha 1.2 development verison (from the subversion repositories) so I have full range of tools from the docs.
Here are my models for grins:
class Category (models.Model):
created = models.DateTimeField(auto_now_add = True)
modified = models.DateTimeField(auto_now = True)
title = models.CharField(max_length = 128)
def __unicode__(self):
return self.title
class Link (models.Model):
category = models.ForeignKey(Category)
user = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add = True)
modified = models.DateTimeField(auto_now = True)
fame = models.PositiveIntegerField(default = 1)
url = models.URLField(max_length = 2048)
title = models.CharField(max_length = 256)
active = models.BooleanField(default = True)
def __unicode__(self):
return self.title
class Vote (models.Model):
link = models.ForeignKey(Link)
user = models.ForeignKey(User)
created = models.DateTimeField(auto_now_add = True)
modified = models.DateTimeField(auto_now = True)
karma_delta = models.SmallIntegerField(default = 1)
def __unicode__(self):
return str(self.karma_delta)
How I am able to turn
def latest(request):
links = Link.objects.all().order_by('-created')[:20]
return render_to_response('links/list.html', {'links': links})
Into the above query?
I've only been able to make some progress using things like Aggregation but how to tackle my use of CASE is beyond me. Any help would be much appreciated. I always prefer to work in a framework's built in ORM but if raw SQL is necessary...
I don't have time at the moment to attempt a full translation of that query, but if the CASE is your main stumbling block, I can tell you right now it isn't supported natively, you'll need to use a call to .extra() with some raw SQL for that. Something like:
.extra(select={'user_vote': 'SUM(CASE WHEN links_vote.user_id = 1 THEN links_vote.karma_delta ELSE 0 END')})
But if this query works well as-is, why bother translating it into the ORM? Just grab a cursor and run it as a SQL query. Django's ORM is intentionally not a 100% solution, there's a reason it exposes the raw cursor API.
Update: And since Django 1.2, there's also Manager.raw() to let you make raw SQL queries and get model objects back (thanks Van Gale).