Creating a user actions stream in rails - ruby-on-rails-3

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

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

Updating Handles for MatrixBlock "Field Layout Fields" in Craft CMS Migrations

After reading this excellent Medium article, I've been stoked on Migrations in CraftCMS. They've been super useful in importing the same changes across the 10 or so developers who work on our site.
When trying to change the handles on individual fields within block types within matrix blocks (whew) via migrations, I came across a hurdle. The field itself can easily have its "handle" attribute updated, but the columns table for that matrix block's content (matrixcontent_xxxxx) do not get updated to reflect any updated handles. The association between the Matrix Block & its associated Matrix Content table only exists in the info column in the field record for that Matrix Block.
If the Matrix Block's field is updated via the CMS, the change is reflected, so it's gotta be somewhere in Craft's source, but it's not apparent through the Craft CMS Class Reference.
Any ideas?
Edited to add the migration snippet:
public function safeUp()
{
// Strings for labels, etc.
$matrix_block_instructions = "instructions";
$block_label = "Video";
$block_handle = "video";
$field_handle = "video_url";
$field_label = "Video URL";
$field_instructions = "Add a YouTube or Facebook video URL.";
// Fetching the MatrixBlock fields used on the entries themselves
$video_gallery_1 = Craft::$app->fields->getFieldByHandle("handle_1");
$video_gallery_2 = Craft::$app->fields->getFieldByHandle("handle_2");
$video_gallery_3 = Craft::$app->fields->getFieldByHandle("handle_3");
$galleries = [$video_gallery_1, $video_gallery_2, $video_gallery_3];
foreach( $galleries as $gallery ) {
// Fetching the record for this specific MatrixBlock field.
$gallery_record = \craft\records\Field::find()->where(
['id' => $gallery->id]
)->one();
// Fetching the record for this specific MatrixBlockType
$gallery_block_id = $gallery->getBlockTypes()[0]->id;
$gallery_block = \craft\records\MatrixBlockType::find()->where(
['id' => $gallery_block_id]
)->one();
// Assigning standard labels for the MatrixBlockType
$gallery_block->name = $block_label;
$gallery_block->handle = $block_handle;
$gallery_block->update();
// Getting the specific ... 1 ... field to edit
$field_group = \craft\records\FieldLayout::find()->where(
['id' => $gallery_block->fieldLayoutId]
)->one();
$field_layout_field = $field_group->fields[0];
$field = $field_layout_field->field;
$field = \craft\records\Field::find()->where(
['id' => $field->id]
)->one();
// Assigning standard labels for the Label
$field->name = $field_label;
$field->handle = $field_handle;
$field->instructions = $field_instructions;
$field->update();
// Updating the MatrixBlock record with new instructions
$gallery_record->refresh();
$gallery_record->instructions = $matrix_block_instructions;
$gallery_record->update();
}
OK, so my apologies if anyone was stoked on figuring this out, but my approach above was kind of a crazy person's approach, but I've found my own solution.
The key here is that I should have been interacting with craft\fields\MatrixBlock and the craft\fields\PlainText objects, not craft\records\Field objects. There's a method within \craft\services\Fields for saving the field that requires a FieldInterface implemented. This is actually the default classes returned, and I was making more work for myself in the code!
Within that foreach loop, this worked out:
// Fetching the record for this specific MatrixBlock field.
$gallery->instructions = $matrix_block_instructions;
// Update the MatrixBlockType
$gallery_block_id = $gallery->getBlockTypes()[0]->id;
$gallery_block = \craft\records\MatrixBlockType::find()->where(
['id' => $gallery_block_id]
)->one();
$gallery_block->name = $block_label;
$gallery_block->handle = $block_handle;
$gallery_block->update();
// Update the actual field.
$field = $gallery->blockTypeFields[0];
$field->name = $field_label;
$field->handle = $field_handle;
$field->instructions = $field_instructions;
Craft::$app->getFields()->saveField( $field );
Craft::$app->getFields()->saveField( $gallery );
Thanks for looking at this, and sorry for being a crazy person.

How can I make raw sql ManyToMany and Annotation in django?

class Post(models.Model):
user = models.ForeignKey(FBUser, related_name='posts')
group = models.ForeignKey(Group, related_name='posts')
class Comment(models.Model):
user = models.ForeignKey(FBUser, related_name='comments')
post = models.ForeignKey(Post, related_name='comments')
group = models.ForeignKey(Group, related_name='comments')
class Group(models.Model):
name = models.CharField(max_length=100)
I made this code, but this code is really slow to get result over 10 seconds.
_group.user_set.filter(posts__group=_group, comments__group=_group) \
.annotate(p_count=Count('posts', distinct=True), c_count=Count('comments', distinct=True))
How can I make this code to raw sql?

multiple string queries on a single table

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] %>
...

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).