2011-03-28 8 views
6

Estoy tratando de escribir un registro utilizando el inventor y el comerciante activo. La forma es compleja en la que mi objeto de usuario se ve así:Validación en un modelo complejo para un formulario de varias páginas

class User < ActiveRecord::Base 
    include ActiveMerchant::Utils 

    # Include default devise modules. Others available are: 
    # :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable 
    devise :database_authenticatable, :registerable, 
     :recoverable, :rememberable, :trackable, :validatable, :omniauthable 

    # Setup accessible (or protected) attributes 
    attr_accessible :email, :password, :password_confirmation, :remember_me, :username, :first_name, 
        :subscription_attributes, :last_name, :zipcode, 
        :payment_profile_attributes, :customer_cim_id, :payment_profile_id 

... 

    # Track multi-page registration 
    attr_writer :current_step 

... 

    # Setup Payment Profile element (authorize.net billing profile) 
    has_one :payment_profile, :dependent => :delete 
    accepts_nested_attributes_for :payment_profile 

Ahora la clase PaymentProfile tiene sus propios hijos, dos artículos de comerciante activo:

require 'active_merchant' 

class PaymentProfile < ActiveRecord::Base 
    include ActiveMerchant::Billing 
    include ActiveMerchant::Utils 

    validate_on_create :validate_card, :validate_address 

    attr_accessor :credit_card, :address 

    belongs_to :user 

    validates_presence_of :address, :credit_card 

    def validate_address 
    unless address.valid? 
     address.errors.each do |error| 
     errors.add(:base, error) 
     end 
    end 
    end 

    def address 
    @address ||= ActiveMerchant::Billing::Address.new(
     :name  => last_name, 
     :address1 => address1, 
     :city  => city, 
     :state => state, 
     :zip  => zipcode, 
     :country => country, 
     :phone => phone 
    ) 
    end 

    def validate_card 
    unless credit_card.valid? 
     credit_card.errors.full_messages.each do |message| 
     errors.add(:base, message) 
     end 
    end 
    end 

    def credit_card 
    @credit_card ||= ActiveMerchant::Billing::CreditCard.new(
     :type    => card_type, 
     :number    => card_number, 
     :verification_value => verification_code, 
     :first_name   => first_name, 
     :last_name   => last_name 
    ) 
    @credit_card.month ||= card_expire_on.month unless card_expire_on.nil? 
    @credit_card.year ||= card_expire_on.year unless card_expire_on.nil? 
    return @credit_card 
    end 

