Im having a problem in rails. Im actually solving it but I guess theres a easier way out there.
I got user/membership/group models and user/invitation/event models. Membership joins user and group. Invitation joins user and event.
The membership and invitation model are equal. Group and event do have some equal some different columns. The membership/invitation model both have a boolean column "accepted", meaning the user which is invited to a group/event has to accept this invitation before he is a member/participant.
Now if a user signs in all group and event invitations should appear in a list. In fact I want to add more notifications to the system later on and events aren't even included in mine yet.
My solution is to add a notification model which belongs to user. So every user has many notifications. Additionally, this model is polymorphic and belongs to membership AND invitation.
#user model
class User < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :groups, :through => :memberships
has_many :invitations, :dependent => :destroy
has_many :events, :through => invitations
has_many :notifications
#membership model (equal to invitation model)
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :group
has_one :notifications, :as => :noticeable
#group model (equal to event model but participants for members and invitation for membership)
class Group < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
has_many :members, :through => :memberships, :source => :user,
:conditions => ['memberships.accepted = ?', true]
#notification model
class Notification < ActiveRecord::Base
belongs_to :user
belongs_to :noticeable, :polymorphic => true
Ive added some data to the database and Im testing it in the console
myUser = User.find(6) # will be exchanged with current_user in the actual program
I will run through all notifications with each do... but for the start I test all further actions on one notification
myNotice = myUser.notifications.first
so whether the noticeable_type of myNotices is membership or invitation I will render it as group or event notification
in this case noticeable_type=membership
myGroup = Group.find(Membership.find(myNotice.noticeable_id).group_id)
--> Do you want to join the Group "myGroup.name"? Yes | No
On Yes: Membership.find(myNotice.noticeable_id).accepted = true
On No: Membership.find(myNotice.noticeable_id).destroy
And: myNotice.destroy
Thats the my idea.
Is this the way to solve the problem?
The "each do" which goes through all notifications will be in a view file. Which means "Group.find(Membership.find(myNotice.noticeable_id).group_id)" has to be in the view file or a partial aswell. Isn't that a bit ugly?
I think Ive used a lot of "find" which means many SQL queries. Isn't there a way to reduce them with any "Ruby on Rails"-magic?
Thank you :)
Needed something similar for my app, so updating the answer here in case someone finds it useful.
#user model
has_many :membership_notices, :through => :notifications, :source => :membership, :conditions => {"notifications.noticeable_type" => "membership"}
has_many :event_notices, :through => :notifications, :source => :event ,:conditions => {"notifications.noticeable_type" => "event"}
The notifications are now accessible as
user.membership_notices
user.event_notices
Finally, I found how to solve this! I bumped into this problem again and googled "reverse polymorphic". I found the following post:
https://gist.github.com/runemadsen/1242485
Polymorphic Associations reversed.
It's pretty easy to do polymorphic associations in Rails: A Picture
can belong to either a BlogPost or an Article. But what if you need
the relationship the other way around? A Picture, a Text and a Video
can belong to an Article, and that article can find all media by
calling #article.media
This example shows how to create an ArticleElement join model that handles the polymorphic relationship. To add fields that are common to all polymorphic models, add fields to the join model.
class Article < ActiveRecord::Base
has_many :article_elements
has_many :pictures, :through => :article_elements, :source => :element, :source_type => 'Picture'
has_many :videos, :through => :article_elements, :source => :element, :source_type => 'Video'
end
class Picture < ActiveRecord::Base
has_one :article_element, :as =>:element
has_one :article, :through => :article_elements
end
class Video < ActiveRecord::Base
has_one :article_element, :as =>:element
has_one :article, :through => :article_elements
end
class ArticleElement < ActiveRecord::Base
belongs_to :article
belongs_to :element, :polymorphic => true
end
t = Article.new
t.article_elements # []
p = Picture.new
t.article_elements.create(:element => p)
t.article_elements # [<ArticleElement id: 1, article_id: 1, element_id: 1, element_type: "Picture", created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]
t.pictures # [#<Picture id: 1, created_at: "2011-09-26 18:26:45", updated_at: "2011-09-26 18:26:45">]
Related
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?
Here is what I'm trying to do:
class Cashflow < ActiveRecord::Base
belongs_to from_account, :class_name => 'Account'
belongs_to to_account, :class_name => 'Account'
end
class Account < ActiveRecord::Base
has_many :cashflows
end
where Account::cashflows is obviously a list of all cashflows that either have the account_id stored in from_account or in to_account.
I'm confused. What is the proper way of handling such a case? How bad design is this? What would be the proper way of designing such a relation?
I think you have the right structure as there can only two accounts be involved in a particular transaction/cashflow. if you use many to many association you would need to handle the validation for not involving more or less than 2 accounts. For your current structure you can change your moidel associations to be:
class Cashflow < ActiveRecord::Base
belongs_to from_account, :class_name => 'Account', :foreign_key => :from_account
belongs_to to_account, :class_name => 'Account', :foreign_key => :to_account
end
class Account < ActiveRecord::Base
has_many :debits, :class_name => 'Cashflow', :foreign_key => :from_account
has_many :credits, :class_name => 'Cashflow', :foreign_key => :to_account
def cashflows
transactions = []
transactions << self.debits
transactions << self.credits
transactions.flatten!
## or may be the following commented way
# Cashflow.where('from_account = ? OR to_account = ?', self.id, self.id)
end
end
This way you can keep track of the amount debited/credited in a particular account and also get the accounts involved in a particular transaction/cashflow.
Suggestions on top of my mind
1) Your class (table) cashflows should have two columns from_account and to_account.
2) from_account and to_account should have the id of the account concerned
3) cashflows should belongs_to :account
4) account should has_many :cashflows. Ideally it should be cash_flows
These should be good starting points. Don't they meet your requirements?
I think you should use has and belongs to many association here:
class Account < ActiveRecord::Base
has_and_belongs_to_many :incoming_cashflows, :class_name => 'Cashflow', :join_table => :incoming_cashflows_accounts
has_and_belongs_to_many :outcoming_cashflows, :class_name => 'Cashflow', :join_table => :outcoming_cashflows_accounts
end
class Cashflow < ActiveRecord::Base
has_and_belongs_to_many :from_accounts, :class_name => 'Account', :join_table => :incoming_cashflows_accounts
has_and_belongs_to_many :to_accounts, :class_name => 'Account', :join_table => :outcoming_cashflows_accounts
end
Also you will need some validation code allows to add only one account to Cashflow.
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
This is my code:
class Friend < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
end
class User < ActiveRecord::Base
#...
has_many :friends
has_many :users, :through => :friends
#...
end
When I now start adding users by...
user.users << user2
user.save
Only the user_id of friend is filled, friend_id is null.
Any help?
Yours,
Joern.
Try: Railscasts - Self-Referential Associations. Generally has very good tutorials on all topics listed.
You need to add the :source attribute to your has_many through association.
class User < ActiveRecord::Base
has_many :friends
has_many :users, :source => :friend, :through => :friends
end
Now the following calls will work.
u1.users << u2
u.friends.last
# will print #<Friend id: 1, user_id: 1, friend_id: 4>
Notes:
Rails auto saves the associations.You need to call save only if the user model is new.
You probably should rename the association to something more explicit. E.g: friend_users etc.
I think you need delete the belongs_to :user in your Friend model
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.