Finding all users with specific attribute in a through relation - sql

See the details below to make sense.
I am trying to write a line of code that will be searching the database and show the users that have a specific skill.
My clue is that I should be using something like #user.where('skill = ?', skillvariable) or a specific query. Any point/guidance to the right direction is greatly appreciated.
Details:
I have a users model, a skills model and a user_skills model.
In user.rb I have the relation has_many :skills, through: :user_skills.
In the skill.rb I have the relations has_many :user_skills and has_many :users, through: :user_skills,
and then in user_skills.rb I have the belongs_to :user and belongs_to :skill.
The user_skills schema is:
t.integer "user_id"
t.integer "skill_id"
And the skills schema is:
t.string "name"
t.string "slug"

You can simply do:
User.includes(:skills).where(skills: { name: 'Archery' })
This will letterally:
Retrieve all users having at least one skill named 'archery'.
Similar questions:
association named not found perhaps misspelled issue in rails association
Rails active record querying association with 'exists'
Rails 3, has_one / has_many with lambda condition
Rails 4 scope to find parents with no children
Join multiple tables with active records
Rails: Finding all Users whose relationship has a specified attribute

Related

Multiple associations on same schemas

Using Ecto v2.2.6, phoenix 1.3
I have a scenario in which a user can create posts, and then other users can like posts. Users have a one-to-many relationship with posts via creation, and a many-to-many relationship with likes, via a linking table.
Setup:
mix phx.gen.json Account User users name:string
mix phx.gen.json Content Post posts title:string content:text user_id:references:users
mix phx.gen.json Content Like likes user_id:references:users post_id:references:posts
Schemas:
schema "users" do
field :name, :string
has_many :posts, SocialNetwork.Content.Post, foreign_key: :users_id
many_to_many :posts, SocialNetwork.Content.Post, join_through: "likes"
timestamps()
end
schema "posts" do
field :content, :string
field :title, :string
belongs_to :user, SocialNetwork.Accounts.User
many_to_many :users, SocialNetwork.Accounts.User, join_through: "likes"
timestamps()
end
schema "likes" do
belongs_to :user, SocialNetwork.Accounts.User
belongs_to :post, SocialNetwork.Content.Post
timestamps()
end
When I run mix phx.server, I get this error:
== Compilation error in file lib/social_network/account/user.ex ==
** (ArgumentError) field/association :posts is already set on schema
Is there a way that I can set up more than one association to the same schema, but through a different context?
Is there a way that I can set up more than one association to the same schema, but through a different context?
Yes, but you'll have to choose a different name for the two associations, like this:
has_many :posts, SocialNetwork.Content.Post, foreign_key: :users_id
many_to_many :liked_posts, SocialNetwork.Content.Post, join_through: "likes"
You shouldn't need to modify anything in Post since Post already uses different names for the two associations.

Rails news feed for comments

I updated the question because I combined following locations and users into one polymorphic model.
So I am trying to make a news feed for comments in my app.
I have a user model, a model for following locations and users. And then I have a model for comments.
How do I grab the comments from users that a user follows and comments from a location that a user follows and put these together?
I need this to be paginated as well as sorted by the created_at time stamp ( showing newest first).
Heres what my migrations look like
create_table :comments do |t|
t.text :text
t.integer :user_id
t.integer :commentable_id
t.string :commentable_type
create_table :follows do |t|
t.integer :user_id
t.integer :followable_id
t.string :followable_type
And this is what my models look like
class User < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
update:
I found something called a CASE statement in sql (like an if statement) How do I perform an IF...THEN in an SQL SELECT?
I am still unsure of how to write this query but I think the CASE statement may help.
If I'm gathering your question correctly you might see something like this for a model:
class User < ActiveRecord::Base
has_many :comments, as: :commentable, dependent: :nullify
has_many :follows, dependent: :destroy
has_many :followed_users, class_name: 'Follow', conditions: {followable_type: 'User'}
has_many :followed_locations, class_name: 'Follow', conditions: {followable_type: 'Location'}
has_many :followers, as: :followable, dependent: :destroy
end
And in your controller you might see something like this:
# See all comments made by those you follow (and yourself)
# See all comments made on locations you follow
# See all comments made on users you follow
#comments = Comment.where(
'user_id in (:followed_user_ids) or (commentable_type = 'Location' and commentable_id in (:followed_location_ids)) or (commentable_type = 'User' and commentable_id in (:followed_user_ids))',
{
followed_user_ids: current_user.followed_users.pluck(:user_id) | [current_user.id],
followed_location_ids: current_user.followed_locations.pluck(:location_id)
}
).paginate(page: params[:page]).order(created_at: :desc)
As this gets more complex, and it will get much more complex, look into scopes and merging. Probably pulling this method out into a search class somewhere.

Rails Joining together multiple tables

