Skip to content

Instantly share code, notes, and snippets.

@pond
Created March 23, 2010 15:02
Show Gist options
  • Save pond/341262 to your computer and use it in GitHub Desktop.
Save pond/341262 to your computer and use it in GitHub Desktop.
########################################################################
# File:: without_table.rb
# (C):: http://stackoverflow.com/questions/315850/rails-model-without-database/318919#318919
#
# Purpose:: Define a class which lets us use ActiveRecord validations
# without needing a database representation of the model.
# ----------------------------------------------------------------------
# 23-Mar-2010 (ADH): Created.
########################################################################
class WithoutTable < ActiveRecord::Base
self.abstract_class = true
def self.columns
@columns ||= [];
end
def self.column( name, sql_type = nil, default = nil, null = true )
columns << ActiveRecord::ConnectionAdapters::Column.new(
name.to_s,
default,
sql_type.to_s,
null
)
end
end
########################################################################
# File:: payment_card.rb
# (C):: Hipposoft 2010
#
# Purpose:: Support a payment (credit/debit) card entry form with
# ActiveRecord-like validations but without needing a database
# table underneath.
# ----------------------------------------------------------------------
# 23-Mar-2010 (ADH): Created.
########################################################################
class PaymentCard < WithoutTable
# ===========================================================================
# CHARACTERISTICS
# ===========================================================================
# This model has no database representation - see "models/without_table.rb".
self.abstract_class = true # Keeps AR quiet but does not prevent instantiation
# Card details
column :card_name, :string
column :card_type, :string
column :card_number, :string
column :card_cvv, :string
column :card_to, :datetime
column :card_from, :datetime
column :card_issue, :string
validates_presence_of :card_name,
:card_type,
:card_number
validate :not_expired,
:valid_number,
:valid_type,
:valid_switch_or_solo_attributes,
:valid_cvv_if_needed
def not_expired
errors.add( :card_to, :has_expired ) if card.expired?
end
def valid_number
# Don't add both "cannot be blank" and "is invalid" errors to the same field
errors.add( :card_number, :is_invalid ) unless card_number.blank? || ActiveMerchant::Billing::CreditCard.valid_number?( card_number )
end
def valid_type
# Views should use a menu of type options, but this is here in case someone
# tries to try and hack the site with a custom form request
errors.add( :card_type, :is_invalid ) unless card_type.blank? || ActiveMerchant::Billing::CreditCard.card_companies.keys.include?( card_type )
end
def valid_switch_or_solo_attributes
if ( %w{ switch solo }.include?( card_type ) )
errors.add( :card_issue, :is_invalid ) unless card.valid_issue_number( card_number ) # Very odd API!
end
end
def valid_cvv_if_needed
if ( ActiveMerchant::Billing::CreditCard.requires_verification_value? )
errors.add_on_blank :card_cvv
end
end
# User billing address details
column :address_1, :string
column :address_2, :string
column :address_3, :string
column :city, :string
column :state, :string
column :country, :string
column :postcode, :string
validates_presence_of :address_1,
:city,
:country
# ===========================================================================
# PERMISSIONS
# ===========================================================================
# Permissions are equivalent to those for the User model.
#
def self.can_modify?( param1, param2 )
#TODO
raise "PaymentCard permissions TBD"
end
# ===========================================================================
# GENERAL
# ===========================================================================
# Read-only accessor style method which defines a credit card with Active
# Merchant and caches the result internally. Only read the card once you
# have filled in the various "card_..." column values (see above).
#
def card
names = card_name.try( :split, ' ' ) || []
first_name = names.shift
last_name = names.join( ' ' )
@card ||= ActiveMerchant::Billing::CreditCard.new(
:first_name => first_name,
:last_name => last_name,
:number => card_number,
:type => card_type,
:month => card_to.try( :month ).to_s,
:year => card_to.try( :year ).to_s,
:verification_value => card_cvv,
:start_month => card_from.try( :month ).to_s,
:start_year => card_from.try( :year ).to_s,
:issue_number => card_issue
)
end
end
# Then, in e.g. "en.yml":
en:
activerecord:
errors:
models:
payment_card:
has_expired: "has expired"
is_invalid: "is invalid"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment