Rails3: Should nested ActiveModel be treated like nested ActiveRecord

I have an User model (ActiveRecord). It has many Addresses (ActiveRecord) and one CreditCard (ActiveModel)

When I submit my nested form the CreditCard validations did not fire.

So... in my User model I added this so the credit card validations would get called.

  def before_validation     credit_card.valid? unless credit_card.nil?     return true   end

This worked great. the missing credit card fields were highlighted and everything... except

@user.errors.full_messages only contained errors for the user and addresses.

So I updated my before_validation call again

  def before_validation     credit_card.valid? unless credit_card.nil?     credit_card.errors.each{|k,v|       errors["credit_card.#{k}"] = v     }     return true   end

Now everything is cool except the ugly error messages that nested forms give you.

I chop off the credit card and addresses prefix when displaying them

      <% @user.errors.full_messages.each do |msg| %>         <%         message = msg.gsub(/Credit card /, '').capitalize         message = message.gsub(/Addresses /, '').capitalize       %>         <li><%= message %></li>       <% end %>

This seems like a lot of work and I suspect there is a 1 liner I'm missing that will make this all automatic.

Something like this in my user model.

has_one_virtual :credit_card

Does such a thing exist? I hope so :slight_smile:

================== Code snippets ====================

==== User model ====

class User < ActiveRecord::Base   acts_as_authentic   has_many :addresses   accepts_nested_attributes_for :addresses   validates :credit_card, :presence => true   attr_accessor :email_confirmation, :credit_card

  validates_confirmation_of :email, :message => "should match confirmation"

  def credit_card_attributes=(attributes)     self.credit_card = CreditCard.new(attributes)   end

  def before_validation     credit_card.valid? unless credit_card.nil?     credit_card.errors.each{|k,v|       errors["credit_card.#{k}"] = v     }     return true   end end

==credit_card================= class CreditCard

  include ActiveModel::Validations   include ActiveModel::AttributeMethods   include ActiveModel::Callbacks   include ActiveModel::Conversion   extend ActiveModel::Naming

  # belongs_to :user

attr_accessor :card_type, :card_number, :card_verification, :card_expires_on, :agree   validates :card_type, :presence => true   validates :card_number, :presence => true   validates :card_verification, :presence => true   validates :card_expires_on, :presence => true   validates :agree, :presence => true

  def initialize(attributes = {})     expire_date = {}     attributes.each do |name, value|       if name.include?('card_expires_on')         expire_date[name] = value       else         send("#{name}=", value)       end     end     # yeah, this is a total mess. Can ActiveModel handle this?     ymd = expire_date.map(&:last).map(&:to_i)     begin       send("card_expires_on=", Time.zone.local(ymd[2], ymd[1],1)) unless ymd[1].blank? || ymd[2].blank?     rescue     end   end

  def persisted?     false   end end

(I'm still looking for some feedback on an equivalent way to do accepts_nested_attributes for nested resources that are ActiveModels.. instead of the hack I have below).

For the bad error message formatting...instead of stripping the prefixes from the error messages I could use l18n

      <% @user.errors.full_messages.each do |msg| %>         <%         message = msg.gsub(/Credit card /, '').capitalize         message = message.gsub(/Addresses /, '').capitalize       %>         <li><%= message %></li>       <% end %>

after some trial and error I figured out how to to make l18n with nested resources.

en:   activerecord:     attributes:       user:         credit_card:           agree: "Agreement"           card_verification: "Card Verification Value"         addresses:           address1: "Street Address"

I'll probably stick to using gsub for most cases (until I start localizing all my text). Unless of course there is an automatic way to make "Credit card card verification can't be blank" display "Card verification can't be blank"