Визуализация объекта JSON модели соединения и связанных с ней моделей

в рельсах (4.1.5 / ruby 2.0.0p481 / win64 ) приложение у меня есть отношения "многие ко многим" между студент и курс и модель StudentCourse, который представляет ассоциацию, и дополнительный атрибут под названием started (по умолчанию установлено значение "false").

я также добавил индекс в таблицу соединений из student_id и идентификатор_курса, и установите уникальную проверку на этом, как это

t.index [:student_id, :course_id], :unique => true, :name => 'by_student_and_course'

я хотел, чтобы это был составной первичный ключ, но поскольку в rails нет составных первичных ключей (без использования драгоценного камня), я также добавил первичный ключ под названием id:

t.column :id, :primary_key

теперь я вижу, что ассоциации создаются либо делать:

Student.first.courses.create(:name => "english")

или

Course.first.students << Student.first

это нормально, и это ожидаемое поведение, я полагаю.


Тем не менее, я изо всех сил пытаюсь обернуть свой ум вокруг резолюций ассоциации в запросах ActiveRecord. Позвольте мне объяснить это лучше:

Для справки:студент.все!--24-->, конечно.все!--24--> и StudentCourses.все!--24--> вернул бы такие таблицы:

студент.все!--27-->

+----+-----------+
| id | name      |
+----+-----------+
| 1  | Aidan     |
| 2  | Alison    |
| 3  | Elizabeth |
+----+-----------+

конечно.все!--27-->

+----+----------+------------------+
| id | name     | description      |
+----+----------+------------------+
| 1  | english  | desc. here       |
| 2  | music    | desc. here       |
| 3  | dance    | desc. here       |
| 4  | science  | desc. here       |
| 5  | french   | desc. here       |
| 6  | arts     | desc. here       |
+----+----------+------------------+

StudentCourse.все!--27-->

+-------------+------------+------------+----+
| course_id   | student_id | started    | id |
+-------------+------------+------------+----+
| 1           | 1          | false      | 1  |
| 2           | 1          | false      | 2  |
| 3           | 1          | false      | 3  |
| 1           | 2          | true       | 4  |
| 2           | 2          | true       | 5  |
| 4           | 3          | false      | 6  |
| 5           | 2          | false      | 7  |
| 5           | 1          | true       | 8  |
| 6           | 2          | false      | 9  |
+-------------+------------+------------+----+


до сих пор я могу с удовольствием сделать объект json всех курсы, и имена всех студентов для каждого курса, как это:

render json: Course.all.to_json(:include => {:students => {:only => :name}})

я также могу легко сделать все курсы, которые студент посещает или собирается посетить с

render json: @student.courses.to_json(:include => {:students => {:only => :name}})

который также включает в себя других студентов для этих курсов.

но предположим, я хотел бы сделать курсы одного студента, которые студент еще не начал, вместе со всеми другими студентами, которые находятся на этом курсе (которые или не начали курс) [пожалуйста, прочитайте раздел Обновления ниже, я ищу противоположную вещь на самом деле!]

я думаю, что подход easieast заключается в запросе таблицы соединений для чего-то вроде:

StudentCourse.all.where(student_id: @student.id, started: false)

+-------------+------------+------------+----+
| course_id   | student_id | started    | id |
+-------------+------------+------------+----+
| 5           | 2          | false      | 7  |
| 6           | 2          | false      | 9  |
+-------------+------------+------------+----+

но как мне перейти от этой результирующей таблицы (объект ассоциации), чтобы получить красиво упакованный объект json с именами курсов (и всеми другими атрибутами), а также в том числе студенты в нем, как я сделал с: Course.all.to_json(:include => {:students => {:only => :name}}) ?

я думаю, что мне не хватает некоторых базовых знаний о некоторых важных ключевых понятиях здесь, но на данный момент я даже не могу их идентифицировать и был бы очень признателен за некоторую помощь... спасибо.



обновление:

я просто понял, что следующая часть-это то, что я изначально пытался сделать. Все наоборот. Среди всех этих подробностей я заблудился на пути. Я надеюсь, что это нормально, если я просто добавлю его здесь.

Итак, учитывая студента (назовем его Эйденом), мне нужно вернуть объект JSON, содержащий только курсы, в которых он находится, и что он стал, только когда на таких курсах есть другие студенты, которые еще не начали их, и он также должен включать имена этих студентов для каждого курса.

так...

