Group using Sunspot/Solr? - ruby-on-rails

I'm having some problem searching by group when using Sunspot.
Here is an example:
# == Schema Information
#
# Table name: movies
#
# id :integer(4) not null, primary key
# title :string(255)
class Movie < ActiveRecord::Base
has_and_belongs_to_many :actors
searchable do
text :title
integer :ages, multiple: true do
actors.map(&:age)
end
text :names, multiple: true do
actors.map(&:name)
end
end
end
# == Schema Information
#
# Table name: actors
#
# id :integer(4) not null, primary key
# name :string(255)
# age :integer(30)
class Actor < ActiveRecord::Base
has_and_belongs_to_many :movies
searchable do
integer :age
text :name
end
end
I want to find every movie that has an actor named John at age 30.
Movie.search do
with(:names).equal_to("John")
with(:ages).equal_to(30)
with(:title).equal_to("...")
# ...
end
The problem is here that it may find a movie that has two actors; one named John and one at age 30. Is there a way to somehow group this together so that the movie found have an actor named John at age 30?

The solution, just like MaurĂ­cio Linhares wrote in his comment, is to go through the actors model and group by movies.
The problem is that Sunspot doesn't support Solr 3.3 or 4.0, which is the only Solr versions that support grouping.
Here is my solution using Sunspot 1.2.1 and Solr 3.3.
In my example movie_id is placed in the actors table, this isn't done in my real application.
# == Schema Information
#
# Table name: actors
#
# id :integer(4) not null, primary key
# name :string(255)
# created_at :datetime
# updated_at :datetime
# movie_id :integer(4)
#
class Actor < ActiveRecord::Base
searchable do
# We need to store the movie_id as an string
# So it can be sorted. We also need to pass the
# stored: true params
string :movie_id, stored: true do
movie_id.to_s
end
end
def search_using_solr
scoped = Sunspot.new_search(Actor)
scoped.build do
adjust_solr_params do |params|
params[:group] = true
params[:"group.field"] = "movie_id_s"
params[:"group.format"] = "simple"
end
end
# Sunspot 1.2.1 doesn't support grouping, so we need to do some hacking.
def scoped.hits
#hits ||= #solr_result["grouped"].values.first["doclist"]["docs"].map do |doc|
Sunspot::Search::Hit.new(doc, nil, self)
end
end
def scoped.total
#total ||= #solr_result["grouped"]["movie_id_s"]["matches"] || 0
end
# Here we'll only fetch the stored data from Solr it self,
# and then pass it manualy to ActiveRecord.
Movie.where({
id: scoped.execute.hits.map{ |h| h.stored(:movie_id) }
})
end
end
Cred to alindeman for his example gist.

Related

Arel::SelectManager how to access the result including the projections

I am attempting to access the results of a join where columns of both tables are specified as part of a projection.
I have 2 models (Rails 4.2.11; Arel 6.0.4; Ruby 2.5.3)
# Table name: users
#
# id :integer not null, primary key
# name :string(255)
# email :string(255)
# created_at :datetime not null
# updated_at :datetime not null
class User < ActiveRecord::Base
has_many :photos
end
# Table name: photos
#
# id :integer not null, primary key
# name :string(255)
# created_by_id :integer
# assigned_id :integer
# created_at :datetime not null
# updated_at :datetime not null
# user_id :integer
class Photo < ActiveRecord::Base
belongs_to :user
end
creators = User.arel_table.alias('creators')
updaters = User.arel_table.alias('updaters')
photos = Photo.arel_table
photos_with_credits = photos.
join(photos.join(creators, Arel::Nodes::OuterJoin).on(photos[:created_by_id].eq(creators[:id]))).
join(photos.join(updaters, Arel::Nodes::OuterJoin).on(photos[:assigned_id].eq(updaters[:id]))).
project(photos[:name], photos[:created_at], creators[:name].as('creator'), updaters[:name].as('editor'))
# generated SQL
SELECT photos.name, photos.created_at, creators.name AS creator, updaters.name AS editor
FROM photos
INNER JOIN (SELECT FROM photos LEFT OUTER JOIN users creators ON photos.created_by_id = creators.id)
INNER JOIN (SELECT FROM photos LEFT OUTER JOIN users updaters ON photos.updated_by_id = updaters.id)
How I'd like to process the result
photos_with_credits.map{|x| "#{photo.name} - copyright #{photo.created_at.year} #{photo.creator}, edited by #{photo.editor}"}.join('; ')
This may be a very dumb question, but ...
I have not been able to find a way to use the SelectManager result to produce a meaningful output since map was deprecated (& removed) from the SelectManager class.
I would appreciate your help.
Simplified the activerecord / arel query builder
amended code
photos_with_credits = Photo.select([photos[:name], photos[:created_at], creators[:name].as('creator'), updaters[:name].as('editor')]).
joins(photos.outer_join(creators).on(photos[:created_by_id].eq(creators[:id])).join_sources).
joins(photos.outer_join(updaters).on(photos[:assigned_id].eq(updaters[:id])).join_sources)
photos_with_credits.map do |photo|
puts "#{photo.name} - copyright #{photo.created_at.year} #{photo.creator}, edited by #{photo.editor}".light_blue
end
amended SQL (simpler)
SELECT photos.name, photos.created_at, creators.name AS creator, updaters.name AS editor
FROM photos
LEFT OUTER JOIN users creators ON photos.created_by_id = creators.id
LEFT OUTER JOIN users updaters ON photos.assigned_id = updaters.id
We should figure out a way to trigger SelectManage and get ActiveRelation result, then we can iterate it by using map, etc.
So I would like to suggest:
Photo.join(photos_with_credits.join_sources).map do |photo|
"#{photo.name} - copyright #{photo.created_at.year} #{photo.creator}, edited by #{photo.editor}"}.join('; ')
end

