rails 4 - sort by number of votes - ruby-on-rails

So... I have images. and those images have votes.
I currently have image.rb
class Image < ActiveRecord::Base
belongs_to :user
belongs_to :event
has_many :image_votes, dependent: :destroy
default_scope { order(ci_lower_bound) }
def taken_by? (photographer)
self.user == photographer
end
def self.ci_lower_bound
pos = image_votes.where(value: 1).size
n = image_votes.size
if n == 0
return 0
end
z = 1.96
phat = 1.0*pos/n
(phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
end
end
I've been playing around with this... the only way to get default scope to work is to use a method with self. I found that formula at http://www.evanmiller.org/how-not-to-sort-by-average-rating.html - how would I call this to make it work??

I'd create a new scope called by_votes, include sum() and order by this new column:
scope :by_votes, -> { select("#{Image.table_name}.*, sum(#{ImageVote.table_name}.votes) AS votes").order("votes DESC") }

Related

Better method to count an associated object off a group of objects

How would one count the number of Items that are tickets in the following scenario? The ticket column lives in the Items table.
I'm trying to grab a group of redemptions, and then count the number of items that are 'tickets' for that group of redemptions.
class Item < ActiveRecord::Base
has_many :redemptions
class Redemption < ActiveRecord::Base
belongs_to :item
#This method works, but is there a much better way?
def tickets_sold
my_tickets_sold = 0
#redemptions = Redemption.where(state: "valid")
redemptions.each do |redemption|
if redemption.item.ticket == true
my_tickets_sold = my_tickets_sold + 1
end
end
my_tickets_sold
end
Yes you can do it in better way, you can use below of the 3 ways
1) Add associans for getting records of sold tickets
has_many :sold_tickets, -> { left_outer_joins(:item).where("state = 'valid' AND items.ticket = true ") }
2) Add scope for fetching count of sold tickets
scope :sold_tickets_count, -> { left_outer_joins(:item).where("state = 'valid' AND items.ticket = true ").count }
3) Add instance method in model for fetching count of sold tickets
def sold_tickets_count
left_outer_joins(:item).where("state = 'valid' AND items.ticket = true ").count
end

Active Record: find records by ceratin condition

My goal is to find three doctors with more than 1 review and with average rating >= 4
At the moment I'm using this service
class RatingCounterService
def get_three_best_doctors
doctors = find_doctors_with_reviews
sorted_doctors = sort_doctors(doctors)
reversed_hash = reverse_hash_with_sorted_doctors(sorted_doctors)
three_doctors = get_first_three_doctors(reversed_hash)
end
private
def find_doctors_with_reviews
doctors_with_reviews = {}
Doctor.all.each do |doctor|
if doctor.reviews.count > 0 && doctor.average_rating >= 4
doctors_with_reviews[doctor] = doctor.average_rating
end
end
doctors_with_reviews
end
def sort_doctors(doctors)
doctors.sort_by { |doctor, rating| rating }
end
def reverse_hash_with_sorted_doctors(sorted_doctors)
reversed = sorted_doctors.reverse_each.to_h
end
def get_first_three_doctors(reversed_hash)
reversed_hash.first(3).to_h.keys
end
end
Which is very slow.
My Doctor model:
class Doctor < ApplicationRecord
has_many :reviews, dependent: :destroy
def average_rating
reviews.count == 0 ? 0 : reviews.average(:rating).round(2)
end
end
Review model:
class Review < ApplicationRecord
belongs_to :doctor
validates :rating, presence: true
end
I can find all doctors with more than 1 review with this request
doctors_with_reviews = Doctor.joins(:reviews).group('doctors.id').having('count(doctors.id) > 0')
But how can I find doctors with an average rating >= 4 and order them by the highest rating if the "average rating" is an instance method?
Thanks to this answer :highest_rated scope to order by average rating
My final solution is
Doctor.joins(:reviews).group('doctors.id').order('AVG(reviews.rating) DESC').limit(3)

Display Similar Items With Having Distinct Count Rails 5.1

