Rails: Sorting Objects From Different Models - ruby-on-rails

I've populated a hash with two different models. I then try to sort them like so:
#search_results = User.find(:all, :conditions => ['name LIKE ?', "%#{params[:query]}%"])
#search_results += Book.find(:all, :conditions => ['title LIKE ?', "%#{params[:query]}%"])
#search_results.sort! { |a,b| a.impressions_count <=> b.impressions_count }
This throws the following error:
comparison of User with Book failed
Both users and books have an integer-based impressions_count. Why can't I sort via this attribute? What other options do I have?

I faced a similar problem recently and ended up writing some custom sql because all other ways returned an array. Pretty sure its not a good idea to use the sort method since it will always be more efficient to sort in SQL than ruby, especially when the data set gets large
#combined_results = User.find_by_sql(["SELECT title, id, impressions_count, NULL as some_attribute_of_book
FROM user
WHERE title LIKE ?
UNION SELECT title, id, impressions_count, some_attribute_of_book FROM book
WHERE title LIKE ?
ORDER BY impressions_count", params[:query], params[:query]])
The above is completely untested code, more of an example than anything

Related

How do I combine ActiveRecord results from multiple has_many :through queries?

Basically, I have an app with a tagging system and when someone searches for tag 'badger', I want it to return records tagged "badger", "Badger" and "Badgers".
With a single tag I can do this to get the records:
#notes = Tag.find_by_name(params[:tag_name]).notes.order("created_at DESC")
and it works fine. However if I get multiple tags (this is just for upper and lower case - I haven't figured out the 's' bit either yet):
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger'])
I can't use .notes.order("created_at DESC") because there are multiple results.
So, the question is.... 1) Am I going about this the right way? 2) If so, how do I get all my records back in order?
Any help much appreciated!
One implementation would be to do:
#notes = []
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger']).each do |tag|
#notes << tag.notes
end
#notes.sort_by {|note| note.created_at}
However you should be aware that this is what is known as an N + 1 query, in that it makes one query in the outer section, and then one query per result. This can be optimized by changing the first query to be:
Tag.find(:all, :conditions => [ "lower(name) = ?", 'badger'], :includes => :notes).each do |tag|
If you are using Rails 3 or above, it can be re-written slightly:
Tag.where("lower(name) = ?", "badger").includes(:notes) do |tag|
Edited
First, get an array of all possible tag names, plural, singular, lower, and upper
tag_name = params[:tag_name].to_s.downcase
possible_tag_names = [tag_name, tag_name.pluralize, tag_name.singularize].uniq
# It's probably faster to search for both lower and capitalized tags than to use the db's `lower` function
possible_tag_names += possible_tag_names.map(&:capitalize)
Are you using a tagging library? I know that some provide a method for querying multiple tags. If you aren't using one of those, you'll need to do some manual SQL joins in your query (assuming you're using a relational db like MySQL, Postgres or SQLite). I'd be happy to assist with that, but I don't know your schema.

Search multiple columns - Rails

I am currently writing a search method for my rails applications, and at the moment it works fine. I have the following in my game.rb:
def self.search(search)
if search
find(:all, :conditions => ['game_name LIKE ? OR genre LIKE ? OR console LIKE ?', "%#{search}%", "#{search}", "#{search}"])
else
find(:all)
end
end
Now that searches fine, but my problem is that if there is a record in game_name that has the word 'playstation' in it, it will finish the search there. It only returns that record, rather than all games that have 'playstation' stored in console. Now I understand this is because I have 'OR' in my conditions, but I don't know an alternative. 'AND' requires all the conditions to match or none return at all. What is an alternative I can use to AND and OR? Help would be much appreciated.
If there is a solution that has separate search boxes and entries, then that would be fine, I don't necessarily require the search to find it all based on one search form.
If I understand your question correctly, your SQL looks good to me for what you are trying to do. An OR clause will return all records that match in column1, column2, or column3. It doesn't stop at the first match. I do see an issue with your parameters in that the first you are using LIKE with % but in the second two you aren't, maybe that is where your issue is coming from.
Should this be your find (% around second and third search)?
find(:all, :conditions => ['game_name LIKE ? OR genre LIKE ? OR console LIKE ?', "%#{search}%", "%#{search}%", "%#{search}%"])
or better use DRY version (above will not work for Rails 4.2+):
Item.where('game_name LIKE :search OR genre LIKE :search OR console LIKE :search', search: "%#{search}%")
What if you have 15 columns to search then you will repeat key 15 times. Instead of repeating key 15 times in query you can write like this:
key = "%#{search}%"
#items = Item.where('game_name LIKE :search OR genre LIKE :search OR console LIKE :search', search: key).order(:name)
It will give you same result.
Thanks
I think this is a little bit of a cleaner solution. This allows you to add/remove columns more easily.
key = "%#{search}%"
columns = %w{game_name genre console}
#items = Item.where(
columns
.map {|c| "#{c} like :search" }
.join(' OR '),
search: key
)
A more generic solution for searching in all fields of the model would be like this
def search_in_all_fields model, text
model.where(
model.column_names
.map {|field| "#{field} like '%#{text}%'" }
.join(" or ")
)
end
Or better as a scope in the model itself
class Model < ActiveRecord::Base
scope :search_in_all_fields, ->(text){
where(
column_names
.map {|field| "#{field} like '%#{text}%'" }
.join(" or ")
)
}
end
You would just need to call it like this
Model.search_in_all_fields "test"
Before you start.., no, sql injection would probably not work here but still better and shorter
class Model < ActiveRecord::Base
scope :search_all_fields, ->(text){
where("#{column_names.join(' || ')} like ?", "%#{text}%")
}
end
I think this is a more efficient solution if you want to search an array of columns as I do.
First and most importantly you can add a private function to your model that creates a query template:
def self.multiple_columns_like_query(array)
array.reduce('') { |memo, x| #
unless memo == '' #
memo += ' or ' # This is the
end #
memo += "#{x} like :q" # core part
} #
end
Than you can use the function in your search function:
def self.search(query)
if fields = self.searched_fields && query
where multiple_like_query(fields), q: "%#{query}%"
end
end
Here you should also define self.searched_fields as an array of field names.