get the higher value in ruby on rails

I am working with ruby on rails and i have a question.
I have a table who contains data like this:
{id: 471, from_city_id: '9', to_city_id: 14, product_id: 61, saving_in_from_currency: 262.0},
{id: 472, from_city_id: '9', to_city_id: 14, product_id: 61, saving_in_from_currency: 150.0}
Here the data model,
# == Schema Information
#
# Table name: pricings
#
# id :integer not null, primary key
# from_city_id :integer
# to_city_id :integer
# product_id :integer
# saving_in_from_currency :decimal(, )
# created_at :datetime
# updated_at :datetime
#
class Pricing < ActiveRecord::Base
belongs_to :from_city, :class_name => 'City'
belongs_to :to_city, :class_name => 'City'
belongs_to :product
end
I try to display a table in my view like this:
for example:
the user select the from_city_id, and i display for this id all the elements like this
to_city_id to_city_id to_city_id
product_id price price price
product_id price price price
product_id price price price
product_id price price price
For the moment i select the "saving_in_from_currency", by the from_city_id, the to_city_id and product_id.
But like you can see, in some case, only the id and the saving_in_from_currency change , and i want to display for each case like this the higher value, here "262.0".
Do you have any idea of how i could do this?(and sorry for my english)
Thank you.
It seems you'd likely benefit from using distinct in your query. I suspect something like this would accomplish what you desire. (Replace the model name of course, as it wasn't mentioned above).
Model.select(:from_city_id, :to_city_id, :product_id).distinct.order(saving_in_from_currency: :desc)
Edit:
Or, if you need access to attributes on the model other than from_city_id, to_city_id, product_id, this would also work.
# Replace with any additional constraints as needed
p = Pricing.all.order(saving_in_from_currency: :desc)
p.uniq! { |a| [a.from_city_id, a.to_city_id, a.product_id] }

How do I compare two rows of two different tables in Rails 3.2.8

