Rails, find by association - ruby-on-rails

I'm about to build a messaging feature for a rails app.
I thought about having conversations, conversation_participants and conversation_messages.
Which means:
Conversation:
has_many :conversation_participants
has_many :conversation_messages, through: :conversation_participants
Conversation Participant:
belongs_to :conversation
belongs_to :user
has_many :conversation_messages
Conversation Message:
belongs_to :conversation_participant
So far, so good.
I'm just stuck in some scenarios:
How do I find the conversation for User 1 and User 2?
How do I find one for User 1, User 4 & User 5?
What if there are two conversations, one for Users 1,4,5 and one for 1,4,5,6 but I'm just looking for 1,4,5?
Hope somebody could help me out on this one! Thank you!

You can exclude converstion_messages. These are irrelevant, and need to include users.
#user
has_many :conversations, through: converstion_participants
user_12_conv_ids = user_1.conversation_ids & user_2.conversation_ids
user_123_conv_ids = user_12_conv_ids & user_3.conversation_ids
user_123_conversations = Conversation.where(id: user_123_conv_ids)
Now you can select conversations that include only 1, 2, and 3 (as user_ids)
conversations.select{|c| c.user_ids == user_ids}

You may want to look more into the merge and where methods.
The first example would look something like:
Conversation.joins(:conversation_participants).merge(User.where(:id => user1_id)).merge(User.where(:id => user2_id))
Each merge() filters the results. You wouldn't want to use merge(User.where(:id => [user1_id, user2_id])) because you would get all the conversations for both users, not just the common ones.
The second example would be similar to the first one.
In the third example you could add something like .merge(User.where.not(:id => user6_id) at the end of the query to not include conversations with User 6.
UPDATE
To chain multiple merge dynamically you could try something like:
conversations = Conversation.joins(:conversation_participants)
user_ids.each{|uid| conversations.merge!(User.where(:id => uid))}

It sounds like you will want to set up an association between users and conversations through conversation participants.
How do I find the conversation for User 1 and User 2?
This doesn't seem to be a unique conversation given the way you've set things up. Users 1 and 2 could be in one conversation with only each other, and another conversation that includes other participants. That being said, if you join the conversation_participants table to itself on conversation_id you should be able to find the matches. There are other ways to go about this to and I'm happy to provide more info. The way you decide to approach this will factor into the answers to your other questions as well.
UPDATE:
The query would look something like this:
SELECT cp1.conversation_id
FROM conversation_participants as cp1
JOIN conversation_participants as cp2 ON cp1.conversation_id = cp2.conversation_id
WHERE cp1.participant_id = 1 and cp2.participant_id =2;

Related

Rails associations - orders

So I have been trying to create a dummy application to try and learn Rails. The app I thought I could create is a coffee ordering app for a group of people in work.
So the website will have many users.
A user can create a coffee_order.
A coffee order contains orders for other individual users.
Each user can have one or more coffee_shop_items (e.g. latte,
cappuccino,danish, muffin, etc)
A coffee order also has an assignee, this is the person who is tasked
with going and getting the order.
So as a user, I create a coffee order, select an assignee, add users to the order, and add one or more coffee shop items to each user,
I am really struggling with how the database should be, and what the associations need to be, along with any join tables?
I am also trying to use nested attributes for the form entry.
Thanks in advance for help.
Update with some code I have tried to create a coffee order:
#coffee_order = CoffeeOrder.new(coffee_order_params)
params[:coffee_order][:user_coffee_orders_attributes].each do |user_order|
order = #coffee_order.user_coffee_orders.new(user_id: user_order[1][:user_id].to_i)
user_order[1][:coffee_shop_items].each do |item|
coffee_shop_item = CoffeeShopItems.find(item) if item != ""
# this line fails! see error below
#coffee_order.user_coffee_orders.coffee_shop_items << coffee_shop_item if coffee_shop_item != nil
end
end
error:
NoMethodError (undefined method `coffee_shop_items' for #<UserCoffeeOrder::ActiveRecord_Associations_CollectionProxy:0x42c6180>):
The coffee_shop_items belong to the order, not the user. After all, a user could probably create another order another day? You should probably also check out the rails documentation, which, IIRC actually contains a walk-through of a shopping cart application.
User has_many :coffes_orders
User has_many :coffee_orders_he_needs_to_get, class_name: "CoffeeOrder", foreign_key: "assignee_id"
CoffeeOrder belongs_to :user
CoffeeOrder belongs_to :assignee, class_name: "User"
CoffeeOrder has_and_belongs_to_many :coffee_shop_items
Coffee_shop_items has_and_belongs_to_many :coffee_orders

Having issues building a rails query that includes three associated models

I have three models I'm working with: User, Deal, and Investment.
User
User has many :deals
User has many :investments
Deal
Deal has many :investments
Deal belongs to :user
Investment
Investment belongs to :user
Investment belongs to :deal
(these are the only associations I have set up between these models)
Lets say I have a User record 'u', and Deal has an attribute called funding_type_id.
I want to find all investments made by user 'u' where the investment.deal.funding_type_id == 3.
Or to be more clear: Investments are made on a deal by a user. I was the set of investments made by user 'u' on deals who's funding type id is 3.
I posted this awhile ago, but didn't receive any successful responses. I've made several attempts on my own since then, but all have been met with failure, so I'm back to SO. Hopefully I explained my question clearly. Thanks!
EDIT: My bad, misread the question -- early morning after a late night :)
Try this instead:
investments = Investments.joins(:deal).where(user_id: u.id, deals: { funding_type_id: 3 })
That should generate the following SQL (subbing in 1 for u.id):
SELECT "investments".* FROM "investments"
INNER JOIN "deals" ON "deals"."id" = "investments"."deal_id"
WHERE "investments"."id" = 1 AND "deals"."funding_type_id" = 3
Which should give you the rows you want.
If you set up a has_many :through association (see the Association Basics guide) between User and Deal, you can directly access all the deals belonging to a user:
# user.rb
has_many :deals, through: :investments
You can then get all the deal for a particular user using:
user_deals = User.deals
You can optionally put a where condition on that to limit it the way you want. Where u is the user you want the deals for:
deals = u.deals.where(funding_type_id: 3)

Forem gem: how to link a forum to other models

I have groups (Group model) in my app, which represent groups of people.
I want each group to have its own forum.
Should I just have the forum id in the groups table? It doesn't feel right. If I did it myself, the forum would have a polymorphic association to a "forumable" element (groups in this case, but I have other models that would need a forum).
Any opinions on what I should do? Modify the gem to fit my needs, or just have the forum_id in my models that need a forum? Or another solution maybe?
I'm the guy who started Forem (its the volunteers who did most of the hard work, though!), I think I can answer this question.
If you want only certain groups to have access to one and only one forum then you can put the forum_id field on the groups table and do it that way. What you can do then is override the can_read_forem_forum? method in your User model to perform a permission check for that user:
def can_read_forem_forum?(forum)
groups.where(:forum_id => forum.id).any?
end
This is used in Forem's ability model to determine whether or not a person can access a forum. What this method is going to do is that it will only return groups for that user that have link that specific forum. If there are any, then it's known that the user can access that forum.
Now if you're going the other route where a group may have access to many forums, well then you'd define a joins table between groups and forem_forums (called forum_groups) and define it as an association in your Group model like this:
has_many :forum_groups
has_many :forums, :through => :forum_groups, :class_name => "Forem::Forum"
You would need to also define a new model inside your application for this forum_groups association, it would be called ForumGroup and go a little like this:
class ForumGroup < ActiveRecord::Base
belongs_to :forum, :class_name => "Forem::Forum"
belongs_to :group
end
We're doing it this way so you have an easy way to manage the associations between forums and groups. If you did has_and_belongs_to_many, it generally only provides a gigantic pain in the ass when you want to delete one specific record from that join table.
Now, with that all nicely set up, the method you want to define in your User model is this one:
def can_read_forem_forum?(forum)
groups.joins(:forums).where("forem_forums.id = ?", forum.id).any?
end
Same thing, except this time we find all the groups that are linked to a specific forum through that association we set up earlier. This will do an INNER JOIN on the forum_groups table, and then another on the forem_forums table, getting the data required.
I hope this helps you, and thanks for using Forem!

Best implementation of a multi-model association in rails?

Alright, a Rails Noob here, :D
It looks like has__many :through is the latest greatest way to handle many to many relationships, but I am trying to keep this simple. Hopefully one of you guru's out there have handled this situation before:
Here is the basic model setup I have now:
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :clients
end
class Client < ActiveRecord::Base
has_and_belongs_to_many :products
end
Essentially, I have users in the system, that will have access (through association) to many different products that are being created, those products have many clients, but the clients can also be a part of many products, and the Products accessed by many users.
All of the associations are working well, but now I want users to be able to add clients to their products, but only see clients that are associated with products they have access too.
Scenario:
Given Bob has access to product A and B
And does NOT have access to product C
And and has clients on product B
And wants to add them to product A.
When in product A Bob should see clients from product B in his add list,
And Bob should not see clients from product C
My noobish experience with rails fails to give me the experience on how to best build the array that will hold his client list.
The way I am thinking be to use #bob.products to get the products Bob has access to then to .each those and find the clients associated with each product and then join them into a single array. But is this the BEST way?
Thanks!
Not sure if this is what you're looking for, but if you want to remove all non-authorized clients for a particular user:
user = current_user
#clients_access = Array.new
user.products.each { |p| #clients_access.push(p.clients).uniq! }
#clients_access.flatten!
Alright so I achieved the functionality I wanted by the following:
user = current_user
#clients_no_access = Client.find(:all, :order => :business_name)
user.products.each do |product|
#clients_no_access -= product.clients
end
#all_clients = Client.find(:all,
:order => :business_name) - #clients_no_access - #product.clients
Basically, finding all the the clients, then iterating through the linked authorized products and removing them from the list, basically creating a list of non-authorized clients.. then doing the search again and clearing out the non-authorized clients and the clients already assigned in the group.. But, I have ran out of duct-tape.. any better solutions?

select many through many... kind of

This is my first post on Stack, so please bear with me if I breach any protocol.
I'm working on a project in Rails (2.1.2), and I have a relations scenario that looks like this:
event [has_many] days
People (in different categories) can sign up for event days, giving the following binding results:
category [has_many] customers [has_many] orders [has_many] days
[belongs_to] event
Now, I'd like to have the total number of 'events' for one customer, or for all customers in a certain category, and I'm stuck. AFAIK, there's no way of performing a simple 'find' through an array of objects, correct? So, what would be the solution; nested loops, and a collect method to get the 'events' from the 'days' in orders?
Please let me know if I'm unclear.
Thanks for your help!
I would personally do this using a MySQL statement. I don't know for sure, but I think it is a lot faster then the other examples (using the rails provided association methods).
That means that in the Customer model you could do something like:
(Note that I'm assuming you are using the default association keys: 'model_name_id')
class Customer
def events
Event.find_by_sql("SELECT DISTINCT e.* FROM events e, days d, orders o, customers c WHERE c.id=o.customer_id AND o.id=d.order_id AND e.id=d.event_id")
end
end
That will return all the events associated with the user, and no duplicated (the 'DISTINCT' keyword makes sure of that). You will, as with the example above, lose information about what days exactly the user signed up for. If you need that information, please say so.
Also, I haven't included an example for your Category model, because I assumed you could adapt my example yourself. If not, just let me know.
EDIT:
I just read you just want to count the events. That can be done even faster (or at least, less memory intensive) using the count statement. To use that, just use the following function:
def event_count
Event.count_by_sql(SELECT DISTINCT COUNT(e.*) FROM ... ... ...
end
Your models probably look like this:
class Category
has_many :customers
class Customer
has_many :orders
has_many :days, :through => :orders # I added this
belongs_to :category
class Order
has_many :days
belongs_to :customer
class Day
belongs_to :event
belongs_to :order
class Event
has_many :days
With this you can count events for customer:
events = customer.days.count(:group => 'event_id')
It will return OrderedHash like this:
#<OrderedHash {1=>5, 2=>13, 3=>0}>
You can get events count by:
events[event_id]
events[1] # => 5
events[2] # => 13
etc.
If you want total number of uniq events:
events.size # => 3
In case of counting events for all customers in category I'd do something like this:
events = {}
category.customers.each {|c| events.merge!(c.days.count(:group => 'event_id') ) }
events.size # => 9 - it should be total number of events
But in this case you lose information how many times each event appeared.
I didn't test this, so there could be some mistakes.

Resources