How to group Associations' attributes in a Collection (Active Model Serializer) - ruby-on-rails-5

Consider the below two entities: Posts, Authors
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, author_id
belongs_to :author
end
Class AuthorSerializer < ActiveModel::Serializer
attributes :id, :name
end
Using the 'JSON' adapter, for the index action of PostsController we get the response as below:
{
"posts":[
{
"id":1,
"title":"Hic consectetur et delectus",
"author_id": 1,
"author":{"id":1,"name":"Author-A"}
},
{
"id":2,
"title":"Hic consectetur et delectus",
"author_id": 2,
"author":{"id":2,"name":"Author-B"}
},
{
"id":3,
"title":"Hic consectetur et delectus",
"author_id": 1,
"author":{"id":1,"name":"Author-A"}
}
]
}
Is it possible to group the Authors data outside the Posts data for sideloading? (as shown below)
{
posts:[{...}, {...}, {...}],
authors:[
{id:1, name:'Author-A'},
{id:2, name:'Author-B'}
]
}

Seems like you want to mimic the jsonapi included relationships. So, w json adapter, post has an attribute, to keep things simple, author_id, and then, you render the post collection separately from authors and generate the response, or you renders them mixed and reduce them. You may be able to just render as [posts, authors]!

As a workaround, we created a custom adapter extending the Json adapter and modifying the hash to our desired format in the serializable_hash(...) method
class CustomAdapter < ActiveModelSerializers::Adapter::Json
def serializable_hash(options = nil)
result_hash = super(options)
... code to modify hash format ...
result_hash
end
end

Related

How do I prevent an ActiveResource model from making a request?

I have some ActiveResource models like so:
class Meeting
has_many :sections
end
class Section
has_many :items
end
class Items
has_many :items
end
When I do a find on a Meeting, I get a json response like:
{
id: 1, ...
sections: [
{
id: 1, ...
items: [
{id: 16, ...
items: [
{id: 534, ...
items: [
{id: 8976, ...},
{id: 8977, ...}
]}
]}
]
}
}
And run the following (heavily edited) routine:
#meeting = Meeting.find(#meeting_guid)
import_sections
private
def import_sections
#meeting.sections.each do |section|
new_section = Item.find_or_initialize_by(foreign_guid: section.guid)
new_section.update!(layout: :section)
import_items(section, new_section)
end
end
def import_items(item, parent)
item.items.each do |item|
position = mema_item.orderpreference.to_i + 1
sub_item = Item.find_or_initialize_by(foreign_guid: item.guid)
sub_item.update!(layout: :item, title: item.name, parent: parent)
import_items(item, sub_item)
end
end
When it hits Items 8976 and 8977, it makes requests to /items?item_id=8976 and /items?item_id=8977. Is there a way to prevent it from making these requests when it hits the end of the last child? I don't think I'll ever call Items directly, so can I disable requests on the Item model or something?
I feel silly for not catching this, but I was able to solve the problem quite simply, albeit not through an ActiveResource feature. On the recursive import_items call, I check for if item.attributes.key? 'items', essentially just checking the attributes on the parent object without going into the relationship and creating an api call.

Rails - Serialize related data

I've got two models:
class Continent < ActiveRecord::Base
has_many :countries
end
class Country < ActiveRecord::Base
belongs_to :continent
end
I created controller like:
class ContinentsController < ApplicationController
def index
#continents = Continent.all
render json: #continents
end
end
and serializer:
class ContitnentSerializer < ActiveModel::Serializer
attributes :name, :countries
end
Here my issue begins. I'd like to serialize only countries with given condition where value comes from HTTP GET params. E.g country inside serializer should be displayed only if population is more than params[:population]. The problem is inside serializer we don't have access to params to examine that.
[
{
name: 'Europe'
countries: [
{
name: 'Italy',
population: 1000000
}
]
},
{
name: 'Africa'
countries: [
]
}
]
I've tried to join table with condition but it seems be not working.
#continents = Continent.all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > #{params[:population]}")
Create a scope and call the scope with param value from controller:
scope :population_more_than, ->(population) {all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > ?", population)}
Now call it from controller instead of Continent.all
Continent.population_more_than(params[:population])
You can try
#continents = Continent.all
#continents.num_population = params[:population]
render json: #continents.to_json(methods: :countries_with_population_gt)
in your Continent model
attr_accessor :num_population
def countries_with_population_gt(num_population=0)
countries.where('population > ?', #num_population)
end
Basically, you need to select only Continents that fall under specific rule. If this is a frequently used filter, then I would go with the Babar's suggestion and create a scope.
If this is a one time selection, then I prefer simply do filtering right there without cluttering up my models with non-frequently used scopes.
Continent.joins(:countries).where("countries.population > :population", population: params[:population])
# Or event shorter
Continent.joins(:countries).where("countries.population > :population", params)

ActiveRecord - Group by attribute of included table

I have the following two models
post_comment.rb
class PostComment < ActiveRecord::Base
has_many :replies, dependent: :destroy
...
end
reply.rb
class Reply < ActiveRecord::Base
belongs_to :post_comments
belongs_to :reply
...
end
I have the following query that gets all the post_comments and includes their respective replies:
PostComment.all.includes(:replies)
I want to, however, also group the replies of by the reply_id attribute of the reply model
Ideally I'd like to end up with something like the following straight out of the DB:
[
<Post 1:
replies: { nil: [...], 1: [...], 2: [...], ... }
...
>,
<Post 2:
replies: { nil: [...], 123: [...], 341: [...], ... }
...
>,
]
Thanks!
I don't think ActiveRecord can do that
We can group relies like this,but maybe it isn't the way you want
posts = PostComment.all.includes(:replies)
for post in posts
...
replies_map = post.replies.group_by(&:reply_id)
...
end
you can add a virtual column to model
To group the replies by the reply_id attribute of the reply model, you can do this:
PostComment.joins(:replies).group('replies.reply_id')

How to save multiple translation from 1 object without to reset I18n.locale with Globalize?

I'm trying to set my controller to save multiple languages with an object like this:
{ text: { fr: "francais", en: "English" } }
In your controller ex: (models_controller.rb)
def create
model = Model.new model_save_param
model.save
end
def model_save_params
translations_attributes: params[:model][:text].map { |locale, translation| {locale: locale, text: translation} }
end
In your model ex: (model.rb)
translates :text
accepts_nested_attributes_for :translations
Or, there is a Gem Globalize Accessor that can do the job

Netzke Grid filtering

I have an issue related to filtering data in Netzke Grid.
column :user_id do |c|
c.editor = {xtype: :combobox, editable: false, min_chars: 2}
end
It is mentioned in the doc that,
A hash that will override the automatic editor configuration. For example, for one-to-many association column you may set it to {min_chars: 1}, which will be passed to the combobox and make it query its remote data after entering 1 character (instead of default 4).
Seems {min_chars: 1} is not working as expected.
Please see example below for simple Customers grid and let me know if it works for you. Netzke way is to use __ (double underscore) to define one-to-many associations. This gives you combobox and all necessary data bindings. I tried different ways to make min_chars property work, but it all failed. Could be a bug. In the end, the only thing that worked is to do it from init_component method.
class Customers < Netzke::Basepack::Grid
def configure(c)
super
c.model = 'Customer'
c.columns = [
{ name: :name, header: 'Customer Name' },
{ id: :country__name, name: :country__name, header: 'Country' }
]
end
js_configure do |c|
c.init_component = <<-JS
function() {
this.callParent();
Ext.ComponentManager.get('country__name').editor.minChars = 2;
}
JS
end
end