Use alternate association id for ActiveModel::Serializer association - ruby-on-rails

I have an app where there I have normal ActiveRecord ids as well as a unique field (e.g. ident) that's unique on an external, canonical database. A model looks like:
class Parent
has_many :childs, foreign_key: :parent_ident, primary_key: :ident
end
class Child
belongs_to :parent, foreign_key: :parent_ident, primary_key: :ident
end
For various reasons I'd like the consumer of my Rails API to use the canonical ids (e.g. ident) not the ids defined on the app. So I've defined my serializers (using ActiveModel::Serializer):
class ParentSerializer < ActiveModel::Serializer
attributes :id, :ident, :other, :stuff
has_many :children
def id
object.ident
end
end
class ChildSerializer < ActiveModel::Serializer
attributes :id, ident, :parent_ident, :things
def id
object.ident
end
end
the problem is that the JSON generated correctly is using my overridden IDs for the top-level attributes but the IDs in the child_ids field are the local ids not the canonical idents I want to use.
{
parents: [
{
id: 1234, // overridden correctly in AM::S
ident: 1234,
other: 'other',
stuff: 'stuff',
child_ids: [ 1, 2, 3 ], // unfortunately using local ids
}
],
childs: [
{
id: 2345, // doesn't match child_ids array
ident: 2345,
parent_ident: 1234,
things: 'things'
}
]
}
Question: is there a way to make the parent serializer use the ident field of it's association rather than the default id field?
I have tried putting a def child_ids in the ParentSerializer without success.
I am using Rails 4.2 and the 9.3 version of active_model_serializers gem.

You can specify custom serializers for associations as per the docs
has_many :children, serializer: ChildSerializer

Related

Postgresql JSONB nested form ruby on rails

I have product as active record table and option_type as activemodel model. option types is an array of objects as follows,
[
{name: 'color', values: ['red', 'blue']},
{name: 'size', values: ['small', 'medium']}
]
class OptionType
include ActiveModel::Model
attr_accessor :name, :values, :default_value
def initialize(**attrs)
attrs.each do |attr, value|
send("#{attr}=", value)
end
end
def attributes
[:name, :values, :default_value].inject({}) do |hash, attr|
hash[attr] = send(attr)
hash
end
end
class ArraySerializer
class << self
def load(arr)
arr.map do |item|
OptionType.new(item)
end
end
def dump(arr)
arr.map(&:attributes)
end
end
end
end
I want to desing a form_for with nested form for option_types so that user can add various option names and it's values. How to do it?
reference links are as follow,
Validation of objects inside array of jsonb objects with RubyOnRails
I know this isn't the answer you're hoping for but instead of just tossing the whole lot into a JSONB column and hoping for the best you should model it as far as possible in a relational way:
class Product < ApplicationRecord
has_many :options
has_many :product_options, through: :options
end
# rails g model option name:string product:belongs_to
class Option < ApplicationRecord
belongs_to :product
has_many :product_options
end
# rails g model product_option option:belongs_to name:string ean:string
class ProductOption < ApplicationRecord
belongs_to :option
has_one :product, through: :options
end
If your data is actually structured enough that you can write code that references its attributes then a JSON column isn't the right answer. JSON/arrays aren't the right answer for setting up assocations either.
This lets you use foreign keys to maintain referential integrity and has a somewhat sane schema and queries instead of just dealing with a totally unstructed mess. If you then have to deal with an attribute that can have varying types like for example an option that can be string, boolean or numerical you can use a JSON column to store the values to somewhat mitigate the disadvantages of the old EAV pattern.
Creating variants of a product could then either be done via a seperate form, nested attributes or AJAX depending on your requirements.

Preload dynamically filtered has_many association with ActiveRecord

I'm trying to include / preload a has_many association that has a dynamic runtime condition. I'm getting the correct result, but my variable is hard coded and I'm not sure how to pass it from includes.
My query:
#volunteers = Volunteer.includes({ signups: [{position: [{ eventday: :event }] }] }, :event_survey_results).where(events: { id: #event.id })
Volunteer model:
class Volunteer < ApplicationRecord
# Hard coded solution works
has_many :event_survey_results, -> { where event_id: 60 }, class_name: 'Questionnaires::SurveyResult'
end
# Dynamic solution. How to pass event_id to this from :includes ?
has_many :event_survey_results, ->(event_id) { where event_id: event_id }, class_name: 'Questionnaires::SurveyResult'
end
I need to pass event_id to event_survey_results has_many dynamic solution in the :includes statement. How can I achieve this?
I'm using Rails 5.1
has_many doesn't have such feature as it can be done using a standard where clause as per Rails Team, check please :
There is no need to add this functionality. It's already there using standard where clauses
Reference:
https://github.com/rails/rails/issues/3912
You can do the following:
Edited query:
# #event.id refers to the value of 60
#volunteers = Volunteer.includes({ signups: [{position: [{ eventday: :event }] }] }, :event_survey_result).where(event_survey_results: { id: #event.id })
Volunteer model:
class Volunteer < ApplicationRecord
# No need to filter here, as you can filter it using standard where
has_many :event_survey_results , class_name: 'Questionnaires::SurveyResult'
end

