Users list with follow/unfollow button - ruby-on-rails-3

I have an Users list, where I need to put follow/unfollow button for each user when I'm signed in.
There will be some other places where I want to use same method to follow topic/group and etc. So I think it would be best to write an association to avoid additional queries every time I need these buttons.
My user_followings table structure
| id | user_id | recipient_id |
| 1 | 1 | 2 |
| 2 | 3 | 2 |
| 3 | 10 | 3 |
So here user with id=1 follows user => 2...
So bassicaly there is has_many association type, because user is following many other users. But I came up with an idea to add has_one association type, where I will put an additional condition recipient_id=current_user.id so only one record will be left and it shows following status.
If I succeed making this association, there will be super easy to check following status via user.following.nil? in view with no additional queries, caching and etc.
I have made this once before in my CakePHP app, the SQL query looks like:
ON (`UserFollowing`.`recipient_id` = `User`.`id` AND `UserFollowing`.`user_id` = 1)
But CakePHP lets to bind relations on the fly, so I added current_user.id from the session directly to association conditions.
I can't do this in Rails, because I can't put current_user.id in model to describe conditions:
user.rb
has_one :following, :class_name => 'UserFollowing', :conditions => {:user_id => current_user.id}
Method Nb. 2
Maybe I can solve this using :through, but I can't find any example to get results from the same users table from which I'm making the association.
Method Nb. 3
Do it using scope Rails 3 devise, current_user is not accessible in a Model ?
Not sure, which way is the best...

although it's not a direct answer to your question: consider the gem: acts_as_follower. I used it for a similar situation and it seems to fit your needs. It's possible to let anything follow anything else - check it out...

Please read Chapter 11 for this question specifically. Read the whole book for A-Z about building a twitter like app.
Ruby On Rails Tutorial

Well, I have found the way to do this with scopes:
# user.rb
scope :with_relations, lambda { |current_user_id|
has_one :user_following, :foreign_key => 'recipient_id', :conditions => {:user_id => current_user_id}
where(:status => 1)
}
# users_controller.rb
#users = User.with_relations(current_user.id).limit(30).order('last_action desc')

Related

RAILS: How to query if there's already a team formed by an array of users

I'm working on an rails API, more specifically in a create operation.
The workflow that I have is this, I have two rails applications, one is an API and the other is an interface. The API manages the different backend operations in order to handle data, store data, and respond in json format to the interface. The interface serves as the frontend, just making http requests to the API in order to display the information.
In my API I have the 3 model listed below:
class Team < ApplicationRecord
has_many :team_users
has_many :users, through: :team_users
end
class User <ApplicationRecord
has_many :team_users
has_many :teams, through: :team_users
end
class TeamUser < ApplicationRecord
belongs_to :user
belongs_to :team
end
Basically I'm sending an array of user ids from the Interface to the API and I would like to make a query to find out if there's already a team formed by the users (user ids) that I've passed to the API.
I have already tried to do this:
Team.joins(:team_users).where('team_users.user_id' => [3,5])
The problem with this query is that it returns every team that contains the users with id that are equal to 3 or 5. The correct result would be to return a team that has the users 3 and 5 or 5 and 3 as their team members.
Thanks in advance.
Update
The original business rule is this, have an aplication that keeps track of trees, so I have a model named tree and when we create a tree whe must say what team created this tree. So I used a multi select user dropdown field with select2 js library that is how I'm passing the user_ids to the API. So the basic idea is to check is theres already a team composed only by the users passed to the API, if there is already a team I use it's id and say that the tree was registered by that team, if there insn't a team with coposed only buy the users I create a new team and reference it's Id to the tree.
You can approach the problem in different ways. Scrolling over each Team record to check if it contains the associated user_ids is pretty straightforward but inefficient:
team.user_ids.sort == user_ids.sort
But we can make it performant by reversing the process, i.e. iterating over the user_ids to find out corresponding teams, taking their intersection of Team ids and finally checking if any team_id holds those user_ids. This line will return true if there's already a team formed by the users (user ids):
user_ids.map { |id| User.find(id).team_ids }.reduce(:&).present?
You may include it in the User class scope like below:
class User < ApplicationRecord
...
def self.team_exists_for?(user_ids)
# return early
# what should it return when user_ids is empty
# return ??? if user_ids.blank?
# what should it return when any of the id is absent from :users table
# set_of_all_user_ids = Set.new(User.pluck(:id))
# set_of_user_ids = Set.new(user_ids)
# return ??? unless set_of_user_ids.subset? set_of_all_user_ids
# finally
user_ids.map { |id| User.find(id).team_ids }.reduce(:&).present?
end
...
end
Update
So you want to find the team which only has those users provided by user_ids or create a team with them and assign back to the Tree model instance. Combining both approaches described above and defining a scope in the Team model itself seems like a better solution.
class Team < ApplicationRecord
...
def self.find_or_create_for(user_ids)
# find all team_ids which contain user_ids (inclusive)
team_ids = user_ids.map { |id| User.find(id).team_ids }.reduce(:&).flatten
if team_ids.present? # search for the team which ONLY has 'user_ids'
team_id = team_ids.find { |id| Team.find(id).user_ids.sort == user_ids.sort }
end
return Team.find(team_id) if team_id
# or create a team with user_ids and return
team = Team.create! # create a team with required attributes
team.user_ids = user_ids
team
end
...
end
i have implemented this as
add a field key: string on Team and in Team model
class Team < ApplicationRecord
has_many :team_users
has_many :users, through: :team_users
#callback
before_validation :update_key
def update_key
self.key = Team.key_for_users(self.users)
end
def self.key_for_users(users)
users.sort.map(&:id).join("_")
end
end
so basically after this callback whenever you will create a team there will be a key
for example: -
users = [3,5]
then key in Team will be 3_5
or users = [5,3]
then key in Team will be 5_3
From this we can easily get the result what you wanted
example: -
user_ids = [3,5]
[14] pry(main)> user_ids_simple = user_ids.join('_')
=> "3_5"
[15] pry(main)> user_ids_reverse = user_ids.reverse.join('_')
=> "5_3"
and query will be like this: -
Team.where("key IN (?)",[user_ids_simple, user_ids_reverse])
it may be helpful for you. thanks

