valuable 0.6 → 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`.
@@ -1,40 +1,48 @@
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.
1
+ # Valuable is the class from which all classes (who are so inclined)
2
+ # should inherit.
5
3
  #
6
- # Example:
4
+ # ==Example:
7
5
  #
8
- # class Bus < Valuable
9
- # has_value :color, :default => 'yellow'
10
- # has_value :number, :klass => Integer
11
- # has_collection :riders
12
- # end
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'}
13
19
  class Valuable
14
20
 
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.
21
+ # Returns a Hash representing all known values. Values are set three ways:
19
22
  #
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
+ # (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.
23
32
  #
24
- # >> bus = Bus.new(:number => 16) # color has default value 'yellow'
25
- # >> bus.attributes
26
- # => {:color => 'yellow', :number => 16}
33
+ # >> bus = Bus.new(:number => 16) # color has default value 'yellow'
34
+ # >> bus.attributes
35
+ # => {:color => 'yellow', :number => 16}
27
36
  def attributes
28
- @attributes ||= HashWithIndifferentAccess.new(deep_duplicate_of(self.class.defaults))
37
+ @attributes ||= deep_duplicate_of(self.class.defaults)
29
38
  end
30
39
 
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 ) }
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
35
44
  end
36
45
 
37
- # Creates a duplicate of all values.
38
46
  def deep_duplicate_of(value)
39
47
  Marshal.load(Marshal.dump(value))
40
48
  end
@@ -49,92 +57,88 @@ class Valuable
49
57
  # Returns a name/value set of the values that will be used on
50
58
  # instanciation unless new values are provided.
51
59
  #
52
- # class Bus < Valuable
53
- # has_value :color, :default => 'yellow'
54
- # end
55
- #
56
- # >> Bus.defaults
57
- # => {:color => 'yellow'}
58
- #
60
+ # >> Bus.defaults
61
+ # => {:color => 'yellow'}
59
62
  def defaults
60
63
  @defaults ||= {}
61
64
  end
62
65
 
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.
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.
67
73
  #
68
- # :default - for the given attribute, use this value if no other is
69
- # provided.
74
+ # :klass - light weight type casting. Use :integer, :string or
75
+ # :boolean. Alternately, supply a class.
70
76
  #
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.
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.
75
82
  #
76
- # A great example: PhoneNumber < String is useful if you
83
+ # A good example: PhoneNumber < String is useful if you
77
84
  # want numbers to come out the other end properly formatted, when your
78
85
  # input may come in as an integer, or string without formatting, or
79
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.
80
92
  def has_value(name, options={})
93
+
94
+ name = name.to_sym
95
+
81
96
  attributes << name
82
97
 
83
98
  defaults[name] = options[:default] unless options[:default].nil?
84
99
 
85
100
  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]
101
+ create_question_for(name) if options[:klass] == :boolean
102
+ create_negative_question_for(name, options[:negative]) if options[:klass] == :boolean && options[:negative]
88
103
 
89
104
  create_setter_for(name, options[:klass], options[:default])
90
105
 
91
106
  check_options_validity(name, options)
92
107
  end
93
108
 
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
109
  # Creates the method that sets the value of an attribute. This setter
108
110
  # is called both by the constructor. The constructor handles type
109
111
  # casting. Setting values via the attributes hash avoids the method
110
112
  # defined here.
111
113
  def create_setter_for(name, klass, default)
112
-
113
- if klass == nil
114
+
115
+ case klass
116
+ when NilClass
117
+
114
118
  define_method "#{name}=" do |value|
115
119
  attributes[name] = value
116
120
  end
117
121
 
118
- elsif klass == Integer
122
+ when :integer
119
123
 
120
124
  define_method "#{name}=" do |value|
121
125
  value_as_integer = value && value.to_i
122
126
  attributes[name] = value_as_integer
123
127
  end
124
128
 
125
- elsif klass == String
129
+ when :string
126
130
 
127
131
  define_method "#{name}=" do |value|
128
132
  value_as_string = value && value.to_s
129
133
  attributes[name] = value_as_string
130
134
  end
131
135
 
132
- elsif klass == Boolean
136
+ when :boolean
133
137
 
134
138
  define_method "#{name}=" do |value|
135
- attributes[name] = !!value
139
+ attributes[name] = value == '0' ? false : !!value
136
140
  end
137
-
141
+
138
142
  else
139
143
 
140
144
  define_method "#{name}=" do |value|
@@ -160,13 +164,13 @@ class Valuable
160
164
  # In addition to the normal getter and setter, boolean attributes
161
165
  # get a method appended with a ?.
162
166
  #
163
- # class Planer < Valuable
164
- # has_value :free_agent, :klass => Boolean
165
- # end
167
+ # class Player < Valuable
168
+ # has_value :free_agent, :klass => Boolean
169
+ # end
166
170
  #
167
- # juan = Bus.new(:free_agent => true)
168
- # >> juan.free_agent?
169
- # => true
171
+ # juan = Player.new(:free_agent => true)
172
+ # >> juan.free_agent?
173
+ # => true
170
174
  def create_question_for(name)
171
175
  define_method "#{name}?" do
172
176
  attributes[name]
@@ -175,13 +179,13 @@ class Valuable
175
179
 
176
180
  # In some situations, the opposite of a value may be just as interesting.
177
181
  #
178
- # class Coder < Valuable
179
- # has_value :agilist, :klass => Boolean, :negative => :waterfaller
180
- # end
182
+ # class Coder < Valuable
183
+ # has_value :agilist, :klass => Boolean, :negative => :waterfaller
184
+ # end
181
185
  #
182
- # monkey = Coder.new(:agilist => false)
183
- # >> monkey.waterfaller?
184
- # => true
186
+ # monkey = Coder.new(:agilist => false)
187
+ # >> monkey.waterfaller?
188
+ # => true
185
189
  def create_negative_question_for(name, negative)
186
190
  define_method "#{negative}?" do
187
191
  !attributes[name]
@@ -191,18 +195,39 @@ class Valuable
191
195
  # this is a more intuitive way of marking an attribute as holding a
192
196
  # collection.
193
197
  #
194
- # class Bus < Valuable
195
- # has_collection :riders
196
- # end
198
+ # class Bus < Valuable
199
+ # has_value :riders, :default => [] # meh...
200
+ # has_collection :riders # better!
201
+ # end
197
202
  #
198
- # >> bus = Bus.new
199
- # >> bus.riders << 'jack'
200
- # >> bus.riders
201
- # => ['jack']
203
+ # >> bus = Bus.new
204
+ # >> bus.riders << 'jack'
205
+ # >> bus.riders
206
+ # => ['jack']
202
207
  def has_collection(name)
203
208
  has_value(name, :default => [] )
204
209
  end
205
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
+
206
231
  end
207
232
 
208
233
  end
@@ -1,193 +1,76 @@
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
-
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
+
@@ -1,23 +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
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
@@ -4,9 +4,6 @@ require 'test/unit'
4
4
  require 'valuable.rb'
5
5
  require 'mocha'
6
6
 
7
- class Signature < String
8
- end
9
-
10
7
  class Cubical < String
11
8
  def initialize(number)
12
9
  super "Lives in Cubical #{number}"
@@ -20,17 +17,16 @@ class DevCertifications < Valuable
20
17
  end
21
18
 
22
19
  class Developer < Valuable
23
- has_value :experience, :klass => Integer
20
+ has_value :experience, :klass => :integer
24
21
  has_value :has_exposure_to_sunlight, :default => false
25
22
  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
23
+ has_value :name, :default => 'DHH Jr.', :klass => :string
24
+ has_value :snacks_per_day, :klass => :integer, :default => 7
29
25
  has_value :cubical, :klass => Cubical
30
26
  has_value :hacker, :default => true
31
- has_value :certifications, :default => DevCertifications.new
27
+ has_value :certifications, :klass => DevCertifications, :default => DevCertifications.new
32
28
  has_value :quote
33
- has_value :employed, :klass => Boolean, :negative => 'unemployed'
29
+ has_value :employed, :klass => :boolean, :negative => 'unemployed'
34
30
 
35
31
  has_collection :favorite_gems
36
32
 
@@ -71,8 +67,8 @@ class BaseTest < Test::Unit::TestCase
71
67
  end
72
68
 
73
69
  def test_that_attributes_can_be_klassified
74
- dev = Developer.new(:signature => 'brah brah')
75
- assert_equal Signature, dev.signature.class
70
+ dev = Developer.new(:cubical => 12)
71
+ assert_equal Cubical, dev.cubical.class
76
72
  end
77
73
 
78
74
  def test_that_defaults_appear_in_attributes_hash
@@ -84,7 +80,7 @@ class BaseTest < Test::Unit::TestCase
84
80
  end
85
81
 
86
82
  def test_that_randomly_classed_attributes_persist_nils
87
- assert_equal nil, Developer.new.signature
83
+ assert_equal nil, Developer.new.cubical
88
84
  end
89
85
 
90
86
  def test_that_randomly_classed_attributes_respect_defaults
@@ -103,7 +99,7 @@ class BaseTest < Test::Unit::TestCase
103
99
  end
104
100
 
105
101
  def test_that_attributes_are_available_as_class_method
106
- assert Developer.attributes.include?(:signature)
102
+ assert Developer.attributes.include?(:cubical)
107
103
  end
108
104
 
109
105
  def test_that_a_model_can_have_a_collection
@@ -143,9 +139,9 @@ class BaseTest < Test::Unit::TestCase
143
139
  end
144
140
 
145
141
  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)
142
+ stapler = Cubical.new('in sub-basement')
143
+ Cubical.expects(:new).with(stapler).never
144
+ Developer.new(:cubical => stapler)
149
145
  end
150
146
 
151
147
  def test_that_values_can_be_set_to_false
@@ -168,6 +164,10 @@ class BaseTest < Test::Unit::TestCase
168
164
  assert_equal false, Developer.new(:employed => nil).employed
169
165
  end
170
166
 
167
+ def test_that_string_zero_becomes_false
168
+ assert_equal false, Developer.new(:employed => '0').employed
169
+ end
170
+
171
171
  def test_that_boolean_values_get_questionmarked_methods
172
172
  assert Developer.instance_methods.include?('employed?')
173
173
  end
@@ -179,4 +179,16 @@ class BaseTest < Test::Unit::TestCase
179
179
  def test_that_negative_methods_are_negative
180
180
  assert_equal true, Developer.new(:employed => false).unemployed?
181
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
182
194
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: valuable
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.6"
4
+ version: "0.8"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Johnathon Wright
@@ -9,20 +9,11 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-06-22 00:00:00 -05:00
12
+ date: 2009-12-16 00:00:00 -06:00
13
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.
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.
26
17
  email: jw@mustmodify.com
27
18
  executables: []
28
19
 
@@ -31,12 +22,11 @@ extensions: []
31
22
  extra_rdoc_files: []
32
23
 
33
24
  files:
34
- - lib/boolean.rb
35
25
  - lib/valuable.rb
36
- - README.txt
26
+ - README.markdown
37
27
  - rakefile.rb
38
28
  has_rdoc: true
39
- homepage: http://valuable.mustmodify.com
29
+ homepage: http://www.github.com/mustmodify/valuable
40
30
  licenses: []
41
31
 
42
32
  post_install_message:
@@ -59,10 +49,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
49
  requirements: []
60
50
 
61
51
  rubyforge_project: valuable
62
- rubygems_version: 1.3.3
52
+ rubygems_version: 1.3.5
63
53
  signing_key:
64
54
  specification_version: 3
65
55
  summary: attr_accessor on steroids with defaults, constructor, and light casting.
66
56
  test_files:
67
57
  - test/bad_attributes_test.rb
58
+ - test/deprecated_test.rb
59
+ - test/inheritance_test.rb
68
60
  - test/valuable_test.rb
data/README.txt DELETED
@@ -1,79 +0,0 @@
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.
@@ -1,2 +0,0 @@
1
- module Boolean
2
- end