Rewrite SQL query with tire - ruby-on-rails

I'm trying to rewrite a query with tire.
This is the model that I have:
class User < ActiveRecord::Base
has_many :bookmarks, :dependent => :destroy
has_many :followed_venues, :through => :bookmarks, :source => :bookmarkable, :source_type => 'Venue'
end
A user can follow venues. And I need to search for venues that are followed by a certain users.
So far I've been doing it with ActiveRecord:
#user.followed_venues.where(["venues.title LIKE ?", "%"+params[:q]+"%"])
This is obviously not ideal, so I added elasticsearch to my app with tire.
How would I search for venues with tire, filtering by the user that is following them?

I'm going to post an answer to my own question.
So it's quite easy to just search venues. Standard Venue.tire.search {...} The problem is how to filter by user that follows venues. Instead of using the Venue model for searching, I decided to index bookmarks.
This is my bookmark model
class Bookmark < ActiveRecord::Base
belongs_to :bookmarkable, :polymorphic => true
def to_indexed_json
{
:title => bookmarkable.display_name,
:user_id => user_id
}.to_json
end
end
After this I have the user_id and the venue name in the index. Now search becomes as simple as this:
Bookmark.tire.search :load => {:include => 'bookmarkable'}, :page => page, :per_page => per_page do
query do
fuzzy :title => { :value => query.downcase, :min_similarity => 0.6, :prefix_length => 2}
end
filter :terms, {:bookmarkable_type => ["Venue"], :user_id => [user.id]}
end
Now this is not a complete solution. And I hope i'm even using filter :terms correctly. The result that I get back now is an array of bookmarks actually. But it's easy to load the actual venues for them, and maybe wrap it in a WillPaginate collection for better pagination on the frontend.
Any problems with this solution? How would it compare to what phoet suggested with putting user ids to the venue index?

i would create a document for each venue and then add a field with an array of all the user-ids that are following.
are you really sure, that this is a proper task for elasticsearch?
i guess it would be way easier to just search the index for the name of the venue and then look up the data you need in your relational database.

Related

includes / joins with has_and_belongs_to_many and sort / ordering on both models

I'm having a little bit of a brain problem with what I think would be a simple call:
I've got:
class Channel < ActiveRecord::Base
has_and_belongs_to_many :shows, :join_table => :channels_shows
end
class Show < ActiveRecord::Base
has_and_belongs_to_many :channels, :join_table => :channels_shows
end
A channel has a :position and :hidden in the database (:hidden can be false, or nil if not saved as I had forgotten about defaulting to 0).
A show has :approved (same as :hidden) and of course :created_at.
I want to be able to get Channels that are (:hidden => [nil, false] ) with each channels included Shows where a show is :approved and by created_at, newest first.
I can't figure out if this is a join or an include. The closest I've gotten is this, but this doesn't sort the included shows in the right order:
Channel.order('channels.position').where(:hidden => [nil, false] ).includes(:shows).where(shows:{approved: true})
Still looking at docs and trying things in the irb; feel like it's crazy simple but I'm just not getting it.
To sort the join records, just include that sort in the order clause after your primary sort. Your channels will still have the primary sort order, but when they are equal (ie when comparing the same channel but a different show), it will fall back to sorting by the second order (effectively sorting your included table):
Channel.order('channels.position, shows.created_at').includes(:shows)...
I think you should be able to do something like this:
class Channel < ActiveRecord::Base
has_and_belongs_to_many :shows, :join_table => :channels_shows
has_and_belongs_to_many :active_shows, :class_name => 'Show', :join_table => :channels_shows, :conditions => ["approved = ?", true], :order => "created_at desc"
end
To allow you to go
Channel.order('channels.position').where(:hidden => [nil, false] ).includes(:active_shows)
This is rails 3 syntax by the way.

Rails 3.1 - Simple search across three (or more) models?

