current relation is below.
class Hoge < ApplicationRecord
belongs_to :hoge_category
class HogeCategory < ApplicationRecord
belongs_to :big_hoge_category
has_many :hoges
class BigHogeCategory < ApplicationRecord
has_many :hoge_categories
has_many :hoges, through: :hoge_categories
I want to extract Hoge's data which is related HogeCategory table and BigHogeCategory table.
Like this.
HogesController
hoges = Hoge.index( params[:keyword], nil, params[:hoge_category_id], params[:offset].to_i, 10 ).as_json(include: :hoge_category)
render status: 200, json: { hoges: hoges } #send API
Hoge.rb
class Hoge < ApplicationRecord
searchkick callbacks: :async, language: "japanese", word_middle: [:title, :message]
def self.index(keyword, connect_id, hoge_category_id, offset, limit) #execute search
where = {}
if keyword.present?
hoges = Hoge.search(keyword, fields: [:title, :message],misspellings: false, where: where, order: { created_at: :desc }, limit: limit, offset: offset).results
else
hoges = Hoge.search(fields: [:title, :message],misspellings: false, where: where, order: { created_at: :desc }, limit: limit, offset: offset).results
end
return hoges
end
def search_data
{
title: title,
message: message,
created_at: created_at,
hoge_category_id: hoge_category&.id
}
end
end
From my search, it's better to use as_json with include option.
But I don't know how to write when related tables are multiple.
How can I write that?
Or if you have a better idea, let me know please..
Related
I'm stuck here and couldn't find solution to proceed my work,
I have 3 models: plans, days, and meals.
This is my Plan Controller I've managed to get the correct answer in the controller, I want it nested and inside the serializer because I'm using URL helper to retrieve my images URLs, is there a possible way to use the #plan.id inside the DaySerializer?
def meals
#plan = Plan.find(params[:id])
#days = #plan.days
#meals = Meal.where("plan_id = ? ", #plan.id)
render :json => { :plan => #plan, :days => #days,
:meals => #meals }
end
This is my Plan model
class Plan < ApplicationRecord
has_many :days
has_one_attached :image, dependent: :destroy
end
This is my Day model
class Day < ApplicationRecord
has_many :meals
has_many :plans
end
This is my Meal model
class Meal < ApplicationRecord
belongs_to :plan
belongs_to :day
has_one_attached :image, dependent: :destroy
end
I want to show all meals for a specific Plan, to do that I need to use a variable inside the daySerializer but I couldn't find how to do it.
This is my planSerializer
class PlanSerializer < ActiveModel::Serializer
attributes :id, :name, :monthly_price, :plan_days
def plan_days
object.days.map do |day|
DaySerializer.new(day, scope: scope, root: false, event: object)
end
end
end
and this is my DaySerializer which I need to use the instance of the plan inside
class DaySerializer < ActiveModel::Serializer
attributes :number, :plan_meals
def plan_meals
#how to be able to use this line in Serilizer? !important
#plan = Plan.find(params[:id])
object.meals.map do |meal|
if meal.plan_id == #plan.id
MealSerializer.new(meal, scope: scope, root: false, event: object)
end
end
end
end
target reason response :
{
id: 8,
name: "Plan1",
monthly_price: 88,
plan_days: [
{
number: 5,
plan_meals: [],
},
{
number: 4,
plan_meals: [],
},
{
number: 3,
plan_meals: [],
},
{
number: 2,
plan_meals: [],
},
{
number: 1,
plan_meals: [
{
id: 11,
name: "test meal",
calories: 32,
protein: 32,
fat: 32,
carbohydrates: 32,
plan_id: 8,
},
],
},
],
}
currently it's showing all meals that belongs to each day,
not only the meals with the plan_id = Plan.find(params[:id])
In general I think you could use something like this should work.
ActiveModel::Serializer::CollectionSerializer.new. It actually by itself allows you to pass additional information to your serializer. It does the same as your current code just you are able to explicitly pass new data.
Controller:
def meals
#plan = Plan.find(params[:id])
#days = #plan.days
#meals = Meal.where("plan_id = ? ", #plan.id)
render :json => {
:plan => #plan,
:days => ActiveModel::Serializer::CollectionSerializer.new(#days, serializer: DaySerializer, plan_id: #plan.id),
:meals => #meals
}
end
And then in DaySerializer:
class DaySerializer < ActiveModel::Serializer
attributes :number, :plan_meals
def plan_meals
object.meals.map do |meal|
if meal.plan_id == instance_options[:plan_id]
MealSerializer.new(meal, scope: scope, root: false, event: object)
end
end
end
end
So in short ActiveModel::Serializer::CollectionSerializer.new in controller and instance_options in serializer to access passed additional parameters.
UPDATED:
How about add meal serializer?
class MealSerializer < ActiveModel::Serializer
attributes :id, :name, :calories, :protein, :fat, # etc
end
class DaySerializer < ActiveModel::Serializer
attributes :number
has_many :meals, serializer: MealSerializer
end
ORIGINAL:
class PlanSerializer < ActiveModel::Serializer
attributes :id, :name, :monthly_price, :plan_days
has_many :plan_days, serializer: DaySerializer
end
something like this.
I have a rails api with a number of models that are being serialized by the fast_jsonapi gem.
This is what my models look like:
class Shift < ApplicationRecord
belongs_to :team, optional: true
...
class Team < ApplicationRecord
has_many :shifts
...
This is what the serializer looks like
class ShiftSerializer
include FastJsonapi::ObjectSerializer
...
belongs_to :team
...
end
The serialization works. However, even though I am including the compound team document:
def index
shifts = policy_scope(Shift).includes(:team)
options = {}
options[:include] = [:team, :'team.name', :'team.color']
render json: ShiftSerializer.new(shifts, options)
end
I'm still getting the object formatted like so:
...
relationships: {
team: {
data: {
id: "22",
type: "Team"
}
}
}
Whereas I'm expecting to get also the attributes of my team model.
fast_jsonapi implements json api specification so respond includes "included" key, where serialized data for relationships placed.That's default behavior
If you use the options[:include] you should create a serializer for the included model, and customize what is included in the response there.
in your case if you use
ShiftSerializer.new(shifts, include: [:team]).serializable_hash
you should create a new serializer serializers/team_serializer.rb
class TeamSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :color
end
this way your response will be
{
data: [
{
id: 1,
type: "shift",
relationships: {
team: {
data: {
id: "22",
type: "Team"
}
}
}
}
],
included: [
id: 22,
type: "Team",
attributes: {
name: "example",
color: "red"
}
]
}
and you will find the custom data of your association in the response "included"
If you use like this then maybe solve your problem
class Shift < ApplicationRecord
belongs_to :team, optional:true
accepts_nested_attributes_for :team
end
In your ShiftSerializer.rb please write this code,
attribute :team do |object|
object.team.as_json
end
And you will get custom data that you want.
Reference: https://github.com/Netflix/fast_jsonapi/issues/160#issuecomment-379727174
I'm trying to implement elasticsearch with two models account.rb and item.rb. The account model contains the address info and the item model contains the searchable item.
I'm also using the gem geocoded in order to get the latitude and longitude coordinates when the address is inserted to the account.rb.
With the following setup I'm receiving this error:
Elasticsearch::Transport::Transport::Errors::BadRequest in ShowcaseController#index
[400] No handler found for uri [//items] and method [PUT]
on these two lines item.rb and showcase_controller.rb respectively:
Item.__elasticsearch__.client.indices.create \
#items = Item.all
item.rb
require 'elasticsearch/model'
class Item < ApplicationRecord
mount_uploader :image, ImageUploader
belongs_to :category
belongs_to :store
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
settings index: { number_of_shards: 1, number_of_replicas: 0 } do
mapping do
indexes :location, type: 'geo_point'
end
end
def location
[latitude, longitude]
end
def self.search()
__elasticsearch__.search(
{
query: {
match_all: {}
},
sort: [{'_geo_distance' => {
location: {
latitude: 0,
longitude: 0
}
}
}]
}
)
end
end
Item.__elasticsearch__.client.indices.delete index: Item.index_name rescue nil
Item.__elasticsearch__.client.indices.create \
index: Item.index_name,
body: { settings: Item.settings.to_hash, mappings: Item.mappings.to_hash }
Item.import
showcase_controller.rb
class ShowcaseController < ApplicationController
def index
#items = Item.all
end
private
def front_params
params.require(:front).permit(:title, :description, :price, :image, :location )
end
end
Any ideas on how to solve this error?
I have two models: Cabinet and Workplace.
class Cabinet < ActiveRecord::Base
def as_json(options={})
options.merge!({except: [:created_at, :updated_at]})
super(options)
end
end
class Workplace < ActiveRecord::Base
belongs_to :cabinet
def as_json(options = {})
options.merge!(:except => [:created_at, :updated_at, :cabinet_id], include: :cabinet)
super(options)
end
end
When I called Cabinet.first.to_json I get
{
id: 1,
cabinet: "100"
}
but when I called Workplace.first.to_json id get
{
name: "first workplace",
Cabinet: {
id: 1,
cabinet: "100",
created_at: "#created_at",
updated_at: "#updated_at"
}
}
Why this? Thanks and sorry for my english :)
Not sure if I am following you, but do you want to get just attributes from Workplace model, and not Cabinet data when you do Workplace.first.to_json?
I think it is because you include cabinet in as_json method configuration as explained here.
You should either remove it or do this:
Workplace.first.attributes.to_json
Let me know if I am missing something from your question.
Let's assume that your model Cabinet has :id, :cabinet, :created_at, :updated_at attributes and Workplace has :id, :name, :cabinet_id, .....
Now, if you try to fire Cabinet.first.to_json, ofcourse it will render the following:
{
id: 1,
cabinet: "100"
}
becuase that is the attributes belongs to Cabinet model. Then you also added these line of code options.merge!({except: [:created_at, :updated_at]}) that's why it only renders :id and :name attributes. And if you try to fire Workplace.first.to_json then it will render:
{
name: "first workplace",
Cabinet: {
id: 1,
cabinet: "100",
created_at: "#created_at",
updated_at: "#updated_at"
}
}
because, of these options.merge!(:except => [:created_at, :updated_at, :cabinet_id], include: :cabinet). You include the model Cabinet so it will automatically added to your json.
> u = User.first
> u.viewable_cars
OR
> Car.for(u)
would get me just the cars the user has permission to view but not the cars he owns! SQL in irb for both u.viewable_cars & Car.for(u), which is the same, cars with id 1 to 50 which belongs to user don't get called at all:
SELECT "cars".* FROM "cars" INNER JOIN "permissions" ON "permissions"."thing_id" = "cars"."id" AND "permissions"."thing_type" = $1 WHERE ((cars.user_id = 1) OR (permissions.action = 'view' AND permissions.user_id = 1)) ORDER BY created_at DESC [["thing_type", "Car"]]
=> #<ActiveRecord::Relation [#<Car id: 52, content: "sport edition", name: "BMW", user_id: 2, created_at: "2014-11-01 04:58:19", updated_at: "2014-11-01 04:58:19">, #<Car id: 51, content: "super sport car", name: "BMW M6", user_id: 3, created_at: "2014-11-01 04:44:31", updated_at: "2014-11-01 04:44:31">]>
class Car < ActiveRecord::Base
belongs_to :user
has_many :permissions, as: :thing
default_scope -> { order('created_at DESC') }
validates :user_id, presence: true
validates :name, presence: true, length: { maximum: 50 }, uniqueness: true
validates :content, length: { maximum: 300 }, allow_blank: true
scope :viewable_by, ->(user) do
joins(:permissions).where(permissions: { action: "view",
user_id: user.id })
end
scope :for, ->(user) do
joins(:permissions).
where("(cars.user_id = :user_id) OR (permissions.action = 'view' AND permissions.user_id = :user_id)", user_id: user.id)
end
end
class User < ActiveRecord::Base
...
has_many :cars, dependent: :destroy
has_many :permissions
has_many :viewable_cars, ->(user) { joins(:permissions).
where("(cars.user_id = :user_id) OR (permissions.action = 'view' AND permissions.user_id = :user_id)", user_id: user.id) },
class_name: "Car"
def viewable_cars
Car.for(self)
end
end
class Permission < ActiveRecord::Base
belongs_to :user
belongs_to :thing, polymorphic: true
end
Rails.application.routes.draw do
resources :users do
resources :cars
end
end
Your scope for in car.rb should be this:
scope :for, ->(user) do
joins("LEFT OUTER JOIN permissions ON permissions.thing_id = cars.id AND permissions.thing_type = 'Car'").where("(cars.user_id = :user_id) OR (permissions.action = 'view' AND permissions.user_id = :user_id)", user_id: user.id)
end
Now you can do: Car.for(current_user).find(params[:id]). However, this looks like an antipattern to me. So you can create another association in user.rb like this:
Rails 4:
has_many :viewable_cars, ->(user) {
joins("LEFT OUTER JOIN permissions ON permissions.thing_id = cars.id AND permissions.thing_type = 'Car'").
where("(cars.user_id = :user_id) OR (permissions.action = 'view' AND permissions.user_id = :user_id)", user_id: user.id) },
class_name: 'Car'
Rails 3(couldn't find a better way of doing it, e.g.: associations):
def viewable_cars
Car.for(self)
end
So that you can fetch all cars for user:
current_user.viewable_cars
In controller:
#car = current_user.viewable_cars.find(params[:id])