encoder 0.0.1 → 0.0.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 +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
|