Include properties for ActiveModelSerializer only if called within has many

I have a rails app with the following models.
class Project
has_many :project_clips
has_many :clips, through: :project_clips
end
class Clip
has_many :project_clips
has_many :projects, through: :project_clips.
end
class ProjectSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :clips
end
class ClipSerializer < ActiveModel::Serializer
attributes :id, :name
end
I was wondering if it's possible to display the values of the associated project_clip, if the clip has been called within the context of project.
Let's say the ProjectClip model, has a field called priority. I want the results to show up like this.
{ projects: { "id": 1, "name": "ipsum", "clips": [{ "id": 1, "name": "lorem", "priority": "high"}] } }
I don't want the values of project_clips to be included, just a few properties when returning the data for projects.
If I'm getting your question right, you can do something like:
res = project.clips.collect{ |clip| [clip, clip.project_clips] }
or if you want to return hashes and not objects, you can do:
res = project.clips.collect{ |clip| [clip.attributes, clip.project_clips.collect{|pc| pc.attributes}] }

Serialize a summary of a has_many relationship

How can I include a summary of the associated objects rather than the objects itself. For example, if a client has_many projects I could do this:
class ClientSerializer < ActiveModel::Serializer
attributes :id, :name
has_many :projects
end
But this will return all of the associated projects. I would much rather bring back just a count of the projects, the url to download the full list of projects, the last time a project was updated, etc.
What is the best way to include a summary of the associated objects?
Ideally, for example the resulting JSON would look like this:
{
"id": 10,
"name": "My Client",
"projects": {
"count": 5,
"updated_at": "2014-09-09T13:36:20.000-04:00",
"url": "https://my.baseurl.com/clients/10/projects"
}
I'm not sure if this is the best way to do it, but I got this to work:
class ClientSerializer < ActiveModel::Serializer
attributes :id, :name, :archive, :updated_at, :projects
def projects
collection = object.projects.to_a
{ count: collection.length,
updated_at: collection.map(&:updated_at).max,
url: projects_url }
end
end
You could create an instance method:
class ClientSerializer < ActiveModel::Serializer
has_many :projects
def project_count
projects.size
end
end

Rails Active Model Serializer - has_many and accessing the parent record

I'm trying to build a JSON representation of some Rails models using Active Model Serializer, where some models embed others. For example, I have Event and Attendees, Event has_and_belongs_to_many Attendees.
class EventSerializer < ActiveModel::Serializer
attributes :name
has_many :attendees, serializer: AttendeeSerializer
end
class AttendeeSerializer < ActiveModel::Serializer
attributes :name
end
This would result in JSON like { name: 'Event One', attendees: [{ name: 'Alice' }, { name: 'Bob' }] }.
Now, I'd like to add what the attendees have said about the event. Let's say, Comment belongs_to Event, belongs_to Attendee. I'd like to include said comments in the serialized output of event, so it would become { name: 'Event One', attendees: [{ name: 'Alice', comments: [{ text: 'Event One was great!'}] }, { name: 'Bob', comments: [] }] }.
I could have
class AttendeeSerializer < ActiveModel::Serializer
attributes :name
has_many :comments
end
but that would select all the comments by this attendee for all the events - not what I want. I'd like to write this, but how do I find the particular event for which I'm doing serialization? Can I somehow access the 'parent' object, or maybe pass options to a has_many serializer?
class AttendeeSerializer < ActiveModel::Serializer
attributes :name
has_many :comments
def comments
object.comments.where(event_id: the_event_in_this_context.id)
end
end
Is this something that can be done, or should I just build the JSON in another way for this particular use case?
I'd do things manually to get control:
class EventSerializer < ActiveModel::Serializer
attributes :name, :attendees
def attendees
object.attendees.map do |attendee|
AttendeeSerializer.new(attendee, scope: scope, root: false, event: object)
end
end
end
class AttendeeSerializer < ActiveModel::Serializer
attributes :name, :comments
def comments
object.comments.where(event_id: #options[:event].id).map do |comment|
CommentSerializer.new(comment, scope: scope, root: false)
end
end
end

Resources