encoder 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +70 -0
- data/VERSION +1 -1
- data/lib/encoder.rb +30 -10
- data/test/database.rb +2 -2
- data/test/functional/encoder_test.rb +10 -0
- data/test/unit/encoder_test.rb +21 -0
- metadata +4 -4
- data/README +0 -44
data/README.markdown
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Encode
|
2
|
+
|
3
|
+
## About
|
4
|
+
|
5
|
+
Encode makes it easy use get meaningful descriptions from encoded values.
|
6
|
+
|
7
|
+
task.status = 'P'
|
8
|
+
task.status.decode # => 'Pending'
|
9
|
+
|
10
|
+
It also simplifies using constants to define encoded values and descriptions.
|
11
|
+
|
12
|
+
task.status = Task::Status::New
|
13
|
+
task.status # => 'N'
|
14
|
+
task.status.decode # => 'New'
|
15
|
+
|
16
|
+
## Observations
|
17
|
+
|
18
|
+
1. Magic numbers and characters make code more difficult to maintain because they don't have instrinsic meaning.
|
19
|
+
1. Magic strings, while meaningful, make code more difficult to maintain because minor variations can cause subtle problems.
|
20
|
+
1. Literal values scattered throughout a codebase increase refactoring time and effort.
|
21
|
+
1. Mapping between encoded values and descriptions shouldn't require a lot of effort.
|
22
|
+
|
23
|
+
## Using Encoder
|
24
|
+
|
25
|
+
### Setup
|
26
|
+
class Task < ActiveRecord::Base
|
27
|
+
include Encoder
|
28
|
+
|
29
|
+
code :status do
|
30
|
+
Status::New = "N"
|
31
|
+
Status::Pending = "P"
|
32
|
+
Status::Finished = "F"
|
33
|
+
Status::OverDue = "O"
|
34
|
+
end
|
35
|
+
|
36
|
+
code :priority do
|
37
|
+
Priority::High = 1
|
38
|
+
Priority::Low = 5
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
### Usage Examples
|
44
|
+
t = Task.new # => #<Task id: nil, status: nil, priority: nil>
|
45
|
+
|
46
|
+
# Using a constant to assign the value
|
47
|
+
t.status = Task::Status::New # => "N"
|
48
|
+
t.status # => "N"
|
49
|
+
t.status.decode # => "New"
|
50
|
+
|
51
|
+
# Using a decoded value to assign the value
|
52
|
+
t.priority = 1 # => 1
|
53
|
+
t.priority # => 1
|
54
|
+
t.priority.decode # => "High"
|
55
|
+
|
56
|
+
# Obtaining a list of the constant names defined for each attribute
|
57
|
+
Task::Status.constants # => ["Pending", "New", "OverDue", "Finished"]
|
58
|
+
Task::Priority.constants # => ["Low", "High"]
|
59
|
+
Task::Priority.values # => [5, 1]
|
60
|
+
Task::Priority.mapping # => { "Low" => 5, "High" => 1 }
|
61
|
+
|
62
|
+
|
63
|
+
## To Do
|
64
|
+
|
65
|
+
- Auto-mixin on init
|
66
|
+
- Choose a better name
|
67
|
+
- Use shoulda or rspec
|
68
|
+
- Obtain list of constant values (for use with validations)
|
69
|
+
|
70
|
+
Copyright (c) 2010 Jon Morton, released under the MIT license
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.2
|
data/lib/encoder.rb
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# Adding decode to all objects simplifies the complexity of handling
|
2
|
+
# Fixnums with decodable values.
|
3
|
+
class Object
|
4
|
+
attr_accessor :decode
|
5
|
+
end
|
6
|
+
|
1
7
|
module Encoder
|
2
8
|
|
3
9
|
def self.included(base)
|
@@ -29,11 +35,25 @@ module Encoder
|
|
29
35
|
def code(attribute_name, &block)
|
30
36
|
|
31
37
|
# Create a 'namespace' for constants
|
32
|
-
const_set(attribute_name.to_s.camelcase, Module.new)
|
38
|
+
namespace = const_set(attribute_name.to_s.camelcase, Module.new)
|
33
39
|
|
34
40
|
# Expecting constants to be assigned values.
|
35
41
|
yield
|
36
42
|
|
43
|
+
# Add methods for obtaining namespaced constants and their values.
|
44
|
+
namespace.module_eval do
|
45
|
+
def self.values
|
46
|
+
constants.map { |c| self.const_get(c) }
|
47
|
+
end
|
48
|
+
def self.mapping
|
49
|
+
constants.inject(Hash.new) do |memo, const|
|
50
|
+
memo[const] = self.const_get(const)
|
51
|
+
memo
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
37
57
|
# create setter for attribute
|
38
58
|
self.send(:define_method, "#{attribute_name}=") do |arg|
|
39
59
|
|
@@ -47,14 +67,14 @@ module Encoder
|
|
47
67
|
# Since the given value might not match the constant name exactly
|
48
68
|
# (because of case sensitivity or white space variations) we
|
49
69
|
# normalize it a bit.
|
50
|
-
normalized = arg && arg.gsub(/\s+/,'').downcase
|
70
|
+
normalized = arg && arg.to_s.gsub(/\s+/,'').downcase
|
51
71
|
|
52
72
|
# Now we look for a constant whose name or actual value match the
|
53
73
|
# normalized argument. We 'tap' whatever constant name is found
|
54
74
|
# and lookup the actual value.
|
55
75
|
namespace.constants.find do |const_name|
|
56
|
-
normalized == const_name.downcase ||
|
57
|
-
normalized == namespace.const_get(const_name).downcase
|
76
|
+
normalized == const_name.to_s.downcase ||
|
77
|
+
normalized == namespace.const_get(const_name).to_s.downcase
|
58
78
|
end.tap do |match|
|
59
79
|
encoded_value = namespace.const_get(match) if match
|
60
80
|
end
|
@@ -74,12 +94,12 @@ module Encoder
|
|
74
94
|
namespace.const_get(constant) == encoded_attribute
|
75
95
|
end.first
|
76
96
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
97
|
+
if decoded_value.is_a?(Fixnum)
|
98
|
+
encoded_attribute.instance_variable_set(:@decode, decoded_value)
|
99
|
+
elsif ! decoded_value.nil?
|
100
|
+
encoded_attribute.instance_variable_set(:@decode, decoded_value.underscore.titleize)
|
101
|
+
else
|
102
|
+
encoded_attribute.instance_variable_set(:@decode, nil)
|
83
103
|
end
|
84
104
|
|
85
105
|
return encoded_attribute
|
data/test/database.rb
CHANGED
@@ -21,4 +21,14 @@ class EncoderTest < ActiveSupport::TestCase
|
|
21
21
|
assert db_task.status.decode == 'New'
|
22
22
|
end
|
23
23
|
|
24
|
+
test "setting an attribute using mass assignment should encode a decoded value" do
|
25
|
+
task = ::Task.new({ :status => 'New' })
|
26
|
+
assert task.status == 'N'
|
27
|
+
end
|
28
|
+
|
29
|
+
test "setting an attribute using mass assignment should ignore a value" do
|
30
|
+
task = ::Task.new({ :status => 'Nothing Matches This' })
|
31
|
+
assert task.status == nil
|
32
|
+
end
|
33
|
+
|
24
34
|
end
|
data/test/unit/encoder_test.rb
CHANGED
@@ -66,4 +66,25 @@ class EncoderTest < ActiveSupport::TestCase
|
|
66
66
|
assert Task.const_defined?(:FooBar)
|
67
67
|
end
|
68
68
|
|
69
|
+
test "constants with fixnum values" do
|
70
|
+
t = ::Task.new
|
71
|
+
t.priority = 1
|
72
|
+
assert t.priority == Task::Priority::High
|
73
|
+
end
|
74
|
+
|
75
|
+
test "namespaced constants" do
|
76
|
+
assert Task::Priority.constants.all? { |v| ["High", "Low"].include?(v) }
|
77
|
+
end
|
78
|
+
|
79
|
+
test "namespaced values" do
|
80
|
+
assert Task::Priority.values.all? { |v| [1,5].include?(v) }
|
81
|
+
end
|
82
|
+
|
83
|
+
test "namespaced mapping" do
|
84
|
+
assert Task::Priority.mapping == {
|
85
|
+
"High" => 1,
|
86
|
+
"Low" => 5
|
87
|
+
}
|
88
|
+
end
|
89
|
+
|
69
90
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: encoder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Morton
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-04-06 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -20,11 +20,11 @@ executables: []
|
|
20
20
|
extensions: []
|
21
21
|
|
22
22
|
extra_rdoc_files:
|
23
|
-
- README
|
23
|
+
- README.markdown
|
24
24
|
files:
|
25
25
|
- .gitignore
|
26
26
|
- MIT-LICENSE
|
27
|
-
- README
|
27
|
+
- README.markdown
|
28
28
|
- Rakefile
|
29
29
|
- VERSION
|
30
30
|
- install.rb
|
data/README
DELETED
@@ -1,44 +0,0 @@
|
|
1
|
-
Encode
|
2
|
-
=========
|
3
|
-
|
4
|
-
Encode makes it easy to go from codes to meaningful strings.
|
5
|
-
|
6
|
-
Example
|
7
|
-
=======
|
8
|
-
|
9
|
-
Suppose you have a task in one of three possible states: pending, active, finished. When you store tasks, you'd like to have the state represented as P for pending, A for active, and F, for finished. However, you don't want to worry about the details of turning a state into a specific character.
|
10
|
-
|
11
|
-
t = Task.new('feed the cat')
|
12
|
-
|
13
|
-
# By default
|
14
|
-
t.status = Status::Pending
|
15
|
-
t.status # => 'P'
|
16
|
-
t.status.decode # => 'Pending'
|
17
|
-
|
18
|
-
# You'd probably encapsulate this in a method though wouldn't you?
|
19
|
-
t.finish!
|
20
|
-
|
21
|
-
# How should that be implemented though?
|
22
|
-
@status = 'f'
|
23
|
-
@status = 'finished'
|
24
|
-
@status = Task::Status::Finished
|
25
|
-
|
26
|
-
However, you want to store the state as a single character behind the scenes without having to define a bunch of custom getters or setters.
|
27
|
-
|
28
|
-
class Task
|
29
|
-
code :status do
|
30
|
-
Status::Pending = "P"
|
31
|
-
Status::Active = "A"
|
32
|
-
Status::Finished = "F"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
To Do
|
37
|
-
=====
|
38
|
-
|
39
|
-
[ ] Auto-mixin on init
|
40
|
-
[ ] Make a proper gem
|
41
|
-
[ ] Choose a better name
|
42
|
-
[ ] Use shoulda or rspec (maybe)
|
43
|
-
|
44
|
-
Copyright (c) 2010 Jon Morton, released under the MIT license
|