Rails AMS, specify different serializers in polymorphic list - ruby-on-rails

I implemented a multi-table search using pg_search, and then serialize the result with Active Model Serializer (version 0.10) - it works fine, but AMS uses the default serializer for each of the types returned.
Here's the serializer:
class SearchBarSerializer < ApplicationSerializer
attributes :searchable_type
belongs_to :searchable
end
Thus, for example, when serializing the returned objects from pg_search, if the relevant object is a "User", then AMS uses UserSerializer. If the relevant type is a league, then AMS uses LeagueSerializer.
That's fine - but I would like to use a different serializer for each type. This is for a search bar, and so I only care about a much smaller amount of data than the full standard serializer. (EDIT: the standard serializers serialize all attributes and associations for each of the User and League models, which can be seen below. Each model is somewhat significantly large, and for the purposes of just a search, I really only need each model's name and id, and perhaps some other smaller data for each type)
Is there some way that I can specify which serializer to use depending on the object?
Thank you!
EDIT:
User Model:
class User < ActiveRecord::Base
include PgSearch
#################### Associations
has_and_belongs_to_many :roles
belongs_to :profile_page_visibility, optional: true # the optional part is just for when user's are created.
has_and_belongs_to_many :leagues, class_name: "Leagues::League", join_table: "users_leagues_leagues"
has_one :customer, class_name: "Payments::Customer", dependent: :destroy
has_many :unpaid_charges, class_name: "Payments::UnpaidCharge", dependent: :destroy
has_many :charges, class_name: "Payments::Charge", dependent: :destroy
has_many :cards, class_name: "Payments::Card", dependent: :destroy
has_many :league_join_requests, class_name: "Leagues::JoinRequest", dependent: :destroy
has_many :notifications, class_name: "Notification", foreign_key: :recipient_id
has_many :league_invitations, class_name: "Leagues::Invitation", dependent: :destroy
has_many :teams, class_name: "Leagues::Team"
has_many :divisions, class_name: "Leagues::Division" # Can act as division commissioner
has_many :conferences, class_name: "Leagues::Conference" # Can act as conference commissioner
League Model:
class Leagues::League < ApplicationRecord
enum pay_level: [ :basic, :custom, :premium ]
include PgSearch
#################### Associations
has_and_belongs_to_many :users, class_name: "User", join_table: "users_leagues_leagues"
has_and_belongs_to_many :commissioners, class_name: "User", join_table: "commissioners_leagues_leagues"
belongs_to :commissioner, class_name: "User", foreign_key: :commissioner_id, optional: true
has_and_belongs_to_many :feature_requests, class_name: "FeatureRequest", join_table: "feature_requests_leagues_leagues"
has_many :join_requests, class_name: "Leagues::JoinRequest", dependent: :destroy
has_many :invitations, class_name: "Leagues::Invitation", dependent: :destroy
has_many :notifications, class_name: "Notification", as: :notifiable_subject, dependent: :destroy
has_many :teams, class_name: "Leagues::Team", dependent: :destroy
has_many :conferences, class_name: "Leagues::Conference", dependent: :destroy
has_many :divisions, class_name: "Leagues::Division", dependent: :destroy

If you want to define a specific serializer lookup for your associations, you can override the ActiveModel::Serializer.serializer_for method to return a serializer class based on defined conditions.
For your case, it might look something like:
class SearchBarSerializer < ApplicationSerializer
attributes :searchable_type
belongs_to :searchable
class << self
def serializer_for(model, options)
return TinyUserSerializer if model.class == User
return TinyLeagueSerializer if model.class == Leagues::League
super
end
end
end

Related

Self-Referential Has Many Through with Custom Foreign Keys in Rails

I have a User model and a relationship table called ParentsChildren.
I'm trying to create two relationships on the User model so that User#children returns all of a users children and User#parents returns all of a users parents.
I've managed to get this working before, but I'm doing something wrong right this time, and I'm not sure what it is exactly.
class ParentsChildren < ApplicationRecord
self.table_name = 'parents_children'
belongs_to :parent_user, class_name: 'User'
belongs_to :child_user, class_name: 'User'
end
class User
has_many :parent_relationships, class_name: 'ParentsChildren', foreign_key: :parent_user_id
has_many :child_relationships, class_name: 'ParentsChildren', foreign_key: :child_user_id
has_many :children, through: :parent_relationships, class_name: 'User', source: :child_user
has_many :parents, through: :child_relationships, class_name: 'User', source: :parent_user
end
# => uninitialized constant ParentsChildren::ChildUser
Figured it out. The key was to drop 'User' as the class name for has_many :parents and has_many :users. It's inferred through the given sources.
class User
has_many :parent_relationships, foreign_key: :child_user_id,
class_name: 'ParentsChildren'
has_many :children, through: :parent_relationships,
source: :parent_user
has_many :child_relationships, foreign_key: :parent_user_id,
class_name: 'ParentsChildren'
has_many :parents, through: :child_relationships,
source: :child_user
end