Rails sort collection by association count

I'm working in Rails 4, and have two relevant models:
Account Model
has_many :agent_recalls, primary_key: "id", :foreign_key => "pickup_agent_id", class_name: "Booking"
Hence, queries like Account.find(10).agent_recalls would work.
What I want to do is sort the entire Account collection by this agent_recalls association.
Ideally it'd look something like (but obviously not):
#agents = Account.where(agent: true).order(:agent_recalls)
Question: What's the correct query to output an ordered list, by this agent_recall count?
Well to accomplish what you are looking for you have 2 options:
first, only a query, but it will implied a join, so there will be lost the Accounts that doesn't have any agent_recalls, so i will discard this option
second, i think this one is more appropriate for what you are trying to do
Account.find(:all, :conditions => { :agent => true }, :include => :agent_recalls).sort_by {|a| a. agent_recalls.size}
As you can see is a mix between a query and ruby, hope it helps :)

Rails Active Record Query for Double Nested Joins with a Select Call

I have the following schema:
Photos has many Groups has many Users.
I am using this Rails server as a backend to an iOS application and constantly need to push out notifications to all involved users of a group when a photo is added.
I problem is finding the least expensive query to resolve only the User.ids affected.
So far I have
Photo.find(1).groups.joins(:users)
I know that I have to put a select argument after this, but can't figure out the syntax for the life of me.
Given a photo, I am looking for the most efficient way to find a collection of the affected user id's.
Any help would be much appreciated!!!
In your Photo model, you can have another associations called users
has_many :group_users, :through => :groups, :source => :users
Then you can find the users by the following code
#photo = Photo.includes([:group_users]).where("photos.id = ?", 1).first
#affected_users = []
#photo.group_users.map {|user| #affected_users << user.id}
Now the #affected_users contains all the user ids.
users_id = []
Group.where(photo_id: 1).users.collect{|u| users_id << u.id}
puts users_id
As Photo has_many groups, so, groups belongs_to photo and groups table has photo_id as foreign key .
So, please try this.

Rails: complex search on 3 models, return only newest - how to do this?

