valuable 0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/README.txt +79 -0
- data/lib/boolean.rb +2 -0
- data/lib/valuable.rb +208 -0
- data/rakefile.rb +193 -0
- data/test/bad_attributes_test.rb +23 -0
- data/test/valuable_test.rb +182 -0
- metadata +68 -0
data/README.txt
ADDED
@@ -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.
|
data/lib/boolean.rb
ADDED
data/lib/valuable.rb
ADDED
@@ -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
|
data/rakefile.rb
ADDED
@@ -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
|