Rails belongs_to association with multiple foreign keys - ruby-on-rails

I'm trying to figure out a way to define a belongs_to association where the target record can be found by one of 4 different columns. The setup:
User
class User < ActiveRecord::Base
has_many :managerships, foreign_key: :employee_id
has_many :managers, through: :managerships
end
Managership
class Managership < ActiveRecord::Base
belongs_to :employee, class_name: 'User'
belongs_to :manager # <-- ideas?
end
The managerships table has 4 columns manager_id, manager_custom_id, manager_email, manager_saml_id that we can use to look up the user's managers. For every Managership, only one of these columns is ever present.
I'm looking for a way to be able to call user.managers (a manager is a User) so that it returns all users where managerships.manager_id = users.id OR managerships.manager_custom_id = users.custom_id OR managerships.manager_email = users.email OR managerships.manager_saml_id = users.saml_id
Many thanks for any ideas!

Although, I would doubt that 4 foreign keys for one relation is a good idea at all, one way to get managers from User is to use method instead of scope. However you won't be able to use #joins, if you do so.
class User
def managers
self.class.joins(:managerships).where('managerships.manager_id = ? OR managerships.manager_custom_id = ? OR managerships.manager_email = ? OR managerships.manager_saml_id', id, custom_id, emd, saml_id)
end
end
in Managership you can do similar thing, declare #manger instead of belongs_to :manager

Related

Question about table/models logic before database generation

Im trying to make my first side project using rails to learn would you kindly help me understand this?
The basic idea is to have a betting game where one user generates a new bet that can only be accepted by another user (only 2 competitors assigned for each bet, the creator and the other player).
I'm thinking about 2 tables:
users
bets
Normally I would just have a one to many relationship for the user that created the bet. But I'm confused about the 'competitor' column where another user is also a user with a user_id. How can I express this relationship better than this:  
After thinking it through it doesn't look like a good setup because I'm renaming a column where I'm storing the user_id and having a many to many 'through' model doesn't make sense since it is a "only one competitor can participate in that bet".
I was thinking about a 'one to one through' creating a 'competitors' table like so:
Could you explain to me how to build it in a better way?
Many thanks!
just an idea, you can do this with 2 foreign_keys
so user can be as creator or competitors, you can also makesure that creator_id and competitor_id cannot be same value since user cannot bet with self
class Bet < ActiveRecord::Base
belongs_to :creator, foreign_key: "creator_id", class_name: "User"
belongs_to :competitor, foreign_key: "competitor_id", class_name: "User"
end
class User < ActiveRecord::Base
# as creator to create bet
has_many: creator_bets, foreign_key: :creator_id, class_name: "Bet"
# as competitor to create bet
has_many: competitor_bets, foreign_key: :competitor_id, class_name: "Bet"
end
#user = User.first
#user.creator_bets.build(...)
# this to create bet as creator
#user.competitor_bets.build(...)
# this to create bet as competitor
having a many to many 'through' model doesn't make sense since it is a
"only one competitor can participate in that bet".
Actually it does. Its is in many ways simpler than having muliple assocations pointing to the same table as you don't have to deal with the situation where a user could be in either column which requires something like:
# this gets much messier as you have to deal with more complex problems
Bet.where('bets.user_id = :id OR bets.competitor_id = :id', id: params[:user_id])
A many to many association also gives you the option of removing that limitation later with minimal redesign.
Given the following associations:
# This represents an event that users can bet on
# for example Elon Musk being the first man on mars.
class Event < ApplicationRecord
has_many :bets
has_many :users, through: :bets
end
class User < ApplicationRecord
has_many :bets
has_many :events, through: :bets
end
# This is the "join model" that joins User and Event
# columns:
# - user_id [bigint, fk]
# - event_id [bigint, fk]
# - amount [decimal]
class Bet < ApplicationRecord
belongs_to :user
belongs_to :event
end
You can simply create a bet by:
#event = Event.create!(description: "Elon Musk will be the first man on mars.")
#event.bets.new(user: User.first, amount: 500)
You can of course cap the number of users to two by adding a custom validation or in your controllers. If you only have two you can assume that event.users.first is the creator and event.users.last is the competitor.

Create Rails scope comparing fields on two tables

I have a number of associated tables in an application
class Listing < ActiveRecord::Base
belongs_to :house
belongs_to :multiple_listing_service
end
class House < ActiveRecord::Base
has_one :zip_code
has_one :primary_mls, through: :zip_code
end
I wanted to create a scope that produces all the Listings that are related to the Primary MLS for the associated House. Put another way, the scope should produce all the Listings where the multiple_listing_service_id = primary_mls.id for the associated house.
I've tried dozens of nested joins scopes, and none seem to work. At best they just return all the Listings, and normally they fail out.
Any ideas?
If I understand correctly, I'm not sure a pure scope would be the way to go. Assuming you have:
class MultipleListingService < ActiveRecord::Base
has_many :listings
has_many :zip_codes
end
I would go for something like:
class House < ActiveRecord::Base
...
def associated_listings
primary_mls.listings
end
end
Update 1
If your goal is to just get the primary listing then I would add an is_primary field to the Listing. This would be the most efficient. The alternative is a 3 table join which can work but is hard to optimize well:
class Listing < ActiveRecord::Base
...
scope :primary, -> { joins(:houses => [:zip_codes])
.where('zip_codes.multiple_listing_service_id = listings.multiple_listing_service_id') }

