Отправка объектов внешнего ключа со всеми дочерними или дочерними объектами - PullRequest
0 голосов
/ 15 апреля 2019

есть ли способ всегда получать родительские объекты с дочерним или дочерним объектом для приложения только для rails API?

например. У меня есть массив @students. Каждый учащийся объект в массиве @students имеет два внешних ключа: standard_id и school_id. Теперь все объекты имеют по умолчанию standard_id и school_id. Вместо этого я хочу стандартный объект и школьный объект в каждом объекте ученика в массиве @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 Ответ

2 голосов
/ 15 апреля 2019

Если в вашем контроллере используется сериализатор Rails по умолчанию as_json, то есть см. Ниже:

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 включал вложенные ассоциации первого уровня; см. ниже

Решение

приложение / модели / 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(&:name)
  #     )
  #   end
  #   super(options)
  # end
end

Глобальное решение (Rails> = 5)

то же самое, что и Решение выше, но если вы хотите, чтобы это работало для ALL моделей, а не только для Student модели, то следующее:

приложение / модели / 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(&: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

приложение / модели / 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(&: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

приложение / 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

приложение / Config / Инициализаторы / 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=2&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=2&page=1&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,
    ...
  }
]

P.S. поскольку решение, приведенное выше, включает в себя «мартышку», я рекомендую вместо этого использовать ActiveModelSerializers :: Model , потому что запрошенная вами функция теперь становится сложной.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...