Ruby - ActiveAdmin CSV custom import - ruby-on-rails

Currently I have an ActiveAdmin page which gets data from a CSV file to populate a database table (users). This table has one unique identifier which is not the ID used in relationships (a user-friendly code for users to view).
The page does this via "active_admin_import"
Now, I want the same thing to populate another table (user_paths), the problem is, this table uses foreign keys from the "users" table, so I want the CSV file to contain this unique identifier from "users" table.
Is there any solution for this?

sorry for late response.
Just lately I added new example to gem wiki that is very similar to your problem.
It can be solved with custom before_batch_import hook using master branch
Next example demonstrates how to resolve author_id value from author name and change csv values dynamically before performing insert query.
ActiveAdmin.register Post do
active_admin_import validate: true,
headers_rewrites: { :'Author name' => :author_id },
before_batch_import: ->(importer) {
authors_names = importer.values_at(:author_id)
# replacing author name with author id
authors = Author.where(name: authors_names).pluck(:name, :id)
options = Hash[*authors.flatten] # #{"Jane" => 2, "John" => 1}
importer.batch_replace(:author_id, options) #replacing "Jane" with 1, etc
}
end

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/

Passing Ruby SQL View parameters

I have a lot of data that I need to query out of a database. Heroku is timing out when I do the following, because of the 30 second limit:
account.records.all.each do |record|
record.contacts.all.each do |contact|
contact.address.all.each do |address|
..write to file etc
end
end
end
I've read that an SQL View will help with performance rather than querying every record in a .each(), however I need to do a where clause on this set of data. Currently, if I use the 'ExportAllRecord' view like so: ExportAllRecords.where("account_id = 3"), it executes the following:
ExportAllRecord Load (5.0ms) SELECT "export_all_records".* FROM "export_all_records" WHERE (account_id = 3)
whereas, I actually need it to add the 'where clause' to the view.
How can I parameterise the SQL View?
I'm using ActiveRecord.
Thanks.
ActiveRecord doesn't care where it queries from a normal database table or a database view.
Assuming your database view is named export_all_records, then just create a new model:
# in app/model/export_all_record.rb
class ExportAllRecord < ActiveRecord::Base
default_scope { readonly }
end
Use this model like a normal ActiveRecord model:
id = 3 # or perhaps params[:id]
ExportAllRecord.find_by(account_id: id)
#=> returns all records from the view with the given id
You can add more conditions if you need to:
ExportAllRecord.
where(account_id: id).
where(column1: true, column2: 'foobar')
order(:column3)

Postgres ORDER BY values in IN list using Rails Active Record

I receive a list of UserIds(about 1000 at a time) sorted by 'Income'. I have User records in "my system's database" but the 'Income' column is not there. I want to retrieve the Users from "my system's database"
in the Sorted Order as received in the list. I tried doing the following using Active Record expecting that the records would be retrieved in the same order as in the Sorted List but it does not work.
//PSEUDO CODE
User.all(:conditions => {:id => [SORTED LIST]})
I found an answer to a similar question at the link below, but am not sure how to implement the suggested solution using Active Record.
ORDER BY the IN value list
Is there any other way to do it?
Please guide.
Shardul.
Your linked to answer provides exactly what you need, you just need to code it in Ruby in a flexible manner.
Something like this:
class User
def self.find_as_sorted(ids)
values = []
ids.each_with_index do |id, index|
values << "(#{id}, #{index + 1})"
end
relation = self.joins("JOIN (VALUES #{values.join(",")}) as x (id, ordering) ON #{table_name}.id = x.id")
relation = relation.order('x.ordering')
relation
end
end
In fact you could easily put that in a module and mixin it into any ActiveRecord classes that need it, since it uses table_name and self its not implemented with any specific class names.
MySQL users can do this via the FIELD function but Postgres lacks it. However this questions has work arounds: Simulating MySQL's ORDER BY FIELD() in Postgresql

Texticle and ActsAsTaggableOn

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.

IF/CASE statement OR MySQL OR array for get category name

On my site I got entries which have category. Site have only 5 categories, so I have dilemma:
Make relationship between category table and entries (category_id) table
OR
Make method which return category name via IF/CASE statement? Like:
case #entry.category.id
when 1
"Games"
when 2
"Movies"
when 3
"Fun"
[...]
end
(I remind that I must get 10 category name per page)
OR
Use array:
cat[1] = "Games"
cat[2] = "Movies"
cat[3] = "Fun"
[...]
<%= cat[#entry.category.id] %>
I think this relation definitely belongs into the database. (adding a category table)
it is the most sane and most scalable option.
It is also the cleanest, because you break the seperation of data, display and logic (MVC: model, view, controller) when hardcoding the categories in your application.
you can easily select the item AND its category with a single query:
SELECT item.*, category.name
FROM item
LEFT JOIN category ON category.id = item.category_id
WHERE some=condition
there are similar queries for INSERTs and UPDATEs (at least in MySQL), so you never need a second query.
If the only thing you care about category is "name", then you should just store the category_name in the entries table.
OR
Make a constant CATEGORY_NAME and wrapper method to get the name with id in the entries table (without using Category table/model at all). eg.,
class Entry
CATEGORY_NAME = [ "Games", "Movies", "Fun"]
def category_name
CATEGORY_NAME[cat_id] #cat_id being just 0,1,2 .. depends how you want to store
end
...
I am sure there are many ways to achieve this anyway.
Hope it helps.

Resources