Skip to content

Commit

Permalink
Move Address, State, Country, and QuietNil into plugin. Also improve …
Browse files Browse the repository at this point in the history
…testing environment for plugin (add Machinist and a bunch of other stuff). [#44]
  • Loading branch information
marnen committed Oct 23, 2009
1 parent 56df6bc commit ec9ea72
Show file tree
Hide file tree
Showing 15 changed files with 260 additions and 149 deletions.
89 changes: 0 additions & 89 deletions app/models/address.rb

This file was deleted.

4 changes: 0 additions & 4 deletions app/models/country.rb

This file was deleted.

10 changes: 0 additions & 10 deletions app/models/state.rb

This file was deleted.

13 changes: 0 additions & 13 deletions lib/quiet_nil.rb

This file was deleted.

29 changes: 0 additions & 29 deletions spec/models/quiet_nil_spec.rb

This file was deleted.

91 changes: 91 additions & 0 deletions vendor/plugins/acts_as_addressed/lib/acts/addressed/address.rb
@@ -0,0 +1,91 @@
# Value object for addresses.

module Acts::Addressed
class Address
# List of readable attributes.
FIELDS = [:street, :street2, :city, :state, :zip, :coords]
FIELDS.each do |f|
attr_reader f
end

# Initializes a new #Address object from a #Hash of fields or separate arguments in the order given in FIELDS.
def initialize(*args)
case args.length
when 0
# do nothing
when 1
if args[0].kind_of?(Hash)
set_from_hash args[0]
else
raise ArgumentError, "expected Hash, got #{args[0].class.name}"
end
when FIELDS.length
set_from_array args
else
raise ArgumentError, "expected 0, 1, or #{FIELDS.length} arguments, got #{args.length}"
end
if !@state.blank? and !@state.kind_of?(State)
@state = State.find(@state)
end
end

# Returns the #Country that the address belongs to.
def country
state.country
end

# Returns the #State that the address belongs to, or a #QuietNil if there is no state.
def state
@state || QuietNil.instance
end

# Converts #Address to a #String.
#
# Valid values of +format+:
# <tt>:geo</tt>:: Address in comma-separated format for feeding to geocoder.
def to_s(format = :geo)
case format
when :geo
"#{street}, #{city}, #{state.code}, #{zip}, #{country.code}"
else
raise ArgumentError, "unknown format parameter: #{format}"
end
end

# Compares two Addresses by value.
def ==(other)
if other.kind_of?(Address)
FIELDS.each do |f|
if self.send(f) != other.send(f)
return false
end
end
return true
else
return false
end
end

protected

# TODO: set_from_* methods should perhaps become public.

# Sets instance variables from the supplied #Hash.
def set_from_hash(hash)
FIELDS.each do |f|
if hash.has_key?(f)
eval "@#{f.to_s} = hash[:#{f.to_s}]"
end
end
end

# Sets instance variables from the supplied #Array, in the same order as #FIELDS.
def set_from_array(array)
hash = {}
FIELDS.each_index do |i|
hash[FIELDS[i]] = array[i]
end
set_from_hash hash
end
end
end
@@ -0,0 +1,7 @@
module Acts::Addressed
class Country < ActiveRecord::Base
has_many :states
validates_length_of :code, :is => 2
validates_presence_of :name
end
end
15 changes: 15 additions & 0 deletions vendor/plugins/acts_as_addressed/lib/acts/addressed/quiet_nil.rb
@@ -0,0 +1,15 @@
# This is just like NilClass, but when methods are called on a QuietNil, they just return the QuietNil without raising an exception.
#
# WARNING: This class is easy to abuse. Please resist temptation!

module Acts::Addressed
class QuietNil < ActiveSupport::BasicObject
# Implementation is yanked from http://coderrr.wordpress.com/2007/09/15/the-ternary-destroyer/
include Singleton

def method_missing(method, *args, &b)
return self unless nil.respond_to? method
nil.send(method, *args, &b) rescue self
end
end
end
12 changes: 12 additions & 0 deletions vendor/plugins/acts_as_addressed/lib/acts/addressed/state.rb
@@ -0,0 +1,12 @@
module Acts::Addressed
class State < ActiveRecord::Base
belongs_to :country
validates_presence_of :country_id

validate do |state|
if state.code.blank? ^ state.name.blank?
state.errors.add_to_base("Code and name must both be blank or must both be nonblank.")
end
end
end
end
File renamed without changes.
@@ -1,8 +1,17 @@
require File.dirname(__FILE__) + '/../spec_helper'
require File.dirname(__FILE__) + '/../../spec_helper'

describe Country, "(validations)" do
require 'acts/addressed/country'

describe Acts::Addressed::Country, "(associations)" do
it "should have many states" do
assoc = Acts::Addressed::Country.reflect_on_association(:states)
assoc.should_not be_nil
assoc.macro.should == :has_many
end
end
describe Acts::Addressed::Country, "(validations)" do
before(:each) do
@country = Country.new
@country = Acts::Addressed::Country.new
@country.code = "AA" # arbitrary value of length 2
@country.name = "x" # arbitrary value
end
Expand Down
@@ -0,0 +1,31 @@
require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')

module Acts::Addressed
describe QuietNil do
it "should be a valid class" do
QuietNil.class.should == Class
end

it "should respond as nil to predefined methods" do
qn = QuietNil.instance
qn.should be_nil
pending "can't figure out how to convert to boolean" do
(!!qn).should be_false
end
qn.to_s.should == nil.to_s
qn.to_a.should == nil.to_a
end

it "should not whine when it receives methods that aren't defined on nil" do
qn = QuietNil.instance
p = lambda {qn.foobarbaz(1, 2, 3)}
p.should_not raise_error

# We're overriding equality, so we can't do
# (p.call.equal? qn).should be_true
# Instead:
p.call.should be_nil
lambda {p.call.quux(:quuuux)}.should_not raise_error
end
end
end
File renamed without changes.
23 changes: 23 additions & 0 deletions vendor/plugins/acts_as_addressed/spec/blueprints.rb
@@ -0,0 +1,23 @@
# Object blueprints for Machinist.
require 'machinist/active_record'
require 'sham'
require 'faker'
LETTERS = ('A'..'Z').to_a

module Acts::Addressed
Country.blueprint do
name {Sham.generic_name}
code
end

State.blueprint do
country
name {Sham.generic_name}
code
end
end

Sham.define do
generic_name {Faker::Name.last_name}
code {LETTERS.rand + LETTERS.rand} # 2 random letters
end

0 comments on commit ec9ea72

Please sign in to comment.