wayne-friendly 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (136) hide show
  1. data/.document +2 -0
  2. data/.gitignore +26 -0
  3. data/APACHE-LICENSE +202 -0
  4. data/CHANGELOG.md +28 -0
  5. data/CONTRIBUTORS.md +7 -0
  6. data/LICENSE +20 -0
  7. data/README.md +288 -0
  8. data/Rakefile +68 -0
  9. data/TODO.md +5 -0
  10. data/VERSION +1 -0
  11. data/examples/friendly.yml +7 -0
  12. data/friendly.gemspec +240 -0
  13. data/lib/friendly.rb +58 -0
  14. data/lib/friendly/associations.rb +7 -0
  15. data/lib/friendly/associations/association.rb +34 -0
  16. data/lib/friendly/associations/set.rb +37 -0
  17. data/lib/friendly/attribute.rb +98 -0
  18. data/lib/friendly/boolean.rb +10 -0
  19. data/lib/friendly/cache.rb +24 -0
  20. data/lib/friendly/cache/by_id.rb +33 -0
  21. data/lib/friendly/data_store.rb +73 -0
  22. data/lib/friendly/document.rb +70 -0
  23. data/lib/friendly/document/associations.rb +50 -0
  24. data/lib/friendly/document/attributes.rb +114 -0
  25. data/lib/friendly/document/convenience.rb +41 -0
  26. data/lib/friendly/document/mixin.rb +15 -0
  27. data/lib/friendly/document/scoping.rb +66 -0
  28. data/lib/friendly/document/storage.rb +63 -0
  29. data/lib/friendly/document_table.rb +56 -0
  30. data/lib/friendly/index.rb +73 -0
  31. data/lib/friendly/indexer.rb +50 -0
  32. data/lib/friendly/memcached.rb +48 -0
  33. data/lib/friendly/newrelic.rb +6 -0
  34. data/lib/friendly/query.rb +42 -0
  35. data/lib/friendly/scope.rb +100 -0
  36. data/lib/friendly/scope_proxy.rb +43 -0
  37. data/lib/friendly/sequel_monkey_patches.rb +34 -0
  38. data/lib/friendly/storage.rb +31 -0
  39. data/lib/friendly/storage_factory.rb +24 -0
  40. data/lib/friendly/storage_proxy.rb +111 -0
  41. data/lib/friendly/table.rb +15 -0
  42. data/lib/friendly/table_creator.rb +50 -0
  43. data/lib/friendly/time.rb +14 -0
  44. data/lib/friendly/translator.rb +33 -0
  45. data/lib/friendly/uuid.rb +148 -0
  46. data/lib/tasks/friendly.rake +7 -0
  47. data/rails/init.rb +3 -0
  48. data/spec/config.yml.example +7 -0
  49. data/spec/fakes/data_store_fake.rb +29 -0
  50. data/spec/fakes/database_fake.rb +12 -0
  51. data/spec/fakes/dataset_fake.rb +28 -0
  52. data/spec/fakes/document.rb +18 -0
  53. data/spec/fakes/serializer_fake.rb +12 -0
  54. data/spec/fakes/time_fake.rb +12 -0
  55. data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
  56. data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
  57. data/spec/integration/batch_insertion_spec.rb +29 -0
  58. data/spec/integration/convenience_api_spec.rb +25 -0
  59. data/spec/integration/count_spec.rb +12 -0
  60. data/spec/integration/default_value_spec.rb +30 -0
  61. data/spec/integration/dirty_tracking_spec.rb +43 -0
  62. data/spec/integration/find_via_cache_spec.rb +101 -0
  63. data/spec/integration/finder_spec.rb +71 -0
  64. data/spec/integration/has_many_spec.rb +18 -0
  65. data/spec/integration/index_spec.rb +57 -0
  66. data/spec/integration/named_scope_spec.rb +34 -0
  67. data/spec/integration/offline_indexing_spec.rb +53 -0
  68. data/spec/integration/pagination_spec.rb +63 -0
  69. data/spec/integration/scope_chaining_spec.rb +22 -0
  70. data/spec/integration/table_creator_spec.rb +69 -0
  71. data/spec/integration/write_through_cache_spec.rb +53 -0
  72. data/spec/spec.opts +1 -0
  73. data/spec/spec_helper.rb +105 -0
  74. data/spec/unit/associations/association_spec.rb +57 -0
  75. data/spec/unit/associations/set_spec.rb +43 -0
  76. data/spec/unit/attribute_spec.rb +125 -0
  77. data/spec/unit/cache_by_id_spec.rb +102 -0
  78. data/spec/unit/cache_spec.rb +21 -0
  79. data/spec/unit/data_store_spec.rb +201 -0
  80. data/spec/unit/document/attributes_spec.rb +130 -0
  81. data/spec/unit/document_spec.rb +318 -0
  82. data/spec/unit/document_table_spec.rb +126 -0
  83. data/spec/unit/friendly_spec.rb +25 -0
  84. data/spec/unit/index_spec.rb +196 -0
  85. data/spec/unit/memcached_spec.rb +114 -0
  86. data/spec/unit/query_spec.rb +104 -0
  87. data/spec/unit/scope_proxy_spec.rb +44 -0
  88. data/spec/unit/scope_spec.rb +113 -0
  89. data/spec/unit/storage_factory_spec.rb +59 -0
  90. data/spec/unit/storage_proxy_spec.rb +244 -0
  91. data/spec/unit/translator_spec.rb +91 -0
  92. data/website/index.html +210 -0
  93. data/website/scripts/clipboard.swf +0 -0
  94. data/website/scripts/shBrushAS3.js +61 -0
  95. data/website/scripts/shBrushBash.js +66 -0
  96. data/website/scripts/shBrushCSharp.js +67 -0
  97. data/website/scripts/shBrushColdFusion.js +102 -0
  98. data/website/scripts/shBrushCpp.js +99 -0
  99. data/website/scripts/shBrushCss.js +93 -0
  100. data/website/scripts/shBrushDelphi.js +57 -0
  101. data/website/scripts/shBrushDiff.js +43 -0
  102. data/website/scripts/shBrushErlang.js +54 -0
  103. data/website/scripts/shBrushGroovy.js +69 -0
  104. data/website/scripts/shBrushJScript.js +52 -0
  105. data/website/scripts/shBrushJava.js +59 -0
  106. data/website/scripts/shBrushJavaFX.js +60 -0
  107. data/website/scripts/shBrushPerl.js +74 -0
  108. data/website/scripts/shBrushPhp.js +91 -0
  109. data/website/scripts/shBrushPlain.js +35 -0
  110. data/website/scripts/shBrushPowerShell.js +76 -0
  111. data/website/scripts/shBrushPython.js +66 -0
  112. data/website/scripts/shBrushRuby.js +57 -0
  113. data/website/scripts/shBrushScala.js +53 -0
  114. data/website/scripts/shBrushSql.js +68 -0
  115. data/website/scripts/shBrushVb.js +58 -0
  116. data/website/scripts/shBrushXml.js +71 -0
  117. data/website/scripts/shCore.js +30 -0
  118. data/website/scripts/shLegacy.js +30 -0
  119. data/website/styles/friendly.css +103 -0
  120. data/website/styles/help.png +0 -0
  121. data/website/styles/ie.css +35 -0
  122. data/website/styles/magnifier.png +0 -0
  123. data/website/styles/page_white_code.png +0 -0
  124. data/website/styles/page_white_copy.png +0 -0
  125. data/website/styles/print.css +29 -0
  126. data/website/styles/printer.png +0 -0
  127. data/website/styles/screen.css +257 -0
  128. data/website/styles/shCore.css +330 -0
  129. data/website/styles/shThemeDefault.css +173 -0
  130. data/website/styles/shThemeDjango.css +176 -0
  131. data/website/styles/shThemeEclipse.css +190 -0
  132. data/website/styles/shThemeEmacs.css +175 -0
  133. data/website/styles/shThemeFadeToGrey.css +177 -0
  134. data/website/styles/shThemeMidnight.css +175 -0
  135. data/website/styles/shThemeRDark.css +175 -0
  136. metadata +337 -0
