Sorting and searching on a field which model doesn't have - ruby-on-rails

I have an HTML table where I show the data from a particular model but I also show a column in the HTML table which doesn't belongs to that model. this column comes after some calculation(In this calculation I use 3-4 more tables). I want to give the sorting and searching functionality on that column. Anyone having idea what is the best way to do that?
Updated:
The main problem is if I provide sorting/searching on that column then I have to fetch all records from database for calculation which will not be good idea.

If you are looking for performance solution: just add new field into your table to store your calculation.
Anyway, you can't solve your problem without fetching all your records.
Or you can use JavaScript for searching and sorting, but only if there are not many items.

You can sort you model:
q_new.sort! { |x, y|
y.creation_date <=> x.creation_date
}
creation_date is a any field or a method of the model.

I would create a virtual resource on the model, which contains the sorter in Ruby-code. Assuming sum is your virtual property.
def by_sum(options = {})
#items = self.class.find(options)
#items.sort! { |a,b| a.sum <=> b.sum }
end
In your controller you can call:
Item.by_sum({:where => "'foo' = 'bar'"})

Related

How to sort a custom column in ActiveAdmin?

I'm trying to sort a custom column on the index page in ActiveAdmin that shows data provided by a helper method.
I have tried multiple sort solutions and none of them worked. I was thinking about trying to sort with custom scopes but I am looking for a solution in the Active Admin.
index do
selectable_column
id_column
column ("Driver") { |cd| link_to("#{cd.campaign_driver.full_name}", admin_driver_path(cd.campaign_driver.driver_id)) }
column :started_at
column :ended_at
column ("Distance(km)") { |route| route_distance(route) }
column ("Clean distance(km)") { |route| route_clean_distance(route) }
column ("Distance diff(km)") { |route| route_distance_diff(route) }
column ("Duration") { |route| route_duration(route) }
column ("Average speed") { |route| route_avg_speed(route) }
actions
end
The 'Distance Diff' column should be sortable.
I think you need to refactor your method first, make it to scoped_collection.
controller do
def scoped_collection
Route.select("routes.*, (routes.ended_at-routes.ended_at) AS distance_diff_route")
end
end
Then rewrite your index column to
column :distance_diff_route, sortable: :distance_diff_route
In short, sorting by virtual attribute is not possible by the ways you tried to use.
Here is why. When you request the index page to be sorted by an attribute, database query is created, asking DB to sort records by that column. In that query, filters (if provided) are applied and resulting records in your drivers table are sorted by the selected real attribute and only a subset (depending on your paging setup in config/initializers/active_admin.rb config.default_per_page = 30) is returned. Your helper methods are applied to this subset (and therefore if it worked, it would only sort there 30 or so records). The database is not aware of your virtual attribute and cannot sort all records accordingly.
There are at least two solutions to this:
1) Default scope
Easy solution is using Rails' own default_scope. It modifies the base query that is used as a base for model's query builder. You can offload the construction of the virtual fields there and then use it in Rails, see example below.
There are downsides: 1) it's going to get difficult if your virtual fields depend on other tables, 2) usage of default scope is often advised against - google "rails default scope bad" to catch up.
class Route < ApplicationRecord
default_scope { select(Arel.star, 'md5(name) hashed_name') }
...
end
ActiveAdmin.register Route do
index do
column :hashed_name, sortable: true
end
end
2) View based model
Proper, but also more complicated solution is to create a database view that will compute all the virtual fields you need and then build a Rails model based on that view. Here is a resource that can help you achieve that - https://danchak99.wordpress.com/enterprise-rails/chapter-11-view-backed-models/

Activerecord, calculate average percentage for multiple value of grouped value

