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