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”