how can I do inverse of self-reference with rails - ruby-on-rails

I have the user model like :
has_many :users, class_name: 'User'
belongs_to :master_user, class_name: 'User', optional: true, inverse_of: :users
I Would like to find :
User.first.master_user it's ok
MasterUser.users but get an error : "NameError: uninitialized constant MasterUser"

You are getting that error because you have not defined a MasterUser model. I am guessing you only have a User model as described in your question. If you want to find the users belonging to a "master_user" then you need to find a "master_user" first, then request its users. It would look something like this:
user_with_a_master = User.where.not(master_user_id: nil).first
master = user_with_a_master.master_user
master_users = master.users

Here is an example of how to properly setup a self-referential association with a bit less confusing naming:
class User < ApplicationRecord
belongs_to :manager
class_name: 'User', # requied here since it cannot be derided from the name
optional: true,
inverse_of: :subordinates
has_many :subordinates,
class_name: 'User', # requied here since it cannot be derided from the name
foreign_key: :manager_id, # what column on the users table should we join
inverse_of: :manager
end
"Managers" here are not a separate class. While you could use single table inheritance to that purpose you should probally get the basics figured out first. Even if you did have a MasterUser class you would get NoMethodError since you're calling .users on the class and not an instance of the class - that will never work and is a very common beginner misstake.
Note that this strictly speaking would actually work without the inverse_of: option which is really just used to explicity set the two way binding in memory.
So in your case it should look like:
class User < ApplicationRecord
# class_name isn't required since it can be derided from the name
has_many :users,
foreign_key: :master_user_id,
inverse_of: :master_user
belongs_to :master_user,
class_name: 'User', # requied here since it cannot be derided from the name
optional: true,
inverse_of: :users
end
Note that the users table must have a master_user_id column.

Related

Rails model with rubocop error - specify an `:inverse_of` option

I've got two models with optional relations has_many - belongs_to, like below:
class Journey < ApplicationRecord
has_many :activities, dependent: :destroy, foreign_key: 'cms_journey_id'
end
class Activity < ApplicationRecord
belongs_to :journey, optional: true, foreign_key: 'cms_journey_id'
end
As you see the relation is based on non-standard foregin_key name (both models are linked with each other by record called cms_journey_id). After I added foreign_key: 'cms_journey_id' I'm getting Rubocop error in both models:
Rails/InverseOf: Specify an `:inverse_of` option
If you don't explicitly specify the inverse relationships, Rails does its best to infer the inverse association on a model (at least for has_many, has_one, and belongs_to associations) using the class names as the basis for its guesses.
But anytime you use a scope or set non-standard naming, you need to explicitly tell Rails how to navigate the associations using inverse_of. In your case:
class Journey < ApplicationRecord
has_many :activities, dependent: :destroy,
foreign_key: 'cms_journey_id',
inverse_of: :journey
end
class Activity < ApplicationRecord
belongs_to :journey, optional: true,
foreign_key: 'cms_journey_id',
inverse_of: :activities
end
For future reference, the Rubocop documentation on individual cops is generally good and clear, and includes examples of 'good' and 'bad'. Just search for the cop name (e.g. 'Rails/InverseOf') and you're in good shape.

Association with proc for mongoid

I saw in sources of Mongoid that no proc can be send to association method, what is best practice to achieve below AR association with Mongoid:
class Task
...
belongs_to :creator, ->{where(type: :manager)}, class_name: "User"
belongs_to :acceptor, ->{where(type: :acceptor)}, class_name: "User"
end
It seems I'm find answer. Relations methods in Mongoid accepts block as third argument.
belongs_to :creator, class_name: "User", inverse_of: :created_tasks do
->{ where(type: :manager)}
end
belongs_to :executor, class_name: "User", inverse_of: :accepted_tasks do
->{where(type: :acceptor)}
end
At least for has_many relations I've been having trouble getting this working with a default filter in mongo, so I wonder if your answer is actually working correctly for belongs_to. This has been working correctly on the has_many side for me so I figured it might be a useful answer for someone else as well:
belongs_to :user do
def creator
where(type: :manager)}
end
def executor
where(type: :acceptor)
end
end
They can then be accessed by saying task.user.accepted, task.user.created, etc, however I have been unable to figure out how to actually set the default filtering for the overall relation.

Using includes on a custom association in rails

