ActiveAdmin how to sort column with associations - ruby-on-rails

I'm developing an ActiveAdmin app, and I want to sort a column of businesses by their "type". Unfortunately my code is not working. What code should I use to accomplish this? Here is my code...
app/models/business.rb
class Business < ActiveRecord::Base
belongs_to :type
attr_accessible :description, :email, :facebook, :foursquare, :google, :manager,
:mobile, :name, :phone, :type_id, :url, :yelp
end
app/models/type.rb
class Type < ActiveRecord::Base
attr_accessible :category
has_many :businesses
def to_s
category
end
end
app/admin/businesses.rb
ActiveAdmin.register Business, { :sort_order => :name_asc } do
scope :joined, :default => true do |businesses|
businesses.includes [:type]
end
index do
column :name
column :type, :sortable => 'businesses.type'
column :manager
column :email
default_actions
end
end
Thanks!

according to this discussion: https://github.com/gregbell/active_admin/pull/623, if you don't want to use scopes, you can use the scoped collection method instead:
ActiveAdmin.register Business, { :sort_order => :name_asc } do
scope :all, :default => true
index do
column :name
column :type, :sortable => 'types.category'
column :manager
column :email
default_actions
end
controller do
def scoped_collection
end_of_association_chain.includes(:type)
end
end
end

FIXED
column :type, :sortable => 'types.category'

Yes, the scoped_collection Evgenia provided works great. Also for more than one columns:
ActiveAdmin.register Foo do
index do
column :field_name_a, :sortable => 'association_a.field_name'
column :field_name_b, :sortable => 'association_b.field_name'
end
end
controller do
def scoped_collection
end_of_association_chain.includes([:association_a, :association_b])
end
end

This can be done.
Here I have a model called Star. A star belongs to a Person. I'm going to put the Person.name in the Star admin index, make it sortable, make it work with scopes, and add filters.
First you have to add the join model to each of your scopes. In this case I had 3 scopes: all, category_subscriptions and person_subscriptions. I'm declaring the scopes and adding the join model to them:
ActiveAdmin.register Star do
[ :all, :category_subscriptions, :person_subscriptions ].each do |sym|
scope(sym, :default => (sym == :all) ) do |stars|
stars.includes [:person]
end
end
end
Now to add the person name from the join model into my star index I do this:
index do
id_column
column("Name", nil, :sortable => :"people.name") {|star| star.person.name}
column("Email", nil, :sortable => :"people.email") {|star| star.person.email}
default_actions
end
Let's dissect that:
column("Name", nil, :sortable => :"people.name") {|star| star.person.name}
The first parameter is the column title.
The second is not needed since we're overriding sort and the value.
:sortable tells Active Admin how to sort the thing. This is the table name, since it's going into SQL.
The block tells Active Admin what to use as a row value.
Now to add some filters. This is much easier:
filter :person_name, :as => :string
filter :person_email, :as => :string
And you're done.

Based on your needs this is my implementation working for sorting, default index and filtering:
ActiveAdmin.register Business do
index do
column :name
column :type, :sortable => 'businesses.type'
column :manager
column :email
default_actions
end
controller do
def scoped_collection
super.includes(:businesses)
end
end
end
Just in case you are wondering how to sort when you have one more level of the association, this is the way I'm dealing with that for eg:
class TechResult < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
belongs_to :organization
end
class Organization < ActiveRecord::Base
has_many :users
end
and then in your tech_results section, you want to render an organization field, in my case the value tech_result.user.organization.name this is the way to sort them out:
ActiveAdmin.register TechResult do
config.batch_actions = false
permit_params :user_id
preserve_default_filters!
index do
column :id
column 'user', sortable: 'users.name' do |tr|
tr.user.name
end
column 'organization', nil, sortable: 'organizations.name' do |tr|
tr.user.organization.name.titleize if tr.user.organization
end
end
controller do
def scoped_collection
super.includes(user: [:organization])
end
end
end

Related

How to show a link to related records FROM ALIAS in ActiveAdmin?

