Parent-child relationship in User model (self join) - ruby-on-rails

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

Related

Rails, uninitialized constant on create

im getting error when im tring to save my object of type Wedding. I have many foreign keys to the same class and on create action i gets NameError (uninitialized constant Wedding::user)
Wedding.rb
class Wedding < ApplicationRecord
belongs_to :creator, class_name: 'user', foreign_key: 'creator_id'
belongs_to :second_creator, class_name: 'user', foreign_key: 'second_creator_id', optional: true
belongs_to :third_creator, class_name: 'user', foreign_key: 'third_creator_id', optional: true
belongs_to :bride, class_name: 'user', foreign_key: 'bride_id'
belongs_to :groom, class_name: 'user', foreign_key: 'groom_id'
belongs_to :photo_type, class_name: 'tier', foreign_key: 'photo_type', optional: true
belongs_to :video_type, class_name: 'tier', foreign_key: 'video_type', optional: true
enum wedding_status: { reservation: 0, signed: 1, post: 2, delivered: 3, done: 4 }
end
User.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
before_save :set_full_name
has_many :weddings, class_name: 'wedding', foreign_key: 'creator_id'
has_many :weddings, class_name: 'wedding', foreign_key: 'second_creator_id'
has_many :weddings, class_name: 'wedding', foreign_key: 'third_creator_id'
has_many :weddings, class_name: 'wedding', foreign_key: 'bride_id'
has_many :weddings, class_name: 'wedding', foreign_key: 'groom_id'
validates :role, :email, :first_name, :last_name, presence: true
enum role: { super_admin: 0, admin: 1, bride: 2, groom: 3 }
protected
def set_full_name
self.full_name = "#{first_name} #{last_name}"
end
end
wedding_controller.rb
def create
#wedding = Wedding.new(wedding_params)
if #wedding.save
redirect_to weddings_path, notice: t('views.weddings.create.notice')
else
render :new
end
end
private
def wedding_params
ret = params.require(:wedding).permit(:creator_id, :bride_id, :groom_id, :photo_type_id,
:video_type_id, :wedding_date, :wedding_location, :reception_location, :bride_house,
:groom_house, :is_session_photo, :is_session_video, :session_location, :session_date,
:price, :is_paid, :advance, :is_advance_paid, :is_photo, :is_video, :video_link,
:photo_link, :thumb_image, :second_creator_id, :third_creator_id, :wedding_status)
ret
end
Im using simple_form to render my form.
The class name should be capitalized.
belongs_to :creator, class_name: 'User', foreign_key: 'creator_id'
You need to change all other lines (the same issue)

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

Rails 4:How to make users view only files in their own office

I am creating a file tracking system whereby users can track the movement from one office to another. I've gotten most of the application to work but presently every user can view all files regardless of where it's in their office, or not because in the file index.
I am using File.all in my file index action. Is there a way I can have a user only view and track files that is only currently in their own office, while the registry officer(admin) can view and track all files?
My relationships between models:
File model
class Nasfile < ActiveRecord::Base
belongs_to :category
has_many :trackers, dependent: :destroy
before_save :file_full_number, :on => [:create, :update]
def file_full_number
if self.file_sub.present?
self.file_number = [self.file_number , self.file_sub].join('/')
else
self.file_number = self.file_number
end
end
end
Office Model
class Office < ActiveRecord::Base
belongs_to :department
has_many :users
has_many :received_files,:class_name => 'Tracker', :foreign_key => 'office_sent_to_id'
has_many :sent_files,:class_name => 'Tracker', :foreign_key => 'office_sent_from_id'
def self.all_without(excluded)
where("id NOT IN (?)", excluded)
end
end
Tracker Model
class Tracker < ActiveRecord::Base
belongs_to :nasfile
belongs_to :sender, :foreign_key => :sender_id, class_name: 'User'
belongs_to :receiver, :foreign_key => :receiver_id, class_name: 'User'
belongs_to :office_receiving, :foreign_key => :office_sent_to_id, class_name: 'Office'
belongs_to :office_sending, :foreign_key => :office_sent_from_id, class_name: 'Office'
before_save :office_sent_to, :on => [:create, :update]
def office_sent_to
self.office_sent_to_id = self.receiver.office.id
end
end
User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable,:recoverable
devise :database_authenticatable, :registerable,
:rememberable, :trackable, :validatable,
:authentication_keys => [:username], password_length: 6..25
belongs_to :office
accepts_nested_attributes_for :office
has_many :sent_files,:class_name => 'Tracker', :foreign_key => 'sender_id'
has_many :received_files,:class_name => 'Tracker', :foreign_key => 'receiver_id'
def email_required?
false
end
def email_changed?
false
end
def self.all_without(excluded)
where("id NOT IN (?)", excluded)
end
end
Thanks for the help
Rather than doing
#files = File.all
try to filter the files you include by doing something like:
#files = File.where("office_id = ?", current_user.office_id)
This way, you only get files for the office to which the user belongs.
I don't know how you have your roles set up, but you can add some branching logic to allow registry officers to see all files, regardless of office:
if user.role = "registry officer"
#files = File.all
else
#files = File.where("office_id = ?", current_user.office_id)
end

