Stop nesting data of the active model serializer gem - ruby-on-rails

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.

Related

One relation for two models

I was trying to find answer on my question, but didn't success with it.
I have models Event, participants, participation_form, invitation, user.
Event has_many participants
User has_many invitations
User has_many participation_form
For Participant I want to have field like "based_on" and it will be references with invitation or participation_form.
I have one idea about it - make two fields and one model method that will be check which field contains value and return "based_on"
My question is - is there any way to reference one model to two models with pair of fields - class (model name) and value (id) so I will add another type if I need it in future.
You could use polymorphic associations for that: (http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Could you tell more about models relations so I can write some example? Why do you need Participant model?
As mentioned byKuba Ploskonka, you'll probably benefit from a polymorphic association here:
--
Setup
For Participant I want to have field like "based_on" and it will be references with invitation or participation_form.
As per your specifications, you'll want to use the following:
#app/models/participation.rb
Class Participation < ActiveRecord::Base
belongs_to :participle, polymorphic: true
end
#app/models/invitation.rb
Class Invitation < ActiveRecord::Base
has_many :participations, as: :participle
end
#app/models/participation_form.rb
Class ParticipationForm < ActiveRecord::Base
has_many :participations, as: :participle
end
This will give you the ability to save your objects as follows:
#app/controllers/invitations_controller.rb
Class InvitationsController < ApplicationController
def create
#invitation = Invitation.new invitation_params
#invitation.participations.build #-> will save a blank "Participation" object
#inviation.save
end
end

Include a record by default in any association

I have a Client Model as below:
class Client < ActiveRecord::Base
has_many :custodians,:dependent => :destroy
I have a Custodian Model as below:
class Custodian < ActiveRecord::Base
belongs_to :client
In my custodians table I have record with id = 0 , name = 'N/A' that I want to include in all my collection_selects irrespective of the client_id.
e.g for client_id = 10 I want the following in collection_select
Custodian.where('client_id = 10 or client_id = 0')
I know I can do it in my views but I have too many views so it is not practical. Plus I want a more DRY method on either Custodian model or associations. I tried default_scope on Custodian model but could not get it to work.
Basically I am looking for way to always include custodian with id=0 in each association and collection_select.
You can't do what you want using a has_many and belongs_to approach. To implement a belongs_to relationship, the Custodian record has to have a single client_id field. Your logic requires that the custodian_id=0 record belong to many Client records, so it would have to have many client_id fields, but it can only have one. See the Rails Guides-Active Record Associations-The belongs_to Association (http://guides.rubyonrails.org/association_basics.html)
You can accomplish what you want by using a has_and_belongs_to_many relationship. By making both the Custodian and Client models has_and_belongs_to_many to each other, you will be able to have the custodian_id=0 record belong to many Client records and all the other Custodian records will only belong to one client (even though they could belong to many, your program logic must only allow them to belong to one.) See the has_and_belongs_to_many section of the above Rails Guide. To be clear, here is how your models would look:
class Client < ActiveRecord::Base
has_many_and_belongs_to_many :custodians
end
class Custodian < ActiveRecord::Base
has_many_and_belongs_to_many :client
end
Also, because of your special case on custodian_id=0, you will need to establish the look-up table record for the custodian_id=0 record relationship using an active_record callback (probably before_validation or before_create) when you create a new Client record.
Similarly, you will need to implement your own :dependent => :destroy functionality using the before_destroy callback to preserve the custodian_id=0 record and delete all the other associated Custodian records. You'll also have to destroy the corresponding look-up table entries.
This sounds like a lot of work, but if you absolutely must have the custodian_id=0 record associated with every Client, this is the only way I can see it being done. You may want to evaluate it this is really necessary. There may be other program logic that could allow you to get to similar results without going through this process.
You could use an instance or class method:
#app/models/client.rb
Class Client < ActiveRecord::Base
has_many :custodians,:dependent => :destroy
def inc_zero(id)
where("client_id = ? OR client_id = 0", id)
end
def self.inc_zero_custodians(id)
joins(:custodians).where("client_id = ? OR client_id = 0", id)
end
end
#-> Client.custodians.inc_zero(10)
#-> Client.inc_zero_custodians(10)

Rails Admin Eager loading relations for list view

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])

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

Creating associations by using checkboxes

A User can only have two types of Subscriptions: DailySubscription and WeeklySubscription. When the user is at the new and edit action, I'd like them to check off either of the subscriptions they would like to get.
I'm comfortable using nested fields (as per Ryan Bates' screencast here) but I think when I add inheritance, it really complicating matters. Is there a better way?
class User < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
# type field is defined in the migration for Single Table Inheritance
end
class DailySubscription < Subscription
# Business logic here
end
class WeeklySubscription < Subscription
# Different business logic here
end
My initial efforts with the controller are wacky:
class UsersController < ApplicationController
def new
#user = User.new
# I can't use #user. subscriptions.build as Rails doesn't
# know what type of model to add!
#user.subscriptions = [DailySubscription.new, WeeklySubscription.new]
end
...
end
I think I am conceptually really missing something here but I can't figure it out. Help!
Judging from your description, your user has only two possible subscription choices: daily and/or weekly. Therefore you dont need to have a has_many association because two has_ones would suffice(note polymorphic subscribeable below:
class User < ActiveRecord::Base
has_one :daily_subscription, :as => :subscribeable
has_one :weekly_subscription, :as => :subscribeable
end
class Subscription < ActiveRecord::Base
belongs_to :subscribeable, :polymorphic => true
# type field is defined in the migration for Single Table Inheritance
end
class DailySubscription < Subscription
# Business logic here
end
class WeeklySubscription < Subscription
# Different business logic here
end
furthermore for the controller you just need to initialize User. Upon initialization, #user.daily_subscription and weekly_subscription will be null as determined by .blank? method. When you go ahead and create the user in the create method, you will need to populate these fields with instances of corresponding subscriptions.
class UsersController < ApplicationController
def new
#user = User.new
# bam -- youre done.
end
...
end

Resources