I have three models that i'd like to perform a simple search across:
class Release < ActiveRecord::Base
has_many :artist_releases
has_many :artists, :through => :artist_releases
has_many :products, :dependent => :destroy
end
class Product < ActiveRecord::Base
belongs_to :release
has_many :artists, :through => :releases
end
class Artist < ActiveRecord::Base
has_many :artist_releases
has_many :releases, :through => :artist_releases
end
In my Product Controller i can successfully render a product list searching across release and product using:
#products = Product.find(:all, :joins => :release, :conditions => ['products.cat_no LIKE ? OR releases.title LIKE ?', "%#{params[:search]}%","%#{params[:search]}%"])
I really need to be able to search artist as well. How would I go about doing that? I ideally need it within the Product Controller as it's a product list I need to display.
I've tried adding :joins => :artist and variations thereof, but none seem to work.
I'm aware there are options out there like Sphinx for a full search, but for now I just need this simple approach to work.
Thanks in advance!
if you only want products back, just add both joins:
#products = Product.joins(:release,:artists).where('products.cat_no LIKE :term OR releases.title LIKE :term OR artists.name LIKE :term', :term => "%#{params[:search]}%").all
You may also need group_by to get distinct products back.
if you want polymorphic results, try 3 separate queries.
I know I'm suggesting a simple approach (and probably not the most efficient) but it will get your job done:
I would create a method in your Product model similar to this:
def find_products_and_artists
results = []
Product.find(:all, :conditions => ['products.cat_no LIKE ?', "%#{params[:search]}%"]).each do |prod|
results << prod
end
Release.find(:all, :conditions => ['releases.title LIKE ?', "%#{params[:search]}%"]).each do |rel|
results << rel
end
Artist.find(:all, :conditions => ['artist.name LIKE ?', "%#{params[:search]}%"]).each do |art|
results << art
end
return results
end
Then when you call it the method and store the returned results in a variable (e.g. results), you can check what object each element is by doing
results[i].class
and can make your code behave accordingly for each object.
Hope I helped.

Search by record id with ultrasphinx

I'm trying to search by record id with ultrasphinx on Rails 2.3.8
In my model i tried the following:
class Offer < ActiveRecord::Base
is_indexed :fields => [{:field => 'id', :as => 'offer_id'}]
end
and
class Offer < ActiveRecord::Base
is_indexed :fields => ['id']
end
And I search with
Ultrasphinx::Search.new(:query => "1691")
It doesn't return any results, while searching for other indexed fields does.
Wow. a blast from the past.
Whilst I shifted to ThinkingSphinx after starting off with UltraSphinx, are you sure that you shouldn't be using
class Offer > ActiveRecord::Base
is_indexed :fields => ['id']
end
maybe try that for now and then figure out how to do the AS after.

Thinking Sphinx, Rails, :has_many :through => ... has

Model: Product
has-many product-categories, :through => ...
Question 1) How do I index a many to many association with thinking sphinx
Must I use has?
Questions 2) How is this searched in the controller
ex. Product.search params[:search-params], :conditions => {some_conditions}
I've not tried this on a has_many :through so shoot me down in flames if you have, but I don't see why this wouldn't work for you too, (I'm using it on a has_many association) you basically use your association in the index definition. Then searches against that model will also search the child records.
class Product < ActiveRecord::Base
has_many :product_categories
define_index do
indexes a_product_field_to_index
indexes product_categories.name, :as => :categories
end
end
In the controller:
#products = Product.search(params[:query] || '')
#params[:query] is simply the search string, I can't remember if you need to sanitize this, I would always assume you do unless you find out otherwise
In the view:
#products.each do |p|
p.categories.each do |cat|
end
end
If you don't already have it I would highly recommend the thinking-sphinx book available on peepcode: https://peepcode.com/products/thinking-sphinx-pdf
Hope that helps.

Rails, ActiveRecord: how do I get the results of an association plus some condition?

I have two models, user and group. I also have a joining table groups_users.
I have an association in the group model:
has_many :groups_users
has_many :users, :through=> :groups_users
I would like to add pending_users which would be the same as the users association but contain some conditions. I wish to set it up as an association so that all the conditions are handled in the sql call. I know there's a way to have multiple accessors for the same model, even if the name is not related to what the table names actually are. Is it class_name?
Any help would be appreciated, thanks
Use named_scopes, they're your friend
Have you tried using a named_scope on the Group model?
Because everything is actually a proxy until you actually need the data,
you'll end up with a single query anyway if you do this:
class User < ActiveRecord::Base
named_scope :pending, :conditions => { :status => 'pending' }
and then:
a_group.users.pending
Confirmation
I ran the following code with an existing app of mine:
Feature.find(6).comments.published
It results in this query (ignoring the first query to get feature 6):
SELECT *
FROM `comments`
WHERE (`comments`.feature_id = 6)
AND ((`comments`.`status` = 'published') AND (`comments`.feature_id = 6))
ORDER BY created_at
And here's the relevant model code:
class Feature < ActiveRecord::Base
has_many :comments
class Comment < ActiveRecord::Base
belongs_to :feature
named_scope :published, :conditions => { :status => 'published' }
This should be pretty close - more on has_many.
has_many :pending_users,
:through => :groups_users,
:source => :users,
:conditions => {:pending => true}
:pending is probably called something else - however you determine your pending users. As a side note - usually when you see a user/group model the association is called membership.
In the User model:
named_scope :pending, :include => :groups_users, :conditions => ["group_users.pending = ?", true]
That's if you have a bool column named "pending" in the join table group_users.
Edit:
Btw, with this you can do stuff like:
Group.find(id).users.pending(:conditions => ["insert_sql_where_clause", arguments])

Resources