How do I make my task assignment associations? - ruby-on-rails

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

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).

Migration has_one and has_many

class Post < ActiveRecord::Base
has_one :owner, class_name: "User", foreign_key: "owner_id" #creator post
has_many :users #followers post
end
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :posts
end
What command line I need to perform to migrate to perform these different relationships between the User and Post tables?
Thanks
Post should belong_to:owner, because the posts table has the foreign key. Also, its #users is a bit too ambiguously named, as is User#posts.
Here are your models:
class Post < ActiveRecord::Base
belongs_to :owner, class_name: 'User', inverse_of: :owned_posts # foreign_key: :owner_id will be inferred
has_many :subscriptions
has_many :followers, through: :subscriptions, source: :user, class_name: 'User', inverse_of: :followed_posts
end
class Subscription < ActiveRecord::Base
belongs_to :post
belongs_to :user
end
class User < ActiveRecord::Base
has_many :owned_posts, class_name: 'Post', inverse_of: :owner
has_many :subscriptions
has_many :followed_posts, through: :subscriptions, source: :post, class_name: 'Post', inverse_of: :followers
end
And here are the migrations to support them:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
# ...
end
end
end
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.integer :owner_id
# ...
end
end
end
class CreateSubscriptions < ActiveRecord::Migration
def change
create_table :subscriptions do |t|
t.integer :post_id
t.integer :user_id
end
end
end
If it weren't for the 'ownership' relation, you could use a has_and_belongs_to_many relationship:
rename the subscriptions migration to posts_users (must be plurals in alphabetical order),
do away with its model entirely, and
have Post.has_and_belongs_to_many :users and User.has_and_belongs_to_many :posts.
In fact, you technically could do that, but having ambiguous names like that is bad practice.

Parent-child relationship in User model (self join)

In rails, I have this User model:
class User < ActiveRecord::Base
enum role: [:adult, :child, :admin]
after_initialize :set_default_role, :if => :new_record?
# belongs_to :spouse, :foreign_key => :spouse_id, :class_name => 'User', :inverse_of => :spouse
def set_default_role
self.role ||= :adult
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
def marry(user)
self.spouse = user
user.spouse = self
end
end
I added spouses through this migration:
class AddFieldsToUser < ActiveRecord::Migration
def change
# for marriages
add_column :users, :spouse_id, :integer, index: true
end
end
and that works decently (though my inverse of function never worked # belongs_to :spouse, :foreign_key => :spouse_id, :class_name => 'User', :inverse_of => :spouse
). I am now trying to do another self join with a "child-parent" relationship. A parent can have many children and a child can have (many / two) parents. This is the migration I came up with:
class CreateParentalRelationships < ActiveRecord::Migration
def change
create_table :parental_relationships do |t|
t.references :parent, index: true
t.references :child
end
end
end
and I added this to the model:
has_many :children, :through => :parental_relationships, class_name: "User"
has_many :parents, :through => :parental_relationships, class_name: "User"
but the relationship did not work with the following error:
[7] pry(main)> u = User.find(3)
User Load (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 3]]
=> #<User id: 3, email: ...>
[8] pry(main)> u.children
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :parental_relationships in model Us
er
[9] pry(main)> u.parent
NoMethodError: undefined method `parent' for #<User:0x5e52eb0>
from C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activemodel-4.1.8/lib/active_model/attribute_methods.rb:435:in
`method_missing'
[10] pry(main)> u.parents
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :parental_relationships in model Us
er
from C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activerecord-4.1.8/lib/active_record/reflection.rb:690:in `che
ck_validity!'
[11] pry(main)> u2.children
ActiveRecord::HasManyThroughAssociationNotFoundError: Could not find the association :parental_relationships in model Us
er
from C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/activerecord-4.1.8/lib/active_record/reflection.rb:690:in `che
ck_validity!'
What am I missing?
Setting up the parent child relationship takes some finagling.
class ParentalRelationship < ActiveRecord::Base
belongs_to :parent, class_name: 'User'
belongs_to :child, class_name: 'User'
end
class User < ActiveRecord::Base
has_many :parent_relationships,
foreign_key: 'child_id',
class_name: 'ParentalRelationship'
has_many :child_relationships,
foreign_key: 'parent_id',
class_name: 'ParentalRelationship'
has_many :parents,
through: :parent_relationships,
class_name: 'User'
has_many :children,
through: :child_relationships,
class_name: 'User'
has_and_belongs_to_many :marriages
belongs_to :current_marriage, class_name: 'Marriage'
def marry(spouse)
marriage = Marriage.create(users: [self, spouse])
marriage.users.each { |u| u.update(current_marriage: marriage ) }
marriage
end
def spouse
return nil unless current_marriage
current_marriage
.users.where.not('marriages_users.user_id' => id).first
end
def birth(child)
child.parents << self
child.parents << spouse if spouse
end
end
Note that we need to setup the relation to ParentalRelationship twice since we need to tell rails which foreign key it should look at for each type of relation (user is parent or child).
Since in these modern times people can actually have be married several times we need a Marriage model and a join table for users_marriages.
class Marriage < ActiveRecord::Base
has_and_belongs_to_many :users
validates :users, length: { minimum: 2, maximum: 2 }
end
rails g migration CreateUsersMarriagesJoinTable users marriages
Example app with specs: https://github.com/maxcal/sandbox/tree/31614819
I came up with a solution. Not sure if it is the best:
my new User model:
class User < ActiveRecord::Base
enum role: [:adult, :child, :admin]
after_initialize :set_default_role, :if => :new_record?
belongs_to :spouse, :foreign_key => :spouse_id, :class_name => 'User', :inverse_of => :spouse
has_many :parental_relationships
has_many :children, :through => :parental_relationships, class_name: 'User'
has_many :parents, :through => :parental_relationships, class_name: 'User'
def set_default_role
self.role ||= :adult
end
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
def marry(user)
self.spouse = user
user.spouse = self
end
def birth(user)
self.children << user
user.parents << self
if self.spouse
self.spouse.children << user
user.parents << self.spouse
end
end
end
I had to edit a few migrations and models.
ParentalRelationships migration
class CreateParentalRelationships < ActiveRecord::Migration
def change
create_table :parental_relationships do |t|
t.references :user, index: true
t.references :child
t.references :parent
end
end
end
ParentalRelationship model:
class ParentalRelationship < ActiveRecord::Base
belongs_to :user
# , :class_name => "User"
# belongs_to :parent, :class_name => "User"
belongs_to :child, :class_name => "User"
belongs_to :parent, :class_name => "User"
end
So, to add relationships:
u = User.find(50)
u.birth(User.find(60))

Resources