Rails activity feed from a polymorphic following - ruby-on-rails

I'm tracking the following tables:
Story (id, user_id, content)
Vote (id, user_id, story_id)
Flag (id, user_id, story_id)
etc..
with an activity table:
Activity (id, user_id,action, trackable_id, trackable_type)
The relationship table:
Relationship (id, follower_id, followed_id)
I'm currently getting the activities from users a user is following like this:
def get_activity_from_followers(current_user)
followed_user_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
where("user_id IN (#{followed_user_ids})",
user_id: user.id)
end
My question is, how do i get the activities where the the trackable table(e.g. story,vote,flag) belongs to you.
So right now i'm getting things like:
"someone you are following" posted a story
"someone you are following" voted a story
i want to also get things like:
"someone you are not following" voted your story
"someone you are not following" flagged your story
How do I go about this? Thanks.

I suggest you have following class definitions and associations:
# User
class User :trackable
end
# Flag (id, user_id, story_id)
class Flag :trackable
end
# with an activity table:
# Activity (id, user_id,action, trackable_id, trackable_type)
class Activity true
end
# The relationship table:
# Relationship (id, follower_id, following_id)
class Relationship "User"
belongs_to :following, :class_name => "User"
end
Now, Lets find activities of the users to whom you follow:
# List activities of my followings
Activity.where(:user_id => current_user.followings)
Listing my activities
current_user.activities

To solve this, I'd search into Activity using votes and flags id's instead of user id's. Something like this:
def get_votes(current_user)
stories = current_user.stories
votes = Votes.where(story_id: stories.map {|s| s.id})
activity = Activity.where(action: 'vote', trackable_id: votes.map{|v| v.id})
end
I madre some assumptions. First that you can call user.stories, and then that in Activity model 'action' can be vote or flag and that trackable_id relates to Vote and Flag id. If that's not correct, you can get the general idea anyways.
Hope it helps

