Rails Admin Eager loading relations for list view - ruby-on-rails

We have a list view for a model Ticket in rails admin that loads very slowly.
class Ticket < ActiveRecord::Base
belongs_to :crew
end
The reason it is slow is that we display the ticket's crew relation through a method rails_admin_pretty_print which accesses other related models.
class Crew < ActiveRecordBase
belongs_to :pool
belongs_to :leader
def rails_admin_pretty_print
"leader : #{leader.name} at time #{pool.datetime}"
end
end
I want to eager load all of these objects in the initial query in order to speed up the request. Something like:
config.model "Ticket" do
object_label_method :rails_admin_pretty_print
list do
field :crew, includes(:pool, :leader)
end
end
I can't find any way to do this in the rails admin docs. Is there a way of doing this?

If the default_scope of a model is set, Rails Admin uses it for the list view. Obviously it's not ideal (unless this is incredibly important and worth the workaround), but you could set your default scope in your Crew model as follows:
class Crew < ActiveRecord::Base
default_scope { includes(:pool, :leader) }
# more crew stuff
end
Like I said, it's not ideal, because you'd have to use unscoped every time you want to access your Crew model without including :pool and leader.
Crew.unscoped.where(id: [1,2,4])

Related

Which is the best use of discard (or act_as_paranoid) in rails-admin?

I'm using the gem discard and also rails-admin. I would like to know which is the best approach to list my active users in a shop in rails admin, taking into consideration the users kept
I've created a method in the model shop:
class Shop < ApplicationRecord
include ShopRailsAdmin
has_many :users
def active_users
users.kept
end
end
In rails admin I'm using:
field :active_users do
label 'Users'
end
But I'm receiving an AssociationRelation instead of a CollectionProxy so in the view, the association looks like
#<User::ActiveRecord_AssociationRelation:0x00007f9c34c1f8e0>
Is there another way to do this so I can avoid defining the method in the model shop?
PD: the tag should have been also discard but it does not exist and I could not create it.
Thanks!
You need to define it as an scoped association
class Shop < ApplicationRecord
include ShopRailsAdmin
has_many :users
has_many :active_users, -> lambda {
where(discarded_at: nil)
}, class_name: 'User'
end
I'm assuming you did not personalize the discard_column.
Rails admin should display them right.

Stop nesting data of the active model serializer gem

