Valuable 0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -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