2012-02-02 16 views
12

Conocí por primera vez acerca de Data, context, and interaction (DCI) a través de this blog post. Fascinado por el concepto, me esforcé por incorporarlo a mi próxima aplicación Rails. Dado que DCI funciona en conjunto con MVC, pensé que no sería demasiado difícil hacer la API RESTful al mismo tiempo. Así que hice un recurso RESTful, Report y lo extendí en varios contextos. La forma en que implementé contextos en Rails fue creando un directorio, /app/contexts/, para módulos que extienden las acciones del controlador. Así que mi reports_controller.rb se parece a esto:RESTful DCI contexts in Rails

class ReportsController < ApplicationController 
    before_filter :only => :new do |c| 
    c.switch_context("submission") 
    end 

    # GET /reports 
    def index 
    @context.report_list 
    end 

    # GET /reports/1 
    def show 
    @context.display_report 
    end 

    # GET /reports/new 
    def new 
    @context.new_report 
    end 

    # GET /reports/1/edit 
    def edit 
    @context.edit_report 
    end 

    # POST /reports 
    def create 
    @context.create_report 
    end 

    def update 
    @context.update_report 
    end 

    # DELETE /reports/1 
    def destroy 
    @context.destroy_report 
    end 

    protected 

    def switch_context(context_name) 
    session[:context] = context_name 
    context = session[:context].camelize.constantize 
    @context ||= self.extend context 
    end 
end 

Y en el application_controller.rb puse el contexto de una before_filter:

class ApplicationController < ActionController::Base 
    before_filter :contextualize 
    protect_from_forgery 

    protected 

    # Sets the context of both current_user and self 
    # by extending with /app/roles/role_name 
    # and /app/contexts/context_name respectively 
    def contextualize 
    # Extend self (ActionController::Base) with context 
    if session[:context] 
     context_class = session[:context].camelize.constantize 
     if current_user.allowed_contexts.include?(context_class) 
     context_class = current_user.context if context_class == Visiting 
     else 
     context_class = Visiting 
     end 
    else 
     context_class = current_user.context 
    end 
    @context ||= self.extend context_class 
    end 
end 

Aviso extiendo current_user con un Role además del marco regulador.

Así es como funciona:

  1. Un usuario inicia sesión en
  2. El papel del usuario es RegisteredUser..
  3. RegisteredUser El contexto predeterminado es Search (como se define en /app/roles/registered_user.rb).
  4. Dentro del contexto Search, el usuario solo puede ver informes publicados.
  5. El usuario presiona el botón "crear nuevo informe" y el contexto se cambia a Submission y se almacena en la sesión current_user.
  6. El usuario procede a enviar un informe a través de un formulario de varios pasos.
  7. Cada vez que el usuario guarda el informe al recorrer el formulario, el contexto /app/contexts/submission.rb maneja la acción.

Los otros contextos (revisión, editorial, etc.) y roles (coautor, editor, etc.) son varios.

Hasta ahora, este enfoque ha funcionado bien en su mayor parte. Pero hay un error: cuando un usuario abre varias ventanas del navegador y cambia contextos en una de ellas, todas las otras ventanas estarán en el contexto incorrecto. Esto podría ser un problema si el usuario está en el medio del formulario de pasos múltiples y luego abre una ventana en el contexto Search. Cuando vuelva al formulario y presione "Siguiente", el controlador realizará la acción definida por el contexto Search en lugar del contexto Submission.

Hay 2 posibles formas de evitar esto que me ocurre:

  1. espacio de nombres del Report recurso con el nombre de contexto. Por lo tanto, el usuario visitaría direcciones URL como /search/reports y /submission/reports/1. Esto no me parece RESTANTE y preferiría mantener la URL lo más limpia posible.
  2. Ponga el nombre del contexto en un campo oculto. Este método requiere que los desarrolladores recuerden poner el campo oculto en todas las formas del sitio, y no funciona para las solicitudes GET.

¿Hay alguna otra forma de solucionar este problema, o mejores implementaciones generales?

Sé de this project, pero es demasiado limitado para nuestras necesidades.

+1

Haveyou miró los ejemplos de Rickard Öberg de hacer DCI y REST en Java con Qi4J. Creo que usa la estructura de URI para construir el contexto, por lo que la solicitud en sí misma contiene toda la información necesaria para reconstruir el contexto en el servidor. También es posible que desee intentar publicar esto en el grupo de google de composición de objetos. –

+0

tal vez el almacenamiento del paso anterior en las cookies puede ayudar? – Fivell

+0

El contexto actual ya está almacenado en la sesión a través de una cookie. No veo qué almacenar en el paso anterior se sumaría a esta situación. También sería inútil al realizar alguna acción en otra ventana del navegador. –

Respuesta

3

Si desea permitir múltiples contextos, entonces obviamente debe poner la información que determina el contexto actual en algún almacenamiento que no se comparte entre las pestañas. Las sesiones, tal como se implementan en Rack/Rails, usan cookies y las cookies se comparten entre pestañas.

Simplemente ponga el contexto en algo que no se comparte. ¿Qué tal un contexto = parámetro de URL del espectador?

Para hablar REST Creo que es discutible si un recurso es igual o no en diferentes contextos. Se podría argumentar que un informe para un usuario "Visitante" es diferente de un informe para un usuario "Administrador". En ese caso, un enfoque RESTy probablemente espaciará las solicitudes (lo que nuevamente coloca el contexto en la URL), p./visiting/reports/1 vs/administration/reports/1.

Y una tercera forma de poner el contexto en la URL sería usarlo como parte del nombre de dominio.

+0

Terminé usando el espacio de nombres. –