valuable 0.9.1 → 0.9.2
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.
- data/README.markdown +109 -44
- data/lib/valuable/utils.rb +4 -2
- data/lib/valuable.rb +1 -1
- data/test/typical_test.rb +1 -1
- data/test/valuable_test.rb +2 -2
- data/todo.txt +12 -20
- data/valuable.version +1 -1
- metadata +4 -4
data/README.markdown
CHANGED
@@ -1,31 +1,65 @@
|
|
1
1
|
Introducing Valuable
|
2
2
|
====================
|
3
3
|
|
4
|
-
Valuable enables quick modeling... it's attr_accessor on steroids.
|
4
|
+
Valuable enables quick modeling... it's attr_accessor on steroids. For all those times you wanted to use OO to model something but it seemed like too much of a pain, try Valuable. Its simple interface allows you to model without hassles, so you can get on with the logic specific to your application.
|
5
5
|
|
6
|
-
|
6
|
+
Frequent Uses:
|
7
|
+
|
8
|
+
**pre-refactor modeling** to model a class you want to abstract but know would be a pain... as in, "I would love to pull Appointment out of this WorkOrder class, but since that isn't going to happen soon, let me quickly create WorkOrder.appointments... I can then create Appointment\#to\_s, appointment.end_time, appointment.duration, etc. I can use that to facilitate emitting XML or doing something with views, rather than polluting WorkOrder with appointment-related logic."
|
9
|
+
|
10
|
+
**as a presenter** as in, "I need to take in a few different models to generate this map... I need a class that models the integration, but I don't need to persist that to a database."
|
11
|
+
|
12
|
+
**creating models from non-standard data sources** to keep data from non-standard data sources in memory during an import or to render data from an API call.
|
13
|
+
|
14
|
+
Valuable provides DRY decoration like attr_accessor, but includes default values and other formatting (like, "2" => 2), and a constructor that accepts an attributes hash. It provides a class-level list of attributes, an instance-level attributes hash, and more.
|
7
15
|
|
8
16
|
Type Casting in Ruby? You must be crazy...
|
9
17
|
-------------------------------------------------------------
|
10
18
|
Yeah, I get that alot. I mean, about type casting. I'm not writing
|
11
|
-
C# over here.
|
19
|
+
C# over here. Rails does it, they just don't call it type casting,
|
20
|
+
so no one complains when they pass in "2" as a parameter and mysteriously
|
21
|
+
it ends up as an integer. In fact, I'm going to start using the euphamism
|
12
22
|
'Formatting' just so people will stop looking at me that way.
|
13
23
|
|
14
|
-
Say you
|
15
|
-
|
24
|
+
Say you're getting information for a directory from a web service via JSON:
|
25
|
+
|
26
|
+
class Person < Valuable
|
27
|
+
has_value :name
|
28
|
+
has_value :age, :klass => :integer
|
29
|
+
has_value :phone_number, :klass => PhoneNumber
|
30
|
+
# see /examples/phone_number.rb
|
31
|
+
|
32
|
+
'person' =>
|
33
|
+
'name' => 'Mr. Freud',
|
34
|
+
'age' => "344",
|
35
|
+
'phone_number' => '8002195642',
|
36
|
+
'specialization_code' => "2106"
|
37
|
+
|
38
|
+
you'll end up with this:
|
39
|
+
|
40
|
+
>> p = Person.new(params[:person])
|
41
|
+
|
42
|
+
>> p.age
|
43
|
+
=> 344
|
44
|
+
|
45
|
+
>> p.phone_number
|
46
|
+
=> (337) 326-3121
|
47
|
+
|
48
|
+
>> p.phone_number.class
|
49
|
+
=> PhoneNumber
|
50
|
+
|
51
|
+
"Yeah, I could have just done that myself."
|
52
|
+
"Right, but now you don't have to."
|
16
53
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
'specialization_code' => 2106
|
54
|
+
Default Values
|
55
|
+
--------------
|
56
|
+
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.
|
21
57
|
|
22
|
-
|
58
|
+
When a default value and a klass are specified, the default value will NOT be cast to type klass -- you must do it.
|
23
59
|
|
24
|
-
|
25
|
-
has_value :name, :default => 'Unknown'
|
26
|
-
has_value :phone_number
|
60
|
+
If a value having a default is set to null after it is constructed, it will NOT be set to the default.
|
27
61
|
|
28
|
-
|
62
|
+
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.
|
29
63
|
|
30
64
|
Examples
|
31
65
|
-------
|
@@ -67,7 +101,7 @@ _setting a value to nil overrides the default._
|
|
67
101
|
>> Developer.new(:name => 'KDD', :nickname => nil).nickname
|
68
102
|
=> nil
|
69
103
|
|
70
|
-
|
104
|
+
_formatting aka light-weight type-casting_
|
71
105
|
|
72
106
|
class BaseballPlayer < Valuable
|
73
107
|
|
@@ -87,6 +121,59 @@ _light weight type casting_
|
|
87
121
|
>> joe.average
|
88
122
|
=> 0.25
|
89
123
|
|
124
|
+
# Currently supports:
|
125
|
+
# - integer
|
126
|
+
# - decimal ( casts to BigDecimal... NOTE: nil remains nil, not 0 as in nil.to_i )
|
127
|
+
# - string
|
128
|
+
# - boolean ( NOTE: '0' casts to FALSE... I would be fascinated to know when this is not the correct behavior. )
|
129
|
+
# - or any class ( formats as SomeClass.new( ) unless value.is_a?( SomeClass ) )
|
130
|
+
|
131
|
+
|
132
|
+
_collections_
|
133
|
+
|
134
|
+
class MailingList < Valuable
|
135
|
+
has_collection :emails
|
136
|
+
end
|
137
|
+
|
138
|
+
>> m = MailingList.new
|
139
|
+
|
140
|
+
>> m.emails
|
141
|
+
=> []
|
142
|
+
|
143
|
+
>> m = MailingList.new(:emails => [ 'johnathon.e.wright@nasa.gov', 'other.people@wherever.com' ])
|
144
|
+
|
145
|
+
=> m.emails
|
146
|
+
>> [ 'johnathon.e.wright@nasa.gov', 'other.people@wherever.com' ]
|
147
|
+
|
148
|
+
_formatting collections_
|
149
|
+
|
150
|
+
class Player < Valuable
|
151
|
+
has_value :first_name
|
152
|
+
has_value :last_name
|
153
|
+
has_value :salary
|
154
|
+
end
|
155
|
+
|
156
|
+
class Team < Valuable
|
157
|
+
has_value :name
|
158
|
+
has_value :long_name
|
159
|
+
|
160
|
+
has_collection :players, :klass => Player
|
161
|
+
end
|
162
|
+
|
163
|
+
t = Team.new(:name => 'Toronto', :long_name => 'The Toronto Blue Jays',
|
164
|
+
'players' => [
|
165
|
+
{'first_name' => 'Chad', 'last_name' => 'Beck', :salary => 'n/a'},
|
166
|
+
{'first_name' => 'Shawn', 'last_name' => 'Camp', :salary => '2250000'},
|
167
|
+
{'first_name' => 'Brett', 'last_name' => 'Cecil', :salary => '443100'},
|
168
|
+
Player.new(:first_name => 'Travis', :last_name => 'Snider', :salary => '435800')
|
169
|
+
])
|
170
|
+
|
171
|
+
>> t.players.first
|
172
|
+
=> #<Player:0x7fa51e4a1da0 @attributes={:salary=>"n/a", :first_name=>"Chad", :last_name=>"Beck"}>
|
173
|
+
|
174
|
+
>> t.players.last
|
175
|
+
=> #<Player:0x7fa51ea6a9f8 @attributes={:salary=>"435800", :first_name=>"Travis", :last_name=>"Snider"}>
|
176
|
+
|
90
177
|
_aliases_
|
91
178
|
|
92
179
|
# This example requires active_support because of Hash.from_xml
|
@@ -97,22 +184,12 @@ _aliases_
|
|
97
184
|
|
98
185
|
>> xml = '<software><Title>Windows XP</Title></software>'
|
99
186
|
|
100
|
-
>> xp = Software.new(
|
187
|
+
>> xp = Software.new(Hash.from_xml(xml)['software'])
|
101
188
|
|
102
189
|
>> xp.name
|
103
190
|
=> "Windows XP"
|
104
191
|
|
105
192
|
|
106
|
-
_I find myself using classes to format things... ( PhoneNumber is provided in `/examples` )_
|
107
|
-
|
108
|
-
class School < Valuable
|
109
|
-
has_value :name
|
110
|
-
has_value :phone, :klass => PhoneNumber
|
111
|
-
end
|
112
|
-
|
113
|
-
>> School.new(:name => 'Vanderbilt', :phone => '3332223333').phone
|
114
|
-
=> '(333) 222-3333'
|
115
|
-
|
116
193
|
_as a presenter in Rails_
|
117
194
|
|
118
195
|
class CalenderPresenter < Valuable
|
@@ -130,13 +207,13 @@ _as a presenter in Rails_
|
|
130
207
|
def events
|
131
208
|
Event.find(:all, :conditions => event_conditions)
|
132
209
|
end
|
133
|
-
|
210
|
+
|
134
211
|
def event_conditions
|
135
212
|
['starts_at between ? and ?', start_date, end_date]
|
136
213
|
end
|
137
214
|
end
|
138
215
|
|
139
|
-
|
216
|
+
this class might appear in a controller like this:
|
140
217
|
|
141
218
|
class CalendarController < ApplicationController
|
142
219
|
def show
|
@@ -144,7 +221,7 @@ _this class might appear in a controller like this:_
|
|
144
221
|
end
|
145
222
|
end
|
146
223
|
|
147
|
-
|
224
|
+
but it's easier to understand like this:
|
148
225
|
|
149
226
|
>> @presenter = CalendarPresenter.new({}) # first pageload
|
150
227
|
|
@@ -155,7 +232,7 @@ _but it's easier to understand like this:_
|
|
155
232
|
=> Thu, 31 Dec 2009
|
156
233
|
|
157
234
|
>> # User selects some other month and year; the next request looks like...
|
158
|
-
|
235
|
+
|
159
236
|
>> @presenter = CalendarPresenter.new({:month => '2', :year => '2002'})
|
160
237
|
|
161
238
|
>> @presenter.start_date
|
@@ -179,7 +256,7 @@ _you can access the attributes via the attributes hash. Only default and specifi
|
|
179
256
|
>> elvis = Person.new(:name => 'The King')
|
180
257
|
|
181
258
|
>> elvis.attributes
|
182
|
-
=> {:name=>"The King", :is_developer=>false}
|
259
|
+
=> {:name=>"The King", :is_developer=>false}
|
183
260
|
|
184
261
|
>> elvis.attributes[:name]
|
185
262
|
=> "The King"
|
@@ -188,20 +265,8 @@ _you can access the attributes via the attributes hash. Only default and specifi
|
|
188
265
|
=> nil
|
189
266
|
|
190
267
|
_also, you can get a list of all the defined attributes from the class_
|
191
|
-
|
268
|
+
|
192
269
|
>> Person.attributes
|
193
270
|
=> [:name, :is_developer, :ssn]
|
194
271
|
|
195
|
-
Default Values
|
196
|
-
--------------
|
197
|
-
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.
|
198
|
-
|
199
|
-
When a default value and a klass are specified, the default value will NOT be cast to type klass -- you must do it.
|
200
|
-
|
201
|
-
If a value having a default is set to null after it is constructed, it will NOT be set to the default.
|
202
|
-
|
203
|
-
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.
|
204
272
|
|
205
|
-
KLASS-ification
|
206
|
-
---------------
|
207
|
-
`: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/valuable/utils.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# Trying to extract as much logic as possible to minimize the memory
|
2
|
+
# footprint of individual instances. Feedback welcome.
|
1
3
|
require 'bigdecimal'
|
2
4
|
|
3
5
|
module Valuable::Utils
|
@@ -17,7 +19,7 @@ module Valuable::Utils
|
|
17
19
|
Marshal.load(Marshal.dump(value))
|
18
20
|
end
|
19
21
|
|
20
|
-
def
|
22
|
+
def format( name, value, attributes, collection_item = false )
|
21
23
|
klass = collection_item ? attributes[name][:item_klass] : attributes[name][:klass]
|
22
24
|
|
23
25
|
case klass
|
@@ -28,7 +30,7 @@ module Valuable::Utils
|
|
28
30
|
when :collection
|
29
31
|
if( value.kind_of?(Array) )
|
30
32
|
out = value.map do |item|
|
31
|
-
Valuable::Utils.
|
33
|
+
Valuable::Utils.format( name, item, attributes, true )
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
data/lib/valuable.rb
CHANGED
@@ -87,7 +87,7 @@ class Valuable
|
|
87
87
|
attribute = Valuable::Utils.find_attribute_for( name, self.class._attributes )
|
88
88
|
|
89
89
|
if attribute
|
90
|
-
self.attributes[attribute] = Valuable::Utils.
|
90
|
+
self.attributes[attribute] = Valuable::Utils.format(attribute, value, self.class._attributes)
|
91
91
|
else
|
92
92
|
raise( ArgumentError, "#{self.class.to_s} does not have an attribute or alias '#{name}'", caller) unless self.permissive?
|
93
93
|
end
|
data/test/typical_test.rb
CHANGED
@@ -3,7 +3,7 @@ $: << File.expand_path(File.dirname(__FILE__) + '/../lib')
|
|
3
3
|
require 'test/unit'
|
4
4
|
require 'valuable.rb'
|
5
5
|
require 'date'
|
6
|
-
require File.dirname(__FILE__) + '/../examples/phone_number'
|
6
|
+
require File.expand_path(File.dirname(__FILE__) + '/../examples/phone_number')
|
7
7
|
class Person < Valuable
|
8
8
|
has_value :dob, :klass => :date
|
9
9
|
end
|
data/test/valuable_test.rb
CHANGED
@@ -169,11 +169,11 @@ class BaseTest < Test::Unit::TestCase
|
|
169
169
|
end
|
170
170
|
|
171
171
|
def test_that_boolean_values_get_questionmarked_methods
|
172
|
-
assert Developer.instance_methods.include?(
|
172
|
+
assert Developer.instance_methods.map(&:to_sym).include?(:employed?)
|
173
173
|
end
|
174
174
|
|
175
175
|
def test_that_boolean_values_get_negative_methods
|
176
|
-
assert Developer.instance_methods.include?(
|
176
|
+
assert Developer.instance_methods.map(&:to_sym).include?(:unemployed?)
|
177
177
|
end
|
178
178
|
|
179
179
|
def test_that_negative_methods_are_negative
|
data/todo.txt
CHANGED
@@ -1,10 +1,3 @@
|
|
1
|
-
Find some way to do
|
2
|
-
|
3
|
-
has_value :something, :klass => Decimal
|
4
|
-
|
5
|
-
* must handle BigDecimal not being around
|
6
|
-
|
7
|
-
|
8
1
|
- has_value :start_date, :default => {Date.today}
|
9
2
|
- has_value :price, :default => 50, :extend => MoneyFormatter
|
10
3
|
|
@@ -19,6 +12,17 @@ class SomeSearchThing < Valuable
|
|
19
12
|
|
20
13
|
end
|
21
14
|
|
15
|
+
class Datum < Valuable
|
16
|
+
has_index :by_name
|
17
|
+
has_list :calculations
|
18
|
+
end
|
19
|
+
-or-
|
20
|
+
class Datum < Valuable
|
21
|
+
lists :calculations
|
22
|
+
indexes :by_name
|
23
|
+
end
|
24
|
+
|
25
|
+
|
22
26
|
BEST PRACTICES DOC
|
23
27
|
|
24
28
|
When creating a searcher:
|
@@ -74,18 +78,6 @@ like this:
|
|
74
78
|
end
|
75
79
|
|
76
80
|
|
77
|
-
|
78
|
-
:klass => :decimal
|
79
|
-
- When is decimal available? Only load then.
|
80
|
-
- If calling :decimal and it hasn't loaded, error
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
class Change < Decimal
|
85
|
-
extend Valuable::whatever or
|
86
|
-
extend Valuable
|
87
|
-
|
88
|
-
...
|
89
|
-
end
|
81
|
+
what about has_value :number, :construct => 'Decimal#parse'
|
90
82
|
|
91
83
|
- do a better job with method_missing
|
data/valuable.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.2
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: valuable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 63
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 9
|
9
|
-
-
|
10
|
-
version: 0.9.
|
9
|
+
- 2
|
10
|
+
version: 0.9.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Johnathon Wright
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-
|
18
|
+
date: 2011-09-27 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies: []
|
21
21
|
|