Undefined method 'about' for nill class - ruby-on-rails

I have two table: User has one Account, Account belongs_to User. When I want to get some value from field 'about' (table Accounts), I have a problem 'undefined method about' for nil:NilClass'. The challenge is to get the list of followers and gain their avatar or information 'about' from another table and output it in View.
My method in controller
def list_of_follower
#followers_id = Follow.select("follower_id ").where("followable_id = ?", current_user)
#followers = User.where("id in (?)", #followers_id)
#followables_id = Follow.select("followable_id").where("follower_id = ?", current_user)
#followables = User.where("id in (?)", #followables_id)
end
View list_of_follower.html.haml
%h1 My followers
- #followers.each do |f|
%ul.list-group
%li.list-group-item
%p=f.name
%p=f.account.about
%h1 I'm follower
- #followables.each do |followable|
%ul.list-group
%li.list-group-item
%p=followable.name
%p=followable.account.about
Create_Accounts.rb
class CreateAccounts < ActiveRecord::Migration
def change
create_table :accounts do |t|
t.belongs_to :user, index: true
t.text :about
t.timestamps null: false
end
end
end
User.rb
class User < ActiveRecord::Base
acts_as_followable
acts_as_follower
acts_as_liker
has_one :account
has_many :posts
has_many :comments
accepts_nested_attributes_for :account
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def email_required?
false
end
def email_changed?
false
end
validates :login, :email, uniqueness: true
end
Account.rb
class Account < ActiveRecord::Base
belongs_to :user
mount_uploader :avatar, AvatarUploader
end
table User content is displayed without problems (work requests), but the contents of the table associated with it does not displayed, what is the problem?

I think, a problem is not every user has an account.
You can try this:
%p=f.account.try(:about)

Related

Rails ActiveModel designing belongs_to and has_many with single class

I need some help modeling my models and controller. Here is what I want to achieve:
I want to have a devise user named User (as usual) and a second model named Project. A Project should belong to a single User and at the same time should have many participants. The participants in a project should also be users (with devise registration/login) but the user, that created the project should not be able to participate.
So far, so good. Here comes the tricky part: In my controller I want to be able to write:
def participate
p = Project.find(id: params[:id])
p.participants << current_user unless p.participants.includes?(current_user) && !p.user_id.equal(current_user.id)
if p.save
redirect_back
else
render :project
end
end
This doesn't work because p.participants is not an array and the query (I tried it in rails console) does not check my n:m table.
Here is my current model setup:
class Project < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
belongs_to :user
has_and_belongs_to_many :participants, class_name: "User"
end
class User < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_and_belongs_to_many :projects
end
Finally my migrations:
class CreateProjects < ActiveRecord::Migration[6.0]
def change
create_table :projects, id: false do |t|
t.string :id, limit: 36, primary_key: true
t.string :title
t.belongs_to :user, index: true, foreign_key: true, type: :uuid
t.datetime :published_at
t.timestamps
end
end
end
class CreateJoinTableProjectsUsers < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :projects do |t|
t.index :project_id
t.index :user_id
end
end
end
It is better to use has_many: through instead of has_and_belongs_to_many. This allows you to write cleaner code for validation.
Remove has_and_belongs_to_many from User and Project models
Add has_many :through to User and Project models
rails g model UserProject user:references project:references
rails db:migrate
class User < ApplicationRecord
..
has_many :user_projects
has_many :projects, through: :user_projects
..
end
class Project < ApplicationRecord
..
has_many :user_projects
has_many :participants, through: :user_projects, source: 'user'
..
end
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
end
Add validation to UserProject model
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
validate :check_participant
private
def check_participant
return if project.participants.pluck(:id).exclude?(user.id) && project.user != user
errors.add(:base, 'You cannot be participant')
end
end
Update participate method
def participate
p = Project.find(id: params[:id])
begin
p.participants << current_user
redirect_back
rescue ActiveRecord::RecordInvalid => invalid
puts invalid.record.errors
render :project
end
end

Rails poll users with different vote weight for each user

