Foreign Keys pointing to same Model - ruby-on-rails

I have an User model
class User < ActiveRecord::Base
attr_accessible :email, :name
has_many :client_workouts
end
And a ClientWorkout model
class ClientWorkout < ActiveRecord::Base
attr_accessible :client_id, :trainer_id, :workout_id
belongs_to :client, :class_name => User, :foreign_key => 'client_id'
belongs_to :trainer, :class_name => User, :foreign_key => 'trainer_id'
end
I first want to know what or if I'm doing something wrong when writing the associations. Ideally I want to be able to call a query where I find the user's clients workouts where the user's id matches with client_id or trainer_id. So...
user.client_workouts.trainer???

This will not work as rails assumes that the ClientWorkout have a user_id column. I don't think there is any way to make a has_many relation that matches two columns... Instead you could create a method like this:
class User < ActiveRecord::Base
attr_accessible :email, :name
has_many :client_workouts, :foreign_key => "client_id"
has_many :trainer_workouts, :foreign_key => "trainer_id"
def client_and_trainer_workouts
ClientWorkouts.where("client_id = ? OR trainer_id = ?", id, id)
end
end
Otherwise you could create a scope on the ClientWorkout model like this:
class ClientWorkout < ActiveRecord::Base
attr_accessible :client_id, :trainer_id, :workout_id
belongs_to :client, :class_name => User, :foreign_key => 'client_id'
belongs_to :trainer, :class_name => User, :foreign_key => 'trainer_id'
scope :workouts_for_user,
lambda {|user| where("client_id = ? OR trainer_id = ?", user.id, user.id) }
end
You could also do both, and let the method on the use call the scope on the ClientWorkout.

Related

Rails - how to make association between these models (not based on "id", but on "email")

I have these models that obviously have more attributes, but for simplicity I kept them just like this:
class User < ActiveRecord::Base
has_many :subs, :foreign_key => :email, :class_name => "subs"
end
class Subscription < ActiveRecord::Base
belongs_to :plan
belongs_to :user
end
In the table subscriptions, there's a column called email. This column points to the table users where matches an email address of a single user (the column email is in both tables unique).
I would need to create an association between these two models based on the email value. But when I try to run this query (and to get all subscription for the currently sign in user):
<%= current_user.subs.inspect %>
I get this error message:
uninitialized constant User::subs
I'd like to ask you guys for helping me with this association.
Thanks
uninitialized constant User::subs
This code
class User < ActiveRecord::Base
has_many :subs, :foreign_key => :email, :class_name => "subs"
end
should be like this
class User < ActiveRecord::Base
has_many :subs, :foreign_key => :email, :class_name => "Subscription"
end
When you are using a class_name option with the associations,it should point to the respected model classname(in your case it is Subscription not subs).Since there is no model with the classname subs,it throws that exception.
class User < ActiveRecord::Base
has_many :subs, :foreign_key => :email, :class_name => "Subscription", :primary_key => :email
end
class Subscription < ActiveRecord::Base
belongs_to :plan
belongs_to :user, :foreign_key => :email, :class_name => "Subscription", :primary_key => :email
end
You have to set the correct Class name and also you have to set the primary key.

Filtering Association By Another Association

I have a friendship model that contains a status.
class Friendship < ActiveRecord::Base
attr_accessible :friend_id, :user_id, :source_id
after_create :check_friend_status
# Relationships
belongs_to :user, :touch => true
belongs_to :friend, :class_name => "User", :touch => true
belongs_to :source
has_one :status, :class_name => "FriendStatusDescriptor", :foreign_key => 'friendship_id'
validates_uniqueness_of :user_id, :scope => [:friend_id, :source_id]
def check_friend_status
# Check user/friend for existing friend status
if FriendStatusDescriptor.find(:first, :conditions => ["friendship_id = ?", self.id]).nil?
status = FriendStatusDescriptor.new
status.friendship_id = self.id
status.save
end
end
end
class FriendStatusDescriptor < ActiveRecord::Base
attr_accessible :alert, :friendship_id, :hide
belongs_to :friendship
validates_uniqueness_of :friendship_id
end
The status model has a boolean variable called hide. I want to be able to filter my user's friendships by the ones with hide set to false. Something along these lines.
# In User Model
# Friendships
has_many :friendships do
def visible
# Where !friendship.status.hide
end
end
So that in my controller I can just do this
user.friendships.visible
I'm not sure how to access the individual friendship in this method though.
I think you want:
class User
has_many :friendships,
:class_name => "FriendStatusDescriptor",
:foreign_key => 'friendship_id'
If you want to filter visible and non-visible friendships by separete you can add scopes to Friendship model:
class Friendship
scope :visible, -> { joins(:status).where("friend_status_descriptors.hide = ?", false) }
Then apply that scope:
user.friendships.visible