I'm trying to display a list of gins that have a similar minimum number of botanicals on my show page. I feel I'm close, but the current output is not right. It's actually just printing the name of the gin a number of times.
Gin Load (1.6ms) SELECT "gins".* FROM "gins" INNER JOIN
"gins_botanicals" ON "gins_botanicals"."gin_id" = "gins"."id" INNER
JOIN "botanicals" ON "botanicals"."id" =
"gins_botanicals"."botanical_id" WHERE "botanicals"."id" IN (4, 10, 3)
AND ("gins"."id" != $1) GROUP BY gins.id HAVING (COUNT(distinct
botanicals.id) >= 3) [["id", 2]]
I have three models; two resources with a joins table:
gin.rb
class Gin < ApplicationRecord
belongs_to :distillery, inverse_of: :gins
accepts_nested_attributes_for :distillery, reject_if: lambda {|attributes| attributes['name'].blank?}
acts_as_punchable
has_many :gins_botanical
has_many :botanicals, through: :gins_botanical
botanical.rb
class Botanical < ApplicationRecord
has_many :gins_botanical
has_many :gins, through: :gins_botanical
gins_botanical.rb
class GinsBotanical < ApplicationRecord
belongs_to :gin
belongs_to :botanical
gins_controller
def show
#gin = Gin.friendly.find(params[:id])
#gin.punch(request)
#meta_title = meta_title #gin.name
#similiar_gins = Gin.joins(:botanicals).where("botanicals.id" => #gin.botanical_ids).where.not('gins.id' => #gin.id).having("COUNT(distinct botanicals.id) >= 3").group("gins.id")
end
so in #similar_gins i am trying to count how many matching botanicals does the current #gin have compared to all the other #gins and if >= 3 return the values.
And in my view:
show.html.erb
<% #similiar_gins.each do |gin| %>
<%= #gin.name %>
<% end %>
I'm suspecting my where is not correct...
Yes, I have the similar feature but I have implemented like below
#gin = Gin.find(params[:id])
if #gin.botanicals.count > 1
#botanicals = #gin.botanical_ids
#gin_ids = Botanical.select('distinct gin_id').where('gin_id IN (?)', #botanicals).limit(10)
#ids = #gin_ids.map(&:gin_id)
#similiar_gins = Gin.where('id IN (?)', #ids).where.not(id: #gin) #=> similar all without current gin
end
This code is converted from my code which is relation is category and jobs, if you need to see my code for showing the similar jobs then it is
def show
#job = Job.find(params[:id])
if #job.categories.count > 1
#category = #job.category_ids
#jobs = JobCategory.select('distinct job_id').where('category_id IN (?)', #category).limit(10)
ids = #jobs.map(&:job_id)
#releted_jobs = Job.where('id IN (?)', ids).where.not(id: #job)
end
end
Hope it helps

Rails association scope by method with aggregate

I'm trying to retrieve association records that are dependent on their association records' attributes. Below are the (abridged) models.
class Holding
belongs_to :user
has_many :transactions
def amount
transactions.reduce(0) { |m, t| t.buy? ? m + t.amount : m - t.amount }
end
class << self
def without_empty
includes(:transactions).select { |h| h.amount.positive? }
end
end
class Transaction
belongs_to :holding
attributes :action, :amount
def buy?
action == ACTION_BUY
end
end
The problem is my without_empty method returns an array, which prevents me from using my pagination.
Is there a way to rewrite Holding#amount and Holding#without_empty to function more efficiently with ActiveRecord/SQL?
Here's what I ended up using:
def amount
transactions.sum("CASE WHEN action = '#{Transaction::ACTION_BUY}' THEN amount ELSE (amount * -1) END")END")
end
def without_empty
joins(:transactions).group(:id).having("SUM(CASE WHEN transactions.action = '#{Transaction::ACTION_BUY}' THEN transactions.amount ELSE (transactions.amount * -1) END) > 0")
end

Rails ActiveRecord intersect query with has_many association

I have the following models:
class Piece < ActiveRecord::Base
has_many :instrument_pieces
has_many :instruments, through: :instrument_pieces
end
class Instrument < ActiveRecord::Base
has_many :pieces, through: :instrument_pieces
has_many :instrument_pieces
end
class InstrumentPiece < ActiveRecord::Base
belongs_to :instrument
belongs_to :piece
end
And I have the following query:
Piece
.joins(:instrument_pieces)
.where(instrument_pieces: { instrument_id: search_params[:instruments] } )
.find_each(batch_size: 20) do |p|
Where search_params[:instruments] is an array. The problem with this query is that it will retrieve all pieces that have any of the instruments, so if search_params[:instruments] = ["1","3"], the query will return pieces with an instrument association of either 1 or 3 or of both. I'd like the query to only return pieces whose instrument associations include both instruments 1 and 3. I've read through the docs, but I'm still not sure how this can be done...
It seems like what I wanted was an intersection between the two queries, so what i ended up doing was:
queries = []
query = Piece.joins(:instruments)
search_params[:instruments].each do |instrument|
queries << query.where(instruments: {id: instrument})
end
sql_str = ""
queries.each_with_index do |query, i|
sql_str += "#{query.to_sql}"
sql_str += " INTERSECT " if i != queries.length - 1
end
Piece.find_by_sql(sql_str).each do |p|
Very ugly, but ActiveRecord doesn't support INTERSECT yet. Time to wait for ActiveRecord 5, I suppose.
You can use where clause chaining to achieve this. Try:
query = Piece.joins(:instrument_pieces)
search_params[:instruments].each do |instrument|
query = query.where(instrument_pieces: { instrument_id: instrument } )
end
query.find_each(batch_size: 20) do |p|
or another version
query = Piece.joins(:instruments)
search_params[:instruments].each do |instrument|
query = query.where(instrument_id: instrument)
end
query.find_each(batch_size: 20) do |p|

Resources