I need to compare a row of 7 attributes in a winning_numbers table with 1 or more rows in another table, lottery_selections, containing 6 attributes in rails 3.2.8. From the result I want to count the number of attributes from one table that match the other and render the result.
I have two models: lottery_selections & winning_numbers
# == Schema Information
#
# Table name: lottery_selections
#
# id :integer not null, primary key
# syndicate_id :integer
# lottery_selection_1 :integer
# lottery_selection_2 :integer
# lottery_selection_3 :integer
# lottery_selection_4 :integer
# lottery_selection_5 :integer
# lottery_selection_6 :integer
# lottery_selection_date :datetime
# created_at :datetime not null
# updated_at :datetime not null
#
class LotterySelection < ActiveRecord::Base
attr_accessible :lottery_selection_1, :lottery_selection_2, :lottery_selection_3, :lottery_selection_4,\
:lottery_selection_5, :lottery_selection_6, :lottery_selection_date, :syndicate_id
belongs_to :winning_number
#validates_associated :winning_number
validates :lottery_selection_1, :lottery_selection_2, :lottery_selection_3, :lottery_selection_4,\
:lottery_selection_5, :lottery_selection_6, :presence => true, :numericality => {:greater_than_or_equal_to => 1}, \
:numericality => {:less_than_or_equal_to => 49}
UNIQU_FIELDS = [:lottery_selection_1, :lottery_selection_2, :lottery_selection_3, :lottery_selection_4,\
:lottery_selection_5, :lottery_selection_6]
validate :lottery_numbers_are_unique
def lottery_numbers_are_unique
unless UNIQU_FIELDS.map{|field| self[field] }.uniq.length == UNIQU_FIELDS.length
errors[:base] << "You have repeated one or more numbers for that day's draw"
end
end
end
# == Schema Information
#
# Table name: winning_numbers
#
# id :integer not null, primary key
# winning_numbers_date :datetime
# winning_number_1 :integer
# winning_number_2 :integer
# winning_number_3 :integer
# winning_number_4 :integer
# winning_number_5 :integer
# winning_number_6 :integer
# winning_number_bonus :integer
# created_at :datetime not null
# updated_at :datetime not null
#
class WinningNumber < ActiveRecord::Base
attr_accessible :winning_number_1, :winning_number_2, :winning_number_3, :winning_number_4, :winning_number_5,\
:winning_number_6, :winning_number_bonus, :winning_numbers_date
has_many :lottery_selections
#validates_associated :lottery_selections
validates :winning_number_1, :winning_number_2, :winning_number_3, :winning_number_4, :winning_number_5,\
:winning_number_6, :winning_number_bonus, :presence => true, :numericality => {:greater_than_or_equal_to => 1}, \
:numericality => {:less_than_or_equal_to => 49}
#Below checks that for any given date there can only be one set of winning numbers
validates :winning_numbers_date, uniqueness: { scope: [:winning_number_1, :winning_number_2, :winning_number_3,\
:winning_number_4, :winning_number_5, :winning_number_6, :winning_number_bonus] }
UNIQ_FIELDS = [:winning_number_1, :winning_number_2, :winning_number_3, :winning_number_4, :winning_number_5, :winning_number_6, :winning_number_bonus]
validate :winning_numbers_are_unique
def winning_numbers_are_unique
unless UNIQ_FIELDS.map{|field| self[field] }.uniq.length == UNIQ_FIELDS.length
errors[:base] << "You have repeated one or more numbers for that day's draw"
end
end
end
Can you please advise on how I make the comparison and use the result to render a result?
Most effective solution (even more rails developer will advise not to do it): Create a stored procedure on the database where you can do the comparison and return the result.
Something quick you can do (although the more you research the better, on a lottery app you may have a lot of tickets and you need to run something that is really effective):
winning_numbers = # Do a query and put the array of winning numbers here
seven_numbers_match = LotterySelection.where("number_1 in (?) and number_2 in (?) and number_3 in (?) and number_4 in (?) and number_5 in (?) and number_6 in (?) and number_7 in (?)", winning_numbers, winning_numbers, winning_numbers, winning_numbers, winning_numbers, winning_numbers, winning_numbers)
six_number_match = LotterySelection.where("(number_1 in (?) and number_2 in (?) and number_3 in (?) and number_4 in (?) and number_5 in (?) and number_6 in (?) and number_7 not in(?)") OR (number_1 in (?) and number_2 in (?) and number_3 in (?) and number_4 in (?) and number_5 in (?) and number_6 not in (?) and number_7 in (?)) OR (...")
And so on, it may not be the best algorithm but it works and use the db engine to do the compassion.
But definitely for a problem like this, I'll simply hire a DBA consultant to write the stored procedure for me.

Rails 3.0.5 belongs_to association not updating primary key in declaring class

I am trying to do a basic belongs_to/has_many association but running into a problem. It seems that the declaring class's foreign key column is not being updated. Here are my models:
#
# Table name: clients
#
# id :integer not null, primary key
# name :string(255)
# created_at :datetime
# updated_at :datetime
#
class Client < ActiveRecord::Base
has_many :units
...
attr_accessible :name
end
#
# Table name: units
#
# id :integer not null, primary key
# client_id :integer
# name :string(255)
# created_at :datetime
# updated_at :datetime
#
class Unit < ActiveRecord::Base
belongs_to :client
...
attr_accessible :name
end
When I open rails console I do the following:
#This works as it should
c1 = Client.create(:name => 'Urban Coding')
u1 = c1.units.create(:name => 'Birmingham Branch')
The above gives me the correct results. I have a client and a unit. The unit has the client_id foreign key field populated correctly.
#This does not work.
c1 = Client.create(:name => 'Urban Coding')
u1 = Unit.create(:name => 'Birmingham Branch')
u1.client = c1
I feel the above should have the same effect. However this is not the case. I have a unit and a client but the units client_id column is not populated. Not sure exactly what I am doing wrong here. Help is appreciated. Let me know if you need more information.
You're simply not saving u1, hence no change to the database.
If you want it assigned and saved in a single operation, use update_attribute
u1.update_attribute(:client, c1)
Yep, I think if you save it the ID will get set.
The first syntax is much better. If you don't want to do the save action right away, which create does for you, then use build:
c1 = Client.create(:name => 'Urban Coding')
u1 = c1.units.build(:name => 'Birmingham Branch')
# do stuff with u1
u1.save
This works:
c1 = Client.create(:name => 'Urban Coding')
u1 = Unit.create(:name => 'Birmingham Branch')
u1.client_id = c1.id
u1.save
c1.save
but the other way is the better way to create it.

Rails 3: Feed is taking too long

I have a twitter-like feed in my web app. I've tried to optimize the query as much as possible but it still freezes when it's loading the "tweets" from PostgresSQL database.
Twitter, github, facebook, feeds are so smooth and fast.
What's the best way to accomplish that?
Regards.
**Edit: the code is the one in railstutorial.org.
http://railstutorial.org/chapters/following-users#sec:scopes_subselects_and_a_lambda
# Return microposts from the users being followed by the given user.
scope :from_users_followed_by, lambda { |user| followed_by(user) }
private
# Return an SQL condition for users followed by the given user.
# We include the user's own id as well.
def self.followed_by(user)
followed_ids = user.following.map(&:id).join(", ")
where("user_id IN (#{followed_ids}) OR user_id = :user_id",
{ :user_id => user })
end
# Table name: microposts
#
# id :integer not null, primary key
# content :string(255)
# user_id :integer
# created_at :datetime
# updated_at :datetime
#
Sorry for not providing enough information.**
Did you put any index on your tables?

Resources