Rails 2.3.5 Problem Building Conditions Array dynamically when using in (?)

Rails 2.3.5
I've looked at a number of other questions relating to building conditions dynamically for an ActiveRecord find.
I'm aware there are some great gems out there like search logic and that this is better in Rails3. However, I'm using geokit for geospacial search and I'm trying to build just a standard conditions set that will allow me to combine a slew of different filters.
I have 12 different filters that I'm trying to combine dynamically for an advanced search. I need to be able to mix equality, greater than, less than, in (?) and IS NULLs conditions.
Here's an example of what I'm trying to get working:
conditions = []
conditions << ["sites.site_type in (?)", params[:site_categories]] if params[:site_categories]
conditions << [<< ["sites.operational_status = ?", 'operational'] if params[:oponly] == 1
condition_set = [conditions.map{|c| c[0] }.join(" AND "), *conditions.map{|c| c[1..-1] }.flatten]
#sites = Site.find :all,
:origin => [lat,lng],
:units => distance_unit,
:limit => limit,
:within => range,
:include => [:chargers, :site_reports, :networks],
:conditions => condition_set,
:order => 'distance asc'
I seem to be able to get this working fine when there are only single variables for the conditions expression but when I have something that is a (?) and has an array of values I'm getting an error for the wrong number of bind conditions. The way I'm joining and flattening the conditions (based on the answer from Combine arrays of conditions in Rails) seems not to handle an array properly and I don't understand the flattening logic enough to track down the issue.
So let's say I have 3 values in params[:site_categories] I'll the above code leaves me with the following:
Conditions is
[["sites.operational_status = ?", "operational"], ["sites.site_type in (?)", ["shopping", "food", "lodging"]]]
The flattened attempt is:
["sites.operational_status = ? AND sites.site_type in (?)", ["operational"], [["shopping", "food", "lodging"]]]
Which gives me:
wrong number of bind variables (4 for 2)
I'm going to step back and work on converting all of this to named scopes but I'd really like to understand how to get this working this way.
Rails 4
users = User.all
users = User.where(id: params[id]) if params[id].present?
users = User.where(state: states) if states.present?
users.each do |u|
puts u.name
end
Old answer
Monkey patch the Array class. Create a file called monkey_patch.rb in config/initializers directory.
class Array
def where(*args)
sql = args.first
unless (sql.is_a?(String) and sql.present?)
return self
end
self[0] = self.first.present? ? " #{self.first} AND #{sql} " : sql
self.concat(args[1..-1])
end
end
Now you can do this:
cond = []
cond.where("id = ?", params[id]) if params[id].present?
cond.where("state IN (?)", states) unless states.empty?
User.all(:conditions => cond)

find all :conditions id found in an array of values

I have 2 models (player and team linked through the model lnkteamplayer)
Team has_many players through lnkteamplayer
Player has_many teams through lnkteamplayer
I need to retrieve all players not belonging to a specific team.
<% #players = Player.find(:all, :conditions => ["id != ?",#team.lnkteamplayers.player_id ]) %>
I am getting an error with above line of code. My question is how do i pass an array of values in the above condition.
Thanks for any suggestion provided.
You've got a couple of problems there:
1) the first part of conditions, "id != ?", is a fragment of sql, and in sql you do "not equals" as <> not !=. Eg "id <> ?"
2) To use an array, the sql syntax is id in (1,2,3) or id not in (1,2,3). In your conditions you can do this like :conditions => ["id not in (?)", array_of_ids]
So, you could get players not on a team like this:
#team = Team.find(params[:team_id])
#not_on_team = Player.find(:all, :conditions => ["id not in (?)", #team.player_ids])
Since you haven't provided an error message, I am kind of guessing here. However, I don't think != is a valid syntax in many SQL dialects. You are probably looking for something like NOT IN () instead.
Also, #team.lnkteamplayers.player_id probably doesn't work since the value returned from #team.lnkteamplayers likely doesn't have a player_id method; you might want the ids of the actual players instead.
That can be done using something like #team.lnkteamplayer_ids.
All in all, your line probably needs to look like
<% #players = Player.find(:all, :conditions => ["id NOT IN (?)", #team.lnkteamplayer_ids]) %>
but without more information we can't say for sure.

