Ruby: Outputting Sequel model associations - ruby-on-rails

I don't think this is possible using just Sequel models, but what I would like to do is have my parent model (Author) output its child model (Book) when I do something like Author.to_json. Here is my code:
require 'sequel'
require 'json'
db = Sequel.connect('postgres://localhost/testing');
class Sequel::Model
self.plugin :json_serializer
end
class Author < Sequel::Model(:author)
one_to_many :book, key: :author_id, primary_key: :id
def get_author
Author.each do |e|
books = Array.new
e.book.each do |f|
books.push(f.values)
end
e.values[:books] = books
puts JSON.pretty_generate(e.values)
end
end
end
class Book < Sequel::Model(:book)
end
author = Author.new
author.get_author
My output looks something like this:
[{
"id": 1,
"name": "Jack Johnson",
"books": [{
"id": 4,
"name": "Songs with Chords",
"genre": "Learning",
"author_id": 1
}]
}, {
"id": 2,
"name": "Mulder",
"books": [{
"id": 2,
"name": "UFOs",
"genre": "Mystery",
"author_id": 2
}, {
"id": 3,
"name": "Unexplained Paranorma",
"genre": "Suspense",
"author_id": 2
}]
}, {
"id": 3,
"name": "Michael Crichton",
"books": [{
"id": 1,
"name": "Jurassic Park",
"genre": "Incredible",
"author_id": 3
}]
}]
That's exactly how I want my output to look, but the way I'm going about it is questionable. Ideally, if there's some function already on the Author model that allows me to do this, that'd be awesome... as I don't want to have to implement a get_model function for all of my models that have different associations. Also, I was hoping NestedAttributes could lend a hand here, but it doesn't look like it.
I'm very new to Ruby and Sequel, so I'd like to know if there's a simpler way of doing this?

Sequel's json_serializer plugin already has support for associations via the :include option. Also, you need to fix your association. Something like this should work:
Author.one_to_many :books
Author.order(:id).to_json(:include=>:books)

Related

Get associated record for each object in ActiveRecord array

