Related model can be one of two other models - ruby-on-rails

I currently have essentially 3 models at the moment. I have Project, User and Contact.
I'm trying to assign users or contacts as a sort of 'member' to each project. I initially thought that a linking table here would suffice, for example ProjectMembers but i'm currently hitting a brick wall in my thought process when it comes to a project member only being allowed to be either a Contact or a User and whether to handle this via a relationship or by code in the model or the controller by checking which between user_id or contact_id was not null.
I had a look at polymorphic associations which looked promising, but somehow I ended up with the association backwards (Projects were being entered into the ProjectMember table as the type, rather than User or Contact) and confused myself even more.
The final output I would pretty much like would be simply to have the ability to run something like Project.first.project_members and have those members return with the role in that project. It'd be even nicer if I could run User.first.projects/Contact.first.projects and get those too, but that's something I can figure out down the line.
Any pointers would be greatly appreciated.

Polymorphic association is something that you should go for here.
First: Project has a many to many relationship with User and Contact, hence you need a join table.
Second: Since you need to call something like Project.first.project_members, you should have a polymorphic association.
For this scenario, you should create a polymorphic join table.
Let's say you have the polymorphic table ProjectMember with memberable_id, memberable_type and project_id (well I have a hang of railcasts).
class ProjectMember < ActiveRecord::Base
belongs_to :memberable, :polymorphic => true
belongs_to :project
end
and then in your User model,
class User < ActiveRecord::Base
has_many :project_members, :as => :memberable
has_many :projects, through: :project_members
end
and in Contact model
class Member < ActiveRecord::Base
has_many :project_members, :as => :memberable
has_many :projects, through: :project_members
end
and in your Project model
class Project < ActiveRecord::Base
has_many :users, :through => :project_members, :source => :memberable, :source_type => 'User'
has_many :contacts, :through => :project_members, :source => :memberable, :source_type => 'Contact'
has_many :project_members
end
Now you can call your desired associations, Project.first.project_members, User.first.projects or Contact.first.projects. Hope this helps.
Thanks

Related

Source Reflection Errors with has_many :through

I'm attempting to create a system where my site's users can favorites pages. Those pages have two types, either clubs or sports. So, I have four models, associated as such:
User Model:
class User < ActiveRecord::Base
..
has_many :favorites
has_many :sports, :through => :favorites
has_many :clubs, :through => :favorites
..
end
Favorites Model:
class Favorite < ActiveRecord::Base
..
belongs_to :user
belongs_to :favoritable, :polymorphic => true
end
Club Model:
class Club < ActiveRecord::Base
..
has_many :favorites, :as => :favoritable
has_many :users, :through => :favorites
def to_param
slug
end
end
Sport Model:
class Sport < ActiveRecord::Base
..
def to_param
slug
end
..
has_many :favorites, :as => :favoritable
has_many :users, :through => :favorites
..
end
Essentially, the User has_many sports or clubs through favorites, and the association between favorites, sports, and clubs is polymorphic.
In practice, this is all working exactly the way I want it to, and the whole system I have designed works. However, I'm using Rails_Admin on my site, and I get an error in three places:
When loading the Dashboard (/admin) the first time. If I refresh the page, it works fine.
When loading the User model in Rails_Admin
When loading the Favorites model in Rails_Admin
Here is the error message on /admin/user (gist). All of the errors are similar, referencing ActiveRecord::Reflection::ThroughReflection#foreign_key delegated to source_reflection.foreign_key, but source_reflection is nil:.
Can anyone point me in the right direction so that I can fix this? I've searched all over, and asked other programmers/professionals, but no one could spot the error in my models. Thanks so much!
Alright, well, I finally worked this out, and figured that I'd post the fix just in case it helps someone else out in the future (no one likes finding someone else with the same problem and no posted answer).
As it turns out, with a polymorphic has_many :through, there is a little more configuration needed. My User model should have looked like this:
class User < ActiveRecord::Base
..
has_many :favorites
has_many :sports, :through => :favorites, :source => :favoritable, :source_type => "Sport"
has_many :clubs, :through => :favorites, :source => :favoritable, :source_type => "Club"
..
end
This answer to another question about polymorphic has_many :through associations is what helped me figure this out.
I encountered this error when the code included a has_many for an association that doesn't exist (mid-refactor). So it can also be caused by some general has_many misconfigure. The Ruby/Rails code never cares because the dynamic style of Ruby means the association is only called on demand. But Rails-Admin exhaustively inspects properties, leading to reflection problems.

has_many :through relationships explained

I'm new to Rails and have some doubts about the kind of relationship do I need to use. Here is the case.
I have two models Offer and User, a user could belong to to many offers and offers can have many user. Also the users create the offers.
I think I have to use a has_many :through ralationship. For example I've created another model "Applicant". Applicant belongs_to user and belongs_to offer. But how is the relationship from the user and offer model? For example:
User Model
has_many :offer, :through => :applicant
Offer Model
has_many :user, :through => :applicant
My doubt is because I already have this two relationship
User Model
has_many :offers, :dependent => :destroy
Offer Model
belongs_to :user
After solve this, I guest I have to save the record in the applicant model from the applicanst_controller, right?
Thanks in advance
What you have described is a many-to-many relationship using a join table. You're actually pretty close but you just need to remove the has_many :offers, :dependent => :destroy from your user model and the blongs_to :user in your offer model. It should look something like this:
class User < ActiveRecord::Base
has_many :offers, :through => :applicants
end
class Applicant < ActiveRecord::Base
belongs_to :users
belongs_to :offers
end
class Offer < ActiveRecord::Base
has_many :users, :through => :applicants
end
You don't have to worry about the dependent destroy part as associations are automatically removed as the corresponding objects are removed. With a many to many association it doesn't really matter how you go about building the relationship. Either of the following will work:
#user.offers << #offer
#offers.users << #user
If you don't need to store any information specific to your applicant join table (e.g., time stamps, descriptions) you might instead want to look at a has_and_belongs_to_many relationship. Check out choosing between has_many_through and has_and_belongs_to_many for reference.
Edit
Heres the code for a HABTM relationship:
class User < ActiveRecord::Base
has_and_belongs_to_many :offers
end
class Offer < ActiveRecord::Base
has_and_belongs_to_many :users
end

