Rails combine two objects into one result hash - ruby-on-rails-3

I have the following models in Rails: Users and *Friendship_Requests* friendship_requests contains user ids in the fields "from_user" and "to_user". I want to get a list of requests linked to a specific user from the requests model (which contains a message field also). I would also like the result to return the requesting users data that is held in the Users model.
In SQL I would use the following:
SELECT users.*, friendships_requests.*
FROM friendships_requests
JOIN users ON friendships_requests.from_user = users.id
Any ideas appreciated, thanks.

To find the friendship requests sent to #user:
#friendship_requests = FriendshipRequests.find_by_to_user(#user.id)
and then the informations relative to the sending users can be retrieved as:
#friendship_requests.each do |request|
user = User.find_by_id request.from_user
# Do something
end
or, if you want to collect all of them
#users = #friendship_requests.map {|r| User.find_by_id r.from_user}

Related

Fastest way to return a list of records depending on many many-to-many relationships

I'm developing an API in Rails in which exists users and messages tables. Also users have a gender (gender_id), belong to a country (country_id) and also have a civil status (civil_status_id) and the messages are created by admins.
So up to here I have this model.
Now I have to develop the following requirement
An admin should be able to create a message targeted to users depending on its attributes (country, gender or civil status). Also, the admin should be able to declare a message as a global message, in this case "all" users should receive it, but exceptions should also be allowed. For example, in the case where an admin want to send a message to users from all countries, except people from Russia and China.
The thing is I'm no Rails/SQL expert, but I want to make this efficiently so that if tomorrow the app has ten thousand or a hundred thousand users the server responds quickly.
So I was thinking the following
First create 3 many-to-many relationships (countries_messages, genders_messages and civil_statuses_messages). The record of these tables represent the relations between the messages and the countries, civil_statuses and genders.
Then create a form where an admin can create a message, where by means of several select boxes, he should be able to choose the attributes of the users to whom he wants to reach. The form for creating a message should also have a checkbox to determine if the message is global, if its marked then I would consider that the selected countries, genders and civil statuses would be the categories that the administrator wants to exclude, i.e. if an admin want to send a message to all the people in the system except for people who are from Canada he should mark the global option and select the country Canada in the select box (obviously this would be stated in the view).
Now up to here I have this model.
In what I do have doubts is which way is more efficient to return the messages that corresponds to a user.
Method 1
When an admin specifies that a message is global, except for those from country with id 3 then I could add to countries_messages records like (country_id: 1, message_id: 2), (country_id: 2, message_id: 2), (country_id: 4, message_id: 2), ..., etc. i.e. forming a relation with every country except the country with id 2.
Then retriveng the messages that the current user should read like the following:
global_messages = Message.where(global: true).ids
country_messages = current_user.country.messages.ids
gender_messages = current_user.gender.messages.ids
civil_status_messages = current_user.current_status.messages.ids
#messages = Message.find(global_messages + country_messages + gender_messages + civil_status_messages)
Method 2
Other way could be forming a relation of that message with the excluded country, i.e. if I make a message exclusively for people from country with id 2 then I should add the record (country_id: 2, message_id: 2) in countries_messages, but in the contrary case if I made a message to every country except the country with id 2 then I should also add the record (country_id: 2, message_id: 2) to countries_messages.
In this case I can know if a message is excluded for males and people from Argentina, for example, if the message is global AND it's associated with the country and gender record that represents Argentina and males.
Then the retriveng of the messages that the current user should read would be like this:
global_messages = Message.where(global: true).ids
country_messages = current_user.country.messages
gender_messages = current_user.gender.messages
civil_status_messages = current_user.current_status.messages
excluded_country_messages_ids = country_messages.where(global: true).ids
excluded_gender_messages_ids = gender_messages.where(global: true).ids
excluded_civil_status_messages_ids = civil_status_messages.where(global: true).ids
#messages = Message.find(global_messages + country_messages + gender_messages + civil_status_messages - excluded_country_messages_ids - excluded_gender_messages_ids - excluded_civil_status_messages_ids)
There could be more ways to do the same, so I want to receive recommendations or if you see that I could make improvements to do the same then tell me. If there is something you do not understand ask.
Depending on your database choice, you may want to consider storing the message "attributes" (country, gender, ...) in a jsonb column on your messages table. It could also include a user_ids attribute to simplify things. The queries might look a little funny, but you can move a lot into scopes to clear things up in your code and even add indexes to speed things up.
Here is a great article on using jsonb with indexes in Rails with postgresql.
Another alternative you could do is make a UserMessage join table with a polymorphic reference that could associate the different attributes to a message and user:
class UserMessage < ApplicationRecord
belongs_to :user
belongs_to :message
belongs_to :messageable_type # e.g. "Country"
belongs_to :messageable_id # e.g. some Country id
end

SQL query to filter out users who already have recieved an email

