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