@@ -0,0 +1,63 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+ require 'active_support/core_ext'
3
+
4
+ describe "Paginating" do
5
+ before do
6
+ User.all(:name => "Fred").each { |u| u.destroy }
7
+ @users = (0...15).map do |i|
8
+ User.create(:name => "Fred",
9
+ :created_at => i.hours.ago)
10
+ end
11
+ end
12
+
13
+ describe "fetching the nil page" do
14
+ before do
15
+ @found = User.paginate(:name => "Fred",
16
+ :order! => :created_at.desc,
17
+ :page! => nil,
18
+ :per_page! => 5)
19
+ end
20
+
21
+ it "returns the first :per_page results" do
22
+ @found.should == @users.slice(0,5)
23
+ end
24
+
25
+ it "returns an instance of WillPaginate::Collection" do
26
+ @found.should be_instance_of(WillPaginate::Collection)
27
+ end
28
+ end
29
+
30
+ describe "fetching a page by number" do
31
+ before do
32
+ @found = User.paginate(:name => "Fred",
33
+ :order! => :created_at.desc,
34
+ :page! => 2,
35
+ :per_page! => 5)
36
+ end
37
+
38
+ it "returns the :per_page results starting at offset :per_page * page" do
39
+ @found.should == @users.slice(5,5)
40
+ end
41
+
42
+ it "returns an instance of WillPaginate::Collection" do
43
+ @found.should be_instance_of(WillPaginate::Collection)
44
+ end
45
+ end
46
+
47
+ describe "when :page is a string" do
48
+ before do
49
+ @found = User.paginate(:name => "Fred",
50
+ :order! => :created_at.desc,
51
+ :page! => "2",
52
+ :per_page! => 5)
53
+ end
54
+
55
+ it "returns the :per_page results starting at offset :per_page * page" do
56
+ @found.should == @users.slice(5,5)
57
+ end
58
+
59
+ it "returns an instance of WillPaginate::Collection" do
60
+ @found.should be_instance_of(WillPaginate::Collection)
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+ require "active_support/core_ext"
3
+
4
+ describe "Chaining scopes together" do
5
+ describe "then calling #all" do
6
+ before do
7
+ User.all(:name => "Quagmire").each { |q| q.destroy }
8
+ @users = (0...10).map do |i|
9
+ User.create :name => "Quagmire",
10
+ :created_at => i.hours.ago
11
+ end
12
+ end
13
+
14
+ it "queries using a combination of both scopes" do
15
+ User.named_quagmire.recent.all.should == @users.slice(0, 3)
16
+ end
17
+
18
+ it "gives scopes on the right priority" do
19
+ User.named_joe.named_quagmire.first.name.should == "Quagmire"
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,69 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Creating the tables for a model" do
4
+ before do
5
+ @klass = Class.new do
6
+ include Friendly::Document
7
+
8
+ def self.table_name; "stuffs"; end
9
+
10
+ attribute :name, String
11
+ attribute :user_id, Friendly::UUID
12
+
13
+ indexes [:name, :created_at]
14
+ indexes :user_id
15
+ end
16
+
17
+ @klass.create_tables!
18
+ @schema = Friendly.db.schema(:stuffs)
19
+ @unique_id_index = Friendly.db.indexes(:stuffs)
20
+ @table = Hash[*@schema.map { |s| [s.first, s.last] }.flatten]
21
+ @index_schema = Friendly.db.schema(:index_stuffs_on_name_and_created_at)
22
+ @index_table = Hash[*@index_schema.map { |s| [s.first, s.last] }.flatten]
23
+ @id_idx_schema = Friendly.db.schema(:index_stuffs_on_user_id)
24
+ @id_index_table = Hash[*@id_idx_schema.map { |s| [s.first, s.last] }.flatten]
25
+ end
26
+
27
+ after do
28
+ Friendly.db.drop_table(:stuffs)
29
+ Friendly.db.drop_table(:index_stuffs_on_name_and_created_at)
30
+ end
31
+
32
+ it "creates a table for the document" do
33
+ @table.keys.length.should == 5
34
+ @table[:added_id][:db_type].should == "int(11)"
35
+ @table[:added_id][:primary_key].should be_true
36
+ @table[:id][:db_type].should == "binary(16)"
37
+ @table[:attributes][:db_type].should == "text"
38
+ @table[:created_at][:db_type].should == "datetime"
39
+ @table[:updated_at][:db_type].should == "datetime"
40
+
41
+ @unique_id_index.keys.length.should == 1
42
+ @unique_id_index[:id][:columns].should == [:id]
43
+ @unique_id_index[:id][:unique].should be_true
44
+ end
45
+
46
+ it "creates a table for each index" do
47
+ @index_table.keys.length.should == 3
48
+ @index_table[:name][:db_type].should == "varchar(255)"
49
+ @index_table[:name][:primary_key].should be_true
50
+ @index_table[:created_at][:db_type].should == "datetime"
51
+ @index_table[:created_at][:primary_key].should be_true
52
+ @index_table[:id][:db_type].should == "binary(16)"
53
+ @index_table[:id][:primary_key].should be_true
54
+ end
55
+
56
+ it "knows how to create an index for a field of a custom type" do
57
+ @id_index_table.keys.length.should == 2
58
+ @id_index_table[:user_id][:db_type].should == "binary(16)"
59
+ @id_index_table[:user_id][:primary_key].should be_true
60
+ @id_index_table[:id][:db_type].should == "binary(16)"
61
+ @id_index_table[:id][:primary_key].should be_true
62
+ end
63
+
64
+ it "doesn't raise if the tables already exist" do
65
+ lambda {
66
+ @klass.create_tables!
67
+ }.should_not raise_error
68
+ end
69
+ end
@@ -0,0 +1,53 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Writing through to cache on create" do
4
+ before do
5
+ @address = Address.create :street => "Spooner"
6
+ end
7
+
8
+ it "writes through to memcache using the model street and guid as cache key" do
9
+ $cache.get("Address/0/#{@address.id.to_guid}").should == @address
10
+ end
11
+ end
12
+
13
+ describe "Writing with a version number" do
14
+ before do
15
+ Address.storage_proxy.caches.clear
16
+ Address.caches_by :id, :version => 1
17
+ @address = Address.create :street => "Spooner"
18
+ end
19
+
20
+ after do
21
+ Address.storage_proxy.caches.clear
22
+ Address.caches_by :id
23
+ end
24
+
25
+ it "adds the version number to the cache key" do
26
+ $cache.get("Address/1/#{@address.id.to_guid}").should == @address
27
+ end
28
+ end
29
+
30
+ describe "Writing through to cache on update" do
31
+ before do
32
+ @address = Address.create :street => "Spooner"
33
+ @address.street = "Joe"
34
+ @address.save
35
+ end
36
+
37
+ it "writes through to memcache using the model street and guid as cache key" do
38
+ $cache.get("Address/0/#{@address.id.to_guid}").street.should == @address.street
39
+ end
40
+ end
41
+
42
+ describe "Writing through to cache on destroy" do
43
+ before do
44
+ @address = Address.create :street => "Spooner"
45
+ @address.destroy
46
+ end
47
+
48
+ it "removes the object from cache" do
49
+ lambda {
50
+ $cache.get("Address/0/#{@address.id.to_guid}")
51
+ }.should raise_error(Memcached::NotFound)
52
+ end
53
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color --backtrace
@@ -0,0 +1,105 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ Dir[File.expand_path(File.dirname(__FILE__)) + "/fakes/*.rb"].each do |f|
4
+ require f
5
+ end
6
+ require 'rubygems'
7
+ require 'spec'
8
+ require 'spec/autorun'
9
+ require 'sequel'
10
+ require 'json/pure'
11
+ gem 'jferris-mocha'
12
+ require 'mocha'
13
+ require 'memcached'
14
+ require 'friendly'
15
+
16
+ config = YAML.load(File.read(File.dirname(__FILE__) + "/config.yml"))['test']
17
+ Friendly.configure config
18
+ $db = Friendly.db
19
+ Sequel::MySQL.default_engine = "InnoDB"
20
+
21
+ $db.drop_table :users if $db.table_exists?("users")
22
+ $db.drop_table :index_users_on_name if $db.table_exists?("index_users_on_name")
23
+ if $db.table_exists?("index_users_on_name_and_created_at")
24
+ $db.drop_table :index_users_on_name_and_created_at
25
+ end
26
+
27
+ datastore = Friendly::DataStore.new($db)
28
+ Friendly.datastore = datastore
29
+
30
+ $cache = Memcached.new
31
+ Friendly.cache = Friendly::Memcached.new($cache)
32
+
33
+ class User
34
+ include Friendly::Document
35
+
36
+ attribute :name, String
37
+ attribute :age, Integer
38
+ attribute :happy, Friendly::Boolean, :default => true
39
+ attribute :sad, Friendly::Boolean, :default => false
40
+ attribute :friend, Friendly::UUID
41
+
42
+ indexes :happy
43
+ indexes :friend
44
+ indexes :name
45
+ indexes :name, :created_at
46
+
47
+ named_scope :named_joe, :name => "Joe"
48
+ named_scope :named_quagmire, :name => "Quagmire"
49
+ named_scope :recent, :order! => :created_at.desc,
50
+ :limit! => 3
51
+
52
+ has_many :addresses
53
+ has_many :addresses_override, :class_name => "Address",
54
+ :foreign_key => :user_id
55
+ end
56
+
57
+ User.create_tables!
58
+
59
+ class Address
60
+ include Friendly::Document
61
+
62
+ attribute :user_id, Friendly::UUID
63
+ attribute :street, String
64
+
65
+ indexes :user_id
66
+ indexes :street
67
+ indexes :created_at
68
+
69
+ caches_by :id
70
+ end
71
+
72
+ Address.create_tables!
73
+
74
+ module Mocha
75
+ module API
76
+ def setup_mocks_for_rspec
77
+ mocha_setup
78
+ end
79
+ def verify_mocks_for_rspec
80
+ mocha_verify
81
+ end
82
+ def teardown_mocks_for_rspec
83
+ mocha_teardown
84
+ end
85
+ end
86
+ end
87
+
88
+ module Factory
89
+ def row(opts = {})
90
+ { :id => 1, :created_at => Time.new, :updated_at => Time.new }.merge(opts)
91
+ end
92
+
93
+ def query(conditions)
94
+ stub(:order => conditions.delete(:order!),
95
+ :limit => conditions.delete(:limit!),
96
+ :preserve_order? => conditions.delete(:preserve_order!),
97
+ :conditions => conditions,
98
+ :offset => conditions.delete(:offset!))
99
+ end
100
+ end
101
+
102
+ Spec::Runner.configure do |config|
103
+ config.mock_with Mocha::API
104
+ config.include Factory
105
+ end
@@ -0,0 +1,57 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::Associations::Association" do
4
+ before do
5
+ @owner_klass = stub(:name => "User")
6
+ @klass = stub
7
+ # FIXME: ugh.
8
+ String.any_instance.stubs(:constantize).returns(@klass)
9
+ @assoc_klass = Friendly::Associations::Association
10
+ end
11
+
12
+ describe "with defaults" do
13
+ before do
14
+ @association = @assoc_klass.new(@owner_klass, :addresses)
15
+ end
16
+
17
+ it "has a default klass of name.classify.constantize" do
18
+ @association.klass.should == @klass
19
+ end
20
+
21
+ it "has a foreign_key of owner_klass.name.singularize + '_id'" do
22
+ @association.foreign_key.should == :user_id
23
+ end
24
+
25
+ it "returns a scope on klass of {:foreign_key => document.id}" do
26
+ @scope = stub
27
+ @klass.stubs(:scope).with(:user_id => 42).returns(@scope)
28
+
29
+ @association.scope(stub(:id => 42)).should == @scope
30
+ end
31
+ end
32
+
33
+ describe "with overridden attributes" do
34
+ before do
35
+ @klass = stub
36
+ @class_name = "SomeOtherClass"
37
+ @class_name.stubs(:constantize).returns(@klass)
38
+ @association = @assoc_klass.new @owner_klass, :whatever,
39
+ :class_name => @class_name,
40
+ :foreign_key => :other_id
41
+ end
42
+
43
+ it "uses the overridden class_name to get the class" do
44
+ @association.klass.should == @klass
45
+ end
46
+
47
+ it "uses the overridden foreign_key" do
48
+ @association.foreign_key.should == :other_id
49
+ end
50
+
51
+ it "uses the override foreign_key in the scope" do
52
+ @scope = stub
53
+ @klass.stubs(:scope).with(:other_id => 42).returns(@scope)
54
+ @association.scope(stub(:id => 42)).should == @scope
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,43 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::Associations::Set" do
4
+ before do
5
+ @klass = Class.new
6
+ @association_klass = stub
7
+ @set = Friendly::Associations::Set.new(@klass, @association_klass)
8
+ @assoc = stub
9
+ @association_klass.stubs(:new).
10
+ with(@klass, :my_awesome_association, {}).returns(@assoc)
11
+ end
12
+
13
+ describe "adding an association" do
14
+ before do
15
+ @set.add(:my_awesome_association)
16
+ end
17
+
18
+ it "creates an association and adds it to its hash by name" do
19
+ @set.associations[:my_awesome_association].should == @assoc
20
+ end
21
+
22
+ it "adds an instance method to the klass through which to acces the assoc" do
23
+ @doc = @klass.new
24
+ @scope = stub
25
+ @klass.stubs(:association_set).returns(@set)
26
+ @assoc.stubs(:scope).with(@doc).returns(@scope)
27
+ @doc.my_awesome_association.should == @scope
28
+ end
29
+ end
30
+
31
+ it "can return the association by name" do
32
+ @set.add(:my_awesome_association)
33
+ @set.get(:my_awesome_association).should == @assoc
34
+ end
35
+
36
+ it "provides the scope for an association by name" do
37
+ @doc = stub
38
+ @scope = stub
39
+ @assoc.stubs(:scope).with(@doc).returns(@scope)
40
+ @set.add(:my_awesome_association)
41
+ @set.get_scope(:my_awesome_association, @doc).should == @scope
42
+ end
43
+ end
@@ -0,0 +1,125 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "Friendly::Attribute" do
4
+ before do
5
+ @klass = Class.new { def will_change(a); end }
6
+ @name = Friendly::Attribute.new(@klass, :name, String)
7
+ @id = Friendly::Attribute.new(@klass, :id, Friendly::UUID)
8
+ @no_type = Friendly::Attribute.new(@klass, :no_type, nil)
9
+ @default = Friendly::Attribute.new(@klass, :default, String, :default => "asdf")
10
+ @false = Friendly::Attribute.new(@klass, :false, String, :default => false)
11
+ @klass.stubs(:attributes).returns({:name => @name,
12
+ :id => @id,
13
+ :default => @default,
14
+ :false => @false})
15
+ @object = @klass.new
16
+ end
17
+
18
+ it "creates a getting on klass that notifies it of a change" do
19
+ @object.stubs(:will_change)
20
+ @object.name = "Something"
21
+ @object.should have_received(:will_change).with(:name)
22
+ end
23
+
24
+ it "creates a getter on klass" do
25
+ @object.name = "Something"
26
+ @object.name.should == "Something"
27
+ end
28
+
29
+ it "creates an 'attr_was' getter" do
30
+ @object.instance_variable_set(:@name_was, "Joe the Plumber")
31
+ @object.name_was.should == "Joe the Plumber"
32
+ end
33
+
34
+ it "creates an attr_changed? query method" do
35
+ @object.stubs(:attribute_changed?).with(:name).returns(true)
36
+ @object.should be_name_changed
37
+ end
38
+
39
+ it "typecasts values using the converter function" do
40
+ uuid = Friendly::UUID.new
41
+ @id.typecast(uuid.to_s).should == uuid
42
+ end
43
+
44
+ it "doesn't typecast values if they are of the right type" do
45
+ uuid = Friendly::UUID.new
46
+ @id.typecast(uuid).should == uuid
47
+ end
48
+
49
+ it "raises a useful error if it can't typecast" do
50
+ attribute = Friendly::Attribute.new(@klass, :weird, Class)
51
+ lambda {
52
+ attribute.typecast("ASDF")
53
+ }.should raise_error(Friendly::NoConverterExists)
54
+ end
55
+
56
+ it "has a default value of type.new" do
57
+ @id.default.should be_instance_of(Friendly::UUID)
58
+ end
59
+
60
+ it "has a default of nil if the type doesn't respond to :new" do
61
+ Friendly::Attribute.new(@klass, :age, Integer).default.should be_nil
62
+ end
63
+
64
+ it "doesn't try to convert when there's no type" do
65
+ @no_type.typecast(true).should == true
66
+ end
67
+
68
+ it "can have a default value" do
69
+ @default.default.should == "asdf"
70
+ @obj = @klass.new
71
+ @default.assign_default_value(@obj)
72
+ @obj.default.should == "asdf"
73
+ end
74
+
75
+ it "has a default value even if it's false" do
76
+ @false.default.should be_false
77
+ end
78
+
79
+ it "knows how to assign its own default" do
80
+ @object = stub(:false= => nil)
81
+ @false.assign_default_value(@object)
82
+ @object.should have_received(:false=).with(false)
83
+ end
84
+
85
+ describe "registering a type" do
86
+ before do
87
+ @klass = Class.new
88
+ Friendly::Attribute.register_type(@klass, "binary(16)") { |t| t.to_i }
89
+ end
90
+
91
+ after { Friendly::Attribute.deregister_type(@klass) }
92
+
93
+ it "tells Attribute about the sql_type" do
94
+ Friendly::Attribute.sql_type(@klass).should == "binary(16)"
95
+ end
96
+
97
+ it "registers the conversion method" do
98
+ Friendly::Attribute.new(@klass, :something, @klass).convert("1").should == 1
99
+ end
100
+
101
+ it "is custom_type?(@klass)" do
102
+ Friendly::Attribute.should be_custom_type(@klass)
103
+ end
104
+ end
105
+
106
+ describe "deregistering a type" do
107
+ before do
108
+ @klass = Class.new
109
+ Friendly::Attribute.register_type(@klass, "whatever") {}
110
+ Friendly::Attribute.deregister_type(@klass)
111
+ end
112
+
113
+ it "removes the converter method" do
114
+ Friendly::Attribute.converters.should_not be_has_key(@klass)
115
+ end
116
+
117
+ it "removes the sql_type" do
118
+ Friendly::Attribute.sql_types.should_not be_has_key(@klass)
119
+ end
120
+
121
+ it "is not custom_type?(@klass)" do
122
+ Friendly::Attribute.should_not be_custom_type(@klass)
123
+ end
124
+ end
125
+ end