Suppose:
You tried to send a mass mailing, but something went wrong, and only some users got the mail.
You sent a mass mailing recently, but now new users have signed up, and they need to receive the mail as well.
How do you filter those who have already received the news (via an Eloquent query or a select command from database .)
Actually I faced this problem in a project based on Laravel 4, I am searching for a query using Laravel Eloquent on a pivot table and keep track the sent emails to the users.
In my case there is two Model: Post and User
Its a many to many relationship: any user can receive many posts and any post can be sent to many users
You'll want to add a column to your users table eg a datetime field called mailed_at. Then in your email or signup method (wherever it is you're sending that first email) update the user with the datetime they were emailed.
From then on you can query based on mailed_at for any users who still need an email.
To check for multiple users that need a newsletter, let's say your users table schema is as follows (keeping it simple):
id | email | password | mailed_at (nullable)
We check here whether a user has received an email by querying based on the mailed_at column. To get all users that need to receive a mailout you would do the following:
$usersToMail = User::whereNull('mailed_at')->get();
There is two Model: Post and User
Its a many to many relationship: any user can receive many posts and any post can be sent to many users
I implemented it as the following:
class Post extends Eloquent {
public function recipients()
{
return $this->belongsToMany('User', 'posts_users', 'post_id', 'user_id');
}
}
class User extends Eloquent {
public function mails()
{
return $this->belongsToMany('Post', 'posts_users', 'user_id', 'post_id');
}
}
So, when I want to send a post to users I use
$usersToMail = User::whereNotExists(function($query) use ($post_id)
{
$query->select(DB::raw(1))
->from('posts_users')
->whereRaw('posts_users.user_id = users.id')
->whereRaw('posts_users.post_id = '.$post_id);
})->get();
foreach ($usersToMail as $user)
{
// send email
$user->mails()->save($post); // it records that this post has been sent to the user
}

django sort objects by number of friends who have them

I have Ownership model, which has FK to User and Item.
User has m2m to self, friends.
class Ownership:
user = FK(User)
item = FK(Item)
class User:
friends = M2M(User)
I want to get Ownership objects for a user, sorted by the number of user's friends who have the same item. Is it possible in QuerySet at all, or should I go with raw SQL, and if so, what that SQL would look like?
you should be able to do:
(with user u)
users_objects = sorted(Ownership.objects.filter(user=u), key=lambda x:Ownership.objects.filter(item=x.item, user__in=u.friends).count())

Django aggregate query

I have a model Page, which can have Posts on it. What I want to do is get every Page, plus the most recent Post on that page. If the Page has no Posts, I still want the page. (Sound familiar? This is a LEFT JOIN in SQL).
Here is what I currently have:
Page.objects.annotate(most_recent_post=Max('post__post_time'))
This only gets Pages, but it doesn't get Posts. How can I get the Posts as well?
Models:
class Page(models.Model):
name = models.CharField(max_length=50)
created = models.DateTimeField(auto_now_add = True)
enabled = models.BooleanField(default = True)
class Post(models.Model):
user = models.ForeignKey(User)
page = models.ForeignKey(Page)
post_time = models.DateTimeField(auto_now_add = True)
Depending on the relationship between the two, you should be able to follow the relationships quite easily, and increase performance by using select_related
Taking this:
class Page(models.Model):
...
class Post(models.Model):
page = ForeignKey(Page, ...)
You can follow the forward relationship (i.e. get all the posts and their associated pages) efficiently using select_related:
Post.objects.select_related('page').all()
This will result in only one (larger) query where all the page objects are prefetched.
In the reverse situation (like you have) where you want to get all pages and their associated posts, select_related won't work. See this,this and this question for more information about what you can do.
Probably your best bet is to use the techniques described in the django docs here: Following Links Backward.
After you do:
pages = Page.objects.annotate(most_recent_post=Max('post__post_time'))
posts = [page.post_set.filter(post_time=page.most_recent_post) for page in pages]
And then posts[0] should have the most recent post for pages[0] etc. I don't know if this is the most efficient solution, but this was the solution mentioned in another post about the lack of left joins in django.
You can create a database view that will contain all Page columns alongside with with necessary latest Post columns:
CREATE VIEW `testapp_pagewithrecentpost` AS
SELECT testapp_page.*, testapp_post.* -- I suggest as few post columns as possible here
FROM `testapp_page` LEFT JOIN `testapp_page`
ON test_page.id = test_post.page_id
AND test_post.post_time =
( SELECT MAX(test_post.post_time)
FROM test_post WHERE test_page.id = test_post.page_id );
Then you need to create a model with flag managed = False (so that manage.py sync won't break). You can also use inheritance from abstract Model to avoid column duplication:
class PageWithRecentPost(models.Model): # Or extend abstract BasePost ?
# Page columns goes here
# Post columns goes here
# We use LEFT JOIN, so all columns from the
# 'post' model will need blank=True, null=True
class Meta:
managed = False # Django will not handle creation/reset automatically
By doing that you can do what you initially wanted, so fetch from both tables in just one query:
pages_with_recent_post = PageWithRecentPost.objects.filter(...)
for page in pages_with_recent_post:
print page.name # Page column
print page.post_time # Post column
However this approach is not drawback free:
It's very DB engine-specific
You'll need to add VIEW creation SQL to your project
If your models are complex it's very likely that you'll need to resolve table column name clashes.
Model based on a database view will very likely be read-only (INSERT/UPDATE will fail).
It adds complexity to your project. Allowing for multiple queries is a definitely simpler solution.
Changes in Page/Post will require re-creating the view.

Creating a good search solution

I have an app where users have a role,a username,faculty and so on.When I'm looking for a list of users by their role or faculty or anything they have in common I can call (among others possible)
#users = User.find_by_role(params[:role]) #or
#users = User.find_by_shift(params[:shift])
So it keeps the system
Class.find_by_property
So the question is: What if at different points users lists should be generated based on different properties.I mean: I'm passing from different links
params[:role] or
params[:faculty] or
params[:department]
to my list action in my users controller.As I see it all has to be in that action,but which parameter should the search be made by?
Try https://github.com/ernie/meta_search if you're on Rails 3