Rails API Mode JBuilder Pagination - ruby-on-rails

I am currently trying to work on a large data set with over 9000 records coming in and would like to paginate it. I have never worked with pagination I know the gems Kaminari and Pagy can do this. I believe I need to wrap my find with either Kaminari supplied methods or Pagy but I am not sure what I am supposed to do with my Jbuilder views after that. Below is a snippet of my find method and a builder for it. The attendees hash has roughly 9000 records causing the load to either time out or take about 5 minutes. I am hoping pagination should smooth this out.
Controller
class ConferencesController < ::ApplicationController
def attendees
#conference = Conference.find(attendee_params[:conference_id])
end
private
def attendee_params
params.permit(:conference_id)
end
end
end
View
json.full_name attendee.full_name
json.email attendee.email
json.requires_certification attendee.requires_certification
Output
{
"conference": {
"title": "Avengers Assemble",
"description": null,
"starts_at": "2021-04-21T16:00:00.0000+0000",
"ends_at": "2021-04-21T16:45:00.0000+0000",
"attendees_count": 9002
},
"attendees": [
{
"full_name": "Steve Rogers",
"email": "blank#blank.com",
"requires_certification": true
},
{
"full_name": "Bruce Banner",
"email": "denise.parisian#kirlin.biz",
"requires_certification": false
},
{
"full_name": "THor Odinson",
"email": "blank#blank.com",
"requires_certification": false
}

Related

Rails Active Storage REST API

I've been able to set up Active Storage file uploads and now I'm trying to return associated images when I do, for instance, Speaker.all or Speaker.find(2).
Calling the associated endpoint I get something like:
{
"id": 2,
"name": "Rafael",
"email": "rafael.almeida#mail-provider.com",
"company": "XING",
"social_media": "{\"twitter\": \"#rafaelcpalmeida\"}",
"created_at": "2018-10-01T17:21:50.993Z",
"updated_at": "2018-10-01T17:21:51.144Z"
}
How can I also return its associated avatar?
I figured out what to do in order to achieve the result I wanted. First, we need to add the active_model_serializers to the Gemfile, followed by bundle install.
After we installed the gem we should add include ActionController::Serialization to every controller that's going to use the Serializer.
We generate a new serializer using rails g serializer speaker. My SpeakerSerializer looks like:
class SpeakerSerializer < ActiveModel::Serializer
attributes :id, :name, :email, :company, :avatar
def avatar
rails_blob_path(object.avatar, only_path: true) if object.avatar.attached?
end
end
And my output looks like
{
"speaker": {
"id": 2,
"name": "Rafael",
"email": "rafael.almeida#xing.com",
"company": "XING",
"avatar": "/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBCdz09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--515a0de8817b3529b5d3d168871cebf6ccee0463/xing-photo.jpg"
}
}
Check if this approach is good for your needs. This is the case of has_one_attached.
First, fetch the record:
speaker = Speaker.find(2)
Then convert it to a Ruby hash (please note .as_json):
speaker_hash = speaker.as_json
Now, just append the pair key-value you need, using url_for helper:
speaker_hash['url'] = url_for(speaker.avatar)
Finally, convert the hash to json:
speaker_hash.to_json

Using Ransack in a Rails 5 API only application

I recently started working on a Rails 5 API only application and I included jsonapi-resources as well as ransack to easily filter the results for any given request. By default, jsonapi-resources provides basic CRUD functionality, but in order to insert the search parameters for ransack I need to overwrite the default index method in my controller:
class CarsController < JSONAPI::ResourceController
def index
#cars = Car.ransack(params[:q]).result
render json: #cars
end
end
Now, this works fine, but it no longer uses jsonapi-resources to generate the JSON output, which means the output changed from:
# ORIGINAL OUTPUT STRUCTURE
{"data": [
{
"id": "3881",
"type": "cars",
"links": {
"self": ...
},
"attributes": {
...
}
}]
}
To a default Rails JSON output:
[{
"id": "3881",
"attr_1": "some value",
"attr_2": "some other value"
}]
How can I keep the original output structure while patching the index method in this controller?
Try to use the gem https://github.com/tiagopog/jsonapi-utils. In your case the problem will be solved in this way:
class CarsController < JSONAPI::ResourceController
include JSONAPI::Utils
def index
#cars = Car.ransack(params[:q]).result
jsonapi_render json: #cars
end
end

Rails API Design: best way to include other attributes with json_api relationships

I have a Rails 5 app in which I use the gem active_model_serializers(https://github.com/rails-api/active_model_serializers). In my app I have a simplified data model that looks something like this:
# LocalizedString.rb
has_many :translations
# Translation.rb
belongs_to :localized_string
I'm trying to follow the best practices from JSON API, I have configured active_model_serializers like this:
ActiveModelSerializers.config.adapter = :json_api
When a user of the API requests translations (http://[root]/api/apps/117/translations) I currently get the following result:
{
"data": [
{
"id": "152",
"type": "translations",
"attributes": {
"value": "Test",
},
"relationships": {
"language": {
"data": {
"id": "1",
"type": "languages"
}
},
"localized-string": {
"data": {
"id": "162",
"type": "localized-strings"
}
}
}
},
[...]
From my localised-string I also want to include another attribute that is critical for the consumer of the API, and I don't want to have to make another API call to get the value of the attribute. I wonder what is the best / recommended way to do this that also follows json_api if possible.
Something like this could work:
"localized-string": {
"data": {
"id": "162",
"key": "my key value", # the attribute I need.
"type": "localized-strings"
}
}
But I'm not sure how to achieve that using active_model_serializers or if it is another recommended way of doing what I want with [json_api][1].
For completion, my relevant serialiser files looks lik this:
class TranslationSerializer < ActiveModel::Serializer
attributes :id, :value, :created_at, :updated_at
has_one :language
has_one :localized_string, serializer: LocalizedStringParentSerializer
end
class LocalizedStringParentSerializer < ActiveModel::Serializer
# I want to include the key attribute in my relationship object, but this doesn't work.
attributes :id, :key
end
So, any ideas on what I need to do to achieve what I want?
Per spec, relationships are represented by resource object identifiers. To include more than just the id and type, you'll want to use the include param. In AMS, I think that would be 'include: [:localizations], fields: { localizations: [:key]}' (not at computer now, but is approx right)

Rabl conforming to jsonapi.org

I'm having a lot of trouble getting rabl to conform the the jsonapi.org spec. I have seen the wiki entry on github regarding this (https://github.com/nesquena/rabl/wiki/Conforming-to-jsonapi.org-format) but I dont understand how to get multiple root level nodes in rabl and still have collections work. The wiki entry on collections says this should work
collection [#post] => :posts
attributes :id, :title
child #post => :links do
node(:author) { #post.author_id }
node(:comments) { #post.comments_ids }
end
which for single root documents it does, but as soon as I try to add a meta or links to the root of the document as declared in the jsonapi.org spec, those nodes are appended to the existing collection node. Here is my rabl_init.rb
require 'rabl'
Rabl.configure do |config|
config.cache_sources = Rails.env != 'development'
config.include_child_root = false
config.include_json_root = false
end
I would like json that looks like this:
{
"links": {
"posts.comments": "http://example.com/posts/{posts.id}/comments"
},
"posts": [{
"id": "1",
"title": "Rails is Omakase"
}, {
"id": "2",
"title": "The Parley Letter"
}]
}
is Rabl capable of doing this?
Try something like this:
object false
node(:links) do
{"posts.comments" => "http://example.com/posts/{posts.id}/comments"}
end
child(#posts) do
attributes :id, :title
end

Rails 3 How can I allow nested attributes to be passed without the _attributes designation

When using accepts_nested_attributes_for, instead of having to pass "child_attributes", I'd like to pass "child". I'm pretty sure if I put a lot of the logic in my controller to create the the records and children, I could accomplish this. However, in an effort to keep my controllers clean and logic where it should be, the model in this case, I'd like to know how to switch rails 3 around to use this syntax when doing a POST or PUT.
{
"name": "test",
"child_attributes": [
{
"id": 1,
"name": "test_child_update"
},
{
"name": "test_child_create"
}
}
Rather
{
"name": "test",
"child": [
{
"id": 1,
"name": "test_child_update"
},
{
"name": "test_child_create"
}
}
Evidently, this can't be done.
The _attributes suffix adds no value to JSON requests and responses, but to get rid of it in the model layer, you would have to monkey patch ActiveRecord. Everyone hates monkey-patching ActiveRecord relations.
How about doing it in the controller layer?
#comment = Comment.new(attributify(:comment))
# snip
# ApplicationController
def attributify()
# Now I'll go and write this!
end
Edit: Done. The controller mixin is here: https://gist.github.com/johncant/6056036

Resources