Rails - Find results from two join tables

I have have 3 Tables of data and 2 Join Tables connecting everything. I'm trying to figure out a way to query the results based on the condition that the join table data is the same.
To explain, I have User, Interest, and Event Tables. These tables are linked through an HABTM relationship (which is fine for my needs since I dont need to store any other fields) and joined through two join tables. So i also have a UsersInterests table with (user_id, interest_id) and a EventsInterests table with (event_id, interest_id).
The problem comes when trying to query all the Events where the users interests match the events interests.
I thought it would look something like this...
#events= Event.find(:all, :conditions => [#user.interests = #event.interests])
but I get the error
"undefined method `interests' for nil:NilClass", Is there something wrong with my syntax or my logic?
You're problem is that either #user or #event is undefined. Even if you define them, before executing this statement, the conditions option supplied is invalid, [#user.interests = #event.interests].
This named scope on events should do the trick
class Event < ActiveRecord::Base
...
named_scope :shares_interest_with_user, lambda {|user|
{ :joins => "LEFT JOIN events_interests ei ON ei.event_id = events.id " +
"LEFT JOIN users_intersets ui ON ui.interest_id = ei.interest_id",
:conditions => ["ui.user_id = ?", user], :group_by => "events.id"
}
end
#events = Event.shares_interest_with_user(#user)
Given Event <-> Interest <-> User query all the Events where the users interests match the events interests (so the following will find all such Events that this event's interest are also interests of at least one user).
First try, the simplest thing that could work:
#events = []
Interest.all.each do |i|
i.events.each do |e|
#events << e if i.users.any?
end
end
#events.uniq!
Highly inefficient, very resource hungry and cpu intensive. Generates lots of sql queries. But gets the job done.
Second try should incorporate some complicated join, but the more I think about it the more I see how vague your problem is. Be more precise.
Not sure I completely follow what you are trying to do. If you have one user and you want all events that that user also has interest in then something like:
Event.find(:all, :include => [:events_interests], :conditions => ['events_interests.interest_id in (?)', #user.interests.collect(&:id)])
should probably work.

Resources