Creating Family of Users in Rails - ruby-on-rails-5

I'm trying to group users in families. A family can have one parent and multiple members
so a parent is considered a member as well.
I have tried answers provided here
association and migration between users and teams (rails)
and here
rails many to many self join
to try to make it work but no luck
Here is what I have
class User < ActiveRecord::Base
# this defines the parent to members fine and you can get them
# only if you have the parent
has_many :memberships, :class_name => 'Family', :foreign_key => 'user_id'
has_many :family_members, :through => :memberships, :source => :registrar
# trying to define that user is also a member of family
belongs_to :registrar_family, :foreign_key => 'member_user_id'
end
class Family < ActiveRecord::Base
belongs_to :user, :class_name => 'User', :foreign_key => "user_id"
has_many :users, :class_name => 'User', :foreign_key => "id"
end
So if I have user 1 who is a parent and has four members I can use
user.family_members # to get family members for this parent
but how do I make so that I can also get the full family from a members
examples of DB
Users:
id, name
1, King
2, Queen
3, Prince
4, Duaghter
Users Family:
id,user_id, member_user_id
1, 1, 2
1, 1, 3
1, 1, 4
How do I say something like
user = User.find(4)
user.family.parent.members # which would return a family association
The complete solution for this is (if anyone is interested):
class User < ActiveRecord::Base
def family
members = Family.where("user_id = ? OR member_user_id = ?", self.id, self.id)
# if members is only 1 person then this person is a member only
# then get all members from parent
if members.count == 1
members = members.first.parent.family
end
members
end
def family_count
# if there is family then count is family + parent else 0
family.count > 0 ? family.count + 1 : 0
end
end
class Family < ActiveRecord::Base
belongs_to :parent, :class_name => 'User', :foreign_key => "user_id"
end

Perhaps you have reasons you haven't mentioned why you need a Family class. But for a simple implementation you can do it all within the User model:
class User < ApplicationRecord
def is_parent?
parent_id.nil?
end
def family
User.where(id: id_of_parent).or(User.where(parent_id: id_of_parent))
end
private
def id_of_parent
is_parent? ? id : parent_id
end
end
if the users table contains
| id | first_name | parent_id |
| 1 | Fred | nil |
| 2 | Wilma | 1 |
| 3 | Pebbles | 1 |
| 4 | Barney | 1 |
Then:
> User.find(1).family.map(&:first_name) # -> [Fred,Wilma,Pebbles,Barney]
> User.find(2).family.map(&:first_name) # -> [Fred,Wilma,Pebbles,Barney]
You can add a self-join in the User model, if you like, but it doesn't add much:
Class User < ActiveRecord::Base
belongs_to :parent, class_name: 'User', foreign_key: :parent_id
etc...
I realize it's not exactly what you asked, but does it meet your needs?

Related

Filtering an association by missing records in Rails