Multiple polymorphic associations on the same object

I have a Flag model which is joined to multiple other objects with FlagInstance and a polymorphic flaggable on that table:
table 'flag_instances'
flag_id
flaggable_id
flaggable_type
.....
With has many_through I'm able to fetch any flaggable object like user.flags which is great.
However I'm trying to flag objects with errors and the notify other objects so I've added
table 'flag_instances'
flag_id
flaggable_id
flaggable_type
notifiable_id
notifiable_type
.....
The problem is, a User can have a flag and can be notified of a flag. So user.flags isn't specific enough to show me which is a flag and which is a notification of a flag.
I think I need to change the relationships:
user.rb
has_many :flag_instances, as: :flaggable, dependent: :destroy
has_many :flags, through: :flag_instances
has_many :flag_instances, as: :notifiable, dependent: :destroy
has_many :flags, through: :flag_instances
But I'm not sure what to change them to. Can someone please suggest a solution?
Note: both flags and notifications of flags can belong to multiple objects, so they both need to remain polymorphic.
Thanks
Association for notifiable needs to be changed. In this case user.rb:
has_many :flag_instances, as: :flaggable, dependent: :destroy
has_many :flags, through: :flag_instances
has_many :notifiables, as: :notifiable, dependent: :destroy, class_name: 'FlagInstance'
has_many :notifications, through: :notifiables, class_name: 'Flag'
Note: You might also need to provide foreign_key in case Rails association is not able to pick up the key itself.
Each association must have a unique name - otherwise the later definition will just overwrite the former.
Here the third line overwrites the first line:
has_many :flag_instances, as: :flaggable, dependent: :destroy
has_many :flags, through: :flag_instances
has_many :flag_instances, as: :notifiable, dependent: :destroy
To reference the correct associations we would need to setup the user model as so:
class User < ApplicationRecord
has_many :flag_instances_as_flaggable,
as: :flaggable
class_name: 'FlagInstance'
has_many :flags_as_flaggable,
through: :flag_instances_as_flaggable,
source: :flag
has_many :flag_instances_as_notifiable,
as: :notifiable
class_name: 'FlagInstance'
has_many :flags_as_notifiable,
through: :flag_instances_as_notifiable,
source: :flag
end
In your case you might want to use concerns to keep it dry:
module Flaggable
extend ActiveSupport::Concern
included do
has_many :flag_instances_as_flaggable,
as: :flaggable,
class_name: 'FlagInstance'
has_many :flags_as_flaggable,
through: :flag_instances_as_flaggable,
source: :flag
end
end
module Notifiable
extend ActiveSupport::Concern
included do
has_many :flag_instances_as_notifiable,
as: :notifiable,
class_name: 'FlagInstance'
has_many :flags_as_notifiable,
through: :flag_instances_as_notifiable,
source: :flag
end
end
class User < ApplicationRecord
include Flaggable
include Notifiable
end

Rails has_many through with where clause