I am creating a poll app. I am modifying this https://www.sitepoint.com/polling-users-rails/ to my needs.
Users answer polls and results are shown.
polls
t.string :question
t.text :description
t.references :division, foreign_key: true
t.date :open_date
t.date :close_date
vote_options
t.string :title
t.references :poll, foreign_key: true
votes
t.references :user, foreign_key: true
t.references :vote_option, foreign_key: true
users
t.string :email
t.decimal :vote_weight
user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :votes, dependent: :destroy
has_many :vote_options, through: :votes
def voted_for?(poll)
vote_options.any? {|v| v.poll == poll }
end
end
vote_option.rb
class VoteOption < ApplicationRecord
belongs_to :poll
validates :question, presence: true
has_many :users,
has_many :votes, dependent: :destroy
def get_vote_count
VoteOption.joins(:votes).joins(:users).where(id: self.id).sum(:vote_weight)
end
end
vote.rb
class Vote < ApplicationRecord
belongs_to :user
belongs_to :vote_option
end
poll.helper
def visualize_votes_for(option)
content_tag :div, class: 'progress' do
content_tag :div, class: 'progress-bar',
style: "width: #{option.poll.normalized_votes_for(option)}%" do
"#{option.votes.count}"
end
visualize_votes_for shows total votes for each option. At the moment it considers 1 for each value and counts the total for each option.
I would like instead to be able to set a vote_weight for each user so that instead of 1 will be counted the value specified in vote_weight column in users table.
I have tried:
"#{sum(option.votes.user.vote_weight)}"
but it returns:
undefined method `user' for #<ActiveRecord::Associations::CollectionProxy []>
What am I doing wrong?
option.votes will return an active record collection of votes. Note that it will be a collection, not a single object. So, invoking method user on a collection will not work as a vote belongs to a user. So user method can be invoked only on an instance of vote object, not on collection.
You can make a method get_vote_count in VoteOption Model
def get_vote_count
Vote.joins(:vote_option).joins(:user).where("vote_options.id = #{self.id}").sum(:vote_weight)` # Adjust singularity/plurality of objects as per the requirement
end
And use this method in view dierctly on the option object like option.get_vote_count.

Creating a Conversation between 3 users in a rails chat app