I have a class Team that has many Matchups
class Team < ActiveRecord::Base
has_many(
:home_matchups,
class_name: "Matchup",
foreign_key: :team_1_id,
primary_key: :id
)
has_many(
:away_matchups,
class_name: "Matchup",
foreign_key: :team_2_id,
primary_key: :id
)
def matchups
Matchup.where("team_1_id = ? OR team_2_id = ?", id, id)
end
end
and
class Matchup < ActiveRecord::Base
validates :team_1_id, :team_2_id, :week, presence: true
belongs_to(
:team_1,
class_name: "Team",
foreign_key: :team_1_id,
primary_key: :id
)
belongs_to(
:team_2,
class_name: "Team",
foreign_key: :team_2_id,
primary_key: :id
)
has_one :league, through: :team_1, source: :league
end
This all works fine, until I want to use includes like this: Team.includes(:matchups) because although matchups returns an active record relation I can't use it. This is a problem later because I need this kind of information for many teams and I don't want to make a query for every team. Can anyone tell me how I could use includes to get the combination?
Like Sean Huber said, include the home and away matchups, then refer to them with .matchups
def matchups
(home_matchups + away_matchups).sort_by(&:week)
end
It's a little slower (maybe), but you'll be guaranteed to use the relation if it's loaded.
The general approach here - include the relations like you would, then use convenience methods that refer to those relations - whether it's in a view or another model method like this example.

Self Referencing ActiveRecord Model

I just started learning Rails. I have the following question.
I generated "User" models (table "users") and I want to add relation to manager. Manager is also a User. I want to be able to write:
user.manager # => returns User object
I tried to write it like this:
class User
belongs_to :user, as: :manager
end
But it didn't work. Can you please help me.
If Manager is referenced by manager_id field, then:
class User
belongs_to :manager, class_name: 'User'
end
You can also identify subordinates of the user using has_many relation:
class User
belongs_to :manager, class_name: 'User'
has_many :subordinates, class_name: 'User', foreign_key: 'manager_id'
end
belongs_to :manager, class_name: 'User', foreign_key: 'manager_id'
Try this

"has_two" model relationship with polymorphic model?

I have a polymorphic PLACE model like this:
class Place < ActiveRecord::Base
belongs_to :placeable, polymorphic: :true
...
So, for example, in my Lodging model I have something like:
class Lodging < ActiveRecord::Base
has_one :place, as: :placeable, dependent: :destroy
...
And they work as expected.
The point is that now I want to create a CarRental model and this model has TWO places, on is the pick-up place and the other one is the drop-off place.
So I wrote:
class Transportation < ActiveRecord::Base
has_one :start_place, as: :placeable, dependent: :destroy
has_one :end_place, as: :placeable, dependent: :destroy
And of course that does not work. Any insights on how to do that?
EDIT 1: IF I DO LIKE
class Transportation < ActiveRecord::Base
has_one :start_place, class_name: 'Place', as: :placeable, dependent: :destroy
has_one :end_place, class_name: 'Place', as: :placeable, dependent: :destroy
It works! But why? Where is the start or end information saved?
** EDIT 2: NOPE, IT DOES NOT WORK **
It does not work... =(
I guess, associations spitted out errors before because of the unknown classes. has_one :start_place by default assumes you mean a class StartPlace that doesn't exist. It singularizes the term (as best as it can) and converts it to CamelCase. Once you've specified that you mean Place, it's clear.
You should be adding a new column anyways.Let's try single table inheritance (STI). Add a column type of type string to your places table:
rails generate migration AddTypeToPlaces type:string
...make sure it does what it says, then migrate and create new models like so:
rails generate model StartPlace --no-migration --parent=Place
rails generate model EndPlace --no-migration --parent=Place
Note: they don't get a dedicated table and inherit from Place, not ActiveRecord::Base. It should generate two empty classes, that's fine, they inherit things from Place.
...then revert your associations to what didn't work a while ago:
has_one :start_place, as: :placeable, dependent: :destroy
has_one :end_place, as: :placeable, dependent: :destroy
...and they should work now, because StartPlace is defined as
Place with type equal to "StartPlace"
...same with EndPlace with corresponding type.
I described this quite some time ago for a similar case.
Your schema doesn't make sense to me - that places belong to lodgings etc. Surely there's a fixed number of places, and then they can have a number of different things in them? In your schema the same place could be in the database lots of times which seems wrong. I don't think that polymorphic associations are the way to go here either.
I would model this like so:
class Place
has_many :lodgings
has_many :starting_transportations, :class_name => "Transportation", :as => :start_place
has_many :ending_transportations, :class_name => "Transportation", :as => :end_place
class Lodging
belongs_to :place #using lodgings.place_id
class Transportation
belongs_to :start_place, :class_name => "Place" #via transportations.start_place_id
belongs_to :end_place, :class_name => "Place" #via transportations.end_place_id
btw, i think "Location" is a better name than "Place" for physical locations in the real world. "Place" sounds too vague.

Resources