So I am trying to create a news feed of sorts but I am unsure as to how to make the queries.
I have a user model, a model for followed locations and a model for followed users. And then I have a model for comments. I need to grab all the comments from users that a user follows and all comments from a location that a user follows and I have to put these together.
I'm not that familiar with how to do this in sql or rails. Can anyone link me to an article or the docs where I might find how to do this?
If you need more information just comment what else I should include because I was unsure what to include in the post.
The comments model looks like this and it is polymorphic and can be posted to locations and events
create_table :comments do |t|
t.text :text
t.integer :user_id
t.integer :commentable_id
t.string :commentable_type
And then there is two separate tables for following users and following locations
create_table :followed_locations do |t|
t.integer :user_id
t.integer :location_id
create_table :followed_users do |t|
t.integer :user_id
t.integer :followed_id
Here's how the model associations would look:
class User < ActiveRecord::Base
has_many :comments, as: :commentable
has_many :followed_locations
has_many :followed_users
def followed_items
followed_locations.map(&:location).flatten + followed_users.map(&:followed).flatten
end
end
class Location < ActiveRecord::Base
has_many :comments, as: :commentable
end
class FollowedUser < ActiveRecord::Base
belongs_to :user
belongs_to :followed, class_name: 'User'
end
class FollowedLocation < ActiveRecord::Base
belongs_to :user
belongs_to :location
end
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
The code above defines the relationships among all the models, and adds one User instance method to collect all the items (locations or users) that a given user follows. Now you can gather all the comments for users/locations that a single user is following, like so:
user.followed_items.map(&:comments).flatten
This will gather all the followed items for a user (both locations and other users), get a list of all their comments, then flatten them into a simple array. If you want to sort them, my creation for example, tack that onto the end:
user.followed_items.map(&:comments).flatten.sort_by(&:created_at)
There are ways to optimize this, but at this point you probably just want to focus on getting the concepts down.
UPDATE:
I've created a simple Rails 4 app that implements this solution, and published it on github:
https://github.com/rubycuts/so26169791

Extra Role column in join table in Rails

I have users and companies in a many to many relationship by a join table which has a column for user Role. I'm not sure if this is the best way to have the model set up.
Now each user can have different roles depending on the company, what is the best way to design and access user's role using ActiveRecord associations?
I would like to return via JSON the user's role based on their current company and default to something if their company is nil OR their role has not been set (nil).
Update:
What I've got now after reading Many-to-many relationship with the same model in rails? which is a bit different (many to many on itself).
CompaniesUser
belongs_to :company
belongs_to :user
Company
has_many(:companies_users, :dependent => :destroy)
has_many :users, :through => :companies_users
User
has_one :company
has_many(:companies_users, :dependent => :destroy)
has_many :companies, :through => :companies_users
Appreciate any advice as I'm just starting to learn this!
What you have above is correct, in terms of the ActiveRecord relationships. If you'd like to read more on the subject I believe this is the best source: http://guides.rubyonrails.org/association_basics.html
One problem I see there is that CompaniesUsers should be in singular form: CompanyUser, and then in all cases where you use :companies_users use: :company_users
I am assuming here that the current company of the User is the last one assigned.
Now in order to serialize in JSON format you should add the following in your User ActiveRecord:
def serializable_hash(options = nil)
options ||= {}
h = super(options)
if(defined?self.company_users.last and defined?(self.company_users.last).role)
h[:role] = (self.company_users.last).role
else
h[:role] = 'default_value'
end
end

Ruby on Rails: has_many referential --which model objects does it own?

I am new to Rails and finished Michael Hartl's "Ruby on Rails 3 Tutorial". Although the book teaches me a lot, I find this puzzle I don't understand.
To preview the puzzle, that is, I don't understand, inside User model,
has_many :following, :through=>:relationship, :source=>:followed
how this piece of code link "user.following" to an array of User instances.
And below is the whole puzzle.
First of all, I have the Relationship model, which records followed_id and follower_id infos. Inside Relationship model, the association is simple as
class Relationship < ActiveRecord::Base
attr_accessible :followed_id
belongs_to :follower, :class_name => "User"
belongs_to :followed, :class_name => "User"
end
Then, inside the User model, a user will assume the role of follower, and collect all its following rows in relationships table through relationships association.
class User < ActiveRecord::Base
.
.
.
has_many :relationships, :foreign_key => "follower_id", :dependent => :destroy
.
Until now, I got it.
But confusion came at the next line, where through user.following it can assemble all that user's following(User instances). Like so,
has_many :following, :through=>:relationships, :source=>:followed
I understand that :source=>:followed will overwrite the default, and let find all followed_ids associated with that user.
But, how can Rails recognize followed_id to link to User object? The label name doesn't match users, nor is there :class_name specified. I just don't get how Rails do this underlying work, or I missed out some hints.
Thank you! :)
But, how can Rails recognize followed_id to link to User object? The
label name doesn't match users, nor is there :class_name specified. I
just don't get how Rails do this underlying work, or I missed out some
hints.
Rails recognize that is an user object because it is set in Relationship's belongs_to. What Rails does here is to follow the relationship class through the foreign key "follower_id" and returning every User that has a relationship with the current user as followed. Of course Rails do that in a single SQL statement like this:
SELECT `users`.* FROM `users` INNER JOIN `relationships` ON `relationships`.followed_id = `users`.id WHERE ((`relationships`.follower_id = <the current user id> ))
has_many :following, :through=>:relationships, :source=>:followed
This explains to Rails that following is the inverse relationship of following and that users has many following and followed through his relationships.
The way Rails knows that followed_id is linked to User is that it is defined in your Relationship model.
Hope you've understood ! Good luck :)