I have 3 models: User, List, Following
I'm trying to implement a system, where
A user can create many lists (list contains photos, but it's not relevant in this question)
A user can follow lists created by other users
Here's how I'm trying to build this system:
First we have a database table of lists:
lists: id, user_id
And specifying the models like the following:
class User < ActiveRecord::Base
has_many :lists
end
class List < ActiveRecord::Base
belongs_to :user
end
We can do User.first.lists without problem.
Now my challenge comes when trying to create the followership. I'd like a user to be able to find
All the lists he's following
All the lists being followed that are created by him
All the users following his lists (or, equivalently, all "followers")
Here's the database table I'm trying to use to fulfill the above function:
followings: user_id, list_id, list_user_id
In this table definition, user_id specifies who's following the list, list_id specifies the list being followed, and list_user_id specifies the owner of the list being followed. list_user_id is used here to speed up the database lookups, so that we don't have to join lists table with users table.
And now I'm stuck. I tried to change the user model to the following:
class User < ActiveRecord::Base
has_many :lists
has_many :followings
# Works
has_many :following_lists, :through => :followings, :class_name => "List", :source => :list
# Doesn't work
has_many :followed_lists, :through => :followings, :class_name => "List", :source => :list, :conditions => {:list_user_id => self.id}
# Doesn't work
has_many :followers, :through => :followings, :class_name => "User", :source => :user
end
The first goal, "Find all the lists he's following", is done, through has_many :following_lists, without problem. However, it seems it's difficult to get "all lists being followed" and "all followers" of a user.
The problem is that there seems to be no way to specify the key to use for lookup in the followings table. For example, when looking for user A's followers, I'll need to find all rows in followings table where the list_user_id equals A.id, but has_many method doesn't provide an option to do this, nor does the condition :conditions => {:list_user_id => self.id} work (it'll complain undefined method 'id').
So..how would you deal with this situation? Is there a better way to design the tables, or can we actually work out something based on the current table definitions?
Btw, here's how Following model is defined:
class Following < ActiveRecord::Base
attr_accessible :list_id, :list_user_id, :user_id
belongs_to :user
belongs_to :list
belongs_to :list_user, :class_name => "User"
end
You are trying to get these 2 things:
1.) All the lists being followed that are created by him
2.) All the users following his lists (or, equivalently, all "followers")
Both of these are filtering on top of lists owned by the user. Therefore the associations with :through => followings are incorrect. Since that scopes the lists to ones you follow, not ones you own.
One way to do want you want would be like this:
def followed_lists
# This essentially limits your owned lists to ones with an entry in the followings table
lists.join(:followings).all
end
def users_following_owned_lists
lists.followers.all
end
You will need to add the followers association to the List AR.
class List < ActiveRecord::Base
has_many :followings
has_many :followers, :through => :followings, :class_name => "User", :source => :user
end
Note also that list_user_id on followings table is not really needed.
Related
I have a model of users and things
Users can have one leader and many followers which are other users. User can also have things
So I have these definitions which use a self join relationship:
class User < ActiveRecord::Base
has_many :things, dependent: :destroy
has_many :followers, :class_name => 'User', :foreign_key => 'leader_id'
belongs_to :leader, :class_name => 'User', :foreign_key => 'leader_id'
end
class Thing < ActiveRecord::Base
belongs_to :user
end
So I can query ask for a list of things that a user has by asking, for example User.first.things. I can also get a list of followers of a user with User.first.followers.
How do I get a list of things that a user's followers have. I think I might need to use a has_many through relationship but I can't seem to figure it out as I'm not sure how to deal with the fact that a Leader can have things through a 'follower' but also directly themselves
Thanks
Something like this:
def all_things
Thing.where(:user_id => followers.map(&:id).push(id))
end
It returns a scope so you should be able to continue the chain, for example:
User.first.all_things.visible
Update
If you are only interested in the followers' things without adding the user's things to the batch it is better is you do it directly with a has_many through:
has_many :followers_things, :through => :followers, :source => :things
Check this other SO thread
How about:
all_things = (your_user.followers.each { |f| f.things }).flatten
This should return an array containing things that belong to all your_user's followers.
So this might be really bad form. I'm relatively new to rails. I'm not sure.
I have a project model and I want there to be many owners (who can read and write everything) and many collaborators (who can read and write some stuff).
In my project.rb file I have:
has_many :project_user_relationships, :dependent => :destroy
has_many :collaborators, :through => :project_user_relationships, :source => :user
has_many :project_owners_relationships, :class_name => "ProjectUserRelationship", :foreign_key => "project_id",
:before_add => Proc.new { |p,owner_r| owner_r.owner = true }, :conditions => "`project_user_relationships`.owner = true"
has_many :owners, :through => :project_owners_relationships, :source => :user
So this works reasonably well. If I add a new owner, that user is also a collaborator which is what I want. The issue I'm not sure how to solve is if I add a user that is already collaborator as an owner, I get two entries in the join table. I'd like for it to just amend the record that's already there. How do I do that?
Here's the data model I would suggest for this:
class Project < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
...
end
class Membership < ActiveRecord::Base
belongs_to :project
belongs_to :user
...
end
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :projects, :through => :memberships
...
end
And then the membership table will have the following attributes:
:id
:user_id
:project_id
:is_owner (boolean)
A scope defined on the membership class:
scope :owner, where("is_owner")
And a special method for User instances:
def owned_projects
memberships.owner.includes(:projects).inject([]) {|array, m| array << m.project; array}
end
will allow you to retrieve a user's owned projects with the user.owned_projects call.
And just a call to user.projects to see a user's projects that they either collaborate on or own.
You have better data normalization with this data model, and a simple boolean attribute to define whether or not a user is a project owner.
This data model is used in this project, with the exception that s/Project/Group/, and there's some additional functionality to handle inviting users to the Project.
This doesn't answer your "real question", but I think part of the issue is that a data model where collaborators are owners are stored in the same table is needed to minimize redundancies and the need to manage two separate tables.
I am having a somewhat too nested database layout, however, I seem to need it. That is, Our website visitors may each have a single account with maintaining multiple users (think of identities) within.
Now they may create tickets, which are grouped by ticket sections, and we have ticket manager (operator) to process the incoming tickets.
Not every ticket manager may see every ticket but only those this manager is a member of the given ticket section for.
Now, I am totally fine in querying via raw SQL statements, but I failed to verbalizing those two special queries the Rails way.
Here is the (abstract) model:
# account system
class Account < ActiveRecord::Base
has_many :users
has_many :tickets, :through => :users
has_many :managing_ticket_sections, ... # TicketSection-collection this account (any of its users) is operate for
has_many :managing_tickets, ... # Ticket-collection this account (any of its users) may operate on
end
class User < ActiveRecord::Base
belongs_to :account
has_many :tickets
has_many :managing_ticket_sections, ... # TicketSection-collection this user is operate for
has_many :managing_tickets, ... # Ticket-collection this user may operate on
end
# ticket system
class Ticket < ActiveRecord::Base
belongs_to :author, :class_name => "User"
belongs_to :assignee, :class_name => "User"
belongs_to :section, :class_name => "TicketSection"
end
class TicketSection < ActiveRecord::Base
has_many :tickets
has_many :operators
end
class TicketSectionManager < ActiveRecord::Base
belongs_to :manager, :class_name => "User"
belongs_to :section
end
I am aware of basic has_many :through-constructs, however, here, I am accessing more than three tables to get the tickets.
Something that actually works for in the User's model is:
class User < ActiveRecord::Base
has_many :managing_relations, :class_name => "TicketSectionManager" # XXX some kind of helper, for the two below
has_many :managing_sections, :class_name => "TicketSection", :through => :managing_relations, :source => :section
has_many :managing_tickets, :class_name => "Ticket", :through => :managing_relations, :source => :section
end
Here I am using a helper relation (managing_relations), which is absolutely never used except by the two has_many relations below.
I were not able to describe a User.managing_sections nor User.managing_tickets relation without this helper, which is, where I need an advice for.
Secondly, the customer is to have a look at all of the tickets he can manage on any User (think of an identity) he has logged in, so what I need, is a way to collect all tickets (/sections) this Account is permitted to manage (identified by being a member of the corresponding TicketSection)
Here I even were not able to express this relation the ruby way, and I had to work around it by the following:
class Account
def managing_tickets
Ticket.find_by_sql("SELECT t.* FROM tickets AS t
INNER JOIN ticket_section_managers AS m ON m.section_id = t.section_id
INNER JOIN users AS u ON u.id = m.user_id
WHERE u.account_id = #{id}")
end
end
I'd appreciate any kind of advice, and
many thanks in advance,
Christian Parpart.
This question almost answers it, but I still think it's overkill.
Trouble with Rails has_many relationships
I really just want a to do assignments like this assign
#user.browsing_location = location1
#user.home_location = location2
I've done a lot of googling around and all the information is contradictory, or explains setting up many to many relationships, and explains methods using an intermediary table. But really all the database should need is for the user table to have two differently names id fields for the locations. Will something like the following work?
User Class
class User < ActiveRecord::Base
#locations created by this user
has_many :locations, :foreign_key => :creator_id
#locations for browsing and visiting
belongs_to :browsing_location, :source => :location
belongs_to :home_location, :source => :location
end
Location Class
class Location < ActiveRecord::Base
#Users who are just browsing this location now
has_many :browsing_users, :foreign_key => :browsing_location_id, :source => :users
#Users who are living here now
has_many :home_users, :foreign_key => :home_location_id, :source => :users
#User who created this location
has_one :user
end
Quite a lot of my models will need relationships like this so I would like to avoid having to create extra tables for this.
It appears that you're attempting to have two tables that inherit the location class, browsing_location and home_location and two tables that inherit the user class, browsing_user and home_user. For Rails 3:
You've got the general idea, but it appears that you have mixed things up a bit. :source is used for many to many relationships to determine which association to use. What you appear to need instead is :class_name
I would need to see your table definitions for users and locations to make sure you're using :foreign_key attribute correctly.
user.rb
class User < ActiveRecord::Base
# locations created by this user
has_many :locations, :foreign_key => :creator_id
# I'm assuming the locations belong to the user, as you're end goal was stated as
# the ability to call set user.browsing_location and user.home_location
# that would mean that browsing_location and home_location would need to belong to
# the User class, not the other way around.
has_many :browsing_locations, :class_name => :location
has_many :home_locations, :class_name => :location
end
class Location < ActiveRecord::Base
# User who created the location
belongs_to :user
# Users who are just browsing this location now
has_many :browsing_users, :class_name => :users
# Users who are living here now
has_many :home_users, :class_name => :users
end
I'm working on a rails site that I've inherited and am trying to troubleshooting some sub-optimal model behavior. I have users, songs, and songs_download, each of which is its own model.
Here's the relevant line from the users model:
has_and_belongs_to_many :downloaded_songs, :class_name => 'Song', :join_table => :song_downloads
From the songs model:
has_and_belongs_to_many :downloaded_users, :class_name => 'User', :join_table => :song_downloads
And from the song_downloads model:
belongs_to :user
belongs_to :song
Here's the code to create a new song_download record when a user downloads a song (in the songs controller):
SongDownload.create( :song_id => #song.id,
:user_id => current_user.id,
:download_date => Date.today )
The problem I'm having is that once a user downloads a song, if I try to invoke the downloaded users from the interactive console, by, say, typing the following:
Song.find(<some id>).downloaded_users
I get back the complete record of the user, but the id in the returned objected is the primary key of the SongDownload, not the primary key of the User. All of the other fields are accurate, but the ID is not.
I didn't come up with this modeling scheme and it seems to me that :has_and_belongs_to_many might be more appropriately used with no explicitly modeled SongDownload object, but I'd rather not overhaul the codebase if I can help it. Are there any ways to get back the right user id given the current modeling scheme?
Thanks very much for your time and consideration!
Justin
Has and belongs to relationships are being phased out in favour of has many :through relationships.
On the upside you won't need to change any of your underlying structure, just the relationship declarations in the Song and User models.
class Song < ActiveRecord::Base
has_many :song_downloads
has_many :users, :through => :song_downloads
...
end
class User < ActiveRecord::Base
has_many :song_downloads
has_many :songs, :through => :song_downloads
...
end
Now
Song.find(<some id>).users
Returns an array of User objects which are joined to the selected song through the song_downloads table.
The has_many :through is recommended when the join table has more columns than simply two foreign keys.
class User < ActiveRecord::Base
has_many :song_downloads
has_many :downloaded_songs,
:through => :song_downloads,
:source => :song
end
class Song < ActiveRecord::Base
has_many :song_downloads
has_many :downloaded_users,
:through => :song_downloads,
:source => :user
end
Now you can get a list of users that have downloaded a particular song like so:
Song.find(1).downloaded_users