code-box 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.md +54 -3
- data/lib/code-box/acts_as_code.rb +94 -36
- data/lib/code-box/code_attribute.rb +22 -8
- data/lib/code-box/version.rb +1 -1
- data/test/code-box/test_acts_as_code.rb +25 -3
- data/test/code-box/test_code_attribute.rb +59 -7
- data/test/resources/locale/de.yml +1 -1
- data/test/resources/locale/en.yml +1 -1
- data/test/resources/models.rb +57 -3
- data/test/resources/schema.rb +16 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -6,7 +6,7 @@ TODO: Write a gem description
|
|
6
6
|
|
7
7
|
Add this line to your application's Gemfile:
|
8
8
|
|
9
|
-
gem 'code-
|
9
|
+
gem 'code-box'
|
10
10
|
|
11
11
|
And then execute:
|
12
12
|
|
@@ -14,11 +14,62 @@ And then execute:
|
|
14
14
|
|
15
15
|
Or install it yourself as:
|
16
16
|
|
17
|
-
$ gem install code-
|
17
|
+
$ gem install code-box
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
-
|
21
|
+
### Specifying attributes as codes
|
22
|
+
|
23
|
+
There are cases you want to store 'named codes' instead artificial keys.
|
24
|
+
Codes make sense for stable references and better readability of the raw data.
|
25
|
+
|
26
|
+
There are several options to specify an attribute as a code:
|
27
|
+
1. Attribute is a code. There is no associated object involved, but simple I18n translation of the code
|
28
|
+
1. Attribute is a code. There exists a code object that is looked up on access.
|
29
|
+
1. Attribute is a code. There exists an AR code object that is looked up through AR association.
|
30
|
+
|
31
|
+
#### Lookup through I18n
|
32
|
+
|
33
|
+
Example
|
34
|
+
|
35
|
+
class Person
|
36
|
+
iclude CodeBox::CodeAttribute
|
37
|
+
|
38
|
+
attr_accessor :nationality_code
|
39
|
+
end
|
40
|
+
|
41
|
+
The include will create the following methods in Person:
|
42
|
+
|
43
|
+
#nationality Will return the nationality looked up through I18n on key: 'activerecord.values.person.nationality_code.de: Germany', where de would 'de' the nationality code.
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
#### Lookup through code object
|
48
|
+
|
49
|
+
Example
|
50
|
+
|
51
|
+
class Person
|
52
|
+
iclude CodeBox::CodeAttribute
|
53
|
+
|
54
|
+
attr_accessor :nationality_code, :lookup_type => :lookup
|
55
|
+
end
|
56
|
+
|
57
|
+
class Code::Nationality
|
58
|
+
attr_accessor :code, :name
|
59
|
+
|
60
|
+
def lookup(code)
|
61
|
+
return the correct Code::Nationality for the passed code
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
The include will create the following methods in Person:
|
66
|
+
|
67
|
+
#nationality Will return the nationality looked through lookup on the associated code object.
|
68
|
+
|
69
|
+
|
70
|
+
#### Lookup through associated AR Code Object
|
71
|
+
to be completed ...
|
72
|
+
|
22
73
|
|
23
74
|
## Contributing
|
24
75
|
|
@@ -6,67 +6,125 @@ module CodeBox
|
|
6
6
|
module ActsAsCode
|
7
7
|
@opts = {}
|
8
8
|
|
9
|
-
def self.[](options
|
9
|
+
def self.[](*options)
|
10
10
|
@opts = options
|
11
11
|
self
|
12
12
|
end
|
13
13
|
|
14
14
|
def self.included(base)
|
15
15
|
base.extend(ClassMethods)
|
16
|
-
base.acts_as_code(@opts
|
16
|
+
base.acts_as_code(*@opts) if @opts.size > 0
|
17
17
|
end
|
18
18
|
|
19
19
|
|
20
20
|
module ClassMethods
|
21
21
|
DefaultOptions = {
|
22
|
+
:model_type => :poro,
|
22
23
|
:code_attribute => :code,
|
23
24
|
:polymorphic => false,
|
24
|
-
:
|
25
|
-
:
|
25
|
+
:uniqueness_case_sensitive => true,
|
26
|
+
:position_attr => :position,
|
26
27
|
}
|
27
28
|
|
28
|
-
def acts_as_code(
|
29
|
+
def acts_as_code(*codes_and_or_options)
|
30
|
+
options = codes_and_or_options.extract_options!
|
31
|
+
codes = codes_and_or_options
|
29
32
|
opts = DefaultOptions.merge(options)
|
30
33
|
code_attr = opts[:code_attribute]
|
31
34
|
position_attr = opts[:position_attribute]
|
32
35
|
case_sensitive = opts[:uniqueness_case_sensitive]
|
36
|
+
model_type = opts.delete(:model_type)
|
33
37
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
code_attr.to_s
|
38
|
+
# Create a constant for each code
|
39
|
+
codes.each do |code|
|
40
|
+
const_set("Code#{code.to_s.camelize}", code)
|
38
41
|
end
|
39
42
|
|
40
|
-
|
41
|
-
validates_presence_of :#{code_attr}
|
42
|
-
validates_uniqueness_of :#{code_attr}#{opts[:polymorphic] ? ', :scope => :type' : ' '}, :case_sensitive => #{case_sensitive}
|
43
|
+
case model_type
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def self.for(code)
|
51
|
-
code_cache[code]
|
52
|
-
end
|
53
|
-
|
54
|
-
def hash
|
55
|
-
(self.class.name + '#' + #{code_attr}).hash
|
56
|
-
end
|
45
|
+
when :active_record
|
46
|
+
order_expression = if self.attribute_names.include?(position_attr) then
|
47
|
+
"coalesce(#{position_attr.to_s}, #{code_attr.to_s})"
|
48
|
+
else
|
49
|
+
code_attr.to_s
|
50
|
+
end
|
57
51
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
52
|
+
class_eval <<-CODE
|
53
|
+
validates_presence_of :#{code_attr}
|
54
|
+
validates_uniqueness_of :#{code_attr}#{opts[:polymorphic] ? ', :scope => :type' : ' '}, :case_sensitive => #{case_sensitive}
|
55
|
+
|
56
|
+
default_scope order('#{order_expression}')
|
57
|
+
|
58
|
+
def self.initialize_cache
|
59
|
+
all.inject({}) {|hash, obj| hash[obj.#{code_attr}] = obj; hash }
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.lookup(code)
|
63
|
+
code_cache[code]
|
64
|
+
end
|
65
|
+
|
66
|
+
def hash
|
67
|
+
(self.class.name + '#' + #{code_attr}).hash
|
68
|
+
end
|
69
|
+
|
70
|
+
def equal?(other)
|
71
|
+
other && is_a?(other.class) && #{code_attr} == other.#{code_attr}
|
72
|
+
end
|
73
|
+
|
74
|
+
def ==(other)
|
75
|
+
self.equal? other
|
76
|
+
end
|
77
|
+
CODE
|
78
|
+
|
79
|
+
instance_eval <<-CODE
|
80
|
+
class << self
|
81
|
+
def code_cache
|
82
|
+
@code_cache ||= initialize_cache
|
83
|
+
end
|
84
|
+
end
|
85
|
+
CODE
|
86
|
+
|
87
|
+
when :poro
|
88
|
+
order_attr = position_attr ? position_attr.to_s : code_attr.to_s
|
89
|
+
|
90
|
+
class_eval <<-CODE
|
91
|
+
def self.initialize_cache
|
92
|
+
all.inject({}) {|hash, obj| hash[obj.#{code_attr}] = obj; hash }
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.lookup(code)
|
96
|
+
code_cache[code]
|
97
|
+
end
|
98
|
+
|
99
|
+
def hash
|
100
|
+
(self.class.name + '#' + #{code_attr}).hash
|
101
|
+
end
|
102
|
+
|
103
|
+
def equal?(other)
|
104
|
+
other && is_a?(other.class) && #{code_attr} == other.#{code_attr}
|
105
|
+
end
|
106
|
+
|
107
|
+
def ==(other)
|
108
|
+
self.equal? other
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.all
|
112
|
+
raise "Sublass responsibility. You should implement '.all' returning all codes"
|
113
|
+
end
|
114
|
+
CODE
|
115
|
+
|
116
|
+
instance_eval <<-CODE
|
117
|
+
class << self
|
118
|
+
def code_cache
|
119
|
+
@code_cache ||= initialize_cache
|
120
|
+
end
|
121
|
+
end
|
122
|
+
CODE
|
123
|
+
|
124
|
+
else
|
125
|
+
raise ArgumentError, "'#{model_type}' is not a valid type. Use :active_record or :poro(default) instead"
|
126
|
+
end
|
62
127
|
|
63
|
-
instance_eval <<-CODE
|
64
|
-
class << self
|
65
|
-
def code_cache
|
66
|
-
@code_cache ||= initialize_cache
|
67
|
-
end
|
68
|
-
end
|
69
|
-
CODE
|
70
128
|
end
|
71
129
|
end
|
72
130
|
end
|
@@ -12,14 +12,14 @@ module CodeBox
|
|
12
12
|
module ClassMethods
|
13
13
|
DefaultOptions = {
|
14
14
|
:foreign_code_attribute => :code,
|
15
|
-
:
|
15
|
+
:lookup_type => :i18n,
|
16
16
|
:code_attribute_suffix => 'code'
|
17
17
|
}
|
18
18
|
|
19
19
|
def code_attribute(*code_names)
|
20
20
|
options = code_names.extract_options!
|
21
21
|
opts = DefaultOptions.merge(options)
|
22
|
-
lookup_type = opts.delete(:
|
22
|
+
lookup_type = opts.delete(:lookup_type)
|
23
23
|
code_attr_suffix = (opts.delete(:code_attribute_suffix) || "code").to_s
|
24
24
|
foreign_code_attr_name = opts.delete(:foreign_code_attribute)
|
25
25
|
|
@@ -29,11 +29,12 @@ module CodeBox
|
|
29
29
|
code_class_name = opts_copy.delete(:class_name) || "::Codes::#{code_name.to_s.camelize}"
|
30
30
|
|
31
31
|
case lookup_type
|
32
|
-
|
32
|
+
|
33
|
+
when :lookup
|
33
34
|
class_eval <<-RUBY_
|
34
35
|
# getter
|
35
36
|
def #{code_name}
|
36
|
-
code_class_name.
|
37
|
+
#{code_class_name}.lookup(#{code_attr_name})
|
37
38
|
end
|
38
39
|
|
39
40
|
# setter
|
@@ -42,26 +43,39 @@ module CodeBox
|
|
42
43
|
#{code_attr_name} = value
|
43
44
|
end
|
44
45
|
RUBY_
|
45
|
-
|
46
|
+
|
47
|
+
when :associated
|
46
48
|
association_options = opts_copy.merge({
|
47
49
|
:class_name => "#{code_class_name}",
|
48
50
|
:foreign_key => "#{code_attr_name}".to_sym,
|
49
51
|
:primary_key => "#{foreign_code_attr_name}"
|
50
52
|
})
|
51
53
|
belongs_to "#{code_name}".to_sym, association_options
|
54
|
+
|
52
55
|
when :i18n
|
53
56
|
class_eval <<-RUBY_
|
54
57
|
# getter
|
55
58
|
def #{code_name}(locale=I18n.locale)
|
56
|
-
|
57
|
-
|
58
|
-
I18n.t("activerecord.\#{self.class.name.underscore}.values.#{code_attr_name}.\#{value_key}", :locale => locale)
|
59
|
+
code = self.#{code_attr_name}
|
60
|
+
self.class.translate_#{code_attr_name}(code, locale)
|
59
61
|
end
|
60
62
|
|
61
63
|
# setter
|
62
64
|
def #{code_name}=(code)
|
63
65
|
raise "#{code_name} is a i18n code and can not be set. Use the the correct method '#{code_attr_name}='' instead."
|
64
66
|
end
|
67
|
+
|
68
|
+
# translator
|
69
|
+
class << self
|
70
|
+
def translate_#{code_attr_name}(code, locale=I18n.locale)
|
71
|
+
codes = Array(code)
|
72
|
+
tranlsated_codes = codes.map { |code|
|
73
|
+
code_key = code.nil? ? :null_value : code
|
74
|
+
I18n.t("activerecord.\#{self.name.underscore}.values.#{code_attr_name}.\#{code_key}", :locale => locale)
|
75
|
+
}
|
76
|
+
tranlsated_codes.size == 1 ? tranlsated_codes.first : tranlsated_codes
|
77
|
+
end
|
78
|
+
end
|
65
79
|
RUBY_
|
66
80
|
else
|
67
81
|
raise ArgumentError, "'#{lookup_type}' is not valid. Must be one of [:code_cache, :association]"
|
data/lib/code-box/version.rb
CHANGED
@@ -4,11 +4,33 @@ require 'helper'
|
|
4
4
|
|
5
5
|
class TestActsAsCode < Test::Unit::TestCase
|
6
6
|
|
7
|
-
def setup
|
8
7
|
|
8
|
+
def test_constants
|
9
|
+
assert_equal 'single', Codes::CivilStatus::CodeSingle
|
9
10
|
end
|
10
11
|
|
11
|
-
def
|
12
|
-
assert_equal
|
12
|
+
def test_constants
|
13
|
+
assert_equal 2, Codes::CivilStatus::all.size
|
14
|
+
|
15
|
+
assert_equal Codes::CivilStatus.lookup('single'), Codes::CivilStatus::all.first
|
16
|
+
assert_equal Codes::CivilStatus.lookup('married'), Codes::CivilStatus::all.last
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def test_ar_code_all
|
21
|
+
Codes::ArCode.create(:code => 'code_1', :name => "Code_1_name")
|
22
|
+
Codes::ArCode.create(:code => 'code_2', :name => "Code_2_name")
|
23
|
+
|
24
|
+
assert_equal 2, Codes::ArCode.all.size
|
13
25
|
end
|
26
|
+
|
27
|
+
|
28
|
+
def test_ar_code_lookup
|
29
|
+
code_1 = Codes::ArCode.create(:code => 'code_1', :name => "Code_1_name")
|
30
|
+
code_2 = Codes::ArCode.create(:code => 'code_2', :name => "Code_2_name")
|
31
|
+
|
32
|
+
assert_equal code_2, Codes::ArCode.lookup('code_2')
|
33
|
+
end
|
34
|
+
|
35
|
+
|
14
36
|
end
|
@@ -7,8 +7,10 @@ class TestCodeAttribute < Test::Unit::TestCase
|
|
7
7
|
def setup
|
8
8
|
end
|
9
9
|
|
10
|
-
|
11
|
-
|
10
|
+
# :type => :i18n -------------------------------------------------------------------
|
11
|
+
def test_code_attribute_i18n_lookup
|
12
|
+
obj = Codes::SampleClass.new(gender_code: 'f', country_iso: 'de')
|
13
|
+
I18n.locale =:en
|
12
14
|
|
13
15
|
assert_equal('f', obj.gender_code)
|
14
16
|
assert_equal('female', obj.gender)
|
@@ -22,14 +24,64 @@ class TestCodeAttribute < Test::Unit::TestCase
|
|
22
24
|
assert_equal('de', obj.country_iso)
|
23
25
|
assert_equal('Deutschland', obj.country(:de))
|
24
26
|
|
25
|
-
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_code_attribute_i18n_translator_with_single_code
|
30
|
+
I18n.locale = :de
|
31
|
+
translation = Codes::SampleClass.translate_gender_code('f')
|
32
|
+
assert_equal('weiblich', translation)
|
33
|
+
|
34
|
+
translation = Codes::SampleClass.translate_gender_code('f', :en)
|
35
|
+
assert_equal('female', translation)
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_code_attribute_i18n_translator_with_multiple_codes
|
39
|
+
I18n.locale = :de
|
40
|
+
translation = Codes::SampleClass.translate_gender_code(['f', 'm'])
|
26
41
|
|
27
|
-
|
28
|
-
|
42
|
+
assert translation.kind_of? Array
|
43
|
+
assert_equal('weiblich', translation.first)
|
44
|
+
assert_equal('männlich', translation.last)
|
29
45
|
|
30
|
-
|
31
|
-
|
46
|
+
translation = Codes::SampleClass.translate_gender_code(['f', 'm'], :en)
|
47
|
+
|
48
|
+
assert translation.kind_of? Array
|
49
|
+
assert_equal('female', translation.first)
|
50
|
+
assert_equal('male', translation.last)
|
32
51
|
end
|
33
52
|
|
34
53
|
|
54
|
+
# :type => :lookup -------------------------------------------------------------------
|
55
|
+
def test_code_attribute_lookup_default
|
56
|
+
code_single = Codes::CivilStatus.lookup('single')
|
57
|
+
code_married = Codes::CivilStatus.lookup('married')
|
58
|
+
|
59
|
+
code_client = Codes::SampleClass.new(:civil_status_code => 'single')
|
60
|
+
|
61
|
+
assert_equal('single', code_client.civil_status_code)
|
62
|
+
assert_equal(code_single, code_client.civil_status)
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_code_attribute_lookup_custom_code_name
|
66
|
+
code_child = Codes::AgerType.new('child')
|
67
|
+
code_teenager = Codes::AgerType.new('teenager')
|
68
|
+
|
69
|
+
code_client = Codes::SampleClass.new(:ager_type_code => 'child')
|
70
|
+
|
71
|
+
assert_equal('child', code_client.ager_type_code)
|
72
|
+
assert_equal(code_child, code_client.ager_type)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# :type => :associated ----------------------------------------------------------------
|
77
|
+
def test_code_attribute_lookup_associated
|
78
|
+
code_ch = Codes::Country.create(:code => 'CH', :name => 'Switzerland')
|
79
|
+
code_de = Codes::Country.create(:code => 'DE', :name => 'Germany')
|
80
|
+
|
81
|
+
code_client = Codes::SampleClass.new(:country_2_code => 'CH')
|
82
|
+
|
83
|
+
assert_equal('CH', code_client.country_2_code)
|
84
|
+
assert_equal(code_ch, code_client.country_2)
|
85
|
+
end
|
86
|
+
|
35
87
|
end
|
data/test/resources/models.rb
CHANGED
@@ -1,13 +1,67 @@
|
|
1
1
|
# ------------------------------------------------------
|
2
2
|
# Defined the respective AR Models
|
3
3
|
# ------------------------------------------------------
|
4
|
-
module
|
4
|
+
module Codes
|
5
|
+
|
5
6
|
class SampleClass < ActiveRecord::Base
|
6
|
-
self.table_name = :
|
7
|
+
self.table_name = :codes_sample_class
|
7
8
|
|
8
9
|
include CodeBox::CodeAttribute
|
9
10
|
|
11
|
+
# i18n codes
|
10
12
|
code_attribute :gender
|
11
|
-
code_attribute :country,
|
13
|
+
code_attribute :country, :lookup_type => :i18n, :code_attribute_suffix => 'iso'
|
14
|
+
|
15
|
+
# lookup codes
|
16
|
+
code_attribute :civil_status, :lookup_type => :lookup, :class_name => 'Codes::CivilStatus'
|
17
|
+
code_attribute :ager_type, :lookup_type => :lookup, :foreign_code_attribute => 'code_id'
|
18
|
+
|
19
|
+
code_attribute :country_2, :lookup_type => :associated, :class_name => 'Codes::Country'
|
20
|
+
end
|
21
|
+
|
22
|
+
class CivilStatus
|
23
|
+
include CodeBox::ActsAsCode['single', 'married', :type => :poro]
|
24
|
+
|
25
|
+
attr_accessor :code
|
26
|
+
|
27
|
+
def initialize(code)
|
28
|
+
@code = code
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.all
|
32
|
+
[
|
33
|
+
Codes::CivilStatus.new('single'),
|
34
|
+
Codes::CivilStatus.new('married'),
|
35
|
+
]
|
36
|
+
end
|
12
37
|
end
|
38
|
+
|
39
|
+
class AgerType
|
40
|
+
@@code_cache = {}
|
41
|
+
attr_accessor :code_id
|
42
|
+
|
43
|
+
def initialize(code)
|
44
|
+
@code_id = code
|
45
|
+
self.class.cache_code(self)
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.cache_code(code_obj)
|
49
|
+
@@code_cache[code_obj.code_id] = code_obj
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.lookup(code)
|
53
|
+
@@code_cache[code]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class Country < ActiveRecord::Base
|
58
|
+
self.table_name = :codes_country
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
class ArCode < ActiveRecord::Base
|
63
|
+
include CodeBox::ActsAsCode[:model_type => :active_record]
|
64
|
+
self.table_name = :codes_ar_code
|
65
|
+
end
|
66
|
+
|
13
67
|
end
|
data/test/resources/schema.rb
CHANGED
@@ -3,10 +3,25 @@
|
|
3
3
|
# ------------------------------------------------------
|
4
4
|
ActiveRecord::Schema.define(:version => 0) do
|
5
5
|
|
6
|
-
create_table :
|
6
|
+
create_table :codes_sample_class, :force => true do |t|
|
7
7
|
t.string :gender_code
|
8
8
|
t.string :country_iso
|
9
|
+
|
10
|
+
t.string :civil_status_code
|
11
|
+
t.string :ager_type_code
|
12
|
+
|
13
|
+
t.string :country_2_code
|
9
14
|
end
|
10
15
|
|
16
|
+
create_table :codes_country, :force => true do |t|
|
17
|
+
t.string :code
|
18
|
+
t.string :name
|
19
|
+
end
|
20
|
+
|
21
|
+
create_table :codes_ar_code, :force => true do |t|
|
22
|
+
t.string :code
|
23
|
+
t.string :name
|
24
|
+
t.integer :position
|
25
|
+
end
|
11
26
|
|
12
27
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code-box
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
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: 2012-06-
|
12
|
+
date: 2012-06-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|