Valuable 0.8

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.
@@ -0,0 +1,169 @@
1
+ Introducing Valuable
2
+ ====================
3
+
4
+ Valuable enables quick modeling... it's attr_accessor on steroids. It intends to use a simple and intuitive interface, allowing you easily create models without hassles, so you can get on with the logic specific to your application. I find myself using it in sort of a presenter capacity, when I have to pull data from non-standard data sources, and to handle temporary data during imports.
5
+
6
+ Valuable provides DRY decoration like attr_accessor, but includes default values, light weight type casting and a constructor that accepts an attributes hash. It provides a class-level list of attributes, an instance-level attributes hash, and more.
7
+
8
+ Examples
9
+ -------
10
+
11
+ _basic syntax_
12
+
13
+ class Fruit
14
+ has_value :name
15
+ has_collection :vitamins
16
+ end
17
+
18
+ _constructor accepts an attributes hash_
19
+
20
+ >> apple = Fruit.new(:name => 'Apple')
21
+
22
+ >> apple.name
23
+ => 'Apple'
24
+
25
+ >> apple.vitamins
26
+ => []
27
+
28
+ _default values_
29
+
30
+ class Developer
31
+ has_value :name
32
+ has_value :nickname, :default => 'mort'
33
+ end
34
+
35
+ >> dev = Developer.new(:name => 'zk')
36
+
37
+ >> dev.name
38
+ => 'zk'
39
+
40
+ >> dev.nickname
41
+ => 'mort'
42
+
43
+ _setting a value to nil overrides the default._
44
+
45
+ >> Developer.new(:name => 'KDD', :nickname => nil).nickname
46
+ => nil
47
+
48
+ _light weight type casting_
49
+
50
+ class BaseballPlayer < Valuable
51
+
52
+ has_value :at_bats, :klass => :integer
53
+ has_value :hits, :klass => :integer
54
+
55
+ def average
56
+ hits/at_bats.to_f if hits && at_bats
57
+ end
58
+ end
59
+
60
+ >> joe = BaseballPlayer.new(:hits => '5', :at_bats => '20', :on_drugs => '0' == '1')
61
+
62
+ >> joe.at_bats
63
+ => 20
64
+
65
+ >> joe.average
66
+ => 0.25
67
+
68
+ _I find myself using classes to format things... ( PhoneNumber is provided in `/examples` )_
69
+
70
+ class School < Valuable
71
+ has_value :name
72
+ has_value :phone, :klass => PhoneNumber
73
+ end
74
+
75
+ >> School.new(:name => 'Vanderbilt', :phone => '3332223333').phone
76
+ => '(333) 222-3333'
77
+
78
+ _as a presenter in Rails_
79
+
80
+ class CalenderPresenter < Valuable
81
+ has_value :month, :klass => Integer, :default => Time.now.month
82
+ has_value :year, :klass => Integer, :default => Time.now.year
83
+
84
+ def start_date
85
+ Date.civil( year, month, 1)
86
+ end
87
+
88
+ def end_date
89
+ Date.civil( year, month, -1) #strange I know
90
+ end
91
+
92
+ def events
93
+ Event.find(:all, :conditions => event_conditions)
94
+ end
95
+
96
+ def event_conditions
97
+ ['starts_at between ? and ?', start_date, end_date]
98
+ end
99
+ end
100
+
101
+ _this class might appear in a controller like this:_
102
+
103
+ class CalendarController < ApplicationController
104
+ def show
105
+ @presenter = CalendarPresenter.new(params[:calendar])
106
+ end
107
+ end
108
+
109
+ _but it's easier to understand like this:_
110
+
111
+ >> @presenter = CalendarPresenter.new({}) # first pageload
112
+
113
+ >> @presenter.start_date
114
+ => Tue, 01 Dec 2009
115
+
116
+ >> @presenter.end_date
117
+ => Thu, 31 Dec 2009
118
+
119
+ >> # User selects some other month and year; the next request looks like...
120
+
121
+ >> @presenter = CalendarPresenter.new({:month => '2', :year => '2002'})
122
+
123
+ >> @presenter.start_date
124
+ => Fri, 01 Feb 2002
125
+
126
+ >> @presenter.end_date
127
+ => Thu, 28 Feb 2002
128
+
129
+ ...
130
+
131
+ So, if you're reading this, you're probably thinking, "I could have done that!" Yes, it's true. I'll happily agree that it's a relatively simple tool if you'll agree that it lets you model a calendar with an intuitive syntax, prevents you from writing yet another obvious constructor, and allows you to keep your brain focused on your app.
132
+
133
+ _you can access the attributes via the attributes hash. Only default and specified attributes will have entries here._
134
+
135
+ class Person < Valuable
136
+ has_value :name
137
+ has_value :is_developer, :default => false
138
+ has_value :ssn
139
+ end
140
+
141
+ >> elvis = Person.new(:name => 'The King')
142
+
143
+ >> elvis.attributes
144
+ => {:name=>"The King", :is_developer=>false}
145
+
146
+ >> elvis.attributes[:name]
147
+ => "The King"
148
+
149
+ >> elvis.ssn
150
+ => nil
151
+
152
+ _also, you can get a list of all the defined attributes from the class_
153
+
154
+ >> Person.attributes
155
+ => [:name, :is_developer, :ssn]
156
+
157
+ Default Values
158
+ --------------
159
+ Default values are used when no value is provided to the constructor. If the value nil is provided, nil will be used instead of the default.
160
+
161
+ When a default value and a klass are specified, the default value will NOT be cast to type klass -- you must do it.
162
+
163
+ If a value having a default is set to null after it is constructed, it will NOT be set to the default.
164
+
165
+ If there is no default value, the result will be nil, EVEN if type casting is provided. Thus, a field typically cast as an Integer can be nil. See calculation of average.
166
+
167
+ KLASS-ification
168
+ ---------------
169
+ `:integer`, `:string` and `:boolean` use `to_i`, `to_s` and `!!` respectively. All other klasses use `klass.new(value)` unless the value `is_a?(klass)`, in which case it is unmolested. Nils are never klassified. In the example above, hits, which is an integer, is `nil` if not set, rather than `nil.to_i = 0`.
@@ -0,0 +1,233 @@
1
+ # Valuable is the class from which all classes (who are so inclined)
2
+ # should inherit.
3
+ #
4
+ # ==Example:
5
+ #
6
+ # class Bus < Valuable
7
+ #
8
+ # has_value :number, :klass => :integer
9
+ # has_value :color, :default => 'yellow'
10
+ # has_collection :riders
11
+ #
12
+ # end
13
+ #
14
+ # >> Bus.attributes
15
+ # => [:number, :color, :riders]
16
+ # >> bus = Bus.new(:number => '3', :riders => ['GOF', 'Fowler', 'Mort']
17
+ # >> bus.attributes
18
+ # => {:number => 3, :riders => ['GOF', 'Fowler', 'Mort'], :color => 'yellow'}
19
+ class Valuable
20
+
21
+ # Returns a Hash representing all known values. Values are set three ways:
22
+ #
23
+ # (1) a default value
24
+ # (2) they were passed to the constructor
25
+ # Bus.new(:color => 'green')
26
+ # (3) they were set via their namesake setter
27
+ # bus.color = 'green'
28
+ #
29
+ # Values that have not been set and have no default not appear in this
30
+ # collection. Their namesake attribute methods will respond with nil.
31
+ # Always use symbols to access these values.
32
+ #
33
+ # >> bus = Bus.new(:number => 16) # color has default value 'yellow'
34
+ # >> bus.attributes
35
+ # => {:color => 'yellow', :number => 16}
36
+ def attributes
37
+ @attributes ||= deep_duplicate_of(self.class.defaults)
38
+ end
39
+
40
+ # accepts an optional hash that will be used to populate the
41
+ # predefined attributes for this class.
42
+ def initialize(atts = nil)
43
+ atts.each { |name, value| __send__("#{name}=", value ) } if atts
44
+ end
45
+
46
+ def deep_duplicate_of(value)
47
+ Marshal.load(Marshal.dump(value))
48
+ end
49
+
50
+ class << self
51
+
52
+ # Returns an array of the attributes available on this object.
53
+ def attributes
54
+ @attributes ||= []
55
+ end
56
+
57
+ # Returns a name/value set of the values that will be used on
58
+ # instanciation unless new values are provided.
59
+ #
60
+ # >> Bus.defaults
61
+ # => {:color => 'yellow'}
62
+ def defaults
63
+ @defaults ||= {}
64
+ end
65
+
66
+ # Decorator method that lets you specify the attributes for your
67
+ # model. It accepts an attribute name (a symbol) and an options
68
+ # hash. Valid options are :default, :klass and (when :klass is
69
+ # Boolean) :negative.
70
+ #
71
+ # :default - for the given attribute, use this value if no other
72
+ # is provided.
73
+ #
74
+ # :klass - light weight type casting. Use :integer, :string or
75
+ # :boolean. Alternately, supply a class.
76
+ #
77
+ # When a :klassified attribute is set to some new value, if the value
78
+ # is not nil and is not already of that class, the value will be cast
79
+ # to the specified klass. In the case of :integer, it wil be done via
80
+ # .to_i. In the case of a random other class, it will be done via
81
+ # Class.new(value). If the value is nil, it will not be cast.
82
+ #
83
+ # A good example: PhoneNumber < String is useful if you
84
+ # want numbers to come out the other end properly formatted, when your
85
+ # input may come in as an integer, or string without formatting, or
86
+ # string with bad formatting.
87
+ #
88
+ # IMPORTANT EXCEPTION
89
+ #
90
+ # Due to the way Rails handles checkboxes, '0' resolves to FALSE,
91
+ # though it would normally resolve to TRUE.
92
+ def has_value(name, options={})
93
+
94
+ name = name.to_sym
95
+
96
+ attributes << name
97
+
98
+ defaults[name] = options[:default] unless options[:default].nil?
99
+
100
+ create_accessor_for(name)
101
+ create_question_for(name) if options[:klass] == :boolean
102
+ create_negative_question_for(name, options[:negative]) if options[:klass] == :boolean && options[:negative]
103
+
104
+ create_setter_for(name, options[:klass], options[:default])
105
+
106
+ check_options_validity(name, options)
107
+ end
108
+
109
+ # Creates the method that sets the value of an attribute. This setter
110
+ # is called both by the constructor. The constructor handles type
111
+ # casting. Setting values via the attributes hash avoids the method
112
+ # defined here.
113
+ def create_setter_for(name, klass, default)
114
+
115
+ case klass
116
+ when NilClass
117
+
118
+ define_method "#{name}=" do |value|
119
+ attributes[name] = value
120
+ end
121
+
122
+ when :integer
123
+
124
+ define_method "#{name}=" do |value|
125
+ value_as_integer = value && value.to_i
126
+ attributes[name] = value_as_integer
127
+ end
128
+
129
+ when :string
130
+
131
+ define_method "#{name}=" do |value|
132
+ value_as_string = value && value.to_s
133
+ attributes[name] = value_as_string
134
+ end
135
+
136
+ when :boolean
137
+
138
+ define_method "#{name}=" do |value|
139
+ attributes[name] = value == '0' ? false : !!value
140
+ end
141
+
142
+ else
143
+
144
+ define_method "#{name}=" do |value|
145
+ if value.nil?
146
+ attributes[name] = nil
147
+ elsif value.is_a? klass
148
+ attributes[name] = value
149
+ else
150
+ attributes[name] = klass.new(value)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # creates a simple accessor method named after the attribute whose
157
+ # value it will provide during the life of the instance.
158
+ def create_accessor_for(name)
159
+ define_method name do
160
+ attributes[name]
161
+ end
162
+ end
163
+
164
+ # In addition to the normal getter and setter, boolean attributes
165
+ # get a method appended with a ?.
166
+ #
167
+ # class Player < Valuable
168
+ # has_value :free_agent, :klass => Boolean
169
+ # end
170
+ #
171
+ # juan = Player.new(:free_agent => true)
172
+ # >> juan.free_agent?
173
+ # => true
174
+ def create_question_for(name)
175
+ define_method "#{name}?" do
176
+ attributes[name]
177
+ end
178
+ end
179
+
180
+ # In some situations, the opposite of a value may be just as interesting.
181
+ #
182
+ # class Coder < Valuable
183
+ # has_value :agilist, :klass => Boolean, :negative => :waterfaller
184
+ # end
185
+ #
186
+ # monkey = Coder.new(:agilist => false)
187
+ # >> monkey.waterfaller?
188
+ # => true
189
+ def create_negative_question_for(name, negative)
190
+ define_method "#{negative}?" do
191
+ !attributes[name]
192
+ end
193
+ end
194
+
195
+ # this is a more intuitive way of marking an attribute as holding a
196
+ # collection.
197
+ #
198
+ # class Bus < Valuable
199
+ # has_value :riders, :default => [] # meh...
200
+ # has_collection :riders # better!
201
+ # end
202
+ #
203
+ # >> bus = Bus.new
204
+ # >> bus.riders << 'jack'
205
+ # >> bus.riders
206
+ # => ['jack']
207
+ def has_collection(name)
208
+ has_value(name, :default => [] )
209
+ end
210
+
211
+ private
212
+
213
+ def inherited(child)
214
+ attributes.each {|att| child.attributes << att }
215
+ defaults.each {|(name, value)| child.defaults[name] = value }
216
+ end
217
+
218
+ def known_options
219
+ [:klass, :default, :negative]
220
+ end
221
+
222
+ # this helper raises an exception if the options passed to has_value
223
+ # are wrong. Mostly written because I occasionally used :class instead
224
+ # of :klass and, being a moron, wasted time trying to find the issue.
225
+ def check_options_validity(name, options)
226
+ invalid_options = options.keys - known_options
227
+
228
+ raise ArgumentError, "has_value did not know how to respond to option(s) #{invalid_options.join(', ')}. Valid (optional) arguments are: #{known_options.join(', ')}" unless invalid_options.empty?
229
+ end
230
+
231
+ end
232
+
233
+ end
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'config.rb'
3
+
4
+ require 'rake'
5
+ require 'rake/rdoctask'
6
+ require 'rake/testtask'
7
+ require 'rake/packagetask'
8
+ require 'rake/gempackagetask'
9
+ require 'rake/contrib/rubyforgepublisher'
10
+
11
+ task :default => [:test]
12
+
13
+ PKG_FILE_NAME = "#{CONFIG[:name]}-#{CONFIG[:version]}"
14
+ RUBY_FORGE_PROJECT = 'valuable'
15
+ RUBY_FORGE_USER = 'mustmodify'
16
+
17
+ desc "Run unit tests"
18
+ Rake::TestTask.new("test") { |t|
19
+ t.pattern = 'test/*_test.rb'
20
+ t.verbose = true
21
+ t.warning = true
22
+ }
23
+
24
+ desc 'clean temporary files, rdoc, and gem package'
25
+ task :clean => [:clobber_package, :clobber_rdoc] do
26
+ temp_filenames = File.join('**', '*.*~')
27
+ temp_files = Dir.glob(temp_filenames)
28
+ if temp_files.empty?
29
+ puts 'no temp files to delete'
30
+ else
31
+ puts "deleting #{temp_files.size} temp files"
32
+ end
33
+
34
+ File.delete(*temp_files)
35
+ end
36
+
37
+ desc 'Generate documentation for the Valuable plugin'
38
+ Rake::RDocTask.new(:rdoc) do |rdoc|
39
+ rdoc.title = 'Valuable - light weight modeling'
40
+ rdoc.options << '--line-numbers'
41
+ rdoc.options << '--inline-source'
42
+ rdoc.rdoc_files.include('README.markdown')
43
+ rdoc.rdoc_files.include('lib/**/*.rb')
44
+ end
45
+
46
+ spec = Gem::Specification.new do |s|
47
+ s.name = CONFIG[:name]
48
+ s.version = CONFIG[:version]
49
+ s.platform = Gem::Platform::RUBY
50
+ s.summary = 'attr_accessor on steroids with defaults, constructor, and light casting.'
51
+ s.description = "Valuable is a ruby base class that is essentially attr_accessor on steroids. It intends to use a simple and intuitive interface, allowing you to get on with the logic specific to your application."
52
+
53
+ s.files = FileList["{lib, test, examples}/**/*"].to_a + %w( README.markdown rakefile.rb )
54
+ s.require_path = 'lib'
55
+ s.test_files = FileList["{test}/**/*test.rb"].to_a
56
+ s.has_rdoc = true
57
+
58
+ s.rubyforge_project = RUBY_FORGE_PROJECT
59
+ s.author = 'Johnathon Wright'
60
+ s.email = 'jw@mustmodify.com'
61
+ s.homepage = 'http://www.github.com/mustmodify/valuable'
62
+ end
63
+
64
+ Rake::GemPackageTask.new(spec) do |pkg|
65
+ #pkg.need_zip = true
66
+ #pkg.need_tar = true
67
+ end
68
+
69
+ desc "Publish the API documentation"
70
+ task :pdoc => [:rdoc] do
71
+ Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload
72
+ end
73
+
74
+ desc 'Publish the gem and API docs'
75
+ task :publish => [:pdoc, :rubyforge_upload]
76
+
@@ -0,0 +1,23 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'test/unit'
4
+ require 'valuable.rb'
5
+ require 'mocha'
6
+
7
+ class Infrastructure < Valuable
8
+ end
9
+
10
+ class BadAttributesTest < Test::Unit::TestCase
11
+
12
+ def test_that_has_value_grumbles_when_it_gets_bad_attributes
13
+ assert_raises ArgumentError do
14
+ Infrastructure.has_value :fu, :invalid => 'shut your mouth'
15
+ end
16
+ end
17
+
18
+ def test_that_valid_arguments_cause_no_grumbling
19
+ assert_nothing_raised do
20
+ Infrastructure.has_value :bar, :klass => Integer
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,80 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'test/unit'
4
+ require 'valuable.rb'
5
+ require 'mocha'
6
+
7
+ class Signature < String
8
+ end
9
+
10
+ class Cube < String
11
+ def initialize(number)
12
+ super "Lives in Cube #{number}"
13
+ end
14
+ end
15
+
16
+ class DevCertifications < Valuable
17
+ has_value :a_plus, :default => false
18
+ has_value :mcts, :default => false
19
+ has_value :hash_rocket, :default => false
20
+ end
21
+
22
+ class Dev < Valuable
23
+ has_value :has_exposure_to_sunlight, :default => false
24
+ has_value :mindset
25
+ has_value :name, :default => 'DHH Jr.', :klass => String
26
+ has_value :signature, :klass => Signature
27
+ has_value :cubical, :klass => Cube
28
+ has_value :hacker, :default => true
29
+ has_value :certifications, :default => DevCertifications.new
30
+ has_value :quote
31
+
32
+ has_collection :favorite_gems
33
+
34
+ end
35
+
36
+ # Previously, we used :klass => Klass instead of :klass => :klass.
37
+ # I decided it was just plain dirty. On refactoring, I realized that
38
+ # most it would continue to work. Other stuff, unfortunately, would
39
+ # break horribly. (Integer.new, for instance, makes Ruby very angry.)
40
+ # The purpose of these tests is to verify that everything _either_
41
+ # breaks horribly or works, where the third option is fails silently
42
+ # and mysteriously.
43
+ class DeprecatedTest < Test::Unit::TestCase
44
+
45
+ def test_that_attributes_can_be_klassified
46
+ dev = Dev.new(:signature => 'brah brah')
47
+ assert_equal Signature, dev.signature.class
48
+ end
49
+
50
+ def test_that_randomly_classed_attributes_persist_nils
51
+ assert_equal nil, Dev.new.signature
52
+ end
53
+
54
+ def test_that_randomly_classed_attributes_respect_defaults
55
+ assert_equal 'DHH Jr.', Dev.new.name
56
+ end
57
+
58
+ def test_that_constructor_casts_attributes
59
+ assert_equal 'Lives in Cube 20', Dev.new(:cubical => 20).cubical
60
+ end
61
+
62
+ def test_that_setter_casts_attributes
63
+ golden_boy = Dev.new
64
+ golden_boy.cubical = 20
65
+
66
+ assert_equal 'Lives in Cube 20', golden_boy.cubical
67
+ end
68
+
69
+ def test_that_properly_klassed_values_are_not_rekast
70
+ why_hammer = Signature.new('go ask your mom')
71
+ Signature.expects(:new).with(why_hammer).never
72
+ hammer = Dev.new(:signature => why_hammer)
73
+ end
74
+
75
+ def test_that_default_values_can_be_set_to_nothing
76
+ assert_equal nil, Dev.new(:hacker => nil).hacker
77
+ end
78
+
79
+ end
80
+
@@ -0,0 +1,28 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'test/unit'
4
+ require 'valuable.rb'
5
+ require 'mocha'
6
+
7
+ class Parent < Valuable
8
+ has_value :name, :default => 'unknown'
9
+ end
10
+
11
+ class Child < Parent
12
+ has_value :age
13
+ end
14
+
15
+ class InheritanceTest < Test::Unit::TestCase
16
+
17
+ def test_that_children_inherit_their_parents_attributes
18
+ assert Child.attributes.include?(:name)
19
+ end
20
+
21
+ def test_that_children_have_distinctive_attributes
22
+ assert Child.attributes.include?(:age)
23
+ end
24
+
25
+ def test_that_parents_do_not_inherit_things_from_children
26
+ assert_equal [:name], Parent.attributes
27
+ end
28
+ end
@@ -0,0 +1,194 @@
1
+ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
2
+
3
+ require 'test/unit'
4
+ require 'valuable.rb'
5
+ require 'mocha'
6
+
7
+ class Cubical < String
8
+ def initialize(number)
9
+ super "Lives in Cubical #{number}"
10
+ end
11
+ end
12
+
13
+ class DevCertifications < Valuable
14
+ has_value :a_plus, :default => false
15
+ has_value :mcts, :default => false
16
+ has_value :hash_rocket, :default => false
17
+ end
18
+
19
+ class Developer < Valuable
20
+ has_value :experience, :klass => :integer
21
+ has_value :has_exposure_to_sunlight, :default => false
22
+ has_value :mindset
23
+ has_value :name, :default => 'DHH Jr.', :klass => :string
24
+ has_value :snacks_per_day, :klass => :integer, :default => 7
25
+ has_value :cubical, :klass => Cubical
26
+ has_value :hacker, :default => true
27
+ has_value :certifications, :klass => DevCertifications, :default => DevCertifications.new
28
+ has_value :quote
29
+ has_value :employed, :klass => :boolean, :negative => 'unemployed'
30
+
31
+ has_collection :favorite_gems
32
+
33
+ end
34
+
35
+ class BaseTest < Test::Unit::TestCase
36
+
37
+ def test_that_an_attributes_hash_is_available
38
+ assert_kind_of(Hash, Developer.new.attributes)
39
+ end
40
+
41
+ def test_that_static_defaults_hash_is_available
42
+ assert_equal 'DHH Jr.', Developer.defaults[:name]
43
+ end
44
+
45
+ def test_that_an_accessor_is_created
46
+ dev = Developer.new(:mindset => :agile)
47
+ assert_equal :agile, dev.mindset
48
+ end
49
+
50
+ def test_that_setter_is_created
51
+ dev = Developer.new
52
+ dev.mindset = :enterprisey
53
+ assert_equal :enterprisey, dev.mindset
54
+ end
55
+
56
+ def test_that_attributes_can_be_cast_as_integer
57
+ dev = Developer.new(:experience => 9.2)
58
+ assert_equal 9, dev.experience
59
+ end
60
+
61
+ def test_that_integer_attributes_respect_default
62
+ assert_equal 7, Developer.new.snacks_per_day
63
+ end
64
+
65
+ def test_that_an_integer_attribute_with_no_value_results_in_nil
66
+ assert_equal nil, Developer.new.experience
67
+ end
68
+
69
+ def test_that_attributes_can_be_klassified
70
+ dev = Developer.new(:cubical => 12)
71
+ assert_equal Cubical, dev.cubical.class
72
+ end
73
+
74
+ def test_that_defaults_appear_in_attributes_hash
75
+ assert_equal false, Developer.new.attributes[:has_exposure_to_sunlight]
76
+ end
77
+
78
+ def test_that_attributes_can_have_default_values
79
+ assert_equal false, Developer.new.has_exposure_to_sunlight
80
+ end
81
+
82
+ def test_that_randomly_classed_attributes_persist_nils
83
+ assert_equal nil, Developer.new.cubical
84
+ end
85
+
86
+ def test_that_randomly_classed_attributes_respect_defaults
87
+ assert_equal 'DHH Jr.', Developer.new.name
88
+ end
89
+
90
+ def test_that_constructor_casts_attributes
91
+ assert_equal 'Lives in Cubical 20', Developer.new(:cubical => 20).cubical
92
+ end
93
+
94
+ def test_that_setter_casts_attributes
95
+ golden_boy = Developer.new
96
+ golden_boy.cubical = 20
97
+
98
+ assert_equal 'Lives in Cubical 20', golden_boy.cubical
99
+ end
100
+
101
+ def test_that_attributes_are_available_as_class_method
102
+ assert Developer.attributes.include?(:cubical)
103
+ end
104
+
105
+ def test_that_a_model_can_have_a_collection
106
+ assert_equal [], Developer.new.favorite_gems
107
+ end
108
+
109
+ def test_that_values_do_not_mysteriously_jump_instances
110
+ panda = Developer.new
111
+ panda.mindset = 'geek'
112
+
113
+ hammer = Developer.new
114
+
115
+ assert_not_equal 'geek', hammer.mindset
116
+ end
117
+
118
+ def test_that_collection_values_do_not_roll_across_instances
119
+ jim = Developer.new
120
+ jim.favorite_gems << 'Ruby'
121
+
122
+ clark = Developer.new
123
+
124
+ assert_equal [], clark.favorite_gems
125
+ end
126
+
127
+ def test_that_attributes_are_cast
128
+ panda = Developer.new(:name => 'Code Panda', :experience => '8')
129
+ assert_kind_of Integer, panda.attributes[:experience]
130
+ end
131
+
132
+ def test_that_stringy_keys_are_tried_in_absence_of_symbolic_keys
133
+ homer = Developer.new('quote' => "D'oh!")
134
+ assert_equal "D'oh!", homer.quote
135
+ end
136
+
137
+ def test_that_default_values_from_seperate_instances_are_not_references_to_the_default_value_for_that_field
138
+ assert_not_equal Developer.new.favorite_gems.object_id, Developer.new.favorite_gems.object_id
139
+ end
140
+
141
+ def test_that_properly_klassed_values_are_not_rekast
142
+ stapler = Cubical.new('in sub-basement')
143
+ Cubical.expects(:new).with(stapler).never
144
+ Developer.new(:cubical => stapler)
145
+ end
146
+
147
+ def test_that_values_can_be_set_to_false
148
+ assert_equal false, Developer.new(:hacker => false).hacker
149
+ end
150
+
151
+ def test_that_default_values_needing_deep_duplication_get_it
152
+ a = Developer.new
153
+ b = Developer.new
154
+
155
+ a.certifications.hash_rocket = true
156
+ assert_equal false, b.certifications.hash_rocket
157
+ end
158
+
159
+ def test_that_default_values_can_be_set_to_nothing
160
+ assert_equal nil, Developer.new(:hacker => nil).hacker
161
+ end
162
+
163
+ def test_that_values_are_cast_to_boolean
164
+ assert_equal false, Developer.new(:employed => nil).employed
165
+ end
166
+
167
+ def test_that_string_zero_becomes_false
168
+ assert_equal false, Developer.new(:employed => '0').employed
169
+ end
170
+
171
+ def test_that_boolean_values_get_questionmarked_methods
172
+ assert Developer.instance_methods.include?('employed?')
173
+ end
174
+
175
+ def test_that_boolean_values_get_negative_methods
176
+ assert Developer.instance_methods.include?('unemployed?')
177
+ end
178
+
179
+ def test_that_negative_methods_are_negative
180
+ assert_equal true, Developer.new(:employed => false).unemployed?
181
+ end
182
+
183
+ def test_that_constructor_can_handle_an_instance_of_nothing
184
+ assert_nothing_raised do
185
+ Developer.new(nil)
186
+ end
187
+ end
188
+
189
+ def test_that_klassification_does_not_break_when_stringified
190
+ assert_nothing_raised do
191
+ Developer.new(:experience => '2')
192
+ end
193
+ end
194
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Valuable
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.8"
5
+ platform: ruby
6
+ authors:
7
+ - Johnathon Wright
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-09 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Valuable is a ruby base class that is essentially attr_accessor on steroids. It intends to use a simple and intuitive interface, allowing you to get on with the logic specific to your application.
17
+ email: jw@mustmodify.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/valuable.rb
26
+ - README.markdown
27
+ - rakefile.rb
28
+ has_rdoc: true
29
+ homepage: http://www.github.com/mustmodify/valuable
30
+ licenses: []
31
+
32
+ post_install_message:
33
+ rdoc_options: []
34
+
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: "0"
42
+ version:
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ requirements: []
50
+
51
+ rubyforge_project: valuable
52
+ rubygems_version: 1.3.5
53
+ signing_key:
54
+ specification_version: 3
55
+ summary: attr_accessor on steroids with defaults, constructor, and light casting.
56
+ test_files:
57
+ - test/bad_attributes_test.rb
58
+ - test/deprecated_test.rb
59
+ - test/inheritance_test.rb
60
+ - test/valuable_test.rb