multiple joins using activerecord in rails - sql

I'm building a small twitter style microblogging service where users can follow other users and get a feed of their messages
I have the following models:
class Follow < ActiveRecord::Base
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
class User < ActiveRecord::Base
has_many :follows, :foreign_key => 'follower_id',
:class_name => 'Follow'
has_many :followers, :through => :follows
has_many :followed, :foreign_key => 'followee_id',
:class_name => 'Follow'
has_many :followees, :through => :followed
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
To get a feed for the current user, I want to perform the following SQL query:
SELECT * FROM follows JOIN users JOIN messages WHERE follows.follower_id = current_user.id AND follows.followee_id = users.id AND users.id = messages.user_id;
What is the correct ActiveRecord way of doing this?

Not sure what you're looking for, but here is my suggestion:
I assume that you have other purposes for that Follow class, otherwise I don't see the purpose of it.
The "correct way" (i.e. my completely subjective way) to do it would actually be something like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :followers, :foreign_key => 'followed_id',
:class_name => 'User', :association_foreign_key => 'follower_id',
:include => [:messages]
has_and_belongs_to_many :follows, :foreign_key => 'follower_id',
:class_name => 'User', :association_foreign_key => 'followed_id'
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
Then create the following table:
create_table :users_users, :id => false do |t|
t.integer :followed_id
t.integer :follower_id
end
And you're set:
followed = User.find :first
follower = User.find :last
followed.followers << follower
followed.followers.first.messages
followed.followers.first.followers.first.messages # etc...
But from what I make it, you want to show all the messages from all the followers at the same time.
This should be possible to achieve by adding
has_and_belongs_to_many :followed_messages, :foreign_key => 'follower_id',
:class_name => 'Message', :association_foreign_key => 'followed_id'
to the User class, but I don't know how correct that way would be. Or it might be possible to achieve with association extensions but there I can't really give any examples.
Update:
By changing the :class_name, it will associate it with the Message.id, didn't think about that so it will not be correct in this way.
So the only "nice" option is to go through the User class like in the first example.
The only other options I can see is either the association extensions (which I can't give you an example for) or perhaps using a finder statement.
has_many :followed_messages, :class_name => 'Message',
:finder_sql => 'select * from messages where user_id in(select followed_id from users_users where follower_id = #{id})'
You probably have to customize that sql statement to get everything to work, but at least you should get the picture :)

Keijro's arrangement would work better, though if you need the Follow table, then you can execute the SQL query you specified as follows:
Follow.all(:joins => { :messages, :users }, :conditions => { "follows.follower_id" => current_user.id, "follows.followee_id" => "users.id", "users.id" => "messages.user_id"} )

Related

find_each and finder_sql not playing well with each other?

find_each doesn't seem to be playing nice with an ActiveRecord relation that uses finder_sql. Has anyone dealt with this before? For example:
class User < ActiveRecord::Base
has_many :games, :dependent => :destroy,
:finder_sql =>
proc {"SELECT * FROM games WHERE player_0_id = #{id} OR player_1_id = #{id}"}
end
class Game < ActiveRecord::Base
belongs_to :player_0, :class_name => "User", :foreign_key => "player_0_id", :inverse_of => :games
belongs_to :player_1, :class_name => "User", :foreign_key => "player_1_id", :inverse_of => :games
end
Calling #user.games.each works fine and iterates over all the games we care about. But calling #user.games.find_each results in:
ActiveRecord::StatementInvalid:
SQLite3::SQLException: no such column: games.user_id: SELECT "games".* FROM "games" WHERE "games"."user_id" = 2 AND ("games"."id" >= 0) ORDER BY "games"."id" ASC LIMIT 1000
Have a look at http://compositekeys.rubyforge.org/ to solve the given problem.

How should I model horse pedigrees in rails?

