good 0.1.0 → 0.1.1
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.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
|