Rails ActiveRecord - Users behave as Team after migrations - ruby-on-rails

I have a users and a teams table. The users table has a column called team_id. But now the goal is to allow users to have multiple teams.
So I created the user_teams table using migrations like so:
class CreateUserteams < ActiveRecord::Migration[5.0]
def change
create_table :user_teams do |t|
t.integer :user_id
t.integer :team_id
t.timestamps
end
end
end
And my code is as follows:
models/user.rb
class User < ApplicationRecord
#belongs_to :team
# should now have multiple teams
has_many :user_teams
has_many :teams, through: :user_teams
models/team.rb
class Team < ApplicationRecord
has_many :members, foreign_key: "team_id", class_name: "User"
has_many :user_teams
has_many :users, through: :user_teams
models/user_teams.rb (new file)
class UserTeam < ApplicationRecord
belongs_to :user
belongs_to :team
end
(This works fine) To invite a user to join a team I send him an invite and if he accepts, I add him to the new users_team table using:
controllers/team_invites_controller.br
def accept
user = #team_invite.user
#team_invite.update(status: TeamInvite.statuses[:accepted])
user.team_id = #team_invite.team_id
user.company_id = #team_invite.team.company_id
user.teams << Team.find(user.team_id)
user.save(:validate => false)
respond_to do |format|
if user.shadow?
format.html {
redirect_to new_user_registration_path({
:email => user.email,
:role => user.main_role,
})
}
else
format.html { redirect_to dashboard_root_path, notice: 'Invite accepted' }
end
format.json { head :no_content }
end
end
The problem is, now "user" somehow became a Team and not a User, and it broke all of the code using "user.something". Also because of this I cannot access User methods because "user" is no longer a User but a Team.
For example, now:
<% if user.photo.exists? %>
gives:
undefined method `photo' for Team:0x00007f381dea4708
Or:
<% if current_user.teams.members.count > 1 %>
gives:
undefined method `members' for
Team::ActiveRecord_Associations_CollectionProxy:0x00007f3845954eb0 Did you mean? member?
Why does users.something now behaves as a team? I already re-did migrations and got the same error. Any idea how thatvhappened and how to fix it? How can I access users properties and methods correctly?
Thank you
EDIT
On
<% if user.photo.exists? %>
it's meant to display the image of another user and it gives the undefined method `photo' error. But if I use (although I don't want to)
<% if current_user.photo.exists? %>
It works. How come?

Your users have multiple teams and thus current_user.teams is not a Team anymore, it's a collection proxy, to access individual teams from there, depending on your logic you can use:
<% if current_user.teams.any?{|team| team.members.count > 1 } %>
(note that this code may be inefficient if there're many teams)

Related

Ruby on Rails || Following conferences

I'm trying to create an app in Ruby on Rails where I can:
Create conferences (working)
See a listing of conferences (working)
Follow and unfollow a conference (not working)
View a following conference (not working)
I've started working with Ruby on Rails back in December and used Micheal Hartl's tutorial to get started. In chapter 14 (https://www.railstutorial.org/book/following_users) Micheal introduces following and unfollowing users, trough relationships.
I'm trying to apply his techniques, but adjusting it to where you have a relation between one model and one controller, to where there are two models and two controllers.
One controller & model is the User_controller and the User Model, the other controller & model are the Conference_controller and the Conference Model.
I started by adding active relations to the User Model, since it's the party that's following the conferences
user.rb
has_many :active_relationships, class_name: "Relationship",
foreign_key: "follower_id",
dependent: :destroy
has_many :following, through: :active_relationships, source: :followed
I've done the opposite in the Conference Model, because i'ts the party thats being followed
Conference.rb
has_many :passive_relationships, class_name: "Relationship",
foreign_key: "followed_id",
dependent: :destroy
has_many :followers, through: :passive_relationships, source: :follower
To make the structure cleared I've added to following line of code to the Relationship model
Relationship.rb
belongs_to :follower, class_name: "User"
belongs_to :followed, class_name: "Conference"
validates :follower_id, presence: true
validates :followed_id, presence: true
When trying to see if the user is actually following the conference an error occurs in the User model stating:
ActiveRecord::RecordNotUnique in RelationshipsController#create
SQLite3::ConstraintException: UNIQUE constraint failed: relationships.follower_id, relationships.followed_id: INSERT INTO "relationships" ("follower_id", "followed_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)
The root of the problem lies in:
app/models/user.rb:116:in `follow'
app/controllers/relationships_controller.rb:6:in `create'
I understand that the problem occurs when a record cannot be inserted because it would violate a uniqueness constraint, but I don't know what uniqueness constraint is being violated.
Now the first problem happens in the user.rb, when an active_relationship is being created between user and conference.
# Returns true if the current user is following the other user.
def following?(other_conference)
following.include?(other_user)
end
# Follows a conference.
def follow(other_conference)
active_relationships.create(followed_id: other_conference.id)
end
# Unfollows a conference.
def unfollow(other_conference)
active_relationships.find_by(followed_id: other_conference.id).destroy
end
The second problem is in the Relationships_controller, where the current_user should follow the conference.
def create
#conference = Conference.find(params[:followed_id])
current_user.follow(#conference)
respond_to do |format|
format.html { redirect_to #conference }
format.js
end
end
Now i'm not sure what the cause of the problem is and how to solve it. I hope I've made my problem clear and what i'm trying to achieve. If not I would gladly give more information concerning my problem.
You're following an example that handles a more complex case (where you're joining the same table twice) and your solution is a bit more complicated than it needs to be:
class User
has_many :subscriptions
has_many :conferances, though: :subscriptions
def subscribed_to?(conference)
conferances.include?(conference)
end
def find_subscription(conference)
subscriptions.find_by(conference: conference)
end
end
class Conferance
has_many :subscriptions
has_many :users, though: :subscriptions
end
# Its better to name join models after an actual thing
class Subscription
belongs_to :user
belongs_to :conference
end
resources :conferences, shallow: true do
resource :subscriptions, only: [:create, :destroy]
end
class SubscriptionsController
before_action :set_conferance, only: :create
def create
if current_user.subsciptions.create(conferance: #conferance)
flash[:success] = "You are now subscribed to { #conferance.name }"
else
flash[:error] = "Could not create subscription."
end
redirect_to #conferance
end
def destroy
#subscription = current_user.subsciptions.find(params[:id])
if #subscription.destroy
flash[:success] = "You are no longer subscribed to { #conferance.name }"
else
flash[:error] = "Oh noes"
end
redirect_to #subscription.conferance
end
def set_conferance
#conferance = Conferance.find(params[:conferance_id])
end
end
<% if current_user.subscribed_to?(#conferance) %>
<%= button_to "Subscribe", conferance_subsciptions_path(#conferance), method: :post %>
<% else %>
<%= button_to "Unsubscribe", subscription_path(current_user.find_subscription(#conferance)), method: :delete %>
<% end %>

Rails 4 self referential many to many relationships

I can't wrap my head around this any help would be appreciated. I went through many articles and other postings on here and I can't seem to get the results I'm looking for.
I have a User model and Team model.
Team Model has user_id and team_id
The user who creates the team will be user_id and users who are members of the team will be in the team_id
User Model
has_many :teams, foreign_key: :team_id
has_many :team_members, class_name: "Team", foreign_key: :user_id
Team Model
belongs_to :user, foreign_key: :team_id
belongs_to :team_member, class_name: "User", foreign_key: :user_id
The end result of what I'm trying to achieve is:
Each user can add as many team members
Each user can see a list of users who are part of their team.
A view where, Users who are part of a team can see which team they are part of.
I believe what you're looking for is a join table model. The issue is that both a User and a Team may have many of each other. So the relationship must be stored separately.
See this answer here on a similar question: https://stackoverflow.com/a/15442583/5113832
So you might choose a model structure of User, Team and TeamMembership.
Updated to destroy dependent team memberships when a user or team is destroyed.
#app/models/user.rb
class User < ActiveRecord::Base
has_many :team_memberships, :dependent => :destroy
has_many :teams, :through => :team_memberships
end
#app/models/team.rb
class Team < ActiveRecord::Base
has_many :team_memberships, :dependent => :destroy
has_many :users, :through => :team_memberships
end
#app/models/team_membership.rb
class TeamMembership < ActiveRecord::Base
belongs_to :user
belongs_to :team
end
Updated to reply to question:
How would my controller look on create? (Adding a user to a team) – Michael
In order to add a user to to a team you COULD do it in UserController, you COULD do it in TeamController. However, because you are now creating a TeamMembership resource you would want to create a TeamMembership record in a TeamMembershipsController. This keeps with the "Rails" way of doing things. So for example:
# app/controllers/team_memberships_controller.rb
class TeamMembershipsController < ApplicationController
def index
#team_memberships = TeamMembership.all
end
def new
#team_membership = TeamMembership.new
end
def create
#team_membership = TeamMembership.new(team_membership_params)
if #team_membership.save
flash[:success] = 'Team membership created'
redirect_to team_memberships_path
else
flash[:error] = 'Team membership not created'
render :new
end
end
def destroy
#team_membership = TeamMembership.find_by_id(params[:id])
if #team_membership && #team_membership.destroy
flash[:success] = 'Team membership destroyed'
else
flash[:error] = 'Team membership not destroyed'
end
redirect_to team_memberships_path
end
private
def team_membership_params
params.require(:team_membership).permit(
:user_id,
:team_id
)
end
end
The advantage to having the TeamMembership resource is using this pattern to manage when a user is added (#create), or removed (#destroy) from a team.
The magic of Rails associations will take care of accessing a team's members (users) and a user's teams for each instance of those models.
You just go about your business doing CRUD on these resources and Rails takes care of the organization for you by your conforming to it's conventions.
Also I updated my original model code to destroy team memberships when a user or team is destroyed. This ensures no orphaned records are in your team_memberships table.
As a final note. You should also be able to easily use form_for to send a TeamMembership to your controller to be created. This could be done using select option dropdowns for users and teams with Rails' collection_select:
<%# app/views/team_memberships/new.html.erb %>
<h1>
Create Team Membership
</h1>
<%= form_for(#team_membership) do |f| %>
<%= f.label(:user) %>
<%= f.collection_select(
:user_id,
User.all,
:id,
:username
) %>
<%= f.label(:team) %>
<%= f.collection_select(
:team_id,
Team.all,
:id,
:name
) %>
<%= f.submit %>
<% end %>
The above code will render dropdown for all users and teams allowing you to select a specific combination to create a team membership from. Deleting a team membership is as easy as sending a DELETE #destroy request with the id of the team membership.
Another consideration might be adding a unique pair constraint to your database table and model within the migration and model validations.
Hope this helps!

Ruby on Rails relationship model

In Ruby on Rails 4, how do you create a many-to-many relationship inside a relationship model for a friends list such as Facebook using the has_many :through ... syntax ?? I'm a newbie and currently learning Ruby on Rails 4. I have looked at this link.
But still have a hard time grasping it.
you will need a join table that references both sides of the relations
let us say you have an relation Post and another relation Category with a many to many relationship between them you need a join table to be able to represent the relationship.
migration for a join table would be
class CreateCategoriesPosts < ActiveRecord::Migration
def change
create_table :categories_posts do |t|
t.integer :category_id
t.integer :post_id
t.timestamps
end
add_index :categories_posts, [:category_id, :post_id]
end
end
and in the models/post.rb
Class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
and in the models/category.rb
Class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
more here:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
I think #RAF pretty much nailed it. But to use the OP's example:
class User < ActiveRecord::Base
has_and_belongs_to_many :users_list
end
class UsersList < ActiveRecord::Base
has_and_belongs_to_many :users
end
Although at first it might seem like a User should have only one list of friends (UsersList), that might not always be the case. Think of types within the UserList model, such as: 'close friends', 'work friends', 'all friends' for example.
My advice: dig into the Rails guides. This is a concept worth learning and truly understanding (which I'm still doing :).
many-to_many relationships are a simple concept, but complex when using the database because of the way databases work. A person could have 1 to N different friends, which means that a single entry for a database would need a dynamic amount of memory for each entry, which in the db world is a no-no. So instead of creating a list of friends you would have to make a table that represents the links between friends, for example:
friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :friend, foreign_key: 'friend_A' # this entry has a field called 'friend_A'
belongs_to :friend, foreign_key: 'friend_B' # this entry has a field called 'friend_B'
end
These links will represent your network of friends. However, as the two previous answers have mentioned, Rails has some nifty magic, "has_and_belongs_to_many", which will do this for you.
NOTICE: The problem here is that in my StatusesController, in the index action, the #relationship object only gets the statuses of all your friends, but does not get your own statuses. Is there a better way of approaching this? I am trying to create a view to view all statuses of users that are your friends, and your own statuses too, and so far, I can't seem to figure out how to order it chronologically, even if in my status model, i included "default_scope -> { order(created_at: :desc) } ". Any advice would be deeply appreciated
class User < ActiveRecord::Base
has_many :relationships
has_many :friends, :through => :relationships
has_many :inverse_relationships, class_name: 'Relationship', foreign_key: 'friend_id'
has_many :inverse_friends, through: 'inverse_relationships', :source => :user end
#
class Relationship < ActiveRecord::Base
# before_save...
belongs_to :user
belongs_to :friend, class_name: 'User'
end
#
class RelationshipsController < ApplicationController
def friend_request
user_id = current_user.id
friend_id = params[:id]
if Relationship.where( user_id: user_id, friend_id: friend_id, accepted: false).blank?
Relationship.create(user_id: user_id, friend_id: friend_id, accepted: false)
redirect_to user_path(params[:id])
else
redirect_to user_path(params[:id])
end
end
def friend_request_accept
# accepting a friend request is done by the recipient of the friend request.
# thus the current user is identified by to_id.
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
if Relationship.exists?(relationship) and relationship.accepted == false
relationship.update_attributes(accepted: true)
end
redirect_to relationships_path
end
def friend_request_reject
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
relationship.destroy
redirect_to relationships_path
end
################################
def index
#relationships_pending = Relationship.where(friend_id: current_user.id, accepted: false)
end
end
#
class StatusesController < ApplicationController
def index
#status = Status.new
#relationship = Relationship.where('friend_id = ? OR user_id = ?', current_user.id, current_user.id).
where( accepted: true)
end
def new
#status = Status.new
end
end
#

requesting membership to a group mvc

I have a group model that has_many :members, and has_many :memberships. What I would like to do is make it so that in some groups the creator of the group would make it so that you have to request membership in order to join that specific group. How could I set this up in my rails application?
I have added a boolean field to the memberships ActiveRecord but I dont know how to set it up in a way that would allow me to join groups that dont require the "request a membership" function but also to create a "request a membership" function.
as of right now my models look like this:
class Group < ActiveRecord::Base
belongs_to :creator, :class_name => "User"
has_many :members, :through => :memberships
has_many :memberships, :foreign_key => "new_group_id"
has_many :events
end
class User < ActiveRecord::Base
has_many :groups, foreign_key: :creator_id
has_many :memberships, foreign_key: :member_id
has_many :new_groups, through: :memberships
end
class Membership < ActiveRecord::Base
belongs_to :member, class_name: "User"
belongs_to :new_group, class_name: "Group"
validates :new_group_id, uniqueness: {scope: :member_id}
has_many :accepted_memberships, -> {where(memberships: { approved: true}) }, :through => :memberships
has_many :pending_memberships, -> {where(memberships: { approved: false}) }, :through => :memberships
end
and my membership controller:
class MembershipsController < ApplicationController
def create
#group = Group.find(params[:new_group_id])
#membership = current_user.memberships.build(:new_group_id => params[:new_group_id])
if #membership.save
flash[:notice] = "Joined #{#group.name} "
else
flash[:notice] = "You're already in this group"
end
redirect_to groups_url
end
def destroy
#group = Group.find(params[:id])
#membership = current_user.memberships.find_by(params[membership_id: #group.id]).destroy
redirect_to groups_url
end
end
I believe that you are already very close to your solution, and that it is more of a business problem than a technical one. First I would add a boolean to the group to indicate that approval is required. e.g.
rails generate migration add_require_approval_to_groups require_approval:boolean
This would get set when the creator first creates the group depending upon the type of group that they have created.
Now, somehow a user has to be able to discover that there are groups that they can join, and you need to communicate an awareness to them that for some groups, membership is not automatic, but must be approved by the group creator.
So, assuming that you have communicated this to the user, and that they are on a page with a selection box listing all of the groups that they can become a member of (not necessarily the best design choice, but will do for this example). You need to have a query in your model that will gather all of the available groups that a user can still join.
def self.available_groups(user_id)
where("id not in (select group_id from group_members where user_id = ?)", user_id)
.select("id, name")
.collect{ |g| [g.name, g.id] }
end
In your controller:
#available_groups = Group.available_groups(#current_user)
And in your view:
<h2>Please select the group to join:</h2>
<p>
<%= form_tag :action => 'join_group' do %>
<%= select("group", "id",
#available_groups) %>
<%= submit_tag "Join" %>
<% end %>
</p>
Now, when you process the "post" in your membership_controller, you need to inform the creator that someone is trying to join the group that requires approval (perhaps a mailer). If the require_approval boolean is not set, then you need to automatically approve the user so that they can access the group immediately.

Devise User Review/Rating System in Rails 4

I'm trying to create a review system on users in rails. I want one user to be able to rate another user on their profile page in devise. I've tried a few different methods but I am fairly new to rails and haven't been able to accomplish this.
Right now I have default devise views but no user profile page. I'd like users to review a another user on 5 or so different issues.
Any help would be much appreciated!
In order to do that, you can use the association called has_many through association :
http://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
Your models should look like that "
class User < ActiveRecord::Base
has_many :rates
has_many :rated_users, through: :rates, class_name: "User", foreign_key: :rated_user_id # The users this user has rated
has_many :rated_by_users, through: :rates, class_name: "User", foreign_key: :rating_user_id # The users that have rated this client
end
class Rates < ActiveRecord::Base
belongs_to :rating_user, class_name: "User"
belongs_to :rated_user, class_name: "User"
end
And your migrations :
class createRates < ActiveRecord::Migration
def change
create_table :changes do |t|
t.belongs_to :rated_user
t.belongs_to :rating_user
t.integer :value
t.timestamps
end
end
end
Oxynum - great concept! After adding models and applying migrations, starts with templates. Starting point for you is a users_controller.rb. Probably, you already have a 'show' action inside UsersController. This action available for authenticated users.
Modify this action to smth like:
class UsersController < ApplicationController
before_filter :authenticate_user!
before_filter :load_ratable, :only => [:show, :update_rating]
def show
# Renders app/views/users/show.html.erb with user profile and rate controls
end
def update_rating
my_rate_value = params[:value] == 'up' ? +1 : -1
if #rated_by_me.blank?
Rate.create(rated_user: #userProfile, rating_user: #user, value: my_rate_value)
flash[:notice] = "You rated #{#userProfile.name}: #{params[:value]}"
else
flash[:notice] = "You already rated #{#userProfile.name}"
end
render action: 'show'
end
protected:
def load_ratable
#userProfile = User.find(params[:id]) # - is a viewed profile.
#user = current_user # - is you
#rated_by_me = Rate.where(rated_user: #userProfile, rating_user: #user)
end
end
Add to routes:
get 'users/update_rating/:value' => 'user#update_rating'
Start rails server, Log In, and try to change rating directly:
http://localhost:3000/users/update_rating/up

Resources