AR joined class in parent class

i have Author entity which belongs_to User. User has_many posts. Please advice how can i show recent_posts on Author entity from User.
class User < ActiveRecord::Base
has_many :posts, :foreign_key => "author_id"
end
class Post < ActiveRecord::Base
attr_accessible :title, :content
belongs_to :author, :class_name => "User"
end
class Author < ActiveRecord::Base
belongs_to :user
has_many :recent_posts, :through => :user,
:class_name => "Post",
:limit => 3,
:order => "updated_at desc"
end
How recent_post should be done? Raw sql?
You want the :source option to has_many, which you use to specify the association on the other model, like so:
has_many :recent_posts, :through => :user, :source => :posts, :limit => 3, :order => 'updated_at desc'

Validation on associtions in rails with roles

So I have two ActiveRecord classes
class User < ActiveRecord::Base
has_many :buyer_deals, :class_name => "Deal", :foreign_key => :buyer_id
has_many :seller_deals, :class_name => "Deal", :foreign_key => :seller_id
validates_presence_of :name # THIS SHOULD ONLY BE RUN IF USER IS A SELLER
# IN THE DEAL
validates_presence_of :phone # THIS SHOULD ONLY BE RUN IF USER IS A BUYER
# IN THE DEAL
end
class Deal < ActiveRecord::Base
belongs_to :seller, :class_name => 'User'
belongs_to :buyer, :class_name => 'User'
validates_associated :seller
validates_associated :buyer
end
What I want to do is create a new deal with.
Deal.create(A NICE STRUCT WITH SELLER AND BUYER)
However I only want to run the name validation if the relation from the deal is a seller and the phone if the the relation from the deal is a seller, is this possible in rails, does not seem to find anything in the documentation.
You should be able to do this by adding a condition to you validation.
So, your User class would wind up looking something like...
class User < ActiveRecord::Base
has_many :buyer_deals, :class_name => "Deal", :foreign_key => :buyer_id
has_many :seller_deals, :class_name => "Deal", :foreign_key => :seller_id
validates_presence_of :name, :if => :has_an_active_seller_deal?
validates_presence_of :phone, :if => :has_an_active_buyer_deal?
def has_active_seller_deals?
seller_deals.count > 0
end
def has_active_buyer_deals?
buyer_deals.count > 0
end
end
An alternative to this would be to simply require all users to have a name and phone number on file at all times (no conditional validation), and only reveal it to other users with which they had active deals, and not as part of a user's public profile, thereby protecting the user's privacy when possible. This would probably be simpler.
You could put the validations in a callback:
before_save :check_user_type
private
def check_user_type
user_type = self.responds_to?(seller_id) ? :seller : :buyer
if user_type == :seller
validates_presence_of :name
else
validates_presence_of :phone
end

Multiple References From Single Table

I've seen this issue referenced a few times, but nothing too complete. I'm having a problem with using a join table for a single model. For example, suppose we have Users and Highfives. Highfives will just be a join table for the two users highfiving. So I have this:
class Highfive < ActiveRecord::Base
belongs_to :user1,
:class_name => "User"
belongs_to :user2,
:class_name => "User"
end
class User < ActiveRecord::Base
has_many :highfives
end
However, with this, I am unable to do something like User.find(1).highfives since that generates a query like:
SELECT "highfives".* FROM "highfives" WHERE "highfives"."user_id" = 1
Really, I should be getting a query like:
SELECT "highfives".* FROM "highfives" WHERE "highfives"."user1_id" = 1 or "highfives"."user2_id" = 1
I imagine to do this I'll need to modify my User model in some way. But what am I missing?
Thanks.
You need to specify the foreign key in your has_many statement, otherwise Rails will assume it's user_id:
class User < ActiveRecord::Base
has_many :highfives, :foreign_key => :user1_id
end
Of course, this only works for a single foreign key. In your case, you'd probably want an instance method instead:
class User < ActiveRecord::Base
def highfives
Highfive.where("user1_id = ? or user2_id = ?", id, id)
end
end
Or, assuming it's impossible for a User to highfive himself:
class User < ActiveRecord::Base
has_many :highfives1, :class => "Highfive", :foreign_key => :user1_id
has_many :highfives2, :class => "Highfive", :foreign_key => :user2_id
def highfives
highfives1 + highfives2
end
end
Specify :foreign_key in your Models. So..
class Highfive < ActiveRecord::Base
belongs_to :user1,
:class_name => "User",
:foreign_key => "user1_id"
belongs_to :user2,
:class_name => "User",
:foreign_key => "user2_id"
end
class User < ActiveRecord::Base
has_many :highfive1,
:class_name => "Highfive",
:foreign_key => "highfive1_id"
has_many :highfive2,
:class_name => "Highfive",
:foreign_key => "highfive2_id"
end
Reference!

Resources