I have this classes in order to show related records within user and all records on top menu.
ActiveAdmin.register Position, :as => 'Positionsfake' do
belongs_to :user
controller do
def scoped_collection
end_of_association_chain.where(category: 1)
end
end
ActiveAdmin.register Position, :as => 'Contactsfake' do
belongs_to :user
controller do
def scoped_collection
end_of_association_chain.where(category: 0)
end
end
ActiveAdmin.register Position do
controller do
def scoped_collection
end_of_association_chain.where(category: 1)
end
end
ActiveAdmin.register Position, :as => 'Contacts' do
controller do
def scoped_collection
end_of_association_chain.where(category: 0)
end
end
ActiveAdmin.register User do
controller do
def scoped_collection
User.includes(:provider).where(:providers => {:user_id => nil})
end
end
index do
column :name
column "Positions" do |a|
link_to a.positions.count, admin_user_positions_path(a)
end
column "Contatcs" do |a|
a.contacts.count
end
actions
end
Positions works fine! But Contacts can`t be called from here.
I have only one model. Contact and Position are the same except in category field. There is any way to solve this?
Thanks.
Any of this works for me.
ActiveAdmin.register User do
controller do
def scoped_collection
User.includes(:provider).where(:providers => {:user_id => nil})
end
end
index do
column :name
column :positions do |a|
link_to a.positions.where(category: 1).count, admin_user_positions_path(a)
end
column 'Contacts', :positions do |a|
a.positions.where(category: 0).count
end
actions
end
ActiveAdmin.register User do
controller do
def scoped_collection
User.includes(:provider).where(:providers => {:user_id => nil})
end
end
index do
column :name
column 'Positions' do |a|
link_to a.positions.where(category: 1).count, admin_user_positions_path(a)
end
column 'Contacts', 'Positions' do |a|
a.positions.where(category: 0).count
end
actions
end

How update field in index list

I have "payment" field displayed as tag, boolean type in my database and if is it possible I'd like to add action on this field. In case when i click on this field i'd like change status from false to true, and reverse. And it all in index list.
ActiveAdmin.register Booking do
permit_params :user_id, :race_id, :payment
actions :all
index do
selectable_column
column :race
column :user
column :payment
column :created_at
column :updated_at
actions
end
end
File routes.rb
//config/routes.rb
scope :admin do
resources :bookings do
member do
get :payment
end
end
end
My file booking.rb
//app/admin/booking.rb
ActiveAdmin.register Booking do
permit_params :user_id, :race_id, :payment
actions :all
controller do
def payment
booking = Booking.find(params[:id])
booking.payment = !booking.payment # toggle the status
booking.save
redirect_to booking_path(booking)
end
end
index do
selectable_column
column :race
column :user
column "Confirm" do |booking|
link_to "Confirm", payment_booking_path(booking)
end
column :created_at
column :updated_at
actions
end
end
What is wrong with my code because i get "uninitialized constant BookingsController". My link: admin/bookings/16/payment
In view
= link_to (#booking.payment ? true : false), payment_booking_path(#booking)
in controller
def payment
booking = Booking.find(params[:id])
booking.payment = !booking.payment # toggle the status
booking.save
redirect_to booking_path(booking)
end
in routes
resources :bookings do
member do
get :payment
end
end

Active Admin accessing two models

I would like to access the data from the Profile using Active Admin.
Profile has this model:
class Profile < ActiveRecord::Base
attr_accessible :address, :name, :phone
belongs_to :user
end
Print (orders) has this model:
class Print < ActiveRecord::Base
....
belongs_to :user
has_one :profile
...
end
Both models have user_id column in it, from the User model.
and the Print (Orders) print.rb is written as:
ActiveAdmin.register Print, :as => "Order" do
index do
column "Document", :document_file_name do |print|
link_to print.document_file_name, print.document.url
end
column "Size", :document_file_size do |print|
number_to_human_size(print.document_file_size)
end
column "Print Status", :is_printing
column "Deliver Status", :is_delivered
column "Comments", :comment
default_actions
end
show do
panel "Invoice" do
table_for(order.user.profile) do
column "name" do |profile|
profile.name
end
end
end
end
end
How do i get the data from profile, for example, :address from Active Admin?
EDIT:
I am sharing my solution since someone requested for it:
sidebar "Customer Details", :only => :show do
attributes_table_for customer.profile do
row("Email") { customer.email }
row("Name") { auto_link customer.profile.name }
row("Address") { auto_link customer.profile.address }
row("Phone") { auto_link customer.profile.phone }
end
end
Try this
column :address do |order|
order.user.address
end

How to use ActiveAdmin on models using has_many through association?

I am using ActiveAdmin gem in my project.
I have 2 models using has_many through association. The database schema looks exactly the same as the example in RailsGuide. http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association
(source: rubyonrails.org)
How can I use ActiveAdmin to ...
show appointment date of each patient in physicians page?
edit appointment date of each patient in physicians page?
Thanks all. :)
For 1)
show do
panel "Patients" do
table_for physician.appointments do
column "name" do |appointment|
appointment.patient.name
end
column :appointment_date
end
end
end
For 2)
form do |f|
f.inputs "Details" do # physician's fields
f.input :name
end
f.has_many :appointments do |app_f|
app_f.inputs "Appointments" do
if !app_f.object.nil?
# show the destroy checkbox only if it is an existing appointment
# else, there's already dynamic JS to add / remove new appointments
app_f.input :_destroy, :as => :boolean, :label => "Destroy?"
end
app_f.input :patient # it should automatically generate a drop-down select to choose from your existing patients
app_f.input :appointment_date
end
end
end
In answer tomblomfield follow up question in comments:
Try the following in your AA ActiveAdmin.register Model do block:
controller do
def scoped_collection
YourModel.includes(:add_your_includes_here)
end
end
This should lazy load all your associations for each index page in a separate query
HTH
It should solve the N+1 query problem.
show do
panel "Patients" do
patients = physician.patients.includes(:appointments)
table_for patients do
column :name
column :appointment_date { |patient| patient.appointments.first.appointment_date }
end
end
end
It's work for me (with chosen)
permit_params category_ids: []
form do |f|
inputs 'Shop' do
input :category_ids, collection: Category.all.collect {|x| [x.name, x.id]}, as: :select, multiple: true, input_html: { class: "chosen-input", style: "width: 700px;"}
end
f.actions
end
#monfresh #tomblomfield you can do
has_many :appointments, ->{ includes(:patients) }, :through => :patients
in the physicians model
...or, I'm not sure if you can use it with formtastic but you could make the scope optional with something like
has_many :appointments :through => :patients do
def with_patients
includes(:patients)
end
end
and appointment.patient wont n+1 anymore
If you would like show multiple field in a panel row you can use following view:
show do |phy|
panel "Details" do
attributes_table do
... # Other fields come here
row :appointment_dates do
apps=""
phy.appointments.all.each do |app|
apps += app.patient.name + ":" + app.appoinment_date + ", "
end
apps.chomp(", ")
end
end
end
end
To place it in you redit form first put appointment_ids to permitted list:
permit_params: appointment_ids:[]
Add has many relationship to the form
form do |f|
f.has_many :appointments do |app|
app.inputs "Appointments" do
app.input :patients, :as => :select, :label => "Assigned Patients"
app.input :appointment_date
end
end
end
Should work if there is no coding error.
Regarding #2, it should be like this:
form do |f|
f.inputs 'Physician Details' do
f.input :name
end
f.inputs 'Physician Appointments' do
f.has_many :appointments,
heading: false,
new_record: 'Add new appointment',
remove_record: 'Delete appointment',
allow_destroy: true do |app|
app.input :patient, label: 'Choose the patient', collection: Patient.pluck(:name, :id)
app.input :appointment_date
end
end
Regarding the heading: - it can be false or some label (string)
Regarding the allow_destroy: - you can set it check for the user Administrator privilege's as can seen here
Important - In the Physician model, make sure to have
accepts_nested_attributes_for :appointments, allow_destroy: true
And, in the active admin model file - admin\physicians.rb - set this:
permit_params :name, appointments_attributes: [:patient_id, :_destroy, :id]

ActiveRecord - automatically merging model data as an aggregate

Lets say I have two tables.
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.string :type, :default => 'User'
t.string :user_name, :null => false
t.boolean :is_registered, :default => true
# ... many more fields
end
end
end
class CreateContactInfo < ActiveRecord::Migration
def self.up
create_table :contact_info do |t|
t.integer :resource_id
t.string :resource_type
t.string :first_name
t.string :last_name
t.string :middle_initial
t.string :title
end
end
end
class ContactInfo < ActiveRecord::Base
belongs_to :contactable, :polymorphic => true
end
class User < ActiveRecord::Base
has_one :contact_info, :as => :contactable
# composed_of :contact_info # ... It would be nice if magics happened here
end
I would like to have the User's contact_info automatically merged into my User object as attributes of the user object without having to say #user.contact_info.first_name; instead, I would prefer to be able to write #user.first_name.
The reason I am breaking out attributes to the contact_info table is that these are common attributes to multiple models. That is why I am making setting up the contact_info as a polymorphic association.
Does anyone know of a good way to aggregate/merge the attributes of contact_info directly into my user model?
Use delegate:
class User < ActiveRecord::Base
has_one :contact_info, :as => :contactable
delegate :name, :name=, :email, :email=, :to => :contact_info
end
Not necessarily a good way to do it, but I did something similar by overriding the method_missing method and then calling my aggregated object. So, it would look something like:
class User
def method_missing(method_id)
self.contact_info.send(method_id)
end
end
Edit 1: Better implementation (I think this will work):
class User
alias_method :orig_method_missing, :method_missing
def method_missing(method_id)
if (self.contact_info.respond_to?(method_id))
self.contact_info.send(method_id)
else
orig_method_missing(method_id)
end
end
end
The above has the advantage that all other unknown method calls will get passed correctly.
I finally got it! Thank you both amikazmi and Topher Fangio. I had to implement both the delegate and method_missing techniques to get this to work.
Here is the total madness that finally ended up working for me! If anybody has suggestions on how to further improve this, I'd love to hear your suggestions.
class User < ActiveRecord::Base
attr_accessible *([:user_name, :udid, :password, :password_confirmation, :contact_info] + ContactInfo.accessible_attributes.to_a.map {|a| a.to_sym})
has_one :contact_info, :as => :contactable
def method_missing(method_id, *args)
if (!self.respond_to?(method_id) && self.contact_info.respond_to?(method_id))
self.contact_info.send(method_id, *args)
elsif (!self.class.respond_to?(method_id) && ContactInfo.respond_to?(method_id))
ContactInfo.send(method_id, *args)
else
super(method_id, *args)
end
end
# delegating attributes seems redundant with the method_missing above, but this secret sauce works.
ContactInfo.accessible_attributes.to_a.each do |a|
delegate a.to_sym, "#{a}=".to_sym, :to => :contact_info
end
def initialize(*args)
options = args.extract_options!
contact_attrs = ContactInfo.accessible_attributes.to_a.map{|a| a.to_sym}
#ci = ContactInfo.new(options.reject {|k,v| !contact_attrs.include?(k) })
super(*(args << options.reject { |k,v| contact_attrs.include?(k) }.merge(:contact_info => #ci) ) )
self.contact_info = #ci
end
validates_presence_of :user_name
validates_uniqueness_of :user_name
validates_associated :contact_info
def after_save
# automatically save the contact info record for the user after the user has been saved.
self.contact_info.save!
end
end
class ContactInfo < ActiveRecord::Base
set_table_name "contact_info"
belongs_to :contactable, :polymorphic => true
validates_presence_of :email
validates_uniqueness_of :email
attr_accessible :first_name,
:last_name,
:middle_initial,
:title,
:organization_name,
:email,
:email_2,
:twitter_name,
:website_url,
:address_1,
:address_2,
:city,
:state,
:zip,
:phone_work,
:phone_mobile,
:phone_other,
:phone_other_type
def full_name
[self.first_name, self.last_name].compact.join(' ')
end
end

Resources