Reputation: 899
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
Reputation: 680
The cleanest and most maintainable way to do this would be to use the ActiveRecordSerializers
gem.
app/serializers/newsfeeds_serializer.rb
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
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
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