What inverse_of does mean in mongoid associations? What I can get by using it instead of just association without it?
In a simple relation, two models can only be related in a single way, and the name of the relation is automatically the name of the model it is related to. This is fine in most cases, but isn't always enough.
inverse_of allows you to specify the relation you are referring to. This is helpful in cases where you want to use custom names for your relations. For example:
class User
include Mongoid::Document
has_many :requests, class_name: "Request", inverse_of: :requester
has_many :assignments, class_name: "Request", inverse_of: :worker
end
class Request
include Mongoid::Document
belongs_to :requester, class_name: "User", inverse_of: :requests
belongs_to :worker, class_name: "User", inverse_of: :assignments
end
In this example, users can both request and be assigned to tickets. In order to represent these two distinct relationships, we need to define two relations to the same model but with different names. Using inverse_of lets Mongoid know that "requests" goes with "requester" and "assignments" goes with "worker." The advantage here is twofold, we get to use meaningful names for our relation, and we can have two models related in multiple ways. Check out the Mongoid Relations documentation for more detailed information.
Related
I want to make an association where a user has many deals and a deal belongs to a user as well as another with a role ('associate').
I am using the rolify gem to do this.
Like this:
# user.rb
has_many :deals
# deal.rb
belongs_to :user
belongs_to :associate # User with role 'associate or admin'
The first belongs to could be whatever user, doesn't matter if the user is any role it should still work, the second belongs to however should most definitely be an associate or an admin
Do you think I should use rolify for this? or should I not and just make a different model for each role?
Update
My current solution won't work because the user as an associate would need two associations, the has many deals and the has_many :client_deals. I'm not sure in the naming still.
Update 2
Max's solution works great!
This is not where you want to use Rolify's tables. Rolify creates a one-to-one assocation between roles and resources through the roles tables. Roles then have a many-to-many assocation through the users_roles table to users.
Which means it works great for cases where the association is one-to-many or many-to-many but Rolify really can't guarantee that there will ever only be one user with a particular role due to the lack of database constraints.
Even if you add validations or other application level constraints that still leaves the potential for race conditions that could be a double click away.
Instead you want to just create separate one-to-one assocations:
class Deal < ApplicationRecord
belongs_to :creator,
class_name: 'User',
inverse_of: :created_deals
belongs_to :associate,
class_name: 'User',
inverse_of: :deals_as_associate
validates :must_have_associate_role!
private
def must_have_associate_role!
# this could just as well be two separate roles...
errors.add(:associate, '^ user must be an associate!') unless associate.has_role?(:associate)
end
end
class User < ApplicationRecord
has_many :created_deals,
class_name: 'Deal'
foreign_key: :creator_id,
inverse_of: :creator
has_many :deals_as_associate,
class_name: 'Deal'
foreign_key: :associate_id,
inverse_of: :associate
end
Two models can really have an unlimited number of associations between them as long as the name of each assocation is unique and you configure it correctly with the class_name and foreign_key options.
Since this uses a single foreign key this means that ere can ever only be one and you're safeguarded against race conditions.
For associate you can use the following
belongs_to :associate, -> { includes(:roles).where(roles: {name: ['associate', 'admin'] }) }, class_name: 'User', foreign_key: 'user_id'
Say I have the models Article, ArticleVote and ArticleComment.
Is there any way to directly drop the prefixes like article_ in the relations (rather than setings class_name, foreign_key, etc.)?
class Article
belongs_to :user
has_many :article_votes # Just call this "votes"
has_many :article_comments # Just call this "comments"
end
No there is no way.
You can specify any name you want but need to give the class name when it can not be inferred from the association name (and perhaps the foreign key, depending on how you named it):
class Article
belongs_to :user
has_many :votes, class_name: 'ArticleVote'
has_many :comments, class_name: 'ArticleComment'
end
I don't see how this should be possible in any other way. How would rails know that you want the name built in another way for this specific association?
Having a problem getting associations right. In my model I have users and requests. A request has an owner and many participants. A user has many requests and many participations.
I tried to model it like this:
class User
include Mongoid::Document
has_many :requests, inverse_of: :owner
has_many :participations, class_name: 'Request', inverse_of: :participants
...
class Request
include Mongoid::Document
belongs_to :owner, class_name: 'User', inverse_of: :requests
has_many :participants, class_name: 'User', inverse_of: :participations
...
When I try to set a participation by calling
#request.participants << current_user
I get the following error:
message: When adding a(n) User to Request#participants, Mongoid could
not determine the inverse foreign key to set. The attempted key was
'participations_id'. summary: When adding a document to a relation,
Mongoid attempts to link the newly added document to the base of the
relation in memory, as well as set the foreign key to link them on the
database side. In this case Mongoid could not determine what the
inverse foreign key was. resolution: If an inverse is not required,
like a belongs_to or has_and_belongs_to_many, ensure that :inverse_of
=> nil is set on the relation. If the inverse is needed, most likely the inverse cannot be figured out from the names of the relations and
you will need to explicitly tell Mongoid on the relation what the
inverse is. Example: class Lush include Mongoid::Document
has_one :whiskey, class_name: "Drink", inverse_of: :alcoholic end
class Drink include Mongoid::Document belongs_to :alcoholic,
class_name: "Lush", inverse_of: :whiskey end
I don't get it, I have all inverse_of set, what am I doing wrong?
Given association in User class:
has_many :followers, through: :follows_as_fallowable,
source: :user
It returns user instances that follow a given user. But when i started to dig deeper i realized that i don't completely understand why does this association(user.followers) returns User objects.
Based on what? I know that it can deduce by name of the association or class_name hash argument, but neither of these actually matters in this case.
I don't have Follower model and i have not provided class_name attribute.
Source parameter just say that it should search by user column in join table.
So how does Rails know that it should select from Users table?
EDIT:
follow_as_followable is another association in User model:
has_many :follows_as_fallowable, class_name: 'Follow', as: :followable
Rails would be picking up knowledge of the User through the follows_as_fallowable relationship, using the association name specified by :source, which is :user (e.g. the User model).
While it's not show in the question, it's likely that the model containing follows_as_fallowable has a belongs_to :user relationship defined, hence the use of source: :user to specify which relationship through which to navigate.
You can see more information in The has_many :through Association section of the Active Record Associations guide.
Rails documentation provides a nice explanation of how to handle a self join where only a has_many-belongs_to relationship is required. In the example, an employee (as a manager) can have many employees (each, as a subordinate).
However, how do you handle a has_many-has_many self join (which I've heard referred to as a bi-directional looped association)?
For example, how do you handle the situation in which an employee can have many subordinates, in its capacity as manager, and also have many managers, in its capacity as subordinate?
Or, in other words, where a user can follow many users and be followed by many users?
A User can have many:
followers in its capacity as followee
followees in its capacity as follower.
Here's how the code for user.rb might look:
class User < ActiveRecord::Base
# follower_follows "names" the Follow join table for accessing through the follower association
has_many :follower_follows, foreign_key: :followee_id, class_name: "Follow"
# source: :follower matches with the belong_to :follower identification in the Follow model
has_many :followers, through: :follower_follows, source: :follower
# followee_follows "names" the Follow join table for accessing through the followee association
has_many :followee_follows, foreign_key: :follower_id, class_name: "Follow"
# source: :followee matches with the belong_to :followee identification in the Follow model
has_many :followees, through: :followee_follows, source: :followee
end
Here's how the code for follow.rb:
class Follow < ActiveRecord::Base
belongs_to :follower, foreign_key: "follower_id", class_name: "User"
belongs_to :followee, foreign_key: "followee_id", class_name: "User"
end
The most important things to note are probably the terms :follower_follows and :followee_follows in user.rb. To use a run of the mill (non-looped) association as an example, a Team may have many :players through :contracts. This is no different for a Player, who may have many :teams through :contracts as well (over the course of such Player's career).
But in this case, where only one named model exists (i.e. a User), naming the through: relationship identically (e.g. through: :follow) would result in a naming collision for different use cases of (or access points into) the join table. :follower_follows and :followee_follows were created to avoid such a naming collision.
Now, a User can have many :followers through :follower_follows and many :followees through :followee_follows:
To determine a User’s :followees (upon an #user.followees call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the follower (i.e. foreign_key: :follower_id) through: such User’s :followee_follows.
To determine a User’s :followers (upon an #user.followers call to the database), Rails may now look at each instance of class_name: “Follow” where such User is the the followee (i.e. foreign_key: :followee_id) through: such User’s :follower_follows.