I have the following polymorphic association...
class Activity < ActiveRecord::Base
belongs_to :owner, polymorphic: true
end
class User < ActiveRecord::Base
has_many :activities, as: :owner
end
class Manager < ActiveRecord::Base
has_many :activities, as: :owner
end
I am trying to make a query whereby it only pulls out the activities where the owner (user or manager) has visible set to true.
I have figured out that if I want to do this for one of the owners, I can do this as follows...
Activity.joins("INNER JOIN users ON activities.owner_id = users.id").where(:activities => {:owner_type => 'User'}).where(:users => {:visible => true})
But I cannot figure out how to do it for both. Can anyone help?
This should work:
Activity.
joins("LEFT JOIN users ON activities.owner_type = 'User' AND
activities.owner_id = users.id").
joins("LEFT JOIN managers ON activities.owner_type = 'Manager' AND
activities.owner_id = managers.id").
where("users.visible = ? OR managers.visible = ?", true, true)
Related
I have the following models:
class Conversation < ActiveRecord::Base
belongs_to :sender, foreign_key: :sender_id, polymorphic: true
belongs_to :receiver, foreign_key: :receiver_id, polymorphic: true
.
class Owner < ActiveRecord::Base
.
class Provider < ActiveRecord::Base
.
class Account < ActiveRecord::Base
has_one :provider, dependent: :destroy
has_many :owners, dependent: :destroy
So in a Conversation, sender can be an Owner or a Provider. With this in mind, I can make queries like:
Conversation.includes(sender: :account).limit 5
This works as intended. The problem is when I want to use a where clause in associated model Account. I want to filter conversations which associated account's country is 'US'. Something like this:
Conversation.includes(sender: :account).where('accounts.country' => 'US').limit 5
But this wont work, I get the error ActiveRecord::EagerLoadPolymorphicError: Cannot eagerly load the polymorphic association :sender
What is the correct way of doing this kind of query?
I've also tried to use joins, but I get the same error.
I've found myself a solution, in case anyone else is interested. I ended up using joins with a SQL query:
Conversation.joins("LEFT JOIN owners ON owners.id = conversations.sender_id AND conversations.sender_type = 'Owner'
LEFT JOIN providers ON providers.id = conversations.sender_id AND conversations.sender_type = 'Provider'
LEFT JOIN accounts ON accounts.id = owners.account_id OR accounts.id = providers.account_id").
where('accounts.country' => 'US').limit 5
I have two models, Group and User. A user can create many groups and is then the owner of those groups. Other users can join those groups and therefore become members of those groups.
My question now is how to model this with Ruby on Rails.
I have created a join table between Group and Users and I added a reference from Group to the owner (foreign id to user).
Note: not sure if I did all this correctly.
Here are the attributes I currently have in Group and User (I don't know how to access the join table, so I can't show it to you).
Group: id, topic, home_town, created_at, updated_at, user_id
user_id was added with the following migration and I want it to reference the owner:
class AddOwnerReferenceToGroup < ActiveRecord::Migration
def change
add_reference :groups, :user, index: true
end
end
User: id, name, email, password_digest, remember_token, created_at, updated_at
As for the relationships, here's what each class contains:
User -> has_many :groups
Group -> has_many :users
Is it possible (and do I have) to add a belongs_to relationship in the group class to reference to the owner?
I would set it up like so:
class User < ActiveRecord::Base
has_many :group_users, :dependent => :destroy
has_many :groups, :through => :group_users
has_many :owned_groups, :through => :group_users, :class_name => "Group", :conditions => ["group_users.owner = ?", true]
...
end
class Group < ActiveRecord::Base
has_many :group_users, :dependent => :destroy
has_many :users, :through => :group_users
def owner
self.users.find(:first, :conditions => ["group_users.owner = ?", true])
end
def owner=(user)
if gu = self.group_users.find_by_user_id(user.id)
gu.update_attributes(:owner => true)
else
self.group_users.create(:user_id => user, :owner => true)
end
end
...
end
#group_users table has fields user_id, group_id, owner(bool)
class GroupUser < ActiveRecord::Base
belongs_to :group
belongs_to :user
after_save :enforce_single_owner
def enforce_single_owner
if self.changes["owner"] && self.owner
GroupUser.find(:all, :conditions => ["group_id = ? and id <> ? and owner = ?", self.group_id, self.id, true]).each{|gu| gu.update_attributes(:owner => false)
end
end
...
end
In this schema, the join table model has responsibility for tracking which of the members of the group is the owner of the group. A group has many users, and one of those wil be the owner.
Yes, it is possible and you should.
By adding to your Group
belongs_to :owner, class_name: 'User'
you can model your requirement that a group is created by one of the users.
In my snippet I used an arbitrary name in belongs_to just to make the code more readable. You don't have to but it definitely clears up things. The downside is that ActiveRecord cannot guess to which class you refer when using custom association names, thus, you need to manually specify it.
In a similar fashion you could change has_many :users to
has_many :members, class_name: 'User'
Whatever feels more natural when you read your code.
I have 3 different Models
class GroupMember < ActiveRecord::Base
attr_accessible :group_id, :user_id, :owner_id
has_one :user
end
class Group < ActiveRecord::Base
attr_accessible :name, :owner, :permission
has_many :groupMembers
end
class User < ActiveRecord::Base
end
and when im in the groups_controller.rb i want realize the following query with associations
SELECT * FROM groups
LEFT JOIN group_members ON groups.id = group_members.group_id
LEFT JOIN users ON group_members.user_id = users.id WHERE users.id = 1
or the joins query
Group.joins('LEFT JOIN group_members ON groups.id = group_members.group_id LEFT JOIN users ON group_members.user_id = users.id').where('users.id = ?', current_user.id );
Is this possible?
Group.joins(:user_id => current_user.id)
I believe it is possible but it is a bit difficult to test as soon as I don't have a DB with these tables. Probably something like:
Groups.joins(:group_members, :user).merge(User.where(:id => current_user.id))
But the design is a bit strange. I suppose you want to fetch all groups for a certain user, and it is best put like this:
#user = User.find_by_id(current_user.id)
#groups = #user.groups
for this to work you should have groups association in your User model.
It is also unclear for me why you have GroupMember has_one :user. I understand that group member is related to only one user, but can a user be represented as several group members, of different groups? If yes, the Rails way to design it would be:
class GroupMember < ActiveRecord::Base
attr_accessible :group_id, :user_id, :owner_id
belongs_to :user
belongs_to :group
end
class Group < ActiveRecord::Base
attr_accessible :name, :owner, :permission
has_many :group_members
has_many :users, :through => :group_members
end
class User < ActiveRecord::Base
has_many :group_members
has_many :groups, :through => :group_members
end
I want to be able to select users who have multiple roles:
User < ActiveRecord::Base
has_and_belongs_to_many :roles
end
Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
I know how to select users with either of two roles:
User.joins(:roles).where(roles: { name: [:admin, :manager] })
But how do I find all users who have at least :admin AND :manager roles?
This will work:
users = User.joins(:roles)
users.where("roles.name" => "admin") & users.where("roles.name" => "manager")
Note that this produces two SQL load queries, which I think may be inevitable for this type of search. (Your alternative solution also makes two SQL queries.) Also note that it returns an array rather than an activerecord relation, which may not be what you want.
Here is a working solution:
User.select("DISTINCT users.*").
joins("JOIN users_roles a ON a.user_id = users.id").
joins("JOIN roles b ON b.id = a.role_id").
joins("JOIN users_roles c ON c.user_id = users.id").
joins("JOIN roles d ON d.id = c.role_id").
where("b.name = ? and d.name = ? ", :admin, :manager)
This works but feels wrong:
Role.where(name: :admin).first.users.joins(:roles).where(roles: { name: :manager } )
If you set up the association as has_many :through, you can count the unique roles by user:
class User < ActiveRecord::Base
has_many :user_roles
has_many :roles, :through => :user_roles
end
# Note: add an id column on the join table
class UserRole < ActiveRecord::Base
belongs_to :user
belongs_to :role
end
class Role < ActiveRecord::Base
has_many :user_roles
has_many :users, :through => :user_roles
end
User.joins(:user_roles => :role).
select("users.*, COUNT(DISTINCT user_roles.id) as user_role_count").
where("roles.name IN ('Admin','Manager')").
group("users.id").
having("user_role_count = 2")
Right now I'm building a social media app, where i want an user to have a rating per category, how would the association go? The way it needs to be setup it's Each user will have a different rating in each category.
I'm think that
belongs_to :user
belongs_to :category
in the UserCategoryRating model.
and
has_many :user_category_ratings, through => :category
on the User model, Is this the correct approach?
The UserCategoryRating table has the User_id column, Category_id column, and the rating column, that updates each time an user gets votes (The rating it's just the AVG between votes and the score based on 1-5)
UPDATE: If I'm understanding you correctly, here is a diagram of the simple design you'd like:
And this would be the basic skeleton of your classes:
class User < ActiveRecord::Base
has_many :ratings
# has_many :categories, :through => :ratings
end
class Category < ActiveRecord::Base
has_many :ratings
# has_many :users, :through => :ratings
end
class Rating < ActiveRecord::Base
belongs_to :user
belongs_to :category
validates_uniqueness_of :user_id, :scope => [:category_id]
end
Will allow for these query:
#category_ratings_by_user = Rating.where("ratings.user_id = ? AND ratings.category_id = ?", user_id, category_id)
#specific_rating = user.ratings.where("ratings.category_id = ?", category_id)
# make nice model methods, you know the deal
# ... if you added the has_many :through,
#john = User.find_by_name("john")
# Two ways to collect all categories that john's ratings belong to:
#johns_categories_1 = #john.ratings.collect { |rating| rating.category }
#johns_categories_2 = #john.categories
#categories_john_likes = #john.categories.where("categories.rating >= ?", 7)
I'm just unsure as to why you want this has_many, :through (this doesn't seem like a many to many -- a rating only belongs to one user, correct?).
I will use the following data model:
class User
has_many :user_categories
has_many :categories, :through => :user_categories
end
class UserCategory
belongs_to :user
belongs_to :category
# this model stores the average score also.
end
class Category
has_many :user_categories
has_many :users, :through => :user_categories
end
Now when you want to update the score of a user for a category
uc = u.user_categories.find_by_category_id(id)
uc.score = score
uc.save