oneWorkingHeadphone
oneWorkingHeadphone

Reputation: 899

Combine 2 arrays of hashes into one JSON response

I'm working on a site that has a kind of newsfeed function. I want to pull data from a table with items and data from a table with users then combine parts of that data into a JSON response. Both are arrays of hashes. I don't though want to take all of those fields, only parts.

I was thinking I would pull both full records then do the combining in a method so as to only hit the DB twice. I can easily pull the last 5 items with:

class NewsfeedsController < ApplicationController
  before_action :authenticate_user!

  def index
    @newsfeed_data = json_response(return_items(params[:limit]))
  end

  private

  def return_items(limit)
    output = Item.last(limit=limit).reverse
  end
end

Now I'm stuck. I don't know how to remove the unwanted items from output or how to combine it with the user data. Item belongs to User in the model so there's a user_id field linking them.

Here are the schemas for Items and Users

create_table "items", force: :cascade do |t|
  t.string   "name"
  t.text     "description"
  t.string   "type"
  t.json     "properties"
  t.integer  "user_id"
  t.datetime "created_at",                   null: false
  t.datetime "updated_at",                   null: false
  t.index ["user_id"], name: "index_items_on_user_id", using: :btree
end

create_table "users", force: :cascade do |t|
  [lots of Devise settings...]
  t.string   "first_name"
  t.string   "last_name"
  t.string   "company"
  t.string   "position"
  t.string   "email"
  t.json     "tokens"
  t.datetime "created_at",                               null: false
  t.datetime "updated_at",                               null: false
  t.string   "avatar"
  [indexes...]
end

Here would be my desired output:

{
  "name": "Item name",
  "type": "Item type",
  "first_name": "User owner's first name",
  "company": "User's company",
  "avatar": "URL string to saved image"
}

Upvotes: 0

Views: 189

Answers (2)

George
George

Reputation: 680

The cleanest and most maintainable way to do this would be to use the ActiveRecordSerializers gem.

  1. Install the gem
  2. Create a file at app/serializers/newsfeeds_serializer.rb
  3. In that file, define a serializer that looks like this:

    class NewsfeedsSerializer < ActiveModel::Serializer
      attributes :name, :type, :first_name, :company, :avatar
    
      def first_name
        object.user.first_name
      end
    
      def company
        object.user.company
      end
    
      def avatar
        object.user.avatar
      end
    end
    
  4. In your controller, write your index route as follows:

    class NewsfeedsController < ApplicationController
      before_action :authenticate_user!
    
      def index
        @items = Item
          .order(created_at: :desc)
          .limit(params[:limit])
          .includes(:user)
        render json: @items, each_serializer: NewsfeedsSerializer
      end
    end
    

This will serialize each Item object using the methods we defined in the serializer. Using :includes will preload the User items that each Item belongs to, so everything we'll use gets grabbed in one database call.

If you ever want to edit to the JSON you're sending, it's easy to make changes to the serializer. I suggest reading through the ActiveModelSerializer guides they provide if you run into any problems getting set up or you want more custom config (camelCased keys or something like that).

Upvotes: 2

m3characters
m3characters

Reputation: 2290

You could do something like this but probably rails has something already built for extracting JSON responses.

def return_items(limit=10)
    items = Item.includes(:user).select('user_id, name, type, etc_you_want_to_pick').first(limit)
    response = []
    items.each do |item|
      response << {
        "name": item.name,
        "type": item.type,
        "first_name": item.user.first_name,
        "company": item.user.company,
        "avatar": item.user.avatar
      }
    end
    return response
  end

Probably if you are fine with having the whole user in the hash you could use the same AR query and then just call .as_json on the collection.

Upvotes: 1

Related Questions