MetaSearch sort ordered column - ruby-on-rails

I have a model:
class Event < ActiveRecord::Base
default_scope :order => 'date_begin'
end
There is a sort link in a view file:
= sort_link #search, :date_begin
When I'm trying to order date_begin as DESC nothing happens because the SQL query is:
SELECT * FROM events ORDER BY date_begin, date_begin DESC
How to make MetaSearch reorder this column? (I know that there is a "reorder" method in ActiveRecord but I don't know how to apply it to MetaSearch)

You can use unscoped method when you decided to use meta_search:
#search = Event.unscoped.search(params[:search])

I also wanted to use a default sort order, and didn't figure out any other way than to enforce a default order in the controller, not using any ordering scope in the model:
search = {"meta_sort" => "created_at.desc"}.merge(params[:search] || {})
#search = Photo.search(search)
The default sort order is created_at DESC, but it will be overwritten if a new sort order is received in the params. Seems to work for me.

#search = if params[:q] && params[:q][:s]
# Ransack sorting is applied - cancel default sorting
Event.reorder(nil).search(params[:q])
else
# Use default sorting
Event.search(params[:q])
end
Benefits:
1) only cancels :order scope - useful if you have .where(:deleted_at => nil).order(:date_begin) default scope.
2) uses default ordering when Ransack sorting is not applied.

Related

Rails: How to sort by calculated date difference?

I have a calculated field in my model as follows.
class Products < ApplicationRecord
attr_reader :days
def days
(Date.today - self.created_at).to_i
end
end
When I try to sort it with, I receive an error.
#products = Product.all.order("days").paginate(:page => params[:page], :per_page => 15)
Error:
PG::UndefinedColumn: ERROR: column "days" does not exist
I will appreciate if anyone can show me how to sort based on a calculated field?
Rails order clause parameter columb should be exist in the table, does not support for user defined custom attribute in the model. So, you have to use the ruby sort_by method for custom defined attributes like below,
Product.all.sort_by(&:days)
And also you have to change your method to like below,
def days
DateTime.now.to_i - self.created_at.to_i
end
It will just work but this is not a best practice to sort the records based on custom user defined custom attribute. So, you have to move this logic to sql query itself like below,
Product.all.order("now() - created_at")
It works on postgres, not sure about mysql, please check alternate in mysql if not working.
the problem for your code above is attr_reader :days, meanwhile days you declared as method not variable
here is my idea for your problem
in your model
class Products < ApplicationRecord
def total_days
(Date.today - self.created_at).to_i
end
def self.sorted_by_days
Product.all.sort_by(&:total_days).reverse
# just delete .reverse incase you want ascending
# I put reverse in case you want to sort descending
end
end
in your controller
#products = Product.sorted_by_days.paginate(:page => params[:page], :per_page => 15)
I am not sure how you are running this piece of code:
(Date.today - self.created_at).to_i
Because it expects a numeric value with the - sign. I was able to do it like this:
((Time.zone.now - self.created_at) / 1.day).to_i
But the main point is I think you want to order the records by created_at date. So by default it is ascending and you want to display the records which has been recently created first so you can do it like this directly:
Product.all.order(:created_at)
and if you want it in descending order then you can do it like this:
Product.all.order(created_at: :desc)
Still if you need to sort it by an attr_reader the other answers show how you can do it. Just the issue will be that the pagination works on ActiveRecord::Collection rather than array so for will_paginate you may refer here:
Ruby on Rails will_paginate an array
Hope this helps.

grouping with a non-primary key in postgres / activerecord