I need help to achieve something.
Is it possible that my Analysis has_many :klasses, through: :subjects, but filtered using attributes from the join table AnalysisSubject? Or my models should be different?
class Analysis
has_many :analysis_subjects, dependent: :destroy
has_many :subjects, through: :analysis_subjects
has_many :klasses, -> { where(year: ??????, semester: ??????), through: :subjects
end
class AnalysisSubject
belongs_to :analysis
belongs_to :subject
# There are year:integer and semester:integer attributes
# I want to use those attributes in my where clause for analysis.klasses
end
class Subject
has_many :klasses
has_many :analysis_subjects
has_many :analyses, through: :analysis_subjects
end
class Klass
belongs_to :subject
end
I'm using Rails 5 if it is important. Thank you for your help.
Yes, but you should afford it in a different way: You must declare the where in a has_many :filtered_analysis_subjects.
class Analysis
has_many :filtered_analisys_subjects, -> { where(year: x, semester: y }, foreign_key: "analysis_id", class_name: "AnalysisSubject", dependent: :destroy
has_many :subjects, through: :filtered_analysis_subjects
has_many :klasses, through: :subjects

Rails has_one association expected Model got String

Having the following associations between 3 models:
workout.rb
class Workout < ActiveRecord::Base
has_and_belongs_to_many :workout_sets, :join_table => :workout_sessions
belongs_to :warmup, :class_name => :WorkoutStep, :foreign_key => "workout_step_id"
accepts_nested_attributes_for :workout_sets, allow_destroy: true
accepts_nested_attributes_for :warmup, allow_destroy: true
end
workout_set.rb
class WorkoutSet < ActiveRecord::Base
has_and_belongs_to_many :workout_steps, :join_table => :sets_steps, dependent: :destroy
has_and_belongs_to_many :workouts, :join_table => :workout_sessions
accepts_nested_attributes_for :workout_steps, allow_destroy: true
has_one :intro_video_usage, class_name: 'VideoUsage::Intro', as: :parent, dependent: :destroy
has_one :intro_video, through: :intro_video_usage, source: :video
accepts_nested_attributes_for :intro_video
has_one :get_ready_video_usage, class_name: 'VideoUsage::GetReady', as: :parent, dependent: :destroy
has_one :get_ready_video, through: :get_ready_video_usage, source: :video
has_one :congrats_video_usage, class_name: 'VideoUsage::Congratulations', as: :parent, dependent: :destroy
has_one :congrats_video, through: :congrats_video_usage, source: :video
end
and
workout_step.rb
class WorkoutStep < ActiveRecord::Base
has_and_belongs_to_many :workout_sets, :join_table => :sets_steps
has_many :main_video_usage, class_name: 'VideoUsage::Main', as: :parent
has_many :main_videos, through: :main_video_usage, source: :video
accepts_nested_attributes_for :main_videos
end
And using simple_form and cocoon to handle nested models creation on the top level model (Workout) I'm having troubles building the form for sets and steps - more concise, when associating a workout_set with an intro_video (and whitelisting the params) I'm having the following error:
Video(#70285207226600) expected, got String(#70285080848240)
The params object after sending looks like this:
"workout"=>{"title"=>"",
"workout_sets_attributes"=>{"0"=>{"_destroy"=>"false",
"intro_video"=>"70",
"title"=>""}}},
"image"=>"",
"sound_logo"=>"",
"intro_video"=>"",
"commit"=>"Create workout"}
Thanks in advance.
Your parameters are passing a string ("70") to intro_video= but association accessors like that expect you to pass an actual instance of the associated class (in this case Video).
You should instead be assigning to intro_video_id. The accessor will convert the string to an integer for you.

Trying to 'alias' a polymorphic has_many relationship

I have a User model:
class User < ActiveRecord::Base
has_many :tracks, dependent: :destroy
has_many :tracked_locations, through: :tracks, source: :tracking, source_type: 'Location'
and a Track model (think of it as 'following'):
class Track < ActiveRecord::Base
belongs_to :user
belongs_to :tracking, polymorphic: true
end
The idea here is I will have many models to track / follow so I am using polymorphism. For example I have a Location model:
class Location < ActiveRecord::Base
has_many :tracks, :as => :tracking, :dependent => :destroy
has_many :users, through: :tracks
Now in the console Location.first.users works fine along with User.first.tracked_locations.
Now I will be adding another polymorphic relationship along the lines of Flagged. The user can 'flag' another model with a note etc. So if I add has_many :users, through: :flagged to the Location model for example I need to differentiate between tracking users and flagged users.
I tried:
has_many :tracking_users, through: :tracks, source: :tracking, source_type: 'User'
but I get:
NoMethodError: undefined method `evaluators_for' for #<Location:0x007ff29e5409c8>
Can I even do this or am I missing something simple here?
UPDATE
Based on the answer below I figured it out:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
I'm not 100% on this, but you could try:
has_many :tracking_users, through: :tracks, class_name: "User", foreign_key: "user_id", source: :user
Or you could also just create a class method and do it by hand.
def self.tracking_users
user_ids = tracks.collect(&:user_id)
User.where(id: user_ids)
end
edit: Had a brainfart, changed the "source" up there to :user. That tells what table to actually do the lookup in with the other attribute you've provided. of course it wouldn't be in :tracks

Resources