I'm using the PublicActivity gem. I'm trying to eager load the associated activities for a group of users. I've set up the 'activist' association per their documentation in my user class, which defines this:
def activist
has_many :activities_as_owner,
:class_name => "::PublicActivity::Activity",
:as => :owner
has_many :activities_as_recipient,
:class_name => "::PublicActivity::Activity",
:as => :recipient
end
My query in the controller is:
#results = #action_plan.users.includes(:activities_as_owner).where(activities: {recipient_id: #action_plan.id, key: "action_plan_role.access_granted" }).references(:activities_as_owner)
This does eager load the activities, but it does not include a user if they don't have a corresponding activity, which is not what I want. According to ActiveRecord guides, including the '.references(:activities_as_owner)' should make it an OUTER join instead INNER, so it returns all the users regardless of whether or not they have an activity. In place of ':activities_as_owner', I've tried :activities, :activist, :all, etc, but nothing I can think of does the trick.
Squeel or AREL answers are more than welcome as well!
Related
Ok so have created 2 models User and Following. Where User has a username attribute and Following has 2 attributes which are User associations: user_id, following_user_id. I have set up these associations in the respective models and all works good.
class User < ActiveRecord::Base
has_many :followings, dependent: :destroy
has_many :followers, :class_name => 'Following', :foreign_key => 'following_user_id', dependent: :destroy
end
class Following < ActiveRecord::Base
belongs_to :user
belongs_to :following_user, :class_name => 'User', :foreign_key => 'following_user_id'
end
Now I need to order the results when doing an ActiveRecord query by the username. I can achieve this easily for the straight-up User association (user_id) with the following code which will return to me a list of Followings ordered by the username of the association belonging to user_id:
Following.where(:user_id => 47).includes(:user).order("users.username ASC")
The problem is I cannot achieve the same result for ordering by the other association (following_user_id). I have added the association to the .includes call but i get an error because active record is looking for the association on a table titled following_users
Following.where(:user_id => 47).includes(:user => :followers).order("following_users.username ASC")
I have tried changing the association name in the .order call to names I set up in the user model as followers, followings but none work, it still is looking for a table with those titles. I have also tried user.username, but this will order based off the other association such as in the first example.
How can I order ActiveRecord results by following_user.username?
That is because there is no following_users table in your SQL query.
You will need to manually join it like so:
Following.
joins("
INNER JOIN users AS following_users ON
following_users.id = followings.following_user_id
").
where(user_id: 47). # use "followings.user_id" if necessary
includes(user: :followers).
order("following_users.username ASC")
To fetch Following rows that don't have a following_user_id, simply use an OUTER JOIN.
Alternatively, you can do this in Ruby rather than SQL, if you can afford the speed and memory cost:
Following.
where(user_id: 47). # use "followings.user_id" if necessary
includes(:following_user, {user: :followers}).
sort_by{ |f| f.following_user.try(:username).to_s }
Just FYI: That try is in case of a missing following_user and the to_s is to ensure that strings are compared for sorting. Otherwise, nil when compared with a String will crash.
I am running Ruby on Rails 3.1. I would like to eager loading "second degree" associated objects by applying some conditions, but I am in trouble.
It seems that I already solved part of my issue by using:
article_categories =
article
.categories
.includes(:comments => [:category_relationships])
.where(:category_relationships => {:user_id => #current_user.id})
where involved classes are stated as the following:
class Category < ActiveRecord::Base
has_many :comment_relationships
has_many :comments,
:through => :comment_relationships
...
end
class Comment < ActiveRecord::Base
has_many :category_relationships
has_many :categories,
:through => :category_relationships
...
end
The above code (it seems to do it right):
loads all categories by caring the has_many :through :category_relationships association (that is, by caring the .where(:category_relationships => {:user_id => #current_user.id}) condition);
eager loads all article.comments.where(:user_id => #current_user.id).
However, I would like to make some more:
to order retrieved categories by a :position attribute present in category_relationships so that the resulting article_categories are ordered by position;
to eager load also category_relationship objects where user_id == #current_user.id since the above code doesn't make that.
How can I make that by taking advantage from the eager loading?
The solution:
.order("category_relationships.position")
Imagine eager loading is cartessian product with some filtering so "where" is filtering the end result of include (left join really). But it can be done with where with subquery which first will filter categories by user then your where can be removed.
I am stumped on how to handle this situation using a self referencing HABTM relation, cancan, and ActiveRecord.
I am trying to use accessible_by to determine a set of videos that are visible given a relationship between videos and channels, but the resulting SQL has the wrong table name for part of the query. Here are the relationships:
# video.rb
class Video < ActiveRecord::Base
belongs_to :channel
end
# channel.rb
class Channel < ActiveRecord::Base
has_many :videos
has_and_belongs_to_many :subchannels, :class_name => "Channel", :join_table => "channels_channels", :foreign_key => :parent_id, :association_foreign_key => :subchannel_id
has_and_belongs_to_many :parent_channels, :class_name => "Channel", :join_table => "channels_channels", :foreign_key => :subchannel_id, :association_foreign_key => :parent_id
end
# The appropriate channels_channels table exists with subchannel_id and parent_id fields.
I need a cancan ability to find all public videos that are in public sub-channels of the default channel. I tried the following:
# ability.rb
can :read, Video, :permission => 'public', :channel => {:permission => 'public', :parent_channels => {:name => "Default"}}
In the console, when I try this out, I get the following:
> Video.accessible_by(Ability.new(nil))
SELECT "videos".* FROM "videos" INNER JOIN "channels" ON "channels"."id" = "videos"."channel_id" INNER JOIN "channels_channels" ON "channels_channels"."subchannel_id" = "channels"."id" INNER JOIN "channels" "parent_channels_channels" ON "parent_channels_channels"."id" = "channels_channels"."parent_id" WHERE "videos"."permission" = 'public' AND "channels"."permission" = 'public' AND "channels"."name" = 'Default'
=> []
I have some records in place that should result in a video, and, more to the point, would have expected the end of the query to have "parent_channels_channels"."name" = 'Default', but the table is "channels" in stead, resulting in the wrong dataset since the SQL is referencing the name of the channel, not the parent channel.
I tried changing the ability as follows to force the table name:
can :read, Video, :permission => 'public', :channel => {:permission => 'public', :parent_channels => {"parent_channels_channels.name" => "Default"}}
That results in the correct query, and accessible_by returns the right dataset, but then can?(:read, video) for a video in that result set results in a runtime error.
NoMethodError: undefined method `parent_channels_channels.name' for #<Channel:0x007fef35593748>
How do I specify this ability so it works with both can? and accessible_by?
It's not clear to me from your code whether you are modelling this correctly. Do you really want parent_channels and subchannels to be a HABTM relationship, or even be the same model? Maybe some real-world examples from your app would help me here but it seems like what you want is a parent VideoCategory, and you want HABTM relationships with Channels, which then each have a has_many relationship with videos.
You could then have VideoCategory having a has_many :videos, :through => :channels relationship.
class VideoCategory
has_and_belongs_to_many :channels
has_many :videos, :through => :channels
end
class Video
belongs_to :channel
end
class Channel
has_many :videos
has_and_belongs_to_many :video_cateogories
end
It seems like you are trying to make the Channel class do more than be a channel. The concept of parents and children doesn't make a lot of sense in habtm relationships. Generally if you want a parent class and sub classes, then you want a 1-to-many relationship. Many-to-many implies that neither association is the parent class (although this may be different than how you present it in the UI, of course).
If you really must do this as self-referential, have you tried using scopes instead?
class Video
scope :public, lambda do
joins(:channel).where("videos.permission = 'public' and channels.permission = 'public'")
end
end
It seems that putting the logic into scopes and out of ability.rb might help you better control exactly what SQL gets written -- it removes the layer of abstraction that CanCan is doing.
I am trying to build a relationship model between users. A user can either initiate a relation, or receive a relation from another user. Therefore, the relations table in the db has the foreign keys "initiator_id" and "recipient_id".
Now, I can figure what relations the user initiated or received using the following associations:
has_many :initiated_relations, :foreign_key => :initiator_id, :class_name => 'Relation', :dependent => :destroy
has_many :received_relations, :foreign_key => :recipient_id, :class_name => 'Relation', :dependent => :destroy
What I am trying to do, is build an association that will fetch me all relations that belong to a user (either initiated or received). Trying the following does not work, and complains about the lack of "user_id" field:
has_many :relations, :conditions => 'recipient_id = #{id} or initiator_id = #{id}'
How can I create an association that is solely based on the conditions field, without looking for the default foreign_key? Or is there perhaps a completely different approach to solving this?
From your comments to #neutrino's answer I understand, that you only need this "relation" for read only operations. If you're on Rails 3 you can utilize the fact, that it uses lazy fetching. The where() method returns ActiveRecord::Relation object, which you can later modify. So you can define a method like this:
def User < ActiveRecord::Base
def all_relations
Relation.where("initiator_id => ? OR recipient_id = ?", id, id)
end
end
And then you can do:
User.all_relations.where(:confirmed => true).all
Well, I can think of using finder_sql for that:
has_many :relations, :finder_sql => 'select * from relations right outer join users
on relations.recipient_id = #{id} or relations.initiator_id = #{id}'
Apart from that, you can just write a method that will return a united array of the two relations associations', but you will lose the advantage of an association interface (phew).
Perhaps someone will come up with a better solution.
I'm working on an application that models friendships between users.
class User
has_many :friendships
has_many :friends,
:through => :friendships,
:conditions => "status = #{Friendship::FULL}"
end
class Friendship
belongs_to :user
belongs_to :friend, :class_name => "User", :foreign_key => "friend_id"
end
When two users become friends, two instances of the friendship class are created, one for each direction of the friendship. This is necessary because different permissions can be set in each direction, and also for ease of lookups so we always search by the user_id without creating a second index and needing to double up searches.
Is it possible, using either find or a named scope to pull up a friend along with the counter-friendship? I'd like to do this because I'm allowing users to edit friendship permissions which are stored in their own leg of the friendship, and thus when I'm displaying a friend's info to the user I need to check the opposite leg to see how permissions are set. (As an aside, does this seem like a logical way to do things? Either you store into the opposite side, or you have to consult the opposite side before display, both kind of unpleasant.)
I've managed to whip up a batch of SQL that does the job:
def self.secure_friends(user_id)
User.find_by_sql("SELECT u.*, cf.permissions
FROM users u
INNER JOIN friendships f ON u.id = f.friend_id
INNER JOIN friendships cf ON u.id = cf.user_id AND cf.friend_id = #{user_id}
WHERE ((f.user_id = #{user_id}) AND (f.status = #{Friendship::FULL}))")
end
The function returns all of a user's friends names and ids, along with the permissions. However, this really doesn't seem ideal, and means I have to access the permissions by calling friend.permissions, where permissions isn't actually a member of friend but is just merged in as an attribute by find_by_sql. All in all, I'd really rather be doing this with a named_scope or a find call, but unfortunately, every attempt I've made to use that approach has resulted in errors as Rails chokes on having the same Friendship join table getting joined to the query twice.
I'm not sure how much you want to do, and how much you'd allow a plugin to do -- but I've used this has_many_friends plugin with great success: has_many_friends plugin at Github
You can look at the source and be inspired... Just need a Friendship model that has things like:
belongs_to :friendshipped_by_me, :foreign_key => "user_id", :class_name => "User"
belongs_to :friendshipped_for_me, :foreign_key => "friend_id", :class_name => "User"
and then later, in your user class, have:
has_many :friends_by_me,
:through => :friendships_by_me,
:source => :friendshipped_for_me
Or, just
/app/models/user
has_many_friends
Later:
#current_user.friends.each{|friend| friend.annoy!}