Private fields for multiple models, how to make on RoR? - ruby-on-rails

I have 4 models with complex relations. 3 of them should have descriptions, that should be enable only for user who's create. In other words every user has his own description for Group (for example), or for Post, o something else. Let's talk about only one model, because others are very same. What I have:
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, omniauth_providers: [:vkontakte]
has_and_belongs_to_many :groups
has_many :descriptions
end
group.rb
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :descriptions, :as => :describable
accepts_nested_attributes_for :descriptions
end
description.rb
class Description < ActiveRecord::Base
belongs_to :user
belongs_to :describable, :polymorphic => true
end
table for descriptions
create_table "descriptions", force: :cascade do |t|
t.integer "user_id" -- belongs_to
t.string "content"
t.integer "describable_id"
t.string "describable_type"
end
How to display the description for group that belongs to current_user (I use devise)? How to build an update form with nested description?
I try to do it, but it's not work. I've ask question about part of problem here.

Why do you have an extra model called description?
Although it's not a problem in itself, you really don't need to have a model just for description.
--
Profile
Instead, you may wish to put the details into a profile model, or simply in the user model (there's nothing wrong with adding extra attributes to a Devise model).
We use a profile model, which gives us the ability to add as many "extra" fields as we want to the user model:
You can set it up like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :profile
accepts_nested_attributes_for :profile
before_create :build_profile
delegate :description, :name, to: :profile, prefix: false #-> #user.description
end
#app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
This will allow you to create a single profile per user, have that profile built when the user is created, and then change as many options inside the profile as you wish.

Related

Rails: How to multiple associations between two models

I have the following association between Reviews and Users:
Since I'm using Devise, I kept just a single Users table and identify the roles using client or seller columns (boolean).
So as you can imagine, I need to know the user that made the review and the user being "reviewed".
The first question is: Can I make use of references while creating the migration? I manually created these columns like this: t.integer :client_id, foreign_key: true and t.integer :seller_id, foreign_key: true
The second is: How can I specify the relationship in the models? I did like this has_many :reviews, foreign_key: "client_id" and has_many :reviews, foreign_key: "seller_id" but i'm not sure if it's correct.
Here's the full code of migration:
class CreateReviews < ActiveRecord::Migration[6.0]
def change
create_table :reviews do |t|
t.text :description
t.integer :rating, null: false
t.integer :client_id, foreign_key: true
t.integer :seller_id, foreign_key: true
t.timestamps
end
end
end
The User Model:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :reviews, foreign_key: "client_id"
has_many :reviews, foreign_key: "seller_id"
end
and the Review model:
class Review < ApplicationRecord
belongs_to :user
end
Rails Version: 6.0.3.2 - Ruby Version: 2.6.6
I see what you are trying to achieve.
First thing first, remove foreign_key: true in your CreateReviews migration because it has no effect, you might want to index those two columns by replacing it with index: true.
Then in your User model have two different has_many associations eg
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :client_reviews, foreign_key: "client_id", class_name: 'Review'
has_many :seller_reviews, foreign_key: "seller_id", class_name: 'Review'
end
Why two different associations? well because when you have two same associations it will always use the last association hence overriding the first one.
You might want to try it in your console and see the output, for your case if you inspect the query you will see that it is using seller_id column to find reviews if you try something like.
user = User.first
p user.reviews.to_sql
Now refactor your Review model to have something like this
class Review < ApplicationRecord
belongs_to :client, foreign_key: :client_id, class_name: 'User'
belongs_to :seller, foreign_key: :seller_id, class_name: 'User'
end
Now you can create client_reviews and seller_reviews and query each one
seller = User.create(name: 'Seller 1)
client = User.create(name: 'Client 1')
seller.seller_reviews.create(description: 'I like your product', client: client)
review = Review.first
p review.client
p review.seller
Hope it helps give the picture of what you can do.

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.

Rails polymorphic association and has_many for the same model

I have the Comment model which belongs to some other models like Post, Page etc and has_one (or belongs_to?) User model. But I need the User to be commentable too, so User has to have many Comments from other Users (this is polymorphic :commentable association) and he has to have his own Comments, written by him.
What is the best way to make an association like this? How can I read and create Comments for User in a controller if User has two different associations with Comments?
Now I do this and it's not right I guess:
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :comments, as: :commentable
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
belongs_to :user
end
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.text :content
t.references :commentable, polymorphic: true, index: true
t.belongs_to :user
t.timestamps null: false
end
end
end
You'll want to use another name for that association.
has_many :comments, as: :commentable
has_many :commented_on, class_name: 'Comment' # you might also need foreign_key: 'from_user_id'.
See has_many's documentation online.
The foreign_key should not be needed in your case, but I'm pointing it out Just In Caseā„¢. Rails will guess "{class_lowercase}_id" by default (so user_id in a class named User).
Then you can access both associations (The class_name is explicitly needed because Rails can't find Comment from commented_on).

How do I make my task assignment associations?

I have a User model, a TodoList model, which has many todoItems. My models are :
User Model
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
has_many :todo_lists
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
end
TodoList Model
class TodoList < ActiveRecord::Base
has_many :todo_items
belongs_to :user
end
ToItem Model
class TodoItem < ActiveRecord::Base
include AASM
belongs_to :todo_list
def completed?
!completed_at.blank?
end
#belongs_to :user
#belongs_to :friend, class_name: 'User', foreign_key: 'friend_id'
aasm :column => 'state', :whiny_transitions => false do
state :not_assigned, :initial => true
state :assigned
state :taskCompleted
end
I am trying to modify my models in such that any user can request to be assigned a taskItem and the user whom the task belongs to can accept or deny the requests. Once a an assignment request is approved, I want the task to be also associated to the user assigned to it.
How do I go about that with my model associations and relationships ? Thanks in advance for the help .
You could use an assignments association table, in a many-to-many relationship between User and TodoItem. Your association table would have an additional boolean attribute, indicating whether the item owner has accepted the request. Something like:
class TodoItem < ActiveRecord::Base
...
has_many :users, through: :assignments
...
end
For User:
class User < ActiveRecord::Base
...
has_many :todo_items, through: :assignments
...
end
And finally the association table:
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :todo_item
end
Your migration to create the association table would be something like this:
class CreateAssignments < ActiveRecord::Migration
def change
create_table :assignments do |t|
t.belongs_to :user, index: true
t.belongs_to :todo_item, index: true
t.boolean :request_accepted, default: false, null: false
t.timestamps null: false
end
end
end

How to use owner_id (instead of user_id) as foreign key?

I'm building an app where a user creates an arbitrary number of groups. When the group is created, the owner_id (in the group table) is set to that of the current_user.id.
I'm having some trouble showing the groups owned by a particular user though. Note that there is a many-to-many relationship between users and groups through the GroupMembers table.
Based on this question, I modified my group.rb model like such:
class Group < ActiveRecord::Base
has_many :group_members, :dependent => :destroy
has_many :users, :through => :group_members
belongs_to :owner, class_name: User, foreign_key: :owner_id
end
Adding just the last line to ("owner") to the model. The problem is that when I display the groups belonging to the user in my view:
<h3>User</h3>
<p>User: <%= #user.name %></p>
<p>Email: <%= #user.email if #user.email %></p>
<p>Groups:
<% #user.groups.each do |group|%>
<%= group.name %>
<% end %>
</p>
None of the groups show up. It seems that maybe I need to explicitly join to the groups table from inside the UsersController for this to work? But I'm not sure. I don't want to go too far down the rabbit hole here so I'm looking for some advice.
UPDATE:
Here is my User model:
class User < ActiveRecord::Base
has_many :group_members, :dependent => :destroy
has_many :groups, :through => :group_members
rolify
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
end
When you fetch #user.groups, you're using the :groups association on the User model, which is independent of the has_one :user association on the Group model. So the code you've provided above isn't the relevant bit. What you need is:
class User < ActiveRecord::Base
has_many :groups, foreign_key: :owner_id
end
Update: since you've already got a has_many :groups relation, you'll need to name this something else. e.g.,
class User < ActiveRecord::Base
has_many :owned_groups, foreign_key: :owner_id, class_name: Group
end
Then you'll need to update your view code to do #user.owned_groups instead of #user.groups, or you'll be fetching the wrong collection.
(p.s., the inverse relation on the Group model should be belongs_to :owner, class_name: User, foreign_key: :owner_id, not has_one. The difference is that a model that belongs_to another model is expected to hold the foreign key that defines the relationship, whereas a has_one relationship expects the foreign key to be on the associated table.)
You can do something like
# user.rb
has_many :owned_groups, foreign_key: 'owner_id', class_name: "Group"
That will distinguish between groups that the user owns vs groups that the user is in.

Resources