#ruby-on-rails
#ruby-on-rails
Вопрос:
есть ли какой-либо способ всегда извлекать родительские объекты с помощью дочернего или дочернего объекта для приложения только для rails API?
например, у меня есть массив @students. Каждый объект student в массиве @students имеет два внешних ключа: standard_id и school_id. Теперь все объекты по умолчанию имеют standard_id и school_id. Вместо этого я хочу, чтобы стандартный объект и объект school были в каждом объекте student в массиве @students.
Ответ, который я получаю
[
{
"id": 1,
"standard_id": 1,
"school_id": 1,
"created_at": "2019-04-14T11:36:03.000Z",
"updated_at": "2019-04-14T11:36:03.000Z"
},
{
"id": 2,
"standard_id": 1,
"school_id": 1,
"created_at": "2019-04-14T11:41:38.000Z",
"updated_at": "2019-04-14T11:41:45.000Z"
}
]
Ответ, который я хочу
[
{
"id": 1,
"standard_id": 1,
"school_id": 1,
"created_at": "2019-04-14T11:36:03.000Z",
"updated_at": "2019-04-14T11:36:03.000Z",
"standard": {
"id": 1,
"name": "1",
"created_at": "2019-04-14T11:32:15.000Z",
"updated_at": "2019-04-14T11:32:15.000Z"
},
"school": {
"id": 1,
"name": "SACS",
"created_at": "2019-04-14T11:35:24.000Z",
"updated_at": "2019-04-14T11:35:24.000Z"
}
},
{
"id": 2,
"standard_id": 1,
"school_id": 1,
"created_at": "2019-04-14T11:41:38.000Z",
"updated_at": "2019-04-14T11:41:45.000Z",
"standard": {
"id": 1,
"name": "1",
"created_at": "2019-04-14T11:32:15.000Z",
"updated_at": "2019-04-14T11:32:15.000Z"
},
"school": {
"id": 1,
"name": "SACS",
"created_at": "2019-04-14T11:35:24.000Z",
"updated_at": "2019-04-14T11:35:24.000Z"
}
}
]
Существует ли какое-либо общее решение для всех контроллеров? Поскольку приложение уже создано. Теперь очень сложно форматировать данные вручную в каждом контроллере. Заранее спасибо.
Комментарии:
1. используете ли вы какой-либо сериализатор? или ответ отправляется непосредственно от контроллера как
render json:
Ответ №1:
Если вы используете as_json
сериализатор Rails по умолчанию в вашем контроллере, т.Е. Смотрите ниже:
render json: @students
# ^ above will default call `.to_json` to `@students`, which will also call `.as_json`
# thereby, equivalently calling:
# render json: @students.as_json
… затем вы можете as_json
немного изменить (см. Документы), чтобы JSON включал вложенные ассоциации первого уровня; см. Ниже
Решение
app/models/student.rb
class Student < ApplicationRecord
belongs_to :standard
belongs_to :school
def as_json(**options)
unless options.has_key? :include
options.merge!(include: [:standard, :school])
end
super(options)
end
# or if you don't want to manually include "each" association, and just dynamically include per association
# def as_json(**options)
# unless options.has_key? :include
# options.merge!(
# include: self.class.reflect_on_all_associations.map(amp;:name)
# )
# end
# super(options)
# end
end
Глобальное решение (Rails > = 5)
то же, что и в решении выше, но если вы хотите, чтобы это работало для ВСЕХ моделей, а не только для Student
модели, тогда выполните следующие действия:
app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
def as_json(**options)
unless options.has_key? :include
options.merge!(
include: self.class.reflect_on_all_associations.map(amp;:name)
)
end
super(options)
end
end
Пример использования
# rails console
students = Student.all
puts students.as_json
# => [{"id"=>1, "standard_id"=>1, "school_id"=>1, "created_at"=>"2019-04-14T11:36:03.000Z", "updated_at"=>"2019-04-14T11:36:03.000Z", "standard"=>{"id"=>1, "name"=>"1", "created_at"=>"2019-04-14T11:32:15.000Z", "updated_at"=>"2019-04-14T11:32:15.000Z"}, "school"=>{"id"=>1, "name"=>"SACS", "created_at"=>"2019-04-14T11:35:24.000Z", "updated_at"=>"2019-04-14T11:35:24.000Z"}}, {"id"=>2, "standard_id"=>1, "school_id"=>1, "created_at"=>"2019-04-14T11:41:38.000Z", "updated_at"=>"2019-04-14T11:41:45.000Z", "standard"=>{"id"=>1, "name"=>"1", "created_at"=>"2019-04-14T11:32:15.000Z", "updated_at"=>"2019-04-14T11:32:15.000Z"}, "school"=>{"id"=>1, "name"=>"SACS", "created_at"=>"2019-04-14T11:35:24.000Z", "updated_at"=>"2019-04-14T11:35:24.000Z"}}]
Решения, приведенные выше, отображают ассоциации первого уровня только как часть ответа JSON, он не будет «глубоко» отображать ассоциации второго уровня, третьего уровня и т. Д., И т. Д.
Обновлено (с нумерацией страниц):
Мне стало любопытно узнать о вашем расширенном запросе на интеграцию разбивки на страницы; следовательно, мое возможное решение (проверено, работает, хотя пока не уверен, есть ли побочные эффекты) ниже:
Вам понадобится «разбиение на страницы», т. Е. В моем примере ниже я использую kaminari
app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def as_json(**options)
unless options.has_key? :include
options.merge!(
include: self.class.reflect_on_all_associations.map(amp;:name).inject({}) do |hash, name|
paginate = options.dig(:association_paginations, name.to_sym, :paginate)
paginate = true if paginate.nil?
page = options.dig(:association_paginations, name.to_sym, :page) || 1
per = options.dig(:association_paginations, name.to_sym, :per) || Kaminari.config.default_per_page
hash[name.to_sym] = {
paginate: paginate,
page: page,
per: per
}
hash
end
)
end
super(options)
end
end
app/config/инициализаторы/active_model_serialization_patch.rb
module ActiveModel::Serialization
private def serializable_add_includes(options = {})
if Gem.loaded_specs['activemodel'].version.to_s != '6.0.0.beta3' # '5.2.3'
raise "Version mismatch!
Not guaranteed to work properly without side effects!
You'll have to copy and paste (and modify) to below correct code (and Gem version!) from
https://github.com/rails/rails/blob/5-2-stable/activemodel/lib/active_model/serialization.rb#L178"
else
# copied code: start
return unless includes = options[:include]
unless includes.is_a?(Hash)
includes = Hash[Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }]
end
includes.each do |association, opts|
if opts[:paginate]
opts[:page] ||= 1
opts[:per] ||= Kaminari.config.default_per_page
records = send(association).page(opts[:page]).per(opts[:per])
else
records = send(association)
end
if records
yield association, records, opts
end
end
# copied code: end
end
end
end
app/config/initializers/active_record_relation_patch.rb
class ActiveRecord::Relation
def as_json(**options)
if options[:paginate]
options[:page] ||= 1
options[:per] ||= Kaminari.config.default_per_page
options[:paginate] = false
page(options[:page]).per(options[:per]).as_json(options)
else
super(options)
end
end
end
your_controller.rb
def some_action
@students = Student.all
render json: @students.as_json(
paginate: true,
page: params[:page],
per: params[:per],
association_paginations: params[:association_paginations]
)
end
Пример запроса 1
http://localhost:3000/your_controller/some_action?per=2amp;page=1
Пример ответа 1
вернулись только два ученика, потому per
что = 2
[
{
id: 1,
school_id: 101,
school: { id: 101, ... },
standard_id: 201,
standard: { id: 201, ... },
subjects: [
{ id: 301, ... },
{ id: 302, ... },
{ id: 303, ... },
{ id: 304, ... },
{ id: 305, ... },
...
],
attendances: [
{ id: 123, ... },
{ id: 124, ... },
{ id: 125, ... },
{ id: 126, ... },
{ id: 127, ... },
{ id: 128, ... },
...
]
},
{
id: 2,
...
}
]
Пример запроса 2
http://localhost:3000/your_controller/some_action?per=2amp;page=1amp;association_paginations[subjects][per]=2
Пример ответа 2
вернулись только два ученика, потому per
что = 2
возвращены только два объекта, потому association_paginations[subjects][per]
что = 2
[
{
id: 1,
school_id: 101,
school: { id: 101, ... },
standard_id: 201,
standard: { id: 201, ... },
subjects: [
{ id: 301, ... },
{ id: 302, ... },
],
attendances: [
{ id: 123, ... },
{ id: 124, ... },
{ id: 125, ... },
{ id: 126, ... },
{ id: 127, ... },
{ id: 128, ... },
...
]
},
{
id: 2,
...
}
]
PS поскольку приведенное выше решение связано с исправлением ошибок, я буду рекомендовать вместо этого использовать ActiveModelSerializers::Model, потому что запрошенная вами функция теперь становится сложной.
Комментарии:
1.
self.class.reflect_on_all_associations
выше будет возвращен массив всех ассоциаций, определенных в этой модели (включая:has_many
,:has_one
, и:belongs_to
). У меня такое чувство, что, возможно, вам просто нужно включить их в JSON, только если они:belongs_to
связаны; если это так, просто используйте вместо этого следующее:self.class.reflect_on_all_associations(:belongs_to)
2. большое спасибо. Решение работает просто отлично и спасает жизнь. Большое вам спасибо. Но проблема прямо сейчас возникает из-за ассоциаций has_many. Возможно ли ограничить данные для реализации разбивки на страницы, поскольку я не хочу усложнять вызов API, если у нас есть миллион данных в ассоциациях has_many.
3. @BibekDas смотрите мой обновленный ответ. Надеюсь, это сработает для вас.