This is part of the Chores series of posts.
I’m now attacking the Idenitity model. My idea for this is a model that contains a single field. That field is a valid, properly formatted, immutable OpenID. Because I’m going to need to validate OpenIDs I’m going to install the Open ID Plugin as follows.
$ ruby script/plugin install git://github.com/rails/open_id_authentication.git Initialized empty Git repository in c:/rails/chores/vendor/plugins/open_id_authentication/.git/ remote: Counting objects: 35, done.←[K remote: Compressing objects: 100% (31/31), done.←[K remote: Total 35 (delta 4), reused 21 (delta 2)←[K Unpacking objects: 100% (35/35), done. From git://github.com/rails/open_id_authentication * branch HEAD -> FETCH_HEAD
$ rake open_id_authentication:db:create (in c:/rails/chores) exists db/migrate create db/migrate/20090125184610_add_open_id_authentication_tables.rb
$ rake db:migrate (in c:/rails/chores) == AddOpenIdAuthenticationTables: migrating ================================== -- create_table(:open_id_authentication_associations, {:force=>true}) -> 0.0310s -- create_table(:open_id_authentication_nonces, {:force=>true}) -> 0.0160s == AddOpenIdAuthenticationTables: migrated (0.0470s) =========================
Here is my test for the Identity model
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | require 'test_helper' class IdentityShouldBeValidatedTest < ActiveSupport::TestCase specify "that an open ID is required on create" do identity = Identity.new invalid identity identity.open_id = "test.example.com" valid identity end specify "message for an overly long identifier" do identity = Identity.new :identifier => hundred_and_one_character_identifier validation_message_for identity, :identifier, "is too long." end specify "message for a null identifier" do identity = Identity.new validation_message_for identity, :identifier, "can not be blank." end specify "message for a poorly formatted" do identity = Identity.new :identifier => invalid_open_id validation_message_for identity, :identifier, "is not a valid Open ID." end specify "message for a duplicate identifier" do Identity.make :identifier => valid_identifier identity = Identity.new :identifier => valid_identifier validation_message_for identity, :identifier, "is already in use, please use another." end specify "that 100 character identifier is valid" do identity = Identity.new :identifier => hundred_character_identifier valid identity end specify "that identifier is unique" do Identity.make :identifier => valid_identifier identity = Identity.new :identifier => valid_identifier invalid identity end specify "that open_id must have the correct format" do identity = Identity.new identity.open_id = invalid_open_id invalid identity end specify "that open_id uniqueness extends to identifier when validated" do Identity.make :identifier => valid_identifier identity = Identity.new :open_id => "test.example.com" invalid identity end protected def hundred_character_identifier "http://This.is.a.hundred.character.description.one.hundred.chatacters.is.not.too.long.but.it.of.com/" end def hundred_and_one_character_identifier "http://This.is.a.hundred1.character.description.one.hundred.chatacters.is.not.too.long.but.it.of.com/" end def invalid_open_id "bad_id" end end class IdentityOpenIdShouldSetIdentifierTest < ActiveSupport::TestCase specify "that open_id sets identifier" do identity = Identity.new assert_nil identity.identifier identity.open_id = "test.example.com" assert_not_nil identity.identifier end specify "that identifier sets open_id" do identity = Identity.new assert_nil identity.open_id identity.identifier = "http://test.example.com/" assert_not_nil identity.open_id end specify "that open_id is nicely formatted for display" do identity = Identity.new :identifier => "http://test.example.com/" assert_equal valid_open_id, identity.open_id end end class IdentityShouldBeImmutableTest < ActiveSupport::TestCase specify "that open_id is immutable" do identity = Identity.make assert_raise RuntimeError do identity.open_id = "monkey.example.com" end end specify "that identifier is immutable" do identity = Identity.make assert_raise RuntimeError do identity.identifier = "http://monkey.example.com/" end end end class IdentityShouldBeFoundByOpenID < ActiveSupport::TestCase specify "that open_id can be used to find a record" do identity = Identity.make :identifier => valid_identifier identity_from_db = Identity.find_by_open_id valid_open_id assert_equal identity.identifier, identity.identifier end specify "that display formatted open_id can be used to find a record" do identity = Identity.make :identifier => valid_identifier identity_from_db = Identity.find_by_open_id valid_identifier assert_equal identity.identifier, identity.identifier end end |
Here is the code that passes the test
./test/test_helper.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | ENV["RAILS_ENV"] = "test" require File.expand_path(File.dirname(__FILE__) + "/../config/environment") require File.expand_path(File.dirname(__FILE__) + "/blueprints") require 'test_help' require 'faker' class ActiveSupport::TestCase self.use_transactional_fixtures = true self.use_instantiated_fixtures = false def invalid model assert !model.valid? end def valid model assert model.valid? end def validation_message_for model, column, message invalid model assert_equal message, model.errors.on(column) end def valid_identifier "http://test.example.com/" end def valid_open_id "test.example.com" end end def specify *args, &block test(*args, &block) end |
./app/models/identity.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | class Identity < ActiveRecord::Base include OpenIdAuthentication validates_presence_of :identifier, :message => "can not be blank." validates_uniqueness_of :identifier, :message => "is already in use, please use another." validates_length_of :identifier, :maximum => 100, :allow_nil => true, :message => "is too long." validate_on_create :valid_id def open_id return nil if self.identifier.nil? self.identifier[7..-2] end def identifier= identifier raise "Cannot update identifier." unless new_record? set_identifier identifier end def open_id= open_id raise "Cannot update open_id." unless new_record? set_identifier open_id end def valid_id errors.add(:identifier, "is not a valid Open ID.") if @invalid_id end def self.find_by_open_id open_id id = OpenIdAuthentication::normalize_identifier(open_id) find_by_identifier open_id end protected def normalize_id id begin identifier = normalize_identifier(id) rescue OpenIdAuthentication::InvalidOpenId identifier = id @invalid_id = true end identifier end def set_identifier id write_attribute(:identifier, normalize_id(id)) end end |
./test/blueprints.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | require 'machinist' # if you installed machinist from gem Chore.blueprint do description "Put the dirty clothes in the hamper" end Identity.blueprint do identifier "http://#{Faker::Name.first_name}.#{Faker::Internet.domain_name}/" end Child.blueprint do child { Identity.make } parent { Identity.make } end |
This one took a little work to do and I’m going to revisit it in a little while to see if it needs any refactoring.