Search users based on multiple elements - ruby

I am trying to impliment search functionality for user records with first_name, last_name, email, project_name,feature_name.
Here first_name, last_name and email is from one table(User), project_name from table Project and feature_name from table Feature.Association of models are given below.I have a user index page where lists all users from table User. Need a search which search for users which we are entering.
model user.rb:
class User < ApplicationRecord
has_many :project_users, dependent: :destroy
has_many :projects, through: :project_users, dependent: :destroy
end
User have fields of first_name, last_name, email etc(using these three fields for search)
model project.rb
class Project < ApplicationRecord
belongs_to :user
has_many :project_users, dependent: :destroy
has_many :features, dependent: :destroy
has_many :users, through: :project_users, source: :user
end
Project have project_name(we search using project name)
model feature.rb
class Feature < ApplicationRecord
belongs_to :project
end
Feature have feature_name(with feature_name we need to search)
What I am look for
We have params[:search_member] which contains the searched item(first_name, last_name, email, project_name, feature_name
For ex:
params[:search_member] = "John"
params[:search_member] = "Project1"
params[:search_member] = "Feature1"
params[:search_member] = "john#gmail.com"
Need a single query which checks the "params[:search_member]" in these three tables(User, Project and Feature) in fields first_name, last_name, email, project_name, and feature_name and return the users of searched value.
Working of associations
current_user.projects # will return all projects belongs to current user
project.users # return all users belongs to project
feature.project # return project that feature belongs to
and
feature.project.users # will return all users of projects
def search_all
if params[:search_member].present?
#need query here
else
User.all
end
end
If I enter project_name it will return all users of that particular project
If I enter first_name, last_name or email return all users of this details
If I enter feature name, return all users of project that the feature belongs to
Trying to do in a single joins query

I would try this
def search_all
if params[:search_member].present?
User.includes(projects: :features)
.where(first_name: params[:search_member])
.or(User.where(last_name: params[:search_member])
.or(User.where(email: params[:search_member])
.or(Project.where(project_name: params[:search_member]))
.or(Feature.where(feature_name: params[:search_member]))
else
User.all
end
end
and if that works as expected, then I would refactor the different subqueries to scopes in the models to make is easier to read.

I suggest using pg_search gem (better quality, cleaner code) and then it can be like that:
class User < ApplicationRecord
has_many :project_users, dependent: :destroy
has_many :projects, through: :project_users, dependent: :destroy
has_many :features, through: :projects
pg_search_scope :search_member,
against: [:first_name, :last_name, :email],
associated_against: {
projects: :name,
features: :name
}
end
And then:
def search_all
if params[:search_member].present?
User.search_member(params[:search_member])
else
User.all
end
end

Solution
if params[:search_member].present
search = params[:search_member]
User.joins(projects: :features).where('users.first_name ILIKE :search
OR users.last_name ILIKE :search
OR users.email ILIKE :search
OR projects.project_name ILIKE :search
OR features.name ILIKE :search', search: "%#{search}%").references(:projects).uniq
else
User.all
end

Related

How to use Rails include?

I have a model user with many orders
class User < ApplicationRecord
has_many :orders, inverse_of: :user, dependent: restrict_with_exception
end
and a model order as follows :-
Class Order < ApplicationRecord
belongs_to :user, inverse_of: :order
end
I want to fetch all orders but with details of user like user.name and user.mobile in same set. How do I use include in this case?
You can use includes using the below mentioned query:
#users = User.where(id: 1).includes(:orders)
then iterate over #users and fetch the corresponding user and order data.
Also, you could use lazy loading as well using the below query:
#users = User.where(id: 3).joins(:orders => [:order_data]).select("orders.*, users.first_name")
In this you will be getting all the data in the single query without rails caching the db objects in memory as in the case of includes.

ActiveRecord - Finding all objects with shared attributes in a join model

I have three models
class Boat < ActiveRecord::Base
belongs_to :captain
has_many :boat_classifications
has_many :classifications, through: :boat_classifications
end
class Classification < ActiveRecord::Base
has_many :boat_classifications
has_many :boats, through: :boat_classifications
end
class BoatClassification < ActiveRecord::Base
belongs_to :boat
belongs_to :classification
end
I'm trying to write a simple ActiveRecord query to find all the boats of type sailboat. Something like Boat.where(classifications: "Sailboat")
I think this could work:
Boat.joins(:classifications).where(classifications: { name: 'Sailboat' }) # name or whatever field contains Sailboat
Generates this query:
SELECT `boats`.* FROM `boats` INNER JOIN `boat_classifications` ON `boat_classifications`.`boat_id` = `boats`.`id` INNER JOIN `classifications` ON `classifications`.`id` = `boat_classifications`.`classification_id` WHERE `classification`.`name` = 'Sailboat'
I think you want something like this:
Boat.includes(:classifications).where(classifications: {id: Classification.sailboats})
For this to work, you also need a scope on Classification like this:
def self.sailboats
where(name: "Sailboat")
end

belongs_to and has_many of the same items

I'm modeling a lessons table, the lesson belongs to a user, the teacher and creator of the lesson, and also, the lesson can have many students, which are also users.
So it would be something like this
class Lesson < ActiveRecord::Base
belongs_to :user
has_many :users
end
I'd like to call the first user teacher, and the collection of users students, I've read the documentation at http://guides.rubyonrails.org/association_basics.html but I can't quite find what I want.
This should have what you want: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-belongs_to
I think you want the class_name option:
class Lesson < ActiveRecord::Base
belongs_to :teacher, class_name: "User"
has_many :students, class_name: "User"
end
In your current code, all users could be the "owner" (teacher) of a lesson, instead you should have two additional classes "student" and "teacher" both having a 1:1 relation to the "user" class.
This would fit better:
class Teacher < ActiveRecord::Base
has_one :user
end
class Student < ActiveRecord::Base
has_one :user
end
class Lesson < ActiveRecord::Base
belongs_to :teacher
has_many :students
end

Ruby on Rails: Associations when a user likes a song

I'm trying to figure out the best way to setup my database and models for the following scenario.
A user can like an infinite number of songs.
A song can be liked once by an infinite number of users.
I have these tables:
songs, users, likes etc... Following RoR conventions.
The table named likes has these foreign keys: user_id, song_id. And also a field named 'time' to save a timestamp when the song was liked.
I'm not sure of how to do this, I would like to be able to use code like this in my controllers:
User.find(1).likes.all
This should not return from the likes table, but join the songs table and output all the songs that the user likes.
What are the best practises to achieve this in Ruby on Rails following their conventions?
Unless you need to act specifically on the likes table data, the model itself is probably not necessary. The relationship is easy:
class User < ActiveRecord::Base
has_and_belongs_to_many :songs
end
class Song < ActiveRecord::Base
has_and_belongs_to_many :users
end
This will join through the currently non-existent song_users table. But since you want it to join through likes you can change each one to this:
has_and_belongs_to_many :songs, :join_table => 'likes'
If you want to be able to call User.find(1).likes and get songs, then change the user's version to this:
class User < ActiveRecord::Base
has_and_belongs_to_many :likes, :join_table => 'likes', :class_name => 'Song'
end
And you could change the songs version to something like this:
class Song < ActiveRecord::Base
has_and_belongs_to_many :liked_by, :join_table => 'likes', :class_name => 'User'
end
This will give you Song.find(1).liked_by.all and give you the users (You could keep it to users if you wanted using the first version)
More details on habtm relationships can be found here: http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
Edit to add
If you want to act on the join table for whatever reason (you find yourself needing methods specifically on the join), you can include the model by doing it this way:
class User < ActiveRecord::Base
has_many :songs, :through => :likes
has_many :likes
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :song
end
class Song < ActiveRecord::Base
has_many :users, :through => :likes
has_many :likes
end
This will let you do User.find(1).songs.all, but User.find(1).likes.all will give you the join data

how to traverse rails model to obtain a complex result(has_mas > has_many)

Maybe is simple problem that I don't see, but is a bit tricky to me right now
What I need is know which projects a user had bet.
I want to do something like:
some_user.bets.projects
my models are:
class User < ActiveRecord::Base
has_many :bets
end
class Project < ActiveRecord::Base
has_many :bets
end
class Bet < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
So, just to be clear, starting from a user instance, how can I know which projects a user had bet.
In sql will be something like
select projects.name from users
inner join bets
on bets.user_id = users.id
inner join projects
on bets.project_id = projects.id
where users.id = 1;
how to make it work?
Update your User and Project classes as follows:
class User < ActiveRecord::Base
has_many :bets
has_many :projects, :through => :bets
end
class Project < ActiveRecord::Base
has_many :bets
has_many :users, :through => :bets
end
Then you can do this:
user = User.first # Find a user
projects = user.projects # and return the projects that have bets

Resources