I need to model up to 5 or 6 generations horse pedigrees using rails/activerecord. I did my research here on stack and on the web and ultimately utilized this article as the basis of my approach. Here's what I've come up with.
Two models:
Horse has the following attributes id and horse_name
Pedigree has: id, parent_id and horse_id.
And the following associations:
has_many :parent_horse_relationships, :class_name => "Pedigree", :foreign_key => :horse_id, :dependent => :destroy
has_one :sire_horse_relationship, :class_name => "Pedigree", :foreign_key => :horse_id, :conditions => "horse_gender = 'Male'
has_one :dam_horse_relationship, :class_name => "Pedigree", :foreign_key => :horse_id, :conditions => "horse_gender = 'Female'
has_many :parents, :through => :parent_horse_relationships, :source => :parent
has_one :sire, :through => :sire_horse_relationship,:source => :parent
has_one :dam, :through => :dam_horse_relationship,:source => :parent
has_many :horse_parent_relationships, :class_name => "Pedigree", :foreign_key => :parent_id, :dependent => :destroy
has_many :progenies, :through => :horse_parent_relationships, :source => :horse
This approach is close, however it appears my condition to determine the dam or sire is being applied to the Horse and not the parent. Therefore if the particular horse is Male, the horse.sire will work, but the horse.dam will not and vice versa. Once I get basic functionality working I'd like to add additional methods to get the whole pedigree, grandparents, siblings, descendants, etc.
Questions:
How can I apply the gender condition to the parents and not the horse so that both sire and dam work.
Is the approach that I have take viable or is there a more elegant, efficient way of accomplishing this.
Any other suggestions or guidance would be appreciated.
Apologies for the long question and thanks for your help.
I might start with:
has_one :sire, :class_name => "Pedigree", :foreign_key => :horse_id, :conditions => "horse_gender = 'Male'
has_one :dam, :class_name => "Pedigree", :foreign_key => :horse_id, :conditions => "horse_gender = 'Female'
has_many :parent_horse_relationships, :class_name => "Pedigree", :foreign_key => :horse_id, :dependent => :destroy
has_many :parents, :through => :parent_horse_relationships, :source => :parent
has_many :progenies, :through => :horse_parent_relationships, :source => :horse
I ended up spending a great deal of time on this one, but finally came up with a solution that met my requirements. The associations that ultimately worked follow:
has_many :parent_horse_relationships, :class_name => "Pedigree", :foreign_key => :horse_id, :dependent => :destroy
has_many :parents, :through => :parent_horse_relationships, :source => :parent do
def dam_relationship
owner = self.proxy_association.owner
owner = owner.parents.where(:horse_gender => "Female")
where('pedigrees.parent_id = ?', owner)
end
def sire_relationship
owner = self.proxy_association.owner
owner = owner.parents.where(:horse_gender => "Male")
where('pedigrees.parent_id = ?', owner)
end
end
def dam
parents.dam_relationship
end
def sire
parents.sire_relationship
end
Question responses:
I applied the gender condition through use of an association_proxy and a simple wrapper. I created a dam_relationship and corresponding sire_relationship and then wrapped those methods in a couple of dam and sire wrapper methods.
def dam_relationship
owner = self.proxy_association.owner
owner = owner.parents.where(:horse_gender => "Female")
where('pedigrees.parent_id = ?', owner)
end
def dam
parents.dam_relationship
end
This allows me to do:
#horse.parents, #horse.dam, #horse.sire (not displayed)
as well as most of the methods included in the ancestry gem mentioned below. With a little bit of recursion it's fairly straight forward to display the entire pedigree or the number of generations that interest you.
I decided that the approach of having two models (Horse and Pedigree) provide som additional flexibility compared to having the sire_id and dam_id directly in the Horse model. This approach will enable me to more easily create methods like #horse.uncle, #horse.aunt. I believe these would be more difficult with the sire_id and dam_id directly in the Horse model.
The most popular gem for accomplishing this seems to be ancestry. The author accomplishes this and a lot more simply by adding an ancestry column to the model of interest. Its a very nice solution a definitely worth checking out.

How to get single attribute from array in a has_many :through association (User/Friend model)

In my User model I have the following:
class User < ActiveRecord::Base
has_many :friendships, dependent: :destroy
has_many :friends, :class_name => "User", :foreign_key => "friend_id"
has_many :pending_friends,
:through => :friendships,
:conditions => "status = 'pending'",
:foreign_key => "user_id",
:source => :friend
has_many :requested_friends,
:through => :friendships,
:source => :friend,
:conditions => "status = 'requested'"
def friends
direct_friends | inverse_friends
end
In my Friendship model I have the following:
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
In my view I have created an array of each of the users' friends, shown below. This code works and populates the array with all the data from the friends' "User" database model attributes.
User.first.friends
However, I want to be able to call the users' friend's name's. So, for example, shouldn't I be able to do something like this?
User.first.friends.map(&:name)
How do I get an array containing just the friend's name's, instead of all the friend's user attributes? I would also appreciate if anyone could tell me why .first is used (I got that from here: Rails calling User record from Friends model), as it doesn't just get the first instance of the User's friends (it gets all the instances). And why does just doing:
User.friends
return an empty array?
Try method pluck:
User.first.friends.pluck(:name)
You should use first method to retrieve one object from table. The User is table with a lot of users.

Rails ActiveRecord builds wrong foreign key if there are two belongs_to relationships to same model

Using Rails 3.2.3, I have User and Message models. Each message is owned by a user, and each message has an optional from_user field that also takes a user.id.
app/models/user.rb
class User < ActiveRecord::Base
has_many :messages, :foreign_key => "owner_id", :inverse_of => :owner
has_many :messages, :foreign_key => "from_user_id", :inverse_of => :from_user
end
app/models/message.rb
class Message < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :inverse_of => :messages
validates :owner, :presence => true # Every message must have an owner_id
belongs_to :from_user, :class_name => "User", :inverse_of => :messages
end
The problem I'm seeing is with the .build method. The main reason to use .build is to instantiate a (possibly protected) foreign key, right? (See the Rails Guide on Active Record associations: "the link through their foreign key will be created.") However when I run
#message = #user.messages.build(<accessible attributes>)
I find that it is filling in the optional from_user and not the mandatory owner.
Is there some way to control which foreign key .build fills in? Or do I need to just use .new and assign all foreign keys manually?
#message = Message.new(<accessible attributes>)
#message.owner = #user
#message.from_user = #another_user
ActiveRecord doesnt like that you have 2 associations with the same name. You're going to have to change the association names. This means that you will also have to supply the class_name attibute. Maybe something like:
has_many :owner_messages, :class_name => 'Message', :foreign_key => "owner_id", :inverse_of => :owner
has_many :user_messages, :class_name => 'Message', :foreign_key => "from_user_id", :inverse_of => :from_user

How to write an AREL statement on a self-referencing table

I have written quite a few AREL statements, but I'm tying myself in knots over this one. Here is my situation:
class Product < AR::Base
has_many :parents, :class_name => "ProductLink", :foreign_key => :to_product_id
has_many :children, :class_name => "ProductLink", :foreign_key => :from_product_id
# has an attribute called "identifier"
end
class ProductLink < AR::Base
belongs_to :parent, :class_name => "Product", :foreign_key => :from_product_id
belongs_to :child, :class_name => "Product", :foreign_key => :to_product_id
end
I want to retrieve all of the Products that have a child product with an identifier that matches some value.
I have twisted myself into a pretzel with this, seems easy, but I have been looking at it for too long now. I appreciate any help!
Got it!
brand.products.joins(:children => :child).where(:children => { :child => { :searchable_identifier.matches => "2136" } } )
That works great. See the hashed joins? That's what was throwing me off.