How should I create following model in Rails 3.2? Project can have 1+ owners and 1+ users. Both of them are instances of class Person. I've thought about has_and_belongs_to_many but I don't know how to handle two separate collections of Persons for each Project.
You'll need a join model to represent each has-and-belongs-to-many relationship, and you would access using has-many-through as described here:
class ProjectOwnerLink < ActiveRecord::Base
belongs_to :project
belongs_to :owner, class_name: 'Person'
end
class ProjectUserLink < ActiveRecord::Base
belongs_to :project
belongs_to :user, class_name: 'Person'
end
class Project < ActiveRecord::Base
has_many :project_owner_links
has_many :owners, :through => :project_owner_links
has_many :project_user_links
has_many :users, :through => :project_user_links
end
class Person < ActiveRecord::Base
has_many :project_owner_links
has_many :owned_projects, :through => :project_owner_links, :source => :project
has_many :project_user_links
has_many :used_projects, :through => :project_user_links, :source => :project
end
You could define another model Participation that holds the type of the relationship, i.e. the role of the user. (Untested) code:
class Project < ActiveRecord::Base
has_many :participations
has_many :users, :through => :participations
def with_role(role)
includes(:participations).where('participation.role = ?', role)
end
def owners
users.with_role('owner')
end
def participants
users.with_role('participant')
end
end
class User < ActiveRecord::Base
has_many :participations
has_many :projects, :through => :participations
def with_role(role)
includes(:participations).where('participation.role = ?', role)
end
def projects_owned
projects.with_role('owner')
end
def projects_participating_in
projects.with_role('participant')
end
end
class Participation < ActiveRecord::Base
# has an attribute 'role'
belongs_to :project
belongs_to :user
end
Below is the demo application.
https://github.com/diatmpravin/habtm-demo.git
Please have a look, Let me know if you have any question?
Related
Rails/ActiveRecord newbie here. Consider the following models for a Classroom, User and ClassroomEnrollments (join between the two)
class Classroom < ActiveRecord::Base
has_many :fulltime_enrollments, -> { where(duration: 'full-time') }, class_name: "ClassroomEnrollments"
has_many :fulltimers, :through => :fulltime_enrollments, class_name: "User"
has_many :parttime_enrollments, -> { where(duration: 'part-time') }, class_name: "ClassroomEnrollments"
has_many :parttimers, :through => :parttime_enrollments, class_name: "User"
end
class ClassroomEnrollment < ActiveRecord::Base
# columns: user_id, classroom_id, duration
belongs_to :user
belongs_to :classroom
end
class User < ActiveRecord::Base
has_many :classroom_enrollments
has_many :classrooms, :through => :classroom_enrollments
end
The following model for a classroom and classroom_enrollments does not work. Specifically the :fulltimers and :parttimers aliases throw undefined method 'to_sym' for nil:NilClass errors when I try to access them via my_classroom.fulltimers or my_classroom.parttimers.
If I remove the :parttimers alias and rename :fulltimers to :users it works fine (and displays only the full time students), so it seems to me that it has something to do with it figuring out that :fulltimers is of type User, even though I've specified the classname: "User" in the has_many condition.
What am I doing wrong?
Since the source association cannot be inferred automatically, you need specify it using the :source option:
class Classroom < ActiveRecord::Base
has_many(
:fulltime_enrollments,
-> { where(duration: 'full-time') },
class_name: "ClassroomEnrollments"
)
has_many :fulltimers, :through => :fulltime_enrollments, :source => :user
has_many(
:parttime_enrollments,
-> { where(duration: 'part-time') },
class_name: "ClassroomEnrollments"
)
has_many :parttimers, :through => :parttime_enrollments, :source => :user
end
http://guides.rubyonrails.org/association_basics.html#options-for-has-many-source
How about trying a cleaner, more readable approach? Something like this:
class Classroom < ActiveRecord::Base
has_many :classroom_enrollments
has_many :users, through: :classroom_enrollments
def full_timers
users_by_duration("full-time")
end
def part_timers
users_by_duration("part-time")
end
private
def users_by_duration(duration)
users.where(classroom_enrollments: { duration: duration })
end
end
Then:
my_classroom = Classroom.find(1)
my_classroom.full_timers
I stumbled on this while working on something similar. This is will generate the same sql and is a bit easier to look at.
class Classroom < ActiveRecord::Base
has_many :classroom_enrollments
has_many :users, through: :classroom_enrollments
def fulltimers
users.merge(ClassroomEnrollment.full_time)
end
def parttimers
users.merge(ClassroomEnrollment.part_time)
end
end
class ClassroomEnrollment < ActiveRecord::Base
belongs_to :user
belongs_to :classroom
scope :fulltime, ->{ where(duration: 'full-time') }
scope :parttime, ->{ where(duration: 'part-time') }
end
class User < ActiveRecord::Base
has_many :classroom_enrollments
has_many :classrooms, :through => :classroom_enrollments
end
In my Rails application I have people which can have many projects and vice versa:
# app/models/person.rb
class Person < ActiveRecord::Base
has_many :people_projects
has_many :projects, :through => :people_projects
end
# app/models/people_project.rb
class PeopleProject < ActiveRecord::Base
belongs_to :person
belongs_to :project
end
# app/models/project.rb
class Project < ActiveRecord::Base
has_many :people_projects
has_many :people, :through => :people_projects
def self.search(person_id)
if person_id
where("person_id = ?", person_id) # not working because no person_id column in projects table
else
scoped
end
end
end
How can I filter the projects by person_id in the index view of my ProjectsController, e.g. by using a URL like this: http://localhost:3000/projects?person_id=164
I can't get my head around this. Please help! Thanks...
Your association definition is not complete for Person and Project models. You also need has_many :people_projects defined.
# app/models/person.rb
class Person < ActiveRecord::Base
has_many :people_projects # <-- This line
has_many :projects, :through => :people_projects
end
# app/models/project.rb
class Project < ActiveRecord::Base
has_many :people_projects # <-- This line
has_many :people, :through => :people_projects
end
# app/models/people_project.rb
# This is defined correctly
class PeopleProject < ActiveRecord::Base
belongs_to :person
belongs_to :project
end
Please reference The has_many :through Association for further details.
With this definition, you will be able to get all the projects of the current user using current_user.projects, just like you've already done in your ProjectsController#index.
Update:
You could use either joins or includes in your search method and apply the where condition. Something like follows:
# app/models/project.rb
class Project < ActiveRecord::Base
has_many :people_projects
has_many :people, :through => :people_projects
def self.search(person_id)
if person_id
includes([:people_projects, :people]).where("people.id = ?", person_id)
else
scoped
end
end
end
You will not have a person_id in the projects table because its a has_many<>has_many relationship.
Simply #person.projects will perform a join btw person_projects & projects tables and returns the appropriate projects.
*I assume,current_user returns a Person object.*
Also, complete your Model definitions. Each of them should list their relation to PeopleProjects
class Person < ActiveRecord::Base
has_many :people_projects
has_many :projects, :through => :people_projects
end
class Project < ActiveRecord::Base
has_many :people_projects
has_many :people, :through => :people_projects
end
I have 3 models: User, Object, Likes
Currently, I have the model: a user has many Objects. How do I go about modeling:
1) A user can like many objects
2) an Object can have many likes (from different users)
So I want to be able to do something like this:
User.likes = list of objects liked by a user
Objects.liked_by = list of Users liked by object
The model below is definitely wrong...
class User < ActiveRecord::Base
has_many :objects
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
belongs_to :users
has_many :users, :through => :likes
end
To elaborate further on my comment to Brandon Tilley's answer, I would suggest the following:
class User < ActiveRecord::Base
# your original association
has_many :things
# the like associations
has_many :likes
has_many :liked_things, :through => :likes, :source => :thing
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :thing
end
class Thing < ActiveRecord::Base
# your original association
belongs_to :user
# the like associations
has_many :likes
has_many :liking_users, :through => :likes, :source => :user
end
You are close; to use a :through, relation, you first must set up the relationship you're going through:
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
has_many :likes
has_many :users, :through => :likes
end
Note that Objects should has_many :likes, so that the foreign key is in the right place. (Also, you should probably use the singular form Like and Object for your models.)
Here is a simple method to achieve this. Basically, you can create as many relationships as needed as long as you specify the proper class name using the :class_name option. However, it is not always a good idea, so make sure only one is used during any given request, to avoid additional queries.
class User < ActiveRecord::Base
has_many :likes, :include => :obj
has_many :objs
has_many :liked, :through => :likes, :class_name => 'Obj'
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :obj
end
class Obj < ActiveRecord::Base
belongs_to :user
has_many :likes, :include => :user
has_many :users, :through => :likes
# having both belongs to and has many for users may be confusing
# so it's better to use a different name
has_many :liked_by, :through => :likes, :class_name => 'User'
end
u = User.find(1)
u.objs # all objects created by u
u.liked # all objects liked by u
u.likes # all likes
u.likes.collect(&:obj) # all objects liked by u
o = Obj.find(1)
o.user # creator
o.users # users who liked o
o.liked_by # users who liked o. same as o.users
o.likes # all likes for o
o.likes.collect(&:user)
Models & associations as per naming conventions of rails modeling
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Object < ActiveRecord::Base
belongs_to :user
has_many :likes
has_many :users, :through => :likes
end
Also, you can use of already built-in gems like acts-as-taggable-on to have same functionality without code :)
Right now I have a rich many-to-many association with VideoVote as the independent record.
class VideoVote < ActiveRecord::Base
belongs_to :user
belongs_to :video
end
class User < ActiveRecord::Base
has_many :video_votes
has_many :voted_videos,
:through => :video_votes,
:source => :video
end
class Video < ActiveRecord::Base
has_many :video_votes
has_many :voted_users,
:through => :video_votes,
:source => :user
end
However, I want to trasform this into a polymorphic association where comments can also have many VideoVotes (I realize this is confusing, so I should probably change it to Votes). (also, a video will have many comments.) How should I do this?
You first want to add voteable_id:integer and voteable_type:string to your video_votes table.
Then your models will look like:
class VideoVote < ActiveRecord::Base
belongs_to :voteable, :polymorphic => true
end
class Comment < ActiveRecord::Base
has_many :video_votes, :as => :voteable
#code
end
class Video < ActiveRecord::Base
has_many :video_votes, :as => :voteable
#code
end
Then you can access them just like any other has_many:
#video.video_votes
#comment.video_votes
#etc.
Consider this:
class User < ActiveRecord::Base
# name, email, password
end
class Article < ActiveRecord::Base
# user_id, title, text
end
class Favorites < ActiveRecord::Base
# user_id, article_id
end
How do I put this all together in a way that I can have #user.articles (articles created by the user) and #user.favorite_articles (favorite articles from Favorite model)
Thanks in advance!
You can use a has_many :through association to fetch the favorite articles from user. You can also use it to fetch the users who favorited a given article.
class User < ActiveRecord::Base
has_many :articles
has_many :favorites
has_many :favorite_articles, :through => :favorites, :source => :article
end
class Article < ActiveRecord::Base
belongs_to :user
has_many :favorites
has_many :favorited_by_users, :through => :favorites, :source => :user
end
class Favorite < ActiveRecord::Base
belongs_to :article
belongs_to :user
end
If I understood right what you wanted, I'd say
class User < ActiveRecord::Base
has_many :articles
has_many :favorite_articles, :through => :favorites, :source => :article
end
class Article < ActiveRecord::Base
has_and_belongs_to_many :user
end
class Favorites < ActiveRecord::Base
has_and_belongs_to_many :user
has_one :article
end
edited: added favorite_articles