I'm trying to add an advanced search option to my app in which the user can search for certain links based on attributes from 3 different models.
My app is set up so that a User has_many :websites, Website has_many :links, and Link has_many :stats
I know how create SQL queries with joins or includes etc in Rails but I'm getting stuck since I only want to retrieve the latest stat for each link and not all of them - and I don't know the most efficient way to do this.
So for example, let's say a user has 2 websites, each with 10 links, and each link has 100 stats, that's 2,022 objects total, but I only want to search through 42 objects (only 1 stat per link).
Once I get only those 42 objects in a database query I can add .where("attribute like ?", user_input) and return the correct links.
Update
I've tried adding the following to my Link model:
has_many :stats, dependent: :destroy
has_many :one_stat, class_name: "Stat", order: "id ASC", limit: 1
But this doesn't seem to work, for example if I do:
#links = Link.includes(:one_stat).all
#links.each do |l|
puts l.one_stat.size
end
Instead of getting 1, 1, 1... I get the number of all the stats: 125, 40, 76....
Can I use the limit option to get the results I want or does it not work that way?
2nd Update
I've updated my code according to Erez's advice, but still not working properly:
has_one :latest_stat, class_name: "Stat", order: "id ASC"
#links = Link.includes(:latest_stat)
#links.each do |l|
puts l.latest_stat.indexed
end
=> true
=> true
=> true
=> false
=> true
=> true
=> true
Link.includes(:latest_stat).where("stats.indexed = ?", false).count
=> 6
Link.includes(:latest_stat).where("stats.indexed = ?", true).count
=> 7
It should return 1 and 6, but it's still checking all the stats rather than the latest only.
Sometimes, you gotta break through the AR abstraction and get your SQL on. Just a tiny bit.
Let's assume you have really simple relationships: Website has_many :links, and Link belongs_to :website and has_many :stats, and Stat belongs_to :link. No denormalization anywhere. Now, you want to build a query that finds, all of their links, and, for each link, the latest stat, but only for stats with some property (or it could be websites with some property or links with some property).
Untested, but something like:
Website
.includes(:links => :stats)
.where("stats.indexed" => true)
.where("stats.id = (select max(stats2.id)
from stats stats2 where stats2.link_id = links.id)")
That last bit subselects stats that are part of each link and finds the max id. It then filters out stats (from the join at the top) that don't match that max id. The query returns websites, which each have some number of links, and each link has just one stat in its stats collection.
Some extra info
I originally wrote this answer in terms of window functions, which turned out to be overkill, but I think I should cover it here anyway, since, well, fun. You'll note that the aggregate function trick we used above only works because we're determining which stat to use based on its ID, which exactly the property we need to filter the stats from the join by. But let's say you wanted only the first stat as ranked by some criteria other than ID, such as as, say, number_of_clicks; that trick won't work anymore because the aggregation loses track of the IDs. That's where window functions come in.
Again, totally untested:
Website
.includes(:links => :stats)
.where("stats.indexed" => true)
.where(
"(stats.id, 1) in (
select id, row_number()
over (partition by stats2.id order by stats2.number_of_clicks DESC)
from stat stats2 where stats2.link_id = links.id
)"
)
That last where subselects stats that match each link and order them by number_of_clicks ascending, then the in part matches it to a stat from the join. Note that window queries aren't portable to other database platforms. You could also use this technique to solve the original problem you posed (just swap stats2.id for stats2.number_of_clicks); it could conceivably perform better, and is advocated by this blog post.
I'd try this:
has_one :latest_stat, class_name: "Stat", order: "id ASC"
#links = Link.includes(:latest_stat)
#links.each do |l|
puts l.latest_stat
end
Note you can't print latest_stat.size since it is the stat object itself and not a relation.
Is this what you're looking for?
#user.websites.map { |site| site.links.map { |link| link.stats.last } }.flatten
For a given user, this will return an array with that contains the last stats for the links on that users website.

Related Object Query Rails 3

I'm developing a blog site for practice. My problem is I can't call data from one object to another related object.
Here are my model associations:
1 class Post < ActiveRecord::Base
2 belongs_to :user
3 end
1 class User < ActiveRecord::Base
2 has_many :posts
3 end
In the console:
user = User.find(1)
user.posts // Everything works! It shows me a list of posts related to the user.
user.post(1) //This doesn't work! Is it wrong?
I've been looking over active record query interface guide at rubyonrails.org and still can't find anything in reference to this. Maybe I missed something?
Thanks
Do it this way:
user.posts[0] #=> returns the user's first post