Friendship has_many through model with multiple status' - sql

Currently my User model has the following code:
has_many :notifications
has_many :friendships
has_many :friends, :through => :friendships, :conditions => { status: 'accepted' }
has_many :requested_friends, :through => :friendships, :source => :friend, :conditions => { status: 'requested' }
has_many :pending_friends, :through => :friendships, :source => :friend, :conditions => { status: 'pending' }
And my Friendship model is as follows:
belongs_to :user
belongs_to :friend, :class_name => "User"
def self.request(user, friend)
unless user == friend or Friendship.exists?(user_id: user, friend_id: friend)
transaction do
Friendship.create(:user => user, :friend => friend, :status => 'pending')
Friendship.create(:user => friend, :friend => user, :status => 'requested')
end
else
return "failed"
end
end
def self.accept(user, friend)
unless user == friend or Friendship.exists?(user_id: user, friend_id: friend)
transaction do
accepted_at = Time.now
accept_one_side(user, friend, accepted_at)
accept_one_side(friend, user, accepted_at)
end
else
return "failed"
end
end
def self.accept_one_side(user, friend, accepted_at)
request = find_by_user_id_and_friend_id(user, friend)
request.status = 'accepted'
request.accepted_at = accepted_at
request.save!
end
When I try to run my cucumber tests, however, I am getting this error:
SQLite3::SQLException: no such column: users.status: SELECT "users".* FROM "users" INNER JOIN "friendships" ON "users"."id" = "friendships"."friend_id" WHERE "users"."status" = 'accepted' AND "friendships"."user_id" = ? (ActionView::Template::Error)
I think that this means it is trying to only include inside in for example pending_friends, users which have the attribute status = "pending", where it should actually be including users who belong to friendships which have attribute status = "pending"
Is this right? How would I go about fixing this?

I can't find any documentation on use of :conditions with has_many, but based on the error that's being generated, I gather that the conditions specified are assumed to apply to the model that is subject of the has_many, not the target model or the model through which it is being reference.

I have updated to the following and this works:
has_many :notifications
has_many :friendships
has_many :accepted_friendships, :class_name => "Friendship", :conditions => {status: 'accepted'}
has_many :requested_friendships, :class_name => "Friendship", :conditions => {status: 'requested'}
has_many :pending_friendships, :class_name => "Friendship", :conditions => {status: 'pending'}
has_many :friends, :through => :accepted_friendships
has_many :requested_friends, :through => :requested_friendships, :source => :friend
has_many :pending_friends, :through => :pending_friendships, :source => :friend
If anyone has a different approach without having to create accepted_friendshis, requested_friendships, and pending_friendships, however, I would love to hear it!

status is a column for friendships table.
So when you are writing the code, then mention the table name or else it will take the table of the Current model.
has_many :friends, -> { where "friendships.status = 'accepted'" }, :through => :friendships

Related

rails has many through where query