I'm try to save some precious millisecond in a query.
I have a stats table and I'm calculating the average value of some records grouped by a key
Activity.group(:company_id).average(:completed_courses)
Everything is ok, but I need also the average on other columns of the same table (:readed_news, :logins, etc)
Is there a way to get all the averages with a single query?
I'm using Postgres
You can write select.
Activity.group(:company_id)
.select('AVG(completed_courses) as avg_completed_courses,
AVG(readed_news) as avg_readed_news,
AVG(logins) as avg_logins')
Also, you can write method for generating select expression:
def select_exp(attrs)
attrs.sum { |attr| ("AVG(#{attr}) as avg_#{attr},") }.chop
end
Activity.group(:company_id)
.select select_exp(%w(completed_courses readed_news logins))
You can use single field on average method but you can do this
activities = Activity.group(:company_id)
completed_courses = activities.average(:completed_courses)
readed_news = activities.average(:readed_news)
etc..
Hope this will help you.
Use scopes or class methods on you model:
class Activity < ActiveRecord::Base
self.average_company
group(:company_id).average(:completed_courses)
end
self.average_readed_news
group(:readed_news).average(:completed_courses)
end
end
then call:
Activity.average_company
Actvitity.average_readed_news
You could then try to merge scopes
Activity.average_company.merge(Activity.average_readed_news)
Look at the documentation here:
http://guides.rubyonrails.org/active_record_querying.html

Combining arrays of different objects in RoR and sorting by a date value on a different attribute on each

I have two arrays of objects in ruby on rails. I want to combine them and sort them by two different attributes. Is this possible?
One is an array of 'Post' records, and one is an array of 'Talk' records. They both need to be sorted by date. But for Post, the relevant attribute is created_at while for Talk it's date_given. This is the kind of headache that's making me consider eliminating the two different models and replacing them with a more flexible Post model.
Any thoughts would be appreciated.
I just tried something that worked, but am not sure if it's the best way to handle this issue. I just add a method to each model called sortable date, and set it to what I want. One gotcha with this was that created_at was a Time and date_given was DateTime, so I used this post to deal with that Convert to/from DateTime and Time in Ruby
talk.rb
def sortable_date
date_given
end
post.rb
def sortable_date
created_at.to_datetime
end
Then in my controller -
#posts = Post.order('created_at DESC').limit(3)
#talks = Talk.order('date_given DESC').limit(3)
#news = #posts + #talks
#news.sort_by! &:sortable_date
#news = #news[1..4]
And I just render #news in my view and it summons the appropriate partials nicely. I'd love some feedback.
posts = Post.all
talks = Talk.all
posts+talks.sort do |a,b|
attr_a = a.kind_of?(Post) ? 'created_at' : 'date_given'
attr_b = b.kind_of?(Post) ? 'created_at' : 'date_given'
a.send(attr_a) <=> b.send(attr_b)
end
There should be a SQL way to do this, but well, this way is simpler.

Paginate data from two models into one newsfeed: Ruby on Rails 3 // Will_paginate

I'd like to make a newsfeed for the homepage of a site i'm playing around with. There are two models: Articles, and Posts. If I wanted just one in the newsfeed it would be easy:
#newsfeed_items = Article.paginate(:page => params[:page])
But I would like for the two to be both paginated into the same feed, in reverse chronological order. The default scope for the article and post model are already in that order.
How do I get the articles and posts to be combined in to the newsfeed as such?
Thanks!
EDIT: What about using SQL in the users model?
Just wondering: maybe would it be possible define in User.rb:
def feed
#some sql like (SELECT * FROM articles....)
end
Would this work at all?
in my last project i stuck into a problem, i had to paginate multiple models with single pagination in my search functionality. it should work in a way that the first model should appear first when the results of the first model a second model should continue the results and the third and so on as one single search feed, just like facebook feeds. this is the function i created to do this functionality
def multi_paginate(models, page, per_page)
WillPaginate::Collection.create(page, per_page) do |pager|
# set total entries
pager.total_entries = 0
counts = [0]
offsets = []
for model in models
pager.total_entries += model.count
counts << model.count
offset = pager.offset-(offsets[-1] || 0)
offset = offset>model.count ? model.count : offset
offsets << (offset<0 ? 0 : offset)
end
result = []
for i in 0...models.count
result += models[i].limit(pager.per_page-result.length).offset(offsets[i]).to_a
end
pager.replace(result)
end
end
try it and let me know if you have any problem with it, i also posted it as an issue to will_paginate repository, if everyone confirmed that it works correctly i'll fork and commit it to the library. https://github.com/mislav/will_paginate/issues/351
for those interested, please check this question: Creating a "feed" from multiple rails models, efficiently?
Here, Victor Piousbox provides a good, efficient solution.
Look at paginate_by_sql method. You can write unione query to fetch both articles and posts:
select 'article' as type, id from articles
union
select 'post' as type, id from posts
You can paginate both if you use AJAX. Here is well explained how to paginate using AJAX with WillPaginate.
You can paginate an array using WillPaginate::Collection.create. So you'd need to use ActiveRecord to find both sets of data and then combine them in a single array.
Then take a look at https://github.com/mislav/will_paginate/blob/master/lib/will_paginate/collection.rb for documentation on how to use the Collection to paginate any array.

Order table by grouping values in another table

I have a table of questions: title
Each question has answers, in another table: text, question_id
Each answer has votes, in another table: answer_id
I can ask for total votes with
question.votes.count
I have seen how to group the votes db at
http://guides.rubyonrails.org/active_record_querying.html#group
Vote.group("answer_id")
But I want to order my questions by votes. That is ordering an array from a table via the grouping of another table. Is that possible?
q=Question.last
q.answers.order_by_vote #How to do this?
I think my answer could be doing a scope on the answer model that makes the grouping on the subset of votes that belong to the question. Am I right?
Thank you.
First, I think that you mean table when you say DB (database). A table a structure inside a database that holds data for a specific model, for example (questions, votes and answers. In your case you have three tables.
Second, calling attr_accessible :answer_id does not make an attribute searchable by an index. It is searchable by this attribute regardless of whether or not you declare it accessible. It is not an index unless you specify it as an index in the database. attr_accessible means that the attribute can be set using mass-assignment, for example when you call update_attributes.
Finally, if you want to group by answer_id and order by the number of votes, you can do so with the following query:
Vote.select('count(id) as votes, answer_id').group('answer_id').order('votes DESC')
If you want to order by some other value than the one you are grouping by, you'll have to add that to your group as well. This ensures that you aren't getting arbitrary results for the second value. For example:
Vote.group('answer_id').group('column_1').order('column_1 DESC')
I just figured out a way to organize my links by vote count, using the sort method in my links view:
<% #user.links.sort { |a, b| b.votes.sum(:score) <=> a.votes.sum(:score) }.each do |link| %>
votes.sum(:score) grabs the number of votes a particular link has from the votes table.
hope that helps.

Resources