I have a model Lap:
class Lap < ActiveRecord::Base
belongs_to :car
def self.by_carmodel(carmodel)
scoped = joins(:car_model).where(:car_models => {:name => carmodel})
scoped
end
def self.fastest_per_car
scoped = select("laps.id",:car_id, :time, :mph).group("laps.id", :car_id, :time, :mph).order("time").limit(1)
scoped
end
end
I want to only return the fastest lap for each car.
So, I need to group the Laps by the Lap.car_id and then only return the fastest lap time based on that car, which would determined by the column Lap.time
Basically I would like to stack my methods in my controller:
#corvettes = Lap.by_carmodel("Corvette").fastest_per_car
Hopefully that makes sense...
When trying to run just Lap.fastest_per_car I am limiting everything to 1 result, rather than 1 result per each Car.
Another thing I had to do was add "laps.id" as :id was showing up empty in my results as well. If i just select(:id) it was saying ambiguous
I think a decent approach to this would be to add a where clause based on an efficient SQL syntax for returning the single fastest lap.
Something like this correlated subquery ...
select ...
from laps
where id = (select id
from laps laps_inner
where laps_inner.car_id = laps.car_id
order by time asc,
created_at desc
limit 1)
It's a little complex because of the need to tie-break on created_at.
The rails scope would just be:
where("laps.id = (select id
from laps laps_inner
where laps_inner.car_id = laps.car_id
order by time asc,
created_at desc
limit 1)")
An index on car_id would be pretty essential, and if that was a composite index on (car_id, time asc) then so much the better.
You are using limit which will return you one single value. Not one value per car. To return one car value per lap you just have to join the table and group by a group of columns that will identify one lap (id is the simplest).
Also, you can have a more ActiveRecord friendly friendly with:
class Lap < ActiveRecord::Base
belongs_to :car
def self.by_carmodel(carmodel)
joins(:car_model).where(:car_models => {:name => carmodel})
end
def self.fastest_per_car
joins(:car_model)
.select("laps.*, MIN(car_models.time) AS min_time")
.group("laps.id")
.order("min_time ASC")
end
end
This is what I did and its working. If there is a better way to go about these please post your answer:
in my model:
def self.fastest_per_car
select('DISTINCT ON (car_id) *').order('car_id, time ASC').sort_by! {|ts| ts.time}
end

Rails: Reordering an ActiveRecord result set

I want to retrieve the last five Articles by review date, but I want to display them in chronological order (ascending order). How do I do this?
# controller
#recent_articles = Article.where("reviewed = ?", true).order("review_date DESC").limit(5)
# view
<% #recent_articles.each do |ra| %>
# articles sorted by review_date in ascending order
<% end %>
I tried doing this in the controller, but it retrieved the five oldest review_dates instead (i.e. it just retrieved the records by ASC order instead of reordering the array already retrieved):
#recent_articles = Article.where("reviewed = ?", true).order("review_date DESC").limit(5)
#recent_articles = #recent_articles.reorder("review_date ASC")
So you want the articles from the DESC query but in the reverse order that they come in from the database?
#recent_articles.reverse!
I'd suggest you create a reviewed and recent scopes in your model. This will allow nicely chain-able scopes in your controllers:
# article.rb
scope :reviewed, lambda { where("reviewed = ?", true) }
scope :recent, lambda { |n| order("review_date").last(n) }
# in your controller..
#recent_articles = Article.reviewed.recent(5)
Notice I'm using last(n), not limit. There is no point in ordering by review_date desc just to get the latest articles by review date, then reorder them because the query was wrong. Unless you're using review_date desc for another reason, use last instead.

Use Ruby's select method on a Rails relation and update it

