Accessing two sides of a user-user relationship in rails - ruby-on-rails

Basically, I have a users model in my rails app, and a fanship model, to facilitate the ability for users to become 'fans' of each other.
In my user model, I have:
has_many :fanships
has_many :fanofs, :through => :fanships
In my fanship model, I have:
belongs_to :user
belongs_to :fanof, :class_name => "User", :foreign_key => "fanof_id"
My fanship table basically consists of :id, :user_id and :fanof_id. This all works fine, and I can see what users a specific user is a fan of like:
<% #user.fanofs.each do |fan| %>
#things
<% end %>
My question is, how can I get a list of the users that are a fan of this specific user?
I'd like it if I could just have something like #user.fans, but if that isn't possible what is the most efficient way of going about this?
Thanks!

Add in User model:
has_many :my_fanclubs, :class_name => 'Fanship', :foreign_key => 'fanof_id'
has_many :fans, :through => :my_fanclubs, :source => :user, :class_name => 'User'
(not tested)

Related

What's the most idiomatic way to model this relationship in Rails?

Let's say we have coaches, clients, and users.
What's the ideal way to model this in a way that isn't inheritance? I'd like to avoid STI.
Right now I have something like this:
user.rb
has_many :coaches, :foreign_key => :client_id
has_many :coach_users, :through => :coaches, :source => :user
has_many :clients, :class_name => "Coach"
has_many :client_users, :through => :clients, :source => :client
def is_a_coach_of?(client)
self.client_users.include?(client)
end
def is_a_client_of?(coach)
self.coach_users.include?(coach)
end
coach.rb
belongs_to :user
belongs_to :client, :class_name => "User"
But this feels really clunky to deal with a User object that is supposedly a 'coach' and having to type user.coach_users to get a collection of users that are being coached by this specific user.
It feels very non idiomatic and quite honestly, it's just plain confusing and I hate it. I want something more elegant.
I thought of removing the join model and just having two has_many's on the user.rb model but it still feels clunky, especially the icky feeling of violating roles of objects. These are different roles but are also very similar because they're all a user. How do you separate such common logic in an elegant way, in the right idiomatic way with Rails and Ruby?
A "user of the site" can exist without being a coach or a client.
If the modeling requires just a relationship, then I can see it be a HABTM but what if the individual relationship requires extra logic? E.g extra logic on client or coach? Would you just mixin a class that defines logic in the User model? Or would you create separate AR models for the relationship and if so, how?
If a client/coach is just a relationship, it can be just that, not a separate model. So you can do a has_and_belongs_to_many relationship between Users. Create a migration with:
def up
create_table :coaches_clients do |t|
t.integer :coach_id
t.integer :client_id
end
end
and in you model:
has_and_belongs_to_many :clients,
:foreign_key => 'client_id',
:association_foreign_key => 'coach_id',
:class_name => 'User',
:join_table => 'coaches_clients'
has_and_belongs_to_many :coaches,
:foreign_key => 'coach_id',
:association_foreign_key => 'client_id',
:class_name => 'User',
:join_table => 'coaches_clients'

When an association is specified with class name, how do you access it? Rails 3

So lets I have a Transactions model.
Transactions.rb has:
belongs_to :buyer, :class_name => "User"
belongs_to :seller, :class_name => "User"
The user logged in, is accessed with current_user.
How can I do something like current_user.transactions? (which won't work as transactions doesn't have a user_id column) Or something like current_user.transactions.buyer?
Thanks!
(also the user model has_many :transactions)
All associations in Rails are one-way.
So for each belongs_to, you need to add a has_many or has_one on the other side.
I'd suggest you want something like this;
class User
has_many :transactions_as_buyer,
:class_name => "Transaction",
:foreign_key => :buyer_id
has_many :transactions_as_seller,
:class_name => "Transaction",
:foreign_key => :seller_id
end
Now you can say;
current_user.transactions_as_seller.map(&:buyer)
to get an array of buyers.
Of course, depending on your app you may have a better name than "transactions_as_buyer/seller"

Users model also need to be buyers

Noob here. I have a rails app that has users and postings. I need to add buyers, which are users.
Users can post things, but can also view others posts and then select to buy those posts.
I am not sure how to setup this in my posts or users model.
You can setup something like this,
You can add a field to posts, buyer_id
In Model Posts
belongs_to :user
belongs_to :buyer, :class => "User", :foreign_key => "buyer_id"
In Model User
has_many :posts
has_many :purchases, :class => "Post", :foreign_key => "buyer_id"
This is one way of doing things.

