Rails sort entries based on attributes of related model - ruby-on-rails-3

I have a strange scenario: I would like to order all posts P by the time at which P's creator and the current user became friends.
The result would be a list of posts with newer friends' posts at the top and older friends' posts at the bottom. We can assume all users are friends of all other users, but each friendship has varying creation times.
I have Post, User, and Friendship models. Posts belong to users and users have many friendships.
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :through => :friendships
has_many :inverse_friendships, :class_name => "Friendship", :foreign_key => "friend_id"
has_many :inverse_friends, :through => :inverse_friendships, :source => :user
end
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User"
end
class Post < ActiveRecord::Base
belongs_to :user
end
What's the best way to do this? Thanks!

You can order on the associated table by doing a nested join, like so:
#posts = Post.joins(:user => :friendships).order("friendships.friended_at")

Turn Sean Hill's answer into a scope with:
class Post < ActiveRecord::Base
belongs_to :user
scope :ordered, -> {
joins(:user => :friendships).order("friendships.friended_at")
}
end

Just an addendum to this, as a thank you, Sean Hill's answer also works, slightly modified, to sort results using has_many through relationships with fields_for in Rails 4.
So if Messages belong_to both Conversations and Users and you first want a list of Conversations on the User page—perhaps before updating or modifying some message attribute—but sorted by an attribute on Conversations (e.g. "title"), not Messages, you can go from User through Messages to Conversations and Conversation attributes.
In the view:
<% fields_for :messages, #user.messages.joins(:conversation).order("title ASC") do %>
or in the User.rb model:
has_many :messages, -> {joins(:conversation).order("title ASC")}

Related

Union of 2 active record relation object in rails 3

I have a content model represented by class: content. Now users can rate content, review content or do both. I want to find all the content that a user have either rated, reviewed or rated and reviewed. The reviews table has a many-to-one association with the content table (meaning a content can be reviewed many times). A similar relationship exists between the ratings table and the content table.
I'm thinking I should do separate queries to find all rated content by a user, then all reviewed content by a user, then do a union. But I can't find out how to do a union that returns an active record relation. I need a relation because I want to paginate the results.
Thank you.
Ok, so first let's set up your models. From your explanation I'm thinking you'll want something like this:
class Content < ActiveRecord::Base
has_many :reviews
has_many :reviewing_users, :through => :reviews, :class_name => "User"
has_many :ratings
has_many :rating_users, :through => :ratings, :class_name => "User"
end
class User < ActiveRecord::Base
has_many :reviews
has_many :reviewed_contents, :through => :reviews, :class_name => "Content"
has_many :ratings
has_many :rated_contents, :through => :ratings, :class_name => "Content"
end
class Review < ActiveRecord::Base
belongs_to :content
belongs_to :user
end
class Rating < ActiveRecord::Base
belongs_to :content
belongs_to :user
end
And then for a given user you can find all the content that they've reviewed and/or rated with:
( user.reviewed_contents + user.rated_contents ).uniq
(user.reviewed_contents + user.rated_contents).uniq returns an array, not a relation, so beware. You can test this by attempting to call a class method on #posts (other than paginate).
You can still paginate though. just use #posts.paginate, as the will_paginate gem adds a paginate method to the array class.

Rails 3 - associations "through" - how to get the data from DB?

I have a problem with fetching data from DB, where is between models association kind through.
On my site, I have a categories, like a sports, news, weather etc. When an user is logged in and has a selected the categories, from which want to see the articles, then I would like to display only these articles.
Here's how looks like my models:
class User < ActiveRecord::Base
has_many :user_categories
has_many :categories, :through => :user_categories
end
class Category < ActiveRecord::Base
has_many :articles
has_many :user_categories
has_many :users, :through => :user_categories
end
class UserCategory < ActiveRecord::Base
belongs_to :user
belongs_to :category
end
class Article < ActiveRecord::Base
belongs_to :category
end
But I still can't find the way, how to get all articles from user's selected categories... I tried something like
Article.joins("LEFT JOIN categories ON category.id = user_categories.category_id").where('user_categories.user_id = ?', current_user.id)
I would grateful for every advice!
Thank you
Here's one way to do it:
Article.where(:category_id => current_user.categories.map {|c| c.id})
That will create 2 queries. First one will return a list of the current user's categories. Then the ruby map function will create an array containing the ids of those categories. The second query will then return a list of articles whose category_id is in the array of ids. The second query will look something like:
select articles.* from articles where articles.category_id in(1,2,3);

Rails basic association