My model structure is set up like this,
class Conversation < ActiveRecord::Base
has_many :conversation_statuses, :dependent => :destroy
has_many :users, :through => :conversation_statuses
has_many :messages, :inverse_of => :conversation
accepts_nested_attributes_for :messages
end
class User < ActiveRecord::Base
has_many :conversation_statuses, :dependent => :destroy
has_many :conversations, :through => :conversation_statuses
has_many :messages, :inverse_of => :user
end
class ConversationStatus < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
end
I am trying to create a conversation only if the users don't have a conversation going. I have conversation set up so that more than 2 users can have a conversation. Right now my query is finding conversations including the user ids, but that may include conversations with large groups. My controller query looks like this,
class ConversationsController < ApplicationController
before_filter :set_user_ids_param, :only => :create
def create
#conversation = Conversation.joins(:users)
.where(:users => {:id => params[:conversation][:user_ids]})
.first_or_initialize
#conversation.attributes = conversation_params
if #conversation.save
render :json => { :html => render_new_conversation_form }
return
end
render :status => :bad_request, :json => {
:html => render_conversation_form
}
end
private
def conversation_params
params.require(:conversation).permit([
:user_ids => [],
:messages_attributes => [
:content,
:topic
]
])
end
def set_user_ids_param
return if params[:conversation].blank?
return if params[:conversation].blank? || params[:conversation][:user_ids].blank?
params[:conversation][:user_ids] = params[:conversation][:user_ids].split(',')
params[:conversation][:user_ids].push(current_user.id)
end
def render_new_conversation_form
render_to_string({
:partial => 'conversations/form',
:locals => {
:conversation => #conversation
}
})
end
def render_conversation_form
render_to_string({
:partial => 'conversations/form',
:locals => {
:conversation => #conversation
}
})
end
end
and my form looks like this
= simple_form_for conversation, :html => {:class => 'conversation-form'} do |form|
= form.input :user_ids, :as => :hidden, :input_html => {:class => 'user-ids'}
= form.simple_fields_for :messages do |message_fields|
= render 'messages/fields', :message_fields => message_fields
%button.button.radius.submit.no-margin
Send Message
%button.button.secondary.radius.cancel.no-margin
Cancel
Any ideas on the best way to handle this?
The method that will resolve your problem should be in class ConversationStatus, which will give you the conversation_id of the Conversation, if it exists.
You'll need two sets:
containing_all_users are the conversations that contain all the users in users_id
with_extra_users are the conversations that have more users than those in users_id
The conversation (if exists), is the result of the subtraction of those sets.
class ConversationStatus < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
def self.find_between(user_ids)
containing_all_users = group(:conversation_id)
.where(user_id: user_ids)
.having(['COUNT(user_id) = ?', user_ids.length])
.pluck(:conversation_id)
with_extra_users = group(:conversation_id)
.having(['COUNT(user_id) > ?', user_ids.length])
.pluck(:conversation_id)
(containing_all_users - with_extra_users).first
end
end
You'll get the conversation from the Conversation model. You should set the attributes on this method too:
class Conversation < ActiveRecord::Base
has_many :conversation_statuses, dependent: :destroy
has_many :users, through: :conversation_statuses
def self.find_between(user_ids, attributes = {})
conversation_id = ConversationStatus.find_between(user_ids)
Conversation.where(id: conversation_id).first_or_initialize(attributes)
end
end
On the controller you will call it like this:
#conversation = Conversation.find_between(params[:conversation][:user_ids],
conversation_params)

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.

A FactoryGirl factory for a many-to-many self-referenced model

I've got a self-referenced user model:
class User < ActiveRecord::Base
has_and_belongs_to_many :following,
:class_name => "User",
:foreign_key => 'follower_id',
:association_foreign_key => 'following_id',
:join_table => 'followers'
has_and_belongs_to_many :followers,
:class_name => "User",
:foreign_key => 'following_id',
:association_foreign_key => 'follower_id',
:join_table => 'followers'
...
end
I'm having trouble coming up with a FactoryGirl factory to test this behavior. I've tried some variations of
factory :user do
...
factory :following do
after(:create) do |user, evaluator|
create_list(:user, evaluator.followers_count, follower: user)
end
end
end
but had no luck with it. How would you do this? Thanks for any input.

Rails 3 has_many through scope with joins returns unexpected results

I am trying to create scopes to find all Galleries by a specific category type, like "Style". Eventually, they will be chained to filter by multiple category types, but I can't get the first to work.
Here are the models:
Gallery:
has_many :gallery_categories, :class_name => "GalleryCategories", :dependent => :destroy
has_many :categories, :through => gallery_categories
has_many :colors, :through => gallery_categories, :source => :category, :conditions => {:type => "Color"}
has_many :styles, :through => gallery_categories, :source => :category, :conditions => {:type => "Style"}
...and many more types of categories...
Category:
:has_many :gallery_categories
:has_many :galleries, :through => :gallery_categories
GalleryCategories:
:belongs_to :gallery
:belongs_to :category
I am trying to do something like this in Gallery:
:scope :by_style, lambda {|style| joins(:styles).where(:category => {:name => style})}
Then, for example, I run
Gallery.by_style("Contemporary")
And I am returned 181 Galleries when there are only 40 Galleries, and in this example there should only be one returned with the style "Contemporary".
Here is the resulting SQL:
SELECT `galleries`.* FROM `galleries` INNER JOIN `gallery_categories` ON `galleries`.`id` = `gallery_categories`.`gallery_id` INNER JOIN `categories` ON `categories`.`type` = 'Style' WHERE `categories`.`name` = 'Contemporary'
Any ideas? Thanks in advance.

multiple joins using activerecord in rails

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"} )