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 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