In a Rails application I'm working on, I've got a few different models associated thusly (condensed for clarity):
group.rb
class Group < ApplicationRecord
has_many :members, class_name: 'GroupMember'
has_many :newsletters
end
group_member.rb
class GroupMember < ApplicationRecord
belongs_to :group,
has_many :subscriptions, inverse_of: :group_member, class_name: 'Newsletter::Subscriber'
scope :subscribed_to, ->(newsletter_id) { joins(:subscriptions).merge(Newsletter::Subscriber.where(["newsletter_id = ?", newsletter_id])) }
scope :not_subscribed_to, ->(newsletter_id) { where.missing(:subscriptions) }
end
newsletter.rb
class Newsletter < ApplicationRecord
acts_as_tenant :group
has_many :subscribers, inverse_of: :newsletter
end
newsletter/subscriber.rb
class Newsletter::Subscriber < ApplicationRecord
acts_as_tenant :group
belongs_to :newsletter, inverse_of: :subscribers
belongs_to :group_member, class_name: 'GroupMember', inverse_of: :subscriptions
end
Given the above associated models, here's the framework I'm working within:
Each Group has n Group Members and n Newsletters.
Each Group Member can have multiple Newsletter Subscriptions (one per newsletter in the group)
What I'm trying to do (unsuccessfully, so far), is find out which members in a group are NOT subscribed to a specific newsletter that is associated with the group.
I can find out the members that DO have a subscription using the following scope on the GroupMember object:
scope :subscribed_to, ->(newsletter_id) { joins(:subscriptions).merge(Newsletter::Subscriber.where(["newsletter_id = ?", newsletter_id])) }
That allows me to query, for instance, current_group.members.subscribed_to(current_group.newsletters.first.id).
However, I'm not sure how to negate that and get the the opposite of that set of members. That is, members NOT subscribed to that specific newsletter. The :not_subscribed_to scope I currently have defined isn't cutting it because it doesn't take into account which newsletter I'm referring to.
Given the variable newsletter_id.
One alternative is to use WHERE NOT EXISTS(...) with a subquery:
Member
.where(
'NOT EXISTS(
SELECT 1 FROM "subscriptions"
WHERE "subscriptions"."member_id" = "members"."id"
AND "subscriptions"."newsletter_id" = ?
)', newsletter_id
)
Translated into Arel:
Member.where(
Subscription.select('1')
.where(
Subscription.arel_table[:member_id].eq(Member.arel_table[:id])
).where(
newsletter_id: newsletter_id
).arel.exists.not
)
Or group, count and having:
Member.group(:id)
.left_joins(:subscriptions)
.where(subscriptions: { newsletter_id: newsletter_id })
.having(Subscription.arel_table[:id].count.eq(0))

How to write a Rails SQL query for finding an object where all children have an equal value

I've been reading this, but can't make sense of writing it into a Rails scope :
find all parent records where all child records have a given value (but not just some child records)
I have a Course, Section, and Quiz, object :
class Course < ActiveRecord::Base
has_many :course_members
has_many :members, through: :course_members
has_many :sections
has_many :quizzes, through: :sections
end
class Quiz < ActiveRecord::Base
belongs_to :member
belongs_to :section
end
class Section < ActiveRecord::Base
belongs_to :course
has_many :quizzes
end
I'd like to find all courses of a member, where all quizzes related to that course have the attribute completed = true.
So in my Member class, I'd ideally like to write something like :
has_many :completed_courses, -> {
joins(:courses, :quizzes, :sections)
# .select( 'CASE WHEN quizzes.completed = true then 1 end') ??? maybe ???
}, class_name: 'Course'
Haha! But barring that being too complicated. I've been trying to write this simply in the Course would also be fine.
class Member < ActiveRecord::Base
has_many :courses, through: :course_members
has_many :course_members
has_many :completed_courses,
-> { joins(:quizzes).where.not(quizzes: {completed: [false, nil]}) },
through: :course_members,
source: :course
end
If your completed boolean column is NOT NULL, then change [false, nil] above to just simply false
Usage Example
irb(main):002:0> Member.first.completed_courses
Member Load (0.2ms) SELECT "members".* FROM "members" ORDER BY "members"."id" ASC LIMIT 1
Course Load (0.1ms) SELECT "courses".* FROM "courses" INNER JOIN "sections" ON "sections"."course_id" = "courses"."id" INNER JOIN "quizzes" ON "quizzes"."section_id" = "sections"."id" INNER JOIN "course_members" ON "courses"."id" = "course_members"."course_id" WHERE (NOT (("quizzes"."completed" = 'f' OR "quizzes"."completed" IS NULL))) AND "course_members"."member_id" = ? [["member_id", 1]]

has_many through instead of join query

