valuable 0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,79 @@
1
+
2
+ =Introducing Valuable
3
+
4
+ Valuable is a ruby base class that is essentially attr_accessor on steroids. Its aim is to provide Rails-like goodness without the database. 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.
5
+
6
+ Valuable provides DRY decoration like attr_accessor, but includes default values, light weight type casting, a constructor that accepts an attributes hash, a class-level list of attributes, an instance-level attributes hash, and more.
7
+
8
+ ==Example
9
+
10
+ class BaseballPlayer < Valuable
11
+
12
+ has_value :at_bats, :klass => Integer
13
+ has_value :hits, :klass => Integer
14
+ has_value :league, :default => 'unknown'
15
+ has_value :name, :dependency => true
16
+ has_value :cell, :klass => PhoneNumber, :default => 'Unknown'
17
+ has_value :active, :klass => Boolean
18
+
19
+ has_collection :teammates
20
+
21
+ def average
22
+ hits/at_bats.to_f if hits && at_bats
23
+ end
24
+ end
25
+
26
+
27
+ joe = BaseballPlayer.new(:name => 'Joe', :hits => 5, :at_bats => 20, :cell => '1234567890')
28
+
29
+ joe.at_bats
30
+
31
+ 20
32
+
33
+ joe.active?
34
+
35
+ nil
36
+
37
+ joe.league
38
+
39
+ 'unknown'
40
+
41
+ joe.average
42
+
43
+ 0.25
44
+
45
+ joe.at_bats = nil
46
+
47
+ joe.average
48
+
49
+ nil
50
+
51
+ joe.teammates
52
+
53
+ []
54
+
55
+ joe.cell
56
+
57
+ '(123) 456-7890'
58
+
59
+ joe.cell = nil
60
+
61
+ joe.cell
62
+
63
+ nil
64
+
65
+ ==DEFAULT VALUES
66
+
67
+ 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.
68
+
69
+ When a default value and a klass are specified, the default value will NOT be cast to type klass -- you must do it. See "jersey" example above.
70
+
71
+ If a value having a default is set to null after it is constructed, it will NOT be set to the default.
72
+
73
+ 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.
74
+
75
+ ==KLASS-ification
76
+
77
+ Boolean is defined as a module in lib for uniformity.
78
+
79
+ 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,2 @@
1
+ module Boolean
2
+ end
@@ -0,0 +1,208 @@
1
+ require 'active_support'
2
+ require File.dirname(__FILE__) + '/boolean.rb'
3
+ # This is the base class from which all classes that intend to use
4
+ # Valuable should inherit.
5
+ #
6
+ # Example:
7
+ #
8
+ # class Bus < Valuable
9
+ # has_value :color, :default => 'yellow'
10
+ # has_value :number, :klass => Integer
11
+ # has_collection :riders
12
+ # end
13
+ class Valuable
14
+
15
+ # Returns a Hash with all the name value pairs that have values so far,
16
+ # either because they had a default value or because they were set in
17
+ # the constructor or after instanciation. Values that have not been defined
18
+ # and which have no default value will not appear in this collection.
19
+ #
20
+ # Currently returns a HashWithIndifferentAccess, though that may change
21
+ # since it would remove the dependancy on ActiveSupport. Always use symbols
22
+ # to access these values.
23
+ #
24
+ # >> bus = Bus.new(:number => 16) # color has default value 'yellow'
25
+ # >> bus.attributes
26
+ # => {:color => 'yellow', :number => 16}
27
+ def attributes
28
+ @attributes ||= HashWithIndifferentAccess.new(deep_duplicate_of(self.class.defaults))
29
+ end
30
+
31
+ # accepts a hash that will be used to populate the predefined attributes
32
+ # for this class.
33
+ def initialize(atts = {})
34
+ atts.each { |name, value| __send__("#{name}=", value ) }
35
+ end
36
+
37
+ # Creates a duplicate of all values.
38
+ def deep_duplicate_of(value)
39
+ Marshal.load(Marshal.dump(value))
40
+ end
41
+
42
+ class << self
43
+
44
+ # Returns an array of the attributes available on this object.
45
+ def attributes
46
+ @attributes ||= []
47
+ end
48
+
49
+ # Returns a name/value set of the values that will be used on
50
+ # instanciation unless new values are provided.
51
+ #
52
+ # class Bus < Valuable
53
+ # has_value :color, :default => 'yellow'
54
+ # end
55
+ #
56
+ # >> Bus.defaults
57
+ # => {:color => 'yellow'}
58
+ #
59
+ def defaults
60
+ @defaults ||= {}
61
+ end
62
+
63
+ # Decorator method that determines which attributes will be available
64
+ # for each instance of the object being created. It accepts an
65
+ # attribute name (a symbol) and an options hash. Valid options are
66
+ # :default, :klass and (when :klass is Boolean) :negative.
67
+ #
68
+ # :default - for the given attribute, use this value if no other is
69
+ # provided.
70
+ #
71
+ # :klass - light weight type casting. Use Integer, String, Boolean, or
72
+ # related classes. Alternately, supply a class. When that attribute is
73
+ # set, if the value isn't already of that class, the result will be
74
+ # an instance of that class, with the value passed to the constructory.
75
+ #
76
+ # A great example: PhoneNumber < String is useful if you
77
+ # want numbers to come out the other end properly formatted, when your
78
+ # input may come in as an integer, or string without formatting, or
79
+ # string with bad formatting.
80
+ def has_value(name, options={})
81
+ attributes << name
82
+
83
+ defaults[name] = options[:default] unless options[:default].nil?
84
+
85
+ create_accessor_for(name)
86
+ create_question_for(name) if options[:klass] == Boolean
87
+ create_negative_question_for(name, options[:negative]) if options[:klass] == Boolean && options[:negative]
88
+
89
+ create_setter_for(name, options[:klass], options[:default])
90
+
91
+ check_options_validity(name, options)
92
+ end
93
+
94
+ # This method returns an array of symbols, which are the only allowed
95
+ # options for has_value.
96
+ def known_options
97
+ [:klass, :default, :negative]
98
+ end
99
+
100
+ # validates option hashes passed to has_value
101
+ def check_options_validity(name, options)
102
+ invalid_options = options.keys - known_options
103
+
104
+ raise ArgumentError, "has_value did not know how to respond to #{invalid_options.join(', ')}. Valid (optional) arguments are: #{known_options.join(', ')}" unless invalid_options.empty?
105
+ end
106
+
107
+ # Creates the method that sets the value of an attribute. This setter
108
+ # is called both by the constructor. The constructor handles type
109
+ # casting. Setting values via the attributes hash avoids the method
110
+ # defined here.
111
+ def create_setter_for(name, klass, default)
112
+
113
+ if klass == nil
114
+ define_method "#{name}=" do |value|
115
+ attributes[name] = value
116
+ end
117
+
118
+ elsif klass == Integer
119
+
120
+ define_method "#{name}=" do |value|
121
+ value_as_integer = value && value.to_i
122
+ attributes[name] = value_as_integer
123
+ end
124
+
125
+ elsif klass == String
126
+
127
+ define_method "#{name}=" do |value|
128
+ value_as_string = value && value.to_s
129
+ attributes[name] = value_as_string
130
+ end
131
+
132
+ elsif klass == Boolean
133
+
134
+ define_method "#{name}=" do |value|
135
+ attributes[name] = !!value
136
+ end
137
+
138
+ else
139
+
140
+ define_method "#{name}=" do |value|
141
+ if value.nil?
142
+ attributes[name] = nil
143
+ elsif value.is_a? klass
144
+ attributes[name] = value
145
+ else
146
+ attributes[name] = klass.new(value)
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ # creates a simple accessor method named after the attribute whose
153
+ # value it will provide during the life of the instance.
154
+ def create_accessor_for(name)
155
+ define_method name do
156
+ attributes[name]
157
+ end
158
+ end
159
+
160
+ # In addition to the normal getter and setter, boolean attributes
161
+ # get a method appended with a ?.
162
+ #
163
+ # class Planer < Valuable
164
+ # has_value :free_agent, :klass => Boolean
165
+ # end
166
+ #
167
+ # juan = Bus.new(:free_agent => true)
168
+ # >> juan.free_agent?
169
+ # => true
170
+ def create_question_for(name)
171
+ define_method "#{name}?" do
172
+ attributes[name]
173
+ end
174
+ end
175
+
176
+ # In some situations, the opposite of a value may be just as interesting.
177
+ #
178
+ # class Coder < Valuable
179
+ # has_value :agilist, :klass => Boolean, :negative => :waterfaller
180
+ # end
181
+ #
182
+ # monkey = Coder.new(:agilist => false)
183
+ # >> monkey.waterfaller?
184
+ # => true
185
+ def create_negative_question_for(name, negative)
186
+ define_method "#{negative}?" do
187
+ !attributes[name]
188
+ end
189
+ end
190
+
191
+ # this is a more intuitive way of marking an attribute as holding a
192
+ # collection.
193
+ #
194
+ # class Bus < Valuable
195
+ # has_collection :riders
196
+ # end
197
+ #
198
+ # >> bus = Bus.new
199
+ # >> bus.riders << 'jack'
200
+ # >> bus.riders
201
+ # => ['jack']
202
+ def has_collection(name)
203
+ has_value(name, :default => [] )
204
+ end
205
+
206
+ end
207
+
208
+ end
@@ -0,0 +1,193 @@
1
+ require 'rubygems'
2
+
3
+ require 'rake'
4
+ require 'rake/rdoctask'
5
+ require 'rake/testtask'
6
+ require 'rake/packagetask'
7
+ require 'rake/gempackagetask'
8
+ require 'rake/contrib/rubyforgepublisher'
9
+
10
+ task :default => [:test]
11
+
12
+ PKG_NAME = 'valuable'
13
+ PKG_VERSION = '0.6'
14
+ PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
15
+ RUBY_FORGE_PROJECT = 'valuable'
16
+ RUBY_FORGE_USER = 'mustmodify'
17
+
18
+ desc "Run unit tests"
19
+ Rake::TestTask.new("test") { |t|
20
+ t.pattern = 'test/*_test.rb'
21
+ t.verbose = true
22
+ t.warning = true
23
+ }
24
+
25
+ desc 'clean temporary files'
26
+ task :clean do
27
+ temp_filenames = File.join('**', '*.*~')
28
+ temp_files = Dir.glob(temp_filenames)
29
+ if temp_files.empty?
30
+ puts 'nothing to delete'
31
+ else
32
+ puts "deleting #{temp_files.size} files"
33
+ end
34
+
35
+ File.delete(*temp_files)
36
+ end
37
+
38
+ desc 'Generate documentation for the Valuable plugin'
39
+ Rake::RDocTask.new(:rdoc) do |rdoc|
40
+ rdoc.title = 'Valuable - light weight modeling'
41
+ rdoc.options << '--line-numbers'
42
+ rdoc.options << '--inline-source'
43
+ rdoc.rdoc_files.include('README.txt')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
46
+
47
+ spec = Gem::Specification.new do |s|
48
+ s.name = PKG_NAME
49
+ s.version = PKG_VERSION
50
+ s.platform = Gem::Platform::RUBY
51
+ s.summary = 'attr_accessor on steroids with defaults, constructor, and light casting.'
52
+ s.description = "Valuable is a ruby base class that is essentially attr_accessor on steroids. Its aim is to provide Rails-like goodness where ActiveRecord isn't an option. It intends to use a simple and intuitive interface, allowing you to get on with the logic specific to your application."
53
+
54
+ s.files = FileList["{lib, test}/**/*"].to_a + %w( README.txt rakefile.rb )
55
+ s.add_dependency 'activesupport'
56
+ s.require_path = 'lib'
57
+ s.test_files = FileList["{test}/**/*test.rb"].to_a
58
+ s.has_rdoc = false
59
+
60
+ s.rubyforge_project = RUBY_FORGE_PROJECT
61
+ s.author = 'Johnathon Wright'
62
+ s.email = 'jw@mustmodify.com'
63
+ s.homepage = 'http://valuable.mustmodify.com'
64
+ end
65
+
66
+ Rake::GemPackageTask.new(spec) do |pkg|
67
+ pkg.need_zip = true
68
+ pkg.need_tar = true
69
+ end
70
+
71
+ desc "Publish the API documentation"
72
+ task :pdoc => [:rdoc] do
73
+ Rake::RubyForgePublisher.new(RUBY_FORGE_PROJECT, RUBY_FORGE_USER).upload
74
+ end
75
+
76
+ desc 'Publish the gem and API docs'
77
+ task :publish => [:pdoc, :rubyforge_upload]
78
+
79
+ desc "Publish the release files to RubyForge."
80
+ task :rubyforge_upload => :package do
81
+ files = %w(gem tgz).map { |ext| "pkg/#{PKG_FILE_NAME}.#{ext}" }
82
+
83
+ if RUBY_FORGE_PROJECT then
84
+ require 'net/http'
85
+ require 'open-uri'
86
+
87
+ project_uri = "http://rubyforge.org/projects/#{RUBY_FORGE_PROJECT}/"
88
+ project_data = open(project_uri) { |data| data.read }
89
+ group_id = project_data[/[?&]group_id=(\d+)/, 1]
90
+ raise "Couldn't get group id" unless group_id
91
+
92
+ # This echos password to shell which is a bit sucky
93
+ if ENV["RUBY_FORGE_PASSWORD"]
94
+ password = ENV["RUBY_FORGE_PASSWORD"]
95
+ else
96
+ print "#{RUBY_FORGE_USER}@rubyforge.org's password: "
97
+ password = STDIN.gets.chomp
98
+ end
99
+
100
+ login_response = Net::HTTP.start("rubyforge.org", 80) do |http|
101
+ data = [
102
+ "login=1",
103
+ "form_loginname=#{RUBY_FORGE_USER}",
104
+ "form_pw=#{password}"
105
+ ].join("&")
106
+ http.post("/account/login.php", data)
107
+ end
108
+
109
+ cookie = login_response["set-cookie"]
110
+ raise "Login failed" unless cookie
111
+ headers = { "Cookie" => cookie }
112
+
113
+ release_uri = "http://rubyforge.org/frs/admin/?group_id=#{group_id}"
114
+ release_data = open(release_uri, headers) { |data| data.read }
115
+ package_id = release_data[/[?&]package_id=(\d+)/, 1]
116
+ raise "Couldn't get package id" unless package_id
117
+
118
+ first_file = true
119
+ release_id = ""
120
+
121
+ files.each do |filename|
122
+ basename = File.basename(filename)
123
+ file_ext = File.extname(filename)
124
+ file_data = File.open(filename, "rb") { |file| file.read }
125
+
126
+ puts "Releasing #{basename}..."
127
+
128
+ release_response = Net::HTTP.start("rubyforge.org", 80) do |http|
129
+ release_date = Time.now.strftime("%Y-%m-%d %H:%M")
130
+ type_map = {
131
+ ".zip" => "3000",
132
+ ".tgz" => "3110",
133
+ ".gz" => "3110",
134
+ ".gem" => "1400"
135
+ }; type_map.default = "9999"
136
+ type = type_map[file_ext]
137
+ boundary = "rubyqMY6QN9bp6e4kS21H4y0zxcvoor"
138
+
139
+ query_hash = if first_file then
140
+ {
141
+ "group_id" => group_id,
142
+ "package_id" => package_id,
143
+ "release_name" => PKG_FILE_NAME,
144
+ "release_date" => release_date,
145
+ "type_id" => type,
146
+ "processor_id" => "8000", # Any
147
+ "release_notes" => "",
148
+ "release_changes" => "",
149
+ "preformatted" => "1",
150
+ "submit" => "1"
151
+ }
152
+ else
153
+ {
154
+ "group_id" => group_id,
155
+ "release_id" => release_id,
156
+ "package_id" => package_id,
157
+ "step2" => "1",
158
+ "type_id" => type,
159
+ "processor_id" => "8000", # Any
160
+ "submit" => "Add This File"
161
+ }
162
+ end
163
+
164
+ query = "?" + query_hash.map do |(name, value)|
165
+ [name, URI.encode(value)].join("=")
166
+ end.join("&")
167
+
168
+ data = [
169
+ "--" + boundary,
170
+ "Content-Disposition: form-data; name=\"userfile\"; filename=\"#{basename}\"",
171
+ "Content-Type: application/octet-stream",
172
+ "Content-Transfer-Encoding: binary",
173
+ "", file_data, ""
174
+ ].join("\x0D\x0A")
175
+
176
+ release_headers = headers.merge(
177
+ "Content-Type" => "multipart/form-data; boundary=#{boundary}"
178
+ )
179
+
180
+ target = first_file ? "/frs/admin/qrs.php" : "/frs/admin/editrelease.php"
181
+ http.post(target + query, data, release_headers)
182
+ end
183
+
184
+ if first_file then
185
+ release_id = release_response.body[/release_id=(\d+)/, 1]
186
+ raise("Couldn't get release id") unless release_id
187
+ end
188
+
189
+ first_file = false
190
+ end
191
+ end
192
+ end
193
+
@@ -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,182 @@
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 Cubical < String
11
+ def initialize(number)
12
+ super "Lives in Cubical #{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 Developer < Valuable
23
+ has_value :experience, :klass => Integer
24
+ has_value :has_exposure_to_sunlight, :default => false
25
+ has_value :mindset
26
+ has_value :name, :default => 'DHH Jr.', :klass => String
27
+ has_value :signature, :klass => Signature
28
+ has_value :snacks_per_day, :klass => Integer, :default => 7
29
+ has_value :cubical, :klass => Cubical
30
+ has_value :hacker, :default => true
31
+ has_value :certifications, :default => DevCertifications.new
32
+ has_value :quote
33
+ has_value :employed, :klass => Boolean, :negative => 'unemployed'
34
+
35
+ has_collection :favorite_gems
36
+
37
+ end
38
+
39
+ class BaseTest < Test::Unit::TestCase
40
+
41
+ def test_that_an_attributes_hash_is_available
42
+ assert_kind_of(Hash, Developer.new.attributes)
43
+ end
44
+
45
+ def test_that_static_defaults_hash_is_available
46
+ assert_equal 'DHH Jr.', Developer.defaults[:name]
47
+ end
48
+
49
+ def test_that_an_accessor_is_created
50
+ dev = Developer.new(:mindset => :agile)
51
+ assert_equal :agile, dev.mindset
52
+ end
53
+
54
+ def test_that_setter_is_created
55
+ dev = Developer.new
56
+ dev.mindset = :enterprisey
57
+ assert_equal :enterprisey, dev.mindset
58
+ end
59
+
60
+ def test_that_attributes_can_be_cast_as_integer
61
+ dev = Developer.new(:experience => 9.2)
62
+ assert_equal 9, dev.experience
63
+ end
64
+
65
+ def test_that_integer_attributes_respect_default
66
+ assert_equal 7, Developer.new.snacks_per_day
67
+ end
68
+
69
+ def test_that_an_integer_attribute_with_no_value_results_in_nil
70
+ assert_equal nil, Developer.new.experience
71
+ end
72
+
73
+ def test_that_attributes_can_be_klassified
74
+ dev = Developer.new(:signature => 'brah brah')
75
+ assert_equal Signature, dev.signature.class
76
+ end
77
+
78
+ def test_that_defaults_appear_in_attributes_hash
79
+ assert_equal false, Developer.new.attributes[:has_exposure_to_sunlight]
80
+ end
81
+
82
+ def test_that_attributes_can_have_default_values
83
+ assert_equal false, Developer.new.has_exposure_to_sunlight
84
+ end
85
+
86
+ def test_that_randomly_classed_attributes_persist_nils
87
+ assert_equal nil, Developer.new.signature
88
+ end
89
+
90
+ def test_that_randomly_classed_attributes_respect_defaults
91
+ assert_equal 'DHH Jr.', Developer.new.name
92
+ end
93
+
94
+ def test_that_constructor_casts_attributes
95
+ assert_equal 'Lives in Cubical 20', Developer.new(:cubical => 20).cubical
96
+ end
97
+
98
+ def test_that_setter_casts_attributes
99
+ golden_boy = Developer.new
100
+ golden_boy.cubical = 20
101
+
102
+ assert_equal 'Lives in Cubical 20', golden_boy.cubical
103
+ end
104
+
105
+ def test_that_attributes_are_available_as_class_method
106
+ assert Developer.attributes.include?(:signature)
107
+ end
108
+
109
+ def test_that_a_model_can_have_a_collection
110
+ assert_equal [], Developer.new.favorite_gems
111
+ end
112
+
113
+ def test_that_values_do_not_mysteriously_jump_instances
114
+ panda = Developer.new
115
+ panda.mindset = 'geek'
116
+
117
+ hammer = Developer.new
118
+
119
+ assert_not_equal 'geek', hammer.mindset
120
+ end
121
+
122
+ def test_that_collection_values_do_not_roll_across_instances
123
+ jim = Developer.new
124
+ jim.favorite_gems << 'Ruby'
125
+
126
+ clark = Developer.new
127
+
128
+ assert_equal [], clark.favorite_gems
129
+ end
130
+
131
+ def test_that_attributes_are_cast
132
+ panda = Developer.new(:name => 'Code Panda', :experience => '8')
133
+ assert_kind_of Integer, panda.attributes[:experience]
134
+ end
135
+
136
+ def test_that_stringy_keys_are_tried_in_absence_of_symbolic_keys
137
+ homer = Developer.new('quote' => "D'oh!")
138
+ assert_equal "D'oh!", homer.quote
139
+ end
140
+
141
+ def test_that_default_values_from_seperate_instances_are_not_references_to_the_default_value_for_that_field
142
+ assert_not_equal Developer.new.favorite_gems.object_id, Developer.new.favorite_gems.object_id
143
+ end
144
+
145
+ def test_that_properly_klassed_values_are_not_rekast
146
+ why_hammer = Signature.new('go ask your mom')
147
+ Signature.expects(:new).with(why_hammer).never
148
+ hammer = Developer.new(:signature => why_hammer)
149
+ end
150
+
151
+ def test_that_values_can_be_set_to_false
152
+ assert_equal false, Developer.new(:hacker => false).hacker
153
+ end
154
+
155
+ def test_that_default_values_needing_deep_duplication_get_it
156
+ a = Developer.new
157
+ b = Developer.new
158
+
159
+ a.certifications.hash_rocket = true
160
+ assert_equal false, b.certifications.hash_rocket
161
+ end
162
+
163
+ def test_that_default_values_can_be_set_to_nothing
164
+ assert_equal nil, Developer.new(:hacker => nil).hacker
165
+ end
166
+
167
+ def test_that_values_are_cast_to_boolean
168
+ assert_equal false, Developer.new(:employed => nil).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
+ end
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: valuable
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.6"
5
+ platform: ruby
6
+ authors:
7
+ - Johnathon Wright
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-22 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activesupport
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ description: Valuable is a ruby base class that is essentially attr_accessor on steroids. Its aim is to provide Rails-like goodness where ActiveRecord isn't an option. It intends to use a simple and intuitive interface, allowing you to get on with the logic specific to your application.
26
+ email: jw@mustmodify.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - lib/boolean.rb
35
+ - lib/valuable.rb
36
+ - README.txt
37
+ - rakefile.rb
38
+ has_rdoc: true
39
+ homepage: http://valuable.mustmodify.com
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project: valuable
62
+ rubygems_version: 1.3.3
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: attr_accessor on steroids with defaults, constructor, and light casting.
66
+ test_files:
67
+ - test/bad_attributes_test.rb
68
+ - test/valuable_test.rb