good 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +33 -17
- data/lib/good.rb +11 -9
- metadata +2 -2
data/README.md
CHANGED
@@ -2,11 +2,13 @@
|
|
2
2
|
|
3
3
|
2 little things that make writing good Ruby programs a little easier.
|
4
4
|
|
5
|
-
1. `Good::Value` is a class generator for simple, pleasant [Value objects](http://en.wikipedia.org/wiki/Value_object).
|
5
|
+
1. `Good::Value` is a class generator for simple, pleasant, immutable [Value objects](http://en.wikipedia.org/wiki/Value_object).
|
6
6
|
|
7
|
-
2. `Good::Record` is a class generator for simple, pleasant [Record objects](http://en.wikipedia.org/wiki/Record_(computer_science) "Record Objects"). They're a lot like `Struct`.
|
7
|
+
2. `Good::Record` is a class generator for simple, pleasant, mutable [Record objects](http://en.wikipedia.org/wiki/Record_(computer_science) "Record Objects"). They're a lot like `Struct`.
|
8
8
|
|
9
|
-
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
Both are used the same way, like this:
|
10
12
|
|
11
13
|
```ruby
|
12
14
|
class Person < Good::Value.new(:name, :age)
|
@@ -17,7 +19,6 @@ or like this if you prefer:
|
|
17
19
|
|
18
20
|
```ruby
|
19
21
|
Person = Good::Value.new(:name, :age)
|
20
|
-
end
|
21
22
|
```
|
22
23
|
|
23
24
|
Now, we can create a `Person`:
|
@@ -64,11 +65,21 @@ person.age # => 30
|
|
64
65
|
|
65
66
|
Except for mutability `Good::Value` and `Good::Record` have the same interface.
|
66
67
|
|
67
|
-
Don't forget, `Good::Value` and `Good::Record`
|
68
|
-
|
68
|
+
Don't forget, `Good::Value` and `Good::Record` create regular Ruby classes so
|
69
|
+
they get to have methods just like everybody else:
|
69
70
|
|
70
71
|
```ruby
|
71
|
-
Person < Good::Value.new(:name, :age)
|
72
|
+
class Person < Good::Value.new(:name, :age)
|
73
|
+
def introduction
|
74
|
+
"My name is #{name} and I'm #{age} years old"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
or, via block:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
Person = Good::Value.new(:name, :age) do
|
72
83
|
def introduction
|
73
84
|
"My name is #{name} and I'm #{age} years old"
|
74
85
|
end
|
@@ -76,14 +87,15 @@ end
|
|
76
87
|
```
|
77
88
|
|
78
89
|
Also, classes created with `Good::Value` and `Good::Record` have reasonable
|
79
|
-
|
90
|
+
implementations of `#==`, `#eql?` and `#hash`.
|
80
91
|
|
81
92
|
## Bonus Features
|
82
93
|
|
83
|
-
You can ask `Good::Value` and `Good::Record`
|
94
|
+
You can ask `Good::Value` and `Good::Record` instances about their structure
|
95
|
+
and contents:
|
84
96
|
|
85
97
|
```ruby
|
86
|
-
person.new(:name => "Miss Brahms", :age => 30)
|
98
|
+
person = Person.new(:name => "Miss Brahms", :age => 30)
|
87
99
|
|
88
100
|
Person::MEMBERS # => [:name, :age]
|
89
101
|
person.members # => [:name, :age]
|
@@ -91,7 +103,7 @@ person.values # => ["Miss Brahms", 30]
|
|
91
103
|
person.attributes # => {:name => "Miss Mrahms", :age => 30}
|
92
104
|
```
|
93
105
|
|
94
|
-
You can call `Person.coerce` to coerce input to a `Person` in the
|
106
|
+
You can call `Person.coerce` to coerce input to a `Person` in the following
|
95
107
|
ways:
|
96
108
|
|
97
109
|
```ruby
|
@@ -134,7 +146,7 @@ a number of these classes, the boilerplate code gets heavy pretty quickly. Plus
|
|
134
146
|
you'll probably do it wrong the first time (I certainly did).
|
135
147
|
|
136
148
|
It's worth noting that `Good` in no way seeks to become the foundation of your
|
137
|
-
domain model. The second a class outgrows
|
149
|
+
domain model. The second a class outgrows its `Good::Value` or `Good::Record`
|
138
150
|
roots, by all means you should remove `Good` from the picture and rely on pure
|
139
151
|
Ruby classes instead. `Good` helps you get started quickly by making a
|
140
152
|
particular pattern easy, but when your classes get more mature, it's time for
|
@@ -166,7 +178,7 @@ class Authenticator
|
|
166
178
|
end
|
167
179
|
|
168
180
|
def authentic?
|
169
|
-
...
|
181
|
+
# ...
|
170
182
|
end
|
171
183
|
end
|
172
184
|
```
|
@@ -194,22 +206,22 @@ class Authenticator
|
|
194
206
|
end
|
195
207
|
|
196
208
|
def authentic?
|
197
|
-
...
|
209
|
+
# ...
|
198
210
|
end
|
199
211
|
end
|
200
212
|
```
|
201
213
|
|
202
|
-
Say now that the
|
214
|
+
Say now that the Authenticator needs to pass the user's credentials to
|
203
215
|
another component (to log the attempt, for example), we are now in the enviable
|
204
216
|
position of having an object, with a well defined interface to pass around -
|
205
217
|
not a hash with implicit assumptions about its contents. Further, because of
|
206
218
|
the `.coerce` method we can now accept a hash at the boundary or a fully formed
|
207
219
|
`Credentials` object, it makes no difference to the `Authenticator`.
|
208
220
|
|
209
|
-
This
|
221
|
+
This evolution seems fairly common. To solve an immediate problem, a new
|
210
222
|
`Good::Value` class is created inside the namespace of an existing class, which
|
211
223
|
is at first desirable because it does not inflict this abstraction externally.
|
212
|
-
Then, as the class begins to interact with other
|
224
|
+
Then, as the class begins to interact with other components in the system,
|
213
225
|
this previously internal class can be made external and evolved into it's own
|
214
226
|
fully fledged domain object (perhaps shedding `Good` in the process). When you
|
215
227
|
start with a hash, it can be harder to spot the "missing" class.
|
@@ -232,3 +244,7 @@ Or install it yourself as:
|
|
232
244
|
|
233
245
|
bundle && bundle exec rake
|
234
246
|
|
247
|
+
## Credits
|
248
|
+
|
249
|
+
* Borrowed heavily from https://github.com/tcrayford/Values
|
250
|
+
|
data/lib/good.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class Good
|
2
|
-
VERSION = "0.1.
|
2
|
+
VERSION = "0.1.1"
|
3
3
|
|
4
4
|
class Value
|
5
5
|
def self.new(*members, &block)
|
@@ -15,7 +15,6 @@ class Good
|
|
15
15
|
|
16
16
|
def self.generate(mutable, *members, &block)
|
17
17
|
Class.new do
|
18
|
-
|
19
18
|
mutable ? attr_accessor(*members) : attr_reader(*members)
|
20
19
|
|
21
20
|
const_set(:MEMBERS, members.dup.freeze)
|
@@ -27,16 +26,19 @@ class Good
|
|
27
26
|
else raise TypeError, "Unable to coerce #{coercable.class} into #{self}"
|
28
27
|
end
|
29
28
|
end
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
|
30
|
+
if mutable
|
31
|
+
def initialize(attributes = {})
|
33
32
|
attributes.each { |k, v| send("#{k}=", v) }
|
34
|
-
|
35
|
-
|
33
|
+
end
|
34
|
+
else
|
35
|
+
def initialize(attributes = {})
|
36
|
+
attributes.each { |k, v| instance_variable_set(:"@#{k}", v) }
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
39
|
-
|
40
|
+
|
41
|
+
def attributes
|
40
42
|
{}.tap { |h| self.class::MEMBERS.each { |m| h[m] = send(m) } }
|
41
43
|
end
|
42
44
|
|
@@ -59,7 +61,7 @@ class Good
|
|
59
61
|
def eql?(other)
|
60
62
|
self == other
|
61
63
|
end
|
62
|
-
|
64
|
+
|
63
65
|
def hash
|
64
66
|
attributes.hash
|
65
67
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: good
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-05-
|
12
|
+
date: 2014-05-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|