I have relationship between User models defined through Friendship model. (ROR 4)
User
class User < ActiveRecord::Base
has_many :friendships, ->(object) { where('user_id = :id OR friend_id = :id', id: object.id) }
has_many :friends, ->(object) { where(friendships: {status: 'accepted'}).where('user_id = :id OR friend_id = :id', id: object.id) }, through: :friendships, source: :friend
has_many :requested_friends, -> { where(friendships: {status: 'pending'}) }, through: :friendships, source: :friend
end
Friendship
class Friendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: 'User'
def self.request(user, friend)
unless user == friend or find_friendship(user, friend) != nil
create(user: user, friend: friend, status: 'pending')
end
end
def self.find_friendship(user, friend)
ids = [user.id, friend.id]
where(user_id: ids, friend_id: ids).first
end
end
However, this does not work and my tests are failing because of SQL queries produced.
Friendships relation
> user.friendships
Query:
SELECT "friendships".* FROM "friendships"
WHERE "friendships"."user_id" = ?
AND (user_id = 1 OR friend_id = 1) [["user_id", 1]]
So part of WHERE before AND "kills" my actual where. I made a workaround by making instance method:
def friendships
self.class
.select('friendships.* FROM `friendships`')
.where('user_id = :id OR friend_id = :id', id)
end
Is there a way I can remove my instance method and modify has_many relation to produce the SQL I want?
Requested_friends relation
> Friendship.request(user, friend)
> friend.requested_friends
Query:
SELECT "users".* FROM "users"
INNER JOIN "friendships" ON "users"."id" = "friendships"."friend_id"
WHERE "friendships"."status" = 'pending'
AND "friendships"."user_id" = ?
AND (user_id = 2 OR friend_id = 2) [["user_id", 2]]
It obviously isn't what I need so I made a workaround by removing has_many :requested_friends and making an instance method:
def requested_friends
self.class
.joins('JOIN `friendships` friendships ON users.id = friendships.user_id')
.where('friendships.status = ?', 'pending')
.where('friendships.friend_id = ?', id)
end
Is there any way I can modify my has_many :requested_friends relation to produce same SQL as my instance method?
Very confusing - I'd do something like this:
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :friendships, class_name: "user_friendships", association_foreign_key: "user_id", foreign_key: "friend_id",
has_many :friends, class_name: "User", through: :friendships
end
#app/models/user_friendship.rb
Class UserFriendship < ActiveRecord::Base
belongs_to :user
belongs_to :friend, class_name: "User"
end
You'd have a join table which looks like this:
user_friendships
id | user_id | friend_id | other | info | created_at | updated_at
This should work (I'm not sure about the self referential association). If it does, it will allow you to call:
#user.friends
I hope this helps?
You might also benefit from this gem
you cannot achieve the SQL you want using has_many method with condition. The reason is that the block you pass to the method is only additional condition, on top of the standard query which checks if user_id = ?.
Instead you can simplify your instance method a little bit
def friendships
Friendship.where('user_id = :id or friend_id = :id', id)
end

Select distinct including a value from a different model/table

I have 2 models/tables:
class CollectionPoint < ActiveRecord::Base
belongs_to :collection_type
...
class CollectionType < ActiveRecord::Base
has_many :collection_points
...
Every CollectionPoint has a city (column)
Every CollectionType has a name (column)
I would like to get all the distinct collection type names in each city in order to show a directory like this in my view:
city A
collection_type.name A
collection_type.name B
collection_type.name C
...
city B
collection_type.name A
collection_type.name B
collection_type.name C
...
city C
collection_type.name A
collection_type.name B
collection_type.name C
...
...
The best thing would be an array that is grouped by cities so that i could go like this in my view:
#cities.each do |city|
...
city.each do |collection_type_name|
...
end
end
So i tried this:
CollectionPoint.select("DISTINCT city, collection_type_id")
But then i get only the collection type ids, not the collection type names. Also i don't need the collection point ids (which are nil in the query result anyway), so i tried this:
CollectionPoint.includes(:collection_type).select("DISTINCT city, collection_types.name").references(:collection_types)
But this is not getting me anywhere neither.
I'm quite frustrated because i know there must be a solution to this i don't have a clue about. Maybe you could help me?
Cheers
Solution A
The simplest solution to this problem was adding this line of code to my controller:
#cities = CollectionPoint.includes(:collection_type).select("DISTINCT city, collection_type_id").order(:city).group_by(&:city)
So i could show the results in my view accordingly:
- #cities.each do |city, collection_points|
%h2
= city
= content_tag :ul, title: "... " + city do
%li
= link_to "... " + city, "/.../" + city
- collection_points.each do |cp|
%li
= link_to cp.collection_type.name + " in " + city, "/" + cp.collection_type.name + "/" + city
Solution B
I guess the best solution is to create a separate City model and rework your assosiations:
class City < ActiveRecord::Base
has_many :collection_points
has_many :collection_types, through: :collection_points
...
class CollectionPoint < ActiveRecord::Base
belongs_to :collection_type
belongs_to :city
...
class CollectionType < ActiveRecord::Base
has_many :collection_points
has_many :cities, through: :collection_points
...
Then you can do all kinds of stuff, for example finding all collection types in a specific city:
City.where(name: "Cologne").first.collection_types
To list all distinct collection types in all distinct cities, the controller action looks like this:
#cities = City.includes(:collection_types).distinct
And in the view you can go like:
#cities.each do |city|
city.name
city.collection_types.each do |collection_type|
collection_type.name
end
end
More information on this can be found here:
Rails Model Assosiations
Thanks for your help, guys!
Do your CollectionName and CollectionType models have and belongs to many records? If so, this is the set up I would advise with:
Collector.rb
class Collector < ActiveRecord::Base
attr_accessible :collection_point_id, :collection_type_id
belongs_to :collection_point
belongs_to :collection_type
end
CollectionPoint.rb
class CollectionPoint < ActiveRecord::Base
attr_accessible :city
has_many :collectors
has_many :collection_types, :through => :collectors
end
CollectionType.rb
class CollectionType < ActiveRecord::Base
attr_accessible :name
has_many :collectors
has_many :collection_points, :through => :collectors
end
Then you can utilise the new ActiveRecord association and select distinct records with the group method:
#cities = CollectionPoint.select("DISTINCT(CITY)").all
Then print the #cities object with an each do method.
#cities.each do |city|
...
city.collection_types.each do |collection_type_name|
...
end
end
what about using active record -
grouped_by_city = CollectionPoint.all.group_by { |cp| cp.city }
would return
{ :city =>{stuff in here}}
and then you could do this:
- grouped_by_city.each do |city, info|
%p= city
%ul
%li= info.collection_type.name #not sure what this should be? depends on your models
if you play around with that a bit I think it will work for you. irb is your best friend!

Ruby ActiveRecord has_one with conditions

Let's say I have some Items for sale, and I'm keeping track of their Cost historically:
class Cost < ActiveRecord::Base
belongs_to :item
# eg: costs.amount = 123.45; costs.item_id = 1; costs.created_at = 2011-08-11 16:28
end
class Item < ActiveRecord::Base
has_many :costs
# eg: items.id = 1; items.name = Cheese Sandwich
end
This code works, I can pull out all the previous costs for the item I'm selling.
I feel like it should be possible to have a second clause for Item so that I can pull out the current price directly:
class Item < ActiveRecord::Base
has_many :costs
has_one :current_cost, :class_name => :costs, :conditions => 'MAX(created_at)'
end
my_item.current_cost # => <£123.45, 45 minutes ago>
Any ideas how to achieve this?
class Item < ActiveRecord::Base
has_many :costs
def current_cost
self.costs.order("created_at DESC").first
end
end
my_item.current_cost
has_one :current_cost, :class_name => :costs, :order => 'create_at DESC'
You can use the scope:
class Item < ActiveRecord::Base
has_many :costs
scope :current_cost, limit(1).order("created_at DESC")
end
usage:
my_item.current_cost