I have an ActiveRecord relation of a user's previous "votes"...
#previous_votes = current_user.votes
I need to filter these down to votes only on the current "challenge", so Ruby's select method seemed like the best way to do that...
#previous_votes = current_user.votes.select { |v| v.entry.challenge_id == Entry.find(params[:entry_id]).challenge_id }
But I also need to update the attributes of these records, and the select method turns my relation into an array which can't be updated or saved!
#previous_votes.update_all :ignore => false
# ...
# undefined method `update_all' for #<Array:0x007fed7949a0c0>
How can I filter down my relation like the select method is doing, but not lose the ability to update/save it the items with ActiveRecord?
Poking around the Google it seems like named_scope's appear in all the answers for similar questions, but I can't figure out it they can specifically accomplish what I'm after.
The problem is that select is not an SQL method. It fetches all records and filters them on the Ruby side. Here is a simplified example:
votes = Vote.scoped
votes.select{ |v| v.active? }
# SQL: select * from votes
# Ruby: all.select{ |v| v.active? }
Since update_all is an SQL method you can't use it on a Ruby array. You can stick to performing all operations in Ruby or move some (all) of them into SQL.
votes = Vote.scoped
votes.select{ |v| v.active? }
# N SQL operations (N - number of votes)
votes.each{ |vote| vote.update_attribute :ignore, false }
# or in 1 SQL operation
Vote.where(id: votes.map(&:id)).update_all(ignore: false)
If you don't actually use fetched votes it would be faster to perform the whole select & update on SQL side:
Vote.where(active: true).update_all(ignore: false)
While the previous examples work fine with your select, this one requires you to rewrite it in terms of SQL. If you have set up all relationships in Rails models you can do it roughly like this:
entry = Entry.find(params[:entry_id])
current_user.votes.joins(:challenges).merge(entry.challenge.votes)
# requires following associations:
# Challenge.has_many :votes
# User.has_many :votes
# Vote.has_many :challenges
And Rails will construct the appropriate SQL for you. But you can always fall back to writing the SQL by hand if something doesn't work.
Use collection_select instead of select. collection_select is specifically built on top of select to return ActiveRecord objects and not an array of strings like you get with select.
#previous_votes = current_user.votes.collection_select { |v| v.entry.challenge_id == Entry.find(params[:entry_id]).challenge_id }
This should return #previous_votes as an array of objects
EDIT: Updating this post with another suggested way to return those AR objects in an array
#previous_votes = current_user.votes.collect {|v| records.detect { v.entry.challenge_id == Entry.find(params[:entry_id]).challenge_id}}
A nice approach this is to use scopes. In your case, you can set this up the scope as follows:
class Vote < ActiveRecord::Base
scope :for_challenge, lambda do |challenge_id|
joins(:entry).where("entry.challenge_id = ?", challenge_id)
end
end
Then your code for getting current votes will look like:
challenge_id = Entry.find(params[:entry_id]).challenge_id
#previous_votes = current_user.votes.for_challenge(challenge_id)
I believe you can do something like:
#entry = Entry.find(params[:entry_id])
#previous_votes = Vote.joins(:entry).where(entries: { id: #entry.id, challenge_id: #entry.challenge_id })

Why is my controller not ordering by what Im telling to order by?

My controller action is calling all images belonging to a specific user and Im trying to order by its position (Im using the acts_as_list gem) but when I go to the page, the images are FIRST sorted by the created date, and then position (according to rails console). But because it orders by the creation date first my controller order is being ignored which is no good.
here is my action
def manage_tattoos
#tattoos = current_member.tattoos.order("position DESC")
end
and my server console shows:
Tattoo Load (0.6ms) SELECT `tattoos`.* FROM `tattoos` WHERE
(`tattoos`.member_id = 1) ORDER BY tattoos.created_at DESC, position DESC
Have you tried specifiing the order in the association?
class TodoList < ActiveRecord::Base
has_many :todo_items, :order => "position"
end
Does your association between Member and Tattoo have an order clause? E.g.
# Member class
has_many :tattoos, :order => "created_at DESC"
Is this the case? If so you might need to change your query to something like:
Tattoo.where(:member_id=>current_member.id).order("position DESC")
I'm unaware of a way to clear the order clause from an ActiveRecord association.
Or specify what to do with created_at:
current_member.tattoos.order("position DESC, created_at DESC")
Well it seems Im just very absent minded and had completely forgotten that I set a default scope on the model. Took that out and everything is fine

Resources