I have a collection of Posts, where an Organization has many posts. A post belongs to an Account. The models look like the following:
# Post class
class Post < ApplicationRecord
belongs_to :account, foreign_key: 'account_id'
belongs_to :postable, polymorphic: true
validates :content, length: { in: 1..500 }
end
# Organization
class Organization < ApplicationRecord
has_many :posts, as: :postable
end
I can get the posts of an organization (ie in the console, I can do Organization.first.posts, which returns a shape like:
[
{
"id": 101,
"accountId": 50,
"postableType": "Organization",
"postableId": 3,
"content": "Consequatur in illum vel voluptates.",
"createdAt": "2022-11-23T04:57:45.271Z",
"updatedAt": "2022-11-23T04:57:45.271Z"
},
{
"id": 102,
"accountId": 46,
"postableType": "Organization",
"postableId": 3,
"content": "Fugit totam minus et consequatur.",
"createdAt": "2022-11-23T04:57:45.274Z",
"updatedAt": "2022-11-23T04:57:45.274Z"
},
]
With that query, what would be the best way of including the Account object with every Post returned? I tried doing something like: Organization.first.posts.includes(:account) and Organization.first.posts.joins(:account) but that just returned the same thing. Would it be by simply mapping over and doing a separate query to find the account, and merging that Account object with the Post?
For example, I'd ideally like to return something like:
[
{
"id": 101,
"account": {
"id": 46,
"firstName": "Bob"
},
"postableType": "Organization",
"postableId": 3,
"content": "Consequatur in illum vel voluptates.",
"createdAt": "2022-11-23T04:57:45.271Z",
"updatedAt": "2022-11-23T04:57:45.271Z"
},
{
"id": 102,
"account": {
"id": 46,
"firstName": "Bob"
},
"postableType": "Organization",
"postableId": 3,
"content": "Fugit totam minus et consequatur.",
"createdAt": "2022-11-23T04:57:45.274Z",
"updatedAt": "2022-11-23T04:57:45.274Z"
},
]
I was able to do this by doing:
Organization.first.posts.as_json(include: :account)
I'm not sure if there is a better ActiveRecord-y way of doing this, but it worked for my use-case above.

Rails Serialization - fast_jsonapi / active_model_serializers

I am a bit lost getting on board fast_jsonapi / active_model_serializers to build an API. I have the basics down but seem stuck on a custom solution.
I have this as a serializer:
class AreaSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name, :cost_center, :notes
has_many :children
end
In my area model I have:
has_many :children, -> { Area.where(ancestry: id) }
My controller looks like:
class Api::V1::AreasController < ApiController
def index
render json: AreaSerializer.new(Area.root).serialized_json
end
end
Areas are nested in a hierarchy with the ancestry gem. The output is:
{
"data": [{
"id": "1",
"type": "area",
"attributes": {
"id": 1,
"name": "Calgary",
"cost_center": "123456",
"notes": ""
},
"relationships": {
"children": {
"data": [{
"id": "3",
"type": "child"
}]
}
}
}, {
"id": "2",
"type": "area",
"attributes": {
"id": 2,
"name": "Edmonton",
"cost_center": "78946",
"notes": ""
},
"relationships": {
"children": {
"data": []
}
}
}]
}
I am looking for an out put like this:
{
"data": [{
"id": "1",
"type": "area",
"attributes": {
"id": 1,
"name": "Calgary",
"cost_center": "123456",
"notes": ""
},
"relationships": {
"areas": {
"data": [{
"id": "3",
"type": "area",
"attributes": {
"id": 3,
"name": "Child Area",
"cost_center": "123456",
"notes": ""
}
}]
}
}
}, {
"id": "2",
"type": "area",
"attributes": {
"id": 2,
"name": "Edmonton",
"cost_center": "78946",
"notes": ""
}
}]
}
The idea being where the nested relationship shows the details etc.
I just started using fast_jsonapi in my rails project.
fast_jsonapi adheres to JSON:API spec which you can see here.
So, you will not be able to use the relationship helper functions (:has_many, :belongs_to) to achieve the output you want, which is nesting the attributes of area inside the relationships key.
"relationships": {
"areas": {
"data": [{
"id": "3",
"type": "area",
"attributes": { // nesting attributes of area
"id": 3,
"name": "Child Area",
"cost_center": "123456",
"notes": ""
}
}]
}
}
JSON:API specifies that:
To reduce the number of HTTP requests, servers MAY allow responses
that include related resources along with the requested primary
resources. Such responses are called “compound documents”.
So instead, you will have the attributes of area inside a key called included.
In order to get the included key in your response, you will need to provide an options hash in your controller to the serializer.
I don't quite understand your model Area relationships, but assume an Area has many SubArea.
class Api::V1::AreasController < ApiController
def index
// serializer options
options = {
include: [:sub_area],
collection: true
}
render json: AreaSerializer.new(Area.root, options).serialized_json
end
end
You Cannot use Association in fast_jsonapi . To Get a response in nested format . You need to add methods and need to create another serializer .
class AreaSerializer
include FastJsonapi::ObjectSerializer
set_type 'Area'
attributes :id, :name, :cost_center, :notes
attribute :childrens do |area, params|
ChildrenSerializer.new(area.childrens, {params:
params})
end
end
class ChildrenSerilizer
include FastJsonapi::ObjectSerializer
set_type 'Area'
attributes :id, :name ...
end
I started using the technique listed above but ended up forking and re-writing the jsonapi-serializer gem so it allows nesting (up to 4 levels deep) and does away with the concept of having relationships and attributes keys. I was also frustrated that it only output ID and TYPE keys of which TYPE is redundant most of the time since the object typically stays the same class in an array as an example and people seldom use real polymorphism (although it still supports this and outputs ID/type for polymorphic relationships).
Probably the best part of my re-write is that it allows deterministic field select-ability anywhere within the json key tree via a new fields input.
https://github.com/rubesMN/jsonapi-serializer

Nested resources as_json, sorted, with specific columns

I have 3 resources: pages, sections, and fields each with name and order columns.
My goal is to get a JSON representation of all of these resources, ordered by their order columns.
Example output:
[
{"name": "Page 1", "order": 0, "sections": [
{"name": "Section 1", "order": 0, "fields": [
{"name": "Field 1", "order": 0},
{"name": "Field 2", "order": 1}
]},
{"name": "Section 2", "order": 0, "fields": [
{"name": "Field 1", "order": 0}
]}
]},
{"name": "Page 2", "order": 1, "sections": [
{"name": "Section 1", "order": 0, "fields": [
{"name": "Field 1", "order": 0}
]}
]}
]
Note that the above is actual JSON (i.e. after running to_json).
For the life of me, I can't figure out a way to do this with ActiveModel.
My most successful attempt is something like this:
Page.joins(sections: :fields)
.as_json(only: [:name, :order], include: {
sections: {only: [:name, :order], include: {
fields: {only: [:prototype_id, :order]}
}}
})
This allows me to select the name and order, but then I have to recursively sort every array.
If I try to let ActiveModel do the sorting, I have to select the specific columns in SQL, which requires me to join, which requires a distinct, which makes the JSON nesting very tricky...
Can anyone point out what I'm doing wrong here?
Thanks for your time.
There are 2 solutions to implement the Order on AR:
Solution #1:
You can order the model and its' associations using .order as below:
Page.joins(sections: :fields).order("pages.order, sections.order, fields.order")
Reference:
https://apidock.com/rails/ActiveRecord/QueryMethods/order
Solution #2:
Add sorting to default scope as below:
class Page
default_scope { order(order: :asc) }
end
class Section
default_scope { order(order: :asc) }
end
class Field
default_scope { order(order: :asc) }
end
Reference:
https://apidock.com/rails/ActiveRecord/Base/default_scope/class

Get associations when doing Get request

I am working on ruby on rails as my API with mongoid. Suppose I have 2 models:
class Human
field: salary, type: Integer
has_many: dogs
end
class Dog
field: name, type: String
belongs_to: human
end
I want to get all dogs that human has when I query all humans, how do I do this?
I know embedded documents can do this, but then I cannot find dog document easily. That is why I use association rather than embedded document.
Expected output when I query all human:
[
{
"_id": "1",
"salary": 5000,
"dogs": [
{
"_id": "1",
"name": "dog1",
}
]
},
{
"_id": "2",
"salary": 8000,
"dogs": [
{
"_id": "2",
"name": "dog2",
},
{
"_id": "3",
"name": "dog3",
}
]
}
]
Thanks in advance. I am very new on this and sorry if I asked some stupid questions.
humans = Human.includes(:dog)
humans consists of the required data with each individual containing the dogs they own with details such as their name.

JSON API style sideloading in Rails ActiveModel::Serializers

I'm trying to build a JSON API style API using AM::Serializer. I'm running into an issue with sideloading.
I want to be able to build JSON that looks like:
{
"primaries": [{
"id": 123,
"data": "Hello world.",
"links": {
"secondaries": [ 1, 2, 3 ]
}
}],
"linked" : {
"secondaries": [
{
"id": 1,
"data": "test1"
},
{
"id": 2,
"data": "test2"
},
{
"id": 3,
"data": "test3"
}
]
}
}
The code I've been able to come up with looks like:
class PrimarySerializer < ActiveModel::Serializer
attributes :id, :data
has_many :secondaries, key: :secondaries, root: :secondaries
embed :ids, include: true
end
Which generates JSON that looks like:
{
"primaries": [{
"id": 123,
"data": "Hello world.",
"secondaries": [ 1, 2, 3 ]
}],
"secondaries": [
{
"id": 1,
"data": "test1"
},
{
"id": 2,
"data": "test2"
},
{
"id": 3,
"data": "test3"
}
]
}
Is there a way to override the location of the in-element secondaries and sideloaded secondaries such that they live in child nodes link and linked?
The above code is an abstraction of the actual code and may not work. Hopefully it illustrates the point sufficiently.
Thanks!
ActiveModel Serializers can do this. The problem is that the built-in association methods are to restrictive. Instead you must build up the links & linked parts manually.
(This answer refers to the stable 0.8.1 version of ActiveModel Serializers)
Here's a Gist with a complete JSON-API solution https://gist.github.com/mars/97a637560109b8ddfb27
Example:
class ExampleSerializer < JsonApiSerializer # see Gist for superclass
attributes :id, :name, :links
def links
{
things: object.things.map(&:id),
whatzits: object.whatzits.map(&:id)
}
end
def as_json(*args)
hash = super(*args)
hash[:linked] = {
things: ActiveModel::ArraySerializer.new(
object.things,
each_serializer: ThingsSerializer
).as_json,
whatzits: ActiveModel::ArraySerializer.new(
object.whatzits,
each_serializer: WhatzitsSerializer
).as_json
}
hash
end
end

Resources