Currently, it requires recipient_id and sender_id to start a Conversation between 2 users.
How would the associations work if I wanted to allow a 3rd user to join a Conversation?
When a user starts a conversation, the conversation.id belongs to sender_id and recipient_id.
I want visitor_id(which is the current_user) to be able to join any conversation and all users to be able to view all conversations.
conversation.rb
class Conversation < ActiveRecord::Base
belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
belongs_to :recipient, :foreign_key => :recipient_id, class_name: 'User'
has_many :messages, dependent: :destroy
validates_uniqueness_of :sender_id, :scope => :recipient_id
scope :involving, -> (user) do
where("conversations.sender_id =? OR conversations.recipient_id =?",user.id,user.id)
end
scope :between, -> (sender_id,recipient_id) do
where("(conversations.sender_id = ? AND conversations.recipient_id =?) OR (conversations.sender_id = ? AND conversations.recipient_id =?)", sender_id,recipient_id, recipient_id, sender_id)
end
end
user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :conversations, :foreign_key => :sender_id
after_create :create_default_conversation
I'm curious is if it's easy as adding
belongs_to :visitor, :foreign_key => :visitor_id, class_name: 'User'
to the conversation.rb model. But i'm not sure how i can get visitor_id which belongs to current_user to join a specific conversation (or make all conversations viewable to everyone).
EDIT: Added message.rb and controllers.
message.rb
class Message < ActiveRecord::Base
belongs_to :conversation
belongs_to :user
validates_presence_of :body, :conversation_id, :user_id
end
conversations_controller.rb
class ConversationsController < ApplicationController
before_filter :authenticate_user!
layout false
def create
if Conversation.between(params[:sender_id],params[:recipient_id]).present?
#conversation = Conversation.between(params[:sender_id],params[:recipient_id]).first
else
#conversation = Conversation.create!(conversation_params)
end
render json: { conversation_id: #conversation.id }
end
def show
#conversation = Conversation.find(params[:id])
#reciever = interlocutor(#conversation)
#messages = #conversation.messages
#message = Message.new
end
private
def conversation_params
params.permit(:sender_id, :recipient_id)
end
def interlocutor(conversation)
current_user == conversation.recipient ? conversation.sender : conversation.recipient
end
end
messages_controller.rb
class MessagesController < ApplicationController
before_filter :authenticate_user!
def create
#conversation = Conversation.find(params[:conversation_id])
#message = #conversation.messages.build(message_params)
#message.user_id = current_user.id
#message.save!
##path = conversation_path(#conversation)
end
private
def message_params
params.require(:message).permit(:body)
end
end
Adding :visitor would allow for the third party with minimal changes, but would not allow for a 4th or kth additional visitor easily. This seems a likely scenario and for that you'd need a join table that tracks all users_conversations.
First create a migration for the join table:
class CreateUsersConversationsJoinTable < ActiveRecord::Migration
def change
create_table :users_conversations do |t|
t.integer :user_id, null: false
t.integer :conversation_id, null: false
end
add_index :users_conversations, [:user_id, :conversation_id], unique: true
end
end
Then create a model along these lines:
class UsersConversations
belongs_to :users, class_name: User, inverse_of: :users_conversations
belongs_to :conversations, class_name: Conversation, inverse_of: :users_conversations
validates :user_id, :conversation_id, presence: true
end
You'd need to also create migration to move sender and receiver into this new model.
You'd need to move the sender attributes from conversation to message and make conversations know about senders via messages: has_many :senders through: message, foreign_key: sender_id. That way each message tracks its senders.
Your receiver becomes all users in a conversation that didn't send a particular message. Your message class would need something like this added:
has_many :users_conversations, through: :conversations
has_many :receivers, -> { where('users_conversations.user_id != messages.sender_id') }, through: :users_conversations, foreign_key: :user_id, class_name: 'User'
While your conversation class would need to change :between to use/be replaced by users and your scope would look like:
scope :involving, -> (user) do
users_conversations.where(user: user)
end
To get all conversations you can simply assign #conversations = Conversation.all in the relevant controller, or use the controller index route.

Rails User Groups - Set Group Owner In Another Model

I have user created groups in my application. I'm confused as to how to set the user that creates the group as an owner. I want there to be able to be multiple owners so it's a 'has-many-through' relationship. I can create/edit/delete a group.
So my question is how do I insert the current user_id and the group_id into the group_owners table at the time that the group is created?
Here is what I have that works so far:
User Model
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :group_owners
has_many :user_groups, through: :group_owners
end
Group Model
class UserGroup < ActiveRecord::Base
has_many :goup_owners
has_many :users, through: :groups_owners
validates :name, presence: true, length: {minimum: 5}
validates :visibility, presence: true, length: {minimum: 5}
VISIBILITY_TYPES = ["Public", "Private"]
end
Group Owner Model
class GroupOwner < ActiveRecord::Base
belongs_to :user
belongs_to :user_group
end
User Groups Controller - Create Action
def create
#usergroup = UserGroup.new(usergroup_params)
if #usergroup.save
redirect_to user_groups_path
else
render 'new'
end
end
I assume something needs to go in the user group create method but I'm not sure what.
Thanks for any help you can offer.
You should create UserGroup like
def create
#usergroup = current_user.user_groups.build(usergroup_params)
if #usergroup.save
redirect_to user_groups_path
else
render 'new'
end
end
This way the user group will be created with current users id and the group id into the group owners table.
In you UserGroup model, set a boolean for owner.
create_table |t|
t.references :user
t.references :group
t.boolean :owner
end
class UserGroup < ActiveRecord::Base
belongs_to :user
belongs_to :owner
scope :groups, ->(*g) {where(group_id: g.flatten.compact.uniq)}
scope :users, ->(*u) { where(user_id: u.flatten.compact.uniq)}
scope :owners, ->{where owner:true}
end
class User < ActiveRecord::Base
has_many :user_groups, dependent: :destroy, inverse_of: user
has_many :groups, through: :user_groups
def owned_groups
groups.merge(UserGroup.owners)
end
end
The following changes to the user group controller create method fixed my issue.
def create
#user_group = current_user.user_groups.build(usergroup_params)
if #user_group.save
#user_group.users << current_user
redirect_to user_groups_path
else
render 'new'
end
end

Rails: How to set up Active Record Associations?

I am relatively new to rails and doing the simplest thing: Associate users and posts. I read this, but what more than this do I need to do to make it work (or is this the only thing)?:
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
end
class Post < ActiveRecord::Base
belongs_to :user
end
Update:
I can't make it work. When I make a post with a signed in user, I get false when I do #user.posts.any? in the console. My code:
post.rb
class Post < ActiveRecord::Base
attr_accessible :title, :user_id
belongs_to :user
before_create :default_values
user.rb (I use devise)
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :posts, dependent: :destroy
end
20130320162700_create_posts.rb
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.integer :user_id
t.timestamps
end
end
end
You should make sure to include User's id in the migration that creates the posts table.
In your migration file (in the db/migrate folder you will find a file named like 20130325105934_create_posts.rb)
Inside the file you will find the migration code. Along the other declared attributes add
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
......
t.integer :user_id
......
end
end
It should be enough to make things roll :-)
Inside your code then you can create a new user as
#user = User.new(:login => "my_user", .....)
and then add posts with one of these two ways (there are others two).
post = Post.new(:title => "something", :text => "more of something", :user_id = #user.id)
or
post = Post.new(:title => "something", :text => "more of something")
#user.posts << post

Resources