Ruby on Rails || Following conferences - ruby-on-rails

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 %>

Related

Rails ActiveRecord - Users behave as Team after migrations

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)

Rails 5.1, delete multiple records with conditions

I try to delete several records in my DB according to a few conditions. Before I talk about my problem I will explain how my app is working.
A User can create groups and links. This User own the groups he created same for the links. Other Users can then join this group by providing a token (which is created automatically when the group is created) via a Member model. Where member has the user_id and the group_id in the DB. Then once the User is part of a group he can share the links he created in the groups via Grouplink model. Where grouplink has groupd_id and link_id.
I managed to code the fact that if there is no member in a group the group is destroyed. But how do I manage to remove the links a user shared in the group if he leaves the group ? (when the user is destroyed automatically all the links are deleted so I think that the sharing should be gone as well, I have to try that tho'). When a User leaves the group I destroy his member record and he is gone but the links remain. I display the shared links, the fact that you can kick users (member destroy) and the list of the members of a group in the group show btw.
I thought about a few things. I write down what I did in the console
g = Group.find(66)
u = g.users.find(1)
u.links
and that give me all the links from the user in the group. Next to that
g.grouplinks
would give me all the shared links in the group.
g.grouplinks.map(&:link_id) returns [16, 17, 14, 13, 15]
u.links.map(&:id) returns [13, 15]
Now what can I do here ? My user is leaving the group. The member record is destroyed and how can I destroy those grouplinks according to the links the users has ?
Is there a magic trick I don't know yet ?
Thanks for your help.
EDIT
class User < ApplicationRecord
has_secure_password
has_many :links, dependent: :destroy
has_many :grouplinks, dependent: :destroy
has_many :members, :dependent => :destroy
has_many :groups, :through => :members
has_one :owned_group, foreign_key: "owner_id", class_name: "Group"
end
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
class Link < ApplicationRecord
has_many :grouplinks, :dependent => :destroy
belongs_to :user
end
class Grouplink < ApplicationRecord
belongs_to :group
belongs_to :link
end
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
has_many :grouplinks, :dependent => :destroy
def to_param
auth_token
end
end
I thought that actually I could add the user_id in the grouplinks so I could delete_all according to the user_id in the links and in the groupslinks. Not sure how to that tho' and don't know if there is a better solution.
EDIT 2
I tried your solution within the models. Actually it is smart and I didn't think about that...
Problem is now with the creation of my grouplink (share the link). I had this :
def create
user = current_user if current_user
group = user.groups.find_by(auth_token: params[:auth_token])
share = group.id
group_link = group.grouplinks.build(link_id: params[:link_id])
gl = group.grouplinks
if gl.where(group_id: share).where(link_id: params[:link_id]).exists?
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path
else
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render 'new'
end
end
end
And this is obviously not working anymore and I have this error when I try to share a link : First argument in form cannot contain nil or be empty <%= form_for #grouplink do |f| %>.
I tried to change it like this :
def create
group = Group.find_by(auth_token: params[:auth_token])
share = group.id
group_link = group.grouplinks.build(link_id: params[:link_id])
gl = group.grouplinks
if gl.where(group_id: share).where(link_id: params[:link_id]).exists?
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path
else
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render 'new'
end
end
end
But it is not working either
How about:
class Member < ApplicationRecord
belongs_to :user
belongs_to :group
has_many :group_links, dependent: :destroy
validates :user_id, :presence => true
validates :group_id, :presence => true
validates :user_id, :uniqueness => {:scope => [:user_id, :group_id]}
end
and
class Grouplink < ApplicationRecord
belongs_to :link
belongs_to :member
end
Now, when a Member record is destroyed (i.e., the user is kicked out of or leaves the group), any links shared with the group (i.e., group_links) are also destroyed. But, if the user has shared the link in another group, the link will continue to be shared with the other groups.
As mentioned by #Pablo in the comments, you probably also want to do:
class Group < ApplicationRecord
has_secure_token :auth_token
has_many :members, :dependent => :destroy
has_many :grouplinks, through: :members
has_many :users, through: :members, source: :user
belongs_to :owner, class_name: "User"
has_many :links, through: :grouplinks
def to_param
auth_token
end
end
Which will allow you to do:
group.grouplinks
I also agree with #amr-el-bakry that Member is a bit confusing. I suggest GroupUser as it makes it quite clear that it is an association between Group and User.
Also, I think it might be a bit more conventional to say GroupLink instead of Grouplink. Or, if you want to stick with naming based on associated classes, perhaps MemberLink. If you change Member to GroupUser, then perhaps GroupUserLink.
I'm thinking your create code should probably look something like:
def create
if group
if member
if link
unless group_link
#group_link = member.group_links.build(link: link)
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render :new
end
else
flash[:error] = "You shared this link in '#{group.name}' already."
else
flash[:error] = "That link does not exist."
redirect_to somewhere #fix this
end
else
flash[:error] = "You must be a member of this group to add a link."
redirect_to somewhere #fix this
end
else
flash[:error] = "There is no group with that token."
redirect_to somewhere #fix this
end
end
private
def group
#group ||= Group.find_by(auth_token: params[:auth_token])
end
def member
#member ||= current_user.members.where(group: group)
end
def link
#link ||= Link.find_by(id: params[:link_id])
end
def group_link
#group_link ||= member.group_links.where(link: link)
end
You may be able to write this as:
def create
flash[:error] = "There is no group with that token."
redirect_to somewhere unless group
flash[:error] = "You must be a member of this group to add a link."
redirect_to somewhere unless member
flash[:error] = "That link does not exist."
redirect_to somewhere unless link
flash[:error] = "You shared this link in '#{group.name}' already."
redirect_to mylinks_path if group_link
flash[:error] = nil
#group_link = member.group_links.build(link: link)
if group_link.save
group_link.toggle!(:shared)
flash[:success] = "You shared your link in '#{group.name}'."
redirect_to mylinks_path
else
render :new
end
end
But I can't remember if those redirects will give you heartache.
What you're looking for is dependent :delete_all
In your Group Model, you want to you should have a line like:
has_many :links, dependent :delete_all
This says, the group has many links and if you destroy the group, destroy all the related links.
In your Member model, you could use an after_destroy callback to destroy all user links in the group after membership record is destroyed:
class Member < ApplicationRecord
after_destroy do |record|
record.user.links.each { |link| link.grouplinks.where(group_id: record.group.id).destroy_all }
end
belongs_to :user
belongs_to :group
...
end
Also, I suggest you change Member to Membership to be more clear.
I think the best idea is that the group_links belong to a member and a link (and not a group and a link). And a member (not a user) has many group_links. When your destroy the member, it will destroy the group_links.
EDIT
This is what jvillian suggested in his answer, just before I did. So I believe his answer is the right one (with some minor enhancements I suggested in comments that jvillian will surely accept and add :-).
EDIT2
Regarding the problem you faced after applying jvillian suggestion, when creating a new grouplink, it must be done from a member (not a group). So in the create action you must search the member (by user_id and group_id) and create the grouplink as member.grouplinks.build

Rails 5.1.: destroy records in "has_many: through" association with restriction

I have classic has_many: through relationship:
class UserGroup < ApplicationRecord
has_many :user_groups_users
has_many :users, through: :user_groups_users
end
class UserGroupsUser < ApplicationRecord
belongs_to :user_group
belongs_to :user
end
class User < ApplicationRecord
has_many :user_groups_users
has_many :user_groups, through: :user_groups_users
end
and in order to destroy UserGroup record, I need to destroy appropriate records in UserGroupsUser, which both is part of a gem. Otherwise I will get back error that there are Users tied to UserGroups and I cannot destroy particular UserGroup.
At the moment in my Controller I have this:
def destroy
#user_group = UserGroup.find(params[:id])
UserGroupsUser.where(user_group_id: #user_group).destroy_all
respond_to do |format|
if #user_group.destroy
format.js { flash.now[:notice] = "User group #{#user_group.name} deleted!" }
format.html { redirect_to user_groups_url }
format.json { head :no_content }
else
format.js { flash[:danger] = "User group #{#user_group.name} cannot be deleted because
#{#user_group.users.size} users belong to it" }
end
end
end
however when I click Delete button in View, it destroys a record before I accept that in my modal window. How do I make it do destroy action, after accept in view, please? As I undestand it would require that after accept, it would firs destroy records in through models and then UserGroup.
My "Delete" action in View is quite regular:
<%= link_to 'Delete', user_group, method: :delete, remote: true,
data: { confirm: "Do you confirm deleting #{user_group.name}?" }, class: 'btn-danger btn btn-xs' %>
To simplify the whole thing, you could just add a before_destroy callback to your UserGroup. It'll only execute when you run #user_group.destroy
class UserGroup < ApplicationRecord
has_many :user_groups_users
has_many :users, through: :user_groups_users
before_destroy do
user_groups_users.destroy_all
end
end
Read ActiveRecord Callbacks
Just change has_many :user_groups_users to has_many :user_groups_users, :dependent => :destroy
See more at Association Basics.
Edit: You said it was in a gem. Not an issue, still! Find the class, and add this in an initializer (I know, I know, there are better places, but for the sake of moving on from this):
Whatever::To::UserGroupThing.class_eval do
has_many :user_group_users, :dependent => :destroy
end
But maintenance may not be your friend here if there's some sort of change to the association made down the line by the maintainer.
You could also use a before_destroy hook in user_group.rb
before_destroy do
UserGroupUser.where(:user_group => self).destroy_all
end

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

Add record to a model upon create used in many models

I have a survey and I would like to add participants to a Participant model whenever a user answers to a question for the first time. The survey is a bit special because it has many functions to answer questions such as Tag words, Multiple choices and Open Question and each function is actually a model that has its own records. Also I only want the Participant to be saved once.
The Participant model is fairly simple:
class Participant < ActiveRecord::Base
belongs_to :survey
attr_accessible :survey_id, :user_id
end
The Survey model is also straightforward:
class Survey < ActiveRecord::Base
...
has_many :participants, :through => :users
has_many :rating_questions, :dependent => :destroy
has_many :open_questions, :dependent => :destroy
has_many :tag_questions, :dependent => :destroy
belongs_to :account
belongs_to :user
accepts_nested_attributes_for :open_questions
accepts_nested_attributes_for :rating_questions
accepts_nested_attributes_for :tag_questions
...
end
Then you have models such as rating_answers that belong to a rating_question, open_answers that belong to open_questions and so on.
So initially I thought for within my model rating_answers I could add after_create callback to add_participant
like this:
class RatingAnswer < ActiveRecord::Base
belongs_to :rating_question
after_create :add_participant
...
protected
def add_participant
#participant = Participant.where(:user_id => current_user.id, :survey_id => Survey.find(params[:survey_id]))
if #participant.nil?
Participant.create!(:user_id => current_user.id, :survey_id => Survey.find(params[:survey_id]))
end
end
end
In this case, I didn't know how to find the survey_id, so I tried using the params but I don't think that is the right way to do it. regardles it returned this error
NameError (undefined local variable or method `current_user' for #<RatingAnswer:0x0000010325ef00>):
app/models/rating_answer.rb:25:in `add_participant'
app/controllers/rating_answers_controller.rb:12:in `create'
Another idea I had was to create instead a module Participants.rb that I could use in each controllers
module Participants
def add_participant
#participant = Participant.where(:user_id => current_user.id, :survey_id => Survey.find(params[:survey_id]))
if #participant.nil?
Participant.create!(:user_id => current_user.id, :survey_id => Survey.find(params[:survey_id]))
end
end
end
and in the controller
class RatingAnswersController < ApplicationController
include Participants
def create
#rating_question = RatingQuestion.find_by_id(params[:rating_question_id])
#rating_answer = RatingAnswer.new(params[:rating_answer])
#survey = Survey.find(params[:survey_id])
if #rating_answer.save
add_participant
respond_to do |format|
format.js
end
end
end
end
And I got a routing error
ActionController::RoutingError (uninitialized constant RatingAnswersController::Participants):
I can understand this error, because I don't have a controller for participants with a create method and its routes resources
I am not sure what is the proper way to add a record to a model from a nested model and what is the cleaner approach.
Ideas are most welcome!
current_user is a helper that's accessible in views/controller alone. You need to pass it as a parameter into the model. Else, it ain't accessible in the models. May be, this should help.
In the end I ended up using the after_create callback but instead of fetching the data from the params, I used the associations. Also if #participant.nil? didn't work for some reason.
class RatingAnswer < ActiveRecord::Base
belongs_to :rating_question
after_create :add_participant
...
protected
def add_participant
#participant = Participant.where(:user_id => self.user.id, :survey_id => self.rating_question.survey.id)
unless #participant.any?
#new_participant = Participant.create(:user_id => self.user.id, :survey_id => self.survey.rating_question.id)
end
end
end
The cool thing with associations is if you have deeply nested associations for instead
Survey has_many questions
Question has_many answers
Answer has_many responses
in order to fetch the survey id from within the responses model you can do
self.answer.question.survey.id
very nifty!

Resources