has_many :through issue with new records

I'm modeling "featuring" based on my plan in this question and have hit a bit of a stumbling block.
I'm defining a Song's primary and featured artists like this:
has_many :primary_artists, :through => :performances, :source => :artist,
:conditions => "performances.role = 'primary'"
has_many :featured_artists, :through => :performances, :source => :artist,
:conditions => "performances.role = 'featured'"
This works fine, except when I'm creating a new song, and I give it a primary_artist via new_song.performances.build(:artist => some_artist, :role => 'primary'), new_song.primary_artists doesn't work (since the performance I created isn't yet saved in the database).
What's the best approach here? I'm thinking of going with something like:
has_many :artists, :through => :performances
def primary_artists
performances.find_all{|p| p.role == 'primary'}.map(&:artist)
end
I think you're overcomplicating it. Just because things have similarities doesn't mean you should put them all in the same box.
class Song < ActiveRecord::Base
has_one :artist # This is your 'primary' artist
has_and_belongs_to_many :featured_artists, :source => :artist # And here you make a featured_artists_songs table for the simple HABTM join
validates_presence_of :artist
end
Poof, no more confusion. You still have to add song.artist before you can save, but that's what you wanted. Right?
There's not much that you can do about the association not being recognized until you save. Arguably, it doesn't really exist until you save, validations pass, and the relevant transaction(s) are completed.
Regarding your question of cleaning up your primary_artist method, you could model it something like this.
class Song < ActiveRecord::Base
has_many :performances
has_many :artists, :through => :performances
has_one :primary_artist, :through => :performances, :conditions => ["performances.roll = ?", "primary"], :source => :artist
end
It's unclear if you want one or many primary artists, but you can easily switch that has_one to has_many as needed.
You've nailed the source of your problem with build vs. create.
As for finding the primary artist of a song. I would add a named_scope on artist to select only featured/primary artists.
class Artist < ActiveRecord::Base
...
named\_scope :primary, :joins => :performances, :conditions => "performances.role = primary"
named\_scope :featured, :joins => :performances, :conditions => "performances.role = featured"
end
To get the primary artist for a song, you would do #song.artists.primary or if you prefer your primary_artists method in song.
def primary_artists
artists.primary
end
However, after looking at your initial question, I think your database layout is insufficient. It's workable but not clear, I've posted my suggestions there, where it belongs.
These named scopes I would also work under my proposed scheme as well.

How can I have two columns in one table point to the same column in another with ActiveRecord?

I run the risk of palm-to-forehead here, but I can't quite figure out how to do this with Rails' ActiveRecord sugar.
I have a tickets table that has two columns (submitter_id and assignee_id) that should each reference a different user from the users table (specifically the id column in the users table). I'd like to be able to do things like ticket.submitter.name and ticket.assignee.email using ActiveRecord's associations. Submitter and Assignee are simply user objects under different associative names.
The only thing I've found that comes close to what I am doing is using polymorphic associations, but in the end I'm fairly certain that it's not really what I need. I'm not going to have multiple types, both submitter and assignee will be users, and very well could be two different users.
Any help would be fantastic. Thanks!
class Ticket < ActiveRecord::Base
belongs_to :submitter, :class_name => "User"
belongs_to :assignee, :class_name => "User"
end
Should work.
Edit: Without trying it out, I'm not sure whether you need the :foreign_key parameter or not. My instinct is not, but it couldn't hurt.
Edit again: Sorry, left off the User -> Ticket associations. You didn't mention using them, and I typically will only add associations in one direction if I don't plan on using them in the other direction.
Anyway, try:
class User < ActiveRecord::Base
has_many :assigned_tickets, :class_name => "Ticket", :foreign_key => "assignee_id"
has_many :submitted_tickets, :class_name => "Ticket", :foreign_key => "submitter_id"
end
Something like this should work
class Ticket < ActiveRecord::Base
belongs_to :submitter, :class_name => 'User', :foreign_key => 'submitter_id'
belongs_to :assignee, :class_name => 'User', :foreign_key => 'assignee_id'
end
class User < ActiveRecord::Base
has_many :tickets, :class_name => 'Ticket', :foreign_key => 'submitter_id'
has_many :tickets_assigned, :class_name => 'Ticket', :foreign_key => 'assignee_id'
end
Yes, PreciousBodilyFluids is right we don't need to specify the foreign_key in the Ticket class as rails can infer it from the column name, i.e. submitter_id and assignee_id
But if your association name is different from the column_name_{id} then you will have to specify it, i.e. the User class case

Resources