Active Record querying relation to get other relation - ruby-on-rails

I want to be able to return an AR relation by calling a method on another relation of different model objects. In other words, given a User model which belongs_to a House model (which either has_one or has_many Users) I want to be able to take a relation of users and do users.houses, which should return the relation of house objects to which those users belong.
NOTE -- I'm not trying to create a user.houses method (singular user), but rather a users.houses method, which grabs all houses whose ids are among the list of all house_ids in the list of users. The idea is that this then allows me to call class/relation methods on that relation of houses: users.houses.house_class_method.
I've tried doing this "manually":
class User
belongs_to :house
# Is there some AR Relationship I can declare that would
# write this method (correctly) for me?
def self.houses
House.where(id: pluck(:house_id))
end
end
House model:
class House
has_many :users
def self.addresses
map(&:address)
end
def address
"#{street_address}, #{city}"
end
end
Two issues, though:
This feels (maybe incorrectly) like something that should be
declarable via a AR relationship.
It isn't working correctly. The trouble (as outlined in this
separate question) is that users.houses works fine, but when I
do users.houses.addresses,
the method is called on the House class, rather than the relation of
houses that users.houses returns (!?!). As a consequence, I get an undefined method 'map' for <Class:0x1232132131> error.
Is there a correct way to do this in Rails -- to essentially say that a relation belongs to another relation, or that a model belongs to another, on a relation level as well?
Thanks!

Implementing has_and_belongs_to_many relationship between User and House models might make it easier for accomplishing what you want.
class User
has_and_belongs_to_many :houses
...
end
class House
has_and_belongs_to_many :users
...
end
> user = User.find(user_id)
> user.houses # All houses the user belongs to
> house = House.find(house_id)
> house.users # All users belonging to the house

You don't have to implement self.houses; Rails does this for you.
In your User model (user.rb), do the following
def User
has_many :houses
...
end
and in your House model
def House
belongs_to :user
...
end
from there on, some_user.houses returns all the houses that belong to that user. Just make sure there is a user_id (integer) column in your Houses table.

Hmm, users would be an Array or ActiveRelation, not of type User
I would recommend a static method on User:
def self.houses_for_users(users)
..
end

Related

Rails: Size is not working on a has_many relationship

I have two models, Student and Absence, with the relationship:
class Student < ActiveRecord::Base
has_many :absences
class Absence < ActiveRecord::Base
belongs_to :student
I want to see how many absences have been logged for each student, and show that total on the student's #show page. In the console, I'm able to query this by calling:
a = Student.find(1)
a.absences.size
However, I can't seem to get it to work in the app itself. Would the preferred way of querying this in the app be in the model or the controller?
This will never work, since you are calling association on the model, not instance. At first you should fetch a student and then count associated records:
Student.find(<id here>).absences.size
To aggregate this information for all students, you can add absence_count attribute accessor in Student model:
attr_accessor :absence_count
and then do something like this:
Student.includes(:absences).each{|s| s.absence_count = s.absences.size}
If you just need to output it in the view, then you can try the following:
Student.includes(:absences).each do |student|
puts student.absences.size
end

Deleting first occurrence from many to many collection entry at rails?

In many to many fields delete method is deleting all the occurrence of collection. Say I have:
class user < ActiveRecord::Base
has_and_belongs_to_many :cars
end
class car < ActiveRecord::Base
has_and_belongs_to_many :users
end
users and cars are many to many relationship, I have defined my users_cars table. Now user can have repetitive car entry as relation. For example:
Car: A,B,C
User: U1,U2,U3
U1=[A,B,C,A,A,A,B]
Which can be implemented using many to many relationship, the way I have implemented. BUT, at the time when I want to delete one of the car entries of user the problem occurs.
User.cars.delete(car) #deletes all occurrence of car
User.cars.delete_at(User.cars.find_index(video_card)) #delete_at does not exist
Now how to resolve this?
First of all, you can't call User.cars unless you have defined a class level method cars in your User model, but in this way, you would return all cars, and that - in no way - would make sense.
Second, delete_at is a method that works on Array objects, and expects an integer to be passed in. So as a little hack, you can turn your ActiveRecord::Associations object into an array, and then call delete_at method.
user = User.first
user.cars.to_a.delete_at(Car.last.id) # assuming that the last car belongs
# to the first user, something you would never do in actual
# production code.
Edit:
You can also try the following to achieve the same functionality:
user = User.first
user.cars.where("cars.id = ?", Car.first.id).first.delete
Edit 2:
For what you asked in comment, you can have a model for the table cars_users.
rails g model CarUser
class Car < ActiveRecord::Base
has_many :cars_users
has_many :users, through: car_users
end
class User < ActiveRecord::Base
has_many :cars_users
has_many :cars, through: car_users
end
class CarUser < ActiveRecord::Base
belongs_to :car
belongs_to :user
end
And now, you can do:
CarUser.where("car_id = ? AND user_id = ?", Car.first.id, User.first.id).first.delete

