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/
Related
Suppose I have an Employee model, there is a method in Employee model named def fixed
def fixed
return self.cached_fixed.to_f if self.cached_fixed.present?
return (self.current_salary && self.current_salary.fixed).to_f
end
end
def current_salary
return #current_salary if #current_salary
# #current_salary = self.employee_salaries.detect{|es| es.end_date == nil}
#current_salary = self.db_current_salary
return #current_salary
end
if the fixed were a column in employee table we could have just used Employee.distinct.select(:fixed) to pull the distinct values
is there a way if it's just a method not a field in table without loading all the employees.
I am expecting to get the unique values of a column from a table , but it may not be a column as in the above table
Not for an arbitrary method, no. But you start the query from the EmployeeSalary end and fetch only the column you care about in one query using select:
EmployeeSalary
.select(:fixed)
.join(:employee)
.where(end_date: nil)
This will run a select fixed from... query and return a list of EmployeeSalary objects, but all the fields that aren't listed in the select call will be nil. Assuming the constraint of only one salary record having end_date: nil, there will be one EmployeeSalary object per employee. You can add .distinct in the method chain if you want unique values.
I'm not sure how the caching logic fits into this question. You can apply caching logic on top of that list if you like, but doing one query like this is pretty fast.
I have a User model with a name attribute. In my PagesController, I want to set an instance variable equal to all of the User objects, but I want to order them based on last name. I know I can do:
#sortedusers = User.order(:name => :asc)
to order the users based on their name attribute, but how do I do it based on last name? (i.e. how do I order the Users based on the last word of their name attributes?
Thanks.
Define a virtual attribute last name in your model
def last_name
name.split(' ').last
end
and then
User.order(:last_name => :asc)
I'd suggest storing the users' last name separately. Barring that, you can use sort_by, like so:
items.sort_by! { |a| a.split(' ') } or something along those lines. Note, this most likely cannot be used straight, it's merely intended to point you in the right direction, as most of my answers are.
So I have a CareerEntry model that has the following attributes: name, job_category, company, group, location, year, full_intern, and it represents the job offers that people have received. full_intern is a string that is either "internship" or "full-time", and represents what the type of the job offer is. All CareerEntries will be created by an Admin interface, so it is essentially acting as a standalone model. This is my question: given a bunch of CareerEntry objects, I want to display a table to display on my careers page (which has an action in a PagesController).
I want the table to be sorted according to multiple attributes. I want each year to be its own section in the table, then within each year, I want the internship entries grouped together and the full-time entries grouped together. Then, within these groupings, I want each job_category to be its own section (job_categories comprise of things like 'Investment Banking,' or 'Technology.')
A very good example of what I'm going for is shown under the "2013" tab in this link.
What is the best way to go about achieving this? I know that in the careers action definition of my PagesController, I could have:
class PagesController < ApplicationController
def careers
#careerentries = CareerEntry.order(:year => :desc, :fullintern => :asc, :job_category => :asc)
end
end
But this would simply return all the entries in the order that I want, and would not allow me to place headers and dividers to separate, say, the job_categories.
Is there any easier way of achieving what I'm looking for?
Perhaps you're looking for .group_by?
Group By
From the link you gave, it looks like you want to group your results by year, like this:
#careerentries = CareerEntry.order(year: :desc, fullintern: :asc, job_category: :asc)
#entries_by_year = #careerentries.group_by { |entry| entry.year }
This gives you all the data, ordered to your specs. You can then sort through it, using the group_by method:
#entries_by_year.each do |entry|
entry.name
end
You could then work this into your table
Good reference Group posts by Year - Rails
I'm trying to implement search over tags as part of a Texticle search. Since texticle doesn't search over multiple tables from the same model, I ended up creating a new model called PostSearch, following Texticle's suggestion about System-Wide Searching
class PostSearch < ActiveRecord::Base
# We want to reference various models
belongs_to :searchable, :polymorphic => true
# Wish we could eliminate n + 1 query problems,
# but we can't include polymorphic models when
# using scopes to search in Rails 3
# default_scope :include => :searchable
# Search.new('query') to search for 'query'
# across searchable models
def self.new(query)
debugger
query = query.to_s
return [] if query.empty?
self.search(query).map!(&:searchable)
#self.search(query) <-- this works, not sure why I shouldn't use it.
end
# Search records are never modified
def readonly?; true; end
# Our view doesn't have primary keys, so we need
# to be explicit about how to tell different search
# results apart; without this, we can't use :include
# to avoid n + 1 query problems
def hash
id.hash
end
def eql?(result)
id == result.id
end
end
In my Postgres DB I created a view like this:
CREATE VIEW post_searches AS
SELECT posts.id, posts.name, string_agg(tags.name, ', ') AS tags
FROM posts
LEFT JOIN taggings ON taggings.taggable_id = posts.id
LEFT JOIN tags ON taggings.tag_id = tags.id
GROUP BY posts.id;
This allows me to get posts like this:
SELECT * FROM post_searches
id | name | tags
1 Intro introduction, funny, nice
So it seems like that should all be fine. Unfortunately calling
PostSearch.new("funny") returns [nil] (NOT []). Looking through the Texticle source code, it seems like this line in the PostSearch.new
self.search(query).map!(&:searchable)
maps the fields using some sort of searchable_columns method and does it ?incorrectly? and results in a nil.
On a different note, the tags field doesn't get searched in the texticle SQL query unless I cast it from a text type to a varchar type.
So, in summary:
Why does the object get mapped to nil when it is found?
AND
Why does texticle ignore my tags field unless it is varchar?
Texticle maps objects to nil instead of nothing so that you can check for nil? - it's a safeguard against erroring out checking against non-existent items. It might be worth asking tenderlove himself as to exactly why he did it that way.
I'm not completely positive as to why Texticle ignores non-varchars, but it looks like it's a performance safeguard so that Postgres does not do full table scans (under the section Creating Indexes for Super Speed):
You will need to add an index for every text/string column you query against, or else Postgresql will revert to a full table scan instead of using the indexes.
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'"})