у меня теперь есть:

aiden_started_courses = Student(1).courses.where(:student_courses => {:started => true } )

который для a студент принимает курсы которые имеют значение " true "в столбце" started " таблицы соединений. (снова в таблице join each студент-курс запись" композитно " уникальна, поэтому для данного student_id и course_id может быть только одна уникальная запись).

со следующим запросом, для один из "aiden_started_courses" я могу снять все относительные студент-курсы ассоциации, имеющие значение false на "начало"

aiden_started_courses[0].student_courses.where(:started => false).includes(:student).to_json(:include => :student)

+-------------+------------+------------+----+
| course_id   | student_id | started    | id |
+-------------+------------+------------+----+
| 1           | 2          | false      | 4  |
+-------------+------------+------------+----+
| 1           | 9          | false      | 5  |
+-------------+------------+------------+----+

так вот в чем проблема:мне удалось получить это только для одного курса в aiden_started_courses массив, но как бы я мог построить запрос, который возвращает эти данные для все Эйдена начал курсы?

можно ли сделать это в одну строку? Я знаю, что, вероятно, мог бы использовать циклы перечислителя Ruby, но я чувствую, что я бы нарушил какой-то шаблон как на рельсах уровень конвенции кодирования и уровень производительности? (снова нажимая N+1?) ...


что я мог до сих пор:

я придумал это, где я нахожу всех студентов, которые не начали курсы, которые начал данный пользователь:

Student.includes(:student_courses).
where(:student_courses => { :started => false, :course_id => aiden.courses.where
           (:student_courses => {started: true}).ids  } ) 

или такой:

Course.includes(:students).where(:student_courses => {:started => false, 
   :course_id => aiden.courses.where(:student_courses => {:started =>true}).ids })

который находит все курсы, которые данный студент начал, если эти курсы включают студентов, которые не начали их еще

но что мне действительно нужно, так это получить объект JSON:

[
    {
        "id": 1,
        "name": "english",
        "students": [
            {"name": "ALison"},
            {"name": "Robert"}]
    },
    {
        "id": 2,
        "name": "music",
        "description": null,
        "students": [
            {"name": "Robert"},
            {"name": "Kate"}]
    }
]


где я могу видеть курсы, на которых данный студент находится и начал, но только те, в которых есть другие студенты, которые еще не начали его, вместе с именами этих студентов...

я думаю, что, вероятно, нет способа, как я мог бы получить это через обычный запрос AR, поэтому, возможно, следует построить JSON вручную? Но как смогу ли я это сделать?


Спасибо в adv. и прошу прощения за многословие.. но, надеюсь, это поможет..

2 ответов


используйте JBuilder, он поставляется по умолчанию с Rails. Игнорируйте строки, начинающиеся с'#':

Jbuilder.new do |j|

    # courses: [{
    j.courses <student.courses - replace this with whatever variable> do |course|

      # id: <course.id>
      j.id course.id 

      # name: <course.name>
      j.name course.name

      # students: [{
      j.students <course.students - replace with whatever variable> do |student|

          # name: <student.name>
          j.name student.name

      end
      # }]

   end
   # }]

end

не так много кода. Удаление комментариев и упрощение некоторых функций, это будет выглядеть так:

student_courses = <...blah...>
json = Jbuilder.new do |j|
  j.courses student_courses do |course|
    j.(course, :id, :name)
    j.students <course.students - whatever method here>, :name
  end
end.target!

проверить их руководство, его довольно удивительно генерировать JSON в простой рубиновый DSL. Поэтому идите вперед и используйте любой код ruby, который вы хотите получить для студентов/курсов/studentcourses.


использовать scope в свою пользу:

class Course < ActiveRecord::Base
  # ...
  scope :not_started, -> { joins(:student_courses) \
                                      .where(student_courses: {started: false}) }

  scope :with_classmates, -> { includes(:students) } # use eager loading
end
звоните:
@student.courses.not_started.with_classmates \
                                     .to_json(include: {students: {only: :name}})

выход:

[
    {
        "id": 1,
        "name": "english",
        "description": null,
        "students": [
            {"name": "Aiden"},
            {"name": "Alison"}]},
    {
        "id": 2,
        "name": "music",
        "description": null,
        "students": [
            {"name": "Aiden"},
            {"name": "Alison"}]},
    {
        "id": 3,
        "name": "dance",
        "description": null,
        "students": [
            {"name": "Aiden"}]}]