ruby-on-rails - multiple - rails permit param
Representación de un objeto JSON de un modelo de unión y sus modelos asociados (2)
Use el scope
a su ventaja:
class Course < ActiveRecord::Base
# ...
scope :not_started, -> { joins(:student_courses) /
.where(student_courses: {started: false}) }
scope :with_classmates, -> { includes(:students) } # use eager loading
end
Luego llame:
@student.courses.not_started.with_classmates /
.to_json(include: {students: {only: :name}})
Salida:
[
{
"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"}]}]
En una aplicación Rails (4.1.5 / ruby 2.0.0p481 / win64) tengo una relación muchos a muchos entre Estudiante y Curso y un modelo de unión StudentCourse que representa la asociación, y tiene un atributo adicional llamado iniciado (establecido por defecto en "falso").
También agregué un índice en la tabla de unión compuesta de student_id y course_id , y establecí un control único sobre eso, como este
t.index [:student_id, :course_id], :unique => true, :name => ''by_student_and_course''
Quería que fuera una clave primaria compuesta, pero como en los rieles no hay claves primarias compuestas (sin usar una gema) también agregué una clave principal llamada id:
t.column :id, :primary_key
Ahora veo que las asociaciones se crean al hacer:
Student.first.courses.create(:name => "english")
o
Course.first.students << Student.first
Esto está bien y es el comportamiento esperado, supongo.
Dicho esto, estoy luchando por concentrarme en las resoluciones de asociación en las consultas de ActiveRecord. Déjame explicar esto mejor:Como referencia, Student.all , Course.all y StudentCourses.all devolverán tablas como estas:
Student.all
+----+-----------+
| id | name |
+----+-----------+
| 1 | Aidan |
| 2 | Alison |
| 3 | Elizabeth |
+----+-----------+
Course.all
+----+----------+------------------+
| 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.all
+-------------+------------+------------+----+
| 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 |
+-------------+------------+------------+----+
Hasta ahora puedo representar felizmente un objeto json de todos los cursos, y los nombres de todos los estudiantes para cada curso como este:
render json: Course.all.to_json(:include => {:students => {:only => :name}})
También puedo renderizar fácilmente todos los cursos a los que asiste un alumno o con los que va a asistir
render json: @student.courses.to_json(:include => {:students => {:only => :name}})
que también incluye otros estudiantes para esos cursos.
Pero supongamos que quisiera presentar los cursos de un alumno que el alumno todavía no ha comenzado, junto con todos los demás estudiantes que participan en ese curso (que han comenzado o no el curso) [Lea la sección de ACTUALIZACIÓN a continuación, estoy buscando lo opuesto en realidad!]
Creo que el enfoque más simple es consultar la tabla de unión para algo como:
StudentCourse.all.where(student_id: @student.id, started: false)
+-------------+------------+------------+----+
| course_id | student_id | started | id |
+-------------+------------+------------+----+
| 5 | 2 | false | 7 |
| 6 | 2 | false | 9 |
+-------------+------------+------------+----+
Pero, ¿cómo puedo continuar desde esta tabla resultante (objeto de asociación) para obtener un objeto json bien empaquetado con nombres de cursos (y todos los demás atributos) y también incluir estudiantes en él, como lo hice con: Course.all.to_json(:include => {:students => {:only => :name}})
?
Creo que aquí me faltan algunos conocimientos básicos de algunos conceptos clave importantes, pero en este momento ni siquiera puedo identificarlos y agradecería mucho la ayuda ... gracias.
Actualizar:
Me di cuenta de que la siguiente parte es lo que originalmente estaba tratando de hacer. Es lo opuesto. Entre todos estos detalles me perdí a lo largo del camino. Espero que esté bien si lo agrego aquí.
Entonces, dado un estudiante (llamémosle Aiden), necesito devolver un objeto JSON que contenga solo los cursos en los que se encuentra y que haya comenzado , solo cuando dichos cursos tengan otros estudiantes que no los hayan iniciado , y tiene que incluir los nombres de esos estudiantes para cada curso también.
Asi que...
Ahora tengo:
aiden_started_courses = Student(1).courses.where(:student_courses => {:started => true } )
que para un alumno toma todos los cursos que tienen un valor "verdadero" en la columna "comenzada" de la tabla de unión. (De nuevo, en la tabla de unión, cada registro de curso de un alumno es "único", por lo que solo puede haber un único registro para un student_id y un course_id).
Con la siguiente consulta, para uno de "aiden_started_courses" puedo realizar todas las asociaciones relativas de cursos de estudiantes que tienen un valor falso en "iniciado"
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 |
+-------------+------------+------------+----+
Así que aquí radica el problema: he logrado obtener esto solo para un solo curso en la matriz aiden_started_courses , pero ¿cómo podría crear una consulta que devuelva estos datos para todos los cursos iniciados de Aiden?
¿Es posible hacer eso en una línea? Sé que probablemente podría usar los bucles del enumerador de Ruby, pero de alguna forma siento que rompería algún patrón tanto en el nivel de convención de codificación de Rails como en el nivel de rendimiento. (golpeando N + 1 problema de nuevo?) ...
Lo que pude hasta ahora:
Se me ocurrió esto, donde encuentro a todos los estudiantes que no han comenzado los cursos que un usuario dado ha comenzado:
Student.includes(:student_courses).
where(:student_courses => { :started => false, :course_id => aiden.courses.where
(:student_courses => {started: true}).ids } )
o esto:
Course.includes(:students).where(:student_courses => {:started => false,
:course_id => aiden.courses.where(:student_courses => {:started =>true}).ids })
que encuentra todos los cursos que un estudiante dado ha comenzado si esos cursos incluyen estudiantes que aún no los han empezado
Pero lo que realmente necesito es obtener un objeto JSON como este:
[
{
"id": 1,
"name": "english",
"students": [
{"name": "ALison"},
{"name": "Robert"}]
},
{
"id": 2,
"name": "music",
"description": null,
"students": [
{"name": "Robert"},
{"name": "Kate"}]
}
]
donde puedo ver los cursos en los que un estudiante está y ha comenzado, pero solo aquellos en los que hay otros estudiantes que aún no lo han comenzado, junto con los nombres de esos estudiantes ...
Estoy pensando que probablemente no haya forma de cómo conseguirlo a través de una consulta AR común, ¿entonces debería crear una JSON manualmente? ¿Pero cómo podría hacer eso?
Gracias en adv. y me disculpo por la verbosidad ... pero espero que ayude ...
Use JBuilder, viene por defecto con Rails. Ignora las líneas que comienzan con ''#'':
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
No hay mucho código. Al eliminar los comentarios y simplificar algunas características, se verá así:
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!
Echa un vistazo a su guía , es bastante impresionante generar JSON en Ruby DSL. Así que adelante y usa el código de rubí que quieras para buscar estudiantes / cursos / cursos de estudio.