How to 'subscribe' an user to an existing instance of this Tag model instead of creating a new one?

I have a Post model:
class Post < ActiveRecord::Base
attr_accessible :title, :content, :tag_names
belongs_to :user
has_many :taggings, :dependent => :destroy
has_many :tags, :through => :taggings
attr_writer :tag_names
after_save :assign_tags
def tag_names
#tag_names || tags.map(&:name).join(' ')
end
private
def assign_tags
if #tag_names
self.tags = #tag_names.split(" ").map do |name|
Tag.find_or_create_by_name(name)
end
end
end
end
a Tag model:
class Tag < ActiveRecord::Base
has_many :taggings, :dependent => :destroy
has_many :posts, :through => :taggings
has_many :subscriptions
has_many :subscribed_users, :source => :user, :through => :subscriptions
end
and an User model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :avatar
has_many :posts, :dependent => :destroy
has_many :subscriptions
has_many :subscribed_tags, :source => :tag, :through => :subscriptions
end
posts and tags have a many-to-many relationship (the following is the model for the join table):
class Tagging < ActiveRecord::Base
belongs_to :post
belongs_to :tag
end
users and tags have also a many-to-many relationship:
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :tag
end
Only posts with tags that the user has subscribed to should display:
def index
#title = "Posts"
#posts = current_user.subscribed_tags.map(&:posts).flatten.paginate(:page => params[:page], :per_page => 5)
Let say I create a tag for a post:
$ post.tags.create(:name => "food")
$ post.tags
=> [#<Tag id: 6, name: "food", created_at: "2012-03-02 10:03:59", updated_at: "2012-03-02 10:03:59"]
Now I have no idea how to subscribe the user to that tag.
I tried this:
$ user.subscribed_tags.create(:name => "food")
$ post.tags
=> [#<Tag id: 7, name: "food", created_at: "2012-03-02 10:04:38", updated_at: "2012-03-02 10:04:38"]
But as you can see it actually creates a new tag instead of adding the food tag with ID 6 to the user.subscribed_tags attribute.
Any suggestions to solve this issue?
You can append to the user's subscriped_tags, as you would do an array.
ex: user.subscribed_tags << Tag.find_by_name("food")

How do I pull a list of models (events) through a relationship with another model (Users)?

This is a bit complicated and I'm not sure how to implement it. I have a User model and a Relationship model. Users are able to "follow" each other (just like twitter). The relationship model is all setup properly and works great.
Next, I have an Event model. Each user has_and_belongs_to_many events (many to many association between users and events). Users "attend" events.
What I would like to do is pull a list of all events that are
being attended by the current_user
are being attended by users that current_user is following.
If possible, I would like to have this list accessible via the User model so I can say current_user.event_feed and it will list all events as mentioned above.
Here are my models:
class Event < ActiveRecord::Base
attr_accessible :name,
:description,
:event_date,
:location,
:owner_id,
:category,
:photo
CATEGORIES = ['Music', 'Outdoors', 'Party']
has_and_belongs_to_many :users
and relationship model:
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
belongs_to :follower, :class_name => "User"
belongs_to :followed, :class_name => "User"
validates :follower_id, :presence => true
validates :followed_id, :presence => true
end
and user model:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation, :time_zone
has_and_belongs_to_many :events
has_many :relationships, :dependent => :destroy,
:foreign_key => "follower_id"
has_many :reverse_relationships, :dependent => :destroy,
:foreign_key => "followed_id",
:class_name => "Relationship"
has_many :following, :through => :relationships,
:source => :followed
has_many :followers, :through => :reverse_relationships,
:source => :follower
Thanks!
This is rails 3 only, but quite elegant (untested, hopefully my memory of habtm relationships is ok).
class User < ActiveRecord::Base
# ...
def event_feed
ids = self.followers.collect(&:id) << self.id
Event.includes(:users).where(["`users`.id IN (#{ids.join(',')})"])
end
# ...
end
Event Model:
scope :attended, where("event_date < #{Date.today}")
User Model:
# Returns collection of events that are attended by user and users she follows
def attended events
attended_events = []
attended_events << events.attended
followers.each do |follower|
attended_events << follower.events.attended
end
attended_events
end
1) being attended by the current_user and
This can be achieved simply by calling current_user.events
2) are being attended by users that current_user is following.
This is a little trickier. You want to end up with a flattened list of other user's events: current_user.following.collect { |friend| friend.events }.flatten #=> returns an array of followers' events
Since you want to display all events in a single list (from what I could gather), I think a presenter class would be useful:
class EventFeed
attr_accessor :event, :display_name
def initialize(event, name)
self.event = event
self.name = name
end
end
And now, adding them together to get to current_user.event_feed
class User
def event_feed; []; end
end
And gluing it all together:
current_user.events.each { |e| current_user.event_feed << EventFeed.new(e, 'YOU') }
current_user.following.each do |friend|
friend.events.each { |e| current_user.event_feed << EventFeed.new(e, friend.name) }
end
current_user.event_feed #=> an array of EventFeed objects where you can display "You are going to #{event.name}"
Of course this is pseudo code, but it should get you on the right track

Resources