Rails ActiveRecord includes with run-time parameter

I have a few models...
class Game < ActiveRecord::Base
belongs_to :manager, class_name: 'User'
has_many :votes
end
class Vote < ActiveRecord::Base
belongs_to :game
belongs_to :voter, class_name: 'User'
end
class User < ActiveRecord::Base
has_many :games, dependent: :destroy
has_many :votes, dependent: :destroy
end
In my controller, I have the following code...
user = User.find(params[:userId])
games = Game.includes(:manager, :votes)
I would like to add an attribute/method voted_on_by_user to game that takes a user_id parameter and returns true/false. I'm relatively new to Rails and Ruby in general so I haven't been able to come up with a clean way of accomplishing this. Ideally I'd like to avoid the N+1 queries problem of just adding something like this on my Game model...
def voted_on_by_user(user)
votes.where(voter: user).exists?
end
but I'm not savvy enough with Ruby/Rails to figure out a way to do it with just one database roundtrip. Any suggestions?
Some things I've tried/researched
Specifying conditions on Eager Loaded Associations
I'm not sure how to specify this or give the includes a different name like voted_on_by_user. This doesn't give me what I want...
Game.includes(:manager, :votes).includes(:votes).where(votes: {voter: user})
Getting clever with joins. So maybe something like...
Game.includes(:manager, :votes).joins("as voted_on_by_user LEFT OUTER JOIN votes ON votes.voter_id = #{userId}")
Since you are already includeing votes, you can just count votes using non-db operations: game.votes.select{|vote| vote.user_id == user_id}.present? does not perform any additional queries if votes is preloaded.
If you necessarily want to put the field in the query, you might try to do a LEFT JOIN and a GROUP BY in a very similar vein to your second idea (though you omitted game_id from the joins):
Game.includes(:manager, :votes).joins("LEFT OUTER JOIN votes ON votes.voter_id = #{userId} AND votes.game_id = games.id").group("games.id").select("games.*, count(votes.id) > 0 as voted_on_by_user")

How to write active record query to details by using and

i have two tables
1)Properties :fields are id, name, propert_type,category_id
2)Admins : fields id, name,mobile,category_id
i want to write an active record to list all properties , where category_id in properties table and category_id in Admins table are equal, according to current_user_id
i am listing this property list by logging as admin.
model relation
class Category < ActiveRecord::Base
has_many :admins,dependent: :destroy
has_many :properties,dependent: :destroy
end
class Admin < ActiveRecord::Base
belongs_to :category
has_many :properties
end
class Property < ActiveRecord::Base
belongs_to :admin
belongs_to :category
end
i wrote active record like this , but i got error,
can anyone please suggest me a solution for this
#properties= Property.where('properties.category_id=?','admins.category_id=?').and('admins.id=?',current_user.specific.id)
With your assosciation,You can use a sub query for getting your result in one line
#properties = Property.where(category_id: Admin.select("category_id").where(id: current_user.id))
As per my understanding current_user is an Admin. So You can search by the category_id of current_user. If I'm right, try this
#properties = Property.where(category_id: current_user.category_id)

RoR: How to load the related record in a one-to-one relationship

I have the following:
class User < ActiveRecord::Base
has_one :subscription
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
The User has a subscription_id and thus can have only one subscription (which is what I want).
Which works fine, but now I do:
#users = User.find(:all)
and I want all the subscriptions to be included.
I tried:
#users = User.find(:all, :include=>[:subscription]) # include subscription
But that would like the subscription table to have a user_id (SQLite3::SQLException: no such column: subscriptions.user_id: SELECT "subscriptions".* FROM "subscriptions" WHERE ("subscriptions".user_id = 2)).
Which is (ofcourse) not what I want.
I am new at RoR and I couldn't find a good example of this case in the books I have nor on the web.
I think you have your associations the wrong way round on the model objects. You should have
class User < ActiveRecord::Base
belongs_to :subscription
end
class Subscription < ActiveRecord::Base
has_one :user
end
belongs_to should be used on the side of the association that defines the foreign key (in this case subscription_id). Semantically this probably looks a bit odd, but that's because in this case rails would kind of expect a user_id to be on the subscriptions table instead of the other way round as you have it.
After that
User.find(:all, :include=>[:subscription])
Should work fine
First of all if the user has foreign key (subscription_id) then it should have belongs_to not the other way around. As the Rails docs says for has_one method:
"This method should only be used if the other class contains the foreign key. If the current class contains the foreign key, then you should use belongs_to instead"
(taken from: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#M001834)
Second, in your example you tried to find User and include user. You need to do this:
#users = User.find(:all, :include=>[:subscription])

Resources