What about making Activity STI class?
class Story < ActiveRecord::Base
belongs_to :user
end
class Activity < ActiveRecord::Base
belongs_to :story
belongs_to :user
end
class Vote < Activity
def to_partial_path
'users/vote'
end
end
class Flag < Activity
def to_partial_path
'users/flag'
end
end
You can store all user actions in same table and get it from there with:
Activity.where(user_id: uid).all
and you get as return array of all user actions with correct typing for every action, that allow you to implement polymorphic logic for different types of actions (as to partial path in my example, that allows you to write something like:
render Activity.where(user_id: uid).all

Related

Inserting data into foreign key column

I have a user and group table. In user table I have id,name . In group table I have id group_name, created_by , user_id. Here a user can belong to more than one group but not the same group twice. A group can have many users but not the same user twice.
Here is my code.
group.rb
module Client
class Group < ActiveRecord::Base
has_many :users
end
end
user.rb
class User < ActiveRecord::Base
belongs_to :client_groups
end
Now when a user creates a new group, he should be automatically be persisted into the user_id and created_by field.
So as per the guides , I tried to follow the same with my code like this
#user.Client::Group.new(client_group_params)
but it gave me an err.
So I tried another way,
def create
#client_group = Client::Group.new(client_group_params)
client_group_params[:created_by] = current_user.id
client_group_params[:users_id] = current_user.id
#client_group.save
respond_with(#client_group)
end
def client_group_params
params.require(:client_group).permit(:group_name)
end
It saved the group_name into the db but it did not save the created_by and the users_id field. Any idea?
I have made the user_id field foreign key.
First, you need to change the assoication declaration as below:
class User < ActiveRecord::Base
belongs_to :client_group,
class_name: 'Client::Group',
foreign_key: :group_id
end
As per the association you have, the User model should have the column called group_id. Add it through migration. I am not seeing the point of adding the user_id to the Group model.
Then change the create action as :
def create
#client_group = current_user.build_client_group(
client_group_params.merge(created_by: current_user.id)
)
#client_group.save
respond_with(#client_group)
end

Getting information from the join table in a has many through query

Imagine 3 models: User, Club and UserClub.
class User < ActiveRecord::Base
has_many :clubs,
through: :user_clubs
has_many :user_clubs
end
class Club < ActiveRecord::Base
has_many :users,
through: :user_clubs
has_many :user_clubs
end
class UserClub < ActiveRecord::Base
belongs_to :user
belongs_to :club
end
Very typical join table stuff.
Now, imagine a scenario where you want to retrieve one user's clubs and the amount of users that are in each club.
In a controller, retrieving a users clubs is simple:
def index
#clubs = current_user.clubs
#do whatever you will with them
end
The second part puzzles me though as I don't know how to do it as efficiently as possible.
Sure, I could do something like this:
def index
#clubs = current_user.clubs
#no_of_users_per_club = Hash.new(0)
#clubs.each do |club|
#no_of_users_per_club[club.id] = UserClub.where(club_id: club.id).count
end
#Do whatever you would do after
end
Is there a better way to do this? It would be a tad redundant, but ultimately, maybe the best solution is to simply store that integer as an attribute of each network, so that when a user joins a club, I increment it by one and when a user leaves a club, I decrease it by one?
UPDATE: The selected answer below shows a very cool way to do it and an even cooler way to limit the results to just your clubs.
#no_of_user_per_club_of_mine = UserClub.
joins("INNER JOIN user_clubs AS uc ON user_clubs.club_id = uc.club_id").
where("uc.user_id = ?" , current_user.id).
group("user_clubs.club_id").
count("user_clubs.user_id")
You can use group to retrieve the count of users in each club directly from the join model 'TableClub'.
#no_of_users_per_club = UserClub.group(:club_id).count(:user_id)
# => {1=>1, 2=>5, 3=>8}
To get the number of users in each club, where these clubs are joined by current_user:
#no_of_user_per_club_of_mine = UserClub.joins("INNER JOIN user_clubs AS uc ON user_clubs.club_id = uc.club_id").where("uc.user_id = ?" , current_user.id).group("user_clubs.club_id").count("user_clubs.user_id")
#count = current_user.user_clubs.group_by{|o|o.club_id}.map{|k,v|[k, v.first.club.users.length]}
This will return a array of array which contain club_id at first index and count of users related to this club at second index
#count.length will return total clubs related to current user

Rails model relationship, has many and belongs to many?

I have the following structure:
class User < ActiveRecord::Base
end
class Item < ActiveRecord::Base
end
class Group < ActiveRecord::Base
end
A User can create (and thereby own) a Group.
A Group is a list of Items and a list of the Users who can access these items.
In other words, a user has a list of Items and can control which Users can see these items through the Group membership.
How should I set up the relationship?
Well, you're going to run into some issues with the fact that you want a double many-to-many relationship here. Ie. groups have and belong to many items, and items have and belong to many users.
So, I would setup the relationship this way, assuming you want a group to be able to have many items, and items may belong to more than one group:
User has_many :groups
User has_and_belongs_to_many :items
User has_many :own_items, :class_name => 'Item'
Group belongs_to :user
Group has_and_belongs_to_many :items
Item has_and_belongs_to_many :groups
Item has_and_belongs_to_many :users
Item belongs_to :owner, :class_name => 'User'
Your migrations will need to look like so:
# Group
:user_id, :integer
# Item
:owner_id, :integer
# GroupsItems
:group_id
:item_id
#ItemsUsers
:item_id
:user_id
Now, the structure you're looking at isn't the cleanest in the universe, but it will behave as you expect as long as you're careful about the user association.
For instance, to create a user's item:
#user = User.first
#user.own_items.create(...)
To assign users as able to view an item...
#item = Item.find(...) #or #user.own_items.find(...)
#item.users = [user1,user2,user3]
Now, this sets up the relationships you want, but you'll have to also write your own controller / view logic to limit access, or use a library like CanCan.
For instance:
# View
- if #item.users.include?(current_user)
...show item...
# Items Controller:
def show
#item = Item.find(params[:id])
if #item.users.include?(current_user)
...continue...
else
redirect_to :back, :alert => 'You are not authorized to view this item.'
end
end
I hope those examples point you in the right direction. You'll have a number of issues to deal with relating to access control, but trying to think of them and solve each one I can think of is beyond the scope of this question.
Also, note that this is the simplest setup I could think of. If you have more complex logic in the associations you might want to make a full-fledged join model and use has_many :through associations instead of HABTM.
Good luck!

multiple habtm relationships on one model

I am trying to figure out the best way to accomplish my problem. I've got a pages table, and a user_types table. I am trying to specify multiple user types on a page. They will act as permission groups. I need to do this twice however. Once for read permissions, and once for edit permissions. Here is an example:
Home page has 3 user types that can read it - admin, super admin, public
It has 2 user types that can edit it - admin, super admin
I have one user_types table:
admin
super admin
public
etc
I have created two mapping tables (one for read, and one for edit):
pages_user_read_types
pages_user_edit_types
they both have page_id, and user_type_id
Is there a better way to accomplish this? If this is the best way, I need help figuring out the relationships for the models. I have this for one relationship
has_and_belongs_to_many :user_types, :join_table => :pages_user_read_types
How do i specify two relationships for seperate fields?
Thanks
The HABTM relationship in Rails has seemed to fall out of favor over the last couple of years with Rails developers to the has_many :through relationship. The only time you should use HABTM is when you have no need for any additional information about the relationship between two models. In your case, you are trying to emulate this by creating two HABTM relationships when you could effectively accomplish by having a join model with a editable attribute.
In code, it would look something like this:
class Page < ActiveRecord::Base
has_many :page_permissions
has_many :user_types, :through => page_permissions
def editable_user_types
page_permissions.includes(:user_types).where(:editable => true).map(&:user_type)
end
def read_only_user_types
page_permissions.includes(:user_types).where(:editable => false).map(&:user_type)
end
end
class PagePermission < ActiveRecord::Base
belongs_to :page
belongs_to :user_type
# When you create this model, you should have a boolean attribute for editable
end
class UserType < ActiveRecord::Base
has_many :page_permissions
has_many :pages, :through => :page_permissions
end
I think following this approach will allow you to consolidate to one join table which will be better in the future if you need to add additional attributes to the relationship (PagePermission) between Page and UserType.
At the very least, you probably want to add a Permission model. If it ever gets more complicated than what you've described, I would also recommend using CanCan.
class Permission < ActiveRecord::Base
#table is id, page_id, user_type_id, and permission_type (string).
belongs_to :page
belongs_to :user_type
end
In your controller, you can construct a filter chain like this:
class PagesController < ApplicationController
before_filter :load_page
before_filter :authorize_view!, only: [ :show ]
before_filter :authorize_edit!, only: [ :edit ]
def show
end
def edit
end
private
def load_page
#page = Page.find(params[:id])
end
def authorize_view!
if !#page.permissions.where(user_type_id: current_user.user_type_id, permission_type: "view").exists?
flash[:notice] = "You do not have permission to view that page."
redirect to root_path
end
end
def authorize_edit!
if !#page.permissions.where(user_type_id: current_user.user_type_id, permission_type: "edit").exists?
flash[:notice] = "You do not have permission to edit that page."
redirect to root_path
end
end
end
(This assumes you have a current_user method in your app).

How should I get my model's instances to belong to all users in my user model?

I have the following setup and I want to ensure that all brands in my brand model belong to all users in my user model. I would also like to ensure that once a brand has been created, and it belongs to all users, it will also belong to future users that sign up down the line.
Brand model
has_many :brand_users
has_many :users, :through => :brand_users
after_create :associate_with_all_users
def associate_with_all_users
User.find(:all).each do |user|
users << user
end
save
end
BrandUser model
belongs_to :brand
belongs_to :user
User model
has_many :brand_users
has_many :brands, :through => :brand_users
When I try the following in the console, it shows that currently the last brand instance only belongs to a single user and not both (there are currently 2 users that exist).
>> User.all.count
=> 2
>>BrandUser.last.user_id
=>1 #Should not belong to just the one user but both
Your models look correct, you may be able to clean up your Brand association call to:
def associate_with_all_users
self.users = User.all
# save I don't believe this is necessary anymore if you assign the association
end
As for ensuring all newly created users receive all Brand's, you could do a
class User
after_create :associate_with_brands
def associate_with_brands
self.brands = Brand.all
end
end
or maybe look at an http://api.rubyonrails.org/classes/ActiveRecord/Observer.html
Your code should work, if you try Brand.first.users don't you get all of your users?
Either way, if every brand is associated with every user and viceversa, why don't you do try something like this:
def User > ActiveRecord::Base
def brands
Brand.all
end
end
def Brand > ActiveRecord::Base
def users
User.all
end
end

Resources