Has Many Through Association Callbacks with multiple associations using the same join table

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.

Setting a type for one side of a many-to-many association with a join model

I set up a public github app (see: https://github.com/greenplastik/testapp to download) to work through a problem I'm having with specifying a type on one side of a many-to-many association between two models, via a join model.
Given Person and Book models and a Book_Person join model, I want to be able to do the following:
#book = Book.first
#book.people # lists people for book
#book.authors # lists author-type people for book
#book.editors # lists editor-type people for book
and
#person = Person.first
#person.books # lists books for people
This app was set up in part using the instructions found through Google. There's a link to those instructions in the README of my testapp.
I tried, as best I could, to remove the inconsistencies and typos. I can't get it to work.
Any help would be appreciated. I've included the sqlite database for easier testing on your end.
I'm sorry i haven't got time to post neither a full solution or a tested one, but this should at least put you on the right track..
The best way to achieve what you're looking for is with Single Table Inheritance used with a polymorphic relationship.
I'd suggest redefining Author and Editor to be subclasses of Person
As in
class Person < ActiveRecord::Base
has_many :book_people
has_many :books, :through => :book_people
end
class Author < Person
end
class Editor < Person
end
class BookPerson < ActiveRecord::Base
belongs_to :person, :polymorphic => true
belongs_to :book
end
class Book < ActiveRecord::Base
has_many :book_people
has_many :people, :through => :book_people
has_many :authors, :through => :book_people, :source => :person, :source_type => "Author"
has_many :editors, :through => :book_people, :source => :person, :source_type => "Editor"
end
However this would be suboptimal if a single person could fill all three roles. There's probably a way around that by using the same STI name for the three of them. But then you'd make it harder to query for all authors.

Rails modeling for a user

When building a rails app that allows a User to login and create data, is it best to setup a belongs_to :user association on every single model? For example, let's say a user can create Favorites, Colors and Tags.
And let's say Favorites has_many :tags and Colors also has_many :tags. Is it still important for Tags to belong_to :user assuming the User is the only person who has authority to edit those tags?
And a similar question along the same lines: When updating data in FavoritesController, I've come to the conclusion that you perform CRUD operations by always doing something like current_user.favorites.find(param[:id].update_attributes(param[:favorite]) so that they can definitely only update models that belong to them. Right?
Update Wasn't too happy with any of the answers, as no one really answered my question but instead went after the for-example-only Tags model suggesting better ways to do that. I'm assuming I was right, and models should belong_to :user. I also discovered some great security tips that address my questions here: http://asciicasts.com/episodes/178-seven-security-tips
As you describe the tags it seems that they are more of an aspect, so you can implement them as a polymorphic association. But you should do it many-to-many, as tags can be reused among users and taggable objects. Let's call the join model Tagging, which will be the one that belongs to user if you want to remember who created the tagging.
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :colors, :through => :taggings, :source => :taggable, :source_type => "Color"
has_many :favorites, :through => :taggings, :source => :taggable, :source_type => "Favorite"
end
class Tagging < ActiveRecord::Base
belongs_to :user
belongs_to :taggable, :polymorphic => true
belongs_to :tag
end
class Color < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class Favorite < ActiveRecord::Base
belongs_to :user
has_many :taggings, :as => :taggable
has_many :tags, :through => :taggings
end
class User < ActiveRecord::Base
has_many :favorites
has_many :colors
has_many :taggings
has_many :tags, :through => :taggings
end
As for the Favorite updating, I agree with you: you will mostly work within the scope of a user (most likely the currently logged in user).
It depends on your model. Both cases are valid but I'd discorage making a circular relationships like that. Having a hierarchy is more flexible. For example: User->Favorites->Tags (unless you want to tag users as well)
User.favorites.find(params[:id]).update_attributes(param[:favorite])
is what you mean I guess (syntax). Whoever calls the URL will perform that action. Dont rely on the fact that that URL is visible to one user only (owner of the favorite). You should have checks in place that the currently logged in user is the only one performing actions on the objects that belong to him.
The proposed mechanism sounds a bit too complex for me. I prefer the current_user way. Assume there is a current_user (following the authlogic way) in your authentication system, then simple add a user references (user_id) in every relevant table. Update the current_user for new or update record via a controller filter.
In the models, put relevant belongs_to :users accordingly, put enough has_many in users model if needed.
:has_many and :belongs_to in AR will explains the relationship between models, but not necessarily you have to use them in your models, the associaton between them will be already present in the tables as a foreign key.
But adding :has_many or :belongs_to to your models will give you extra methods to your model
ex:
class User < ActiveRecord::Base
has_many :favorites
#def favorites
# Favorite.find_all_by_user_id(self.id)
# end
end
If you mention has_many it will give a new method in your model called favorites, that method will be invisible (will be present in the AR).
Similarly for any association, if you are planning to use this kind of methods you should use associations in your models.

Resources