I'm trying to do a basic model association in rails.
Basically I have a List table which stores item_id and user_id.
One user can create multiple "list-items."
Is this the correct way to do it?
Thanks.
class Item < ActiveRecord::Base
has_many :users, :through => :lists
end
class User < ActiveRecord::Base
has_many :items, :through => :lists
end
class List < ActiveRecord::Base
belongs_to :user
belongs_to :item
end
Depending on what you want to reach, your solution is the right one (or not). I see the following cases:
You want to create an n:m association between items and users. So each item could be referenced by many users, and each user references many items. If this is the right context, then your solution is the right one. See the Rails Guides: Associations for more information on that.
An alternative for that situation could be to use the has_and_belongs_to_many Association. The situation is the same, but it does not make sense to talk about lists, there will be no model object for it.
If each users may have many lists, and each list may have many items, your solution would be wrong. This would be no n:m association with list as the join table in between, but two 1:n relations.
The code for the third example would look like that:
class User < ActiveRecord::Base
has_many :items, :through => :lists
has_many :lists
end
class List < ActiveRecord::Base
has_many :items
belongs_to :user
end
class Item < ActiveRecord::Base
belongs_to :list
end
In the first solution, you should add the relations for users to lists and items to list:
class Item < ActiveRecord::Base
has_many :lists
has_many :users, :through => :lists
end
class User < ActiveRecord::Base
has_many :lists
has_many :items, :through => :lists
end
If the "list" entity truly is a pure association/join, that is, it has no inherent attributes of its own, then you can simplify a bit and use has_and_belongs_to_many. Then you don't need a "List" class.
class Item < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :items
end
Rails will look for the references in a "items_users" table, so in your migration, you need to create it a la:
create_table :items_users, :id => false do |t|
t.references :users, :items
end
Many people will tell you to always use has_many :through, but others (like me) will disagree - use the right tool for job.

Joining 3 tables in Ruby on Rails 3

I am trying to extract all Posts for a given User in the bellow relationships. Not sure whether I got them right, so I'll better explain. A User has Ownerships and Memberships in some Groups. A User can be either a Member or an Owner of the Group, but not both. Every Post has an id of the user and of the group. I think the problem is due to the relationships noted below. How can I get around it? One more thing. I have to also find all posts that were posted by other users in the user's groups. In other words, I have to pass through groups.
/-- Owner ---\
User -- -- Group -- Post
| \-- Member --/ |
|_______________________________|
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
has_many :ownerships, :foreign_key => "user_id", :dependent => :destroy
has_many :memberships, :foreign_key => "user_id", :dependent => :destroy
# Problem with these two? I think so.
has_many :groups, :through => :memberships, :source => :user
has_many :groups, :through => :ownerships, :source => :user
class Ownership < ActiveRecord::Base
belongs_to :users, :class_name => "User"
belongs_to :groups, :class_name => "Group"
has_many :posts, :through => :groups, :source => :posts
class Membership < ActiveRecord::Base
belongs_to :users, :class_name => "User"
belongs_to :groups, :class_name => "Group"
has_many :posts, :through => :groups, :source => :posts
class Group < ActiveRecord::Base
has_many :posts, :dependent => :destroy
class Post < ActiveRecord::Base
belongs_to :user
belongs_to :groups
The errors is coming from the line:
_groups = user.groups
The error as following:
Could not find the source association(s) :user in model Ownership. Try 'has_many :groups, :through => :ownerships, :source => '. Is it one of :users, :groups, or :postings?
First up: you're getting that error you're seeing because you've defined the associations in the Membership and Ownership table as this:
belongs_to :users
When they should belong to only one user, i.e. singular user:
belongs_to :user
But even then you will run into problems!
I think having a Membership model and an Ownership model are what will trip you up next. I don't understand what the purpose of having an Ownership model provides, other than signifying ownership of a group, which could be done by a field on the memberships table's records called owner for instance. It's over-engineering.
The problem with the Rails code you've got there is that you're defining that you have many posts through one association and then you're telling it that you have many posts through another association. In effect, you're doing this:
def posts
# find posts for the groups that I own
end
def posts
# find posts for the groups I belong to
end
It is not mistake here that there are two identically-named methods. This is exactly what you are doing by defining two has_many associations with the same name.
So hopefully now you can see why having an Ownership and a Membership model is the path to madness.
I would really recommend that you just have a Membership model that has a boolean attribute declaring an owner for a group. This would also mean that, if you wanted to, you could have new owners for a group in a very easy fashion: just flip the boolean. No need to create another record in another table.
One Membership model to rule them all.

Find all records NOT in a many-to-many association

In Rails 3 with ActiveRecord, I have 2 models (Users and Tasks). These models are linked together with a has_many :through association on another model, Assignments. How can I find all Tasks that are NOT associated to a particular user?
class User < ActiveRecord::Base
has_many :assignments
has_many :tasks, :through => :assignments
end
class Tasks < ActiveRecord::Base
has_many :assignments
has_many :users, :through => :assignments
end
class Assignments < ActiveRecord::Base
belongs_to :users
belongs_to :tasks
end
Short 'n sweet:
Task.all - user.tasks
Avoid loading user tasks:
Task.where('id not in (?)', user.task_ids)
I couldn't figure out how to do it with an outer join in AR.
I'm going to assume you want those tasks without any associated user, rather than not associated to a user in particular.
Tasks.joins('left outer join assignments on assignments.task_id = tasks.id').where('assignments.* is null')