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 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
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
- encoded_attribute.instance_variable_set(:@decoding, decoded_value)
78
-
79
- class << encoded_attribute
80
- def decode
81
- @decoding && @decoding.underscore.titleize
82
- end
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
@@ -29,8 +29,8 @@ class Task < ActiveRecord::Base
29
29
  end
30
30
 
31
31
  code :priority do
32
- Priority::High = "H"
33
- Priority::Low = "L"
32
+ Priority::High = 1
33
+ Priority::Low = 5
34
34
  end
35
35
 
36
36
  end
@@ -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
@@ -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.1
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-03-20 00:00:00 -04:00
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