Ahora he overrided la RegistrationsController de Diseñe para manejar el formulario de varias páginas usando la solución del screencast de varias páginas de Ryan Bates (http://railscasts.com/episodes/217-multistep-forms). Tuve que modificarlo un poco para que funcionara con Devise, pero tuve éxito. Ahora, debido a la forma de varias páginas de Ryan se limitó a preguntar para diferentes campos de la misma modelo en diferentes páginas, fue capaz de anular su método valid? añadiendo una: si el bloque a su método de validación a la:

validates_presence_of :username, :if => lambda { |o| o.current_step == "account" } 

Pero en mi caso, estoy solicitando todos los campos en el primer formulario de mi modelo padre (Usuario), y luego solicitando todos los campos de mis dos modelos de nieto (Usuario: Perfil de pago: Dirección, Usuario: Perfil de pago: Tarjeta de crédito) en la segunda página.

El problema al que me enfrento es que, aunque PaymentProfile.valid? devuelve errores basados ​​en la lógica de ActiveMerchant, el formulario en sí no representa o incluso muestra esos errores. El código de la vista de la página de facturación es el siguiente:

<h2>Payment Details</h2> 

<%= semantic_form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f| %> 
    <%= devise_error_messages! %> 

    <%= f.semantic_fields_for :payment_profile do |p| %> 
     <%= p.semantic_fields_for :address do |a| %> 
      <%= a.inputs "Billing Information", :id => "billing" do %> 
       <%= a.input :type, :label => "Credit Card", :as => :select, :collection => get_creditcards %> 
       <%= a.input :number,  :label => "Card Number", :as => :numeric %> 
       <%= a.input :card_expire_on, :as => :date, :discard_day => true, :start_year => Date.today.year, :end_year => (Date.today.year+10), :add_month_numbers => true %> 
       <%= a.input :first_name %>  
       <%= a.input :last_name %> 
       <%= a.input :verification_code, :label => "CVV Code" %> 
      <% end %> 
     <% end %> 

     <%= f.semantic_fields_for :credit_card do |c| %> 
      <%= c.inputs "Billing Address", :id => "address" do %> 
       <%= c.input :address1, :label => "Address" %> 
       <%= c.input :city %> 
       <%= c.input :state, :as => :select, :collection => Carmen::states %> 
       <%= c.input :country, :as => :select, :collection => Carmen::countries, :selected => 'US' %> 
       <%= c.input :zipcode, :label => "Postal Code" %> 
       <%= c.input :phone, :as => :phone %> 
      <% end %> 
     <% end %> 
    <% end %> 

    <%= f.commit_button :label => "Continue" %> 
    <% unless @user.first_step? %> 
    <%= f.commit_button :label => "Back", :button_html => { :name => "back_button" } %> 
    <% end %> 
<% end %> 

he añadido un mensaje puts errors en mi código justo después de la válida? comando y se nota como sigue:

{:base=>[["first_name", ["cannot be empty"]], ["last_name", ["cannot be empty"]], ["year", ["expired", "is not a valid year"]], ["type", ["is required", "is invalid"]], ["number", ["is not a valid credit card number"]], ["verification_value", ["is required"]], ["address1", ["is required"]], ["city", ["is required"]], ["state", ["is required"]], ["zip", ["is required", "must be a five digit number"]], ["phone", ["is required", "must be in the format of 333-333-3333"]]]} 
{:base=>[["first_name", ["cannot be empty"]], ["last_name", ["cannot be empty"]], ["year", ["expired", "is not a valid year"]], ["type", ["is required", "is invalid"]], ["number", ["is not a valid credit card number"]], ["verification_value", ["is required"]], ["address1", ["is required"]], ["city", ["is required"]], ["state", ["is required"]], ["zip", ["is required", "must be a five digit number"]], ["phone", ["is required", "must be in the format of 333-333-3333"]]]} 

Ahora la estructura de esta salida no coincide con la salida de una salida de error estándar que se construye de una sola capa de hash tales como:

{:username=>["can't be blank"]} 

Así Después de mostrarle todo eso, mis preguntas son las siguientes: a) ¿Cómo obtengo que la salida del error se muestre correctamente para que la forma realmente las escupe? b) ¿cómo evito parent.valid? también validando nietos.valido? cuando no estoy en esa página? No puedo usar la solución: if => lambda ... en modelos secundarios porque no saben qué es el actual_step.

Mis disculpas por una publicación tan larga, solo quería incluir la mayor cantidad de información posible. He estado luchando con esto durante una semana y parece que no puedo superarlo. Cualquier consejo sería de gran ayuda. Gracias por adelantado.

Respuesta

4

La razón por la que no se muestran los errores es probablemente porque están pobladas en la base, no en los atributos individuales. Esto sucede en sus métodos validate_card y validate_address. En lugar de agregar errores a la base, debe agregarlos al atributo específico que causó el error.

errors.add(attr , error) 

En segundo lugar, si usted quiere hacer depender sus validaciones en un cierto estado, como el screencast que usted ha mencionado, entonces usted necesita para guardar el estado del pabellón con el modelo (probablemente el mejor de los padres). Puede hacerlo a mano o, mejor, puede usar una gema para esto (recomendado): state_machine

Buena suerte.

0

Soy nuevo en Ruby-on-Rails y sé que esto no responde las preguntas anteriores, pero debería probar Client-Side Validations y echar un vistazo al Rails-casts. ¡Puede ser útil para ti!

+2

La validación del lado del cliente es un buen extra, pero absolutamente ningún reemplazo para la validación del lado del servidor. La validación del lado del cliente es fácilmente socavada por scripts maliciosos que no usan un navegador habilitado para JS. Para garantizar que sus datos sean de alta integridad, la validación del lado del servidor es el único mecanismo confiable. –

1

En un nivel alto, parece que está usando la herencia en su modelado de objetos y este modelo se está construyendo en varias formas, en un enfoque casi de 'asistente'. Mi sugerencia sería para modelar los objetos para reflejar, las formas reales como,

First part of the form collect basic User information : UserInformation model 

Second Part of the form collect payment related information: PaymentInformation model (the Active merchant stuff) 

y así sucesivamente ...

Donde sea el modelo de usuario tiene un UserInformation, tiene un PaymentInformation y así sucesivamente.

Esencialmente reemplaza la herencia con la Composición. Intenta ver si puedes evitar extender el trabajo del marco ActiveMerchant también.

El estilo anterior, ¿le dará más control sobre cuándo desea llamar a #valid? en un subconjunto de tu modelo de datos. Se construye parte por parte a medida que el usuario se mueve a través del formulario.

Disculpe, no tengo una solución específica para usted, pero un enfoque de reescritura más general.

Cuestiones relacionadas