model/sql joins in rails 3 - ruby-on-rails

How can I join the objects with the models below?
User
has_many messages
Message
belongs_to user
Thread
has_many messages
I'm trying to get all threads that belong to user X. I would like to join on message.user_id = user.user_id and thread.message_id = message.message_id. I can do this using find_by_sql but I'm trying to avoid that.
Thanks

Assuming your Message model also belongs_to :thread, then you should be able to put a has_many :threads, :through => :messages in your User model. Then you could just do user.threads to get all the associated threads.

The following should work: (note that I've added your missing message belongs_to :thread)
class User < ActiveRecord::Base
has_many :messages
has_many :threads, :through => :messages
end
class Message < ActiveRecord::Base
belongs_to :user
belongs_to :thread
end
class Thread < ActiveRecord::Base
has_many :messages
has_many :users, :through => :messages
end
This should allow you to do my_user.threads and my_thread.users.
You can read more about has_many :through on the Rails Guides and the API.

Related

How to Model a multi-participant conversation system

I am new to Rails (and programming) and after going through a few tutorials, I am designing a messaging app to test my skills.
The situation I am modelling is where a user can message 2+ other users. Here's what I came up with
A conversation has many participants (users) and many messages
(message).
A user has many conversation and many messages.
A message belongs to users (sender + recipients) and belongs to a conversation.
The ActiveRecord model is then:
class User < ActiveRecord::Base
has_many :messages, :through :conversation
has_many :conversations # or is belongs_to :conversation
end
class Message < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
end
class Conversation < ActiveRecord::Base
has_many :messages
belongs_to :user # or is it has_many :users
end
Or do I have to add a 4th interface inbox
class Inbox < ActiveRecord::Base
belongs_to :user
has_many :conversations
end
and I would change the user and conversation models to be
class User < ActiveRecord::Base
has_one :inbox
has_many :conversations, :through :inbox
has_many :messages, :through :conversation
end
class Conversation < ActiveRecord::Base
belongs_to :inbox
has_many :messages
belongs_to :users
end
The second option looks redundant.
So yeah, I am fuzzy about the relationship/association between a conversation and users. All input to enlighten me is greatly appreciated.
Your first way is better in my opinion, but it should look like this:
class User < ActiveRecord::Base
has_many :messages
has_many :user_conversations
has_many :conversations, through: :user_conversations
end
class Message < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
end
class Conversation < ActiveRecord::Base
has_many :messages
has_many :user_conversations
has_many :users, through: :user_conversations
end
# join table between users and conversations
class UserConversation < ActiveRecord::Base
belongs_to :user
belongs_to :conversation
end
The reason is that a user can have many conversations, and a conversation can have many users. You need to create the join table UserConversation to accommodate this.
Also, the easiest way to find out which model should belong to the other model is to ask which model should have the foreign key of the other model. The one with the foreign keys belongs_to the other model.

How to get records through a many-to-many association?

I have these models in my Rails 4 app:
class Invoice < ActiveRecord::Base
has_many :allocations
has_many :payments, :through => :allocations
end
class Allocation < ActiveRecord::Base
belongs_to :invoice
belongs_to :payment
end
class Payment < ActiveRecord::Base
has_many :allocations
has_many :invoices, :through => :allocations
end
How can I get all the invoices that are related to one particular payment X?
I've been trying to get my head around this all day but to no avail.
Thanks for any help.
As simple as:
payment.invoices
This will follow your association, passing through the allocation model.
Try this,
Identify the Payment and then its invoices like this
payment_invoices = Payment.find(payment_id).invoices

Rails: stringing together multiple has_many relationships

I have three models which look something like this:
Class User < ActiveRecord::Base
has_many :comments
end
Class Comment < ActiveRecord::Base
belongs_to :user
has_many :votes
end
Class Vote < ActiveRecord::Base
belongs_to :comment
end
Now I want to get all the votes associated with a user's comments like so:
#user.comments.votes
But this throws the error:
undefined method `votes' for #<ActiveRecord::Relation:0x3f6f8a0>
This seems like it should work, but I suspect ActiveRecord is coughing on the deeper has_many relationship. I've hacked together an SQL query that gets the desired results, but I suspect there's a cleaner way using purely ActiveRecord. Any tips?
You should use a has_many :through association
In your case it would be
Class User < ActiveRecord::Base
has_many :comments
has_many :votes, :through => :comments
end
Class Comment < ActiveRecord::Base
belongs_to :user
has_many :votes
end
Class Vote < ActiveRecord::Base
belongs_to :comment
end
And then simply get the votes with
#user.votes
Try this:
Vote.joins(comment: :user).where(users: {id: #user.id})

Fetching records through multiple tables

The image shows part of my data model. I would like to fetch all items that are associated with a user (through organizations and items_group). How should I change the models and write this query in the controller? Using :through => organizations I can get all items_groups but I don't how to include one more relation to query related items.
class User < ActiveRecord::Base
has_and_belongs_to_many :organizations
has_many :items_groups, :through => :organizations
end
class Organization < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :items_groups
has_many :items, :through => :items_groups
end
class ItemsGroup < ActiveRecord::Base
has_many :items, :inverse_of => :items_group
has_and_belongs_to_many :organizations
has_many :users, :through => :organizations
end
I think you might have to do it back-to-front and find items joined back to your user.
You could define method like this in your User class:
def items
Item.joins(:items_group => {:organizations => :users}).where("users.id" => self.id).select("distinct items.*")
end
The items it returns will be read-only because of the explicit select but I think you'll want that to avoid returning individual items more than once.
If you set in your models the relationships this should work:
users.organizations.item_groups.items
Though for it to work your models should contain this:
class User < ActiveRecord::Base
has_many :organizations, :through => :organization_users
end
class Organization < ActiveRecord::Base
has_many :item_groups, :through => :items_groups_organizations
end
class ItemsGroup < ActiveRecord::Base
belongs_to :item
end
Hope it works for you!

How do you model "Likes" in rails?

I have 3 models: User, Object, Likes
Currently, I have the model: a user has many Objects. How do I go about modeling:
1) A user can like many objects
2) an Object can have many likes (from different users)
So I want to be able to do something like this:
User.likes = list of objects liked by a user
Objects.liked_by = list of Users liked by object
The model below is definitely wrong...
class User < ActiveRecord::Base
has_many :objects
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
belongs_to :users
has_many :users, :through => :likes
end
To elaborate further on my comment to Brandon Tilley's answer, I would suggest the following:
class User < ActiveRecord::Base
# your original association
has_many :things
# the like associations
has_many :likes
has_many :liked_things, :through => :likes, :source => :thing
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :thing
end
class Thing < ActiveRecord::Base
# your original association
belongs_to :user
# the like associations
has_many :likes
has_many :liking_users, :through => :likes, :source => :user
end
You are close; to use a :through, relation, you first must set up the relationship you're going through:
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Likes < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Objects < ActiveRecord::Base
has_many :likes
has_many :users, :through => :likes
end
Note that Objects should has_many :likes, so that the foreign key is in the right place. (Also, you should probably use the singular form Like and Object for your models.)
Here is a simple method to achieve this. Basically, you can create as many relationships as needed as long as you specify the proper class name using the :class_name option. However, it is not always a good idea, so make sure only one is used during any given request, to avoid additional queries.
class User < ActiveRecord::Base
has_many :likes, :include => :obj
has_many :objs
has_many :liked, :through => :likes, :class_name => 'Obj'
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :obj
end
class Obj < ActiveRecord::Base
belongs_to :user
has_many :likes, :include => :user
has_many :users, :through => :likes
# having both belongs to and has many for users may be confusing
# so it's better to use a different name
has_many :liked_by, :through => :likes, :class_name => 'User'
end
u = User.find(1)
u.objs # all objects created by u
u.liked # all objects liked by u
u.likes # all likes
u.likes.collect(&:obj) # all objects liked by u
o = Obj.find(1)
o.user # creator
o.users # users who liked o
o.liked_by # users who liked o. same as o.users
o.likes # all likes for o
o.likes.collect(&:user)
Models & associations as per naming conventions of rails modeling
class User < ActiveRecord::Base
has_many :likes
has_many :objects, :through => :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :object
end
class Object < ActiveRecord::Base
belongs_to :user
has_many :likes
has_many :users, :through => :likes
end
Also, you can use of already built-in gems like acts-as-taggable-on to have same functionality without code :)

Resources