I am using the active model serializer gem, and it works fine for now. I have however stumbled upon an issue, where I wan't to stop the nesting of the data retrieved.
Lets say I have 3 models:
Users who has_many orders who has_many addresses.
Normally in my Users serializer class I would have have a has_many to the orders model, and in the orders model serializer have a has_many relationship to the addresses.
I now have a users controller, where I don't want the orders out, but not the nested addresses. Can this be done without creating a whole new serializer class?
Update, to clarify:
I have the following 3 models:
class User < ActiveRecord::Base
has_many orders
end
class Orders < ActiveRecord::Base
belongs_to user
has_many addresses
end
class Addresses < ActiveRecord::Base
belongs_to order
end
I have 3 serializers which are identical to the models.
For my orders API I would like to retrieve the addresses as well, but when I query users I only wants the associated orders and not addresses. As it is now when I query the users it both returns all the orders and addresses, since I have a has_many to addresses from the orders.
Is the only option to create separate serializers for the two options (it just doesn't feel very DRY)?
You can use default_serializer_options method that defines serializer options for
all serializers and their children used by this controller.
#users_controller.rb
class UsersController < ApplicationController
def default_serializer_options
{ include_addresses: false }
end
end
Then override initialize method in Order serializers to check serializer options for include_addresses flag:
# order_serializer.rb
class OrderSerializer < ActiveModel::Serializer
def initialize(object, options = {})
options.reverse_merge! include_addresses: true # by default it should include addresses
#include_addresses = options[:include_addresses]
super
end
def include_addresses?
#include_addresses
end
end
And that's it. Specify this option in every controller that you don't need addresses to be nested.
You can remove the has_many from the serializer and it will stop including the associations.

What type of relations to choose if I want to link 2 models with one

I have model Message. It may be posted by either Person or Organization from my DB.
I want to call update.contact, where contact is Organization or Person.
class Update < ActiveRecord::Base
has_one :contact
The decision I like to use is like
class Organization < ActiveRecord::Base
belongs_to :update, as: :contact
But this approach not available in Rails.
Should I use polymorphic association? How to organize the architecture for this case?
It sounds like Organization and Person might be two different variants of a same entity (a customer, user, whatever). So, why not create a common parent model for the two of them? Such a parent wouldn't necessarily need a table, and you might just define common methods inside of it. Update is more of an action rather than an object, which could be applied to a Contact object (typically in its controller). For the Contact class, polymorphic association can be used. So you might have:
class Parent < ActiveRecord::Base
# stuff Person and Organization share
end
class Person < Parent
has_one :contact, as: :owner
# Person methods etc.
end
class Organization < Parent
has_one :contact, as: :owner
# Organization stuff
end
class Contact
belongs_to :owner, polymorphic: true
def update
#...
end
# other stuff for Contact
end
Then you can write lines like:
Person.first.contact.update
or whatever you need to do with your objects.
In case your Organization and Person don't differ too much, you could just create a table for the parent class, and add the has_one etc. in there.

How to add methods to a has_many collection

Assuming a typical has_many association
class Customer < ActiveRecord::Base
has_many :orders
end
class Order < ActiveRecord::Base
belongs_to :customer
end
How can I add a method to the collection of orders? For the sake of code organization, I'm trying to reactor this method (this is a made-up example) inside of my Customer class:
def update_orders
ThirdPartyAPI.look_up(self.orders) do |order|
# Do stuff to the orders
# May need to access 'self', the Customer...
end
end
I don't like this because it puts a lot of knowledge about the Order class inside my Customer class. I can't use an instance method off of an order, since the ThirdPartyAPI can do a batch lookup on multiple orders. I could make a static method off of Order and pass in the array of orders, and their parent customer, but this feels clunky.
I found this in the rails docs, but I couldn't find any good examples of how to use this in practice. Are there any other ways?
I think this should do it
has_many :entities do
def custom_function here
end
def custom_function here
end
end

Design considerations for creating associated records on Devise User object on registration

I'm using Devise, and for each User account created I want to generate a relationship where:
class User < ActiveRecord::Base
belongs_to :business
end
class Business < ActiveRecord::Base
has_many :users
has_one :apt_setting
has_many :hours, :as => :hourable
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
end
So upon registration an associated Business object is created, and with each Business object an associated ApptSettings and BusinessHour object is created.
I currently have this implemented like this:
class Admin
before_create :create_associated_records
def create_associated_records
# create the associated business object
business = Business.create(:business_name => business_name, :subdomain => subdomain, :initial_plan => initial_plan)
# retrieve the id of the new business object
self.business_id = business.id
# create the associated records
BusinessHour.default_values(business_id)
ApptSetting.default_values(business_id)
end
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
def self.default_values(business_id)
# ... create record with default values
end
end
class BusinessHour < Hour
belongs_to :hourable, :polymorphic => true
def self.default_values(business_id)
# ... create record with default values
end
end
This does work, but does it seem like the best design?
One alternative I'm considering is handling removing Admin -> create_associated_records, and instead do that work in Users::Accounts::RegistrationsController where I override the 'create' method. There I could build all the associated records, set :accepts_nested_attributes where appropriate, then call 'save' on the Business object, which should then cause all the associated records to be generated.
Thoughts on the best design, or any other ideas?
you don't need the default_values methods. In your create_associated_records you can change those calls to:
ApptSetting.create(:business_id => business_id)
Don't override the create method. before_create callbacks are a better way to go. In either case, If a business has many users, do you really want to create a new business every time a new user is created? How does a second user ever get added to a business? add something like,
def create_associated_records
return unless self.business_id.nil?
....
Also where are the business_name, subdomain, and initial_plan variables coming from in your method? Do you have them as attributes of the admin user? Seems like they should be only values of the business.
I think the biggest question here is, does a user really need a business in order to exist? Why can't the user just create their Business after they create their account?
** Edit: Being more clear / cleaner version using rails association methods:
class Admin
before_create :create_associated_records
private
def create_associated_records
return unless self.business_id.nil?
self.create_business
self.business.create_appt_setting
self.business.hours.create
end
end

Resources