Verification on a complex model for a multi-page form

I am trying to write a registration with the help of a developer and an active trader. The form is complex in that my custom object looks like this:

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 

Now the PaymentProfile class has its own child elements, two elements from the active merchant:

 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 

Now I have redefined RegistrationsController from Devise to handle a multi-page form using a solution from the multi-page Ryan Bates form ( http://railscasts.com/episodes/217-multistep-forms ). I had to tweak it a bit to get it working with Devise, but I was successful. Now, since the multi-page Ryan form just asked for different fields from the same model on different pages, was he able to override his valid? method valid? by adding an if if block to his a la check method:

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

But in my case, I request all the fields in the first form from my parent model (User), and then I request all the fields from two grandson models (User: PaymentProfile: Address, User: PaymentProfile: Credit_Card) on the second page.

The problem I am facing is that although PaymentProfile.valid? returns errors based on ActiveMerchant logic; the form itself does not display or even does not display these errors. The view code for the billing page is as follows:

 <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 %> 

I added puts errors to my code right after the actual one? and it shows the following:

 {: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"]]]} 

Now the structure of this output does not correspond to the output of the standard error output, which is built on a single-layer hash, for example:

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

So, showing you all this, my questions are: a) how can I get the error output to display correctly so that the form actually spits them out? b) how can i prevent parent.valid? from credibility check. when am I not on this page? I can not use the solution: if => lambda ... for child models because they do not know what current_step is.

My apologies for such a long post, I just wanted to include as much information as possible. I struggled with this for a week, and I seem to be unable to get past him. Any advice would be extremely helpful. Thanks in advance.

+6
validation nested-forms ruby-on-rails-3 multipage
source share
3 answers

The reason why errors are not displayed is likely to be because they are populated on the basis, rather than on individual attributes. This happens in the validate_card and validate_address methods. Instead of adding errors to the database, you should add them to the specific attribute that caused the error.

 errors.add( attr , error ) 

Secondly, if you want your checks to depend on a certain state, as a screencast that you mentioned, you need to save the state flag with the model (perhaps the best parent). You can do it manually or, better, you can use a gem for this (recommended): state_machine

Good luck.

+4
source share

At a high level, you seem to be using inheritance in your object modeling, and this model is built in several forms, almost under a โ€œmagicalโ€ way. My suggestion was to simulate your objects to reflect real shapes, like

 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) 

etc.

If the user model has one UserInformation, there is one PaymentInformation, etc.

Essentially replace inheritance with composition. Try and see if you can continue to work with the ActiveMerchant card.

The above style will give you more control if you want to call #valid? on a subset of your data model. It is partially created as the user moves around the form.

Sorry, I do not have a special solution for you, but a more general rewrite approach.

+1
source share

I am new to Ruby-on-Rails, and I know that this does not answer the above questions, but you should try Client Checks and take a look at Rails-casts . This may be useful for you!

0
source share

All Articles