Rails STI vs Polymorphic vs Neither

I have a Users class in my rails app. I need two types of users, 1) Players, 2) Managers. Users will all log in using Devise and have same basic user fields. I will have a League model. What is the best way to model this? STI doesn't seem quite right but not sure if Polymorphic works either since if both Manager < User && Player < User. The only real role a manager will have will be admin roles such as adding/removing players from leagues, setting up schedules etc.
class League < ActiveRecord::Base
has_one :manager
has_many :players
end
class Match < ActiveRecord::Base
has_and_belongs_to_many :players
belongs_to :league
end
class User < ActiveRecord::Base
has_and_belongs_to_many :matches
has_and_belongs_to_many :leagues
end
class Player < User
has_and_belongs_to_many :leagues
has_and_belongs_to_many :matches
end
class Manager < User
has_many :leagues
end
Any suggestions for the best way to set this up are greatly appreciated!
Using STI for players ActiveRecord is looking for Player table for the join but it doesn't exist.
STI can work for this, but since the difference between the types of users is basically a flag that says a user is an admin, you just need something on a user to check whether they are allowed certain actions. Roles can be either exclusive or inclusive (so that a user can be both a Player and a Manager), but once set up you can shape the interface around the capabilities a user has:
<% if #user.has_role?('manager') %>
<%= render 'manager_fields' # or manager_page, etc. %>
<% end %>
How you handle this in the controller depends on how you implement the roles, either via gems like Pundit, CanCanCan, etc. or through explicit checks in something you write yourself. The gems will give you helper methods to reduce the amount of typing involved in views and a more declarative syntax on the back end.
STI eliminates the need of separate tables for each class, given that most (or ideally all) attributes are shared between the two models.
In this case we'd add a new column in the users table called type which will indicate the class that this record will be parsed to, so either Player or Manager
As for the models
class Player < User
and
class Manager < User
As for the permission you could check if the user instance is of type Player or Manager
Of course you don't need to do STI if you don't want to, instead you'll create two tables players and managers and you'll need to join them with the User model, which would be tricky, since not all users have managers and not all users have players, so one or the other will always return nil, .. unless you create a polymorphic relation, but then you'll need to check the type of the object, .. idk
I think STI is your best solution, for the time being

How can i retrieve data in a related model which needs a reference to the calling model

I have a controller and two models, a user and a store.
A user has_many stores. Inside stores I have a method get_items which lists items.
get_items itself needs a reference to the user.
What I have done now is the following
#items = user.stores.first.get_items(user)
this looks weird to me since the initial caller has to pass itself as an argument to the get_items method.
What would you recommend?
best,
phil
If you've defined the relation in two directions,
class User < ActiveRecord::Base
has_many :stores
end
class Store < ActiveRecord::Base
belongs_to :user
end
then you have access to the user that a store belongs to within the Store instance. So you should be able to write
def get_items
do_something_with user
end
and it should use the correct user. The call to get_items could then be
#items = user.stores.first.get_items
I would personally rename get_items to items.

How to create one master condition for activerecord usage

I have many tables that have the user_id field and I want to filter all the queries to these tables by appending a where clause for this user_id. How would I be able to achieve it in Rails?
Thanks!
I think you're thinking of this backwards. Search those tables through the user object that you have, ensuring that they are already associated in ActiveRecord. So, if you have a table called magazines, you'd use something like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :magazines
#app/models/magazine.rb
class Magazine < ActiveRecord::Base
belongs_to :user
Then you can reference all of a user's magazines through an instance of a user:
#user = User.first
#user.magazines.all
That will return all magazines that have a user_id equal to the user's ID.

Resources