I'm trying to set up some new classes and associations in a Rails 2.2.2 app (please don't ask me to upgrade it).
The idea is that there is a Conversation class. It can have many participants, which are a mix of User and Pupil records, and it can have many owners, which can be a mix of a variety of things, including User and Pupil records. This is what I have so far:
#the :owner association can point to *anything*, including a Pupil or User record
class ConversationOwnership < ActiveRecord::Base
belongs_to :conversation
belongs_to :owner, :polymorphic => true
end
#the :participant association will point to either a Pupil or User record
class ConversationParticipant < ActiveRecord::Base
belongs_to :conversation
belongs_to :participant, :polymorphic => true
end
class Conversation < ActiveRecord::Base
has_many :conversation_ownerships
has_many :owners, :through => :conversation_ownerships
has_many :conversation_participants
has_many :participants, :through => :conversation_participants
end
This currently isn't working:
Conversation.first.participants
=> ActiveRecord::HasManyThroughAssociationPolymorphicError: Cannot have a has_many :through association 'Conversation#participants' on the polymorphic object 'Participant#participant'.
Now, I know that the has_many_polymorphs plugin was designed to solve this very problem, but the problem with using that is that it automatically makes associations for each of the listed classes, and because User and Pupil are in both participants and owners, they would clash:
OWNER_CLASSES = [:ilps, :lessons, :digilearning_modules, :resources, :pupil_groups, :pupils, :users]
PARTICIPANT_CLASSES = [:pupils, :users, :contacts, :parent_carers]
has_many_polymorphs :participants, :from => PARTICIPANT_CLASSES, :through => :conversation_participants, :order => "conversation_participants.created_at"
has_many_polymorphs :owners, :from => OWNER_CLASSES, :through => :conversation_ownerships, :order => "conversation_ownerships.owner_type, conversation_ownerships.created_at"
With this, the first hmp makes a .pupils association which effectively means "participants that are Pupils", and the second hmp also makes a .pupils association which effectively means "owners that are Pupils".
I don't want these extra associations that has_many_polymorphs brings, which is why i thought I would roll my own associations. But, I can't get past that Cannot have a has_many :through association 'Conversation#participants' on the polymorphic object 'Participant#participant'. error.
This feels like it should be possible - is it?
Related
So im working on a rails app for users to create events (and attend other created events). You can read about the assignment here (for the Odin Project): https://www.theodinproject.com/courses/ruby-on-rails/lessons/associations
Anyways I thought I had understood many to many relationships in rails, but the way i've seen other people write the models is confusing to me.
To me it seems like it should be something like:
class User < ApplicationRecord
has_many :attendances
has_many :events, through: :attendances
end
class Attendance < ApplicationRecord
belongs_to :user
belongs_to :event
end
class Event < ApplicationRecord
has_many :users
has_many :users, through: :attendances
end
This makes sense to me because a User can create many events, and an event can have many users attending. (Although attendances is probably the wrong word, maybe invites or something).
But i've seen some weird examples (You can see others source code below on the project) and it seems like they are adding much more to the models and also renaming the source/foreign_key/class_name.
Am I missing something? This still allows a user to "own" an event right? Maybe im mis-understanding how many-to-many works. But this fits at least in my mind of how it should be.
For reference some other models I was seeing was similar to this:
class Event < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
has_many :event_attendees, :foreign_key => :attended_event_id
has_many :attendees, :through => :event_attendees
end
class EventAttendee < ActiveRecord::Base
belongs_to :attendee, :class_name => "User"
belongs_to :attended_event, :class_name => "Event"
end
class User < ActiveRecord::Base
has_many :created_events, :foreign_key => :creator_id, :class_name => "Event"
has_many :event_attendees, :foreign_key => :attendee_id
has_many :attended_events, :through => :event_attendees, :foreign_key => :attendee_id'
end
Basically similar things to the above. Im not really sure what this is doing? Or why all the extra is necessary.
In your example everything according to conventions. Maybe except many-to-many table naming.
attendances table has 'user_id' and 'event_id' fields. But in case it could conflict with other fields, or not descriptive enough you could use different keys.
belongs_to :creator, :class_name => "User"
belongs_to :creator by default would look for Creator model, so it is needed to specify class name explicitly, like in the provided example.
has_many :event_attendees, :foreign_key => :attended_event_id
By default foreign key would be event_id, so here it is specified explicitly too.
has_many :created_events, :foreign_key => :creator_id, :class_name => "Event"
By default, rails would look for user_id foreign key and CreatedEvent model. And these attributes specified explicitly.
You just need to understand what attributes rails provides by default, to change if it is required.
ActiveRecord associations default to a class and foreign key with the same name as the association. The code here is specifically specifying these because they are not the default.
As I am new to Rails, there is may be a trivial solution. But I could not even find this exact issue somewhere. Other posts deal with destroy vs. delete (I tried both with the same result) or just do not mention how the associated object behaves.
My problem: I want to create a many-to-many association via :through. When I delete an association (i.e. the relation object, not the related objects) I expect that this association is removed (updated) in all model instances of the associated objects. But this does not fully happen.
My example:
Meeting < ActiveRecord::Base
has_many :participations
has_many :users, :through => :participations
User < ActiveRecord::Base
has_many :participations
has_many :meetings, :through => :participations
Participation < ActiveRecord::Base
belongs_to :meeting, :foreign_key => :meeting_id
belongs_to :user, :foreign_key => :user_id
When I create a new association, the associated objects are updated accordingly:
u = User.find(...)
m = Meeting.find(...)
m.users<< u
The same when creating the association this way:
m.participations.create(:user_id => u.id) # this requires to make the user_id attribute accessible
When I now look at the associated user model instance, it got updated as expected:
u.meetings >> contains the newly created association to the meeting m
When I destroy (not delete!) this association, the associated object is not updated as I expect it:
m.users.find_by_user_id(u.id).destroy
m.users >> []
u.meetings >> still contains the destroyed association to meeting m
I would have expected that u.meetings is updated and empty ([]). Adding validations didn't help to solve this:
Meeting < ActiveRecord::Base
validates_associated :contacts
or
Participation < ActiveRecord::Base
validates_presence_of :contact, :interview
What am I doing wrong or what am I missing here?
I am using Rails 3.2.8
Thanks to everyone who is willing to help me.
You should be doing :dependent => :destroy.
Meeting < ActiveRecord::Base
has_many :participations, :dependent => :destroy
has_many :users, :through => :participations
User < ActiveRecord::Base
has_many :participations, :dependent => :destroy
has_many :meetings, :through => :participations
Participation < ActiveRecord::Base
belongs_to :meeting, :foreign_key => :meeting_id
belongs_to :user, :foreign_key => :user_id
This will make sure to destroy the participation if either of the associated user or meeting is destroyed.
You should update your model with the following relationship option:
dependent: destroy
Which will call destroy on the associated objects.
Reference: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Deleting+from+associations
I guess the problem could be like this. In your example,
m.users.find_by_user_id(u.id).destroy
m.users >> []
u.meetings >> still contains the destroyed association to meeting m
u and u.meetings were already loaded before m.users.find_by_user_id(u.id).destroy. Then u.meetings output the cached data.
You can try u.meetings(true) or u.reload; u.meetings to see if there are any difference.
I have a User model which has many projects and a Project model which can have many users, but also belongs to a single user (ie the user who created this project). It must belong to a User. It also allows a list of users to be associated with it, think collaboration.
With this in mind, my models look like this:
class User < ActiveRecord::Base
has_many :assigned_projects
has_many :projects, :through => :assigned_projects
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :assigned_projects
has_many :users, :through => :assigned_projects
end
class AssignedProject < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
Now, when I want to create a new project through a User, this is how I would do it:
user = User.create(:name => 'injekt')
user.projects.create(:name => 'project one')
Now, I know that projects is provided through an AssignedProject join model, which is why project.user will return nil. What I'm struggling to get my head around is the best way to assign the project creator (which by the way doesn't need to be user, it could be creator or something else descriptive, as long as it is of type User).
The idea then is to create a method to return projects_created from a User which will select only projects created by this user. Where user.projects will of course return ALL projects a user is associated with.
Assuming this kind of association is fairly common, what's the best way to achieve what I want? Any direction is greatly appreciated.
Add a creator_id column to your projects table for the creator relationship, and then add the associations to the models:
class User < ActiveRecord::Base
has_many :assigned_projects
has_many :projects, :through => :assigned_projects
has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :assigned_projects
has_many :users, :through => :assigned_projects
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
end
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many
I wanted to add little improvement to design. We don't actually need intermediate model because it does not contain any extra column other than reference_ids hence HABTM association is best suited over here.
class User < ActiveRecord::Base
has_and_belongs_to_many :projects, :join_table => :assigned_projects
has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end
class Project < ActiveRecord::Base
has_and_belongs_to_many :users, :join_table => :assigned_projects
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
end
I have a Partner model that has_and_belongs_to_many Projects, while each Project has_many Sites. I want to retrieve all sites for a given partner (and am not interested in the projects in between at the moment).
I have accomplished what I need through a named_scope on the Site model, and a project.sites instance method that wraps a call to the Site named scope, as follows:
class Partner < ActiveRecord::Base
has_and_belongs_to_many :projects
def sites
Site.for_partner_id(self.id)
end
end
class Project < ActiveRecord::Base
has_many :sites
end
class Site < ActiveRecord::Base
belongs_to :project
named_scope :for_partner_id, lambda {|partner_id|
{ :include=>{:project=>:partners},
:conditions=>"partners.id = #{partner_id}"
}
}
end
Now, given a partner instance, I can call partner.sites and get back a collection of all sites associated with the partner. This is precisely the behavior I want, but I'm wondering if there's another way to do this using only activerecord associations, without the named scope?
I had a similar deep nesting query/collection problem here (I had to threaten to repeat data before anyone would answer my 4 questions, clever):
Is it appropriate to repeat data in models to satisfy using law of demeter in collections?
The trick is this gem http://rubygems.org/gems/nested_has_many_through which can do something like this:
class Author < User
has_many :posts
has_many :categories, :through => :posts, :uniq => true
has_many :similar_posts, :through => :categories, :source => :posts
has_many :similar_authors, :through => :similar_posts, :source => :author, :uniq => true
has_many :posts_of_similar_authors, :through => :similar_authors, :source => :posts, :uniq => true
has_many :commenters, :through => :posts, :uniq => true
end
class Post < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :comments
has_many :commenters, :through => :comments, :source => :user, :uniq => true
end
This has super-simplified my queries and collections. I hope you find an answer to your problem, it's a tough one!
Can't wrap my head around this...
class User < ActiveRecord::Base
has_many :fantasies, :through => :fantasizings
has_many :fantasizings, :dependent => :destroy
end
class Fantasy < ActiveRecord::Base
has_many :users, :through => :fantasizings
has_many :fantasizings, :dependent => :destroy
end
class Fantasizing < ActiveRecord::Base
belongs_to :user
belongs_to :fantasy
end
... which works fine for my primary relationship, in that a User can have many Fantasies, and that a Fantasy can belong to many Users.
However, I need to add another relationship for liking (as in, a User "likes" a Fantasy rather than "has" it... think of Facebook and how you can "like" a wall-post, even though it doesn't "belong" to you... in fact, the Facebook example is almost exactly what I'm aiming for).
I gathered that I should make another association, but I'm kinda confused as to how I might use it, or if this is even the right approach. I started by adding the following:
class Fantasy < ActiveRecord::Base
...
has_many :users, :through => :approvals
has_many :approvals, :dependent => :destroy
end
class User < ActiveRecord::Base
...
has_many :fantasies, :through => :approvals
has_many :approvals, :dependent => :destroy
end
class Approval < ActiveRecord::Base
belongs_to :user
belongs_to :fantasy
end
... but how do I create the association through Approval rather than through Fantasizing?
If someone could set me straight on this, I'd be much obliged!
Keep your first set of code, then in your User Model add:
has_many :approved_fantasies, :through => :fantasizings, :source => :fantasy, :conditions => "fantasizings.is_approved = 1"
In your Fantasizing table, add an is_approved boolean field.